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