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