Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Common Portability Library
4 : * Purpose: Google OAuth2 Authentication Services
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault, even.rouault at spatialys.com
7 : ******************************************************************************
8 : * Copyright (c) 2013, Frank Warmerdam
9 : * Copyright (c) 2017, Even Rouault
10 : * Copyright (c) 2017, Planet Labs
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_http.h"
16 : #include "cpl_port.h"
17 : #include "cpl_sha256.h"
18 :
19 : #include <cstring>
20 :
21 : #include "cpl_conv.h"
22 : #include "cpl_error.h"
23 : #include "cpl_json.h"
24 : #include "cpl_string.h"
25 :
26 : /* ==================================================================== */
27 : /* Values related to OAuth2 authorization to use fusion */
28 : /* tables. Many of these values are related to the */
29 : /* gdalautotest@gmail.com account for GDAL managed by Even */
30 : /* Rouault and Frank Warmerdam. Some information about OAuth2 */
31 : /* as managed by that account can be found at the following url */
32 : /* when logged in as gdalautotest@gmail.com: */
33 : /* */
34 : /* https://code.google.com/apis/console/#project:265656308688:access*/
35 : /* */
36 : /* Applications wanting to use their own client id and secret */
37 : /* can set the following configuration options: */
38 : /* - GOA2_CLIENT_ID */
39 : /* - GOA2_CLIENT_SECRET */
40 : /* ==================================================================== */
41 : #define GDAL_CLIENT_ID "265656308688.apps.googleusercontent.com"
42 : #define GDAL_CLIENT_SECRET "0IbTUDOYzaL6vnIdWTuQnvLz"
43 :
44 : #define GOOGLE_AUTH_URL "https://accounts.google.com/o/oauth2"
45 :
46 : /************************************************************************/
47 : /* GOA2GetAuthorizationURL() */
48 : /************************************************************************/
49 :
50 : /**
51 : * Return authorization url for a given scope.
52 : *
53 : * Returns the URL that a user should visit, and use for authentication
54 : * in order to get an "auth token" indicating their willingness to use a
55 : * service.
56 : *
57 : * Note that when the user visits this url they will be asked to login
58 : * (using a google/gmail/etc) account, and to authorize use of the
59 : * requested scope for the application "GDAL/OGR". Once they have done
60 : * so, they will be presented with a lengthy string they should "enter
61 : * into their application". This is the "auth token" to be passed to
62 : * GOA2GetRefreshToken(). The "auth token" can only be used once.
63 : *
64 : * This function should never fail.
65 : *
66 : * @param pszScope the service being requested, not yet URL encoded, such as
67 : * "https://www.googleapis.com/auth/fusiontables".
68 : *
69 : * @return the URL to visit - should be freed with CPLFree().
70 : */
71 :
72 0 : char *GOA2GetAuthorizationURL(const char *pszScope)
73 :
74 : {
75 0 : CPLString osScope;
76 0 : osScope.Seize(CPLEscapeString(pszScope, -1, CPLES_URL));
77 :
78 0 : CPLString osURL;
79 : osURL.Printf("%s/auth?scope=%s&redirect_uri=urn:ietf:wg:oauth:2.0:oob&"
80 : "response_type=code&client_id=%s",
81 : GOOGLE_AUTH_URL, osScope.c_str(),
82 0 : CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID));
83 0 : return CPLStrdup(osURL);
84 : }
85 :
86 : /************************************************************************/
87 : /* GOA2GetRefreshToken() */
88 : /************************************************************************/
89 :
90 : /**
91 : * Turn Auth Token into a Refresh Token.
92 : *
93 : * A one time "auth token" provided by the user is turned into a
94 : * reusable "refresh token" using a google oauth2 web service.
95 : *
96 : * A CPLError will be reported if the translation fails for some reason.
97 : * Common reasons include the auth token already having been used before,
98 : * it not being appropriate for the passed scope and configured client api
99 : * or http connection problems. NULL is returned on error.
100 : *
101 : * @param pszAuthToken the authorization token from the user.
102 : * @param pszScope the scope for which it is valid.
103 : *
104 : * @return refresh token, to be freed with CPLFree(), null on failure.
105 : */
106 :
107 0 : char CPL_DLL *GOA2GetRefreshToken(const char *pszAuthToken,
108 : const char *pszScope)
109 :
110 : {
111 : /* -------------------------------------------------------------------- */
112 : /* Prepare request. */
113 : /* -------------------------------------------------------------------- */
114 0 : CPLString osItem;
115 0 : CPLStringList oOptions;
116 :
117 : oOptions.AddString(
118 0 : "HEADERS=Content-Type: application/x-www-form-urlencoded");
119 :
120 : osItem.Printf("POSTFIELDS="
121 : "code=%s"
122 : "&client_id=%s"
123 : "&client_secret=%s"
124 : "&redirect_uri=urn:ietf:wg:oauth:2.0:oob"
125 : "&grant_type=authorization_code",
126 : pszAuthToken,
127 : CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID),
128 0 : CPLGetConfigOption("GOA2_CLIENT_SECRET", GDAL_CLIENT_SECRET));
129 0 : oOptions.AddString(osItem);
130 :
131 : /* -------------------------------------------------------------------- */
132 : /* Submit request by HTTP. */
133 : /* -------------------------------------------------------------------- */
134 0 : CPLHTTPResult *psResult = CPLHTTPFetch(
135 : CPLGetConfigOption("GOA2_AUTH_URL_TOKEN", GOOGLE_AUTH_URL "/token"),
136 0 : oOptions);
137 :
138 0 : if (psResult == nullptr)
139 0 : return nullptr;
140 :
141 : /* -------------------------------------------------------------------- */
142 : /* One common mistake is to try and reuse the auth token. */
143 : /* After the first use it will return invalid_grant. */
144 : /* -------------------------------------------------------------------- */
145 0 : if (psResult->pabyData != nullptr &&
146 0 : strstr(reinterpret_cast<char *>(psResult->pabyData), "invalid_grant") !=
147 : nullptr)
148 : {
149 0 : CPLHTTPDestroyResult(psResult);
150 0 : if (pszScope == nullptr)
151 : {
152 0 : CPLError(
153 : CE_Failure, CPLE_AppDefined,
154 : "Attempt to use a OAuth2 authorization code multiple times. "
155 : "Use GOA2GetAuthorizationURL(scope) with a valid scope to "
156 : "request a fresh authorization token.");
157 : }
158 : else
159 : {
160 0 : CPLString osURL;
161 0 : osURL.Seize(GOA2GetAuthorizationURL(pszScope));
162 0 : CPLError(
163 : CE_Failure, CPLE_AppDefined,
164 : "Attempt to use a OAuth2 authorization code multiple times. "
165 : "Request a fresh authorization token at %s.",
166 : osURL.c_str());
167 : }
168 0 : return nullptr;
169 : }
170 :
171 0 : if (psResult->pabyData == nullptr || psResult->pszErrBuf != nullptr)
172 : {
173 0 : if (psResult->pszErrBuf != nullptr)
174 0 : CPLDebug("GOA2", "%s", psResult->pszErrBuf);
175 0 : if (psResult->pabyData != nullptr)
176 0 : CPLDebug("GOA2", "%s", psResult->pabyData);
177 :
178 0 : CPLError(CE_Failure, CPLE_AppDefined,
179 : "Fetching OAuth2 access code from auth code failed.");
180 0 : CPLHTTPDestroyResult(psResult);
181 0 : return nullptr;
182 : }
183 :
184 0 : CPLDebug("GOA2", "Access Token Response:\n%s",
185 0 : reinterpret_cast<char *>(psResult->pabyData));
186 :
187 : /* -------------------------------------------------------------------- */
188 : /* This response is in JSON and will look something like: */
189 : /* -------------------------------------------------------------------- */
190 : /*
191 : {
192 : "access_token" :
193 : "ya29.AHES6ZToqkIJkat5rIqMixR1b8PlWBACNO8OYbqqV-YF1Q13E2Kzjw", "token_type"
194 : : "Bearer", "expires_in" : 3600, "refresh_token" :
195 : "1/eF88pciwq9Tp_rHEhuiIv9AS44Ufe4GOymGawTVPGYo"
196 : }
197 : */
198 : CPLStringList oResponse =
199 0 : CPLParseKeyValueJson(reinterpret_cast<char *>(psResult->pabyData));
200 0 : CPLHTTPDestroyResult(psResult);
201 :
202 0 : CPLString osAccessToken = oResponse.FetchNameValueDef("access_token", "");
203 0 : CPLString osRefreshToken = oResponse.FetchNameValueDef("refresh_token", "");
204 0 : CPLDebug("GOA2", "Access Token : '%s'", osAccessToken.c_str());
205 0 : CPLDebug("GOA2", "Refresh Token : '%s'", osRefreshToken.c_str());
206 :
207 0 : if (osRefreshToken.empty())
208 : {
209 0 : CPLError(CE_Failure, CPLE_AppDefined,
210 : "Unable to identify a refresh token in the OAuth2 response.");
211 0 : return nullptr;
212 : }
213 : else
214 : {
215 : // Currently we discard the access token and just return the
216 : // refresh token.
217 0 : return CPLStrdup(osRefreshToken);
218 : }
219 : }
220 :
221 : /************************************************************************/
222 : /* GOA2ProcessResponse() */
223 : /************************************************************************/
224 :
225 14 : static char **GOA2ProcessResponse(CPLHTTPResult *psResult)
226 : {
227 :
228 14 : if (psResult == nullptr)
229 0 : return nullptr;
230 :
231 14 : if (psResult->pabyData == nullptr || psResult->pszErrBuf != nullptr)
232 : {
233 0 : if (psResult->pszErrBuf != nullptr)
234 0 : CPLDebug("GOA2", "%s", psResult->pszErrBuf);
235 0 : if (psResult->pabyData != nullptr)
236 0 : CPLDebug("GOA2", "%s", psResult->pabyData);
237 :
238 0 : CPLError(CE_Failure, CPLE_AppDefined,
239 : "Fetching OAuth2 access code from auth code failed.");
240 0 : CPLHTTPDestroyResult(psResult);
241 0 : return nullptr;
242 : }
243 :
244 14 : CPLDebug("GOA2", "Refresh Token Response:\n%s",
245 14 : reinterpret_cast<char *>(psResult->pabyData));
246 :
247 : /* -------------------------------------------------------------------- */
248 : /* This response is in JSON and will look something like: */
249 : /* -------------------------------------------------------------------- */
250 : /*
251 : {
252 : "access_token":"1/fFBGRNJru1FQd44AzqT3Zg",
253 : "expires_in":3920,
254 : "token_type":"Bearer"
255 : }
256 : */
257 : CPLStringList oResponse =
258 28 : CPLParseKeyValueJson(reinterpret_cast<char *>(psResult->pabyData));
259 14 : CPLHTTPDestroyResult(psResult);
260 :
261 28 : CPLString osAccessToken = oResponse.FetchNameValueDef("access_token", "");
262 :
263 14 : CPLDebug("GOA2", "Access Token : '%s'", osAccessToken.c_str());
264 :
265 14 : if (osAccessToken.empty())
266 : {
267 0 : CPLError(CE_Failure, CPLE_AppDefined,
268 : "Unable to identify an access token in the OAuth2 response.");
269 0 : return nullptr;
270 : }
271 :
272 14 : return oResponse.StealList();
273 : }
274 :
275 : /************************************************************************/
276 : /* GOA2GetAccessTokenEx() */
277 : /************************************************************************/
278 :
279 4 : static char **GOA2GetAccessTokenEx(const char *pszRefreshToken,
280 : const char *pszClientId,
281 : const char *pszClientSecret,
282 : CSLConstList /*papszOptions*/)
283 : {
284 :
285 : /* -------------------------------------------------------------------- */
286 : /* Prepare request. */
287 : /* -------------------------------------------------------------------- */
288 8 : CPLString osItem;
289 8 : CPLStringList oOptions;
290 :
291 : oOptions.AddString(
292 4 : "HEADERS=Content-Type: application/x-www-form-urlencoded");
293 :
294 : osItem.Printf(
295 : "POSTFIELDS="
296 : "refresh_token=%s"
297 : "&client_id=%s"
298 : "&client_secret=%s"
299 : "&grant_type=refresh_token",
300 : pszRefreshToken,
301 4 : (pszClientId && !EQUAL(pszClientId, ""))
302 : ? pszClientId
303 1 : : CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID),
304 4 : (pszClientSecret && !EQUAL(pszClientSecret, ""))
305 : ? pszClientSecret
306 12 : : CPLGetConfigOption("GOA2_CLIENT_SECRET", GDAL_CLIENT_SECRET));
307 4 : oOptions.AddString(osItem);
308 :
309 : /* -------------------------------------------------------------------- */
310 : /* Submit request by HTTP. */
311 : /* -------------------------------------------------------------------- */
312 4 : CPLHTTPResult *psResult = CPLHTTPFetch(
313 : CPLGetConfigOption("GOA2_AUTH_URL_TOKEN", GOOGLE_AUTH_URL "/token"),
314 4 : oOptions);
315 :
316 8 : return GOA2ProcessResponse(psResult);
317 : }
318 :
319 : /************************************************************************/
320 : /* GOA2GetAccessToken() */
321 : /************************************************************************/
322 :
323 : /**
324 : * Fetch access token using refresh token.
325 : *
326 : * The permanent refresh token is used to fetch a temporary (usually one
327 : * hour) access token using Google OAuth2 web services.
328 : *
329 : * A CPLError will be reported if the request fails for some reason.
330 : * Common reasons include the refresh token having been revoked by the
331 : * user or http connection problems.
332 : *
333 : * @param pszRefreshToken the refresh token from GOA2GetRefreshToken().
334 : * @param pszScope the scope for which it is valid. Currently unused
335 : *
336 : * @return access token, to be freed with CPLFree(), null on failure.
337 : */
338 :
339 0 : char *GOA2GetAccessToken(const char *pszRefreshToken,
340 : CPL_UNUSED const char *pszScope)
341 : {
342 : char **papszRet =
343 0 : GOA2GetAccessTokenEx(pszRefreshToken, nullptr, nullptr, nullptr);
344 0 : const char *pszAccessToken = CSLFetchNameValue(papszRet, "access_token");
345 0 : char *pszRet = pszAccessToken ? CPLStrdup(pszAccessToken) : nullptr;
346 0 : CSLDestroy(papszRet);
347 0 : return pszRet;
348 : }
349 :
350 : /************************************************************************/
351 : /* GOA2GetAccessTokenFromCloudEngineVM() */
352 : /************************************************************************/
353 :
354 : /**
355 : * Fetch access token using Cloud Engine internal REST API
356 : *
357 : * The default service accounts bound to the current Google Cloud Engine VM
358 : * is used for OAuth2 authentication
359 : *
360 : * A CPLError will be reported if the request fails for some reason.
361 : * Common reasons include the refresh token having been revoked by the
362 : * user or http connection problems.
363 : *
364 : * @param papszOptions NULL terminated list of options. None currently
365 : *
366 : * @return a list of key=value pairs, including a access_token and expires_in
367 : */
368 :
369 4 : char **GOA2GetAccessTokenFromCloudEngineVM(CSLConstList papszOptions)
370 : {
371 8 : CPLStringList oOptions;
372 :
373 4 : oOptions.AddString("HEADERS=Metadata-Flavor: Google");
374 :
375 : /* -------------------------------------------------------------------- */
376 : /* Submit request by HTTP. */
377 : /* -------------------------------------------------------------------- */
378 4 : const char *pszURL = CSLFetchNameValueDef(
379 : papszOptions, "URL",
380 : CPLGetConfigOption("CPL_GCE_CREDENTIALS_URL",
381 : "http://metadata.google.internal/computeMetadata/v1/"
382 : "instance/service-accounts/default/token"));
383 4 : CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, oOptions);
384 :
385 8 : return GOA2ProcessResponse(psResult);
386 : }
387 :
388 : /************************************************************************/
389 : /* GOA2GetAccessTokenFromServiceAccount() */
390 : /************************************************************************/
391 :
392 : /**
393 : * Fetch access token using Service Account OAuth2
394 : *
395 : * See https://developers.google.com/identity/protocols/OAuth2ServiceAccount
396 : *
397 : * A CPLError will be reported if the request fails for some reason.
398 : *
399 : * @param pszPrivateKey Private key as a RSA private key
400 : * @param pszClientEmail Client email
401 : * @param pszScope the service being requested
402 : * @param papszAdditionalClaims additional claims, or NULL
403 : * @param papszOptions NULL terminated list of options. None currently
404 : *
405 : * @return a list of key=value pairs, including a access_token and expires_in
406 : */
407 :
408 6 : char **GOA2GetAccessTokenFromServiceAccount(const char *pszPrivateKey,
409 : const char *pszClientEmail,
410 : const char *pszScope,
411 : CSLConstList papszAdditionalClaims,
412 : CSLConstList papszOptions)
413 : {
414 6 : CPL_IGNORE_RET_VAL(papszOptions);
415 :
416 : /** See
417 : * https://developers.google.com/identity/protocols/OAuth2ServiceAccount and
418 : * https://jwt.io/ */
419 :
420 : // JWT header '{"alg":"RS256","typ":"JWT"}' encoded in Base64
421 6 : const char *pszB64JWTHeader = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9";
422 6 : const char *pszAud = CPLGetConfigOption(
423 : "GO2A_AUD", "https://www.googleapis.com/oauth2/v4/token");
424 :
425 12 : CPLString osClaim;
426 6 : osClaim = "{\"iss\": \"";
427 6 : osClaim += pszClientEmail;
428 6 : osClaim += "\", \"scope\": \"";
429 6 : osClaim += pszScope;
430 6 : osClaim += "\", \"aud\": \"";
431 6 : osClaim += pszAud;
432 6 : osClaim += "\", \"iat\": ";
433 6 : GIntBig now = static_cast<GIntBig>(time(nullptr));
434 6 : const char *pszNow = CPLGetConfigOption("GOA2_NOW", nullptr);
435 6 : if (pszNow)
436 6 : now = CPLAtoGIntBig(pszNow);
437 6 : osClaim += CPLSPrintf(CPL_FRMT_GIB, now);
438 6 : osClaim += ", \"exp\": ";
439 : osClaim += CPLSPrintf(
440 : CPL_FRMT_GIB,
441 6 : now + atoi(CPLGetConfigOption("GOA2_EXPIRATION_DELAY", "3600")));
442 6 : for (CSLConstList papszIter = papszAdditionalClaims;
443 6 : papszIter && *papszIter; ++papszIter)
444 : {
445 0 : char *pszKey = nullptr;
446 0 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
447 0 : if (pszKey && pszValue)
448 : {
449 0 : osClaim += ", \"";
450 0 : osClaim += pszKey;
451 0 : osClaim += "\": ";
452 0 : osClaim += pszValue;
453 0 : CPLFree(pszKey);
454 : }
455 : }
456 6 : osClaim += "}";
457 : #ifdef DEBUG_VERBOSE
458 : CPLDebug("GOA2", "%s", osClaim.c_str());
459 : #endif
460 :
461 : char *pszB64Claim =
462 6 : CPLBase64Encode(static_cast<int>(osClaim.size()),
463 6 : reinterpret_cast<const GByte *>(osClaim.c_str()));
464 : // Build string to sign
465 18 : CPLString osToSign(CPLString(pszB64JWTHeader) + "." + pszB64Claim);
466 6 : CPLFree(pszB64Claim);
467 :
468 6 : unsigned int nSignatureLen = 0;
469 : // Sign request
470 : GByte *pabySignature =
471 6 : CPL_RSA_SHA256_Sign(pszPrivateKey, osToSign.c_str(),
472 6 : static_cast<int>(osToSign.size()), &nSignatureLen);
473 6 : if (pabySignature == nullptr)
474 : {
475 0 : return nullptr;
476 : }
477 6 : char *pszB64Signature = CPLBase64Encode(nSignatureLen, pabySignature);
478 6 : CPLFree(pabySignature);
479 : // Build signed request
480 18 : CPLString osRequest(osToSign + "." + pszB64Signature);
481 6 : CPLFree(pszB64Signature);
482 :
483 : // Issue request
484 : CPLString osPostData("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%"
485 12 : "3Ajwt-bearer&assertion=");
486 6 : char *pszAssertionEncoded = CPLEscapeString(osRequest, -1, CPLES_URL);
487 12 : CPLString osAssertionEncoded(pszAssertionEncoded);
488 6 : CPLFree(pszAssertionEncoded);
489 : // Required in addition to URL escaping otherwise is considered to be space
490 6 : osAssertionEncoded.replaceAll("+", "%2B");
491 6 : osPostData += osAssertionEncoded;
492 :
493 6 : char **papszHTTPOptions = nullptr;
494 : papszHTTPOptions =
495 6 : CSLSetNameValue(papszHTTPOptions, "POSTFIELDS", osPostData);
496 6 : CPLHTTPResult *psResult = CPLHTTPFetch(pszAud, papszHTTPOptions);
497 6 : CSLDestroy(papszHTTPOptions);
498 :
499 6 : return GOA2ProcessResponse(psResult);
500 : }
501 :
502 : /************************************************************************/
503 : /* GOA2Manager() */
504 : /************************************************************************/
505 :
506 : /** Constructor */
507 : GOA2Manager::GOA2Manager() = default;
508 :
509 : /************************************************************************/
510 : /* SetAuthFromGCE() */
511 : /************************************************************************/
512 :
513 : /** Specifies that the authentication will be done using the local
514 : * credentials of the current Google Compute Engine VM
515 : *
516 : * This queries
517 : * http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
518 : *
519 : * @param papszOptions NULL terminated list of options.
520 : * @return true in case of success (no network access is done at this stage)
521 : */
522 4 : bool GOA2Manager::SetAuthFromGCE(CSLConstList papszOptions)
523 : {
524 4 : m_eMethod = GCE;
525 4 : m_aosOptions = papszOptions;
526 4 : return true;
527 : }
528 :
529 : /************************************************************************/
530 : /* SetAuthFromRefreshToken() */
531 : /************************************************************************/
532 :
533 : /** Specifies that the authentication will be done using the OAuth2 client
534 : * id method.
535 : *
536 : * See http://code.google.com/apis/accounts/docs/OAuth2.html
537 : *
538 : * @param pszRefreshToken refresh token. Must be non NULL.
539 : * @param pszClientId client id (may be NULL, in which case the GOA2_CLIENT_ID
540 : * configuration option is used)
541 : * @param pszClientSecret client secret (may be NULL, in which case the
542 : * GOA2_CLIENT_SECRET configuration option is used)
543 : * @param papszOptions NULL terminated list of options, or NULL.
544 : * @return true in case of success (no network access is done at this stage)
545 : */
546 8 : bool GOA2Manager::SetAuthFromRefreshToken(const char *pszRefreshToken,
547 : const char *pszClientId,
548 : const char *pszClientSecret,
549 : CSLConstList papszOptions)
550 : {
551 8 : if (pszRefreshToken == nullptr)
552 : {
553 0 : CPLError(CE_Failure, CPLE_AppDefined, "Refresh token should be set");
554 0 : return false;
555 : }
556 8 : m_eMethod = ACCESS_TOKEN_FROM_REFRESH;
557 8 : m_osRefreshToken = pszRefreshToken;
558 8 : m_osClientId = pszClientId ? pszClientId : "";
559 8 : m_osClientSecret = pszClientSecret ? pszClientSecret : "";
560 8 : m_aosOptions = papszOptions;
561 8 : return true;
562 : }
563 :
564 : /************************************************************************/
565 : /* SetAuthFromServiceAccount() */
566 : /************************************************************************/
567 :
568 : /** Specifies that the authentication will be done using the OAuth2 service
569 : * account method.
570 : *
571 : * See https://developers.google.com/identity/protocols/OAuth2ServiceAccount
572 : *
573 : * @param pszPrivateKey RSA private key. Must be non NULL.
574 : * @param pszClientEmail client email. Must be non NULL.
575 : * @param pszScope authorization scope. Must be non NULL.
576 : * @param papszAdditionalClaims NULL terminate list of additional claims, or
577 : * NULL.
578 : * @param papszOptions NULL terminated list of options, or NULL.
579 : * @return true in case of success (no network access is done at this stage)
580 : */
581 5 : bool GOA2Manager::SetAuthFromServiceAccount(const char *pszPrivateKey,
582 : const char *pszClientEmail,
583 : const char *pszScope,
584 : CSLConstList papszAdditionalClaims,
585 : CSLConstList papszOptions)
586 : {
587 5 : if (pszPrivateKey == nullptr || EQUAL(pszPrivateKey, ""))
588 : {
589 0 : CPLError(CE_Failure, CPLE_AppDefined, "Private key should be set");
590 0 : return false;
591 : }
592 5 : if (pszClientEmail == nullptr || EQUAL(pszClientEmail, ""))
593 : {
594 0 : CPLError(CE_Failure, CPLE_AppDefined, "Client email should be set");
595 0 : return false;
596 : }
597 5 : if (pszScope == nullptr || EQUAL(pszScope, ""))
598 : {
599 0 : CPLError(CE_Failure, CPLE_AppDefined, "Scope should be set");
600 0 : return false;
601 : }
602 5 : m_eMethod = SERVICE_ACCOUNT;
603 5 : m_osPrivateKey = pszPrivateKey;
604 5 : m_osClientEmail = pszClientEmail;
605 5 : m_osScope = pszScope;
606 5 : m_aosAdditionalClaims = papszAdditionalClaims;
607 5 : m_aosOptions = papszOptions;
608 5 : return true;
609 : }
610 :
611 : /************************************************************************/
612 : /* GetBearer() */
613 : /************************************************************************/
614 :
615 : /** Return the access token.
616 : *
617 : * This is the value to append to a "Authorization: Bearer " HTTP header.
618 : *
619 : * A network request is issued only if no access token has been yet queried,
620 : * or if its expiration delay has been reached.
621 : *
622 : * @return the access token, or NULL in case of error.
623 : */
624 19 : const char *GOA2Manager::GetBearer() const
625 : {
626 19 : time_t nCurTime = time(nullptr);
627 19 : if (nCurTime < m_nExpirationTime - 5)
628 8 : return m_osCurrentBearer.c_str();
629 :
630 11 : char **papszRet = nullptr;
631 11 : if (m_eMethod == GCE)
632 : {
633 3 : papszRet = GOA2GetAccessTokenFromCloudEngineVM(m_aosOptions.List());
634 : }
635 8 : else if (m_eMethod == ACCESS_TOKEN_FROM_REFRESH)
636 : {
637 : papszRet =
638 4 : GOA2GetAccessTokenEx(m_osRefreshToken.c_str(), m_osClientId.c_str(),
639 : m_osClientSecret.c_str(), m_aosOptions.List());
640 : }
641 4 : else if (m_eMethod == SERVICE_ACCOUNT)
642 : {
643 4 : papszRet = GOA2GetAccessTokenFromServiceAccount(
644 : m_osPrivateKey, m_osClientEmail, m_osScope,
645 : m_aosAdditionalClaims.List(), m_aosOptions.List());
646 : }
647 :
648 11 : m_nExpirationTime = 0;
649 11 : m_osCurrentBearer.clear();
650 11 : const char *pszAccessToken = CSLFetchNameValue(papszRet, "access_token");
651 11 : if (pszAccessToken == nullptr)
652 : {
653 0 : CSLDestroy(papszRet);
654 0 : return nullptr;
655 : }
656 11 : const char *pszExpires = CSLFetchNameValue(papszRet, "expires_in");
657 11 : if (pszExpires)
658 : {
659 11 : m_nExpirationTime = nCurTime + atoi(pszExpires);
660 : }
661 11 : m_osCurrentBearer = pszAccessToken;
662 11 : CSLDestroy(papszRet);
663 11 : return m_osCurrentBearer.c_str();
664 : }
|