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