The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Google::OAuth - Maintains a database for Google Access Tokens

SYNOPSIS

  use Google::OAuth ;

  ## show a list of users 
  print join "\n", @users = Google::OAuth->token_list, '' ;

  ## eg $users[0] = 'perlmonster@gmail.com'
  $token = Google::OAuth->token( $users[0] ) ;

  ## Get Google Calendar data for this user
  $calobject = $token->content( GET => 'https://www.googleapis.com'
                .'/calendar/v3/users/me/calendarList' 
                ) ;

  ## Insert a calendar event
  %event = ( ... ) ;  ## See Google documentation
  $url = sprintf 'https://www.googleapis.com'
                .'/calendar/v3/calendars/%s/events', $token->{emailkey} ;
  $token->content( POST => $url, 
                Google::OAuth::CGI->new( \%event )->query_string ) ;

DESCRIPTION

Google::OAuth provides the capability to utilize the Google App's published API. The link below (to Google's Calendar reference) demonstrates their API in the form of HTTP REST requests. This API is consistent with the arguments of a Google::OAuth token's methods.

  https://developers.google.com/google-apps/calendar/v3/reference/

Based on the documentation, the integration process looks deceptively easy. Google::OAuth takes the same approach by providing a framework that also looks deceptively easy. Notwithstanding, this package includes the tools to get you set up and running.

BACKGROUND

The complete installation will probably take several hours. If you're lucky or already have some experience with the Google API, you can save time and skip this section.

The Google Apps API provides a mechanism to access the private and personal information in Google accounts. Naturally, there's a significant amount of complexity to authorize and authenticate an integration process. Although the Google::OAuth package handles all this complexity, the following describes the background activity.

Credentials

The first part of the authorization process requires establishing, and then presenting, your credentials for authorization. These credentials start out with a personal Google account, followed by a registration process that describes your integration project. When registration is complete, Google will generate and display several string values that will be used later in the installation process. These string values are refered to as client credentials.

Create a Google account if necessary, log in, then visit Google's developer site:

  http://developers.google.com/

At the bottom of the page (as of April 2013) there is a link to the API Console. Use the API Console to register an application. The information in your registration will be visible to users when they authorize your access. When the registration is complete, Google assigns three credential values:

1. A Client ID
2. A Client Secret
3. One or more Redirect URIs

A fourth value, the "Email address" is not used to establish credentials.

User Authorization

Once you've established credentials, you request that Google users authorize your application to access their account information. Your request is in the form of a URL link. For example, during the installation (described below), you'll email yourself a request link in order to test your installation.

Google uses three values as part of the authorization/authentication process:

1. A Grant Code: The Grant Code is a ticket that can only be used once.
2. An Access Token: The Access Token is transmitted in the header of the REST requests. Access Tokens are only valid for one hour.
3. A Refresh Token: The Refresh Token is the permanent ticket, and is the only value that needs to persist from session to session.

A Google::OAuth token is a Hash object that maintains several values in addition to the Access Token and Refresh Token. Unless otherwise specified, this document always uses the term token to refer to a Google::OAuth token.

NoSQL::PL2SQL

I released NoSQL::PL2SQL as part of an entire solution for advanced web applications, particularly those that use a distributed app technology such as Google Apps. NoSQL::PL2SQL is particularly well suited for the amorphous data objects used in these processes.

Essentially, the only persistent data requirements are a set of NVP pairs that associate a Google Account with a Refresh Token- everything else can be generated at run-time. However, Authentication is only a small piece of integration with a service such as Google Apps, and NoSQL::PL2SQL is appropriate for the larger, more complex data persistence needs.

INSTALLATION

After building this package using Make or CPAN, additional installation tasks are required:

1. Define the application credentials
2. Set up the data persistence component

These tasks are divided into five additional steps that must be peformed before this package is ready to use:

1. perl -MGoogle::OAuth::Install -e config configfile
2. perl -MGoogle::OAuth::Install -e settings configfile
3. perl -MGoogle::OAuth::Install -e grantcode configfile | mail
4. perl -MGoogle::OAuth::Install -e test configfile
5. perl -MGoogle::OAuth::Install -e install configfile

The additional configuration is performed by editing the configfile created in Step 1. During the final installation in Step 5, the configfile will replace the distributed Google::OAuth::Config module. Mostly, the configfile consists of commented instructions. The remainder needs to be legal perl syntax so that the following command succeeds:

  perl -cw configfile

I run Google::OAuth on a secure server that can only be accessed by an administrator. This is the appropriate environment for the default installation. Otherwise, in a multi-user environment, see the section "SECURE INSTALLATION".

Upon installation, the package has at least one persistent token, which should be a Google account owned by you, the installer. During installation, the Google Calendar API is used to recover the account ID. So this token has already been tested and used. But it's important to test your entire integration process using Google's API and the methods described in the SYNOPSIS section. It'll be practically impossible to test or troubleshoot on other users' accounts.

CREATING TOKENS

Most likely, your API tests will fail. By default Google's tokens do not allow data access without explicitly defining scope. The token created during installation is only capable of reading your calendar data. For additional access, you'll need to generate a new token.

As described above, this process consists of 3 phases:

1. Generate a request
2. Acquire Google's ticket, the Grant Code
3. Use the Grant Code to generate a token

Generate a request

First, there are some non-technical considerations. You need to make the request available to the user; and the user needs to trust you enough to approve the request. The request is not user specific, the same request can be used by anyone. Nevertheless, given the personal nature, it's probably best to email your request. Naturally gaining trust is trickier.

Most of this package's methods operate on Google::OAuth tokens. Since these requests are more generic, and independent of any token, the token request method uses a more general superclass, Google::OAuth::Client.

  print Google::OAuth::Client->new->scope(
                'calendar.readonly' )->token_request ;

The token_request method is detailed below. There are numerous internal definitions described within the method. Based on my experience, these default values are sufficient. However, the default values can be overridden using arguments passed to the constructor.

On the other hand, the list elements passed as arguments to the scope() method must be explicit. Each element must be a value from the list below. This list hasn't been verified for completeness, and more options may be added in the next revision:

m8.feeds
calendar
calendar.readonly
drive.readonly
drive

The token_request() method output is a legal, functional URL. The caller is responsible for wrapping the HTML.

Acquire the Grant Code

Google transmits the Grant Code via HTTP using the redirect_uri defined in the client credentials. Google provides the option to define multiple redirects, but Google::OAuth's installation process requires only one.

There are two approaches to using an alternative redirect_uri definition. In either case the definition must match one of the values in Google's API registration. First, the redirect_uri element in the client credentials can be redefined, as with any element, using the setclient() method as follows:

  my @redirect = ( ... ) ;
  Google::OAuth->setclient( redirect_uri => $redirect[1] ) ;

Second, in each specific instance, any component to the token request url can be modified by overriding token_request's defaults. The resulting code is fairly elaborate (and not illustrated here) because all of the internal values must be defined in the Google::OAuth::Client constructor when overriding any default.

After approval, Google returns the Grant Code as a query argument to the redirect_uri. Any output generated by the redirect_uri is displayed to the user.

Generate a token

The best way to generate a token is to load the following code into a CGI script or a similar technique that allows the HTTP server to initiate this sequence (such as tailing the server log).

    use Google::OAuth ;
    ## SECURE INSTALLATION overrides are loaded here

    $token = Google::OAuth->grant_code( $query{code} ) ;

    ## Preferred method
    %token = %{ Google::OAuth->grant_code( $query{code} ) } ;

The grant_code() method performs the following tasks:

1. Acquires the token from Google
2. Uses the token to request the account ID via the Google Calendar API
3. Saves the token as a persistent object keyed on the account ID

The grant_code() method is pretty unreliable, primarily because Google is so restrictive about issuing a token. Confirm a successful operation by examining the method's result, which should contain an emailkey value. When Google is being overly fussy, the result will look like:

  ## The 'requested' value is not part of Google's response
  $token = {
          'requested' => 1366389672,
          'error' => 'Invalid Grant'
        };

Another problem may occur if the token has insufficient privileges to access calendar data:

  $token = bless( {
                 'refresh_token' => '1/1v3Tvzj31e5M',
                 'expires_in' => '3600',
                 'requested' => 1366390047,
                 'access_token' => 'ya29.AHES6ZS',
                 'token_type' => 'Bearer'
               }, 'Google::OAuth' );

In this case, Google issued a valid token, but grant_code() was not able to use the token to access the account ID.

The value returned by grant_code() is volatile, which means it must be explicitly destroyed. If the response is only used to confirm success (or to log operations) it is safer to use the preferred method shown in the example.

USING A TOKEN

The code example under the SYNOPSIS section illustrates how to use a token to access data from Google. The most important caveat is that Google tokens expire after an hour. In applications where a session may last longer than a typical HTTP request, it's probably better to rewrite that example as follows:

  use Google::OAuth;

  ## show a list of users 
  print join "\n", @users = Google::OAuth->token_list, '' ;

  ## eg $users[0] = 'perlmonster@gmail.com'
  $token = Google::OAuth->token( $users[0] ) ;

  ## Intervening operations of an hour or longer

  $token = $token->token if $token->expired ;

  ## Get Google Calendar data for this users
  $calobject = $token->content( GET => 'https://www.googleapis.com'
                .'/calendar/v3/users/me/calendarList' ) ;

Persistent objects in NoSQL::PL2SQL can be either volatile or non-volatile. Volatile objects cache write operations until the object is destroyed, and therefore must be explicitly destroyed. As mentioned above, the grant_code() method returns a volatile object. As a convenience, the token() method returns non-volatile objects. This adds a small inefficency of a database write on every subsequent token() method call. Given how infrequently that method must be called, this is not a significant concern. Here is the volatile object approach nonetheless:

  use Google::OAuth;

  ## show a list of users 
  print join "\n", @users = Google::OAuth->token_list, '' ;

  ## eg $users[0] = 'perlmonster@gmail.com'
  $token = Google::OAuth->SQLObject( $users[0] )->token ;

  ## Intervening operations of an hour or longer

  $token->token if $token->expired ;

  ## Get Google Calendar data for this users
  $calobject = $token->content( GET => 'https://www.googleapis.com'.
                '/calendar/v3/users/me/calendarList' ) ;

  ## Somewhere before exiting
  undef $token ;

METHOD SUMMARY

Google::OAuth

Google::OAuth subclasses the following:

NoSQL::PL2SQL
Google::OAuth::Client
Google::OAuth::Request

SQLClone()

Google::OAuth->SQLClone() overrides NoSQL::PL2SQL->SQLClone and takes only an account ID as an argument.

SQLObject()

Google::OAuth->SQLObject() overrides NoSQL::PL2SQL->SQLObject and takes only an account ID as an argument.

grant_code()

Google::OAuth->grant_code() creates a token using a Grant Code issued by Google.

token_list()

Google::OAuth->token_list() returns a list of account ID's whose tokens are saved in the DSN.

token()

Google::OAuth->token() returns a token as a non-volatile object using an account ID argument.

$token->token() refreshes the Access Token member and returns the result. If the object is non-volatile, the new token is returned by this method. If the object is volatile, the member is replaced with a refreshed value.

headers()

$token->headers() overrides Google::OAuth::Request->headers() to add the Access Token to the HTTP::Request headers.

emailkey()

$token->emailkey() queries the Google Calendar account data and returns the account ID.

classID()

Google::OAuth->classID returns a constant integer that must be overridden by subclasses. This constant satisfies the key requirements of NoSQL::PL2SQL objects: a string and integer. The string component contains an email address, so the class must be represented by an integer constant. Consequently, multiple tokens may be keyed by the same email address if they are accessed using subclasses with different classID values.

grant_type()

Google::OAuth->grant_type returns a string constant to satisfy two different API's:

google => refresh_token
facebook => fb_exchange_token

Google::OAuth::Client

Methods that need to access the client credentials are defined under the Google::OAuth::Client module. In the context of this package, the methods are independent of token object data.

setclient()

Google::OAuth->setclient() is called automatically. It must be explicitly called again to manually set the client_secret in secure installations:

  Google::OAuth->setclient( client_secret => 'xAtN' ) ;

dsn()

Google::OAuth->dsn() can be used to access the DSN. In secure installations, use this method to connect the data source. Other access methods are described in NoSQL::PL2SQL::DBI.

new()

Google::OAuth::Client->new() accepts the same arguments as queryargs(). These arguments are used to override any predefined defaults.

scope()

$client->scope() is used in conjunction with the token_request() method. scope() returns its calling object, and can be invoked inline.

queryargs()

Google::OAuth::Client->queryargs() returns its arguments as an NVP list, according to the following rules: Scalar arguments are assumed to key reference one of the client credential elements. The name and value of that element are returned. An NVP pair is passed straight-through if it is encapsulated inside a hash reference:

  Google::OAuth->setclient( foo => 'bar' ) ;
  my %out = Google::OAuth->queryargs( 'foo', { hello => 'world' } ) ;
  print join( '-', %out ), "\n" ;       ## prints the following:
  # foo-bar-hello-world

token_request()

$client->token_request generates a URL used for a token request. This URL consists of the following definitions:

1. Overrides passed as arguments to the Google::OAuth::Client constructor.
2. Default values defined within the token_request() method.
3. Scope definitions defined by the scope() method.
4. Client credentials defined in Google::OAuth::Config.
5. Client credentials defined by Google::OAuth->setclient().

get_token()

get_token() retrieves a token from Google using a HTTP::Request based on the following definitions:

$client->get_token() If this method is invoked with an object, definitions are passed to the object's constructor and override any internal defaults.

Google::OAuth->get_token() Otherwise, definitions are passed as arguments like queryargs()'s. Some of these definitions are defined internally as defaults.

After get_token() retrieves a token, it adds the requested element to record a timestamp and blesses the returned object.

expired()

Access Tokens are valid for only a short period of time. $token->expired() estimates the expiration time based on the requested and expires_in token properties.

Google::OAuth::Request

Google::OAuth::Request contains methods that generate or use an HTTP::Request object built with specific header rules. Subclasses, such as Google::OAuth are expected to define their own header() methods.

request()

$token->request creates an HTTP::Request object whose headers are constructed, in part, using token data.

Google::OAuth::Request->request creates an HTTP::Request object whose default headers are compatible with Google.

request() takes one, two, three or four arguments: A string representing an HTTP method (GET, POST, etc.), a URL, and an optional string representing POST data. To override the default "Content-Type", the third argument should reflect this header value.

  $r = Google::OAuth::Request->request( $url ) ;        ## Default: GET
  $r = Google::OAuth::Request->request( GET => $url ) ;
  $r = Google::OAuth::Request->request( POST => $url, $content ) ;
  $r = Google::OAuth::Request->request( POST => $url, $type, $content ) ;
  $r = Google::OAuth::Request->request( GET => $url, $type, undef ) ;

This internal method is available for debugging.

response()

$token->response and Google::OAuth::Request->response are nearly identical to the request() method except that this method returns an HTTP::Response object. This internal method is available for debugging.

content()

$token->content and Google::OAuth::Request->content also behaves like request() and accepts the same arguments. It's named after HTTP::Response->content and returns the body of the HTTP response.

If the response body is a JSON definition, content() returns a perl object based on that definition. Otherwise it returns a scalar representation of the HTTP response.

        ## Don't use this statement in production.  Why not?
        print "invalid JSON\n" if $token->content( @args ) 
                        eq $token->response( @args )->content ;

        ## prints raw JSON data
        print $token->response( @args )->content ;

A scalar value returned by this method indicates an error and may be helpful for debugging.

headers()

The headers() method returns default headers compatible with Google.

Google::OAuth::Headers

Google::OAuth::Headers is a trivial subclass for Google::OAuth::Request. Originally intended for testing, this class is currently unused but may be useful for modifying request headers.

  $o = Google::OAuth::Headers->new( $token 
                )->add( %headernvps 
                )->content( GET => $url ) ;

new()

The object uses a token and includes authentication in the resulting headers. Pass the token as an argument to this constructor.

add()

Pass any custom headers to the add() method. add() returns its calling object for inline use.

headers()

The overridden headers() method returns the same header values as Google::OAuth::headers() in addition to those specified by add().

Google::OAuth::Install

Google::OAuth::Install is intended only for command line use, see the INSTALLATION section. The following functions are defined and exported:

config()
settings()
grantcode()
test()
install()

Google::OAuth::Config

Google::OAuth::Config is generated during the installation process, and contains the definitions of the client credentials and DSN.

Google::OAuth::Config::setclient() is its only defined method.

SECURE INSTALLATION

Since the client credentials are built into the installation, some tweaking is required to use Google::OAuth in a shared environment.

The easiest would be a simple launch script as follows:

  use Google::OAuth ;

  $dsn = define_a_dsn() ;

  Google::OAuth->setclient( 
                client_id => '...',
                client_secret => '...',
                redirect_uri => '...',
                dsn => $dsn,
                ) ;

In an shared space like mod_perl, a user might inherit an environment where this configuration has already been defined by another user. In this case, the adventurous can subclass Google::OAuth and override the Google::OAuth::Client methods. mod_perl has a variety of configuration strategies that could be applied, but not an obvious universal solution. For the time being, the foremost concern of this initial release is simplicity.

A more urgent concern is restricting access to Google::OAuth data in a multi-user environment. There are basically two concerns:

1. Access to the client credentials
2. Access to user tokens in the DSN

The solution is to simply leave those definitions incomplete in the configuration file. Define the DSN in the config file, but leave the DSN unconnected until run-time. Then, during run-time, load the missing configuration definitions and connect the data source as follows:

  use Google::OAuth ;

  Google::OAuth->setclient( client_secret => 'xAtN' ) ;
  Google::OAuth->dsn->connect( 'DBI:mysql:'.$dbname, @login ) ;

However, these definitions are required to complete the installation. If you know your way around your perl installation, leave the installation definitions intact and then simply edit the Config.pm file in place.

Otherwise, this modified installation procedure uses a local configuration file to hide any secrets. This file should be named local.pm and written in the current working directory. A template is included in this distribution. The file contents look like:

  package local ;
  use Google::Auth ;

  sub Google::OAuth::setclient {
        my %client = Google::OAuth::Config->setclient ;
        $client{client_secret} = 'xAtN' ;
        $client{dsn}->connect( 'DBI:mysql:'.$dbname, @login ) ;
        Google::OAuth::Client->setclient( %client ) ;
        }

  1
  

Now, perform the installation using these slightly modified steps:

1. perl -MGoogle::OAuth::Install -Mlocal -e config configfile
2. perl -MGoogle::OAuth::Install -Mlocal -e settings configfile
3. perl -MGoogle::OAuth::Install -Mlocal -e grantcode configfile | mail
4. perl -MGoogle::OAuth::Install -Mlocal -e test configfile
5. perl -MGoogle::OAuth::Install -Mlocal -e install configfile

Upon completion, delete the local.pm file. Or if secure, use it for future reference.

EXPORT

none

SEE ALSO

http://developers.google.com/
http://www.tqis.com/eloquency/googlecalendar.htm

There is a page on my developer site to discuss Google::OAuth

http://pl2sql.tqis.com/pl2sql/GoogleOAuth/

This web page includes a forum. But until I am confident enough to release this distro for production, please contact me directly, at the email address below, with questions, problems, or suggestions.

AUTHOR

Jim Schueler, <jim@tqis.com>

COPYRIGHT AND LICENSE

Copyright (C) 2013 by Jim Schueler

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.9 or, at your option, any later version of Perl 5 you may have available.

15 POD Errors

The following errors were encountered while parsing the POD:

Around line 436:

=back doesn't take any parameters, but you said =back 8

Around line 459:

=back doesn't take any parameters, but you said =back 8

Around line 492:

=back doesn't take any parameters, but you said =back 8

Around line 509:

=back doesn't take any parameters, but you said =back 8

Around line 550:

=back doesn't take any parameters, but you said =back 8

Around line 589:

=back doesn't take any parameters, but you said =back 8

Around line 643:

=back doesn't take any parameters, but you said =back 8

Around line 743:

=back doesn't take any parameters, but you said =back 8

Around line 806:

=back doesn't take any parameters, but you said =back 8

Around line 868:

=back doesn't take any parameters, but you said =back 8

Around line 995:

=back doesn't take any parameters, but you said =back 8

Around line 1040:

=back doesn't take any parameters, but you said =back 8

Around line 1087:

=back doesn't take any parameters, but you said =back 8

Around line 1105:

=back doesn't take any parameters, but you said =back 8

Around line 1113:

=back doesn't take any parameters, but you said =back 8