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-04-29 01:40:10 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             :  * 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             : }

Generated by: LCOV version 1.14