LCOV - code coverage report
Current view: top level - port - cpl_google_oauth2.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 118 198 59.6 %
Date: 2024-11-21 22:18:42 Functions: 8 11 72.7 %

          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             : }

Generated by: LCOV version 1.14