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

Generated by: LCOV version 1.14