The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 019
MANIFEST 02
META.yml 89
Makefile.PL 12
eg/spreadsheets.psgi 413
inc/Module/Install/Base.pm 11
inc/Module/Install/Can.pm 11
inc/Module/Install/Fetch.pm 11
inc/Module/Install/Makefile.pm 11
inc/Module/Install/Metadata.pm 11
inc/Module/Install/Win32.pm 11
inc/Module/Install/WriteAll.pm 11
inc/Module/Install.pm 11
lib/Net/Google/DataAPI/Auth/OAuth2.pm 2523
lib/Net/Google/DataAPI/Role/Service.pm 17
lib/Net/Google/DataAPI.pm 11
t/01_role/01_service/07_max_line_length.t 013
t/01_role/01_service/08_max_line_length_custom_size.t 015
t/04_auth/04_oauth2.t 1249
19 files changed (This is a version diff) 60161
@@ -1,5 +1,24 @@
 Revision history for Perl extension Net::Google::DataAPI
 
+0.2805  Sat Jun 28 17:30:00 2014
+        - allow state parameter for Net::Google::DataAPI::Auth::OAuth2
+
+0.2804  Tue Mar 11 14:46:18 2014
+        - increase MaxLineLength of LWP::Protocol::http::EXTRA_SOCK_OPTS (thanks to Erin Spiceland)
+
+0.2803  Mon Feb 18 6:50:00 2013
+        - Official release
+
+0.2802_2 Fri Feb 15 23:15:00 2013
+        - follow changes of Net::OAuth2 0.53
+
+0.2802_1 Sun Jan 13 10:30:00 2013
+        - follow changes of Net::OAuth2 (thanks to @ediblenergy, https://github.com/lopnor/Net-Google-DataAPI/pull/3)
+        - bump up versions of dependencies
+
+0.2802  Mon Sep 10 12:00:00 2012
+        - Support additional params in authorize_url() (thanks to @hsw, https://github.com/lopnor/Net-Google-DataAPI/pull/1)
+
 0.2801  Mon Apr 30 08:55:00 2012
         - Net::Google::DataAPI::Auth::OAuth is deprecated. (#76812)
 
@@ -35,6 +35,8 @@ t/01_role/01_service/03_request.t
 t/01_role/01_service/04_methods.t
 t/01_role/01_service/05_debug.t
 t/01_role/01_service/06_pass_through.t
+t/01_role/01_service/07_max_line_length.t
+t/01_role/01_service/08_max_line_length_custom_size.t
 t/01_role/02_entry/01_instanciate.t
 t/01_role/02_entry/02_update.t
 t/01_role/03_has_content/01_basic.t
@@ -7,7 +7,7 @@ build_requires:
   Test::Exception: 0
   Test::MockModule: 0
   Test::MockObject: 0
-  Test::More: 0.88
+  Test::More: '0.88'
   Test::Warn: 0
   UNIVERSAL::can: 0
   UNIVERSAL::isa: 0
@@ -15,7 +15,7 @@ configure_requires:
   ExtUtils::MakeMaker: 6.59
 distribution_type: module
 dynamic_config: 1
-generated_by: 'Module::Install version 1.06'
+generated_by: 'Module::Install version 1.08'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -27,7 +27,7 @@ no_index:
     - t
     - xt
 recommends:
-  Moose: 0.56
+  Moose: '0.56'
 requires:
   Any::Moose: 0.04
   Carp: 0
@@ -35,15 +35,16 @@ requires:
   LWP::Protocol::https: 0
   LWP::UserAgent: 0
   Lingua::EN::Inflect::Number: 0
-  Mouse: 0.51
-  Net::Google::AuthSub: 0.5
+  Mouse: '0.51'
+  Net::Google::AuthSub: '0.5'
   Net::OAuth: 0
-  Net::OAuth2: 0.07
+  Net::OAuth2: '0.60'
   Text::Glob: 0
   URI: 0
   XML::Atom: 0
+  XML::LibXML: 0
   perl: 5.8.1
 resources:
   license: http://dev.perl.org/licenses/
-  repository: git://github.com/lopnor/Net-Google-DataAPI.git
-version: 0.2801
+  repository: git://lopnor.github.com/lopnor/Net-Google-DataAPI.git
+version: '0.2805'
@@ -3,11 +3,12 @@ name 'Net-Google-DataAPI';
 all_from 'lib/Net/Google/DataAPI.pm';
 
 requires 'Carp';
+requires 'XML::LibXML';
 requires 'XML::Atom';
 requires 'Net::Google::AuthSub' => '0.5';
 requires 'Digest::SHA1';
 requires 'Net::OAuth';
-requires 'Net::OAuth2' => '0.07';
+requires 'Net::OAuth2' => '0.60';
 requires 'LWP::UserAgent';
 requires 'LWP::Protocol::https';
 requires 'URI';
@@ -2,6 +2,7 @@ use strict;
 use warnings;
 use lib 'lib';
 use Amon2::Lite;
+use Amon2::Util;
 use Net::Google::DataAPI::Auth::OAuth2;
 use Net::Google::Spreadsheets;
 
@@ -17,7 +18,7 @@ spreadsheets.psgi - sample web app using google spreadsheets with OAuth 2.0
 
 =head1 DEPENDENCY
 
-you need to have Amon2::Lite and Net::Google::Spreadsheets distributions in you box.
+you need to have Amon2::Lite and Net::Google::Spreadsheets distributions in your box.
 
 =head1 AUTHOR
 
@@ -25,16 +26,18 @@ Nobuo Danjou E<lt>nobuo.danjou@gmail.comE<gt>
 
 =head1 SEE ALSO
 
-http://code.google.com/intl/ja-JP/apis/accounts/docs/OAuth2.html
+https://developers.google.com/accounts/docs/OAuth2
 
 =cut
 
 sub oauth2 {
+    my $state = shift || '';
     Net::Google::DataAPI::Auth::OAuth2->new(
         client_id => $ENV{CLIENT_ID},
         client_secret => $ENV{CLIENT_SECRET},
         redirect_uri => 'http://localhost:5000/callback',
         scope => ['http://spreadsheets.google.com/feeds/'],
+        state => $state,
     );
 }
 
@@ -51,7 +54,9 @@ get '/' => sub {
 
 get '/login' => sub {
     my ($c) = @_;
-    $c->redirect(oauth2()->authorize_url());
+    my $state = Amon2::Util::random_string(30);
+    $c->session->set(state => $state);
+    $c->redirect(oauth2($state)->authorize_url());
 };
 
 get '/logout' => sub {
@@ -67,9 +72,13 @@ get '/callback' => sub {
     } 
     my $code = $c->req->param('code')
         or return $c->redirect($c->uri_for('/'));
+    my $state = $c->session->get('state')
+        or return $c->redirect($c->uri_for('/'));
+    $c->req->param('state') eq $state
+        or return $c->res_403;
     my $oauth2 = oauth2();
     my $at = $oauth2->get_access_token($code)
-        or $c->return_403;
+        or return $c->res_403;
     $c->session->set(token => $at);
     $c->redirect($c->uri_for('/'));
 };
@@ -4,7 +4,7 @@ package Module::Install::Base;
 use strict 'vars';
 use vars qw{$VERSION};
 BEGIN {
-	$VERSION = '1.06';
+	$VERSION = '1.08';
 }
 
 # Suspend handler for "redefined" warnings
@@ -8,7 +8,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.06';
+	$VERSION = '1.08';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.06';
+	$VERSION = '1.08';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -8,7 +8,7 @@ use Fcntl qw/:flock :seek/;
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.06';
+	$VERSION = '1.08';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.06';
+	$VERSION = '1.08';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.06';
+	$VERSION = '1.08';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.06';
+	$VERSION = '1.08';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
@@ -31,7 +31,7 @@ BEGIN {
 	# This is not enforced yet, but will be some time in the next few
 	# releases once we can make sure it won't clash with custom
 	# Module::Install extensions.
-	$VERSION = '1.06';
+	$VERSION = '1.08';
 
 	# Storage for the pseudo-singleton
 	$MAIN    = undef;
@@ -5,7 +5,7 @@ with 'Net::Google::DataAPI::Role::Auth';
 use Net::OAuth2::Client;
 use Net::OAuth2::Profile::WebServer;
 use HTTP::Request::Common;
-our $VERSION = '0.01';
+our $VERSION = '0.05';
 
 has [qw(client_id client_secret)] => (is => 'ro', isa => 'Str', required => 1);
 has redirect_uri => (is => 'ro', isa => 'Str', default => 'urn:ietf:wg:oauth:2.0:oob');
@@ -15,6 +15,7 @@ has scope => (is => 'ro', isa => 'ArrayRef[Str]', required => 1, auto_deref => 1
         'https://www.googleapis.com/auth/userinfo.email'
     ]},
 );
+has state => (is => 'ro', isa => 'Str', default => '');
 has site => (is => 'ro', isa => 'Str', default => 'https://accounts.google.com');
 has authorize_path => (is => 'ro', isa => 'Str', default => '/o/oauth2/auth');
 has access_token_path => (is => 'ro', isa => 'Str', default => '/o/oauth2/token');
@@ -28,19 +29,24 @@ sub _build_oauth2_client {
         site => $self->site,
         authorize_path => $self->authorize_path,
         access_token_path => $self->access_token_path,
+        refresh_token_path => $self->access_token_path,
     );
 }
 has oauth2_webserver => (is => 'ro', isa => 'Net::OAuth2::Profile::WebServer', required => 1, lazy_build => 1);
 sub _build_oauth2_webserver {
     my $self = shift;
-    $self->oauth2_client->web_server( redirect_uri => $self->redirect_uri );
+    $self->oauth2_client->web_server( 
+        redirect_uri => $self->redirect_uri,
+        state => $self->state,
+    );
 }
 has access_token => (is => 'rw', isa => 'Net::Google::DataAPI::Types::OAuth2::AccessToken', coerce => 1);
 
 sub authorize_url {
     my $self = shift;
-    return $self->oauth2_webserver->authorize_url(
-        scope => join(' ', $self->scope)
+    return $self->oauth2_webserver->authorize(
+        scope => join(' ', $self->scope),
+        @_
     );
 }
 
@@ -52,34 +58,19 @@ sub get_access_token {
 
 sub refresh_token {
     my ($self, $refresh_token) = @_;
-    $refresh_token ||= $self->access_token->refresh_token;
-    my $res = $self->oauth2_client->request(
-        POST($self->oauth2_webserver->access_token_url,
-            {
-                refresh_token => $refresh_token,
-                grant_type => 'refresh_token',
-                client_id => $self->client_id,
-                client_secret => $self->client_secret,
-            }
-        )
-    );
-    $res->is_success or die 'refresh_token request failed';
-    my $res_params = Net::OAuth2::Profile::WebServer::_parse_json($res->content) 
-        or die 'parse_json for refresh_token response failed';
-    $self->access_token($res_params);
+    $self->oauth2_webserver->update_access_token($self->access_token);
+    $self->access_token->refresh;
 }
 
 sub userinfo {
     my $self = shift;
     my $at = $self->access_token or die 'access_token is required';
     my $url = URI->new($self->userinfo_url);
-    $url->query_form(access_token => $at->access_token);
-    my $req = $self->sign_request(GET($url));
-    my $res = $self->oauth2_client->request($req);
+    my $res = $at->get($url);
     $res->is_success or die 'userinfo request failed: '.$res->as_string;
-    my $res_params = Net::OAuth2::Profile::WebServer::_parse_json($res->content) 
-        or die 'parse_json for userinfo response failed';
-    return $res_params;
+    my %res_params = $self->oauth2_webserver->params_from_response($res)
+        or die 'params_from_response for userinfo response failed';
+    return \%res_params;
 }
 
 sub sign_request {
@@ -123,6 +114,13 @@ Net::Google::DataAPI::Auth::OAuth2 - OAuth2 support for Google Data APIs
   );
   my $url = $oauth2->authorize_url();
 
+  # you can add optional parameters:
+  #
+  #   my $url = $oauth2->authorize_url(
+  #     access_type => 'offline',
+  #     approval_prompt => 'force',
+  #   );
+
   # show the user $url and get $code
   # if you're making web app, you will do:
   #
@@ -8,11 +8,17 @@ use XML::Atom::Entry;
 use XML::Atom::Feed;
 use Net::Google::DataAPI::Types;
 use Net::Google::DataAPI::Auth::Null;
-our $VERSION = '0.04';
+our $VERSION = '0.05';
 
 $XML::Atom::ForceUnicode = 1;
 $XML::Atom::DefaultVersion = 1;
 
+# Make Net::HTTP not bail out on the connection if it doesn't receive
+# a newline in a timely fashion.
+my %OPTS = @LWP::Protocol::http::EXTRA_SOCK_OPTS;
+$OPTS{MaxLineLength} ||= 1024 * 1024; # default was perhaps 8192
+@LWP::Protocol::http::EXTRA_SOCK_OPTS = %OPTS;
+
 has gdata_version => (
     isa => 'Str',
     is => 'ro',
@@ -5,7 +5,7 @@ use Any::Moose '::Exporter';
 use Carp;
 use Lingua::EN::Inflect::Number qw(to_PL);
 use XML::Atom;
-our $VERSION = '0.2801';
+our $VERSION = '0.2805';
 
 any_moose('::Exporter')->setup_import_methods(
     as_is => ['feedurl', 'entry_has'],
@@ -0,0 +1,13 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+BEGIN {
+    use_ok('Net::Google::DataAPI::Role::Service');
+}
+
+ok my %opts = @LWP::Protocol::http::EXTRA_SOCK_OPTS;
+is $opts{MaxLineLength}, 1048576;
+
+done_testing;
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+BEGIN {
+    push @LWP::Protocol::http::EXTRA_SOCK_OPTS, MaxLineLength => 1024;
+
+    use_ok('Net::Google::DataAPI::Role::Service');
+}
+
+ok my %opts = @LWP::Protocol::http::EXTRA_SOCK_OPTS;
+is $opts{MaxLineLength}, 1024;
+
+done_testing;
@@ -6,7 +6,6 @@ use Test::Exception;
 use LWP::UserAgent;
 use URI;
 use JSON;
-
 BEGIN {
     use_ok 'Net::Google::DataAPI::Auth::OAuth2';
 }
@@ -26,17 +25,28 @@ BEGIN {
 }
 {
     my $ua = Test::MockModule->new('LWP::UserAgent');
+    my $i = 0;
     $ua->mock(request => sub {
             my ($self, $req) = @_;
             is $req->method, 'POST';
-            is_deeply {URI->new('?'.$req->content)->query_form}, {
-                grant_type => 'authorization_code',
-                redirect_uri => 'urn:ietf:wg:oauth:2.0:oob',
-                client_secret => 'mysecret',
-                client_id => 'myclient.example.com',
-                type => 'web_server',
-                code => 'mycode',
-            };
+            my $q = {URI->new('?'.$req->content)->query_form};
+            $i++;
+            if ($i == 1) {
+                is_deeply $q, {
+                    grant_type => 'authorization_code',
+                    redirect_uri => 'urn:ietf:wg:oauth:2.0:oob',
+                    client_secret => 'mysecret',
+                    client_id => 'myclient.example.com',
+                    code => 'mycode',
+                };
+            } else {
+                is_deeply $q, {
+                    grant_type => 'refresh_token',
+                    client_secret => 'mysecret',
+                    client_id => 'myclient.example.com',
+                    refresh_token => 'my_refresh_token',
+                };
+            }
             my $res = HTTP::Response->new(200);
             $res->header('Content-Type' => 'text/json');
             my $json = to_json({
@@ -52,6 +62,7 @@ BEGIN {
     ok my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
         client_id => 'myclient.example.com',
         client_secret => 'mysecret',
+        state => '',
     );
     ok my $url = $oauth2->authorize_url;
     $url = URI->new($url);
@@ -63,12 +74,12 @@ BEGIN {
         redirect_uri => 'urn:ietf:wg:oauth:2.0:oob',
         response_type => 'code',
         scope => 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
-        type => 'web_server',
+        state => '',
     } or note explain {$url->query_form};
 
     ok my $access_token = $oauth2->get_access_token('mycode');
     is $access_token->access_token, 'my_access_token';
-    is $access_token->{refresh_token}, 'my_refresh_token';
+    is $access_token->refresh_token, 'my_refresh_token';
     my $req = HTTP::Request->new('get' => 'http://foo.bar.com');
     ok $oauth2->sign_request($req);
     is $req->header('Authorization'), 'Bearer my_access_token';
@@ -80,6 +91,7 @@ BEGIN {
         client_secret => 'mysecret',
         redirect_uri => 'https://example.com/callback',
         scope => ['http://spreadsheets.google.com/feeds/'],
+        state => 'hogehoge',
     );
     ok my $url = $oauth2->authorize_url;
     $url = URI->new($url);
@@ -91,7 +103,32 @@ BEGIN {
         redirect_uri => 'https://example.com/callback',
         response_type => 'code',
         scope => 'http://spreadsheets.google.com/feeds/',
-        type => 'web_server',
+        state => 'hogehoge',
+    } or note explain {$url->query_form};
+
+#    ok $oauth2->get_access_token('mycode');
+}
+{
+    ok my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
+        client_id => 'myclient.example.com',
+        client_secret => 'mysecret',
+        redirect_uri => 'https://example.com/callback',
+        scope => ['http://spreadsheets.google.com/feeds/'],
+        state => 'foobar',
+    );
+    ok my $url = $oauth2->authorize_url(access_type => 'offline', approval_prompt => 'force');
+    $url = URI->new($url);
+    is $url->scheme, 'https';
+    is $url->host, 'accounts.google.com';
+    is $url->path, '/o/oauth2/auth';
+    is_deeply {$url->query_form}, {
+        client_id => 'myclient.example.com',
+        redirect_uri => 'https://example.com/callback',
+        response_type => 'code',
+        scope => 'http://spreadsheets.google.com/feeds/',
+        access_type => 'offline',
+        approval_prompt => 'force',
+        state => 'foobar',
     } or note explain {$url->query_form};
 
 #    ok $oauth2->get_access_token('mycode');