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