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
PetscBoxWebServer_Private(struct mg_connection * conn)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 */
PetscBoxStartWebServer_Private(void)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 @*/
PetscBoxAuthorize(MPI_Comm comm,char access_token[],char refresh_token[],size_t tokensize)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 @*/
PetscBoxRefresh(MPI_Comm comm,const char refresh_token[],char access_token[],char new_refresh_token[],size_t tokensize)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 @*/
PetscBoxUpload(MPI_Comm comm,const char access_token[],const char filename[])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