LWP-Authen-OAuth2-0.16/0000755000175000017500000000000013435051255013101 5ustar dommdommLWP-Authen-OAuth2-0.16/README0000644000175000017500000000216513366357041013772 0ustar dommdommLWP-Authen-OAuth2 LWP::Authen::OAuth2 lets you access OAuth 2 protected APIs with LWP. Specifically it provides helper methods to construct all requests to the service provider, and takes care (if possible) of details like automatically refreshing tokens when needed. There can be wide variation between implementations of OAuth 2. Simple implementations can be handled with some configuration information. But hooks are available to handle more complex service providers. INSTALLATION To install this module, run the following commands: perl Makefile.PL make make test make install SUPPORT AND DOCUMENTATION After installing, you can find documentation for this module with the perldoc command. perldoc LWP::Authen::OAuth2 You can also look for information at: RT, CPAN's request tracker (report bugs here) http://rt.cpan.org/NoAuth/Bugs.html?Dist=LWP-Authen-OAuth2 AnnoCPAN, Annotated CPAN documentation http://annocpan.org/dist/LWP-Authen-OAuth2 CPAN Ratings http://cpanratings.perl.org/d/LWP-Authen-OAuth2 Search CPAN http://search.cpan.org/dist/LWP-Authen-OAuth2/ LWP-Authen-OAuth2-0.16/ignore.txt0000644000175000017500000000031412347400061015115 0ustar dommdommMakefile Makefile.old Build Build.bat META.* MYMETA.* .build/ _build/ cover_db/ blib/ inc/ .lwpcookies .last_cover_stats nytprof.out pod2htm*.tmp pm_to_blib LWP-Authen-OAuth2-* LWP-Authen-OAuth2-*.tar.gz LWP-Authen-OAuth2-0.16/MANIFEST0000644000175000017500000000232313435051255014232 0ustar dommdommChanges ignore.txt lib/LWP/Authen/OAuth2.pm lib/LWP/Authen/OAuth2/AccessToken.pm lib/LWP/Authen/OAuth2/AccessToken/Bearer.pm lib/LWP/Authen/OAuth2/Args.pm lib/LWP/Authen/OAuth2/Overview.pod lib/LWP/Authen/OAuth2/ServiceProvider.pm lib/LWP/Authen/OAuth2/ServiceProvider/Dwolla.pm lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm lib/LWP/Authen/OAuth2/ServiceProvider/Line.pm lib/LWP/Authen/OAuth2/ServiceProvider/Line/AccessToken.pm lib/LWP/Authen/OAuth2/ServiceProvider/Strava.pm lib/LWP/Authen/OAuth2/ServiceProvider/Yahoo.pm Makefile.PL MANIFEST This list of files MANIFEST.SKIP README README.md t/LWP/Authen/OAuth2.t t/LWP/Authen/OAuth2/AccessToken.t t/LWP/Authen/OAuth2/AccessToken/Bearer.t t/LWP/Authen/OAuth2/Args.t t/LWP/Authen/OAuth2/ServiceProvider.t t/LWP/Authen/OAuth2/ServiceProvider/Dwolla.t t/LWP/Authen/OAuth2/ServiceProvider/Google.t t/LWP/Authen/OAuth2/ServiceProvider/Line.t t/LWP/Authen/OAuth2/ServiceProvider/Line/AccessToken.t t/LWP/Authen/OAuth2/ServiceProvider/Strava.t t/LWP/Authen/OAuth2/ServiceProvider/Yahoo.t t/manifest.t t/pod.t LICENSE META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) LWP-Authen-OAuth2-0.16/META.json0000644000175000017500000000241313435051255014522 0ustar dommdomm{ "abstract" : "Make requests to OAuth2 APIs.", "author" : [ "Ben Tilly " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "LWP-Authen-OAuth2", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "Test::More" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "JSON" : "2", "LWP" : "4", "LWP::Protocol::https" : "6", "Module::Load" : "0", "Mozilla::CA" : "20000101", "perl" : "5.006" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "https://github.com/domm/perl-oauth2.git", "web" : "https://github.com/domm/perl-oauth2" } }, "version" : "0.16", "x_serialization_backend" : "JSON::PP version 2.97001" } LWP-Authen-OAuth2-0.16/MANIFEST.SKIP0000644000175000017500000000003213366357627015011 0ustar dommdomm.tracker.json .travis.yml LWP-Authen-OAuth2-0.16/t/0000755000175000017500000000000013435051255013344 5ustar dommdommLWP-Authen-OAuth2-0.16/t/LWP/0000755000175000017500000000000013435051255014006 5ustar dommdommLWP-Authen-OAuth2-0.16/t/LWP/Authen/0000755000175000017500000000000013435051255015232 5ustar dommdommLWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/0000755000175000017500000000000013435051255016334 5ustar dommdommLWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/AccessToken/0000755000175000017500000000000013435051255020536 5ustar dommdommLWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/AccessToken/Bearer.t0000755000175000017500000000040012347400061022112 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2::AccessToken::Bearer' ) || print "Bail out!\n"; } done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider.t0000755000175000017500000000037112347400061021632 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2::ServiceProvider' ) || print "Bail out!\n"; } done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/Args.t0000755000175000017500000000265312347400061017420 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../lib"; BEGIN { use_ok("LWP::Authen::OAuth2::Args", qw(copy_option assert_options_empty)); } my $obj = bless {}; my $opt = {}; $obj->copy_option($opt, "foo", "bar"); is_deeply($obj, {foo => "bar"}, "Can copy an empty option with default"); $obj->copy_option($opt, "foo", "baz"); is_deeply($obj, {foo => "bar"}, "Silently do not apply default second time"); $opt->{foo} = "baz"; eval { $obj->copy_option($opt, "foo"); }; like($@, qr/Refusing.*'foo'/, "Cannot copy over option"); delete $obj->{foo}; $obj->copy_option($opt, "foo"); is_deeply($obj, {foo => "baz"}, "Do copy if the option is missing"); is_deeply($opt, {}, "Do modify opt on copy"); $obj->assert_options_empty($opt); ok(1, "Did not die asserting empty options empty"); $opt->{bar} = "blat"; eval { $obj->assert_options_empty($opt); }; like($@, qr/Unexpected parameter.*'bar'/, "Catch unexpected parameters"); $obj->copy_option($opt, "bar"); is_deeply($obj, {foo => "baz", bar => "blat"}, "Can copy required"); is_deeply($opt, {}, "Modify opt when copying required"); eval { $obj->copy_option($opt, "baz"); }; like($@, qr/'baz' is required.*missing/, "Notice missing required"); $opt->{baz} = undef; eval { $obj->copy_option($opt, "baz"); }; like($@, qr/'baz' is required.*undef/, "Notice undef required"); done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/AccessToken.t0000755000175000017500000000036512347400061020724 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2::AccessToken' ) || print "Bail out!\n"; } done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider/0000755000175000017500000000000013435051255021447 5ustar dommdommLWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider/Line/0000755000175000017500000000000013435051255022336 5ustar dommdommLWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider/Line/AccessToken.t0000755000175000017500000000324313144645655024744 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken' ) || print "Bail out!\n"; } is($LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken::REFRESH_PERIOD, 10*24*60*60); my $token = LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken->from_ref({ expires_in => 60, access_token => 'dummy_access_token', refresh_token => 'dummy_refresh_token', _class => 'LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken', }); is(ref $token, 'LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken'); is($token->access_token, 'dummy_access_token' ); is($token->refresh_token, 'dummy_refresh_token'); ok(!$token->should_refresh(0), 'not expired'); $token = LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken->from_ref({ expires_in => -60, access_token => 'dummy_access_token', refresh_token => 'dummy_refresh_token', _class => 'LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken', }); ok($token->should_refresh(0), 'expired, refreshable'); $token = LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken->from_ref({ expires_in => -1_000_000, access_token => 'dummy_access_token', refresh_token => 'dummy_refresh_token', _class => 'LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken', }); ok(!$token->should_refresh(0), 'expired, not refreshable'); local $LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken::REFRESH_PERIOD = 2_000_000; ok($token->should_refresh(0), 'REFRESH_PERIOD global is obeyed'); done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider/Dwolla.t0000755000175000017500000000074412670522650023070 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2::ServiceProvider::Dwolla' ) || print "Bail out!\n"; use LWP::Authen::OAuth2; my $oauth2 = LWP::Authen::OAuth2->new( client_id => 'Test', client_secret => 'Test', service_provider => "Dwolla", ); isa_ok($oauth2, 'LWP::Authen::OAuth2'); } done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider/Line.t0000755000175000017500000000172713144645655022547 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2::ServiceProvider::Line' ) || print "Bail out!\n"; use LWP::Authen::OAuth2; my $oauth2 = LWP::Authen::OAuth2->new( client_id => 'Test', client_secret => 'Test', service_provider => 'Line', redirect_uri => 'http://127.0.0.1', ); isa_ok($oauth2, 'LWP::Authen::OAuth2'); my $sp = $oauth2->{service_provider}; is($sp->api_url_base => 'https://api.line.me/v2/' ); is($sp->token_endpoint => 'https://api.line.me/v2/oauth/accessToken' ); is($sp->authorization_endpoint => 'https://access.line.me/dialog/oauth/weblogin' ); is($sp->access_token_class('bearer') => 'LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken'); } done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider/Yahoo.t0000644000175000017500000000123213435050030022677 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../../lib"; BEGIN { use_ok("LWP::Authen::OAuth2::ServiceProvider::Yahoo") || print "Bail out!\n"; use LWP::Authen::OAuth2; my $oauth2 = LWP::Authen::OAuth2->new( client_id => "test", client_secret => "test", service_provider => "Yahoo", redirect_url => "oob", ); isa_ok($oauth2, "LWP::Authen::OAuth2"); } done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider/Google.t0000755000175000017500000000102012670522650023046 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2::ServiceProvider::Google' ) || print "Bail out!\n"; use LWP::Authen::OAuth2; my $oauth2 = LWP::Authen::OAuth2->new( client_id => 'Test', client_secret => 'Test', service_provider => "Google", redirect_uri => "http://127.0.0.1", ); isa_ok($oauth2, 'LWP::Authen::OAuth2'); } done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2/ServiceProvider/Strava.t0000755000175000017500000000102012670522650023072 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2::ServiceProvider::Strava' ) || print "Bail out!\n"; use LWP::Authen::OAuth2; my $oauth2 = LWP::Authen::OAuth2->new( client_id => 'Test', client_secret => 'Test', service_provider => "Strava", redirect_uri => "http://127.0.0.1", ); isa_ok($oauth2, 'LWP::Authen::OAuth2'); } done_testing(); LWP-Authen-OAuth2-0.16/t/LWP/Authen/OAuth2.t0000755000175000017500000000034512347400061016520 0ustar dommdomm#! /usr/bin/env perl use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; use FindBin qw($Bin); use lib "$Bin/../../../lib"; BEGIN { use_ok( 'LWP::Authen::OAuth2' ) || print "Bail out!\n"; } done_testing(); LWP-Authen-OAuth2-0.16/t/manifest.t0000644000175000017500000000062312347400061015332 0ustar dommdomm#!perl -T use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; unless ( $ENV{RELEASE_TESTING} ) { plan( skip_all => "Author tests not required for installation" ); } my $min_tcm = 0.9; eval "use Test::CheckManifest $min_tcm"; plan skip_all => "Test::CheckManifest $min_tcm required" if $@; ok_manifest({ exclude => ['/.git'], filter => [qr/LWP-Authen-OAuth2.*tar.gz/], }); LWP-Authen-OAuth2-0.16/t/pod.t0000644000175000017500000000040112347400061014300 0ustar dommdomm#!perl -T use 5.006; use strict; use warnings FATAL => 'all'; use Test::More; # Ensure a recent version of Test::Pod my $min_tp = 1.22; eval "use Test::Pod $min_tp"; plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; all_pod_files_ok(); LWP-Authen-OAuth2-0.16/README.md0000644000175000017500000000007712347400061014356 0ustar dommdommperl-oauth2 =========== Modules for a Perl consumer of OAuth2 LWP-Authen-OAuth2-0.16/META.yml0000644000175000017500000000131313435051255014350 0ustar dommdomm--- abstract: 'Make requests to OAuth2 APIs.' author: - 'Ben Tilly ' build_requires: Test::More: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: LWP-Authen-OAuth2 no_index: directory: - t - inc requires: JSON: '2' LWP: '4' LWP::Protocol::https: '6' Module::Load: '0' Mozilla::CA: '20000101' perl: '5.006' resources: repository: https://github.com/domm/perl-oauth2.git version: '0.16' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' LWP-Authen-OAuth2-0.16/LICENSE0000644000175000017500000000367513366357041014126 0ustar dommdommLICENSE AND COPYRIGHT Copyright (C) 2013 Rent.com. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: L Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. LWP-Authen-OAuth2-0.16/Makefile.PL0000644000175000017500000000254013435050030015042 0ustar dommdommuse 5.006; use strict; use warnings FATAL => 'all'; use ExtUtils::MakeMaker; WriteMakefile( NAME => 'LWP::Authen::OAuth2', AUTHOR => q{Ben Tilly }, VERSION_FROM => 'lib/LWP/Authen/OAuth2.pm', ABSTRACT_FROM => 'lib/LWP/Authen/OAuth2.pm', LICENSE => 'artistic_2', PL_FILES => {}, MIN_PERL_VERSION => 5.006, CONFIGURE_REQUIRES => { 'ExtUtils::MakeMaker' => 0, }, BUILD_REQUIRES => { 'Test::More' => 0, }, PREREQ_PM => { 'LWP' => 4.0, 'LWP::Protocol::https' => 6.0, 'Mozilla::CA' => 20000101, 'JSON' => 2, 'Module::Load' => 0, }, dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, clean => { FILES => 'LWP-Authen-OAuth2-*' }, test => { TESTS => "t/*.t t/LWP/Authen/*.t t/LWP/Authen/OAuth2/*.t t/LWP/Authen/OAuth2/ServiceProvider/*.t t/LWP/Authen/OAuth2/ServiceProvider/Line/*.t t/LWP/Authen/OAuth2/AccessToken/*.t" }, (eval { ExtUtils::MakeMaker->VERSION(6.46) } ? (META_MERGE => { 'meta-spec' => { version => 2 }, resources => { repository => { type => 'git', url => 'https://github.com/domm/perl-oauth2.git', web => 'https://github.com/domm/perl-oauth2', }, }}) : () ), ); LWP-Authen-OAuth2-0.16/Changes0000644000175000017500000000642313435051231014373 0ustar dommdommRevision history for LWP-Authen-OAuth2 0.16 2019-02-25T21:39:03 - remove useless links to search.cpan.org (Thomas Klausner) - add Michael Stevens to thank-you section (Thomas Klausner) - Add new Yahoo ServiceProvider. (Michael Stevens) - Make "make test" actually find all the tests. (Michael Stevens) - Update github repo link to point to domm not btilly. (Michael Stevens) - MANIFEST fixes (Thomas Klausner) 0.15 2018-10-31T17:54:15 - Update to match CPAN (Colin Newell) - Add license file (eadjei) - Adding travis config (Ndifreke Ekott) - add Adam Millerchip to contributors/thanks (Thomas Klausner) - update synopsis to show state is required (Adam Millerchip) - Add Service Provider for Line (Adam Millerchip) 0.14 2017-08-16T08:18:46 - released on CPAN-Day 2017 from Sibiu, Romania - Add Service Provider for Line (Adam Millerchip) 0.13 2016-11-21T15:08:43 - Add documentation for some new methods, and for Dwolla (Adi Fairbank) 0.12 2016-07-13T12:44:47 - fix typos in documentation (Nick Morrott, RT115463, RT115464) 0.11 2016-03-11T12:02:25 - Ensure lib loads and instantiates (Leon Wright) - Catch feature changes that break instantiation (Leon Wright) - Allow 'use_test_urls' to be optional (Leon Wright) 0.10 2016-01-11T20:28:13 - new class ServiceProvider::Dwolla (Adi Fairbank) - new method OAuth2->make_api_call (Adi Fairbank) - add configuration param use_test_urls (Adi Fairbank) - add expires_time() method (Adi Fairbank) - link to GitHub from META files; fix the string in LICENSE (Gabor Szabo) 0.09 2014-08-25T10:59:11+0200 - squashed some warnings (Leon Wright) - Strava Service Provider (Leon Wright) 0.08 2014-06-27T00:00:00 - NEW MAINTAINER: domm - inverted Changelog (newest entry on top) - Allow additional arguments to be passed to the save_tokens callback (Chris) - Fixed copying required/optional params in collect_action_params (Alexander Dutton) - Added overridable methods for returing authz and token endpoints (Alexander Dutton) 0.07 20131128 - 7:47 AM PST - Property acknowledge domm 0.06 20131128 - 7:47 AM PST - Add Module::Load dependency - Catch that the Google service provider was missing s/flow/client_type/. - Make syntax errors in a service provider less confusing to debug. 0.05 20130720 - 12:38 PM PDT - There was another (why don't I get to see these???) 0.04 20130717 - 6:34 PM PDT - Fix 2 minor POD formatting mistakes. 0.03 20130715 - 7:26 PM PDT - Find more dependencies. - Rename flow to client_type in many places. - Rename *_more_* to *_optional_* in ServiceProvider. - Rename *_defaults to *_init to be more descriptive. - Another documentation pass. 0.02 20130712 - 2:54 PM PDT *sigh* My machine was not running all of the tests that module starter installed. Other environments did, and therefore failed. Removed or fixed those. (Really, I do not want to have end user documentation in subclasses of parent methods that they were supposed to override.) 0.01 20130711 - 3:13 PM PDT First version, released on an unsuspecting world with much documentation and no useful tests. LWP-Authen-OAuth2-0.16/lib/0000755000175000017500000000000013435051255013647 5ustar dommdommLWP-Authen-OAuth2-0.16/lib/LWP/0000755000175000017500000000000013435051255014311 5ustar dommdommLWP-Authen-OAuth2-0.16/lib/LWP/Authen/0000755000175000017500000000000013435051255015535 5ustar dommdommLWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/0000755000175000017500000000000013435051255016637 5ustar dommdommLWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/AccessToken/0000755000175000017500000000000013435051255021041 5ustar dommdommLWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/AccessToken/Bearer.pm0000644000175000017500000000732012645001021022565 0ustar dommdommpackage LWP::Authen::OAuth2::AccessToken::Bearer; use strict; use warnings; use base "LWP::Authen::OAuth2::AccessToken"; use Storable qw(dclone); sub _request { my ($self, $oauth2, $request, @rest) = @_; my $actual_request = dclone($request); # This is where we sign it. $actual_request->header('Authorization' => "Bearer $self->{access_token}"); my $response = $oauth2->user_agent->request($actual_request, @rest); # One would hope for a 401 status, but the specification only requires # this header. (Though recommends a 401 status.) my $try_refresh = ($response->header("WWW-Authenticate")||'') =~ m/\binvalid_token\b/ || ($response->header('Client-Warning')||'') =~ m/Missing Authenticate header/ # Dwolla does not send WWW-Authenticate || $response->content() =~ m/\bExpiredAccessToken\b/ ? 1 : 0; return ($response, $try_refresh); } =head1 NAME LWP::Authen::OAuth2::AccessToken::Bearer - Bearer access tokens for OAuth 2. =head1 VERSION Version 0.02 =cut our $VERSION = '0.02'; =head1 SYNOPSIS This implements bearer tokens. See L for details, L for how this module works, and L for the interface to use to this module. Whether this module gets used depends on the service provider. =head1 AUTHOR Ben Tilly, C<< >> =head1 BUGS We should test this... =head1 ACKNOWLEDGEMENTS Thanks to L for their generous support in letting me develop and release this module. My thanks also to Nick Wellnhofer for Net::Google::Analytics::OAuth2 which was very enlightening while I was trying to figure out the details of how to connect to Google with OAuth2. =head1 LICENSE AND COPYRIGHT Copyright 2013 Rent.com. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: L Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =cut 1; LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/Args.pm0000644000175000017500000000340112347400061020061 0ustar dommdommpackage LWP::Authen::OAuth2::Args; use warnings; use strict; use Carp qw(croak confess); use Exporter qw(import); our @EXPORT_OK = qw(extract_option copy_option assert_options_empty); # Blessed empty hash. sub new { return bless {}, shift; } sub extract_option { my $obj = shift; my $opts = shift; my $name = shift; my $has_default = @_; my $default = shift; if (exists $opts->{$name}) { return delete $opts->{$name}; } elsif ($has_default) { return $default; } else { croak("'$name' is required, cannot be missing"); } } sub copy_option { my $obj = shift; my $opts = shift; my $name = shift; my $has_default = @_; my $default = shift; if (not exists $obj->{$name}) { if (exists $opts->{$name}) { my $value = delete $opts->{$name}; if (defined($value)) { $obj->{$name} = $value; } elsif ($has_default) { $obj->{$name} = $default; } else { croak("'$name' is required, cannot be undef"); } } elsif ($has_default) { $obj->{$name} = $default; } else { croak("'$name' is required, cannot be missing"); } } elsif (exists $opts->{$name}) { # This should not be hit, but if it was, it would be confusing confess("Refusing to copy '$name' that is already in hash"); } } sub assert_options_empty { my ($obj, $opts) = @_; my @keys = sort keys %$opts; if (1 == @keys) { croak("Unexpected parameter: '$keys[0]'"); } elsif (@keys) { my $extra = "'" . (join "', '", @keys) . "'"; croak("Extra parameters: $extra"); } } 1; LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider.pm0000644000175000017500000006162313435050030022306 0ustar dommdommpackage LWP::Authen::OAuth2::ServiceProvider; use 5.006; use strict; use warnings; use Carp qw(confess croak); use JSON qw(decode_json); use Memoize qw(memoize); use Module::Load qw(load); use URI; our @CARP_NOT = qw(LWP::Authen::OAuth2 LWP::Authen::OAuth2::Args); use LWP::Authen::OAuth2::Args qw( extract_option copy_option assert_options_empty ); # Construct a new object. sub new { my ($class, $opts) = @_; # I start as an empty hashref. my $self = {}; # But what class am I supposed to actually be? if (not exists $opts->{service_provider}) { bless $self, $class; } else { # Convert "Google" to "LWP::Authen::OAuth2::ServiceProvider::Google" # Not a method because no object yet exists. $class = service_provider_class(delete $opts->{service_provider}); my $client_type = delete $opts->{client_type}; if (not defined($client_type)) { $client_type = "default"; } bless $self, $class->client_type_class($client_type); } $self->init($opts); } sub init { my ($self, $opts) = @_; # Now let us consume options. 2 args = required, 3 = defaulted. # In general subclasses should Just Work. # need to read this first, since the later opts depend on it $self->copy_option($opts, 'use_test_urls') if defined $opts->{use_test_urls}; # These are required, NOT provided by this class, but are by subclasses. for my $field (qw(token_endpoint authorization_endpoint)) { if ($self->can($field)) { $self->copy_option($opts, $field, $self->$field); } else { $self->copy_option($opts, $field); } } # These are defaulted by this class, maybe overridden by subclasses. for my $field ( qw(required_init optional_init), map { ("$_\_required_params", "$_\_optional_params") } qw(authorization request refresh) ) { $self->copy_option($opts, $field, [$self->$field]); } # And hashrefs for default key/value pairs. for my $field ( map "$_\_default_params", qw(authorization request refresh) ) { $self->copy_option($opts, $field, {$self->$field}); } return $self; } sub authorization_url { my ($self, $oauth2, @rest) = @_; my $param = $self->collect_action_params("authorization", $oauth2, @rest); my $uri = URI->new($self->authorization_endpoint()); $uri->query_form(%$param); return $uri->as_string; } sub request_tokens { my ($self, $oauth2, @rest) = @_; my $param = $self->collect_action_params("request", $oauth2, @rest); my $response = $self->post_to_token_endpoint($oauth2, $param); return $self->construct_tokens($oauth2, $response); } sub can_refresh_tokens { my ($self, $oauth2, %opt) = @_; my %default = $self->refresh_default_params; my $oauth2_args = $oauth2->for_service_provider; for my $param ($self->refresh_required_params) { if ( exists $default{$param} or exists $oauth2_args->{$param} or exists $opt{$param} ) { next; } else { return 0; } } return 1; } sub refreshed_tokens { my ($self, $oauth2, @rest) = @_; my $param = $self->collect_action_params("refresh", $oauth2, @rest); my $response = $self->post_to_token_endpoint($oauth2, $param); # Error message or object, this is what we return. return $self->construct_tokens($oauth2, $response); } sub collect_action_params { my $self = shift; my $action = shift; my $oauth2 = shift; my $oauth2_args = $oauth2->for_service_provider; my @rest = @_; my $opt = {@_}; my $default = $self->{"$action\_default_params"}; if ($oauth2->is_strict) { # We copy one by one with testing. my $result = {}; for my $param (@{ $self->{"$action\_required_params"}}) { if (exists $opt->{$param}) { if (defined $opt->{$param}) { $result->{$param} = delete $opt->{$param}; } else { croak("Cannot pass undef for required param '$param'"); } } elsif (defined $oauth2_args->{$param}) { $result->{$param} = $oauth2_args->{$param}; } elsif (defined $default->{$param}) { $result->{$param} = $default->{$param}; } else { croak("Missing required param '$param'"); } } for my $param (@{ $self->{"$action\_optional_params"} }) { for my $source ($result, $opt, $oauth2_args, $default) { if (exists $source->{$param}) { # Only add it if it is not undef. Else hide. if (defined $source->{$param}) { $result->{$param} = $source->{$param}; } # For opt only, delete if it was found. if ($opt == $source) { delete $opt->{$param}; } last; # source # (undef is deliberate override, which is OK) } } } $self->assert_options_empty($opt); # End of strict section. return $result; } else { # Not strict just bulk copy. my $result = { %$default, ( map {($_, $oauth2_args->{$_})} @{ $self->{"$action\_required_params"} }, @{ $self->{"$action\_optional_params"} } ), %$opt }; for my $key (keys %$result) { if (not defined($result->{$key})) { delete $result->{$key}; } } return $result; } } sub post_to_token_endpoint { my ($self, $oauth2, $param) = @_; my $ua = $oauth2->user_agent(); return $ua->post($self->token_endpoint(), [%$param]); } sub api_url_base { return '' } # override in subclass sub access_token_class { my ($self, $type) = @_; if ("bearer" eq $type) { return "LWP::Authen::OAuth2::AccessToken::Bearer"; } else { return "Token type '$type' not yet implemented"; } } # Attempts to construct tokens, returns the access_token (which may have a # request token embedded). sub construct_tokens { my ($self, $oauth2, $response) = @_; # The information that I need. my $content = eval {$response->decoded_content}; if (not defined($content)) { $content = ''; } my $data = eval {decode_json($content)}; my $parse_error = $@; my $token_endpoint = $self->token_endpoint(); # Can this have done wrong? Let me list the ways... if ($parse_error) { # "Should not happen", hopefully just network. # Tell the programmer everything. my $status = $response->status_line; return <<"EOT" Token endpoint gave invalid JSON in response. Endpoint: $token_endpoint Status: $status Parse error: $parse_error JSON: $content EOT } elsif ($data->{error}) { # Assume a valid OAuth 2 error message. my $message = "OAuth2 error: $data->{error}"; # Do we have a mythical service provider that gives us more? if ($data->{error_uri}) { # They seem to have a web page with detail. $message .= "\n$data->{error_uri} may say more.\n"; } if ($data->{error_description}) { # Wow! Thank you! $message .= "\n\nDescription: $data->{error_description}\n"; } return $message; } elsif (not $data->{token_type}) { # Someone failed to follow the spec... return <<"EOT"; Token endpoint missing expected token_type in successful response. Endpoint: $token_endpoint JSON: $content EOT } my $type = $self->access_token_class(lc($data->{token_type})); if ($type !~ /^[\w\:]+\z/) { # We got an error. :-( return $type; } eval {load($type)}; if ($@) { # MAKE THIS FATAL. (Clearly Perl code is simply wrong.) confess("Loading $type for $data->{token_type} gave error: $@"); } # Try to make an access token. my $access_token = $type->from_ref($data); if (not ref($access_token)) { # This should be an error message of some sort. return $access_token; } else { # WE SURVIVED! EVERYTHING IS GOOD! if ($oauth2->access_token) { $access_token->copy_refresh_from($oauth2->access_token); } return $access_token; } } # Override for your client_types if you have multiple. sub client_type_class { my ($class, $name) = @_; if ("default" eq $name) { return $class; } else { croak("Flow '$name' not defined for '$class'"); } } # Override should you need the front-end LWP::Authen::OAuth object to have # methods for service provider specific functionality. # # This is not expected to be a common need. sub oauth2_class { return "LWP::Authen::OAuth2"; } memoize("service_provider_class"); sub service_provider_class { my $short_name = shift; eval { load("LWP::Authen::OAuth2::ServiceProvider::$short_name"); }; if (not $@) { return "LWP::Authen::OAuth2::ServiceProvider::$short_name"; } elsif ($@ =~ /Compilation failed/) { confess($@); } else { eval { load($short_name); }; if (not $@) { return $short_name; } elsif ($@ =~ /Compilation failed/) { confess($@); } else { croak("Service provider '$short_name' not found"); } } } # DEFAULTS (can be overridden) sub authorization_endpoint { my $self = shift; return $self->{"authorization_endpoint"}; } sub token_endpoint { my $self = shift; return $self->{"token_endpoint"}; } # DEFAULTS (should be overridden) sub required_init { return qw(client_id client_secret); } sub optional_init { return qw(redirect_uri scope); } sub authorization_required_params { return qw(response_type client_id); } sub authorization_optional_params { return qw(redirect_uri state scope); } sub authorization_default_params { return qw(response_type code); } sub request_required_params { return qw(grant_type client_id client_secret code); } sub request_optional_params { return qw(state); } sub request_default_params { return qw(grant_type authorization_code); } sub refresh_required_params { return qw(grant_type refresh_token client_id client_secret); } sub refresh_optional_params { return qw(); } sub refresh_default_params { return qw(grant_type refresh_token); } =head1 NAME LWP::Authen::OAuth2::ServiceProvider - Understand OAuth2 Service Providers =head1 VERSION Version 0.02 =cut our $VERSION = '0.02'; =head1 SYNOPSIS This is a base module for representing an OAuth 2 service provider. It is implicitly constructed from the parameters to Cnew>, and is automatically delegated to when needed. The first way to try to specify the service provider is with the parameters C and possibly C: LWP::Authen::OAuth2->new( ... service_provider => "Foo", client_type => "bar", # optional ... ); The first parameter will cause L to look for either C, or if that is not found, for C. (If neither is present, an exception will be thrown.) The second parameter will be passed to that module which can choose to customize the service provider behavior based on the client_type. The other way to specify the service provider is by passing in sufficient parameters to create a custom one on the fly: LWP::Authen::OAuth2->new( ... authorization_endpoint => $authorization_endpoint, token_endpoint => $token_endpoint, # These are optional but let you get the typo checks of strict mode authorization_required_params => [...], authorization_optional_params => [...], ... ); See L if you are uncertain how to figure out the I and I from the service provider's documentation. =head1 KNOWN SERVICE PROVIDERS The following service providers are provided in this distribution, with hopefully useful configuration and documentation: =over 4 =item L =item L =item L =item L =item L =back =head1 SUBCLASSING Support for new service providers can be added with subclasses. To do that it is useful to understand how things get delegated under the hood. First L asks L to construct a service provider. Based on the C argument, it figures out that it needs to load and use your base class. A service provider might need different behaviors for different client types. You are free to take the client type and dynamically decide which subclass of yours will be loaded instead to get the correct flow. Should your subclass need to, it can decide that that a subclass of L should be used that actually knows about request types that are specific to your service provider. Hopefully most service providers do not need this, but some do. For all of the potential complexity that is supported, B service provider subclasses should be simple. Just state what fields differ from the specification for specific requests and client types, then include documentation. However even crazy service providers should be supportable. Here are the methods that were designed to be useful to override. See the source if you have a need that none of these address. But if you can do what you need to do through these, please do. =over 4 =item C Takes no arguments, returns the URL for the Authorization Endpoint for the service provider. Your subclass cannot function without this. =item C Takes no arguments, returns the URL for the Token Endpoint for the service provider. Your subclass cannot function without this. =item C This method receives your class name and the passed in C. It is supposed to make sure that the class that handles that C is loaded, and then return it. This lets you handle service providers with different behavior for different types of clients. The base implementation just returns your class name. If the programmer does not pass an explicit C the value that is passed in is C. So that should be mapped to a reasonable client type. This likely is something along the line of "webserver". That way your module can be used without specifying a C. =item C After C has figured out the right class to load, it immediately calls C<$self-einit($opts)> with C<$opts> being a hashref of all options passed to Cnew(...)> that were not consumed in figuring out the service provider. This method can then extract any parameters that it wants to before anything else happens. If you only want to require/allow a few parameters to be extracted into the service provider object, then there is no need to write your own C. But if you want additional logic depending on passed in parameters, you can. To consume options and copy them to C<$self> please use the following methods: $self->copy_option($opts, $required_field); $self->copy_option($opts, $optional_field, $default); If you want to consume options and return them as values instead: my $value1 = $self->extract_option($opts, $required_field); my $value2 = $self->extract_option($opts, $optional_field, $default); These methods delete from the hash, so do not try to consume an option twice. =item C The parameters that must be passed into Cnew(...)> to initialize the service provider object. The default required parameters are C and C, which in turn get used as default arguments inside of methods that need them. In general it is good to only require arguments that are needed to generate refreshed tokens. If you will not get a C in your flow, then you should require nothing. =item C The parameters that can be passed into Cnew(...)> to initialize the service provider object. The default optional parameters are C and C which, if passed, do not have to be passed into other method calls. The C is not included as an explicit hint that you should not simply use a default value. Note that these lists are deduped, so there is no harm in parameters being both required and optional, or appearing multiple times. =item C<{authorization,request,refresh}_required_params> These three methods list parameters that B be included in the authorization url, the post to request tokens, and the post to refresh tokens respectively. Supplying these can give better error messages if they are left out. =item C<{authorization,request,refresh}_optional_params> These three methods list parameters that B be included in the authorization url, the post to request tokens, and the post to refresh tokens respectively. In strict mode, supplying any parameters not included in more or required params will be an error. Otherwise this has little effect. =item C<{authorization,request,refresh}_default_params> These three methods returns a list of key/value pairs mapping parameters to B values in the authorization url, the post to request tokens, and the post to get refreshed tokens respectively. Supplying these can stop people from having to supply the parameters themselves. An example where this could be useful is to support a flow that uses different types of requests than normal. For example with some client types and service providers, you might use a type of request with a C of C or C. =item C When a post to a token endpoint is constructed, this actually sends the request. The specification allows service providers to require authentication beyond what the specification requires, which may require cookies, specific headers, etc. This method allows you to address that case. =item C Given a C, what class implements access tokens of that type? If your provider creates a new token type, or implements an existing token type in a quirky way that requires a nonstandard model to handle, this method can let you add support for that. The specification says that all the C must be case insensitive, so all types are lower cased for you. If the return value does not look like a package name, it is assumed to be an error message. As long as you have spaces in your error messages and normal looking class names, this should DWIM. See L for a description of the interface that your access token class needs to meet. (You do not have to subclass that - just duck typing here.) =item C Override this to cause Cnew(...)> to return an object in a custom class. This would be appropriate if people using your service provider need methods exposed that are not in L. Few service provider classes should find a reason to do this, but it can be done if you need. =item C This is the method that processes parameters for a given action. Should your service provider support a new kind of request, you can use this along with the C<*_{required,more,default}_params> functions to support it. The implementation of C in this module give an example of how to use it. =back =head1 CONTRIBUTING Patches contributing new service provider subclasses to this distributions are encouraged. Should you wish to do so, please submit a git pull request that does the following: =over 4 =item Implement your provider The more completely implemented, the better. =item Name it properly The name should be of the form: LWP::Authen::OAuth2::ServiceProvider::$ServiceProvider =item List it It needs to be listed as a known service provider in this module. =item Test it It is impossible to usefully test a service provider module without client secrets. However you can have public tests that it compiles, and private tests that will, if someone supplies the necessary secrets, run fuller tests that all works. See the existing unit tests for examples. =item Include it Your files need to be included in the C in the root directory. =item Document Client Registration A developer should be able to read your module and know how to register themselves as a client of the service provider. =item List Client Types Please list the client types that the service provider uses, with just enough detail that a developer can figure out which one to use. Listed types should, of course, either be implemented or be documented as not implemented. =item Document important quirks If the service provider requires or allows useful parameters, try to mention them in your documentation. =item Document limitations If there are known limitations in your implementation, please state them. =item Link to official documentation If the service provider provides official OAuth 2 documentation, please link to it. Ideally a developer will not need to refer to it, but should know how to find it. =back =head1 AUTHOR Ben Tilly, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc LWP::Authen::OAuth2::ServiceProvider You can also look for information at: =over 4 =item Github (submit patches here) L =item RT: CPAN's request tracker (report bugs here) L =item AnnoCPAN: Annotated CPAN documentation L =item CPAN Ratings L =item Search CPAN L =back =head1 ACKNOWLEDGEMENTS Thanks to L for their generous support in letting me develop and release this module. My thanks also to Nick Wellnhofer for Net::Google::Analytics::OAuth2 which was very enlightening while I was trying to figure out the details of how to connect to Google with OAuth2. =head1 LICENSE AND COPYRIGHT Copyright 2013 Rent.com. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: L Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =cut 1 LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/AccessToken.pm0000644000175000017500000001601712645001021021370 0ustar dommdommpackage LWP::Authen::OAuth2::AccessToken; use strict; use warnings; use Carp qw(confess); our @CARP_NOT = qw(LWP::Authen::OAuth2); =head1 NAME LWP::Authen::OAuth2::AccessToken - Access tokens for OAuth 2. =head1 VERSION Version 0.02; =cut our $VERSION = '0.02'; =head1 SYNOPSIS This is a base class for signing API requests with OAuth2 access tokens. A subclass should override the C method with something that knows how to make a request, detect the need to try to refresh, and attmpts to do that. See L for an example. Subclasses of this one are not directly useful. Please see L for the interface that you should be using. =head1 METHODS =head2 C Construct an access token from a hash reference. The default implementation merely blesses it as an object, defaulting the C field to the current time. my $access_token = $class->from_ref($data); If you roll your own, be aware that the fields C and C<_class> get used for purposes out of this class' control. Any other fields may be used. Please die fatally if you cannot construct an object. =cut sub from_ref { my ($class, $data) = @_; # If create_time is passed, then that will overwrite this default. return bless {create_time => time(), %$data}, $class; } =head2 C Construct an unblessed data structure to represent the object that can be serialized as JSON. The default implementation just creates a shallow copy and assumes there are no blessed subobjects. =cut sub to_ref { my $self = shift; return { %$self }; } =head2 C Estimate expiration time. Not always correct, due to transit delays, clock skew, etc. =cut sub expires_time { my $self = shift; my $initial_expires_in = $self->{expires_in} || 3600; return $self->{create_time} + $initial_expires_in; } =head2 C Estimate the seconds until expiration. Not always correct, due to transit delays, clock skew, etc. =cut sub expires_in { my $self = shift; return $self->expires_time - time(); } =head2 C Boolean saying whether a refresh should be emitted now. =cut sub should_refresh { my ($self, $early_refresh_time) = @_; # If the access tokens are short lived relative to $early_refresh_time # we cheat to avoid refreshing TOO often.... if ($self->expires_in/2 < $early_refresh_time) { $early_refresh_time = $self->expires_in/2; } my $expires_in = $self->expires_in(); if ($expires_in < $early_refresh_time) { return 1; } else { return 0; } } =head2 C Returns key/value pairs for C<$oauth2> (and eventually the service provider class) to use in trying to refresh. =cut sub for_refresh { my $self = shift; if ($self->{refresh_token}) { return refresh_token => $self->{refresh_token}; } else { return (); } } =head2 C Pass in a previous access token, copy anything needed to refresh. =cut sub copy_refresh_from { my ($self, $other) = @_; if ($other->{refresh_token}) { $self->{refresh_token} ||= $other->{refresh_token}; } } =head2 C Make a request. If expiration is detected, refreshing by the best available method (if any). my $response = $access_token->request($oauth2, @request_for_lwp); =cut sub request { # Shift off one for easy redispatch to _request. my $self = shift; my ($oauth2, $request, @rest) = @_; if ( $self->should_refresh($oauth2->{early_refresh_time} || 300) and $oauth2->can_refresh_tokens() ) { $oauth2->refresh_access_token(); $self = $oauth2->access_token if ref($oauth2->access_token); } my ($response, $try_refresh) = $self->_request(@_); if ($try_refresh and $oauth2->can_refresh_tokens()) { # Someone's clock is wrong? Try to refresh. $oauth2->refresh_access_token(); if ($self->expires_in < $oauth2->access_token->expires_in) { # We seem to have renewed, try again. ($response, $try_refresh) = $oauth2->access_token->_request(@_); } } return $response; } =head2 C<_request> Make a request with no retry logic, and return a response, and a flag for whether it is possible the access token is expired.. my ($response, $try_refresh) = $access_token->_request($oauth2, @request_for_lwp); B =cut sub _request { my ($self, $oauth2, $request, @rest) = @_; # ... # return ($response, $try_refresh); confess("Method _request needs to be overwritten."); } =head1 AUTHOR Ben Tilly, C<< >> =head1 BUGS We should support more kinds of access tokens. =head1 ACKNOWLEDGEMENTS Thanks to L for their generous support in letting me develop and release this module. My thanks also to Nick Wellnhofer for Net::Google::Analytics::OAuth2 which was very enlightening while I was trying to figure out the details of how to connect to Google with OAuth2. =head1 LICENSE AND COPYRIGHT Copyright 2013 Rent.com. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: L Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =cut 1; LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/Overview.pod0000644000175000017500000006702212741415303021155 0ustar dommdomm=head1 NAME LWP::Authen::OAuth2::Overview - Overview of accessing OAuth2 APIs with LWP::Authen::OAuth2 =head1 Introduction This attempts to be the document that I wished existed when I first tried to access an API that used OAuth 2 for authentication. It explains what OAuth 2 is, how it works, what you need to know to use it, and how L tries to make that easier. It hopefully also explains this in a way which will help you read documentation written by other people who assume that you have this knowledge. Feel free to read as much or little of this document as makes sense for you. It is not actually designed to be read in a single sitting. Since part of the purpose of this document is to familiarize you with the jargon that you're likely to encounter, all terms commonly used in discussions of OAuth 2 with a specific meaning are I. Terms will hopefully be clear from context, but all highlighted terms are explained in the L section. =head1 The Purpose of OAuth 2 OAuth 2 makes it easy for large Is to write many APIs that Is can securely authorize third party Is to use on their behalf. Everything good (and bad!) about the specification comes from this fact. It therefore specifies an authorization handshake through which permissions are set up, and then a message signing procedure through which you can then access the API. Well, actually it specifies many variations of the authorization handshake, and multiple possible signing procedures, because large organizations run into a lot of use cases and try to cover them all. But conceptually they are all fundamentally similar, and so have been lumped together in one monster spec. =head1 The Purpose of L L exists to help Perl programmers who want to be a I of an API protected by OAuth 2 to construct and make all of the necessary requests to the I that you need to make. You will still need to set up your relationship with the I, build your user interaction, manage private data (hooks are provided to make that straightforward), and figure out how to use the API. If that does not sound like it will make your life easier, then this module is not intended for you. If you are not a I, this module is B not intended for you. (Though this document may still be helpful.) =head1 The Basic OAuth 2 Handshake OAuth 2 allows a I to tell a I that a I should be allowed to access the I's data through an API. This permissioning happens through the following handshake. The I sends the I to an I managed by the I. The I tells the I that the I wants access to that account and asks if this is OK. The I confirms that it is, and is sent back to the I with proof of the conversation. The I presents that proof to the I along with proof that it actually is the I, and is granted tokens that will act like keys to the I's account. After that the I can use said tokens to access the API which is protected by OAuth 2. All variations of OAuth 2 follow this basic pattern. A large number of the details can and do vary widely. For example JavaScript applications that want to make AJAX calls use a different kind of proof. Applications installed on devices without web browsers will pass information to/from the user in different ways. And each I is free to do many, many things differently. The specification tries to document commonalities in what different companies are doing, but does not mandate that they all do the same thing. (This sort of complexity is inevitable from a specification that tries to make the lives of large Is easy, and the lives of Is possible.) =head1 Becoming a Consumer If you want to access an OAuth 2 protected API, you need to become a I. Here are the necessary steps, in the order that things happen in. =over 4 =item Register with the I You cannot access a I without them knowing who you are. After you go through their process, at a minimum you will get a public I, a private I, and have agreed on one or more Is that the I can use to deliver an I back to you. (That is not the only kind of proof that the I can be given for the I, but it is probably the only one that makes sense for a Perl I.) The I is often a C URL under your control. You also are likely to have had to tell the I about what type of software you're writing (webserver, command line, etc). This determines your I. They may call this a scenario, or I, or something else. You will also need information about the I. Specifically you will need to know their I and I. They hopefully also have useful documentation about things like their APIs. L is not directly involved in this step. If a L class exists, it should already have the I specific information, and probably has summarized documentation that may make this smoother. If you're really lucky, there will be a CPAN module (or modules) for the API (or APIs) that you want to use. If those do not exist, please consider creating them. If no such classes exist, you can still use the module. Just pass the necessary I facts in your call to Cnew(...)> and an appropriate L will be created for you on the fly. =item Decide how to store sensitive information All of the data shared between you and the I has to be stored on your end. This includes tokens that will let you access private information for the I. You need to be able to securely store and access these. L does not address this, beyond providing hooks that you are free to use as you see fit. =item Build interaction asking for I permission You need to have some way of convincing the I that they want to give you permission, ending in giving them an I which sends them off to the I to authorize access. This interaction can range from a trivial conversation with yourself if you are the only I you will be handling, to a carefully thought through sales pitch if you are trying to get members of the public to sign up. L helps you build that URL. The rest is up to you. =item Build interaction receiving your I When the I finishes their interaction with the I, if the I is sure that they know where to send the user (they know your I, your I makes sense to them) then they will be sent to the I to pass information back to you. If you succeeded, you will receive a I in some way. For instance if your I is a URL, it will have a get parameter named C. You could get an C parameter back instead. See L for a list of the possible errors. Note that there are possible optional fields with extra detail. I would not advise optimism about their presence. L is not involved with this. =item Request tokens Once you have that I you are supposed to immediately trade it in for tokens. L provides the C method to do this for you. Should you not actually get tokens, then the C method will trigger an error. B that the I cannot be expected to work more than once. Nor can you expect the I to repeatedly hand out working Is for the same permission. (The qualifier "working" matters.) Being told this will hopefully let you avoid a painful debugging session that I did not enjoy. =item Save and pass around tokens (maybe) If you will need access to information in multiple locations (for instance on several different web pages), then you are responsible for saving and retrieving those tokens for future use. L makes it easy to serialize/deserialize tokens, and has hooks for when they change, but leaves this step up to you. =item Access the API L takes care of signing your API requests. What requests you need to actually make are between you and the I. With luck there will be documentation to help you figure it out, and if you are really lucky that will be reasonably accurate. =item Refresh access tokens (maybe) The I that is used to sign requests will only work for a limited time. If you were given a I, that can be used to request another I at any time. Which raises the possibility that you make a request, it fails because the I expired, you refresh it, then need to retry your request. L will perform this refresh/retry logic for you automatically if possible, and provides a hook for you to know to save the updated token data. Some Is are not expected to use this pattern. You are only given an I and are expected to send the user through the handshake again when that expires. The second time through the redirect on the I's side is immediate, so the user experience should be seamless. However L does not try to automate that logic. But C<$oauth2-Eshould_refresh> can let you know when it is time to send the user through, and C<$oauth2-Ecan_refresh_tokens> will let you know whether automatic refreshing is available. Note that even if it is available, retry success is not guaranteed. The I may revoke your access, the I may decide you are a suspicious character, there may have been a service outage, etc. L will throw errors on these error conditions, handling them is up to you. =back =head1 Terminology This section is intended to be used in one of two ways. The first option is that you can start reading someone else's documentation and then refer back to here every time you run across a term that you do not immediately understand. The second option is that you can read this section straight through for a reasonably detailed explanation of the OAuth 2 protocol, with all terms explained. In fact if you choose this option, you will find it explained in more detail than you need to be a successful I. However if you use it in the second way, please be advised that this does not try to be a complete and exact explanation of the specification. In particular the specification requires specific error handling from the service provider that I have glossed over, and allows for extra types of requests that I also glossed over. (Particularly the bit about how any I at any time can add any new method that they want so long as they invent a new I for it.) =over 4 =item consumer The I is the one who needs to be authorized by OAuth 2 to be able to "consume" an API. If you're reading this document, that's likely to be you. =item client The software on the I's side which actually will access the API. From a I's point of view, a I and the I are usually the same thing. But, in fact, a single I may actually write multiple Is. And if one is a web application while another is a command line program, the differences can matter to how OAuth 2 will work. Where I have a choice in this document I say I rather than I because that term is less likely overloaded in most organizations. =item user The I is the entity (person or company) who wishes to let the I access their account. =item Resource Owner What the OAuth 2 specification calls the I, to focus attention on the fact that they own the data which will get accessed. I chose to say I instead of I because that is my best guess as to what the I is most likely to already call them. =item service provider The I is the one which hosts the account, restricts access and offers the API. For example, Google. =item Resource Server In the OAuth 2 specification, this is the service run by the I which hosts provides an API to the user's data. The name has deliberate symmetry with I. =item Authorization Server In the OAuth 2 specification, this is the service run by the I which is responsible for granting access to the I. The I does not need to care about this distinction, but it exposes an important fact about how the I is likely to be structured internally. You typically will have one team that is responsible for granting access, tracking down Is that seem abusive, and so on. And then many teams are free to create useful stuff and write APIs around them, with authorization offloaded to the first team. As a I, you will make API requests to the I signed with proof of auhorization from the I, the I will confirm authorization with the I, and then the I will do whatever it was asked to do. Organizing internal responsibilities in this manner makes it easier for many independent teams in a large company to write public APIs. =item client type The I internally tags each I with a I which tells it something about what environment it is in, and how it interacts with the I. Are are the basic types listed in L: =over 4 =item web application Runs on a web server. Is expected to keep secrets. Likely to be appropriate for a Perl I. =item user-agent-based application JavaScript application running in a browser that wants to make AJAX calls. Can't keep secrets. Does not make sense for A Perl I. =item native application Application installed on a I's machine. Can't keep secrets. Possibly appropriate for a Perl I. =back Of course all of this is up to the I. For example at the time of this writing, Google documents no less than six Is at L, none of which have been given the above names. (They also call them "Scenarios" rather than I.) They rename the top two, split I into two based on whether your application controls a browser, and add two new ones. =item flow Your I is the sequence and methods of interactions that set up authorization. The I depends on your I and I. For example the I might redirect the I to a URL controlled by a web application, while instead for a native application the user is told to cut and paste a code somewhere. Despite I being more common terminology in OAuth 2, I is more self-explanatory, so I've generally gone with that instead. =item client_id The I is a public ID that tells the I about the I that is accessing it. That is, it says both who the I is, and what the I is. Being public, the I can be shared with the I. The details of how this is assigned are between the I and the I. =item client_secret The I is a somewhat private piece of information that the I can pass to the I to prove that the request really comes from the I. How much this is trusted, and how it is used, will depend on the I and I. =item redirect_uri The I needs a way to tell the I how to pass information back to the I in a secure way. That is provided by the I which can be anything from a C URL that the I controls to an instruction that lets the I know that it should tell the I to cut and paste some information. It is up to the I what values of are acceptable for the I, and whether it is a piece of information that is remembered or passed in during the authorization process. =item state The I is an optional piece of information that can be created by the I then added to all requests as an extra piece of protection against forgery. (You are supposed to create a random piece of information for each request, then check that you get it back.) In the OAuth 2 specification it is optional, but recommended. Depending on the combination of your I and I, it may be required. =item scope The I describes what permissions are to be granted. To get multiple permissions, you need to join the permissions requested with spaces. Everything else is up to the I. Inside of the I, what likely happens is that the team which runs a given I tells the team running the I what permissions to their API should be called. And then the I can limit a given I to just the APIs that the I authorized them for. =item Authorization Endpoint The I is the URL provided by the I for the purpose of sending requests to authorize the I to access the I's account. This is part of the I. =item response_type The I tells the I what kind of information it is supposed to pass back. I am not aware of a case where a Perl I could usefully use any value other than C. However there are Is where other things happen. For example the I for the I I uses a I of I. While the field is not very useful for Perl Is, it is required in the specification. So you have to pass it. =item authorization_url This is the URL on the I's website that the I goes to in order to let the I know what authorization is being requested. It is constructed as the I with get parameters added for the I, I, and optionally I. The specification mentions both I and I but does not actually mandate that they be accepted or required. However they may be. And, of course, a given service provider can add more parameters at will, and require (or not) different things by I. An example URL for Google complete with optional extensions is L In L the C method constructs this URL. If your request needs to include the I, I, or any I specific parameter, you need to pass those as parameters. The others are usefully defaulted from the service provider and object. =item (authorization) code If the I is set to C (which should be the case), then on success the I will generate a one use I to give to the user to take back to the I. Depending on the I this could happen with no effort on the part of the I. For example the I can be redirected to the I with the I passed as a get parameter. The web server would then pick these up, finish the handshake, and then redirect the user elsewhere. In all interactions where it is passed it is simply called the I. But it is described in one interaction as an I. =item Token Endpoint The I is the URL provided by the I for the purpose of sending requests from the I to get tokens allowing access to the I's account. =item grant_type The I is the type of grant you expected to get based on the I requested in the I. For a I of C (which is almost certainly what will be used with any I written in Perl), the I has to be C. If they were being consistent, then that would be I like it is everywhere else, but that's what the spec says. We will later encounter the I C. The specification includes potential requests that can be in a I that might prove useful. However you are only likely to encounter that if you are subclassing L. In that case you will hopefully discover the applicability and details of those Is from the I's documentation. =item Access Token Request Once the I has a I the consumer can submit an I by sending a POST request to the I with the I, I, I, I, I and (if in the authorization code) the I. Your I can also require you to authenticate in any further way that they please. You will get back a JSON response. An example request might look like this: POST /o/oauth2/token HTTP/1.1 Host: accounts.google.com Content-Type: application/x-www-form-urlencoded code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7& client_id=8819981768.apps.googleusercontent.com& client_secret={client_secret}& redirect_uri=https://oauth2-login-demo.appspot.com/code& grant_type=authorization_code and the response if you're lucky will look something like: HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", "expires_in":3920, "token_type":"Bearer", "refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" } or if you're unlucky, maybe like this: HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "error":"invalid_grant" } Success is up to the I which can decide not to give you tokens for any reason that they want, including that you asked twice, they think the I might be compromised, they don't like the I, or the phase of the Moon. (I am not aware of any I that makes failure depend on the phase of the Moon, but the others are not made up.) The C method of L will make this request for you, read the JSON and create the token or tokens. If you passed in a C callback in constructing your object, that will be called for you to store the tokens. On future API calls you can retrieve that to skip the handshake if possible. =item token_type The I is a case insensitive description of the type of token that you could be given. In theory there is a finite list of types that you could encounter. In practice Is can add more at any time, either intentionally or unintentionally by failing to correctly implement the one that they claimed to have created. See L for advice on how to add support for a new or incorrectly implemented I. =item expires_in The number of seconds until you will need a new token because the old one should have expired. L provides the C method to let you know when you need that new token. (It actually starts returning true slightly early to avoid problems if clocks are not synchronized, or you begin a series of operations.) =item access_token An I is a temporary token that gives the I access to the I's data in the I's system. In the above response the C is the value of the token, C is the number of seconds it is good for in theory (practice tends to be close but not always exact), and C specifies how it is supposed to be used. Once the authorization handshake is completed, if the I has a supported I. then L will automatically sign any requests for you. =item Bearer token If the I is C (case insensitive), then you should have a I as described by L. For as long as the token is good, any request signed with it is authorized. Signing is as simple as sending an https request with a header of: Authorization: Bearer 1/fFAGRNJru1FTz70BzhT3Zg You can also sign by passing C as a post or get parameter, though the specification recommends against using a get parameter. If you are using L, then it is signed with the header. =item refresh_token The above example also included a I. If you were given one, you can use it later to ask for a refreshed I. Whether you get one is up to your I, who is likely to decide that based on your I. =item Refresh Access Token If you have a I, you can at any time send a I request. This is a POST to the I with the I, I and I arguments. You also have to send a I of C. Thus in the above case we'd send POST /o/oauth2/token HTTP/1.1 Host: accounts.google.com Content-Type: application/x-www-form-urlencoded refresh_token=1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI& client_id=8819981768.apps.googleusercontent.com& client_secret={client_secret}& grant_type=refresh_token and if lucky could get a response like HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"ya29.AHES6ZSiArSow0zeKokajrri5gMBpGc6Sq", "expires_in":3600, "token_type":"Bearer", } and if unlucky could get an error as before. In L this request is made for you transparently behind the scenes if possible. If you're curious when, look in the source for the C method. There are also optional callbacks that you can pass to let you save the tokens, or hijack the refresh method for your own purposes. (Such as making sure that only one process tries to refresh tokens even though many are accessing it.) But note that not all Is offer a I. If you're on one of those Is then you need to send the I back to the I for authorization renewal. From the I's point of view this is likely to be painless because it will be done with transparent redirects. But the I needs to be aware of it. =back =head1 AUTHOR Ben Tilly, C<< >> =head1 ACKNOWLEDGEMENTS Thanks to L for their generous support in letting me develop and release this module. My thanks also to Keith Cascio C<< >> for very helpful feedback on early drafts. LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider/0000755000175000017500000000000013435051255021752 5ustar dommdommLWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider/Yahoo.pm0000644000175000017500000000543313435050632023372 0ustar dommdommpackage LWP::Authen::OAuth2::ServiceProvider::Yahoo; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider); our $VERSION = "0.01"; sub authorization_endpoint { return "https://api.login.yahoo.com/oauth2/request_auth"; } sub token_endpoint { return "https://api.login.yahoo.com/oauth2/get_token"; } sub authorization_required_params { my $self = shift; return ("client_id", "redirect_uri", "response_type", $self->SUPER::authorization_required_params()); } sub authorization_optional_params { my $self = shift; return ("state", "language", $self->SUPER::authorization_optional_params()); } sub refresh_required_params { my $self = shift; return ("client_id", "client_secret", "redirect_uri", "grant_type", $self->SUPER::refresh_required_params()); } sub request_required_params { my $self = shift; return ("client_id", "client_secret", "redirect_uri", "grant_type", $self->SUPER::request_required_params()); } sub authorization_default_params { my $self = shift; return ("response_type" => "code", $self->SUPER::authorization_default_params()); } sub request_default_params { my $self = shift; return ("grant_type" => "authorization_code", $self->SUPER::request_default_params()); } sub refresh_default_params { my $self = shift; return ("grant_type" => "refresh_token", $self->SUPER::refresh_default_params()); } 1; =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Yahoo - Access Yahoo API OAuth2 APIs =head1 VERSION Version 0.01 =head1 SYNOPSIS See L for Yahoo's own documentation. =head1 REGISTERING Before you can use OAuth 2 with Yahoo you need to register yourself as an app. For that, go to L. =head1 AUTHOR Michael Stevens, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc LWP::Authen::OAuth2::ServiceProvider You can also look for information at: =over 4 =item Github (submit patches here) L = item RT: CPAN's request tracker (report bugs here) L =item AnnoCPAN: Annotated CPAN documentation L =item CPAN Ratings L =back http://rt.cpan.org/NoAuth/ReportBug.html?Queue=LWP-Authen-OAuth2 =head1 LICENSE AND COPYRIGHT This software is copyright (c) 2018 by Michael Stevens. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider/Line/0000755000175000017500000000000013435051255022641 5ustar dommdommLWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider/Line/AccessToken.pm0000644000175000017500000000136313144645655025416 0ustar dommdommpackage LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken; use strict; use warnings; use parent 'LWP::Authen::OAuth2::AccessToken::Bearer'; our $REFRESH_PERIOD = 864000; # 10 days. https://devdocs.line.me/en/#refreshing-access-tokens # Line tokens can be refreshed until the refresh period is over. sub should_refresh { my ($self, @args) = @_; return 0 unless $self->SUPER::should_refresh(@args); my $refresh_expires_time = $self->expires_time + $REFRESH_PERIOD; my $refresh_token_valid = time < $refresh_expires_time; return $refresh_token_valid; } # These are exposed for use with /oauth/verify and /oauth/revoke API requests sub access_token { shift->{access_token} } sub refresh_token { shift->{refresh_token} } 1; LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider/Strava.pm0000644000175000017500000000534213435050713023552 0ustar dommdommpackage LWP::Authen::OAuth2::ServiceProvider::Strava; use strict; use warnings; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider); sub authorization_endpoint { return "https://www.strava.com/oauth/authorize"; } sub token_endpoint { return "https://www.strava.com/oauth/token"; } sub authorization_required_params { my $self = shift; return ("client_id", "redirect_uri", "response_type", $self->SUPER::authorization_required_params()); } sub authorization_optional_params { my $self = shift; return ("approval_prompt", "scope", "state", $self->SUPER::authorization_optional_params()); } sub request_default_params { my $self = shift; return ( "scope" => "public", "response_type" => "code", $self->SUPER::request_default_params() ); } =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Strava - Access Strava API v3 OAuth2 APIs =head1 VERSION Version 0.02 =cut package LWP::Authen::OAuth2::ServiceProvider::Strava; our $VERSION = '0.02'; =head1 SYNOPSIS See L for Strava's own documentation. Strava's documentation is very detailed, so that is the best place to find detailed and up to date info about. =head1 REGISTERING Before you can use OAuth 2 with Strava you need to register yourself as a client. For that, go to L and register your application. You'll need to set C with them, which will need to be an C URL under your control. (Though you can set 127.0.0.1 if you are using this in a script). All the standard LWP::Useragent methods are available, but it will also take a Request Object if you need something more. (LWP::Authen:OAuth2 contains all the relevant doco). =head1 AUTHOR Leon Wright, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc LWP::Authen::OAuth2::ServiceProvider You can also look for information at: =over 4 =item Github (submit patches here) L =item RT: CPAN's request tracker (report bugs here) L =item AnnoCPAN: Annotated CPAN documentation L =item CPAN Ratings L =back =head1 LICENSE AND COPYRIGHT This software is copyright (c) 2014 by Leon Wright. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut 1; LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider/Dwolla.pm0000644000175000017500000001032613435050654023536 0ustar dommdommpackage LWP::Authen::OAuth2::ServiceProvider::Dwolla; use strict; use warnings; use base qw/LWP::Authen::OAuth2::ServiceProvider/; use JSON qw/decode_json/; sub authorization_endpoint { my $self = shift; my $host = $self->{use_test_urls} ? 'uat.dwolla.com' : 'www.dwolla.com'; return 'https://'.$host.'/oauth/v2/authenticate'; } sub token_endpoint { my $self = shift; my $host = $self->{use_test_urls} ? 'uat.dwolla.com' : 'www.dwolla.com'; return 'https://'.$host.'/oauth/v2/token'; } sub api_url_base { my $self = shift; my $host = $self->{use_test_urls} ? 'api-uat.dwolla.com' : 'api.dwolla.com'; return 'https://'.$host; } sub authorization_required_params { my $self = shift; return ('scope', $self->SUPER::authorization_required_params()); } sub authorization_optional_params { my $self = shift; return ($self->SUPER::authorization_optional_params(), qw/dwolla_landing verified_account/); } sub default_api_headers { return { 'Content-Type' => 'application/vnd.dwolla.v1.hal+json', 'Accept' => 'application/vnd.dwolla.v1.hal+json' }; } =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Dwolla - Access Dwolla API v2 =head1 SYNOPSIS my $oauth_dwolla = LWP::Authen::OAuth2->new( # client_id/client_secret come from your Dwolla account, under API Keys in # Registered Applications client_id => DWOLLA_APP_KEY, client_secret => DWOLLA_APP_SECRET, service_provider => 'Dwolla', # $use_test = 1 to use uat.dwolla.com in your dev sandbox, for test transactions use_test_urls => $use_test ? 1 : 0, redirect_uri => 'http://my.host/dwolla_redirect_handler', # scope for reading funding sources and sending money; see Dwolla docs for other scopes scope => 'Send|Funding', ); # read user's list of funding sources my $account = $oauth_dwolla->access_token()->{'_links'}->{'account'}->{'href'}; my $funding_sources = eval { $oauth_dwolla->make_api_call($account.'/funding-sources') }; # get all verified bank accounts my @verified_sources = grep { $_->{'status'} eq 'verified' && $_->{'type'} ne 'balance' } @{ $funding_sources->{'_embedded'}->{'funding-sources'} }; # get user's Dwolla balance, if it has a positive balance my ($balance_source) = grep { $_->{'type'} eq 'balance' } @{ $funding_sources->{'_embedded'}->{'funding-sources'} }; my $dwolla_balance = eval { $oauth_dwolla->make_api_call($balance_source->{'_links'}->{'with-available-balance'}->{'href'}) }; print 'Dwolla balance = '.$dwolla_balance->{'balance'}->{'value'}."\n"; # send 100USD from first verified bank account to $recipient_account_id my $success = eval { $oauth2->make_api_call('/transfers', { _links => { destination => { href => $oauth2->api_url_base().'/accounts/'.$recipient_account_id }, source => { href => $verified_sources[0]->{'_links'}->{'account'}->{'href'} }, }, amount => { currency => 'USD', value => '100.00' }, }) }; # (to send via Dwolla balance, use $balance_source->{'_links'}->{'account'}->{'href'} # as source href instead) =head1 REGISTERING First get a Dwolla account, by signing up at dwolla.com. Then create a new application via API Keys -> Create an application. Set up the OAuth Redirect to match the C in your LWP::Authen::OAuth2 object, and use the application's Key and Secret values in client_id and client_secret. Full Dwolla API v2 docs can be found here: L, L =head1 AUTHOR Adi Fairbank, C<< >> =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc LWP::Authen::OAuth2::ServiceProvider You can also look for information at: =over 4 =item Github (submit patches here) CPAN maintainer's branch: L Branch where I work on Dwolla support: L =item RT: CPAN's request tracker (report bugs here) L =item AnnoCPAN: Annotated CPAN documentation L =item CPAN Ratings L =back =cut 1; LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm0000644000175000017500000003041113435050722023521 0ustar dommdommpackage LWP::Authen::OAuth2::ServiceProvider::Google; use strict; use warnings; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider); sub authorization_endpoint { return "https://accounts.google.com/o/oauth2/auth"; } sub token_endpoint { return "https://accounts.google.com/o/oauth2/token"; } sub authorization_required_params { my $self = shift; return ("scope", $self->SUPER::authorization_required_params()); } sub authorization_optional_params { my $self = shift; return ("login_hint", $self->SUPER::authorization_optional_params()); } my %client_type_class = ( default => "WebServer", device => "Device", installed => "Installed", login => "Login", "web server" => "WebServer", service => "Service", ); sub client_type_class { my ($class, $client_type) = @_; if (exists $client_type_class{$client_type}) { return "LWP::Authen::OAuth2::ServiceProvider::Google::$client_type_class{$client_type}"; } else { my $allowed = join ", ", sort keys %client_type_class; Carp::croak("Flow '$client_type' not in: $allowed"); } } package LWP::Authen::OAuth2::ServiceProvider::Google::Device; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider::Google); sub init { Carp::confess(__PACKAGE__ . " is not implemented."); } package LWP::Authen::OAuth2::ServiceProvider::Google::Installed; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider::Google); sub init { Carp::confess(__PACKAGE__ . " is not implemented."); } package LWP::Authen::OAuth2::ServiceProvider::Google::Login; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider::Google); sub init { Carp::confess(__PACKAGE__ . " is not implemented."); } sub authorization_required_params { my $self = shift; return ("state", $self->SUPER::authorization_required_params()); } sub authorization_default_params { my $self = shift; return ( "scope" => "openid email", $self->SUPER::authorization_default_params() ); } sub request_required_params { my $self = shift; return ("state", $self->SUPER::request_required_params()); } sub request_default_params { my $self = shift; return ( "scope" => "openid email", $self->SUPER::request_default_params() ); } package LWP::Authen::OAuth2::ServiceProvider::Google::Service; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider::Google); sub init { Carp::confess(__PACKAGE__ . " is not implemented."); } package LWP::Authen::OAuth2::ServiceProvider::Google::WebServer; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider::Google); # Not guaranteed a refresh token, so require nothing. sub required_init { return (); } sub optional_init { return qw(redirect_uri scope client_id client_secret); } sub authorization_optional_params { my $self = shift; return ( "access_type", "approval_prompt", $self->SUPER::authorization_optional_params() ); } sub request_required_params { my $self = shift; return ("redirect_uri", $self->SUPER::request_required_params()); } =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Google - Access Google OAuth2 APIs =head1 VERSION Version 0.02 =cut package LWP::Authen::OAuth2::ServiceProvider::Google; our $VERSION = '0.02'; =head1 SYNOPSIS See L for basic usage. The one general note is that C is C is optional in the specification, but required for Google. Beyond that Google supports many client types, and their behavior varies widely. See L for Google's own documentation. The documentation here is a Cliff Notes version of that, so look there for any necessary clarification. =head1 REGISTERING Before you can use OAuth 2 with Google you need to register yourself as a client. For that, go to L. Follow their directions to create a project, choose your C (which is called your C in this document - look ahead for advice on available types), and then you'll be given a C and C. If you're in the Login, WebServer or Client client types you'll also need to register a C with them, which will need to be an C URL under your control. At that point you have all of the facts that you need to use this module. Be sure to keep your C secret - if someone else gets it and starts abusing it, Google reserves the right to block you. This module only handles the authorization step, after which it is up to you to figure out how to use whatever API you want to access. =head1 CLIENT TYPES Google offers many client types. Here is the status of each one in this module: =over 4 =item Login This is for applications that want to let Google manage their logins. See L for Google's documentation. This is not yet supported, and would require the use of JSON Web Tokens to support. =item Web Server Application This is intended for applications running on web servers, with the user sitting behind a browser interacting with you. See L for Google's documentation. It can be specified in the constructor with: client_type => "web server", however that is not necessary since it is also the assumed default if no client_type is specified. After registering yourself as a client with Google, you will need to specify the C as an https URL under your control. If you just need this for one or two accounts there is no need to actually build anything at that URL - just go through the authorization as those accounts and grab your C from the URL. If you will support many, making that URL useful is your responsibility. With this client type you are not guaranteed a refresh token, so the constructor does not require C and C. (Passing them there is still likely to be convenient for you.) However there are several optional arguments available to C<$oauth2-Eauthorization_url(...)> that are worth taking note of: =over 4 =item C Pass C "offline",> to C<$oauth2->request_tokens(...)> to request offline access. This means that you get a C which can be used to refresh the access token without help from the user. The intent of this option is to support things like software that delays posting a blog entry until a particular time. In light testing this did not work for me until I passed the next argument, but then it worked perfectly. =item C Pass C "force",> to C<$oauth2->request_tokens(...)> to force the user to see the approval screen. The default behavior without this is that the user sees the approval screen the first time through, and on subsequent times just gets an immediate redirect. =item C If you think you know who the user is, you can pass an email in this parameter to let Google know which account you are trying to access. Google thinks this may be helpful if someone is logged into multiple accounts at the same time. =back =item Client-side Application This client type is only for JavaScript applications. See L for Google's documentation. This is not supported since Perl is not JavaScript. =item Installed Application This client type is for applications that run on the user's machine, which can control a browser. See L for Google's documentation. It can be specified in the constructor with: client_type => "web server", On the first time it is the client's responsibility to open a browser and send the user to C<$oauth2->authorization_url(...)>. If you pass in C "http://localhost:$port",> then your application is expected to be listening on that port. If you instead pass in C "urn:ietf:wg:oauth:2.0:oob",> then the code you need will be in the C inside of the page the browser is redirected to, and you'll need to grab it from there. The returned tokens always give you a refresh token, so you only have to go through this once per user. The only special authorization argument is C<login_hint>, which means the same thing that it does for webserver applications. =item Devices This client_type is for applications that run on the user's machine, which do not control a browser. See L<https://developers.google.com/accounts/docs/OAuth2ForDevices> for Google's documentation. This client_type is not supported because I have not yet thought through how to handle the required polling step of setting up permissions. =item Service Account This client_type is for applications that login to the developer's account using the developer's credentials. See L<https://developers.google.com/accounts/docs/OAuth2ServiceAccount> for Google's documentation. This is not yet supported, and would require the use of JSON Web Tokens to support. =back =head1 AUTHOR Ben Tilly, C<< <btilly at gmail.com> >> =head1 BUGS The main bug is that out of 6 client types, 5 of which could reasonably be supported, only two are so far. Please report any bugs or feature requests to C<bug-lwp-authen-oauth2 at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=LWP-Authen-OAuth2>. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc LWP::Authen::OAuth2::ServiceProvider You can also look for information at: =over 4 =item Github (submit patches here) L<https://github.com/btilly/perl-oauth2> =item RT: CPAN's request tracker (report bugs here) L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=LWP-Authen-OAuth2> =item AnnoCPAN: Annotated CPAN documentation L<http://annocpan.org/dist/LWP-Authen-OAuth2> =item CPAN Ratings L<http://cpanratings.perl.org/d/LWP-Authen-OAuth2> =back =head1 ACKNOWLEDGEMENTS Thanks to L<Rent.com|http://www.rent.com> for their generous support in letting me develop and release this module. My thanks also to Nick Wellnhofer <wellnhofer@aevum.de> for Net::Google::Analytics::OAuth2 which was very enlightening while I was trying to figure out the details of how to connect to Google with OAuth2. =head1 LICENSE AND COPYRIGHT Copyright 2013 Rent.com. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: L<http://www.perlfoundation.org/artistic_license_2_0> Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =cut 1; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2/ServiceProvider/Line.pm��������������������������������0000644�0001750�0001750�00000012757�13144645655�023225� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::ServiceProvider::Line; use strict; use warnings; use parent 'LWP::Authen::OAuth2::ServiceProvider'; sub required_init { return qw(client_id client_secret redirect_uri); } sub authorization_required_params { return qw(client_id redirect_uri response_type state); } sub authorization_default_params { return response_type => 'code'; } sub request_required_params { return qw(client_id redirect_uri grant_type client_secret code); } sub request_default_params { return grant_type => 'authorization_code'; } sub init { my ($self, $opts) = @_; $self->copy_option($opts, line_server => 'line.me'); $self->SUPER::init($opts); } sub authorization_endpoint { my $self = shift; my $server = $self->{line_server} or die 'line_server not configured. Forgot to call init()?'; return "https://access.$server/dialog/oauth/weblogin"; } sub token_endpoint { my $self = shift; my $server = $self->{line_server} or die 'line_server not configured. Forgot to call init()?'; return "https://api.$server/v2/oauth/accessToken"; } sub api_url_base { my $self = shift; my $server = $self->{line_server} or die 'line_server not configured. Forgot to call init()?'; return "https://api.$server/v2/"; } sub access_token_class { my ($self, $type) = @_; if ($type eq 'bearer') { return 'LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken'; } return $self->SUPER::access_token_class($type); } =pod =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Line - Access Line OAuth2 API v2 =head1 SYNOPSIS my $oauth2 = LWP::Authen::OAuth2->new( service_provider => 'Line', redirect_uri => 'http://example.com/', client_id => 'line_client_id' # Retrieved from https://developers.line.me/ client_secret => 'line_client_secret' # Retrieved from https://developers.line.me/ ); my $url = $oauth2->authorization_url(state => $state); # ... Send user to authorization URL and get authorization $code ... $oauth2->request_tokens(code => $code); # Simple requests # User Info my $profile = $oauth2->make_api_call('profile'); my $userId = $profile->{userId}; my $displayName = $profile->{displayName}; my $pictureUrl = $profile->{pictureUrl}; my $statusMessage = $profile->{statusMessage}; # Refresh $oauth2->refresh_access_token(); # More complex requests... # Verify # Manually send the request using the internal user agent - see explanation in "Line API Documentation" below. my $access_token_str = $oauth2->access_token->access_token; my $res = $oauth2->user_agent->post($oauth2->api_url_base.'oauth/verify' => { access_token => $access_token_str }); my $content = eval { decode_json($res->content) }; my $scope = $content->{scope}; my $client_id = $content->{client_id}; my $expires_in = $content->{expires_in}; # Revoke # Look up the internal refresh token - see explanation in "Line API Documentation" below. my $refresh_token_str = $oauth2->access_token->refresh_token; $oauth2->post($oauth2->api_url_base.'oauth/revoke' => { refresh_token => $refresh_token_str }); =head1 REGISTERING Individual users must have an account created with the L<Line application|https://line.me/download>. In order to log in with OAuth2, users must register their email address. Device-specific instructions can be found on the L<Line support site|https://help.line.me/>. API clients can follow the L<Line Login|https://developers.line.me/line-login/overview> documentation to set up the OAuth2 credentials. =head1 Line API Documentation See the Line L<Social REST API Reference|https://devdocs.line.me/en/#how-to-use-the-apis>. As of writing, there are two simple API calls: C<profile> and C<refresh>. There are also C<verify> and C<revoke> endpoints, which require a bit more work. =over =item C<verify> C<verify> is designed for verifying pre-existing access tokens. Instead of using the C<Authorization> header, this endpoint expects the access token to be form-urlencoded in the request body. Because of this, it's necessary to get access to the internal access token string to send in the request body. The L<LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken> token class used by this service provider provides the C<access_token> accessor for this purpose. The server seems to ignore the C<Authorization> header for this request, so including it is probably not a problem. If you want to avoid sending the access token in the header, it's necessary to manually construct the request and decode the response. See L</SYNOPSYS> for usage examples. =item C<revoke> C<revoke> requires the refresh token to be form-urlencoded in the request body. Because of this, it's necessary to get access to the internal refresh token string to send in the request body. The L<LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken> token class used by this service provider provides the C<refresh_token> accessor for this purpose. See L</SYNOPSYS> for usage examples. =back =head1 Refresh timing Line access tokens can be refreshed at any time up until 10 days after the access token expires. The L<LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken> token class used by this service provider extends the C<should_refresh> method for this purpose, causing C<< $oauth2->should_refresh() >> to return false if this 10-day period has lapsed. =head1 AUTHOR Adam Millerchip, C<< <adam at millerchip.net> >> =cut 1; �����������������LWP-Authen-OAuth2-0.16/lib/LWP/Authen/OAuth2.pm�����������������������������������������������������0000644�0001750�0001750�00000071334�13435051123�017177� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2; use 5.006; use strict; use warnings; use Carp qw(croak confess); # LWP::UserAgent lazyloads these, but we always need it. use HTTP::Request::Common; use JSON qw(encode_json decode_json); use LWP::UserAgent; use Module::Load qw(load); our @CARP_NOT = map "LWP::Authen::OAUth2::$_", qw(Args ServiceProvider); use LWP::Authen::OAuth2::Args qw( extract_option copy_option assert_options_empty ); use LWP::Authen::OAuth2::ServiceProvider; sub new { my ($class, %opts) = @_; # Constructing the service provider can consume my options. my $service_provider = LWP::Authen::OAuth2::ServiceProvider->new(\%opts); my $self = bless { service_provider => $service_provider }, $service_provider->oauth2_class(); $self->init(%opts, service_provider => $service_provider); return $self; } sub init { my ($self , %opts) = @_; # Collect arguments for the service providers. my $service_provider = $self->{service_provider}; my $for_service_provider = LWP::Authen::OAuth2::Args->new(); my %is_seen; for my $opt (@{ $service_provider->{required_init} }) { $is_seen{$opt}++; $for_service_provider->copy_option(\%opts, $opt); } for my $opt (@{ $service_provider->{optional_init} }) { if (not $is_seen{$opt}) { $is_seen{$opt}++; $for_service_provider->copy_option(\%opts, $opt, undef); } } $self->{for_service_provider} = $for_service_provider; $self->copy_option(\%opts, "early_refresh_time", 300); $self->copy_option(\%opts, "error_handler", undef); $self->copy_option(\%opts, "is_strict", 1); $self->copy_option(\%opts, "prerefresh", undef); $self->copy_option(\%opts, "save_tokens", undef); $self->copy_option(\%opts, "save_tokens_args", undef); $self->copy_option(\%opts, "token_string", undef); $self->copy_option(\%opts, "user_agent", undef); if ($self->{token_string}) { $self->load_token_string(); } } # Standard shortcut request methods. sub delete { my ($self, @parameters) = @_; my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1); my $request = HTTP::Request::Common::DELETE(@parameters); return $self->request($request, @rest); } sub get { my ($self, @parameters) = @_; my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1); my $request = HTTP::Request::Common::GET(@parameters); return $self->request($request, @rest); } sub head { my ($self, @parameters) = @_; my @rest = $self->user_agent->_process_colonic_headers(\@parameters,1); my $request = HTTP::Request::Common::HEAD(@parameters); return $self->request($request, @rest); } sub post { my ($self, @parameters) = @_; my @rest = $self->user_agent->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1)); my $request = HTTP::Request::Common::POST(@parameters); return $self->request($request, @rest); } sub put { my ($self, @parameters) = @_; my @rest = $self->user_agent->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1)); my $request = HTTP::Request::Common::PUT(@parameters); return $self->request($request, @rest); } sub request { my ($self, $request, @rest) = @_; return $self->access_token->request($self, $request, @rest); } # Now all of the methods that I need. sub token_string { my $self = shift; if ($self->{access_token}) { my $ref = $self->{access_token}->to_ref; $ref->{_class} = ref($self->{access_token}); return encode_json($ref); } else { return undef; } } # This does the actual saving. sub _set_tokens { my ($self, %opts) = @_; my $tokens = $self->extract_option(\%opts, "tokens"); my $skip_save = $self->extract_option(\%opts, "skip_save", 0); assert_options_empty(\%opts); if (ref($tokens)) { # Assume we have tokens. $self->{access_token} = $tokens; if ($self->{save_tokens} and not $skip_save) { my $as_string = $self->token_string; $self->{save_tokens}->($as_string, @{$self->{save_tokens_args}}); } return; } else { # Assume we have an error message. return $self->error($tokens); } } sub authorization_url { my ($self, %opts) = @_; # If we get here, the service provider does it. my $url = $self->{service_provider}->authorization_url($self, %opts); if ($url =~ / /) { # Assume an error. return $self->error($url); } else { return $url; } } sub api_url_base { my $self = shift; return $self->{service_provider}->api_url_base || ''; } sub make_api_call { my ($self, $uri, $params, $headers) = @_; my $url = $uri =~ m|^http| ? $uri : $self->api_url_base.$uri; if ($self->{service_provider}->can('default_api_headers')) { my $service_provider_headers = $self->{service_provider}->default_api_headers; $headers = ref $headers eq 'HASH' ? { %$headers, %$service_provider_headers } : $service_provider_headers || {}; } my $response = $params ? $self->post($url, Content => encode_json($params), %$headers) : $self->get($url, %$headers); if (! $response->is_success()) { #$self->error('failed call to: '.$url.'; status_line='.$response->status_line.'; full error='.$response->error_as_HTML.'; content='.$response->content); $self->{'_api_call_error'} = $response->error_as_HTML || $response->status_line; return undef; } my $content = $response->content; return 1 if ! $content; # success return eval { decode_json($content) }; # return decoded JSON if response has a body } sub api_call_error { return shift->{'_api_call_error'}; } sub request_tokens { my ($self, %opts) = @_; # If we get here, the service provider does it. my $tokens = $self->{service_provider}->request_tokens($self, %opts); # _set_tokens will set an error if needed. return $self->_set_tokens(tokens => $tokens); } sub can_refresh_tokens { my $self = shift; if (not $self->{access_token}) { return 0; } else { my %opts = ($self->{access_token}->for_refresh(), @_); return $self->{service_provider}->can_refresh_tokens($self, %opts); } } sub refresh_access_token { my $self = shift; if (not $self->{access_token}) { croak("Cannot try to refresh access token without tokens"); } my %opts = ($self->{access_token}->for_refresh(), @_); # Give a chance for the hook to do it. if ($self->{prerefresh}) { my $tokens = $self->{prerefresh}->($self, %opts); if ($tokens) { if (not (ref($tokens))) { # Did I get JSON? my $data = eval {decode_json($tokens)}; if ($data and not $@) { # Assume I got it. $tokens = $data; } } return $self->_set_tokens(tokens => $tokens, skip_save => 1); } } my $tokens = $self->{service_provider}->refreshed_tokens($self, %opts); # _set_tokens will set an error if needed. return $self->_set_tokens(tokens => $tokens); } sub access_token { my $self = shift; return $self->{access_token}; } sub should_refresh { my $self = shift; return $self->access_token->should_refresh($self->{early_refresh_time}); } sub expires_time { my $self = shift; return 0 if ! $self->{access_token}; return $self->access_token->expires_time; } sub set_early_refresh_time { my ($self, $early_refresh_time) = @_; $self->{early_refresh_time} = $early_refresh_time; } sub set_is_strict { my ($self, $strict) = @_; $self->{is_strict} = $strict; } sub is_strict { my $self = shift; return $self->{is_strict}; } sub set_error_handler { my ($self, $handler) = @_; $self->{error_handler} = @_; } sub error { my $self = shift; if ($self->{error_handler}) { return $self->{error_handler}->(@_); } else { croak(@_); } } sub for_service_provider { my $self = shift; return $self->{for_service_provider} ||= {}; } sub set_prerefresh { my ($self, $prerefresh) = @_; $self->{prerefresh} = $prerefresh; } sub set_save_tokens { my ($self, $save_tokens) = @_; $self->{save_tokens} = $save_tokens; } sub set_user_agent { my ($self, $agent) = @_; $self->{user_agent} = $agent; } sub load_token_string { my ($self, $token_string) = @_; $token_string ||= $self->{token_string}; # Probably not the object that I need in access_token. my $tokens = eval{ decode_json($token_string) }; if ($@) { croak("While decoding token_string: $@"); } my $class = $tokens->{_class} or croak("No _class in token_string '$token_string'"); eval {load($class)}; if ($@) { croak("Can't load access token class '$class': $@"); } # I will assume this works. $self->{access_token} = $class->from_ref($tokens); } sub user_agent { my $self = shift; return $self->{user_agent} ||= LWP::UserAgent->new(); } =head1 NAME LWP::Authen::OAuth2 - Make requests to OAuth2 APIs. =head1 VERSION Version 0.15 =cut our $VERSION = '0.16'; =head1 SYNOPSIS OAuth 2 is a protocol that lets a I<user> tell a I<service provider> that a I<consumer> has permission to use the I<service provider>'s APIs to do things that require access to the I<user>'s account. This module tries to make life easier for someone who wants to write a I<consumer> in Perl. Specifically it provides convenience methods for all of the requests that are made to the I<service provider> as part of the permission handshake, and after that will proxy off of L<LWP::UserAgent> to let you send properly authenticated requests to the API that you are trying to use. When possible, this will include transparent refresh/retry logic for access tokens expiration. For a full explanation of OAuth 2, common terminology, the requests that get made, and the necessary tasks that this module does not address, please see L<LWP::Authen::OAuth2::Overview> This module will not help with OAuth 1. See the similarly named but unrelated L<LWP::Authen::OAuth> for a module that can help with that. Currently L<LWP::Authen::OAuth2> provides ready-to-use classes to use OAuth2 with =over =item * Dwolla L<LWP::Authen::OAuth2::ServiceProvider::Dwolla> implemented by L<Adi Fairbank|https://github.com/adifairbank> =item * Google L<LWP::Authen::OAuth2::ServiceProvider::Google> =item * Line L<LWP::Authen::OAuth2::ServiceProvider::Line> implemented by L<Adam Millerchip|https://github.com/amillerchip> =item * Strava L<LWP::Authen::OAuth2::ServiceProvider::Strava> implemented by L<Leon Wright|https://github.com/techman83> =item * Yahoo L<LWP::Authen::OAuth2::ServiceProvider::Yahoo> implemented by L<Michael Stevens|https://github.com/michael-stevens> =back You can also access any other OAuth2 service by setting up a plain C<LWP::Authen::OAuth2> object. If you do, and the service provider might be of interest to other people, please submit a patch so we can include it in this distribution, or release it as a standalone package. Here are examples of simple usage. use LWP::Authen::OAuth2; # Constructor my $oauth2 = LWP::Authen::OAuth2->new( client_id => "Public from service provider", client_secret => "s3cr3t fr0m svc prov", service_provider => "Google", redirect_uri => "https://your.url.com/", # Optional hook, but recommended. save_tokens => \&save_tokens, save_tokens_args => [ $dbh ], # This is for when you have tokens from last time. token_string => $token_string. ); # URL for user to go to to start the process. my $url = $oauth2->authorization_url(); # The authorization_url sends the user to the service provider to # say that you want to be authorized. After the user confirms that # request, the service provider sends the user back to you with a # code. This might be a CGI parameter, something that the user is # supposed to paste to you - that's between you and the service # provider. # Assuming that you have your code, get your tokens from the service # provider. $oauth2->request_tokens(code => $code); # Get your token as a string you can easily store, pass around, etc. # If you have a save_tokens callback, that gets passed this string # whenever the tokens change. # # This string bears a suspicious resemblance to serialized JSON. my $token_string = $oauth2->token_string, # Access the API. Consult the service_provider's documentation for when # to use which type of request. Note that argument processing is the # same as in LWP. Thus the parameters array and headers hash are both # optional. $oauth2->get($url, %header); $oauth2->post($url, \@parameters, %header); $oauth2->put($url, %header); $oauth2->delete($url, %header); $oauth2->head($url, %header); # And if you need more flexibility, you can use LWP::UserAgent's request # method $oauth2->request($http_request, $content_file); # In some flows you can refresh tokens, in others you have to go through # the handshake yourself. This method lets you know whether a refresh # looks possible. $oauth2->can_refresh_tokens(); # This method lets you know when it is time to reauthorize so that you # can find out in a nicer way than failing an API call. $oauth2->should_refresh(); =head1 CONSTRUCTOR When you call C<LWP::Authen::OAuth2-E<gt>new(...)>, arguments are passed as a key/value list. They are processed in the following phases: =over 4 =item Construct service provider =item Service provider collects arguments it wants =item L<LWP::Authen::OAuth2> overrides defaults from arguments =item Sanity check =back Here are those phases in more detail. =over 4 =item Construct service provider There are two ways to construct a service provider. =over 4 =item Prebuilt class To load a prebuilt class you just need one or two arguments. =over 4 =item C<< service_provider => $Foo, >> In the above construct, C<$Foo> identifies the base class for your service provider. The actual class will be the first of the following two classes that can be loaded. Failure to find either is an error. LWP::Authen::OAuth2::ServiceProvider $Foo $Foo A list of prebuilt service provider classes is in L<LWP::Authen::OAuth2::ServiceProvider> as well as instructions for making a new one. =item C<< client_type => $name_of_client_type >> Some service providers will keep track of your client type ("webserver" application, "installed" application, etc), and will treat them differently. A base service provider class can choose to accept a C<client_type> parameter to let it know what to expect. Whether this is done, and the allowable values, are up to the service provider class. =back =item Built on the fly The behavior of simple service providers can be described on the fly without needing a prebuilt class. To do that, the following arguments can be filled with arguments from your service provider: =over 4 =item C<authorization_endpoint =E<gt> $auth_url,> This is the URL which the user is directed to in the authorization request. =item C<token_endpoint =E<gt> $token_url,> This is the URL which the consumer goes to for tokens. =item Various optional fields L<LWP::Authen::OAuth2::ServiceProvider> documents many methods that are available to customize the actual requests made, and defaults available. Simple service providers can likely get by without this, but here is a list of those methods that can be specified instead in the constructor: # Arrayrefs required_init optional_init authorization_required_params authorization_optional_params request_required_params request_optional_params refresh_required_params refresh_optional_params # Hashrefs authorization_default_params request_default_params refresh_default_params =back =item Service provider collects arguments it wants In general, arguments passed into the constructor do not have to be passed into individual method calls. Furthermore in order to be able to do the automatic token refresh for you, the constructor must include the arguments that will be required. By default you are required to pass your C<client_id> and C<client_secret>. And optionally can pass a C<redirect_uri> and C<scope>. (The omission of C<state> is a deliberate hint that if you use that field, you should be generating random values on the fly. And not trying to go to some reasonable default.) However what is required is up to the service provider. =item L<LWP::Authen::OAuth2> overrides defaults from arguments The following defaults are available to be overridden in the constructor, or can be overridden later. In the unlikely event that there is a conflict with the service provider's arguments, these will have to be overridden later. =over 4 =item C<error_handler =E<gt> \&error_handler,> Specifies the function that will be called when errors happen. The default is C<Carp::croak>. =item C<is_strict =E<gt> $bool,> Is strict mode on? If it is, then excess parameters to requests that are part of the authorization process will trigger errors. If it is not, then excess arguments are passed to the service provider as is, who according to the specification is supposed to ignore them. Strict mode is the default. =item C<early_refresh_time =E<gt> $seconds,> How many seconds before the end of estimated access token expiration you will have C<should_refresh> start returning true. =item C<prerefresh =E<gt> \&prerefresh,> A handler to be called before attempting to refresh tokens. It is passed the C<$oauth2> object. If it returns a token string, that will be used to generate tokens instead of going to the service provider. The purpose of this hook is so that, even if you have multiple processes accessing an API simultaneously, only one of them will try to refresh tokens with the service provider. (Service providers may dislike having multiple refresh requests arrive at once from the same consumer for the same user.) By default this is not provided. =item C<save_tokens =E<gt> \&save_tokens,> Whenever tokens are returned from the service provider, this callback will receive a token string that can be stored and then retrieved in another process that needs to construct a C<$oauth2> object. By default this is not provided. However if you intend to access the API multiple times from multiple processes, it is recommended. =item C<save_tokens_args =E<gt> [ args ],> Additional arguments passed to the save_tokens callback function after the token string. This can be used to pass things like database handles or other data to the callback along with the token string. Provide a reference to an array of arguments in the constructure. When the callback is called the arguments are passed to the callback as an array, so in the example below $arg1 will be "foo" and $arg2 will be "bar" ... save_tokens => \&save_tokens, save_tokens_args => [ "foo", "bar" ], ... sub save_tokens { my ($token_string, $arg1, $arg2) = @_; ... } =item C<token_string =E<gt> $token_string,> Supply tokens generated in a previous request so that you don't have to ask the service provider for new ones. Some service providers refuse to hand out tokens too quickly, so this can be important. =item C<user_agent =E<gt> $ua,> What user agent gets used under the hood? Defaults to a new L<lWP::UserAgent> created on the fly. =back =item Sanity check Any arguments that are left over are assumed to be mistakes and a fatal warning is generated. =back =over 4 =back =back =head1 METHODS Once you have an object, the following methods may be useful for writing a consumer. =head2 C<$oauth2-E<gt>authorization_url(%opts)> Generate a URL for the user to go to to request permissions. By default the C<response_type> and C<client_id> are defaulted, and all of C<redirect_uri>, C<state> and C<scope> are optional but not required. However in practice this all varies by service provider and client type, so look for documentation on that for the actual list that you need. =head2 C<$oauth2-E<gt>request_tokens(%opts)> Request tokens from the service provider (if possible). By default the C<grant_type>, C<client_id> and C<client_secret> are defaulted, and the C<scope> is required. However in practice this all varies by service provider and client type, so look for documentation on that for the actual list that you need. =head2 C<$oauth2-E<gt>get(...)> Issue a C<get> request to an OAuth 2 protected URL, just like you would using L<LWP::UserAgent> to a normal URL. =head2 C<$oauth2-E<gt>head(...)> Issue a C<head> request to an OAuth 2 protected URL, just like you would using L<LWP::UserAgent> to a normal URL. =head2 C<$oauth2-E<gt>post(...)> Issue a C<post> request to an OAuth 2 protected URL, just like you would using L<LWP::UserAgent> to a normal URL. =head2 C<$oauth2-E<gt>delete(...)> Issue a C<delete> request to an OAuth 2 protected URL, similar to the previous examples. (This shortcut is not by default available with L<LWP::UserAgent>.) =head2 C<$oauth2-E<gt>put(...)> Issue a C<put> request to an OAuth 2 protected URL, similar to the previous examples. (This shortcut is not by default available with L<LWP::UserAgent>.) =head2 C<$oauth2-E<gt>request(...)> Issue any C<request> that you could issue with L<LWP::UserAgent>, except that it will be properly signed to go to an OAuth 2 protected URL. =head2 C<$oauth2-E<gt>make_api_call($uri, $params, $headers)> This is a convenience method which makes a call to an OAuth2 API endpoint given by $uri, and returns the JSON response decoded to a hash. If the $params hashref arg is set, its contents will be JSON encoded and sent as POST request content; otherwise it will make a GET request. Optional $headers may be sent which will be passed through to C<$oauth-E<gt>get()> or C<$oauth-E<gt>post()>. If the call succeeds, it will return the response's JSON content decoded as hash, or if no response body was returned, a value of 1 to indicate success. On failure returns undef, and error message is available from C<$oauth2-E<gt>api_call_error()>. =head2 C<$oauth2-E<gt>api_call_error()> If an error occurred in C<$oauth2-E<gt>make_api_call()>, this method will return it. The error message comes from C<HTTP::Response-E<gt>error_as_HTML()>. =head2 C<$oauth2-E<gt>api_url_base()> Returns the base URL of the service provider, which is sometimes useful to be used in the content of OAuth2 API calls. =head2 C<$oauth2-E<gt>can_refresh_tokens> Is sufficient information available to try to refresh tokens? =head2 C<$oauth2-E<gt>should_refresh()> Is it time to refresh tokens? =head2 C<$oauth2-E<gt>set_early_refresh_time($seconds)> Set how many seconds before the end of token expiration the method C<should_refresh> will start turning true. Values over half the initial expiration time of access tokens will be ignored to avoid refreshing too often. This defaults to 300. =head2 C<$oauth2-E<gt>expires_time()> Returns the raw epoch expiration time of the current access token. Typically this is 3600 seconds greater than the time of token creation. =head2 C<$oauth2-E<gt>set_is_strict($mode)> Set strict mode on/off. See the discussion of C<is_strict> in the constructor for an explanation of what it does. =head2 C<$oauth2-E<gt>set_error_handler(\&error_handler)> Set the error handler. See the discussion of C<error_handler> in the constructor for an explanation of what it does. =head2 C<$oauth2-E<gt>set_prerefresh(\&prerefresh)> Set the prerefresh handler. See the discussion of C<prerefresh_handler> in the constructor for an explanation of what it does. =head2 C<$oauth2-E<gt>set_save_tokens($ua)> Set the save tokens handler. See the discussion of C<save_tokens> in the constructor for an explanation of what it does. =head2 C<$oauth2-E<gt>set_user_agent($ua)> Set the user agent. This should respond to the same methods that a L<LWP::UserAgent> responds to. =head2 C<$oauth2-E<gt>user_agent()> Get the user agent. The default if none was explicitly set is a new L<LWP::UserAgent> object. =head1 AUTHOR Ben Tilly, C<< <btilly at gmail.com> >> currently maintained by Thomas Klausner, C<< <domm@cpan.org> >> =head2 Contributors =over =item * L<Leon Wright|https://github.com/techman83> =item * L<Thomas Klausner|https://github.com/domm> =item * L<Alexander Dutton|https://github.com/alexsdutton> =item * L<Chris|https://github.com/TheWatcher> =item * L<Adi Fairbank|https://github.com/adifairbank> =item * L<Adam Millerchip|https://github.com/amillerchip> =back =head1 BUGS Please report any bugs or feature requests to C<bug-lwp-authen-oauth2 at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=LWP-Authen-OAuth2>. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc LWP::Authen::OAuth2 You can also look for information at: =over 4 =item Github (submit patches here) L<https://github.com/domm/perl-oauth2> =item RT: CPAN's request tracker (report bugs here) L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=LWP-Authen-OAuth2> =item AnnoCPAN: Annotated CPAN documentation L<http://annocpan.org/dist/LWP-Authen-OAuth2> =item CPAN Ratings L<http://cpanratings.perl.org/d/LWP-Authen-OAuth2> =item MetaCPAN L<https://metacpan.org/release/LWP-Authen-OAuth2> =back =head1 ACKNOWLEDGEMENTS Thanks to L<Rent.com|http://www.rent.com> for their generous support in letting me develop and release this module. My thanks also to Nick Wellnhofer <wellnhofer@aevum.de> for Net::Google::Analytics::OAuth2 which was very enlightening while I was trying to figure out the details of how to connect to Google with OAuth2. Thanks to =over =item * L<Thomas Klausner|https://github.com/domm> for reporting that client type specific parameters were not available when the client type was properly specified =item * L<Alexander Dutton|https://github.com/alexsdutton> for making C<ServiceProvider> work without requiring subclassing. =item * L<Leon Wright|https://github.com/techman83> for adding a L<Strava | http://strava.com> Service Provider and various bug fixes =item * L<Adi Fairbank|https://github.com/adifairbank> for adding a L<Dwolla | https://www.dwolla.com/> Service Provider and some other improvements =item * L<Adam Millerchip|https://github.com/amillerchip> for adding a L<Line | https://line.me> Service Provider and some refactoring =item * L<Michael Stevens|https://github.com/mstevens> for adding a C<Yahoo | https://developer.yahoo.com> Service Provider and some dist cleanup =item * Nick Morrott for fixing some documentation typos =back =head1 LICENSE AND COPYRIGHT Copyright 2013 Rent.com. This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: L<http://www.perlfoundation.org/artistic_license_2_0> Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =cut 1; # End of LWP::Authen::OAuth2 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������