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