xref: /petsc/src/sys/webclient/box.c (revision 69f65dfb176f3d3e756fc44d2300fd6791726976)
1 
2 #include <petscwebclient.h>
3 PETSC_PRAGMA_DIAGNOSTIC_IGNORED_BEGIN("-Wdeprecated-declarations")
4 
5 /*
6    These variables identify the code as a PETSc application to Box.
7 
8    See -   https://stackoverflow.com/questions/4616553/using-oauth-in-free-open-source-software
9    Users can get their own application IDs - goto https://developer.box.com
10 
11 */
12 #define PETSC_BOX_CLIENT_ID "sse42nygt4zqgrdwi0luv79q1u1f0xza"
13 #define PETSC_BOX_CLIENT_ST "A0Dy4KgOYLB2JIYZqpbze4EzjeIiX5k4"
14 
15 #if defined(PETSC_HAVE_SAWS)
16   #include <mongoose.h>
17 
18 static volatile char *result = NULL;
19 
20 static int PetscBoxWebServer_Private(struct mg_connection *conn)
21 {
22   const struct mg_request_info *request_info = mg_get_request_info(conn);
23   result                                     = (char *)request_info->query_string;
24   return 1; /* Mongoose will now not handle the request */
25 }
26 
27 /*
28     Box can only return an authorization code to a Webserver, hence we need to start one up and wait for
29     the authorization code to arrive from Box
30 */
31 static PetscErrorCode PetscBoxStartWebServer_Private(void)
32 {
33   int                 optionsLen = 5;
34   const char         *options[optionsLen];
35   struct mg_callbacks callbacks;
36   struct mg_context  *ctx;
37   char                keyfile[PETSC_MAX_PATH_LEN];
38   PetscBool           exists;
39 
40   PetscFunctionBegin;
41   options[0] = "listening_ports";
42   options[1] = "8081s";
43 
44   PetscCall(PetscStrncpy(keyfile, "sslclient.pem", sizeof(keyfile)));
45   PetscCall(PetscTestFile(keyfile, 'r', &exists));
46   if (!exists) {
47     PetscCall(PetscGetHomeDirectory(keyfile, PETSC_MAX_PATH_LEN));
48     PetscCall(PetscStrlcat(keyfile, "/", sizeof(keyfile)));
49     PetscCall(PetscStrlcat(keyfile, "sslclient.pem", sizeof(keyfile)));
50     PetscCall(PetscTestFile(keyfile, 'r', &exists));
51     PetscCheck(exists, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate sslclient.pem file in current directory or home directory");
52   }
53 
54   options[2] = "ssl_certificate";
55   options[3] = keyfile;
56   options[4] = NULL;
57 
58   /* Prepare callbacks structure. We have only one callback, the rest are NULL. */
59   PetscCall(PetscMemzero(&callbacks, sizeof(callbacks)));
60   callbacks.begin_request = PetscBoxWebServer_Private;
61   ctx                     = mg_start(&callbacks, NULL, options);
62   PetscCheck(ctx, PETSC_COMM_SELF, PETSC_ERR_LIB, "Unable to start up webserver");
63   while (!result) { };
64   PetscFunctionReturn(PETSC_SUCCESS);
65 }
66 
67   #if defined(PETSC_HAVE_UNISTD_H)
68     #include <unistd.h>
69   #endif
70 
71 /*@C
72      PetscBoxAuthorize - Get authorization and refresh token for accessing Box drive from PETSc
73 
74    Not collective, only the first rank in `MPI_Comm` does anything
75 
76    Input Parameters:
77 +  comm - the MPI communicator
78 -  tokensize - size of the token arrays
79 
80    Output Parameters:
81 +  access_token - can be used with `PetscBoxUpload()` for this one session
82 -  refresh_token - can be used for ever to obtain new access_tokens with `PetscBoxRefresh()`, guard this like a password
83                    it gives access to your Box Drive
84 
85    Notes:
86     This call requires stdout and stdin access from process 0 on the MPI communicator
87 
88    You can run src/sys/webclient/tutorials/boxobtainrefreshtoken to get a refresh token and then in the future pass it to
89    PETSc programs with -box_refresh_token XXX
90 
91    This requires PETSc be installed using --with-saws or --download-saws
92 
93    Requires the user have created a self-signed ssl certificate with
94 
95 $    saws/CA.pl  -newcert  (using the passphrase of password)
96 $    cat newkey.pem newcert.pem > sslclient.pem
97 
98     and put the resulting file in either the current directory (with the application) or in the home directory. This seems kind of
99     silly but it was all I could figure out.
100 
101    Level: intermediate
102 
103 .seealso: `PetscBoxRefresh()`, `PetscBoxUpload()`, `PetscURLShorten()`
104 @*/
105 PetscErrorCode PetscBoxAuthorize(MPI_Comm comm, char access_token[], char refresh_token[], size_t tokensize)
106 {
107   SSL_CTX    *ctx;
108   SSL        *ssl;
109   int         sock;
110   char        buff[8 * 1024], body[1024];
111   PetscMPIInt rank;
112   PetscBool   flg, found;
113 
114   PetscFunctionBegin;
115   PetscCallMPI(MPI_Comm_rank(comm, &rank));
116   if (rank == 0) {
117     PetscCheck(isatty(fileno(PETSC_STDOUT)), PETSC_COMM_SELF, PETSC_ERR_USER, "Requires users input/output");
118     PetscCall(PetscPrintf(comm, "Cut and paste the following into your browser:\n\n"
119                                 "https://www.box.com/api/oauth2/authorize?"
120                                 "response_type=code&"
121                                 "client_id=" PETSC_BOX_CLIENT_ID "&state=PETScState"
122                                 "\n\n"));
123     PetscCall(PetscBoxStartWebServer_Private());
124     PetscCall(PetscStrbeginswith((const char *)result, "state=PETScState&code=", &flg));
125     PetscCheck(flg, PETSC_COMM_SELF, PETSC_ERR_LIB, "Did not get expected string from Box got %s", result);
126     PetscCall(PetscStrncpy(buff, (const char *)result + 22, sizeof(buff)));
127 
128     PetscCall(PetscSSLInitializeContext(&ctx));
129     PetscCall(PetscHTTPSConnect("www.box.com", 443, ctx, &sock, &ssl));
130     PetscCall(PetscStrncpy(body, "code=", sizeof(body)));
131     PetscCall(PetscStrlcat(body, buff, sizeof(body)));
132     PetscCall(PetscStrlcat(body, "&client_id=", sizeof(body)));
133     PetscCall(PetscStrlcat(body, PETSC_BOX_CLIENT_ID, sizeof(body)));
134     PetscCall(PetscStrlcat(body, "&client_secret=", sizeof(body)));
135     PetscCall(PetscStrlcat(body, PETSC_BOX_CLIENT_ST, sizeof(body)));
136     PetscCall(PetscStrlcat(body, "&grant_type=authorization_code", sizeof(body)));
137 
138     PetscCall(PetscHTTPSRequest("POST", "www.box.com/api/oauth2/token", NULL, "application/x-www-form-urlencoded", body, ssl, buff, sizeof(buff)));
139     PetscCall(PetscSSLDestroyContext(ctx));
140     close(sock);
141 
142     PetscCall(PetscPullJSONValue(buff, "access_token", access_token, tokensize, &found));
143     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Box did not return access token");
144     PetscCall(PetscPullJSONValue(buff, "refresh_token", refresh_token, tokensize, &found));
145     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Box did not return refresh token");
146 
147     PetscCall(PetscPrintf(comm, "Here is your Box refresh token, save it in a save place, in the future you can run PETSc\n"));
148     PetscCall(PetscPrintf(comm, "programs with the option -box_refresh_token %s\n", refresh_token));
149     PetscCall(PetscPrintf(comm, "to access Box Drive automatically\n"));
150   }
151   PetscFunctionReturn(PETSC_SUCCESS);
152 }
153 #endif
154 
155 /*@C
156      PetscBoxRefresh - Get a new authorization token for accessing Box drive from PETSc from a refresh token
157 
158    Not collective, only the first process in the `MPI_Comm` does anything
159 
160    Input Parameters:
161 +   comm - MPI communicator
162 .   refresh token - obtained with `PetscBoxAuthorize()`, if NULL PETSc will first look for one in the options data
163                     if not found it will call `PetscBoxAuthorize()`
164 -   tokensize - size of the output string access_token
165 
166    Output Parameters:
167 +   access_token - token that can be passed to `PetscBoxUpload()`
168 -   new_refresh_token - the old refresh token is no longer valid, not this is different than Google where the same refresh_token is used forever
169 
170    Level: intermediate
171 
172 .seealso: `PetscURLShorten()`, `PetscBoxAuthorize()`, `PetscBoxUpload()`
173 @*/
174 PetscErrorCode PetscBoxRefresh(MPI_Comm comm, const char refresh_token[], char access_token[], char new_refresh_token[], size_t tokensize)
175 {
176   SSL_CTX    *ctx;
177   SSL        *ssl;
178   int         sock;
179   char        buff[8 * 1024], body[1024];
180   PetscMPIInt rank;
181   char       *refreshtoken = (char *)refresh_token;
182   PetscBool   found;
183 
184   PetscFunctionBegin;
185   PetscCallMPI(MPI_Comm_rank(comm, &rank));
186   if (rank == 0) {
187     if (!refresh_token) {
188       PetscBool set;
189       PetscCall(PetscMalloc1(512, &refreshtoken));
190       PetscCall(PetscOptionsGetString(NULL, NULL, "-box_refresh_token", refreshtoken, sizeof(refreshtoken), &set));
191 #if defined(PETSC_HAVE_SAWS)
192       if (!set) {
193         PetscCall(PetscBoxAuthorize(comm, access_token, new_refresh_token, 512 * sizeof(char)));
194         PetscCall(PetscFree(refreshtoken));
195         PetscFunctionReturn(PETSC_SUCCESS);
196       }
197 #else
198       PetscCheck(set, PETSC_COMM_SELF, PETSC_ERR_LIB, "Must provide refresh token with -box_refresh_token XXX");
199 #endif
200     }
201     PetscCall(PetscSSLInitializeContext(&ctx));
202     PetscCall(PetscHTTPSConnect("www.box.com", 443, ctx, &sock, &ssl));
203     PetscCall(PetscStrncpy(body, "client_id=", sizeof(body)));
204     PetscCall(PetscStrlcat(body, PETSC_BOX_CLIENT_ID, sizeof(body)));
205     PetscCall(PetscStrlcat(body, "&client_secret=", sizeof(body)));
206     PetscCall(PetscStrlcat(body, PETSC_BOX_CLIENT_ST, sizeof(body)));
207     PetscCall(PetscStrlcat(body, "&refresh_token=", sizeof(body)));
208     PetscCall(PetscStrlcat(body, refreshtoken, sizeof(body)));
209     if (!refresh_token) PetscCall(PetscFree(refreshtoken));
210     PetscCall(PetscStrlcat(body, "&grant_type=refresh_token", sizeof(body)));
211 
212     PetscCall(PetscHTTPSRequest("POST", "www.box.com/api/oauth2/token", NULL, "application/x-www-form-urlencoded", body, ssl, buff, sizeof(buff)));
213     PetscCall(PetscSSLDestroyContext(ctx));
214     close(sock);
215 
216     PetscCall(PetscPullJSONValue(buff, "access_token", access_token, tokensize, &found));
217     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Box did not return access token");
218     PetscCall(PetscPullJSONValue(buff, "refresh_token", new_refresh_token, tokensize, &found));
219     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Box did not return refresh token");
220 
221     PetscCall(PetscPrintf(comm, "Here is your new Box refresh token, save it in a save place, in the future you can run PETSc\n"));
222     PetscCall(PetscPrintf(comm, "programs with the option -box_refresh_token %s\n", new_refresh_token));
223     PetscCall(PetscPrintf(comm, "to access Box Drive automatically\n"));
224   }
225   PetscFunctionReturn(PETSC_SUCCESS);
226 }
227 
228 #include <sys/stat.h>
229 
230 /*@C
231      PetscBoxUpload - Loads a file to the Box Drive
232 
233      This routine has not yet been written; it is just copied from Google Drive
234 
235      Not collective, only the first process in the `MPI_Comm` uploads the file
236 
237   Input Parameters:
238 +   comm - MPI communicator
239 .   access_token - obtained with `PetscBoxRefresh()`, pass NULL to have PETSc generate one
240 -   filename - file to upload; if you upload multiple times it will have different names each time on Box Drive
241 
242   Options Database Key:
243 .  -box_refresh_token XXX - the token value
244 
245   Usage Patterns:
246 .vb
247     With PETSc option -box_refresh_token XXX given
248     PetscBoxUpload(comm,NULL,filename);        will upload file with no user interaction
249 
250     Without PETSc option -box_refresh_token XXX given
251     PetscBoxUpload(comm,NULL,filename);        for first use will prompt user to authorize access to Box Drive with their processor
252 
253     With PETSc option -box_refresh_token  XXX given
254     PetscBoxRefresh(comm,NULL,access_token,sizeof(access_token));
255     PetscBoxUpload(comm,access_token,filename);
256 
257     With refresh token entered in some way by the user
258     PetscBoxRefresh(comm,refresh_token,access_token,sizeof(access_token));
259     PetscBoxUpload(comm,access_token,filename);
260 
261     PetscBoxAuthorize(comm,access_token,refresh_token,sizeof(access_token));
262     PetscBoxUpload(comm,access_token,filename);
263 .ve
264 
265    Level: intermediate
266 
267 .seealso: `PetscURLShorten()`, `PetscBoxAuthorize()`, `PetscBoxRefresh()`
268 @*/
269 PetscErrorCode PetscBoxUpload(MPI_Comm comm, const char access_token[], const char filename[])
270 {
271   SSL_CTX    *ctx;
272   SSL        *ssl;
273   int         sock;
274   char        head[1024], buff[8 * 1024], *body, *title;
275   PetscMPIInt rank;
276   struct stat sb;
277   size_t      len, blen, rd;
278   FILE       *fd;
279   int         err;
280 
281   PetscFunctionBegin;
282   PetscCallMPI(MPI_Comm_rank(comm, &rank));
283   if (rank == 0) {
284     PetscCall(PetscStrncpy(head, "Authorization: Bearer ", sizeof(head)));
285     PetscCall(PetscStrlcat(head, access_token, sizeof(head)));
286     PetscCall(PetscStrlcat(head, "\r\n", sizeof(head)));
287     PetscCall(PetscStrlcat(head, "uploadType: multipart\r\n", sizeof(head)));
288 
289     err = stat(filename, &sb);
290     PetscCheck(!err, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to stat file: %s", filename);
291     len = 1024 + sb.st_size;
292     PetscCall(PetscMalloc1(len, &body));
293     PetscCall(PetscStrncpy(body,
294                            "--foo_bar_baz\r\n"
295                            "Content-Type: application/json\r\n\r\n"
296                            "{",
297                            len));
298     PetscCall(PetscPushJSONValue(body, "title", filename, len));
299     PetscCall(PetscStrlcat(body, ",", len));
300     PetscCall(PetscPushJSONValue(body, "mimeType", "text.html", len));
301     PetscCall(PetscStrlcat(body, ",", len));
302     PetscCall(PetscPushJSONValue(body, "description", "a file", len));
303     PetscCall(PetscStrlcat(body,
304                            "}\r\n\r\n"
305                            "--foo_bar_baz\r\n"
306                            "Content-Type: text/html\r\n\r\n",
307                            len));
308     PetscCall(PetscStrlen(body, &blen));
309     fd = fopen(filename, "r");
310     PetscCheck(fd, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to open file: %s", filename);
311     rd = fread(body + blen, sizeof(unsigned char), sb.st_size, fd);
312     PetscCheck(rd == (size_t)sb.st_size, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to read entire file: %s %d %d", filename, (int)rd, (int)sb.st_size);
313     fclose(fd);
314     body[blen + rd] = 0;
315     PetscCall(PetscStrlcat(body,
316                            "\r\n\r\n"
317                            "--foo_bar_baz\r\n",
318                            len));
319     PetscCall(PetscSSLInitializeContext(&ctx));
320     PetscCall(PetscHTTPSConnect("www.boxapis.com", 443, ctx, &sock, &ssl));
321     PetscCall(PetscHTTPSRequest("POST", "www.boxapis.com/upload/drive/v2/files/", head, "multipart/related; boundary=\"foo_bar_baz\"", body, ssl, buff, sizeof(buff)));
322     PetscCall(PetscFree(body));
323     PetscCall(PetscSSLDestroyContext(ctx));
324     close(sock);
325     PetscCall(PetscStrstr(buff, "\"title\"", &title));
326     PetscCheck(title, PETSC_COMM_SELF, PETSC_ERR_LIB, "Upload of file %s failed", filename);
327   }
328   PetscFunctionReturn(PETSC_SUCCESS);
329 }
330