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

Generated by: LCOV version 1.14