File Coverage

blib/lib/Google/OAuth.pm
Criterion Covered Total %
statement 3 3 100.0
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 4 4 100.0


line stmt bran cond sub pod time code
1             package Google::OAuth ;
2 1     1   23209 use base NoSQL::PL2SQL ;
  1         3  
  1         696  
3             use Google::OAuth::Config ;
4             use LWP::UserAgent ;
5             use JSON ;
6              
7             use 5.008009;
8             use strict;
9             use warnings;
10              
11             require Exporter;
12              
13             push @Google::OAuth::ISA,
14             qw( Exporter Google::OAuth::Request Google::OAuth::Client ) ;
15              
16             # Items to export into callers namespace by default. Note: do not export
17             # names by default without a very good reason. Use EXPORT_OK instead.
18             # Do not simply export all your public functions/methods/constants.
19              
20             # This allows declaration use Google::OAuth ':all';
21             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
22             # will save memory.
23             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
24              
25             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ) ;
26              
27             our @EXPORT = qw();
28              
29             our $VERSION = '0.05';
30              
31              
32             # Preloaded methods go here.
33              
34             my $duplicate = sub {
35             my ( $emailkey, $errorcode, $perldata, $zero, $obj, $errorstring )
36             = @_ ;
37              
38             my $package = ref $obj ;
39             my $u = $package->SQLObject( $emailkey ) ;
40             my %keys = map { $_ => 1 } keys %$u, keys %$obj ;
41              
42             map { exists $obj->{$_}?
43             ( $u->{$_} = $obj->{$_} ):
44             ( delete $u->{$_} ) } keys %keys ;
45             return bless $u, $package ;
46             } ;
47              
48             sub SQLClone {
49             my $arg = shift ;
50             my ( $self, $package ) = ref $arg? ( $arg, ref $arg ): ( undef, $arg ) ;
51             $self ||= $package->SQLObject( @_ ) ;
52              
53             return bless NoSQL::PL2SQL::SQLClone( $self ), $package ;
54             }
55              
56             sub classID {
57             return 0 ;
58             }
59              
60             sub grant_type {
61             return 'refresh_token' ;
62             }
63              
64             sub SQLObject {
65             my $package = shift ;
66             my $email = shift ;
67             NoSQL::PL2SQL::SQLError( $email,
68             DuplicateObject => $duplicate ) ;
69             my @args = ( $email, $package->dsn, $package->classID ) ;
70              
71             push @args, bless $_[0], $package if @_ ;
72             my $out = NoSQL::PL2SQL::SQLObject( @args ) ;
73             return $out? bless( $out, $package ): undef ;
74             }
75              
76             sub grant_code {
77             my $package = shift @_ ;
78             my $code = shift ;
79             my $token = $package->get_token( 'redirect_uri', { code => $code },
80             { grant_type => 'authorization_code' } ) ;
81              
82             my $key = $token->emailkey
83             if ref $token && $token->{access_token} ;
84             return $key? $package->SQLObject( $key => $token ): $token ;
85             }
86              
87             sub token_list {
88             my $package = shift ;
89              
90             return map { $_->{objecttype} } $package->dsn->fetch(
91             [ reftype => 'perldata', 1 ],
92             [ objectid => $package->classID ]
93             ) ;
94             }
95              
96             sub token {
97             my $arg = shift ;
98             my ( $self, $package ) = ref $arg? ( $arg, ref $arg ): ( undef, $arg ) ;
99              
100             my $object = $self ;
101             $self ||= $package->SQLObject( @_ ) ;
102              
103             my $rr = $package->grant_type ;
104             my $token = $package->get_token(
105             { $rr => $self->{$rr} },
106             { grant_type => $rr }
107             ) ;
108              
109             if ( ref $token && $token->{access_token} ) {
110             map { $self->{$_} = $token->{$_} } keys %$token ;
111             }
112             else {
113             my $error = ref $token? join( "\n", %$token ): $token ;
114             warn join "\n", 'Access renewal failed:', $error, '' ;
115             }
116              
117             ## Object may be a clone
118             unless ( defined $self->SQLObjectID ) {
119             my $package = ref $self ;
120             my $temp = $package->SQLObject( $self->{emailkey} ) ;
121             map { $temp->{$_} = $self->{$_} } keys %$self ;
122             $self = $temp->SQLClone ;
123             }
124              
125             return $object || $self->SQLClone ;
126             }
127              
128             sub headers {
129             my $self = shift ;
130             my $method = shift ;
131              
132             return Google::OAuth::Request::headers( $method ),
133             Authorization =>
134             join ' ', @$self{ qw( token_type access_token ) } ;
135             }
136              
137             sub emailkey {
138             my $self = shift ;
139             my $url = 'https://www.googleapis.com'
140             .'/calendar/v3/users/me/calendarList' ;
141             my $calinfo = $self->content( GET => $url ) ;
142             my @owner = grep $_->{accessRole} eq 'owner', @{ $calinfo->{items} } ;
143             return $self->{emailkey} = $owner[0]->{summary} ;
144             }
145              
146              
147             package Google::OAuth::Client ;
148              
149             require Exporter;
150              
151             @Google::OAuth::Client::ISA = qw( Exporter ) ;
152              
153             # Items to export into callers namespace by default. Note: do not export
154             # names by default without a very good reason. Use EXPORT_OK instead.
155             # Do not simply export all your public functions/methods/constants.
156              
157             # This allows declaration use Google::OAuth ':all';
158             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
159             # will save memory.
160             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
161              
162             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ) ;
163              
164             our @EXPORT = qw() ;
165              
166             our $VERSION = '0.01';
167              
168             our %google ;
169             $google{oauth} = 'https://accounts.google.com/o/oauth2/auth';
170             $google{token} = 'https://accounts.google.com/o/oauth2/token';
171              
172             my %client = () ;
173             setclient() ;
174              
175             sub setclient {
176             my $package = shift ;
177             %client = ( Google::OAuth::Config->setclient, @_ ) ;
178             return undef ;
179             }
180              
181             sub dsn {
182             return $client{dsn} ;
183             }
184              
185             my %scopes = (
186             'm8.feeds'
187             => 'https://www.google.com/m8/feeds',
188             'calendar'
189             => 'https://www.googleapis.com/auth/calendar',
190             'calendar.readonly'
191             => 'https://www.googleapis.com/auth/calendar.readonly',
192             'drive.readonly'
193             => 'https://www.googleapis.com/auth/drive.readonly',
194             'drive'
195             => 'https://www.googleapis.com/auth/drive',
196             ) ;
197              
198             sub new {
199             my $package = shift ;
200             my $self = {} ;
201             $self->{args} = $package->queryargs( @_ ) if @_ ;
202             return bless $self, $package ;
203             }
204              
205             sub scope {
206             shift @_ if $_[0] eq __PACKAGE__ ;
207             my $self = ref $_[0] eq __PACKAGE__? shift @_: undef ;
208             my %args = map { $_ => 1 } ( @_, 'calendar.readonly' ) ;
209              
210             my $scope = join ' ', map { $scopes{$_} } keys %args ;
211             return $scope unless $self ;
212              
213             $self->{scope} = $scope ;
214             return $self ;
215             }
216              
217             sub queryargs {
218             my $package = shift ;
219             my %out = map { ref $_? %$_: ( $_ => $client{$_} ) } @_ ;
220             return \%out ;
221             }
222              
223             sub token_request {
224             my $self = shift ;
225             my $args = $self->{args} || $self->queryargs(
226             'client_id', 'redirect_uri',
227             { response_type => 'code' },
228             { approval_prompt => 'force' },
229             { access_type => 'offline' }
230             ) ;
231             $args->{scope} = $self->{scope} if $self->{scope} ;
232            
233             my $kurl = @_? shift @_: 'oauth' ;
234             return join '?', $google{$kurl} || $kurl,
235             Google::OAuth::CGI->new( $args )->query_string ;
236             }
237              
238             sub get_token {
239             my $arg = shift ;
240             my ( $package, $self ) = ref $arg?
241             ( ref $arg, $arg ):
242             ( $arg,
243             new( $arg, 'client_id', 'client_secret', @_ ) ) ;
244              
245             my $out = Google::OAuth::Request->content(
246             POST => $google{token},
247             Google::OAuth::CGI->new( $self->{args} )->query_string
248             ) ;
249              
250             return $out unless ref $out ;
251             $out->{requested} = time ;
252             return bless $out, $package ;
253             }
254              
255             sub expired {
256             my $self = shift ;
257             return $self->{requested} +$self->{expires_in} < time ;
258             }
259              
260              
261             package Google::OAuth::Request ;
262              
263             my %content_type = () ;
264             $content_type{POST} = 'application/x-www-form-urlencoded' ;
265             $content_type{GET} = 'application/http' ;
266              
267             sub request {
268             my $self = shift ;
269             my $method = @_ > 1? shift @_: 'GET' ;
270             my $url = shift ;
271              
272             my %hh = $self->headers( $method ) ;
273             $hh{'Content-Type'} = shift @_ if @_ > 1 ;
274             $hh{'Content-Length'} = length $_[0] if $method eq 'POST' ;
275              
276             my @args = grep defined $_, ( [ %hh ], @_ ) ;
277             return new HTTP::Request( $method, $url, @args ) ;
278             }
279              
280             sub response {
281             my $self = shift ;
282             my $r = $self->request( @_ ) ;
283             return LWP::UserAgent->new->request( $r ) ;
284             }
285              
286             sub content {
287             my $self = shift ;
288             my $content = $self->response( @_ )->content ;
289             return $content unless $content =~ /^{/s ;
290             return JSON::from_json( $content ) ;
291             }
292              
293             sub headers {
294             shift @_ if $_[0] eq __PACKAGE__ ;
295             shift @_ if ref $_[0] eq __PACKAGE__ ;
296              
297             my $method = shift ;
298              
299             return (
300             'Content-Type' => $content_type{$method},
301             ) ;
302             }
303              
304              
305             ## stupid CGI::Simple fails on mod_perl
306             ## replace with a published distro
307             package Google::OAuth::CGI ;
308              
309             sub new {
310             my $package = shift ;
311             my $source = shift ;
312             return bless { source => $source }, $package ;
313             }
314              
315             sub encode {
316             shift @_ if $_[0] eq __PACKAGE__ ;
317             my $text = shift ;
318             $text =~ s|([^_0-9A-Za-z\. ])|sprintf "%%%02X", ord($1)|seg ;
319             $text =~ s/ /+/g ;
320             return $text ;
321             }
322              
323             sub args {
324             my ( $key, $value ) = @_ ;
325             $value ||= '' ;
326             return join '=', $key, encode( $value ) unless ref $value ;
327              
328             if ( ref $value eq 'ARRAY' ) {}
329             elsif ( grep ref $value eq $_, qw( HASH SCALAR ) ) {
330             return '' ;
331             }
332             elsif ( $value->isa('ARRAY') ) {}
333             else {
334             return '' ;
335             }
336              
337             return join '&', map { join '=', $key, encode( $_ ) } @$value ;
338             }
339              
340             sub query_string {
341             my $self = shift ;
342             my $source = $self->{source} ;
343              
344             return join '&', map { args( $_, $source->{$_} ) } keys %$source ;
345             }
346              
347              
348             1;
349             __END__