xref: /petsc/src/sys/webclient/box.c (revision 58d68138c660dfb4e9f5b03334792cd4f2ffd7cc)
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 process 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 @*/
105 PetscErrorCode PetscBoxAuthorize(MPI_Comm comm, char access_token[], char refresh_token[], size_t tokensize) {
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(PetscStrcpy(body, "code="));
130     PetscCall(PetscStrcat(body, buff));
131     PetscCall(PetscStrcat(body, "&client_id="));
132     PetscCall(PetscStrcat(body, PETSC_BOX_CLIENT_ID));
133     PetscCall(PetscStrcat(body, "&client_secret="));
134     PetscCall(PetscStrcat(body, PETSC_BOX_CLIENT_ST));
135     PetscCall(PetscStrcat(body, "&grant_type=authorization_code"));
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(0);
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: `PetscURLShorten()`, `PetscBoxAuthorize()`, `PetscBoxUpload()`
172 
173 @*/
174 PetscErrorCode PetscBoxRefresh(MPI_Comm comm, const char refresh_token[], char access_token[], char new_refresh_token[], size_t tokensize) {
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(0);
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(PetscStrcpy(body, "client_id="));
203     PetscCall(PetscStrcat(body, PETSC_BOX_CLIENT_ID));
204     PetscCall(PetscStrcat(body, "&client_secret="));
205     PetscCall(PetscStrcat(body, PETSC_BOX_CLIENT_ST));
206     PetscCall(PetscStrcat(body, "&refresh_token="));
207     PetscCall(PetscStrcat(body, refreshtoken));
208     if (!refresh_token) PetscCall(PetscFree(refreshtoken));
209     PetscCall(PetscStrcat(body, "&grant_type=refresh_token"));
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(0);
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:
242 .  -box_refresh_token XXX - the token value
243 
244   Usage Patterns:
245     With PETSc option -box_refresh_token XXX given
246     PetscBoxUpload(comm,NULL,filename);        will upload file with no user interaction
247 
248     Without PETSc option -box_refresh_token XXX given
249     PetscBoxUpload(comm,NULL,filename);        for first use will prompt user to authorize access to Box Drive with their processor
250 
251     With PETSc option -box_refresh_token  XXX given
252     PetscBoxRefresh(comm,NULL,access_token,sizeof(access_token));
253     PetscBoxUpload(comm,access_token,filename);
254 
255     With refresh token entered in some way by the user
256     PetscBoxRefresh(comm,refresh_token,access_token,sizeof(access_token));
257     PetscBoxUpload(comm,access_token,filename);
258 
259     PetscBoxAuthorize(comm,access_token,refresh_token,sizeof(access_token));
260     PetscBoxUpload(comm,access_token,filename);
261 
262    Level: intermediate
263 
264 .seealso: `PetscURLShorten()`, `PetscBoxAuthorize()`, `PetscBoxRefresh()`
265 
266 @*/
267 PetscErrorCode PetscBoxUpload(MPI_Comm comm, const char access_token[], const char filename[]) {
268   SSL_CTX    *ctx;
269   SSL        *ssl;
270   int         sock;
271   char        head[1024], buff[8 * 1024], *body, *title;
272   PetscMPIInt rank;
273   struct stat sb;
274   size_t      len, blen, rd;
275   FILE       *fd;
276   int         err;
277 
278   PetscFunctionBegin;
279   PetscCallMPI(MPI_Comm_rank(comm, &rank));
280   if (rank == 0) {
281     PetscCall(PetscStrcpy(head, "Authorization: Bearer "));
282     PetscCall(PetscStrcat(head, access_token));
283     PetscCall(PetscStrcat(head, "\r\n"));
284     PetscCall(PetscStrcat(head, "uploadType: multipart\r\n"));
285 
286     err = stat(filename, &sb);
287     PetscCheck(!err, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to stat file: %s", filename);
288     len = 1024 + sb.st_size;
289     PetscCall(PetscMalloc1(len, &body));
290     PetscCall(PetscStrcpy(body, "--foo_bar_baz\r\n"
291                                 "Content-Type: application/json\r\n\r\n"
292                                 "{"));
293     PetscCall(PetscPushJSONValue(body, "title", filename, len));
294     PetscCall(PetscStrcat(body, ","));
295     PetscCall(PetscPushJSONValue(body, "mimeType", "text.html", len));
296     PetscCall(PetscStrcat(body, ","));
297     PetscCall(PetscPushJSONValue(body, "description", "a file", len));
298     PetscCall(PetscStrcat(body, "}\r\n\r\n"
299                                 "--foo_bar_baz\r\n"
300                                 "Content-Type: text/html\r\n\r\n"));
301     PetscCall(PetscStrlen(body, &blen));
302     fd = fopen(filename, "r");
303     PetscCheck(fd, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to open file: %s", filename);
304     rd = fread(body + blen, sizeof(unsigned char), sb.st_size, fd);
305     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);
306     fclose(fd);
307     body[blen + rd] = 0;
308     PetscCall(PetscStrcat(body, "\r\n\r\n"
309                                 "--foo_bar_baz\r\n"));
310     PetscCall(PetscSSLInitializeContext(&ctx));
311     PetscCall(PetscHTTPSConnect("www.boxapis.com", 443, ctx, &sock, &ssl));
312     PetscCall(PetscHTTPSRequest("POST", "www.boxapis.com/upload/drive/v2/files/", head, "multipart/related; boundary=\"foo_bar_baz\"", body, ssl, buff, sizeof(buff)));
313     PetscCall(PetscFree(body));
314     PetscCall(PetscSSLDestroyContext(ctx));
315     close(sock);
316     PetscCall(PetscStrstr(buff, "\"title\"", &title));
317     PetscCheck(title, PETSC_COMM_SELF, PETSC_ERR_LIB, "Upload of file %s failed", filename);
318   }
319   PetscFunctionReturn(0);
320 }
321