Google-OAuth
view release on metacpan or search on metacpan
lib/Google/OAuth.pm view on Meta::CPAN
{ grant_type => 'authorization_code' } ) ;
my $key = $token->emailkey
if ref $token && $token->{access_token} ;
return $key? $package->SQLObject( $key => $token ): $token ;
}
sub token_list {
my $package = shift ;
return map { $_->{objecttype} } $package->dsn->fetch(
[ reftype => 'perldata', 1 ],
[ objectid => $package->classID ]
) ;
}
sub token {
my $arg = shift ;
my ( $self, $package ) = ref $arg? ( $arg, ref $arg ): ( undef, $arg ) ;
my $object = $self ;
$self ||= $package->SQLObject( @_ ) ;
my $rr = $package->grant_type ;
my $token = $package->get_token(
{ $rr => $self->{$rr} },
{ grant_type => $rr }
) ;
if ( ref $token && $token->{access_token} ) {
map { $self->{$_} = $token->{$_} } keys %$token ;
}
else {
my $error = ref $token? join( "\n", %$token ): $token ;
warn join "\n", 'Access renewal failed:', $error, '' ;
}
## Object may be a clone
unless ( defined $self->SQLObjectID ) {
my $package = ref $self ;
my $temp = $package->SQLObject( $self->{emailkey} ) ;
map { $temp->{$_} = $self->{$_} } keys %$self ;
$self = $temp->SQLClone ;
}
return $object || $self->SQLClone ;
}
sub headers {
my $self = shift ;
my $method = shift ;
return Google::OAuth::Request::headers( $method ),
Authorization =>
join ' ', @$self{ qw( token_type access_token ) } ;
}
sub emailkey {
my $self = shift ;
my $url = 'https://www.googleapis.com'
.'/calendar/v3/users/me/calendarList' ;
my $calinfo = $self->content( GET => $url ) ;
my @owner = grep $_->{accessRole} eq 'owner', @{ $calinfo->{items} } ;
return $self->{emailkey} = $owner[0]->{summary} ;
}
package Google::OAuth::Client ;
require Exporter;
@Google::OAuth::Client::ISA = qw( Exporter ) ;
# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
# This allows declaration use Google::OAuth ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = ( 'all' => [ qw() ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ) ;
our @EXPORT = qw() ;
our $VERSION = '0.01';
our %google ;
$google{oauth} = 'https://accounts.google.com/o/oauth2/auth';
$google{token} = 'https://accounts.google.com/o/oauth2/token';
my %client = () ;
setclient() ;
sub setclient {
my $package = shift ;
%client = ( Google::OAuth::Config->setclient, @_ ) ;
return undef ;
}
sub dsn {
return $client{dsn} ;
}
my %scopes = (
'm8.feeds'
=> 'https://www.google.com/m8/feeds',
'calendar'
=> 'https://www.googleapis.com/auth/calendar',
'calendar.readonly'
=> 'https://www.googleapis.com/auth/calendar.readonly',
'drive.readonly'
=> 'https://www.googleapis.com/auth/drive.readonly',
'drive'
=> 'https://www.googleapis.com/auth/drive',
) ;
sub new {
my $package = shift ;
my $self = {} ;
$self->{args} = $package->queryargs( @_ ) if @_ ;
return bless $self, $package ;
}
sub scope {
shift @_ if $_[0] eq __PACKAGE__ ;
my $self = ref $_[0] eq __PACKAGE__? shift @_: undef ;
my %args = map { $_ => 1 } ( @_, 'calendar.readonly' ) ;
my $scope = join ' ', map { $scopes{$_} } keys %args ;
return $scope unless $self ;
$self->{scope} = $scope ;
return $self ;
}
sub queryargs {
my $package = shift ;
my %out = map { ref $_? %$_: ( $_ => $client{$_} ) } @_ ;
return \%out ;
}
sub token_request {
my $self = shift ;
my $args = $self->{args} || $self->queryargs(
'client_id', 'redirect_uri',
{ response_type => 'code' },
{ approval_prompt => 'force' },
{ access_type => 'offline' }
) ;
$args->{scope} = $self->{scope} if $self->{scope} ;
my $kurl = @_? shift @_: 'oauth' ;
return join '?', $google{$kurl} || $kurl,
Google::OAuth::CGI->new( $args )->query_string ;
}
sub get_token {
my $arg = shift ;
my ( $package, $self ) = ref $arg?
( ref $arg, $arg ):
( $arg,
new( $arg, 'client_id', 'client_secret', @_ ) ) ;
my $out = Google::OAuth::Request->content(
POST => $google{token},
Google::OAuth::CGI->new( $self->{args} )->query_string
) ;
return $out unless ref $out ;
$out->{requested} = time ;
return bless $out, $package ;
}
sub expired {
my $self = shift ;
return $self->{requested} +$self->{expires_in} < time ;
}
package Google::OAuth::Request ;
my %content_type = () ;
$content_type{POST} = 'application/x-www-form-urlencoded' ;
$content_type{GET} = 'application/http' ;
sub request {
my $self = shift ;
lib/Google/OAuth.pm view on Meta::CPAN
sub new {
my $package = shift ;
my $source = shift ;
return bless { source => $source }, $package ;
}
sub encode {
shift @_ if $_[0] eq __PACKAGE__ ;
my $text = shift ;
$text =~ s|([^_0-9A-Za-z\. ])|sprintf "%%%02X", ord($1)|seg ;
$text =~ s/ /+/g ;
return $text ;
}
sub args {
my ( $key, $value ) = @_ ;
$value ||= '' ;
return join '=', $key, encode( $value ) unless ref $value ;
if ( ref $value eq 'ARRAY' ) {}
elsif ( grep ref $value eq $_, qw( HASH SCALAR ) ) {
return '' ;
}
elsif ( $value->isa('ARRAY') ) {}
else {
return '' ;
}
return join '&', map { join '=', $key, encode( $_ ) } @$value ;
}
sub query_string {
my $self = shift ;
my $source = $self->{source} ;
return join '&', map { args( $_, $source->{$_} ) } keys %$source ;
}
1;
__END__
# Below is stub documentation for your module. You'd better edit it!
=head1 NAME
Google::OAuth - Maintains a database for Google Access Tokens
=head1 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 ) ;
=head1 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.
=head1 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.
=head2 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 I<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:
=over 8
=item 1. A I<Client ID>
=item 2. A I<Client Secret>
=item 3. One or more I<Redirect URIs>
=back 8
A fourth value, the "Email address" is not used to establish credentials.
=head2 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
lib/Google/OAuth.pm view on Meta::CPAN
of integration with a service such as Google Apps, and NoSQL::PL2SQL is
appropriate for the larger, more complex data persistence needs.
=head1 INSTALLATION
After building this package using Make or CPAN, additional installation
tasks are required:
=over 8
=item 1. Define the application credentials
=item 2. Set up the data persistence component
=back 8
These tasks are divided into five additional steps that must be peformed
before this package is ready to use:
=over 8
=item 1. perl -MGoogle::OAuth::Install -e config configfile
=item 2. perl -MGoogle::OAuth::Install -e settings configfile
=item 3. perl -MGoogle::OAuth::Install -e grantcode configfile | mail
=item 4. perl -MGoogle::OAuth::Install -e test configfile
=item 5. perl -MGoogle::OAuth::Install -e install configfile
=back 8
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
L<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
L<SYNOPSIS> section. It'll be practically impossible to test or troubleshoot
on other users' accounts.
=head1 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:
=over 8
=item 1. Generate a request
=item 2. Acquire Google's ticket, the I<Grant Code>
=item 3. Use the I<Grant Code> to generate a token
=back 8
=head2 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 C<Google::OAuth> tokens. Since
these requests are more generic, and independent of any token, the token
request method uses a more general superclass, C<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 C<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:
=over 8
=item I<m8.feeds>
=item I<calendar>
=item I<calendar.readonly>
=item I<drive.readonly>
=item I<drive>
=back 8
The C<token_request()> method output is a legal, functional URL. The
caller is responsible for wrapping the HTML.
=head2 Acquire the Grant Code
Google transmits the I<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 I<redirect_uri> definition.
In either case the definition must match one of the values in Google's API
registration. First, the I<redirect_uri> element in the client credentials
can be redefined, as with any element, using the C<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 C<token_request>'s defaults. The resulting code is
fairly elaborate (and not illustrated here) because I<all> of the internal
values must be defined in the Google::OAuth::Client constructor when
overriding any default.
After approval, Google returns the I<Grant Code> as a query argument to
the redirect_uri. Any output generated by the redirect_uri is displayed to
the user.
=head2 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 C<grant_code()> method performs the following tasks:
=over 8
=item 1. Acquires the token from Google
=item 2. Uses the token to request the account ID via the Google Calendar API
=item 3. Saves the token as a persistent object keyed on the account ID
=back 8
The C<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 I<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 C<grant_code()> was not able to
use the token to access the account ID.
The value returned by C<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.
=head1 USING A TOKEN
The code example under the L<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 L<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
C<grant_code()> method returns a volatile object. As a convenience, the
C<token()> method returns non-volatile objects. This adds a small
inefficency of a database write on every subsequent C<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 ;
=head1 METHOD SUMMARY
=head2 Google::OAuth
C<Google::OAuth> subclasses the following:
=over 8
=item C<NoSQL::PL2SQL>
=item C<Google::OAuth::Client>
=item C<Google::OAuth::Request>
=back 8
=head3 SQLClone()
C<< Google::OAuth->SQLClone() >> overrides C<< NoSQL::PL2SQL->SQLClone >>
and takes only an account ID as an argument.
=head3 SQLObject()
C<< Google::OAuth->SQLObject() >> overrides C<< NoSQL::PL2SQL->SQLObject >>
and takes only an account ID as an argument.
=head3 grant_code()
C<< Google::OAuth->grant_code() >> creates a token using a I<Grant Code>
issued by Google.
=head3 token_list()
C<< Google::OAuth->token_list() >> returns a list of account ID's whose
tokens are saved in the DSN.
=head3 token()
C<< Google::OAuth->token() >> returns a token as a non-volatile object using
an account ID argument.
C<< $token->token() >> refreshes the I<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.
=head3 headers()
C<< $token->headers() >> overrides C<< Google::OAuth::Request->headers() >>
to add the I<Access Token> to the HTTP::Request headers.
=head3 emailkey()
C<< $token->emailkey() >> queries the Google Calendar account data and
returns the account ID.
lib/Google/OAuth.pm view on Meta::CPAN
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 I<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:
=over 8
=item 1. perl -MGoogle::OAuth::Install -Mlocal -e config configfile
=item 2. perl -MGoogle::OAuth::Install -Mlocal -e settings configfile
=item 3. perl -MGoogle::OAuth::Install -Mlocal -e grantcode configfile | mail
=item 4. perl -MGoogle::OAuth::Install -Mlocal -e test configfile
=item 5. perl -MGoogle::OAuth::Install -Mlocal -e install configfile
=back 8
Upon completion, delete the local.pm file. Or if secure, use it for future
reference.
=head1 EXPORT
none
=head1 SEE ALSO
=over 8
=item http://developers.google.com/
=item http://www.tqis.com/eloquency/googlecalendar.htm
=back 8
There is a page on my developer site to discuss Google::OAuth
=over 8
=item http://pl2sql.tqis.com/pl2sql/GoogleOAuth/
=back 8
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.
=head1 AUTHOR
Jim Schueler, E<lt>jim@tqis.comE<gt>
=head1 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.
=cut
( run in 0.555 second using v1.01-cache-2.11-cpan-39bf76dae61 )