xref: /petsc/src/sys/webclient/box.c (revision 4dfa11a44d5adf2389f1d3acbc8f3c1116dc6c3a)
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   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   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(PetscStrcpy(keyfile, "sslclient.pem"));
44   PetscCall(PetscTestFile(keyfile, 'r', &exists));
45   if (!exists) {
46     PetscCall(PetscGetHomeDirectory(keyfile, PETSC_MAX_PATH_LEN));
47     PetscCall(PetscStrcat(keyfile, "/"));
48     PetscCall(PetscStrcat(keyfile, "sslclient.pem"));
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(0);
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()`, guard this like a password
82                    it gives access to your Box Drive
83 
84    Notes:
85     This call requires stdout and stdin access from process 0 on the MPI communicator
86 
87    You can run src/sys/webclient/tutorials/boxobtainrefreshtoken to get a refresh token and then in the future pass it to
88    PETSc programs with -box_refresh_token XXX
89 
90    This requires PETSc be installed using --with-saws or --download-saws
91 
92    Requires the user have created a self-signed ssl certificate with
93 
94 $    saws/CA.pl  -newcert  (using the passphrase of password)
95 $    cat newkey.pem newcert.pem > sslclient.pem
96 
97     and put the resulting file in either the current directory (with the application) or in the home directory. This seems kind of
98     silly but it was all I could figure out.
99 
100    Level: intermediate
101 
102 .seealso: `PetscBoxRefresh()`, `PetscBoxUpload()`, `PetscURLShorten()`
103 @*/
104 PetscErrorCode PetscBoxAuthorize(MPI_Comm comm, char access_token[], char refresh_token[], size_t tokensize) {
105   SSL_CTX    *ctx;
106   SSL        *ssl;
107   int         sock;
108   char        buff[8 * 1024], body[1024];
109   PetscMPIInt rank;
110   PetscBool   flg, found;
111 
112   PetscFunctionBegin;
113   PetscCallMPI(MPI_Comm_rank(comm, &rank));
114   if (rank == 0) {
115     PetscCheck(isatty(fileno(PETSC_STDOUT)), PETSC_COMM_SELF, PETSC_ERR_USER, "Requires users input/output");
116     PetscCall(PetscPrintf(comm, "Cut and paste the following into your browser:\n\n"
117                                 "https://www.box.com/api/oauth2/authorize?"
118                                 "response_type=code&"
119                                 "client_id=" PETSC_BOX_CLIENT_ID "&state=PETScState"
120                                 "\n\n"));
121     PetscCall(PetscBoxStartWebServer_Private());
122     PetscCall(PetscStrbeginswith((const char *)result, "state=PETScState&code=", &flg));
123     PetscCheck(flg, PETSC_COMM_SELF, PETSC_ERR_LIB, "Did not get expected string from Box got %s", result);
124     PetscCall(PetscStrncpy(buff, (const char *)result + 22, sizeof(buff)));
125 
126     PetscCall(PetscSSLInitializeContext(&ctx));
127     PetscCall(PetscHTTPSConnect("www.box.com", 443, ctx, &sock, &ssl));
128     PetscCall(PetscStrcpy(body, "code="));
129     PetscCall(PetscStrcat(body, buff));
130     PetscCall(PetscStrcat(body, "&client_id="));
131     PetscCall(PetscStrcat(body, PETSC_BOX_CLIENT_ID));
132     PetscCall(PetscStrcat(body, "&client_secret="));
133     PetscCall(PetscStrcat(body, PETSC_BOX_CLIENT_ST));
134     PetscCall(PetscStrcat(body, "&grant_type=authorization_code"));
135 
136     PetscCall(PetscHTTPSRequest("POST", "www.box.com/api/oauth2/token", NULL, "application/x-www-form-urlencoded", body, ssl, buff, sizeof(buff)));
137     PetscCall(PetscSSLDestroyContext(ctx));
138     close(sock);
139 
140     PetscCall(PetscPullJSONValue(buff, "access_token", access_token, tokensize, &found));
141     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Box did not return access token");
142     PetscCall(PetscPullJSONValue(buff, "refresh_token", refresh_token, tokensize, &found));
143     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Box did not return refresh token");
144 
145     PetscCall(PetscPrintf(comm, "Here is your Box refresh token, save it in a save place, in the future you can run PETSc\n"));
146     PetscCall(PetscPrintf(comm, "programs with the option -box_refresh_token %s\n", refresh_token));
147     PetscCall(PetscPrintf(comm, "to access Box Drive automatically\n"));
148   }
149   PetscFunctionReturn(0);
150 }
151 #endif
152 
153 /*@C
154      PetscBoxRefresh - Get a new authorization token for accessing Box drive from PETSc from a refresh token
155 
156    Not collective, only the first process in the `MPI_Comm` does anything
157 
158    Input Parameters:
159 +   comm - MPI communicator
160 .   refresh token - obtained with `PetscBoxAuthorize()`, if NULL PETSc will first look for one in the options data
161                     if not found it will call `PetscBoxAuthorize()`
162 -   tokensize - size of the output string access_token
163 
164    Output Parameters:
165 +   access_token - token that can be passed to `PetscBoxUpload()`
166 -   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
167 
168    Level: intermediate
169 
170 .seealso: `PetscURLShorten()`, `PetscBoxAuthorize()`, `PetscBoxUpload()`
171 @*/
172 PetscErrorCode PetscBoxRefresh(MPI_Comm comm, const char refresh_token[], char access_token[], char new_refresh_token[], size_t tokensize) {
173   SSL_CTX    *ctx;
174   SSL        *ssl;
175   int         sock;
176   char        buff[8 * 1024], body[1024];
177   PetscMPIInt rank;
178   char       *refreshtoken = (char *)refresh_token;
179   PetscBool   found;
180 
181   PetscFunctionBegin;
182   PetscCallMPI(MPI_Comm_rank(comm, &rank));
183   if (rank == 0) {
184     if (!refresh_token) {
185       PetscBool set;
186       PetscCall(PetscMalloc1(512, &refreshtoken));
187       PetscCall(PetscOptionsGetString(NULL, NULL, "-box_refresh_token", refreshtoken, sizeof(refreshtoken), &set));
188 #if defined(PETSC_HAVE_SAWS)
189       if (!set) {
190         PetscCall(PetscBoxAuthorize(comm, access_token, new_refresh_token, 512 * sizeof(char)));
191         PetscCall(PetscFree(refreshtoken));
192         PetscFunctionReturn(0);
193       }
194 #else
195       PetscCheck(set, PETSC_COMM_SELF, PETSC_ERR_LIB, "Must provide refresh token with -box_refresh_token XXX");
196 #endif
197     }
198     PetscCall(PetscSSLInitializeContext(&ctx));
199     PetscCall(PetscHTTPSConnect("www.box.com", 443, ctx, &sock, &ssl));
200     PetscCall(PetscStrcpy(body, "client_id="));
201     PetscCall(PetscStrcat(body, PETSC_BOX_CLIENT_ID));
202     PetscCall(PetscStrcat(body, "&client_secret="));
203     PetscCall(PetscStrcat(body, PETSC_BOX_CLIENT_ST));
204     PetscCall(PetscStrcat(body, "&refresh_token="));
205     PetscCall(PetscStrcat(body, refreshtoken));
206     if (!refresh_token) PetscCall(PetscFree(refreshtoken));
207     PetscCall(PetscStrcat(body, "&grant_type=refresh_token"));
208 
209     PetscCall(PetscHTTPSRequest("POST", "www.box.com/api/oauth2/token", NULL, "application/x-www-form-urlencoded", body, ssl, buff, sizeof(buff)));
210     PetscCall(PetscSSLDestroyContext(ctx));
211     close(sock);
212 
213     PetscCall(PetscPullJSONValue(buff, "access_token", access_token, tokensize, &found));
214     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Box did not return access token");
215     PetscCall(PetscPullJSONValue(buff, "refresh_token", new_refresh_token, tokensize, &found));
216     PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_LIB, "Box did not return refresh token");
217 
218     PetscCall(PetscPrintf(comm, "Here is your new Box refresh token, save it in a save place, in the future you can run PETSc\n"));
219     PetscCall(PetscPrintf(comm, "programs with the option -box_refresh_token %s\n", new_refresh_token));
220     PetscCall(PetscPrintf(comm, "to access Box Drive automatically\n"));
221   }
222   PetscFunctionReturn(0);
223 }
224 
225 #include <sys/stat.h>
226 
227 /*@C
228      PetscBoxUpload - Loads a file to the Box Drive
229 
230      This routine has not yet been written; it is just copied from Google Drive
231 
232      Not collective, only the first process in the `MPI_Comm` uploads the file
233 
234   Input Parameters:
235 +   comm - MPI communicator
236 .   access_token - obtained with `PetscBoxRefresh()`, pass NULL to have PETSc generate one
237 -   filename - file to upload; if you upload multiple times it will have different names each time on Box Drive
238 
239   Options Database Key:
240 .  -box_refresh_token XXX - the token value
241 
242   Usage Patterns:
243 .vb
244     With PETSc option -box_refresh_token XXX given
245     PetscBoxUpload(comm,NULL,filename);        will upload file with no user interaction
246 
247     Without PETSc option -box_refresh_token XXX given
248     PetscBoxUpload(comm,NULL,filename);        for first use will prompt user to authorize access to Box Drive with their processor
249 
250     With PETSc option -box_refresh_token  XXX given
251     PetscBoxRefresh(comm,NULL,access_token,sizeof(access_token));
252     PetscBoxUpload(comm,access_token,filename);
253 
254     With refresh token entered in some way by the user
255     PetscBoxRefresh(comm,refresh_token,access_token,sizeof(access_token));
256     PetscBoxUpload(comm,access_token,filename);
257 
258     PetscBoxAuthorize(comm,access_token,refresh_token,sizeof(access_token));
259     PetscBoxUpload(comm,access_token,filename);
260 .ve
261 
262    Level: intermediate
263 
264 .seealso: `PetscURLShorten()`, `PetscBoxAuthorize()`, `PetscBoxRefresh()`
265 @*/
266 PetscErrorCode PetscBoxUpload(MPI_Comm comm, const char access_token[], const char filename[]) {
267   SSL_CTX    *ctx;
268   SSL        *ssl;
269   int         sock;
270   char        head[1024], buff[8 * 1024], *body, *title;
271   PetscMPIInt rank;
272   struct stat sb;
273   size_t      len, blen, rd;
274   FILE       *fd;
275   int         err;
276 
277   PetscFunctionBegin;
278   PetscCallMPI(MPI_Comm_rank(comm, &rank));
279   if (rank == 0) {
280     PetscCall(PetscStrcpy(head, "Authorization: Bearer "));
281     PetscCall(PetscStrcat(head, access_token));
282     PetscCall(PetscStrcat(head, "\r\n"));
283     PetscCall(PetscStrcat(head, "uploadType: multipart\r\n"));
284 
285     err = stat(filename, &sb);
286     PetscCheck(!err, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to stat file: %s", filename);
287     len = 1024 + sb.st_size;
288     PetscCall(PetscMalloc1(len, &body));
289     PetscCall(PetscStrcpy(body, "--foo_bar_baz\r\n"
290                                 "Content-Type: application/json\r\n\r\n"
291                                 "{"));
292     PetscCall(PetscPushJSONValue(body, "title", filename, len));
293     PetscCall(PetscStrcat(body, ","));
294     PetscCall(PetscPushJSONValue(body, "mimeType", "text.html", len));
295     PetscCall(PetscStrcat(body, ","));
296     PetscCall(PetscPushJSONValue(body, "description", "a file", len));
297     PetscCall(PetscStrcat(body, "}\r\n\r\n"
298                                 "--foo_bar_baz\r\n"
299                                 "Content-Type: text/html\r\n\r\n"));
300     PetscCall(PetscStrlen(body, &blen));
301     fd = fopen(filename, "r");
302     PetscCheck(fd, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to open file: %s", filename);
303     rd = fread(body + blen, sizeof(unsigned char), sb.st_size, fd);
304     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);
305     fclose(fd);
306     body[blen + rd] = 0;
307     PetscCall(PetscStrcat(body, "\r\n\r\n"
308                                 "--foo_bar_baz\r\n"));
309     PetscCall(PetscSSLInitializeContext(&ctx));
310     PetscCall(PetscHTTPSConnect("www.boxapis.com", 443, ctx, &sock, &ssl));
311     PetscCall(PetscHTTPSRequest("POST", "www.boxapis.com/upload/drive/v2/files/", head, "multipart/related; boundary=\"foo_bar_baz\"", body, ssl, buff, sizeof(buff)));
312     PetscCall(PetscFree(body));
313     PetscCall(PetscSSLDestroyContext(ctx));
314     close(sock);
315     PetscCall(PetscStrstr(buff, "\"title\"", &title));
316     PetscCheck(title, PETSC_COMM_SELF, PETSC_ERR_LIB, "Upload of file %s failed", filename);
317   }
318   PetscFunctionReturn(0);
319 }
320