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 : * @since GDAL 2.3
368 : */
369 :
370 4 : char **GOA2GetAccessTokenFromCloudEngineVM(CSLConstList papszOptions)
371 : {
372 8 : CPLStringList oOptions;
373 :
374 4 : oOptions.AddString("HEADERS=Metadata-Flavor: Google");
375 :
376 : /* -------------------------------------------------------------------- */
377 : /* Submit request by HTTP. */
378 : /* -------------------------------------------------------------------- */
379 4 : const char *pszURL = CSLFetchNameValueDef(
380 : papszOptions, "URL",
381 : CPLGetConfigOption("CPL_GCE_CREDENTIALS_URL",
382 : "http://metadata.google.internal/computeMetadata/v1/"
383 : "instance/service-accounts/default/token"));
384 4 : CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, oOptions);
385 :
386 8 : return GOA2ProcessResponse(psResult);
387 : }
388 :
389 : /************************************************************************/
390 : /* GOA2GetAccessTokenFromServiceAccount() */
391 : /************************************************************************/
392 :
393 : /**
394 : * Fetch access token using Service Account OAuth2
395 : *
396 : * See https://developers.google.com/identity/protocols/OAuth2ServiceAccount
397 : *
398 : * A CPLError will be reported if the request fails for some reason.
399 : *
400 : * @param pszPrivateKey Private key as a RSA private key
401 : * @param pszClientEmail Client email
402 : * @param pszScope the service being requested
403 : * @param papszAdditionalClaims additional claims, or NULL
404 : * @param papszOptions NULL terminated list of options. None currently
405 : *
406 : * @return a list of key=value pairs, including a access_token and expires_in
407 : * @since GDAL 2.3
408 : */
409 :
410 6 : char **GOA2GetAccessTokenFromServiceAccount(const char *pszPrivateKey,
411 : const char *pszClientEmail,
412 : const char *pszScope,
413 : CSLConstList papszAdditionalClaims,
414 : CSLConstList papszOptions)
415 : {
416 6 : CPL_IGNORE_RET_VAL(papszOptions);
417 :
418 : /** See
419 : * https://developers.google.com/identity/protocols/OAuth2ServiceAccount and
420 : * https://jwt.io/ */
421 :
422 : // JWT header '{"alg":"RS256","typ":"JWT"}' encoded in Base64
423 6 : const char *pszB64JWTHeader = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9";
424 6 : const char *pszAud = CPLGetConfigOption(
425 : "GO2A_AUD", "https://www.googleapis.com/oauth2/v4/token");
426 :
427 12 : CPLString osClaim;
428 6 : osClaim = "{\"iss\": \"";
429 6 : osClaim += pszClientEmail;
430 6 : osClaim += "\", \"scope\": \"";
431 6 : osClaim += pszScope;
432 6 : osClaim += "\", \"aud\": \"";
433 6 : osClaim += pszAud;
434 6 : osClaim += "\", \"iat\": ";
435 6 : GIntBig now = static_cast<GIntBig>(time(nullptr));
436 6 : const char *pszNow = CPLGetConfigOption("GOA2_NOW", nullptr);
437 6 : if (pszNow)
438 6 : now = CPLAtoGIntBig(pszNow);
439 6 : osClaim += CPLSPrintf(CPL_FRMT_GIB, now);
440 6 : osClaim += ", \"exp\": ";
441 : osClaim += CPLSPrintf(
442 : CPL_FRMT_GIB,
443 6 : now + atoi(CPLGetConfigOption("GOA2_EXPIRATION_DELAY", "3600")));
444 6 : for (CSLConstList papszIter = papszAdditionalClaims;
445 6 : papszIter && *papszIter; ++papszIter)
446 : {
447 0 : char *pszKey = nullptr;
448 0 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
449 0 : if (pszKey && pszValue)
450 : {
451 0 : osClaim += ", \"";
452 0 : osClaim += pszKey;
453 0 : osClaim += "\": ";
454 0 : osClaim += pszValue;
455 0 : CPLFree(pszKey);
456 : }
457 : }
458 6 : osClaim += "}";
459 : #ifdef DEBUG_VERBOSE
460 : CPLDebug("GOA2", "%s", osClaim.c_str());
461 : #endif
462 :
463 : char *pszB64Claim =
464 6 : CPLBase64Encode(static_cast<int>(osClaim.size()),
465 6 : reinterpret_cast<const GByte *>(osClaim.c_str()));
466 : // Build string to sign
467 18 : CPLString osToSign(CPLString(pszB64JWTHeader) + "." + pszB64Claim);
468 6 : CPLFree(pszB64Claim);
469 :
470 6 : unsigned int nSignatureLen = 0;
471 : // Sign request
472 : GByte *pabySignature =
473 6 : CPL_RSA_SHA256_Sign(pszPrivateKey, osToSign.c_str(),
474 6 : static_cast<int>(osToSign.size()), &nSignatureLen);
475 6 : if (pabySignature == nullptr)
476 : {
477 0 : return nullptr;
478 : }
479 6 : char *pszB64Signature = CPLBase64Encode(nSignatureLen, pabySignature);
480 6 : CPLFree(pabySignature);
481 : // Build signed request
482 18 : CPLString osRequest(osToSign + "." + pszB64Signature);
483 6 : CPLFree(pszB64Signature);
484 :
485 : // Issue request
486 : CPLString osPostData("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%"
487 12 : "3Ajwt-bearer&assertion=");
488 6 : char *pszAssertionEncoded = CPLEscapeString(osRequest, -1, CPLES_URL);
489 12 : CPLString osAssertionEncoded(pszAssertionEncoded);
490 6 : CPLFree(pszAssertionEncoded);
491 : // Required in addition to URL escaping otherwise is considered to be space
492 6 : osAssertionEncoded.replaceAll("+", "%2B");
493 6 : osPostData += osAssertionEncoded;
494 :
495 6 : char **papszHTTPOptions = nullptr;
496 : papszHTTPOptions =
497 6 : CSLSetNameValue(papszHTTPOptions, "POSTFIELDS", osPostData);
498 6 : CPLHTTPResult *psResult = CPLHTTPFetch(pszAud, papszHTTPOptions);
499 6 : CSLDestroy(papszHTTPOptions);
500 :
501 6 : return GOA2ProcessResponse(psResult);
502 : }
503 :
504 : /************************************************************************/
505 : /* GOA2Manager() */
506 : /************************************************************************/
507 :
508 : /** Constructor */
509 : GOA2Manager::GOA2Manager() = default;
510 :
511 : /************************************************************************/
512 : /* SetAuthFromGCE() */
513 : /************************************************************************/
514 :
515 : /** Specifies that the authentication will be done using the local
516 : * credentials of the current Google Compute Engine VM
517 : *
518 : * This queries
519 : * http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
520 : *
521 : * @param papszOptions NULL terminated list of options.
522 : * @return true in case of success (no network access is done at this stage)
523 : */
524 4 : bool GOA2Manager::SetAuthFromGCE(CSLConstList papszOptions)
525 : {
526 4 : m_eMethod = GCE;
527 4 : m_aosOptions = papszOptions;
528 4 : return true;
529 : }
530 :
531 : /************************************************************************/
532 : /* SetAuthFromRefreshToken() */
533 : /************************************************************************/
534 :
535 : /** Specifies that the authentication will be done using the OAuth2 client
536 : * id method.
537 : *
538 : * See http://code.google.com/apis/accounts/docs/OAuth2.html
539 : *
540 : * @param pszRefreshToken refresh token. Must be non NULL.
541 : * @param pszClientId client id (may be NULL, in which case the GOA2_CLIENT_ID
542 : * configuration option is used)
543 : * @param pszClientSecret client secret (may be NULL, in which case the
544 : * GOA2_CLIENT_SECRET configuration option is used)
545 : * @param papszOptions NULL terminated list of options, or NULL.
546 : * @return true in case of success (no network access is done at this stage)
547 : */
548 8 : bool GOA2Manager::SetAuthFromRefreshToken(const char *pszRefreshToken,
549 : const char *pszClientId,
550 : const char *pszClientSecret,
551 : CSLConstList papszOptions)
552 : {
553 8 : if (pszRefreshToken == nullptr)
554 : {
555 0 : CPLError(CE_Failure, CPLE_AppDefined, "Refresh token should be set");
556 0 : return false;
557 : }
558 8 : m_eMethod = ACCESS_TOKEN_FROM_REFRESH;
559 8 : m_osRefreshToken = pszRefreshToken;
560 8 : m_osClientId = pszClientId ? pszClientId : "";
561 8 : m_osClientSecret = pszClientSecret ? pszClientSecret : "";
562 8 : m_aosOptions = papszOptions;
563 8 : return true;
564 : }
565 :
566 : /************************************************************************/
567 : /* SetAuthFromServiceAccount() */
568 : /************************************************************************/
569 :
570 : /** Specifies that the authentication will be done using the OAuth2 service
571 : * account method.
572 : *
573 : * See https://developers.google.com/identity/protocols/OAuth2ServiceAccount
574 : *
575 : * @param pszPrivateKey RSA private key. Must be non NULL.
576 : * @param pszClientEmail client email. Must be non NULL.
577 : * @param pszScope authorization scope. Must be non NULL.
578 : * @param papszAdditionalClaims NULL terminate list of additional claims, or
579 : * NULL.
580 : * @param papszOptions NULL terminated list of options, or NULL.
581 : * @return true in case of success (no network access is done at this stage)
582 : */
583 5 : bool GOA2Manager::SetAuthFromServiceAccount(const char *pszPrivateKey,
584 : const char *pszClientEmail,
585 : const char *pszScope,
586 : CSLConstList papszAdditionalClaims,
587 : CSLConstList papszOptions)
588 : {
589 5 : if (pszPrivateKey == nullptr || EQUAL(pszPrivateKey, ""))
590 : {
591 0 : CPLError(CE_Failure, CPLE_AppDefined, "Private key should be set");
592 0 : return false;
593 : }
594 5 : if (pszClientEmail == nullptr || EQUAL(pszClientEmail, ""))
595 : {
596 0 : CPLError(CE_Failure, CPLE_AppDefined, "Client email should be set");
597 0 : return false;
598 : }
599 5 : if (pszScope == nullptr || EQUAL(pszScope, ""))
600 : {
601 0 : CPLError(CE_Failure, CPLE_AppDefined, "Scope should be set");
602 0 : return false;
603 : }
604 5 : m_eMethod = SERVICE_ACCOUNT;
605 5 : m_osPrivateKey = pszPrivateKey;
606 5 : m_osClientEmail = pszClientEmail;
607 5 : m_osScope = pszScope;
608 5 : m_aosAdditionalClaims = papszAdditionalClaims;
609 5 : m_aosOptions = papszOptions;
610 5 : return true;
611 : }
612 :
613 : /************************************************************************/
614 : /* GetBearer() */
615 : /************************************************************************/
616 :
617 : /** Return the access token.
618 : *
619 : * This is the value to append to a "Authorization: Bearer " HTTP header.
620 : *
621 : * A network request is issued only if no access token has been yet queried,
622 : * or if its expiration delay has been reached.
623 : *
624 : * @return the access token, or NULL in case of error.
625 : */
626 19 : const char *GOA2Manager::GetBearer() const
627 : {
628 19 : time_t nCurTime = time(nullptr);
629 19 : if (nCurTime < m_nExpirationTime - 5)
630 8 : return m_osCurrentBearer.c_str();
631 :
632 11 : char **papszRet = nullptr;
633 11 : if (m_eMethod == GCE)
634 : {
635 3 : papszRet = GOA2GetAccessTokenFromCloudEngineVM(m_aosOptions.List());
636 : }
637 8 : else if (m_eMethod == ACCESS_TOKEN_FROM_REFRESH)
638 : {
639 : papszRet =
640 4 : GOA2GetAccessTokenEx(m_osRefreshToken.c_str(), m_osClientId.c_str(),
641 : m_osClientSecret.c_str(), m_aosOptions.List());
642 : }
643 4 : else if (m_eMethod == SERVICE_ACCOUNT)
644 : {
645 4 : papszRet = GOA2GetAccessTokenFromServiceAccount(
646 : m_osPrivateKey, m_osClientEmail, m_osScope,
647 : m_aosAdditionalClaims.List(), m_aosOptions.List());
648 : }
649 :
650 11 : m_nExpirationTime = 0;
651 11 : m_osCurrentBearer.clear();
652 11 : const char *pszAccessToken = CSLFetchNameValue(papszRet, "access_token");
653 11 : if (pszAccessToken == nullptr)
654 : {
655 0 : CSLDestroy(papszRet);
656 0 : return nullptr;
657 : }
658 11 : const char *pszExpires = CSLFetchNameValue(papszRet, "expires_in");
659 11 : if (pszExpires)
660 : {
661 11 : m_nExpirationTime = nCurTime + atoi(pszExpires);
662 : }
663 11 : m_osCurrentBearer = pszAccessToken;
664 11 : CSLDestroy(papszRet);
665 11 : return m_osCurrentBearer.c_str();
666 : }
|