LCOV - code coverage report
Current view: directory - port - cpl_google_oauth2.cpp (source / functions) Found Hit Coverage
Test: gdal_filtered.info Lines: 73 22 30.1 %
Date: 2013-03-30 Functions: 4 2 50.0 %

       1                 : /******************************************************************************
       2                 :  * $Id$
       3                 :  *
       4                 :  * Project:  Common Portability Library
       5                 :  * Purpose:  Google OAuth2 Authentication Services
       6                 :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       7                 :  *
       8                 :  ******************************************************************************
       9                 :  * Copyright (c) 2013, Frank Warmerdam 
      10                 :  *
      11                 :  * Permission is hereby granted, free of charge, to any person obtaining a
      12                 :  * copy of this software and associated documentation files (the "Software"),
      13                 :  * to deal in the Software without restriction, including without limitation
      14                 :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15                 :  * and/or sell copies of the Software, and to permit persons to whom the
      16                 :  * Software is furnished to do so, subject to the following conditions:
      17                 :  *
      18                 :  * The above copyright notice and this permission notice shall be included
      19                 :  * in all copies or substantial portions of the Software.
      20                 :  *
      21                 :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      22                 :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23                 :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      24                 :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25                 :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26                 :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27                 :  * DEALINGS IN THE SOFTWARE.
      28                 :  ****************************************************************************/
      29                 : 
      30                 : #include "cpl_http.h"
      31                 : 
      32                 : CPL_CVSID("$Id$");
      33                 : 
      34                 : /* ==================================================================== */
      35                 : /*      Values related to OAuth2 authorization to use fusion            */
      36                 : /*      tables.  Many of these values are related to the                */
      37                 : /*      gdalautotest@gmail.com account for GDAL managed by Even         */
      38                 : /*      Rouault and Frank Warmerdam.  Some information about OAuth2     */
      39                 : /*      as managed by that account can be found at the following url    */
      40                 : /*      when logged in as gdalautotest@gmail.com:                       */
      41                 : /*                                                                      */
      42                 : /*      https://code.google.com/apis/console/#project:265656308688:access*/
      43                 : /*                                                                      */
      44                 : /*      Applications wanting to use their own client id and secret      */
      45                 : /*      can set the following configuration options:                    */
      46                 : /*       - GOA2_CLIENT_ID                                               */
      47                 : /*       - GOA2_CLIENT_SECRET                                           */
      48                 : /* ==================================================================== */
      49                 : #define GDAL_CLIENT_ID "265656308688.apps.googleusercontent.com"
      50                 : #define GDAL_CLIENT_SECRET "0IbTUDOYzaL6vnIdWTuQnvLz"
      51                 : 
      52                 : #define GOOGLE_AUTH_URL "https://accounts.google.com/o/oauth2"
      53                 : 
      54                 : /************************************************************************/
      55                 : /*                          ParseSimpleJson()                           */
      56                 : /*                                                                      */
      57                 : /*      Return a string list of name/value pairs extracted from a       */
      58                 : /*      JSON doc.  The Google OAuth2 web service returns simple JSON    */
      59                 : /*      responses.  The parsing as done currently is very fragile       */
      60                 : /*      and depends on JSON documents being in a very very simple       */
      61                 : /*      form.                                                           */
      62                 : /************************************************************************/
      63                 : 
      64               7 : static CPLStringList ParseSimpleJson(const char *pszJson)
      65                 : 
      66                 : {
      67                 : /* -------------------------------------------------------------------- */
      68                 : /*      We are expecting simple documents like the following with no    */
      69                 : /*      heirarchy or complex structure.                                 */
      70                 : /* -------------------------------------------------------------------- */
      71                 : /*    
      72                 :     {
      73                 :         "access_token":"1/fFBGRNJru1FQd44AzqT3Zg",
      74                 :         "expires_in":3920,
      75                 :         "token_type":"Bearer"
      76                 :     }
      77                 : */
      78                 : 
      79                 :     CPLStringList oWords(
      80               7 :         CSLTokenizeString2(pszJson, " \n\t,:{}", CSLT_HONOURSTRINGS ));
      81               7 :     CPLStringList oNameValue;
      82                 : 
      83              28 :     for( int i=0; i < oWords.size(); i += 2 )
      84                 :     {
      85              21 :         oNameValue.SetNameValue( oWords[i], oWords[i+1] );
      86                 :     }
      87                 :     
      88               7 :     return oNameValue;
      89                 : }
      90                 : 
      91                 : /************************************************************************/
      92                 : /*                      GOA2GetAuthorizationURL()                       */
      93                 : /************************************************************************/
      94                 : 
      95                 : /**
      96                 :  * Return authorization url for a given scope. 
      97                 :  *
      98                 :  * Returns the URL that a user should visit, and use for authentication
      99                 :  * in order to get an "auth token" indicating their willingness to use a 
     100                 :  * service. 
     101                 :  *
     102                 :  * Note that when the user visits this url they will be asked to login
     103                 :  * (using a google/gmail/etc) account, and to authorize use of the 
     104                 :  * requested scope for the application "GDAL/OGR".  Once they have done 
     105                 :  * so, they will be presented with a lengthy string they should "enter
     106                 :  * into their application".  This is the "auth token" to be passed to
     107                 :  * GOA2GetRefreshToken().  The "auth token" can only be used once. 
     108                 :  * 
     109                 :  * This function should never fail. 
     110                 :  *
     111                 :  * @param pszScope the service being requested, not yet URL encoded, such as 
     112                 :  * "https://www.googleapis.com/auth/fusiontables". 
     113                 :  * 
     114                 :  * @return the URL to visit - should be freed with CPLFree().
     115                 :  */
     116                 : 
     117               0 : char *GOA2GetAuthorizationURL(const char *pszScope)
     118                 : 
     119                 : {
     120               0 :     CPLString osScope;
     121               0 :     CPLString osURL;
     122                 : 
     123               0 :     osScope.Seize(CPLEscapeString(pszScope, -1, CPLES_URL));
     124                 :     osURL.Printf( "%s/auth?scope=%s&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=%s",
     125                 :                   GOOGLE_AUTH_URL,
     126                 :                   osScope.c_str(), 
     127               0 :                   CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID));
     128               0 :     return CPLStrdup(osURL);
     129                 : }
     130                 : 
     131                 : /************************************************************************/
     132                 : /*                        GOA2GetRefreshToken()                         */
     133                 : /************************************************************************/
     134                 : 
     135                 : /**
     136                 :  * Turn Auth Token into a Refresh Token. 
     137                 :  * 
     138                 :  * A one time "auth token" provided by the user is turned into a 
     139                 :  * reusable "refresh token" using a google oauth2 web service.
     140                 :  *
     141                 :  * A CPLError will be reported if the translation fails for some reason.
     142                 :  * Common reasons include the auth token already having been used before,
     143                 :  * it not being appropriate for the passed scope and configured client api 
     144                 :  * or http connection problems.  NULL is returned on error.
     145                 :  *
     146                 :  * @param pszAuthToken the authorization token from the user. 
     147                 :  * @param pszScope the scope for which it is valid. 
     148                 :  * 
     149                 :  * @return refresh token, to be freed with CPLFree(), null on failure.
     150                 :  */
     151                 : 
     152               0 : char CPL_DLL *GOA2GetRefreshToken( const char *pszAuthToken,
     153                 :                                    const char *pszScope )
     154                 : 
     155                 : {
     156                 : /* -------------------------------------------------------------------- */
     157                 : /*      Prepare request.                                                */
     158                 : /* -------------------------------------------------------------------- */
     159               0 :     CPLString osItem;
     160               0 :     CPLStringList oOptions;
     161                 : 
     162                 :     oOptions.AddString(
     163               0 :         "HEADERS=Content-Type: application/x-www-form-urlencoded" );
     164                 : 
     165                 :     osItem.Printf(
     166                 :         "POSTFIELDS="
     167                 :         "code=%s"
     168                 :         "&client_id=%s"
     169                 :         "&client_secret=%s"
     170                 :         "&redirect_uri=urn:ietf:wg:oauth:2.0:oob"
     171                 :         "&grant_type=authorization_code", 
     172                 :         pszAuthToken,
     173                 :         CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID), 
     174               0 :         CPLGetConfigOption("GOA2_CLIENT_SECRET", GDAL_CLIENT_SECRET));
     175               0 :     oOptions.AddString(osItem);
     176                 : 
     177                 : /* -------------------------------------------------------------------- */
     178                 : /*      Submit request by HTTP.                                         */
     179                 : /* -------------------------------------------------------------------- */
     180                 :     CPLHTTPResult * psResult = 
     181               0 :         CPLHTTPFetch( GOOGLE_AUTH_URL "/token", oOptions);
     182                 : 
     183               0 :     if (psResult == NULL)
     184               0 :         return NULL;
     185                 : 
     186                 : /* -------------------------------------------------------------------- */
     187                 : /*      One common mistake is to try and reuse the auth token.          */
     188                 : /*      After the first use it will return invalid_grant.               */
     189                 : /* -------------------------------------------------------------------- */
     190               0 :     if( psResult->pabyData != NULL
     191                 :         && strstr((const char *) psResult->pabyData,"invalid_grant") != NULL) 
     192                 :     {
     193               0 :         CPLString osURL;
     194               0 :         osURL.Seize( GOA2GetAuthorizationURL(pszScope) );
     195                 :         CPLError( CE_Failure, CPLE_AppDefined,
     196                 :                   "Attempt to use a OAuth2 authorization code multiple times.\n"
     197                 :                   "Request a fresh authorization token at\n%s.",
     198               0 :                   osURL.c_str() );
     199               0 :         CPLHTTPDestroyResult(psResult);
     200               0 :         return NULL;
     201                 :     }
     202                 : 
     203               0 :     if (psResult->pabyData == NULL ||
     204                 :         psResult->pszErrBuf != NULL)
     205                 :     {
     206               0 :         if( psResult->pszErrBuf != NULL )
     207               0 :             CPLDebug( "GOA2", "%s", psResult->pszErrBuf );
     208               0 :         if( psResult->pabyData != NULL )
     209               0 :             CPLDebug( "GOA2", "%s", psResult->pabyData );
     210                 : 
     211                 :         CPLError( CE_Failure, CPLE_AppDefined,
     212               0 :                   "Fetching OAuth2 access code from auth code failed.");
     213               0 :         CPLHTTPDestroyResult(psResult);
     214               0 :         return NULL;
     215                 :     }
     216                 : 
     217                 :     CPLDebug( "GOA2", "Access Token Response:\n%s", 
     218               0 :               (const char *) psResult->pabyData );
     219                 : 
     220                 : /* -------------------------------------------------------------------- */
     221                 : /*      This response is in JSON and will look something like:          */
     222                 : /* -------------------------------------------------------------------- */
     223                 : /*
     224                 : {
     225                 :   "access_token" : "ya29.AHES6ZToqkIJkat5rIqMixR1b8PlWBACNO8OYbqqV-YF1Q13E2Kzjw",
     226                 :   "token_type" : "Bearer",
     227                 :   "expires_in" : 3600,
     228                 :   "refresh_token" : "1/eF88pciwq9Tp_rHEhuiIv9AS44Ufe4GOymGawTVPGYo"
     229                 : }
     230                 : */
     231                 :     CPLStringList oResponse = ParseSimpleJson(
     232               0 :         (const char *) psResult->pabyData );
     233               0 :     CPLHTTPDestroyResult(psResult);
     234                 : 
     235               0 :     CPLString osAccessToken = oResponse.FetchNameValueDef( "access_token", "" );
     236               0 :     CPLString osRefreshToken = oResponse.FetchNameValueDef( "refresh_token", "" );
     237               0 :     CPLDebug("GOA2", "Access Token : '%s'", osAccessToken.c_str());
     238               0 :     CPLDebug("GOA2", "Refresh Token : '%s'", osRefreshToken.c_str());
     239                 : 
     240               0 :     if( osRefreshToken.size() == 0) 
     241                 :     {
     242                 :         CPLError( CE_Failure, CPLE_AppDefined,
     243               0 :                   "Unable to identify a refresh token in the OAuth2 response.");
     244               0 :         return NULL;
     245                 :     }
     246                 :     else 
     247                 :     {
     248                 :         // Currently we discard the access token and just return the refresh token
     249               0 :         return CPLStrdup(osRefreshToken);
     250               0 :     }
     251                 : }
     252                 : 
     253                 : /************************************************************************/
     254                 : /*                         GOA2GetAccessToken()                         */
     255                 : /************************************************************************/
     256                 : 
     257                 : /**
     258                 :  * Fetch access token using refresh token.
     259                 :  * 
     260                 :  * The permanent refresh token is used to fetch a temporary (usually one
     261                 :  * hour) access token using Google OAuth2 web services. 
     262                 :  *
     263                 :  * A CPLError will be reported if the request fails for some reason.
     264                 :  * Common reasons include the refresh token having been revoked by the
     265                 :  * user or http connection problems.
     266                 :  *
     267                 :  * @param pszRefreshToken the refresh token from GOA2GetRefreshToken().
     268                 :  * @param pszScope the scope for which it is valid. 
     269                 :  * 
     270                 :  * @return access token, to be freed with CPLFree(), null on failure.
     271                 :  */
     272                 : 
     273               7 : char *GOA2GetAccessToken( const char *pszRefreshToken, 
     274                 :                           const char *pszScope )
     275                 : {
     276                 : /* -------------------------------------------------------------------- */
     277                 : /*      Prepare request.                                                */
     278                 : /* -------------------------------------------------------------------- */
     279               7 :     CPLString osItem;
     280               7 :     CPLStringList oOptions;
     281                 : 
     282                 :     oOptions.AddString(
     283               7 :         "HEADERS=Content-Type: application/x-www-form-urlencoded" );
     284                 : 
     285                 :     osItem.Printf(
     286                 :         "POSTFIELDS="
     287                 :         "refresh_token=%s"
     288                 :         "&client_id=%s"
     289                 :         "&client_secret=%s"
     290                 :         "&grant_type=refresh_token", 
     291                 :         pszRefreshToken,
     292                 :         CPLGetConfigOption("GOA2_CLIENT_ID", GDAL_CLIENT_ID), 
     293               7 :         CPLGetConfigOption("GOA2_CLIENT_SECRET", GDAL_CLIENT_SECRET));
     294               7 :     oOptions.AddString(osItem);
     295                 : 
     296                 : /* -------------------------------------------------------------------- */
     297                 : /*      Submit request by HTTP.                                         */
     298                 : /* -------------------------------------------------------------------- */
     299               7 :     CPLHTTPResult *psResult = CPLHTTPFetch(GOOGLE_AUTH_URL "/token", oOptions);
     300                 : 
     301               7 :     if (psResult == NULL)
     302               0 :         return NULL;
     303                 : 
     304               7 :     if (psResult->pabyData == NULL ||
     305                 :         psResult->pszErrBuf != NULL)
     306                 :     {
     307               0 :         if( psResult->pszErrBuf != NULL )
     308               0 :             CPLDebug( "GFT", "%s", psResult->pszErrBuf );
     309               0 :         if( psResult->pabyData != NULL )
     310               0 :             CPLDebug( "GFT", "%s", psResult->pabyData );
     311                 : 
     312                 :         CPLError( CE_Failure, CPLE_AppDefined,
     313               0 :                   "Fetching OAuth2 access code from auth code failed.");
     314               0 :         CPLHTTPDestroyResult(psResult);
     315               0 :         return NULL;
     316                 :     }
     317                 : 
     318                 :     CPLDebug( "GOA2", "Refresh Token Response:\n%s", 
     319               7 :               (const char *) psResult->pabyData );
     320                 : 
     321                 : /* -------------------------------------------------------------------- */
     322                 : /*      This response is in JSON and will look something like:          */
     323                 : /* -------------------------------------------------------------------- */
     324                 : /*
     325                 : {
     326                 : "access_token":"1/fFBGRNJru1FQd44AzqT3Zg",
     327                 : "expires_in":3920,
     328                 : "token_type":"Bearer"
     329                 : }
     330                 : */
     331                 :     CPLStringList oResponse = ParseSimpleJson(
     332               7 :         (const char *) psResult->pabyData );
     333               7 :     CPLHTTPDestroyResult(psResult);
     334                 : 
     335               7 :     CPLString osAccessToken = oResponse.FetchNameValueDef( "access_token", "" );
     336                 : 
     337               7 :     CPLDebug("GOA2", "Access Token : '%s'", osAccessToken.c_str());
     338                 : 
     339               7 :     if (osAccessToken.size() == 0) 
     340                 :     {
     341                 :         CPLError( CE_Failure, CPLE_AppDefined,
     342               0 :                   "Unable to identify an access token in the OAuth2 response.");
     343               0 :         return NULL;
     344                 :     }
     345                 :     else 
     346               7 :         return CPLStrdup(osAccessToken);
     347                 : }
     348                 : 
     349                 : 
     350                 : 

Generated by: LCOV version 1.7