Coverage Report

Created: 2025-08-11 09:23

/src/gdal/port/cpl_google_oauth2.cpp
Line
Count
Source (jump to first uncovered line)
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
0
#define GDAL_CLIENT_ID "265656308688.apps.googleusercontent.com"
42
0
#define GDAL_CLIENT_SECRET "0IbTUDOYzaL6vnIdWTuQnvLz"
43
44
0
#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
char *GOA2GetAuthorizationURL(const char *pszScope)
73
74
0
{
75
0
    CPLString osScope;
76
0
    osScope.Seize(CPLEscapeString(pszScope, -1, CPLES_URL));
77
78
0
    CPLString osURL;
79
0
    osURL.Printf("%s/auth?scope=%s&redirect_uri=urn:ietf:wg:oauth:2.0:oob&"
80
0
                 "response_type=code&client_id=%s",
81
0
                 GOOGLE_AUTH_URL, osScope.c_str(),
82
0
                 CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID));
83
0
    return CPLStrdup(osURL);
84
0
}
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
char CPL_DLL *GOA2GetRefreshToken(const char *pszAuthToken,
108
                                  const char *pszScope)
109
110
0
{
111
    /* -------------------------------------------------------------------- */
112
    /*      Prepare request.                                                */
113
    /* -------------------------------------------------------------------- */
114
0
    CPLString osItem;
115
0
    CPLStringList oOptions;
116
117
0
    oOptions.AddString(
118
0
        "HEADERS=Content-Type: application/x-www-form-urlencoded");
119
120
0
    osItem.Printf("POSTFIELDS="
121
0
                  "code=%s"
122
0
                  "&client_id=%s"
123
0
                  "&client_secret=%s"
124
0
                  "&redirect_uri=urn:ietf:wg:oauth:2.0:oob"
125
0
                  "&grant_type=authorization_code",
126
0
                  pszAuthToken,
127
0
                  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
0
        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
0
            nullptr)
148
0
    {
149
0
        CPLHTTPDestroyResult(psResult);
150
0
        if (pszScope == nullptr)
151
0
        {
152
0
            CPLError(
153
0
                CE_Failure, CPLE_AppDefined,
154
0
                "Attempt to use a OAuth2 authorization code multiple times. "
155
0
                "Use GOA2GetAuthorizationURL(scope) with a valid scope to "
156
0
                "request a fresh authorization token.");
157
0
        }
158
0
        else
159
0
        {
160
0
            CPLString osURL;
161
0
            osURL.Seize(GOA2GetAuthorizationURL(pszScope));
162
0
            CPLError(
163
0
                CE_Failure, CPLE_AppDefined,
164
0
                "Attempt to use a OAuth2 authorization code multiple times. "
165
0
                "Request a fresh authorization token at %s.",
166
0
                osURL.c_str());
167
0
        }
168
0
        return nullptr;
169
0
    }
170
171
0
    if (psResult->pabyData == nullptr || psResult->pszErrBuf != nullptr)
172
0
    {
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
0
                 "Fetching OAuth2 access code from auth code failed.");
180
0
        CPLHTTPDestroyResult(psResult);
181
0
        return nullptr;
182
0
    }
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
0
    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
0
    {
209
0
        CPLError(CE_Failure, CPLE_AppDefined,
210
0
                 "Unable to identify a refresh token in the OAuth2 response.");
211
0
        return nullptr;
212
0
    }
213
0
    else
214
0
    {
215
        // Currently we discard the access token and just return the
216
        // refresh token.
217
0
        return CPLStrdup(osRefreshToken);
218
0
    }
219
0
}
220
221
/************************************************************************/
222
/*                       GOA2ProcessResponse()                          */
223
/************************************************************************/
224
225
static char **GOA2ProcessResponse(CPLHTTPResult *psResult)
226
39
{
227
228
39
    if (psResult == nullptr)
229
0
        return nullptr;
230
231
39
    if (psResult->pabyData == nullptr || psResult->pszErrBuf != nullptr)
232
0
    {
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
0
                 "Fetching OAuth2 access code from auth code failed.");
240
0
        CPLHTTPDestroyResult(psResult);
241
0
        return nullptr;
242
0
    }
243
244
39
    CPLDebug("GOA2", "Refresh Token Response:\n%s",
245
39
             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
39
    CPLStringList oResponse =
258
39
        CPLParseKeyValueJson(reinterpret_cast<char *>(psResult->pabyData));
259
39
    CPLHTTPDestroyResult(psResult);
260
261
39
    CPLString osAccessToken = oResponse.FetchNameValueDef("access_token", "");
262
263
39
    CPLDebug("GOA2", "Access Token : '%s'", osAccessToken.c_str());
264
265
39
    if (osAccessToken.empty())
266
0
    {
267
0
        CPLError(CE_Failure, CPLE_AppDefined,
268
0
                 "Unable to identify an access token in the OAuth2 response.");
269
0
        return nullptr;
270
0
    }
271
272
39
    return oResponse.StealList();
273
39
}
274
275
/************************************************************************/
276
/*                       GOA2GetAccessTokenEx()                         */
277
/************************************************************************/
278
279
static char **GOA2GetAccessTokenEx(const char *pszRefreshToken,
280
                                   const char *pszClientId,
281
                                   const char *pszClientSecret,
282
                                   CSLConstList /*papszOptions*/)
283
0
{
284
285
    /* -------------------------------------------------------------------- */
286
    /*      Prepare request.                                                */
287
    /* -------------------------------------------------------------------- */
288
0
    CPLString osItem;
289
0
    CPLStringList oOptions;
290
291
0
    oOptions.AddString(
292
0
        "HEADERS=Content-Type: application/x-www-form-urlencoded");
293
294
0
    osItem.Printf(
295
0
        "POSTFIELDS="
296
0
        "refresh_token=%s"
297
0
        "&client_id=%s"
298
0
        "&client_secret=%s"
299
0
        "&grant_type=refresh_token",
300
0
        pszRefreshToken,
301
0
        (pszClientId && !EQUAL(pszClientId, ""))
302
0
            ? pszClientId
303
0
            : CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID),
304
0
        (pszClientSecret && !EQUAL(pszClientSecret, ""))
305
0
            ? pszClientSecret
306
0
            : CPLGetConfigOption("GOA2_CLIENT_SECRET", GDAL_CLIENT_SECRET));
307
0
    oOptions.AddString(osItem);
308
309
    /* -------------------------------------------------------------------- */
310
    /*      Submit request by HTTP.                                         */
311
    /* -------------------------------------------------------------------- */
312
0
    CPLHTTPResult *psResult = CPLHTTPFetch(
313
0
        CPLGetConfigOption("GOA2_AUTH_URL_TOKEN", GOOGLE_AUTH_URL "/token"),
314
0
        oOptions);
315
316
0
    return GOA2ProcessResponse(psResult);
317
0
}
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
char *GOA2GetAccessToken(const char *pszRefreshToken,
340
                         CPL_UNUSED const char *pszScope)
341
0
{
342
0
    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
0
}
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
char **GOA2GetAccessTokenFromCloudEngineVM(CSLConstList papszOptions)
371
39
{
372
39
    CPLStringList oOptions;
373
374
39
    oOptions.AddString("HEADERS=Metadata-Flavor: Google");
375
376
    /* -------------------------------------------------------------------- */
377
    /*      Submit request by HTTP.                                         */
378
    /* -------------------------------------------------------------------- */
379
39
    const char *pszURL = CSLFetchNameValueDef(
380
39
        papszOptions, "URL",
381
39
        CPLGetConfigOption("CPL_GCE_CREDENTIALS_URL",
382
39
                           "http://metadata.google.internal/computeMetadata/v1/"
383
39
                           "instance/service-accounts/default/token"));
384
39
    CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, oOptions);
385
386
39
    return GOA2ProcessResponse(psResult);
387
39
}
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
char **GOA2GetAccessTokenFromServiceAccount(const char *pszPrivateKey,
411
                                            const char *pszClientEmail,
412
                                            const char *pszScope,
413
                                            CSLConstList papszAdditionalClaims,
414
                                            CSLConstList papszOptions)
415
0
{
416
0
    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
0
    const char *pszB64JWTHeader = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9";
424
0
    const char *pszAud = CPLGetConfigOption(
425
0
        "GO2A_AUD", "https://www.googleapis.com/oauth2/v4/token");
426
427
0
    CPLString osClaim;
428
0
    osClaim = "{\"iss\": \"";
429
0
    osClaim += pszClientEmail;
430
0
    osClaim += "\", \"scope\": \"";
431
0
    osClaim += pszScope;
432
0
    osClaim += "\", \"aud\": \"";
433
0
    osClaim += pszAud;
434
0
    osClaim += "\", \"iat\": ";
435
0
    GIntBig now = static_cast<GIntBig>(time(nullptr));
436
0
    const char *pszNow = CPLGetConfigOption("GOA2_NOW", nullptr);
437
0
    if (pszNow)
438
0
        now = CPLAtoGIntBig(pszNow);
439
0
    osClaim += CPLSPrintf(CPL_FRMT_GIB, now);
440
0
    osClaim += ", \"exp\": ";
441
0
    osClaim += CPLSPrintf(
442
0
        CPL_FRMT_GIB,
443
0
        now + atoi(CPLGetConfigOption("GOA2_EXPIRATION_DELAY", "3600")));
444
0
    for (CSLConstList papszIter = papszAdditionalClaims;
445
0
         papszIter && *papszIter; ++papszIter)
446
0
    {
447
0
        char *pszKey = nullptr;
448
0
        const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
449
0
        if (pszKey && pszValue)
450
0
        {
451
0
            osClaim += ", \"";
452
0
            osClaim += pszKey;
453
0
            osClaim += "\": ";
454
0
            osClaim += pszValue;
455
0
            CPLFree(pszKey);
456
0
        }
457
0
    }
458
0
    osClaim += "}";
459
#ifdef DEBUG_VERBOSE
460
    CPLDebug("GOA2", "%s", osClaim.c_str());
461
#endif
462
463
0
    char *pszB64Claim =
464
0
        CPLBase64Encode(static_cast<int>(osClaim.size()),
465
0
                        reinterpret_cast<const GByte *>(osClaim.c_str()));
466
    // Build string to sign
467
0
    CPLString osToSign(CPLString(pszB64JWTHeader) + "." + pszB64Claim);
468
0
    CPLFree(pszB64Claim);
469
470
0
    unsigned int nSignatureLen = 0;
471
    // Sign request
472
0
    GByte *pabySignature =
473
0
        CPL_RSA_SHA256_Sign(pszPrivateKey, osToSign.c_str(),
474
0
                            static_cast<int>(osToSign.size()), &nSignatureLen);
475
0
    if (pabySignature == nullptr)
476
0
    {
477
0
        return nullptr;
478
0
    }
479
0
    char *pszB64Signature = CPLBase64Encode(nSignatureLen, pabySignature);
480
0
    CPLFree(pabySignature);
481
    // Build signed request
482
0
    CPLString osRequest(osToSign + "." + pszB64Signature);
483
0
    CPLFree(pszB64Signature);
484
485
    // Issue request
486
0
    CPLString osPostData("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%"
487
0
                         "3Ajwt-bearer&assertion=");
488
0
    char *pszAssertionEncoded = CPLEscapeString(osRequest, -1, CPLES_URL);
489
0
    CPLString osAssertionEncoded(pszAssertionEncoded);
490
0
    CPLFree(pszAssertionEncoded);
491
    // Required in addition to URL escaping otherwise is considered to be space
492
0
    osAssertionEncoded.replaceAll("+", "%2B");
493
0
    osPostData += osAssertionEncoded;
494
495
0
    char **papszHTTPOptions = nullptr;
496
0
    papszHTTPOptions =
497
0
        CSLSetNameValue(papszHTTPOptions, "POSTFIELDS", osPostData);
498
0
    CPLHTTPResult *psResult = CPLHTTPFetch(pszAud, papszHTTPOptions);
499
0
    CSLDestroy(papszHTTPOptions);
500
501
0
    return GOA2ProcessResponse(psResult);
502
0
}
503
504
/************************************************************************/
505
/*                              GOA2Manager()                           */
506
/************************************************************************/
507
508
/** Constructor */
509
387k
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
bool GOA2Manager::SetAuthFromGCE(CSLConstList papszOptions)
525
387k
{
526
387k
    m_eMethod = GCE;
527
387k
    m_aosOptions = papszOptions;
528
387k
    return true;
529
387k
}
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
bool GOA2Manager::SetAuthFromRefreshToken(const char *pszRefreshToken,
549
                                          const char *pszClientId,
550
                                          const char *pszClientSecret,
551
                                          CSLConstList papszOptions)
552
0
{
553
0
    if (pszRefreshToken == nullptr)
554
0
    {
555
0
        CPLError(CE_Failure, CPLE_AppDefined, "Refresh token should be set");
556
0
        return false;
557
0
    }
558
0
    m_eMethod = ACCESS_TOKEN_FROM_REFRESH;
559
0
    m_osRefreshToken = pszRefreshToken;
560
0
    m_osClientId = pszClientId ? pszClientId : "";
561
0
    m_osClientSecret = pszClientSecret ? pszClientSecret : "";
562
0
    m_aosOptions = papszOptions;
563
0
    return true;
564
0
}
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
bool GOA2Manager::SetAuthFromServiceAccount(const char *pszPrivateKey,
584
                                            const char *pszClientEmail,
585
                                            const char *pszScope,
586
                                            CSLConstList papszAdditionalClaims,
587
                                            CSLConstList papszOptions)
588
0
{
589
0
    if (pszPrivateKey == nullptr || EQUAL(pszPrivateKey, ""))
590
0
    {
591
0
        CPLError(CE_Failure, CPLE_AppDefined, "Private key should be set");
592
0
        return false;
593
0
    }
594
0
    if (pszClientEmail == nullptr || EQUAL(pszClientEmail, ""))
595
0
    {
596
0
        CPLError(CE_Failure, CPLE_AppDefined, "Client email should be set");
597
0
        return false;
598
0
    }
599
0
    if (pszScope == nullptr || EQUAL(pszScope, ""))
600
0
    {
601
0
        CPLError(CE_Failure, CPLE_AppDefined, "Scope should be set");
602
0
        return false;
603
0
    }
604
0
    m_eMethod = SERVICE_ACCOUNT;
605
0
    m_osPrivateKey = pszPrivateKey;
606
0
    m_osClientEmail = pszClientEmail;
607
0
    m_osScope = pszScope;
608
0
    m_aosAdditionalClaims = papszAdditionalClaims;
609
0
    m_aosOptions = papszOptions;
610
0
    return true;
611
0
}
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
const char *GOA2Manager::GetBearer() const
627
397k
{
628
397k
    time_t nCurTime = time(nullptr);
629
397k
    if (nCurTime < m_nExpirationTime - 5)
630
397k
        return m_osCurrentBearer.c_str();
631
632
21
    char **papszRet = nullptr;
633
21
    if (m_eMethod == GCE)
634
21
    {
635
21
        papszRet = GOA2GetAccessTokenFromCloudEngineVM(m_aosOptions.List());
636
21
    }
637
0
    else if (m_eMethod == ACCESS_TOKEN_FROM_REFRESH)
638
0
    {
639
0
        papszRet =
640
0
            GOA2GetAccessTokenEx(m_osRefreshToken.c_str(), m_osClientId.c_str(),
641
0
                                 m_osClientSecret.c_str(), m_aosOptions.List());
642
0
    }
643
0
    else if (m_eMethod == SERVICE_ACCOUNT)
644
0
    {
645
0
        papszRet = GOA2GetAccessTokenFromServiceAccount(
646
0
            m_osPrivateKey, m_osClientEmail, m_osScope,
647
0
            m_aosAdditionalClaims.List(), m_aosOptions.List());
648
0
    }
649
650
21
    m_nExpirationTime = 0;
651
21
    m_osCurrentBearer.clear();
652
21
    const char *pszAccessToken = CSLFetchNameValue(papszRet, "access_token");
653
21
    if (pszAccessToken == nullptr)
654
0
    {
655
0
        CSLDestroy(papszRet);
656
0
        return nullptr;
657
0
    }
658
21
    const char *pszExpires = CSLFetchNameValue(papszRet, "expires_in");
659
21
    if (pszExpires)
660
21
    {
661
21
        m_nExpirationTime = nCurTime + atoi(pszExpires);
662
21
    }
663
21
    m_osCurrentBearer = pszAccessToken;
664
21
    CSLDestroy(papszRet);
665
21
    return m_osCurrentBearer.c_str();
666
21
}