EVOLUTION-MANAGER
Edit File: cpl_google_oauth2.cpp
/****************************************************************************** * $Id$ * * Project: Common Portability Library * Purpose: Google OAuth2 Authentication Services * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2013, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "cpl_http.h" CPL_CVSID("$Id$"); /* ==================================================================== */ /* Values related to OAuth2 authorization to use fusion */ /* tables. Many of these values are related to the */ /* gdalautotest@gmail.com account for GDAL managed by Even */ /* Rouault and Frank Warmerdam. Some information about OAuth2 */ /* as managed by that account can be found at the following url */ /* when logged in as gdalautotest@gmail.com: */ /* */ /* https://code.google.com/apis/console/#project:265656308688:access*/ /* */ /* Applications wanting to use their own client id and secret */ /* can set the following configuration options: */ /* - GOA2_CLIENT_ID */ /* - GOA2_CLIENT_SECRET */ /* ==================================================================== */ #define GDAL_CLIENT_ID "265656308688.apps.googleusercontent.com" #define GDAL_CLIENT_SECRET "0IbTUDOYzaL6vnIdWTuQnvLz" #define GOOGLE_AUTH_URL "https://accounts.google.com/o/oauth2" /************************************************************************/ /* ParseSimpleJson() */ /* */ /* Return a string list of name/value pairs extracted from a */ /* JSON doc. The Google OAuth2 web service returns simple JSON */ /* responses. The parsing as done currently is very fragile */ /* and depends on JSON documents being in a very very simple */ /* form. */ /************************************************************************/ static CPLStringList ParseSimpleJson(const char *pszJson) { /* -------------------------------------------------------------------- */ /* We are expecting simple documents like the following with no */ /* heirarchy or complex structure. */ /* -------------------------------------------------------------------- */ /* { "access_token":"1/fFBGRNJru1FQd44AzqT3Zg", "expires_in":3920, "token_type":"Bearer" } */ CPLStringList oWords( CSLTokenizeString2(pszJson, " \n\t,:{}", CSLT_HONOURSTRINGS )); CPLStringList oNameValue; for( int i=0; i < oWords.size(); i += 2 ) { oNameValue.SetNameValue( oWords[i], oWords[i+1] ); } return oNameValue; } /************************************************************************/ /* GOA2GetAuthorizationURL() */ /************************************************************************/ /** * Return authorization url for a given scope. * * Returns the URL that a user should visit, and use for authentication * in order to get an "auth token" indicating their willingness to use a * service. * * Note that when the user visits this url they will be asked to login * (using a google/gmail/etc) account, and to authorize use of the * requested scope for the application "GDAL/OGR". Once they have done * so, they will be presented with a lengthy string they should "enter * into their application". This is the "auth token" to be passed to * GOA2GetRefreshToken(). The "auth token" can only be used once. * * This function should never fail. * * @param pszScope the service being requested, not yet URL encoded, such as * "https://www.googleapis.com/auth/fusiontables". * * @return the URL to visit - should be freed with CPLFree(). */ char *GOA2GetAuthorizationURL(const char *pszScope) { CPLString osScope; CPLString osURL; osScope.Seize(CPLEscapeString(pszScope, -1, CPLES_URL)); osURL.Printf( "%s/auth?scope=%s&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=%s", GOOGLE_AUTH_URL, osScope.c_str(), CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID)); return CPLStrdup(osURL); } /************************************************************************/ /* GOA2GetRefreshToken() */ /************************************************************************/ /** * Turn Auth Token into a Refresh Token. * * A one time "auth token" provided by the user is turned into a * reusable "refresh token" using a google oauth2 web service. * * A CPLError will be reported if the translation fails for some reason. * Common reasons include the auth token already having been used before, * it not being appropriate for the passed scope and configured client api * or http connection problems. NULL is returned on error. * * @param pszAuthToken the authorization token from the user. * @param pszScope the scope for which it is valid. * * @return refresh token, to be freed with CPLFree(), null on failure. */ char CPL_DLL *GOA2GetRefreshToken( const char *pszAuthToken, const char *pszScope ) { /* -------------------------------------------------------------------- */ /* Prepare request. */ /* -------------------------------------------------------------------- */ CPLString osItem; CPLStringList oOptions; oOptions.AddString( "HEADERS=Content-Type: application/x-www-form-urlencoded" ); osItem.Printf( "POSTFIELDS=" "code=%s" "&client_id=%s" "&client_secret=%s" "&redirect_uri=urn:ietf:wg:oauth:2.0:oob" "&grant_type=authorization_code", pszAuthToken, CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID), CPLGetConfigOption("GOA2_CLIENT_SECRET", GDAL_CLIENT_SECRET)); oOptions.AddString(osItem); /* -------------------------------------------------------------------- */ /* Submit request by HTTP. */ /* -------------------------------------------------------------------- */ CPLHTTPResult * psResult = CPLHTTPFetch( GOOGLE_AUTH_URL "/token", oOptions); if (psResult == NULL) return NULL; /* -------------------------------------------------------------------- */ /* One common mistake is to try and reuse the auth token. */ /* After the first use it will return invalid_grant. */ /* -------------------------------------------------------------------- */ if( psResult->pabyData != NULL && strstr((const char *) psResult->pabyData,"invalid_grant") != NULL) { CPLString osURL; osURL.Seize( GOA2GetAuthorizationURL(pszScope) ); CPLError( CE_Failure, CPLE_AppDefined, "Attempt to use a OAuth2 authorization code multiple times.\n" "Request a fresh authorization token at\n%s.", osURL.c_str() ); CPLHTTPDestroyResult(psResult); return NULL; } if (psResult->pabyData == NULL || psResult->pszErrBuf != NULL) { if( psResult->pszErrBuf != NULL ) CPLDebug( "GOA2", "%s", psResult->pszErrBuf ); if( psResult->pabyData != NULL ) CPLDebug( "GOA2", "%s", psResult->pabyData ); CPLError( CE_Failure, CPLE_AppDefined, "Fetching OAuth2 access code from auth code failed."); CPLHTTPDestroyResult(psResult); return NULL; } CPLDebug( "GOA2", "Access Token Response:\n%s", (const char *) psResult->pabyData ); /* -------------------------------------------------------------------- */ /* This response is in JSON and will look something like: */ /* -------------------------------------------------------------------- */ /* { "access_token" : "ya29.AHES6ZToqkIJkat5rIqMixR1b8PlWBACNO8OYbqqV-YF1Q13E2Kzjw", "token_type" : "Bearer", "expires_in" : 3600, "refresh_token" : "1/eF88pciwq9Tp_rHEhuiIv9AS44Ufe4GOymGawTVPGYo" } */ CPLStringList oResponse = ParseSimpleJson( (const char *) psResult->pabyData ); CPLHTTPDestroyResult(psResult); CPLString osAccessToken = oResponse.FetchNameValueDef( "access_token", "" ); CPLString osRefreshToken = oResponse.FetchNameValueDef( "refresh_token", "" ); CPLDebug("GOA2", "Access Token : '%s'", osAccessToken.c_str()); CPLDebug("GOA2", "Refresh Token : '%s'", osRefreshToken.c_str()); if( osRefreshToken.size() == 0) { CPLError( CE_Failure, CPLE_AppDefined, "Unable to identify a refresh token in the OAuth2 response."); return NULL; } else { // Currently we discard the access token and just return the refresh token return CPLStrdup(osRefreshToken); } } /************************************************************************/ /* GOA2GetAccessToken() */ /************************************************************************/ /** * Fetch access token using refresh token. * * The permanent refresh token is used to fetch a temporary (usually one * hour) access token using Google OAuth2 web services. * * A CPLError will be reported if the request fails for some reason. * Common reasons include the refresh token having been revoked by the * user or http connection problems. * * @param pszRefreshToken the refresh token from GOA2GetRefreshToken(). * @param pszScope the scope for which it is valid. * * @return access token, to be freed with CPLFree(), null on failure. */ char *GOA2GetAccessToken( const char *pszRefreshToken, CPL_UNUSED const char *pszScope ) { /* -------------------------------------------------------------------- */ /* Prepare request. */ /* -------------------------------------------------------------------- */ CPLString osItem; CPLStringList oOptions; oOptions.AddString( "HEADERS=Content-Type: application/x-www-form-urlencoded" ); osItem.Printf( "POSTFIELDS=" "refresh_token=%s" "&client_id=%s" "&client_secret=%s" "&grant_type=refresh_token", pszRefreshToken, CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID), CPLGetConfigOption("GOA2_CLIENT_SECRET", GDAL_CLIENT_SECRET)); oOptions.AddString(osItem); /* -------------------------------------------------------------------- */ /* Submit request by HTTP. */ /* -------------------------------------------------------------------- */ CPLHTTPResult *psResult = CPLHTTPFetch(GOOGLE_AUTH_URL "/token", oOptions); if (psResult == NULL) return NULL; if (psResult->pabyData == NULL || psResult->pszErrBuf != NULL) { if( psResult->pszErrBuf != NULL ) CPLDebug( "GFT", "%s", psResult->pszErrBuf ); if( psResult->pabyData != NULL ) CPLDebug( "GFT", "%s", psResult->pabyData ); CPLError( CE_Failure, CPLE_AppDefined, "Fetching OAuth2 access code from auth code failed."); CPLHTTPDestroyResult(psResult); return NULL; } CPLDebug( "GOA2", "Refresh Token Response:\n%s", (const char *) psResult->pabyData ); /* -------------------------------------------------------------------- */ /* This response is in JSON and will look something like: */ /* -------------------------------------------------------------------- */ /* { "access_token":"1/fFBGRNJru1FQd44AzqT3Zg", "expires_in":3920, "token_type":"Bearer" } */ CPLStringList oResponse = ParseSimpleJson( (const char *) psResult->pabyData ); CPLHTTPDestroyResult(psResult); CPLString osAccessToken = oResponse.FetchNameValueDef( "access_token", "" ); CPLDebug("GOA2", "Access Token : '%s'", osAccessToken.c_str()); if (osAccessToken.size() == 0) { CPLError( CE_Failure, CPLE_AppDefined, "Unable to identify an access token in the OAuth2 response."); return NULL; } else return CPLStrdup(osAccessToken); }