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

Generated by: LCOV version 1.14