LCOV - code coverage report
Current view: top level - port - cpl_google_cloud.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 368 397 92.7 %
Date: 2025-10-22 13:51:22 Functions: 19 19 100.0 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  * Project:  CPL - Common Portability Library
       3             :  * Purpose:  Google Cloud Storage routines
       4             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       5             :  *
       6             :  **********************************************************************
       7             :  * Copyright (c) 2017, Even Rouault <even.rouault at spatialys.com>
       8             :  *
       9             :  * SPDX-License-Identifier: MIT
      10             :  ****************************************************************************/
      11             : 
      12             : #include "cpl_google_cloud.h"
      13             : #include "cpl_vsi_error.h"
      14             : #include "cpl_sha1.h"
      15             : #include "cpl_sha256.h"
      16             : #include "cpl_time.h"
      17             : #include "cpl_http.h"
      18             : #include "cpl_mem_cache.h"
      19             : #include "cpl_aws.h"
      20             : #include "cpl_json.h"
      21             : 
      22             : #include <mutex>
      23             : #include <utility>
      24             : 
      25             : #ifdef HAVE_CURL
      26             : 
      27             : static bool bFirstTimeForDebugMessage = true;
      28             : 
      29             : struct GOA2ManagerCache
      30             : {
      31             :     struct ManagerWithMutex
      32             :     {
      33             :         std::mutex oMutex{};
      34             :         GOA2Manager oManager{};
      35             : 
      36          10 :         explicit ManagerWithMutex(const GOA2Manager &oManagerIn)
      37          10 :             : oManager(oManagerIn)
      38             :         {
      39          10 :         }
      40             :     };
      41             : 
      42             :     std::mutex oMutexGOA2ManagerCache{};
      43             :     lru11::Cache<std::string, std::shared_ptr<ManagerWithMutex>>
      44             :         oGOA2ManagerCache{};
      45             : 
      46          19 :     std::string GetBearer(const GOA2Manager &oManager)
      47             :     {
      48          38 :         const std::string osKey(oManager.GetKey());
      49          19 :         std::shared_ptr<ManagerWithMutex> poSharedManager;
      50             :         {
      51          38 :             std::lock_guard oLock(oMutexGOA2ManagerCache);
      52          19 :             if (!oGOA2ManagerCache.tryGet(osKey, poSharedManager))
      53             :             {
      54          10 :                 poSharedManager = std::make_shared<ManagerWithMutex>(oManager);
      55          10 :                 oGOA2ManagerCache.insert(osKey, poSharedManager);
      56             :             }
      57             :         }
      58             :         {
      59          19 :             std::lock_guard oLock(poSharedManager->oMutex);
      60          19 :             const char *pszBearer = poSharedManager->oManager.GetBearer();
      61          38 :             return std::string(pszBearer ? pszBearer : "");
      62             :         }
      63             :     }
      64             : 
      65         338 :     void clear()
      66             :     {
      67         676 :         std::lock_guard oLock(oMutexGOA2ManagerCache);
      68         338 :         oGOA2ManagerCache.clear();
      69         338 :     }
      70             : 
      71         357 :     static GOA2ManagerCache &GetSingleton()
      72             :     {
      73         357 :         static GOA2ManagerCache goGOA2ManagerCache;
      74         357 :         return goGOA2ManagerCache;
      75             :     }
      76             : };
      77             : 
      78             : /************************************************************************/
      79             : /*                    CPLIsMachineForSureGCEInstance()                  */
      80             : /************************************************************************/
      81             : 
      82             : /** Returns whether the current machine is surely a Google Compute Engine
      83             :  * instance.
      84             :  *
      85             :  * This does a very quick check without network access.
      86             :  * Note: only works for Linux GCE instances.
      87             :  *
      88             :  * @return true if the current machine is surely a GCE instance.
      89             :  */
      90        2569 : bool CPLIsMachineForSureGCEInstance()
      91             : {
      92        2569 :     if (CPLTestBool(CPLGetConfigOption("CPL_MACHINE_IS_GCE", "NO")))
      93             :     {
      94           0 :         return true;
      95             :     }
      96             : #ifdef __linux
      97             :     // If /sys/class/dmi/id/product_name exists, it contains "Google Compute
      98             :     // Engine"
      99        2570 :     bool bIsGCEInstance = false;
     100        2570 :     if (CPLTestBool(CPLGetConfigOption("CPL_GCE_CHECK_LOCAL_FILES", "YES")))
     101             :     {
     102           7 :         static bool bIsGCEInstanceStatic = []()
     103             :         {
     104           7 :             bool bIsGCE = false;
     105           7 :             VSILFILE *fp = VSIFOpenL("/sys/class/dmi/id/product_name", "rb");
     106           7 :             if (fp)
     107             :             {
     108           7 :                 const char *pszLine = CPLReadLineL(fp);
     109           7 :                 bIsGCE =
     110           7 :                     pszLine && STARTS_WITH_CI(pszLine, "Google Compute Engine");
     111           7 :                 VSIFCloseL(fp);
     112             :             }
     113           7 :             return bIsGCE;
     114        2563 :         }();
     115        2563 :         bIsGCEInstance = bIsGCEInstanceStatic;
     116             :     }
     117        2570 :     return bIsGCEInstance;
     118             : #else
     119             :     return false;
     120             : #endif
     121             : }
     122             : 
     123             : /************************************************************************/
     124             : /*                 CPLIsMachinePotentiallyGCEInstance()                 */
     125             : /************************************************************************/
     126             : 
     127             : /** Returns whether the current machine is potentially a Google Compute Engine
     128             :  * instance.
     129             :  *
     130             :  * This does a very quick check without network access. To confirm if the
     131             :  * machine is effectively a GCE instance, metadata.google.internal must be
     132             :  * queried.
     133             :  *
     134             :  * @return true if the current machine is potentially a GCE instance.
     135             :  */
     136           9 : bool CPLIsMachinePotentiallyGCEInstance()
     137             : {
     138             : #ifdef __linux
     139           9 :     bool bIsMachinePotentialGCEInstance = true;
     140           9 :     if (CPLTestBool(CPLGetConfigOption("CPL_GCE_CHECK_LOCAL_FILES", "YES")))
     141             :     {
     142           4 :         bIsMachinePotentialGCEInstance = CPLIsMachineForSureGCEInstance();
     143             :     }
     144           9 :     return bIsMachinePotentialGCEInstance;
     145             : #elif defined(_WIN32)
     146             :     // We might add later a way of detecting if we run on GCE using WMI
     147             :     // See https://cloud.google.com/compute/docs/instances/managing-instances
     148             :     // For now, unconditionally try
     149             :     return true;
     150             : #else
     151             :     // At time of writing GCE instances can be only Linux or Windows
     152             :     return false;
     153             : #endif
     154             : }
     155             : 
     156             : //! @cond Doxygen_Suppress
     157             : 
     158             : /************************************************************************/
     159             : /*                            GetGSHeaders()                            */
     160             : /************************************************************************/
     161             : 
     162          38 : static struct curl_slist *GetGSHeaders(const std::string &osPathForOption,
     163             :                                        const std::string &osVerb,
     164             :                                        struct curl_slist *psHeaders,
     165             :                                        const std::string &osCanonicalResource,
     166             :                                        const std::string &osSecretAccessKey,
     167             :                                        const std::string &osAccessKeyId)
     168             : {
     169          38 :     if (osSecretAccessKey.empty())
     170             :     {
     171             :         // GS_NO_SIGN_REQUEST=YES case
     172           2 :         return psHeaders;
     173             :     }
     174             : 
     175             :     std::string osDate = VSIGetPathSpecificOption(osPathForOption.c_str(),
     176          72 :                                                   "CPL_GS_TIMESTAMP", "");
     177          36 :     if (osDate.empty())
     178             :     {
     179          25 :         osDate = IVSIS3LikeHandleHelper::GetRFC822DateTime();
     180             :     }
     181             : 
     182          72 :     std::map<std::string, std::string> oSortedMapHeaders;
     183             :     std::string osCanonicalizedHeaders(
     184             :         IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
     185          72 :             oSortedMapHeaders, psHeaders, "x-goog-"));
     186             : 
     187             :     // See https://cloud.google.com/storage/docs/migrating
     188          72 :     std::string osStringToSign;
     189          36 :     osStringToSign += osVerb + "\n";
     190          36 :     osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-MD5") + "\n";
     191          36 :     osStringToSign += CPLAWSGetHeaderVal(psHeaders, "Content-Type") + "\n";
     192          36 :     osStringToSign += osDate + "\n";
     193          36 :     osStringToSign += osCanonicalizedHeaders;
     194          36 :     osStringToSign += osCanonicalResource;
     195             : #ifdef DEBUG_VERBOSE
     196             :     CPLDebug("GS", "osStringToSign = %s", osStringToSign.c_str());
     197             : #endif
     198             : 
     199          36 :     GByte abySignature[CPL_SHA1_HASH_SIZE] = {};
     200          72 :     CPL_HMAC_SHA1(osSecretAccessKey.c_str(), osSecretAccessKey.size(),
     201          36 :                   osStringToSign.c_str(), osStringToSign.size(), abySignature);
     202             : 
     203          36 :     char *pszBase64 = CPLBase64Encode(sizeof(abySignature), abySignature);
     204          36 :     std::string osAuthorization("GOOG1 ");
     205          36 :     osAuthorization += osAccessKeyId;
     206          36 :     osAuthorization += ":";
     207          36 :     osAuthorization += pszBase64;
     208          36 :     CPLFree(pszBase64);
     209             : 
     210             :     psHeaders =
     211          36 :         curl_slist_append(psHeaders, CPLSPrintf("Date: %s", osDate.c_str()));
     212          36 :     psHeaders = curl_slist_append(
     213             :         psHeaders, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
     214             : 
     215          36 :     return psHeaders;
     216             : }
     217             : 
     218             : /************************************************************************/
     219             : /*                         VSIGSHandleHelper()                          */
     220             : /************************************************************************/
     221          77 : VSIGSHandleHelper::VSIGSHandleHelper(const std::string &osEndpoint,
     222             :                                      const std::string &osBucketObjectKey,
     223             :                                      const std::string &osSecretAccessKey,
     224             :                                      const std::string &osAccessKeyId,
     225             :                                      bool bUseAuthenticationHeader,
     226             :                                      const GOA2Manager &oManager,
     227          77 :                                      const std::string &osUserProject)
     228          77 :     : m_osURL(osEndpoint + CPLAWSURLEncode(osBucketObjectKey, false)),
     229             :       m_osEndpoint(osEndpoint), m_osBucketObjectKey(osBucketObjectKey),
     230             :       m_osSecretAccessKey(osSecretAccessKey), m_osAccessKeyId(osAccessKeyId),
     231             :       m_bUseAuthenticationHeader(bUseAuthenticationHeader),
     232         154 :       m_oManager(oManager), m_osUserProject(osUserProject)
     233             : {
     234          77 :     if (m_osBucketObjectKey.find('/') == std::string::npos)
     235          12 :         m_osURL += "/";
     236          77 : }
     237             : 
     238             : /************************************************************************/
     239             : /*                        ~VSIGSHandleHelper()                          */
     240             : /************************************************************************/
     241             : 
     242         154 : VSIGSHandleHelper::~VSIGSHandleHelper()
     243             : {
     244         154 : }
     245             : 
     246             : /************************************************************************/
     247             : /*                GetConfigurationFromAWSConfigFiles()                  */
     248             : /************************************************************************/
     249             : 
     250          15 : bool VSIGSHandleHelper::GetConfigurationFromConfigFile(
     251             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
     252             :     std::string &osOAuth2RefreshToken, std::string &osOAuth2ClientId,
     253             :     std::string &osOAuth2ClientSecret, std::string &osCredentials)
     254             : {
     255             : #ifdef _WIN32
     256             :     const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
     257             :     constexpr char SEP_STRING[] = "\\";
     258             : #else
     259          15 :     const char *pszHome = CPLGetConfigOption("HOME", nullptr);
     260          15 :     constexpr char SEP_STRING[] = "/";
     261             : #endif
     262             : 
     263             :     // GDAL specific config option (mostly for testing purpose, but also
     264             :     // used in production in some cases)
     265             :     const char *pszCredentials =
     266          15 :         CPLGetConfigOption("CPL_GS_CREDENTIALS_FILE", nullptr);
     267          15 :     if (pszCredentials)
     268             :     {
     269          15 :         osCredentials = pszCredentials;
     270             :     }
     271             :     else
     272             :     {
     273           0 :         osCredentials = pszHome ? pszHome : "";
     274           0 :         osCredentials += SEP_STRING;
     275           0 :         osCredentials += ".boto";
     276             :     }
     277             : 
     278          15 :     VSILFILE *fp = VSIFOpenL(osCredentials.c_str(), "rb");
     279          15 :     if (fp != nullptr)
     280             :     {
     281             :         const char *pszLine;
     282           3 :         bool bInCredentials = false;
     283           3 :         bool bInOAuth2 = false;
     284          25 :         while ((pszLine = CPLReadLineL(fp)) != nullptr)
     285             :         {
     286          22 :             if (pszLine[0] == '[')
     287             :             {
     288           7 :                 bInCredentials = false;
     289           7 :                 bInOAuth2 = false;
     290             : 
     291           7 :                 if (std::string(pszLine) == "[Credentials]")
     292           3 :                     bInCredentials = true;
     293           4 :                 else if (std::string(pszLine) == "[OAuth2]")
     294           2 :                     bInOAuth2 = true;
     295             :             }
     296          15 :             else if (bInCredentials)
     297             :             {
     298           4 :                 char *pszKey = nullptr;
     299           4 :                 const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
     300           4 :                 if (pszKey && pszValue)
     301             :                 {
     302           4 :                     if (EQUAL(pszKey, "gs_access_key_id"))
     303           1 :                         osAccessKeyId = CPLString(pszValue).Trim();
     304           3 :                     else if (EQUAL(pszKey, "gs_secret_access_key"))
     305           1 :                         osSecretAccessKey = CPLString(pszValue).Trim();
     306           2 :                     else if (EQUAL(pszKey, "gs_oauth2_refresh_token"))
     307           2 :                         osOAuth2RefreshToken = CPLString(pszValue).Trim();
     308             :                 }
     309           4 :                 CPLFree(pszKey);
     310             :             }
     311          11 :             else if (bInOAuth2)
     312             :             {
     313           4 :                 char *pszKey = nullptr;
     314           4 :                 const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
     315           4 :                 if (pszKey && pszValue)
     316             :                 {
     317           4 :                     if (EQUAL(pszKey, "client_id"))
     318           2 :                         osOAuth2ClientId = CPLString(pszValue).Trim();
     319           2 :                     else if (EQUAL(pszKey, "client_secret"))
     320           2 :                         osOAuth2ClientSecret = CPLString(pszValue).Trim();
     321             :                 }
     322           4 :                 CPLFree(pszKey);
     323             :             }
     324             :         }
     325           3 :         VSIFCloseL(fp);
     326             :     }
     327             : 
     328          29 :     return (!osAccessKeyId.empty() && !osSecretAccessKey.empty()) ||
     329          29 :            !osOAuth2RefreshToken.empty();
     330             : }
     331             : 
     332             : /************************************************************************/
     333             : /*                        GetConfiguration()                            */
     334             : /************************************************************************/
     335             : 
     336          86 : bool VSIGSHandleHelper::GetConfiguration(const std::string &osPathForOption,
     337             :                                          CSLConstList papszOptions,
     338             :                                          std::string &osSecretAccessKey,
     339             :                                          std::string &osAccessKeyId,
     340             :                                          bool &bUseAuthenticationHeader,
     341             :                                          GOA2Manager &oManager)
     342             : {
     343          86 :     osSecretAccessKey.clear();
     344          86 :     osAccessKeyId.clear();
     345          86 :     bUseAuthenticationHeader = false;
     346             : 
     347          86 :     if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(),
     348             :                                              "GS_NO_SIGN_REQUEST", "NO")))
     349             :     {
     350           6 :         return true;
     351             :     }
     352             : 
     353             :     osSecretAccessKey = VSIGetPathSpecificOption(osPathForOption.c_str(),
     354          80 :                                                  "GS_SECRET_ACCESS_KEY", "");
     355          80 :     if (!osSecretAccessKey.empty())
     356             :     {
     357             :         osAccessKeyId = VSIGetPathSpecificOption(osPathForOption.c_str(),
     358          51 :                                                  "GS_ACCESS_KEY_ID", "");
     359          51 :         if (osAccessKeyId.empty())
     360             :         {
     361           1 :             VSIError(VSIE_InvalidCredentials,
     362             :                      "GS_ACCESS_KEY_ID configuration option not defined");
     363           1 :             bFirstTimeForDebugMessage = false;
     364           1 :             return false;
     365             :         }
     366             : 
     367          50 :         if (bFirstTimeForDebugMessage)
     368             :         {
     369           8 :             CPLDebug("GS", "Using GS_SECRET_ACCESS_KEY and "
     370             :                            "GS_ACCESS_KEY_ID configuration options");
     371             :         }
     372          50 :         bFirstTimeForDebugMessage = false;
     373          50 :         return true;
     374             :     }
     375             : 
     376             :     const std::string osHeaderFile = VSIGetPathSpecificOption(
     377          58 :         osPathForOption.c_str(), "GDAL_HTTP_HEADER_FILE", "");
     378          29 :     bool bMayWarnDidNotFindAuth = false;
     379          29 :     if (!osHeaderFile.empty())
     380             :     {
     381           2 :         bool bFoundAuth = false;
     382           2 :         VSILFILE *fp = nullptr;
     383             :         // Do not allow /vsicurl/ access from /vsicurl because of
     384             :         // GetCurlHandleFor() e.g. "/vsicurl/,HEADER_FILE=/vsicurl/,url= " would
     385             :         // cause use of memory after free
     386           2 :         if (strstr(osHeaderFile.c_str(), "/vsicurl/") == nullptr &&
     387           2 :             strstr(osHeaderFile.c_str(), "/vsicurl?") == nullptr &&
     388           2 :             strstr(osHeaderFile.c_str(), "/vsis3/") == nullptr &&
     389           2 :             strstr(osHeaderFile.c_str(), "/vsigs/") == nullptr &&
     390           2 :             strstr(osHeaderFile.c_str(), "/vsiaz/") == nullptr &&
     391           6 :             strstr(osHeaderFile.c_str(), "/vsioss/") == nullptr &&
     392           2 :             strstr(osHeaderFile.c_str(), "/vsiswift/") == nullptr)
     393             :         {
     394           2 :             fp = VSIFOpenL(osHeaderFile.c_str(), "rb");
     395             :         }
     396           2 :         if (fp == nullptr)
     397             :         {
     398           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot read %s",
     399             :                      osHeaderFile.c_str());
     400             :         }
     401             :         else
     402             :         {
     403           1 :             const char *pszLine = nullptr;
     404        2028 :             while ((pszLine = CPLReadLineL(fp)) != nullptr)
     405             :             {
     406        2027 :                 if (STARTS_WITH_CI(pszLine, "Authorization:"))
     407             :                 {
     408           0 :                     bFoundAuth = true;
     409           0 :                     break;
     410             :                 }
     411             :             }
     412           1 :             VSIFCloseL(fp);
     413           1 :             if (!bFoundAuth)
     414           1 :                 bMayWarnDidNotFindAuth = true;
     415             :         }
     416           2 :         if (bFoundAuth)
     417             :         {
     418           0 :             if (bFirstTimeForDebugMessage)
     419             :             {
     420           0 :                 CPLDebug("GS", "Using GDAL_HTTP_HEADER_FILE=%s",
     421             :                          osHeaderFile.c_str());
     422             :             }
     423           0 :             bFirstTimeForDebugMessage = false;
     424           0 :             bUseAuthenticationHeader = true;
     425           0 :             return true;
     426             :         }
     427             :     }
     428             : 
     429          29 :     const char *pszHeaders = VSIGetPathSpecificOption(
     430             :         osPathForOption.c_str(), "GDAL_HTTP_HEADERS", nullptr);
     431          29 :     if (pszHeaders && strstr(pszHeaders, "Authorization:") != nullptr)
     432             :     {
     433           3 :         bUseAuthenticationHeader = true;
     434           3 :         return true;
     435             :     }
     436             : 
     437             :     std::string osRefreshToken(VSIGetPathSpecificOption(
     438          52 :         osPathForOption.c_str(), "GS_OAUTH2_REFRESH_TOKEN", ""));
     439          26 :     if (!osRefreshToken.empty())
     440             :     {
     441             :         std::string osClientId = VSIGetPathSpecificOption(
     442           8 :             osPathForOption.c_str(), "GS_OAUTH2_CLIENT_ID", "");
     443             :         std::string osClientSecret = VSIGetPathSpecificOption(
     444           8 :             osPathForOption.c_str(), "GS_OAUTH2_CLIENT_SECRET", "");
     445             : 
     446             :         int nCount =
     447           4 :             (!osClientId.empty() ? 1 : 0) + (!osClientSecret.empty() ? 1 : 0);
     448           4 :         if (nCount == 1)
     449             :         {
     450           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     451             :                      "Either both or none of GS_OAUTH2_CLIENT_ID and "
     452             :                      "GS_OAUTH2_CLIENT_SECRET must be set");
     453           0 :             return false;
     454             :         }
     455             : 
     456           4 :         if (bFirstTimeForDebugMessage)
     457             :         {
     458             :             std::string osMsg(
     459           4 :                 "Using GS_OAUTH2_REFRESH_TOKEN configuration option");
     460           2 :             if (osClientId.empty())
     461           1 :                 osMsg += " and GDAL default client_id/client_secret";
     462             :             else
     463           1 :                 osMsg += " and GS_OAUTH2_CLIENT_ID and GS_OAUTH2_CLIENT_SECRET";
     464           2 :             CPLDebug("GS", "%s", osMsg.c_str());
     465             :         }
     466           4 :         bFirstTimeForDebugMessage = false;
     467             : 
     468           4 :         return oManager.SetAuthFromRefreshToken(
     469             :             osRefreshToken.c_str(), osClientId.c_str(), osClientSecret.c_str(),
     470           4 :             nullptr);
     471             :     }
     472             : 
     473             :     std::string osJsonFile(CSLFetchNameValueDef(
     474             :         papszOptions, "GOOGLE_APPLICATION_CREDENTIALS",
     475             :         VSIGetPathSpecificOption(osPathForOption.c_str(),
     476          44 :                                  "GOOGLE_APPLICATION_CREDENTIALS", "")));
     477          22 :     if (!osJsonFile.empty())
     478             :     {
     479          10 :         CPLJSONDocument oDoc;
     480           5 :         if (!oDoc.Load(osJsonFile))
     481             :         {
     482           0 :             return false;
     483             :         }
     484             : 
     485             :         // JSON file can be of type 'service_account' or 'authorized_user'
     486          15 :         std::string osJsonFileType = oDoc.GetRoot().GetString("type");
     487             : 
     488           5 :         if (strcmp(osJsonFileType.c_str(), "service_account") == 0)
     489             :         {
     490           9 :             CPLString osPrivateKey = oDoc.GetRoot().GetString("private_key");
     491           6 :             osPrivateKey.replaceAll("\\n", "\n")
     492           6 :                 .replaceAll("\n\n", "\n")
     493           3 :                 .replaceAll("\r", "");
     494             :             std::string osClientEmail =
     495           9 :                 oDoc.GetRoot().GetString("client_email");
     496           3 :             const char *pszScope = CSLFetchNameValueDef(
     497             :                 papszOptions, "GS_OAUTH2_SCOPE",
     498             :                 VSIGetPathSpecificOption(
     499             :                     osPathForOption.c_str(), "GS_OAUTH2_SCOPE",
     500             :                     "https://www.googleapis.com/auth/devstorage.read_write"));
     501             : 
     502           3 :             return oManager.SetAuthFromServiceAccount(
     503             :                 osPrivateKey.c_str(), osClientEmail.c_str(), pszScope, nullptr,
     504           3 :                 nullptr);
     505             :         }
     506           2 :         else if (strcmp(osJsonFileType.c_str(), "authorized_user") == 0)
     507             :         {
     508           6 :             std::string osClientId = oDoc.GetRoot().GetString("client_id");
     509             :             std::string osClientSecret =
     510           6 :                 oDoc.GetRoot().GetString("client_secret");
     511           2 :             osRefreshToken = oDoc.GetRoot().GetString("refresh_token");
     512             : 
     513           2 :             return oManager.SetAuthFromRefreshToken(
     514             :                 osRefreshToken.c_str(), osClientId.c_str(),
     515           2 :                 osClientSecret.c_str(), nullptr);
     516             :         }
     517           0 :         return false;
     518             :     }
     519             : 
     520             :     CPLString osPrivateKey = CSLFetchNameValueDef(
     521             :         papszOptions, "GS_OAUTH2_PRIVATE_KEY",
     522             :         VSIGetPathSpecificOption(osPathForOption.c_str(),
     523          34 :                                  "GS_OAUTH2_PRIVATE_KEY", ""));
     524             :     std::string osPrivateKeyFile = CSLFetchNameValueDef(
     525             :         papszOptions, "GS_OAUTH2_PRIVATE_KEY_FILE",
     526             :         VSIGetPathSpecificOption(osPathForOption.c_str(),
     527          34 :                                  "GS_OAUTH2_PRIVATE_KEY_FILE", ""));
     528          17 :     if (!osPrivateKey.empty() || !osPrivateKeyFile.empty())
     529             :     {
     530           2 :         if (!osPrivateKeyFile.empty())
     531             :         {
     532           1 :             VSILFILE *fp = VSIFOpenL(osPrivateKeyFile.c_str(), "rb");
     533           1 :             if (fp == nullptr)
     534             :             {
     535           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
     536             :                          osPrivateKeyFile.c_str());
     537           0 :                 bFirstTimeForDebugMessage = false;
     538           0 :                 return false;
     539             :             }
     540             :             else
     541             :             {
     542           1 :                 char *pabyBuffer = static_cast<char *>(CPLMalloc(32768));
     543           1 :                 size_t nRead = VSIFReadL(pabyBuffer, 1, 32768, fp);
     544           1 :                 osPrivateKey.assign(pabyBuffer, nRead);
     545           1 :                 VSIFCloseL(fp);
     546           1 :                 CPLFree(pabyBuffer);
     547             :             }
     548             :         }
     549           4 :         osPrivateKey.replaceAll("\\n", "\n")
     550           4 :             .replaceAll("\n\n", "\n")
     551           2 :             .replaceAll("\r", "");
     552             : 
     553             :         std::string osClientEmail = CSLFetchNameValueDef(
     554             :             papszOptions, "GS_OAUTH2_CLIENT_EMAIL",
     555             :             VSIGetPathSpecificOption(osPathForOption.c_str(),
     556           4 :                                      "GS_OAUTH2_CLIENT_EMAIL", ""));
     557           2 :         if (osClientEmail.empty())
     558             :         {
     559           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     560             :                      "GS_OAUTH2_CLIENT_EMAIL not defined");
     561           0 :             bFirstTimeForDebugMessage = false;
     562           0 :             return false;
     563             :         }
     564           2 :         const char *pszScope = CSLFetchNameValueDef(
     565             :             papszOptions, "GS_OAUTH2_SCOPE",
     566             :             VSIGetPathSpecificOption(
     567             :                 osPathForOption.c_str(), "GS_OAUTH2_SCOPE",
     568             :                 "https://www.googleapis.com/auth/devstorage.read_write"));
     569             : 
     570           2 :         if (bFirstTimeForDebugMessage)
     571             :         {
     572           2 :             CPLDebug("GS",
     573             :                      "Using %s, GS_OAUTH2_CLIENT_EMAIL and GS_OAUTH2_SCOPE=%s "
     574             :                      "configuration options",
     575           2 :                      !osPrivateKeyFile.empty() ? "GS_OAUTH2_PRIVATE_KEY_FILE"
     576             :                                                : "GS_OAUTH2_PRIVATE_KEY",
     577             :                      pszScope);
     578             :         }
     579           2 :         bFirstTimeForDebugMessage = false;
     580             : 
     581           2 :         return oManager.SetAuthFromServiceAccount(osPrivateKey.c_str(),
     582             :                                                   osClientEmail.c_str(),
     583           2 :                                                   pszScope, nullptr, nullptr);
     584             :     }
     585             : 
     586             :     // Next try reading from ~/.boto
     587          30 :     std::string osCredentials;
     588          30 :     std::string osOAuth2RefreshToken;
     589          30 :     std::string osOAuth2ClientId;
     590          30 :     std::string osOAuth2ClientSecret;
     591          15 :     if (GetConfigurationFromConfigFile(osSecretAccessKey, osAccessKeyId,
     592             :                                        osOAuth2RefreshToken, osOAuth2ClientId,
     593             :                                        osOAuth2ClientSecret, osCredentials))
     594             :     {
     595           3 :         if (!osOAuth2RefreshToken.empty())
     596             :         {
     597             :             std::string osClientId =
     598           4 :                 CPLGetConfigOption("GS_OAUTH2_CLIENT_ID", "");
     599             :             std::string osClientSecret =
     600           4 :                 CPLGetConfigOption("GS_OAUTH2_CLIENT_SECRET", "");
     601           2 :             bool bClientInfoFromEnv = false;
     602           2 :             bool bClientInfoFromFile = false;
     603             : 
     604           2 :             const int nCountClientIdSecret = (!osClientId.empty() ? 1 : 0) +
     605           2 :                                              (!osClientSecret.empty() ? 1 : 0);
     606           2 :             if (nCountClientIdSecret == 1)
     607             :             {
     608           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     609             :                          "Either both or none of GS_OAUTH2_CLIENT_ID and "
     610             :                          "GS_OAUTH2_CLIENT_SECRET must be set");
     611           0 :                 return false;
     612             :             }
     613           2 :             else if (nCountClientIdSecret == 2)
     614             :             {
     615           0 :                 bClientInfoFromEnv = true;
     616             :             }
     617           2 :             else if (nCountClientIdSecret == 0)
     618             :             {
     619           2 :                 int nCountOAuth2IdSecret = (!osOAuth2ClientId.empty() ? 1 : 0);
     620           2 :                 nCountOAuth2IdSecret += (!osOAuth2ClientSecret.empty() ? 1 : 0);
     621           2 :                 if (nCountOAuth2IdSecret == 1)
     622             :                 {
     623           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
     624             :                              "Either both or none of client_id and "
     625             :                              "client_secret from %s must be set",
     626             :                              osCredentials.c_str());
     627           0 :                     return false;
     628             :                 }
     629           2 :                 else if (nCountOAuth2IdSecret == 2)
     630             :                 {
     631           2 :                     osClientId = std::move(osOAuth2ClientId);
     632           2 :                     osClientSecret = std::move(osOAuth2ClientSecret);
     633           2 :                     bClientInfoFromFile = true;
     634             :                 }
     635             :             }
     636             : 
     637           2 :             if (bFirstTimeForDebugMessage)
     638             :             {
     639           2 :                 CPLString osMsg;
     640             :                 osMsg.Printf("Using gs_oauth2_refresh_token from %s",
     641           1 :                              osCredentials.c_str());
     642           1 :                 if (bClientInfoFromEnv)
     643             :                     osMsg += " and GS_OAUTH2_CLIENT_ID and "
     644           0 :                              "GS_OAUTH2_CLIENT_SECRET configuration options";
     645           1 :                 else if (bClientInfoFromFile)
     646             :                     osMsg +=
     647             :                         CPLSPrintf(" and client_id and client_secret from %s",
     648           1 :                                    osCredentials.c_str());
     649             :                 else
     650           0 :                     osMsg += " and GDAL default client_id/client_secret";
     651           1 :                 CPLDebug("GS", "%s", osMsg.c_str());
     652             :             }
     653           2 :             bFirstTimeForDebugMessage = false;
     654             : 
     655           2 :             return oManager.SetAuthFromRefreshToken(
     656             :                 osOAuth2RefreshToken.c_str(), osClientId.c_str(),
     657           2 :                 osClientSecret.c_str(), nullptr);
     658             :         }
     659             :         else
     660             :         {
     661           1 :             if (bFirstTimeForDebugMessage)
     662             :             {
     663           1 :                 CPLDebug(
     664             :                     "GS",
     665             :                     "Using gs_access_key_id and gs_secret_access_key from %s",
     666             :                     osCredentials.c_str());
     667             :             }
     668           1 :             bFirstTimeForDebugMessage = false;
     669           1 :             return true;
     670             :         }
     671             :     }
     672             : 
     673             :     // Some Travis-CI workers are GCE machines, and for some tests, we don't
     674             :     // want this code path to be taken. And on AppVeyor/Window, we would also
     675             :     // attempt a network access
     676          19 :     if (!CPLTestBool(CPLGetConfigOption("CPL_GCE_SKIP", "NO")) &&
     677           7 :         CPLIsMachinePotentiallyGCEInstance())
     678             :     {
     679           4 :         oManager.SetAuthFromGCE(nullptr);
     680             : 
     681           4 :         if (!GOA2ManagerCache::GetSingleton().GetBearer(oManager).empty())
     682             :         {
     683           4 :             CPLDebug("GS", "Using GCE inherited permissions");
     684             : 
     685           4 :             bFirstTimeForDebugMessage = false;
     686           4 :             return true;
     687             :         }
     688             :     }
     689             : 
     690           8 :     if (bMayWarnDidNotFindAuth)
     691             :     {
     692           1 :         CPLDebug("GS", "Cannot find Authorization header in %s",
     693             :                  CPLGetConfigOption("GDAL_HTTP_HEADER_FILE", ""));
     694             :     }
     695             : 
     696           8 :     CPLString osMsg;
     697             :     osMsg.Printf(
     698             :         "No valid GCS credentials found. "
     699             :         "For authenticated requests, you need to set "
     700             :         "GS_SECRET_ACCESS_KEY, GS_ACCESS_KEY_ID, GS_OAUTH2_REFRESH_TOKEN, "
     701             :         "GOOGLE_APPLICATION_CREDENTIALS, or other configuration "
     702             :         "options, or create a %s file. Consult "
     703             :         "https://gdal.org/en/stable/user/"
     704             :         "virtual_file_systems.html#vsigs-google-cloud-storage-files "
     705             :         "for more details. "
     706             :         "For unauthenticated requests on public resources, set the "
     707             :         "GS_NO_SIGN_REQUEST configuration option to YES.",
     708           8 :         osCredentials.c_str());
     709             : 
     710           8 :     CPLDebug("GS", "%s", osMsg.c_str());
     711           8 :     VSIError(VSIE_InvalidCredentials, "%s", osMsg.c_str());
     712           8 :     return false;
     713             : }
     714             : 
     715             : /************************************************************************/
     716             : /*                          BuildFromURI()                              */
     717             : /************************************************************************/
     718             : 
     719          86 : VSIGSHandleHelper *VSIGSHandleHelper::BuildFromURI(
     720             :     const char *pszURI, const char * /*pszFSPrefix*/,
     721             :     const char *pszURIForPathSpecificOption, CSLConstList papszOptions)
     722             : {
     723         172 :     std::string osPathForOption("/vsigs/");
     724             :     osPathForOption +=
     725          86 :         pszURIForPathSpecificOption ? pszURIForPathSpecificOption : pszURI;
     726             : 
     727             :     // pszURI == bucket/object
     728         172 :     const std::string osBucketObject(pszURI);
     729             :     std::string osEndpoint(VSIGetPathSpecificOption(osPathForOption.c_str(),
     730         172 :                                                     "CPL_GS_ENDPOINT", ""));
     731          86 :     if (osEndpoint.empty())
     732          15 :         osEndpoint = "https://storage.googleapis.com/";
     733             : 
     734         172 :     std::string osSecretAccessKey;
     735         172 :     std::string osAccessKeyId;
     736             :     bool bUseAuthenticationHeader;
     737         172 :     GOA2Manager oManager;
     738             : 
     739          86 :     if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
     740             :                           osAccessKeyId, bUseAuthenticationHeader, oManager))
     741             :     {
     742           9 :         return nullptr;
     743             :     }
     744             : 
     745             :     // https://cloud.google.com/storage/docs/xml-api/reference-headers#xgooguserproject
     746             :     // The Project ID for an existing Google Cloud project to bill for access
     747             :     // charges associated with the request.
     748             :     const std::string osUserProject = VSIGetPathSpecificOption(
     749          77 :         osPathForOption.c_str(), "GS_USER_PROJECT", "");
     750             : 
     751             :     return new VSIGSHandleHelper(osEndpoint, osBucketObject, osSecretAccessKey,
     752             :                                  osAccessKeyId, bUseAuthenticationHeader,
     753          77 :                                  oManager, osUserProject);
     754             : }
     755             : 
     756             : /************************************************************************/
     757             : /*                           RebuildURL()                               */
     758             : /************************************************************************/
     759             : 
     760          62 : void VSIGSHandleHelper::RebuildURL()
     761             : {
     762          62 :     m_osURL = m_osEndpoint + CPLAWSURLEncode(m_osBucketObjectKey, false);
     763         122 :     if (!m_osBucketObjectKey.empty() &&
     764          60 :         m_osBucketObjectKey.find('/') == std::string::npos)
     765          30 :         m_osURL += "/";
     766          62 :     m_osURL += GetQueryString(false);
     767          62 : }
     768             : 
     769             : /************************************************************************/
     770             : /*                           UsesHMACKey()                              */
     771             : /************************************************************************/
     772             : 
     773           5 : bool VSIGSHandleHelper::UsesHMACKey() const
     774             : {
     775           5 :     return m_oManager.GetAuthMethod() == GOA2Manager::NONE;
     776             : }
     777             : 
     778             : /************************************************************************/
     779             : /*                           GetCurlHeaders()                           */
     780             : /************************************************************************/
     781             : 
     782             : struct curl_slist *
     783          56 : VSIGSHandleHelper::GetCurlHeaders(const std::string &osVerb,
     784             :                                   struct curl_slist *psHeaders, const void *,
     785             :                                   size_t) const
     786             : {
     787          56 :     if (m_bUseAuthenticationHeader)
     788           3 :         return psHeaders;
     789             : 
     790          53 :     if (!m_osUserProject.empty())
     791             :     {
     792             :         psHeaders =
     793           3 :             curl_slist_append(psHeaders, CPLSPrintf("x-goog-user-project: %s",
     794             :                                                     m_osUserProject.c_str()));
     795             :     }
     796             : 
     797          53 :     if (m_oManager.GetAuthMethod() != GOA2Manager::NONE)
     798             :     {
     799             :         const std::string osBearer =
     800          15 :             GOA2ManagerCache::GetSingleton().GetBearer(m_oManager);
     801          15 :         if (!osBearer.empty())
     802             :         {
     803          15 :             psHeaders = curl_slist_append(
     804             :                 psHeaders,
     805             :                 CPLSPrintf("Authorization: Bearer %s", osBearer.c_str()));
     806             :         }
     807          15 :         return psHeaders;
     808             :     }
     809             : 
     810             :     std::string osCanonicalResource(
     811          38 :         "/" + CPLAWSURLEncode(m_osBucketObjectKey, false));
     812          75 :     if (!m_osBucketObjectKey.empty() &&
     813          37 :         m_osBucketObjectKey.find('/') == std::string::npos)
     814           8 :         osCanonicalResource += "/";
     815             :     else
     816             :     {
     817          60 :         const auto osQueryString(GetQueryString(false));
     818          30 :         if (osQueryString == "?uploads" || osQueryString == "?acl")
     819           4 :             osCanonicalResource += osQueryString;
     820             :     }
     821             : 
     822          76 :     return GetGSHeaders("/vsigs/" + m_osBucketObjectKey, osVerb, psHeaders,
     823          38 :                         osCanonicalResource, m_osSecretAccessKey,
     824          76 :                         m_osAccessKeyId);
     825             : }
     826             : 
     827             : /************************************************************************/
     828             : /*                          ClearCache()                                */
     829             : /************************************************************************/
     830             : 
     831         338 : void VSIGSHandleHelper::ClearCache()
     832             : {
     833         338 :     GOA2ManagerCache::GetSingleton().clear();
     834         338 :     bFirstTimeForDebugMessage = true;
     835         338 : }
     836             : 
     837             : /************************************************************************/
     838             : /*                           GetSignedURL()                             */
     839             : /************************************************************************/
     840             : 
     841           5 : std::string VSIGSHandleHelper::GetSignedURL(CSLConstList papszOptions)
     842             : {
     843           8 :     if (!((!m_osAccessKeyId.empty() && !m_osSecretAccessKey.empty()) ||
     844           3 :           m_oManager.GetAuthMethod() == GOA2Manager::SERVICE_ACCOUNT))
     845             :     {
     846           2 :         CPLError(CE_Failure, CPLE_NotSupported,
     847             :                  "Signed URL for Google Cloud Storage is only available with "
     848             :                  "AWS style authentication with "
     849             :                  "GS_ACCESS_KEY_ID+GS_SECRET_ACCESS_KEY, "
     850             :                  "or with service account authentication");
     851           2 :         return std::string();
     852             :     }
     853             : 
     854           3 :     GIntBig nStartDate = static_cast<GIntBig>(time(nullptr));
     855           3 :     const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
     856           3 :     if (pszStartDate)
     857             :     {
     858             :         int nYear, nMonth, nDay, nHour, nMin, nSec;
     859           3 :         if (sscanf(pszStartDate, "%04d%02d%02dT%02d%02d%02dZ", &nYear, &nMonth,
     860           3 :                    &nDay, &nHour, &nMin, &nSec) == 6)
     861             :         {
     862             :             struct tm brokendowntime;
     863           3 :             brokendowntime.tm_year = nYear - 1900;
     864           3 :             brokendowntime.tm_mon = nMonth - 1;
     865           3 :             brokendowntime.tm_mday = nDay;
     866           3 :             brokendowntime.tm_hour = nHour;
     867           3 :             brokendowntime.tm_min = nMin;
     868           3 :             brokendowntime.tm_sec = nSec;
     869           3 :             nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
     870             :         }
     871             :     }
     872             :     GIntBig nExpiresIn =
     873             :         nStartDate +
     874           3 :         atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
     875             :     std::string osExpires(CSLFetchNameValueDef(
     876           6 :         papszOptions, "EXPIRES", CPLSPrintf(CPL_FRMT_GIB, nExpiresIn)));
     877             : 
     878           6 :     std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
     879             : 
     880             :     std::string osCanonicalizedResource(
     881           6 :         "/" + CPLAWSURLEncode(m_osBucketObjectKey, false));
     882             : 
     883           6 :     std::string osStringToSign;
     884           3 :     osStringToSign += osVerb + "\n";
     885           3 :     osStringToSign += "\n";  // Content_MD5
     886           3 :     osStringToSign += "\n";  // Content_Type
     887           3 :     osStringToSign += osExpires + "\n";
     888             :     // osStringToSign += // Canonicalized_Extension_Headers
     889           3 :     osStringToSign += osCanonicalizedResource;
     890             : #ifdef DEBUG_VERBOSE
     891             :     CPLDebug("GS", "osStringToSign = %s", osStringToSign.c_str());
     892             : #endif
     893             : 
     894           3 :     if (!m_osAccessKeyId.empty())
     895             :     {
     896             :         // No longer documented but actually works !
     897           2 :         GByte abySignature[CPL_SHA1_HASH_SIZE] = {};
     898           4 :         CPL_HMAC_SHA1(m_osSecretAccessKey.c_str(), m_osSecretAccessKey.size(),
     899           2 :                       osStringToSign.c_str(), osStringToSign.size(),
     900             :                       abySignature);
     901             : 
     902           2 :         char *pszBase64 = CPLBase64Encode(sizeof(abySignature), abySignature);
     903           2 :         std::string osSignature(pszBase64);
     904           2 :         CPLFree(pszBase64);
     905             : 
     906           2 :         ResetQueryParameters();
     907           2 :         AddQueryParameter("GoogleAccessId", m_osAccessKeyId);
     908           2 :         AddQueryParameter("Expires", osExpires);
     909           2 :         AddQueryParameter("Signature", osSignature);
     910             :     }
     911             :     else
     912             :     {
     913           1 :         unsigned nSignatureLen = 0;
     914           1 :         GByte *pabySignature = CPL_RSA_SHA256_Sign(
     915           1 :             m_oManager.GetPrivateKey().c_str(), osStringToSign.data(),
     916           1 :             static_cast<unsigned>(osStringToSign.size()), &nSignatureLen);
     917           1 :         if (pabySignature == nullptr)
     918           0 :             return std::string();
     919           1 :         char *pszBase64 = CPLBase64Encode(nSignatureLen, pabySignature);
     920           1 :         CPLFree(pabySignature);
     921           1 :         std::string osSignature(pszBase64);
     922           1 :         CPLFree(pszBase64);
     923             : 
     924           1 :         ResetQueryParameters();
     925           1 :         AddQueryParameter("GoogleAccessId", m_oManager.GetClientEmail());
     926           1 :         AddQueryParameter("Expires", osExpires);
     927           1 :         AddQueryParameter("Signature", osSignature);
     928             :     }
     929           3 :     return m_osURL;
     930             : }
     931             : 
     932             : #endif
     933             : 
     934             : //! @endcond

Generated by: LCOV version 1.14