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

Generated by: LCOV version 1.14