LWP-Authen-OAuth2-0.20/0000775000175000017500000000000014361275571013106 5ustar dommdommLWP-Authen-OAuth2-0.20/LICENSE0000644000175000017500000004400514361275571014114 0ustar dommdommThis software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End LWP-Authen-OAuth2-0.20/INSTALL0000644000175000017500000000457314361275571014146 0ustar dommdommThis is the Perl distribution LWP-Authen-OAuth2. Installing LWP-Authen-OAuth2 is straightforward. ## Installation with cpanm If you have cpanm, you only need one line: % cpanm LWP::Authen::OAuth2 If it does not have permission to install modules to the current perl, cpanm will automatically set up and install to a local::lib in your home directory. See the local::lib documentation (https://metacpan.org/pod/local::lib) for details on enabling it in your environment. ## Installing with the CPAN shell Alternatively, if your CPAN shell is set up, you should just be able to do: % cpan LWP::Authen::OAuth2 ## Manual installation As a last resort, you can manually install it. If you have not already downloaded the release tarball, you can find the download link on the module's MetaCPAN page: https://metacpan.org/pod/LWP::Authen::OAuth2 Untar the tarball, install configure prerequisites (see below), then build it: % perl Build.PL % ./Build && ./Build test Then install it: % ./Build install Or the more portable variation: % perl Build.PL % perl Build % perl Build test % perl Build install If your perl is system-managed, you can create a local::lib in your home directory to install modules to. For details, see the local::lib documentation: https://metacpan.org/pod/local::lib The prerequisites of this distribution will also have to be installed manually. The prerequisites are listed in one of the files: `MYMETA.yml` or `MYMETA.json` generated by running the manual build process described above. ## Configure Prerequisites This distribution requires other modules to be installed before this distribution's installer can be run. They can be found under the or the "{prereqs}{configure}{requires}" key of META.json. ## Other Prerequisites This distribution may require additional modules to be installed after running Build.PL. Look for prerequisites in the following phases: * to run ./Build, PHASE = build * to use the module code itself, PHASE = runtime * to run tests, PHASE = test They can all be found in the or the "{prereqs}{PHASE}{requires}" key of MYMETA.json. ## Documentation LWP-Authen-OAuth2 documentation is available as POD. You can run `perldoc` from a shell to read the documentation: % perldoc LWP::Authen::OAuth2 For more information on installing Perl modules via CPAN, please see: https://www.cpan.org/modules/INSTALL.html LWP-Authen-OAuth2-0.20/META.json0000644000175000017500000001125114361275571014525 0ustar dommdomm{ "abstract" : "Make requests to OAuth2 APIs.", "author" : [ "Ben Tilly, ", "Thomas Klausner " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.024, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "LWP-Authen-OAuth2", "no_index" : { "directory" : [ "examples", "t", "xt" ] }, "prereqs" : { "build" : { "requires" : { "Module::Build" : "0.28" } }, "configure" : { "requires" : { "Module::Build" : "0.28" } }, "runtime" : { "requires" : { "Carp" : "0", "Exporter" : "0", "HTTP::Request::Common" : "0", "JSON" : "0", "LWP::UserAgent" : "0", "Memoize" : "0", "Module::Load" : "0", "Storable" : "0", "URI" : "0", "base" : "0", "parent" : "0", "perl" : "5.006" } }, "test" : { "requires" : { "File::Spec" : "0", "File::Temp" : "0", "FindBin" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Test::More" : "0", "lib" : "0" } } }, "provides" : { "LWP::Authen::OAuth2" : { "file" : "lib/LWP/Authen/OAuth2.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::AccessToken" : { "file" : "lib/LWP/Authen/OAuth2/AccessToken.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::AccessToken::Bearer" : { "file" : "lib/LWP/Authen/OAuth2/AccessToken/Bearer.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::Args" : { "file" : "lib/LWP/Authen/OAuth2/Args.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Dwolla" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Dwolla.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Google" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Google::Device" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Google::Installed" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Google::Login" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Google::Service" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Google::WebServer" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Line" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Line.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Line/AccessToken.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Strava" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Strava.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Withings" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Withings.pm", "version" : "0.20" }, "LWP::Authen::OAuth2::ServiceProvider::Yahoo" : { "file" : "lib/LWP/Authen/OAuth2/ServiceProvider/Yahoo.pm", "version" : "0.20" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/domm/perl-oauth2/issues" }, "homepage" : "https://github.com/domm/perl-oauth2", "repository" : { "type" : "git", "url" : "https://github.com/domm/perl-oauth2.git", "web" : "https://github.com/domm/perl-oauth2" } }, "version" : "0.20", "x_generated_by_perl" : "v5.34.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.26", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later" } LWP-Authen-OAuth2-0.20/Changes0000644000175000017500000001003514361275571014376 0ustar dommdommRevision history for LWP-Authen-OAuth2 0.20 2023-01-16 17:29:43+01:00 - keep param default value in non strict mode (André Brás) 0.19 2022-03-10 16:43:24+01:00 - Add Withings Service Provider (Brian Foley) - replace explicit gibberisch with a hopeful better explanation (Thomas Klausner) - Make meaningless doc explicitly meaningless. (Tamas TEVESZ) - Fixed broken link as reported by CPANTS. (Mohammad S Anwar) 0.18 2021-01-10 22:49:23+01:00 - dist housekeeping, Dist::Zilla::PluginBundle::Author::DOMM 0.17 2020-11-22T17:42:02 - fix bug in token-refresh, RT132661 (Ruud) - fix doc error reported by niceperl in RT133154 (niceperl) - add redirect_uri as optional request param (suggested by john ridley) - Fix casing of LWP::UserAgent in Pod link (Olaf Alders) 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 (Alex Dutton) - Added overridable methods for returing authz and token endpoints (Alex 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.20/dist.ini0000644000175000017500000000035114361275571014547 0ustar dommdommname = LWP-Authen-OAuth2 author = Ben Tilly, author = Thomas Klausner license = Perl_5 copyright_holder = Ben Tilly, Rent.com, Thomas Klausner copyright_year = 2013 - 2022 [@Author::DOMM] LWP-Authen-OAuth2-0.20/README.md0000644000175000017500000004611314361275571014370 0ustar dommdomm# NAME LWP::Authen::OAuth2 - Make requests to OAuth2 APIs. # VERSION version 0.20 # SYNOPSIS OAuth 2 is a protocol that lets a _user_ tell a _service provider_ that a _consumer_ has permission to use the _service provider_'s APIs to do things that require access to the _user_'s account. This module tries to make life easier for someone who wants to write a _consumer_ in Perl. Specifically it provides convenience methods for all of the requests that are made to the _service provider_ as part of the permission handshake, and after that will proxy off of [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent) 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 [LWP::Authen::OAuth2::Overview](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AOverview) This module will not help with OAuth 1. See the similarly named but unrelated [LWP::Authen::OAuth](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth) for a module that can help with that. Currently [LWP::Authen::OAuth2](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2) provides ready-to-use classes to use OAuth2 with - Dwolla [LWP::Authen::OAuth2::ServiceProvider::Dwolla](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AServiceProvider%3A%3ADwolla) implemented by [Adi Fairbank](https://github.com/adifairbank) - Google [LWP::Authen::OAuth2::ServiceProvider::Google](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AServiceProvider%3A%3AGoogle) - Line [LWP::Authen::OAuth2::ServiceProvider::Line](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AServiceProvider%3A%3ALine) implemented by [Adam Millerchip](https://github.com/amillerchip) - Strava [LWP::Authen::OAuth2::ServiceProvider::Strava](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AServiceProvider%3A%3AStrava) implemented by [Leon Wright](https://github.com/techman83) - Withings [LWP::Authen::OAuth2::ServiceProvider::Withings](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AServiceProvider%3A%3AWithings) implemented by [Brian Foley](https://github.com/foleybri) - Yahoo [LWP::Authen::OAuth2::ServiceProvider::Yahoo](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AServiceProvider%3A%3AYahoo) implemented by [Michael Stevens](https://github.com/michael-stevens) You can also access any other OAuth2 service by setting up a plain `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(); # CONSTRUCTOR When you call `LWP::Authen::OAuth2->new(...)`, arguments are passed as a key/value list. They are processed in the following phases: - Construct service provider - Service provider collects arguments it wants - [LWP::Authen::OAuth2](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2) overrides defaults from arguments - Sanity check Here are those phases in more detail. - Construct service provider There are two ways to construct a service provider. - Prebuilt class To load a prebuilt class you just need one or two arguments. - `service_provider => $Foo,` In the above construct, `$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 [LWP::Authen::OAuth2::ServiceProvider](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AServiceProvider) as well as instructions for making a new one. - `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 `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. - 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: - `authorization_endpoint => $auth_url,` This is the URL which the user is directed to in the authorization request. - `token_endpoint => $token_url,` This is the URL which the consumer goes to for tokens. - Various optional fields [LWP::Authen::OAuth2::ServiceProvider](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2%3A%3AServiceProvider) 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 - 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 `client_id` and `client_secret`. And optionally can pass a `redirect_uri` and `scope`. (The omission of `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. - [LWP::Authen::OAuth2](https://metacpan.org/pod/LWP%3A%3AAuthen%3A%3AOAuth2) 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. - `error_handler => \&error_handler,` Specifies the function that will be called when errors happen. The default is `Carp::croak`. - `is_strict => $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. - `early_refresh_time => $seconds,` How many seconds before the end of estimated access token expiration you will have `should_refresh` start returning true. - `prerefresh => \&prerefresh,` A handler to be called before attempting to refresh tokens. It is passed the `$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. - `save_tokens => \&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 `$oauth2` object. By default this is not provided. However if you intend to access the API multiple times from multiple processes, it is recommended. - `save_tokens_args => [ 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) = @_; ... } - `token_string => $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. - `user_agent => $ua,` What user agent gets used under the hood? Defaults to a new [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent) created on the fly. - Sanity check Any arguments that are left over are assumed to be mistakes and a fatal warning is generated. # METHODS Once you have an object, the following methods may be useful for writing a consumer. ## `$oauth2->authorization_url(%opts)` Generate a URL for the user to go to to request permissions. By default the `response_type` and `client_id` are defaulted, and all of `redirect_uri`, `state` and `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. ## `$oauth2->request_tokens(%opts)` Request tokens from the service provider (if possible). By default the `grant_type`, `client_id` and `client_secret` are defaulted, and the `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. ## `$oauth2->get(...)` Issue a `get` request to an OAuth 2 protected URL, just like you would using [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent) to a normal URL. ## `$oauth2->head(...)` Issue a `head` request to an OAuth 2 protected URL, just like you would using [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent) to a normal URL. ## `$oauth2->post(...)` Issue a `post` request to an OAuth 2 protected URL, just like you would using [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent) to a normal URL. ## `$oauth2->delete(...)` Issue a `delete` request to an OAuth 2 protected URL, similar to the previous examples. (This shortcut is not by default available with [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent).) ## `$oauth2->put(...)` Issue a `put` request to an OAuth 2 protected URL, similar to the previous examples. (This shortcut is not by default available with [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent).) ## `$oauth2->request(...)` Issue any `request` that you could issue with [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent), except that it will be properly signed to go to an OAuth 2 protected URL. ## `$oauth2->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 `$oauth->get()` or `$oauth->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 `$oauth2->api_call_error()`. ## `$oauth2->api_call_error()` If an error occurred in `$oauth2->make_api_call()`, this method will return it. The error message comes from `HTTP::Response->error_as_HTML()`. ## `$oauth2->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. ## `$oauth2->can_refresh_tokens` Is sufficient information available to try to refresh tokens? ## `$oauth2->should_refresh()` Is it time to refresh tokens? ## `$oauth2->set_early_refresh_time($seconds)` Set how many seconds before the end of token expiration the method `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. ## `$oauth2->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. ## `$oauth2->set_is_strict($mode)` Set strict mode on/off. See the discussion of `is_strict` in the constructor for an explanation of what it does. ## `$oauth2->set_error_handler(\&error_handler)` Set the error handler. See the discussion of `error_handler` in the constructor for an explanation of what it does. ## `$oauth2->set_prerefresh(\&prerefresh)` Set the prerefresh handler. See the discussion of `prerefresh_handler` in the constructor for an explanation of what it does. ## `$oauth2->set_save_tokens($ua)` Set the save tokens handler. See the discussion of `save_tokens` in the constructor for an explanation of what it does. ## `$oauth2->set_user_agent($ua)` Set the user agent. This should respond to the same methods that a [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent) responds to. ## `$oauth2->user_agent()` Get the user agent. The default if none was explicitly set is a new [LWP::UserAgent](https://metacpan.org/pod/LWP%3A%3AUserAgent) object. ## Contributors - [Leon Wright](https://github.com/techman83) - [Thomas Klausner](https://github.com/domm) - [Alex Dutton](https://github.com/alexdutton) - [Chris](https://github.com/TheWatcher) - [Adi Fairbank](https://github.com/adifairbank) - [Adam Millerchip](https://github.com/amillerchip) - [André Brás](https://github.com/whity) # ACKNOWLEDGEMENTS Thanks to [Rent.com](http://www.rent.com) 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. Thanks to - [Thomas Klausner](https://github.com/domm) for reporting that client type specific parameters were not available when the client type was properly specified - [Alex Dutton](https://github.com/alexdutton) for making `ServiceProvider` work without requiring subclassing. - [Leon Wright](https://github.com/techman83) for adding a [Strava ](https://metacpan.org/pod/%20http%3A#strava.com) Service Provider and various bug fixes - [Adi Fairbank](https://github.com/adifairbank) for adding a [Dwolla ](https://metacpan.org/pod/%20https%3A#www.dwolla.com) Service Provider and some other improvements - [Adam Millerchip](https://github.com/amillerchip) for adding a [Line ](https://metacpan.org/pod/%20https%3A#line.me) Service Provider and some refactoring - [Michael Stevens](https://github.com/mstevens) for adding a `Yahoo | https://developer.yahoo.com` Service Provider and some dist cleanup - Nick Morrott for fixing some documentation typos # AUTHORS - Ben Tilly, <btilly at gmail.com> - Thomas Klausner # COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. LWP-Authen-OAuth2-0.20/lib/0000775000175000017500000000000014361275571013654 5ustar dommdommLWP-Authen-OAuth2-0.20/lib/LWP/0000775000175000017500000000000014361275571014316 5ustar dommdommLWP-Authen-OAuth2-0.20/lib/LWP/Authen/0000775000175000017500000000000014361275571015542 5ustar dommdommLWP-Authen-OAuth2-0.20/lib/LWP/Authen/OAuth2.pm0000644000175000017500000006510514361275571017207 0ustar dommdommpackage LWP::Authen::OAuth2; # ABSTRACT: Make requests to OAuth2 APIs. our $VERSION = '0.20'; # VERSION 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 $@) { my $class = $data->{_class} or croak("No _class in token_string '$tokens'"); eval {load($class)}; if ($@) { croak("Can't load access token class '$class': $@"); } $tokens = $class->from_ref($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(); } 1; # End of LWP::Authen::OAuth2 __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2 - Make requests to OAuth2 APIs. =head1 VERSION version 0.20 =head1 SYNOPSIS OAuth 2 is a protocol that lets a I tell a I that a I has permission to use the I's APIs to do things that require access to the I's account. This module tries to make life easier for someone who wants to write a I in Perl. Specifically it provides convenience methods for all of the requests that are made to the I as part of the permission handshake, and after that will proxy off of L 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 This module will not help with OAuth 1. See the similarly named but unrelated L for a module that can help with that. Currently L provides ready-to-use classes to use OAuth2 with =over =item * Dwolla L implemented by L =item * Google L =item * Line L implemented by L =item * Strava L implemented by L =item * Withings L implemented by L =item * Yahoo L implemented by L =back You can also access any other OAuth2 service by setting up a plain C 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 Cnew(...)>, 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 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 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 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 $auth_url,> This is the URL which the user is directed to in the authorization request. =item C $token_url,> This is the URL which the consumer goes to for tokens. =item Various optional fields L 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 and C. And optionally can pass a C and C. (The omission of C 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 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,> Specifies the function that will be called when errors happen. The default is C. =item C $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 $seconds,> How many seconds before the end of estimated access token expiration you will have C start returning true. =item C \&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,> 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 [ 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,> 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 $ua,> What user agent gets used under the hood? Defaults to a new L 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-Eauthorization_url(%opts)> Generate a URL for the user to go to to request permissions. By default the C and C are defaulted, and all of C, C and C 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-Erequest_tokens(%opts)> Request tokens from the service provider (if possible). By default the C, C and C are defaulted, and the C 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-Eget(...)> Issue a C request to an OAuth 2 protected URL, just like you would using L to a normal URL. =head2 C<$oauth2-Ehead(...)> Issue a C request to an OAuth 2 protected URL, just like you would using L to a normal URL. =head2 C<$oauth2-Epost(...)> Issue a C request to an OAuth 2 protected URL, just like you would using L to a normal URL. =head2 C<$oauth2-Edelete(...)> Issue a C request to an OAuth 2 protected URL, similar to the previous examples. (This shortcut is not by default available with L.) =head2 C<$oauth2-Eput(...)> Issue a C request to an OAuth 2 protected URL, similar to the previous examples. (This shortcut is not by default available with L.) =head2 C<$oauth2-Erequest(...)> Issue any C that you could issue with L, except that it will be properly signed to go to an OAuth 2 protected URL. =head2 C<$oauth2-Emake_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-Eget()> or C<$oauth-Epost()>. 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-Eapi_call_error()>. =head2 C<$oauth2-Eapi_call_error()> If an error occurred in C<$oauth2-Emake_api_call()>, this method will return it. The error message comes from Cerror_as_HTML()>. =head2 C<$oauth2-Eapi_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-Ecan_refresh_tokens> Is sufficient information available to try to refresh tokens? =head2 C<$oauth2-Eshould_refresh()> Is it time to refresh tokens? =head2 C<$oauth2-Eset_early_refresh_time($seconds)> Set how many seconds before the end of token expiration the method C 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-Eexpires_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-Eset_is_strict($mode)> Set strict mode on/off. See the discussion of C in the constructor for an explanation of what it does. =head2 C<$oauth2-Eset_error_handler(\&error_handler)> Set the error handler. See the discussion of C in the constructor for an explanation of what it does. =head2 C<$oauth2-Eset_prerefresh(\&prerefresh)> Set the prerefresh handler. See the discussion of C in the constructor for an explanation of what it does. =head2 C<$oauth2-Eset_save_tokens($ua)> Set the save tokens handler. See the discussion of C in the constructor for an explanation of what it does. =head2 C<$oauth2-Eset_user_agent($ua)> Set the user agent. This should respond to the same methods that a L responds to. =head2 C<$oauth2-Euser_agent()> Get the user agent. The default if none was explicitly set is a new L object. =head2 Contributors =over =item * L =item * L =item * L =item * L =item * L =item * L =item * 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. Thanks to =over =item * L for reporting that client type specific parameters were not available when the client type was properly specified =item * L for making C work without requiring subclassing. =item * L for adding a L Service Provider and various bug fixes =item * L for adding a L Service Provider and some other improvements =item * L for adding a L Service Provider and some refactoring =item * L for adding a C Service Provider and some dist cleanup =item * Nick Morrott for fixing some documentation typos =back =head1 AUTHORS =over 4 =item * Ben Tilly, =item * Thomas Klausner =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/0000775000175000017500000000000014361275571016644 5ustar dommdommLWP-Authen-OAuth2-0.20/lib/LWP/Authen/OAuth2/AccessToken/0000775000175000017500000000000014361275571021046 5ustar dommdommLWP-Authen-OAuth2-0.20/lib/LWP/Authen/OAuth2/AccessToken/Bearer.pm0000644000175000017500000000342514361275571022606 0ustar dommdommpackage LWP::Authen::OAuth2::AccessToken::Bearer; # ABSTRACT: Bearer access tokens for OAuth2 our $VERSION = '0.20'; # VERSION 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); } 1; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::AccessToken::Bearer - Bearer access tokens for OAuth2 =head1 VERSION version 0.20 =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 AUTHORS =over 4 =item * Ben Tilly, =item * Thomas Klausner =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/ServiceProvider/0000775000175000017500000000000014361275571021757 5ustar dommdommLWP-Authen-OAuth2-0.20/lib/LWP/Authen/OAuth2/ServiceProvider/Dwolla.pm0000644000175000017500000001006214361275571023534 0ustar dommdommpackage LWP::Authen::OAuth2::ServiceProvider::Dwolla; # ABSTRACT: Access Dwolla API v2 our $VERSION = '0.20'; # VERSION 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' }; } 1; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Dwolla - Access Dwolla API v2 =head1 VERSION version 0.20 =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 AUTHORS =over 4 =item * Ben Tilly, =item * Thomas Klausner =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/ServiceProvider/Google.pm0000644000175000017500000002253114361275571023532 0ustar dommdommpackage LWP::Authen::OAuth2::ServiceProvider::Google; # ABSTRACT: Google OAuth2 our $VERSION = '0.20'; # VERSION 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()); } 1; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Google - Google OAuth2 =head1 VERSION version 0.20 =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 => "installed", 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 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/ServiceProvider/Strava.pm������������������������������0000644�0001750�0001750�00000004425�14361275571�023560� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::ServiceProvider::Strava; # ABSTRACT: Access Strava using OAuth2 our $VERSION = '0.20'; # VERSION 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() ); } 1; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Strava - Access Strava using OAuth2 =head1 VERSION version 0.20 =head1 SYNOPSIS See L<http://strava.github.io/api/> 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 NAME LWP::Authen::OAuth2::ServiceProvider::Strava - Access Strava API v3 OAuth2 APIs =head1 VERSION Version 0.02 =head1 REGISTERING Before you can use OAuth 2 with Strava you need to register yourself as a client. For that, go to L<https://www.strava.com/settings/api> and register your application. You'll need to set C<redirect_uri> with them, which will need to be an C<https://...> 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<< <techman@cpan.org> >> =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/ServiceProvider/Line.pm��������������������������������0000644�0001750�0001750�00000013736�14361275571�023214� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::ServiceProvider::Line; # ABSTRACT: Access Line OAuth2 API v2 our $VERSION = '0.20'; # VERSION 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); } 1; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Line - Access Line OAuth2 API v2 =head1 VERSION version 0.20 =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</SYNOPSIS> 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> >> =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/ServiceProvider/Line/����������������������������������0000775�0001750�0001750�00000000000�14361275571�022646� 5����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/lib/LWP/Authen/OAuth2/ServiceProvider/Line/AccessToken.pm��������������������0000644�0001750�0001750�00000002476�14361275571�025415� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken; # ABSTRACT: Line AccessToken our $VERSION = '0.20'; # VERSION 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; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Line::AccessToken - Line AccessToken =head1 VERSION version 0.20 =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/ServiceProvider/Yahoo.pm�������������������������������0000644�0001750�0001750�00000004310�14361275571�023370� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::ServiceProvider::Yahoo; # ABSTRACT: Access Yahoo using OAuth2 our $VERSION = '0.20'; # VERSION our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider); 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; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Yahoo - Access Yahoo using OAuth2 =head1 VERSION version 0.20 =head1 SYNOPSIS See L<https://developer.yahoo.com/oauth2/guide/"> 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<https://developer.yahoo.com/apps/create/>. =head1 AUTHOR Michael Stevens, C<< <mstevens@etla.org> >> =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/ServiceProvider/Withings.pm����������������������������0000644�0001750�0001750�00000006021�14361275571�024106� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::ServiceProvider::Withings; # ABSTRACT: Withings OAuth2 our $VERSION = '0.18'; our $VERSION = '0.20'; # VERSION use strict; use warnings; use JSON; our @ISA = qw(LWP::Authen::OAuth2::ServiceProvider); sub authorization_endpoint { return ' https://account.withings.com/oauth2_user/authorize2'; } sub token_endpoint { return 'https://wbsapi.withings.net/v2/oauth2'; } sub authorization_required_params { return ( 'client_id', 'state', 'scope', 'redirect_uri', 'response_type' ); } sub authorization_default_params { return ( response_type => 'code', state => 'auth', scope => 'user.metrics' ); } sub request_required_params { return ( 'action', 'grant_type', 'client_id', 'client_secret', 'code', 'redirect_uri' ); } sub request_default_params { return ( grant_type => 'authorization_code', action => 'requesttoken' ); } sub refresh_required_params { return ( 'action', 'grant_type', 'client_id', 'client_secret', 'refresh_token' ); } sub refresh_default_params { return ( grant_type => 'refresh_token', action => 'requesttoken' ); } sub construct_tokens { my ( $self, $oauth2, $response ) = @_; my $content = eval { decode_json( $response->content ) }; $content = $content->{ 'body' }; $response->content( encode_json( $content ) ); $self->SUPER::construct_tokens( $oauth2, $response ); } 1; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Withings - Withings OAuth2 =head1 VERSION version 0.20 =head1 SYNOPSIS See L<https://developer.withings.com/> for Withings's own documentation. Withings's documentation is very detailed, so that is the best place to find detailed and up to date info about. =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Withings - Access Withings using OAuth2 =head1 VERSION version 0.18 =head1 NAME LWP::Authen::OAuth2::ServiceProvider::Withings - Access Withings OAuth2 APIs =head1 VERSION Version 0.01 =head1 REGISTERING Before you can use OAuth 2 with Withings you need to register a developer account at L<https://account.withings.com/connectionuser/account_create> You also need to create an application at L<https://account.withings.com/partner/> which will provide you with a C<clientId> and C<client_secret>. =head1 AUTHOR Brian Foley, C<< <brianf@sindar.net> >> =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/ServiceProvider.pm�������������������������������������0000644�0001750�0001750�00000054517�14361275571�022327� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::ServiceProvider; # ABSTRACT: ServiceProvider base class our $VERSION = '0.20'; # VERSION 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 { exists $oauth2_args->{$_} ? ($_, $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 redirect_uri); } 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); } 1 __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::ServiceProvider - ServiceProvider base class =head1 VERSION version 0.20 =head1 SYNOPSIS This is a base module for representing an OAuth 2 service provider. It is implicitly constructed from the parameters to C<LWP::Authen::OAuth2-E<gt>new>, and is automatically delegated to when needed. The first way to try to specify the service provider is with the parameters C<service_provider> and possibly C<client_type>: LWP::Authen::OAuth2->new( ... service_provider => "Foo", client_type => "bar", # optional ... ); The first parameter will cause L<LWP::Authen::OAuth2::ServiceProvider> to look for either C<LWP::Authen::OAuth2::ServiceProvider::Foo>, or if that is not found, for C<Foo>. (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<LWP::Authen::OAuth2::Overview> if you are uncertain how to figure out the I<Authorization Endpoint> and I<Token Endpoint> 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<LWP::Authen::OAuth2::ServiceProvider::Dwolla|Dwolla> =item L<LWP::Authen::OAuth2::ServiceProvider::Google|Google> =item L<LWP::Authen::OAuth2::ServiceProvider::Line|Line> =item L<LWP::Authen::OAuth2::ServiceProvider::Strava|Strava> =item L<LWP::Authen::OAuth2::ServiceProvider::Withings|Withings> =item L<LWP::Authen::OAuth2::ServiceProvider::Yahoo|Yahoo> =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<LWP::Authen::OAuth2> asks L<LWP::Authen::OAuth2::ServiceProvider> to construct a service provider. Based on the C<service_provider> 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<LWP::Authen::OAuth2> 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<most> 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<authorization_endpoint> Takes no arguments, returns the URL for the Authorization Endpoint for the service provider. Your subclass cannot function without this. =item C<token_endpoint> Takes no arguments, returns the URL for the Token Endpoint for the service provider. Your subclass cannot function without this. =item C<client_type_class> This method receives your class name and the passed in C<client_type>. It is supposed to make sure that the class that handles that C<client_type> 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<client_type> the value that is passed in is C<default>. 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<client_type>. =item C<init> After C<new> has figured out the right class to load, it immediately calls C<$self-e<gt>init($opts)> with C<$opts> being a hashref of all options passed to C<LWP::Authen::OAuth2-E<gt>new(...)> 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<init>. 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<required_init> The parameters that must be passed into C<LWP::Authen::OAuth2-E<gt>new(...)> to initialize the service provider object. The default required parameters are C<client_id> and C<client_secret>, 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<refresh_token> in your flow, then you should require nothing. =item C<optional_init> The parameters that can be passed into C<LWP::Authen::OAuth2-E<gt>new(...)> to initialize the service provider object. The default optional parameters are C<redirect_uri> and C<scope> which, if passed, do not have to be passed into other method calls. The C<state> 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<must> be included in the authorization url, the post to request tokens, and the post to refresh tokens respectively. If you explicitly provide these lists of required parameters, and a user fails to provide one (or more) of the parameters, the generated error message can tell the user which parameters are missing. =item C<{authorization,request,refresh}_optional_params> These three methods list parameters that B<can> 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<default> 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<grant_type> of C<password> or C<client_credentials>. =item C<post_to_token_endpoint> 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<access_token_class> Given a C<token_type>, 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<token_type> 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<LWP::Authen::OAuth2::AccessToken> 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<oauth2_class> Override this to cause C<LWP::Authen::OAuth2-E<gt>new(...)> 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<LWP::Authen::OAuth2>. Few service provider classes should find a reason to do this, but it can be done if you need. =item C<collect_action_params> 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<request_tokens> 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<MANIFEST> 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 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/AccessToken.pm�����������������������������������������0000644�0001750�0001750�00000012007�14361275571�021402� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::AccessToken; # ABSTRACT: Access tokens for OAuth2. our $VERSION = '0.20'; # VERSION use strict; use warnings; use Carp qw(confess); our @CARP_NOT = qw(LWP::Authen::OAuth2); sub from_ref { my ($class, $data) = @_; # If create_time is passed, then that will overwrite this default. return bless {create_time => time(), %$data}, $class; } sub to_ref { my $self = shift; return { %$self }; } sub expires_time { my $self = shift; my $initial_expires_in = $self->{expires_in} || 3600; return $self->{create_time} + $initial_expires_in; } sub expires_in { my $self = shift; return $self->expires_time - time(); } 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; } } sub for_refresh { my $self = shift; if ($self->{refresh_token}) { return refresh_token => $self->{refresh_token}; } else { return (); } } sub copy_refresh_from { my ($self, $other) = @_; if ($other->{refresh_token}) { $self->{refresh_token} ||= $other->{refresh_token}; } } 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; } sub _request { my ($self, $oauth2, $request, @rest) = @_; # ... # return ($response, $try_refresh); confess("Method _request needs to be overwritten."); } 1; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::AccessToken - Access tokens for OAuth2. =head1 VERSION version 0.20 =head1 SYNOPSIS This is a base class for signing API requests with OAuth2 access tokens. A subclass should override the C<request> method with something that knows how to make a request, detect the need to try to refresh, and attmpts to do that. See L<lWP::Authen::OAuth2::AccessToken::Bearer> for an example. Subclasses of this one are not directly useful. Please see L<LWP::Authen::OAuth2> for the interface that you should be using. =head1 METHODS =head2 C<from_ref> Construct an access token from a hash reference. The default implementation merely blesses it as an object, defaulting the C<create_time> field to the current time. my $access_token = $class->from_ref($data); If you roll your own, be aware that the fields C<refresh_token> 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. =head2 C<to_ref> 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. =head2 C<expires_time> Estimate expiration time. Not always correct, due to transit delays, clock skew, etc. =head2 C<expires_in> Estimate the seconds until expiration. Not always correct, due to transit delays, clock skew, etc. =head2 C<should_refresh> Boolean saying whether a refresh should be emitted now. =head2 C<for_refresh> Returns key/value pairs for C<$oauth2> (and eventually the service provider class) to use in trying to refresh. =head2 C<copy_refresh_from> Pass in a previous access token, copy anything needed to refresh. =head2 C<request> Make a request. If expiration is detected, refreshing by the best available method (if any). my $response = $access_token->request($oauth2, @request_for_lwp); =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<THIS IS THE ONLY METHOD A SUBCLASS MUST OVERRIDE!> =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/Overview.pod�������������������������������������������0000644�0001750�0001750�00000067556�14361275571�021177� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# ABSTRACT: Overview of accessing OAuth2 APIs with LWP::Authen::OAuth2 # PODNAME: LWP::Authen::OAuth2::Overview __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::Overview - Overview of accessing OAuth2 APIs with LWP::Authen::OAuth2 =head1 VERSION version 0.20 =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<LWP::Authen::OAuth2> 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<highlighted>. Terms will hopefully be clear from context, but all highlighted terms are explained in the L</Terminology> section. =head1 The Purpose of OAuth 2 OAuth 2 makes it easy for large I<service provider>s to write many APIs that I<user>s can securely authorize third party I<consumer>s 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<LWP::Authen::OAuth2> L<LWP::Authen::OAuth2> exists to help Perl programmers who want to be a I<consumer> of an API protected by OAuth 2 to construct and make all of the necessary requests to the I<service provider> that you need to make. You will still need to set up your relationship with the I<service provider>, 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<consumer>, this module is B<definitely> not intended for you. (Though this document may still be helpful.) =head1 The Basic OAuth 2 Handshake OAuth 2 allows a I<user> to tell a I<service provider> that a I<consumer> should be allowed to access the I<user>'s data through an API. This permissioning happens through the following handshake. The I<consumer> sends the I<user> to an I<authorization_url> managed by the I<service provider>. The I<service provider> tells the I<user> that the I<consumer> wants access to that account and asks if this is OK. The I<user> confirms that it is, and is sent back to the I<consumer> with proof of the conversation. The I<consumer> presents that proof to the I<service provider> along with proof that it actually is the I<consumer>, and is granted tokens that will act like keys to the I<user>'s account. After that the I<consumer> 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<service provider> 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 I<service provider>s easy, and the lives of I<consumer>s possible.) =head1 Becoming a Consumer If you want to access an OAuth 2 protected API, you need to become a I<consumer>. Here are the necessary steps, in the order that things happen in. =over 4 =item Register with the I<service provider> You cannot access a I<service provider> without them knowing who you are. After you go through their process, at a minimum you will get a public I<client_id>, a private I<client_secret>, and have agreed on one or more I<redirect_uri>s that the I<user> can use to deliver an I<authorization code> back to you. (That is not the only kind of proof that the I<user> can be given for the I<consumer>, but it is probably the only one that makes sense for a Perl I<consumer>.) The I<redirect_uri> is often a C<https:///...> URL under your control. You also are likely to have had to tell the I<service provider> about what type of software you're writing (webserver, command line, etc). This determines your I<client type>. They may call this a scenario, or I<flow>, or something else. You will also need information about the I<service provider>. Specifically you will need to know their I<Authorization Endpoint> and I<Token Endpoint>. They hopefully also have useful documentation about things like their APIs. L<LWP::Authen::OAuth2> is not directly involved in this step. If a L<LWP::Authen::OAuth2::ServiceProvider::Foo> class exists, it should already have the I<service provider> 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<service provider> facts in your call to C<LWP::Authen::OAuth2-E<gt>new(...)> and an appropriate L<LWP::Authen::OAuth2::ServiceProvider> 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<service provider> has to be stored on your end. This includes tokens that will let you access private information for the I<user>. You need to be able to securely store and access these. L<LWP::Authen::OAuth2> does not address this, beyond providing hooks that you are free to use as you see fit. =item Build interaction asking for I<user> permission You need to have some way of convincing the I<user> that they want to give you permission, ending in giving them an I<authorization_url> which sends them off to the I<service provider> to authorize access. This interaction can range from a trivial conversation with yourself if you are the only I<user> 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<LWP::Authen::OAuth2> helps you build that URL. The rest is up to you. =item Build interaction receiving your I<authorization code> When the I<user> finishes their interaction with the I<service provider>, if the I<service provider> is sure that they know where to send the user (they know your I<client_id>, your I<redirect_uri> makes sense to them) then they will be sent to the I<redirect_uri> to pass information back to you. If you succeeded, you will receive a I<code> in some way. For instance if your I<redirect_uri> is a URL, it will have a get parameter named C<code>. You could get an C<error> parameter back instead. See L<RFC 6749|http://tools.ietf.org/html/rfc6749#section-4.1.2.1> 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<LWP::Authen::OAuth2> is not involved with this. =item Request tokens Once you have that I<code> you are supposed to immediately trade it in for tokens. L<LWP::Authen::OAuth2> provides the C<request_tokens> method to do this for you. Should you not actually get tokens, then the C<request_tokens> method will trigger an error. B<NOTE> that the I<code> cannot be expected to work more than once. Nor can you expect the I<service provider> to repeatedly hand out working I<code>s 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<LWP::Authen::OAuth2> 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<LWP::Authen::OAuth2> takes care of signing your API requests. What requests you need to actually make are between you and the I<service provider>. 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<access token> that is used to sign requests will only work for a limited time. If you were given a I<request token>, that can be used to request another I<access token> at any time. Which raises the possibility that you make a request, it fails because the I<access token> expired, you refresh it, then need to retry your request. L<LWP::Authen::OAuth2> 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 I<client type>s are not expected to use this pattern. You are only given an I<access token> and are expected to send the user through the handshake again when that expires. The second time through the redirect on the I<service provider>'s side is immediate, so the user experience should be seamless. However L<LWP::Authen::OAuth2> does not try to automate that logic. But C<$oauth2-E<gt>should_refresh> can let you know when it is time to send the user through, and C<$oauth2-E<gt>can_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<user> may revoke your access, the I<service provider> may decide you are a suspicious character, there may have been a service outage, etc. L<LWP::Authen::OAuth2> 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<consumer>. 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<service provider> at any time can add any new method that they want so long as they invent a new I<grant_type> for it.) =over 4 =item consumer The I<consumer> 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<consumer>'s side which actually will access the API. From a I<consumer>'s point of view, a I<consumer> and the I<client> are usually the same thing. But, in fact, a single I<consumer> may actually write multiple I<client>s. 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<consumer> rather than I<client> because that term is less likely overloaded in most organizations. =item user The I<user> is the entity (person or company) who wishes to let the I<consumer> access their account. =item Resource Owner What the OAuth 2 specification calls the I<user>, to focus attention on the fact that they own the data which will get accessed. I chose to say I<user> instead of I<Resource Owner> because that is my best guess as to what the I<consumer> is most likely to already call them. =item service provider The I<service provider> 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<service provider> which hosts provides an API to the user's data. The name has deliberate symmetry with I<Resource Owner>. =item Authorization Server In the OAuth 2 specification, this is the service run by the I<service provider> which is responsible for granting access to the I<Resource Server>. The I<consumer> does not need to care about this distinction, but it exposes an important fact about how the I<service provider> is likely to be structured internally. You typically will have one team that is responsible for granting access, tracking down I<client>s 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<consumer>, you will make API requests to the I<Resource Server> signed with proof of auhorization from the I<Authorization Server>, the I<Resource Server> will confirm authorization with the I<Authorization Server>, and then the I<Resource Server> 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<service provider> internally tags each I<client> with a I<client type> which tells it something about what environment it is in, and how it interacts with the I<user>. Are are the basic types listed in L<RFC 6749|http://tools.ietf.org/html/rfc6749#section-2.1>: =over 4 =item web application Runs on a web server. Is expected to keep secrets. Likely to be appropriate for a Perl I<client>. =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<client>. =item native application Application installed on a I<user>'s machine. Can't keep secrets. Possibly appropriate for a Perl I<client>. =back Of course all of this is up to the I<service provider>. For example at the time of this writing, Google documents no less than six I<client type>s at L<https://developers.google.com/accounts/docs/OAuth2>, none of which have been given the above names. (They also call them "Scenarios" rather than I<client type>.) They rename the top two, split I<native application> into two based on whether your application controls a browser, and add two new ones. =item flow Your I<flow> is the sequence and methods of interactions that set up authorization. The I<flow> depends on your I<service provider> and I<client type>. For example the I<service provider> might redirect the I<user> 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<flow> being more common terminology in OAuth 2, I<client type> is more self-explanatory, so I've generally gone with that instead. =item client_id The I<client_id> is a public ID that tells the I<service provider> about the I<client> that is accessing it. That is, it says both who the I<consumer> is, and what the I<client type> is. Being public, the I<client_id> can be shared with the I<user>. The details of how this is assigned are between the I<consumer> and the I<service provider>. =item client_secret The I<client_secret> is a somewhat private piece of information that the I<consumer> can pass to the I<service provider> to prove that the request really comes from the I<consumer>. How much this is trusted, and how it is used, will depend on the I<client type> and I<service provider>. =item redirect_uri The I<service provider> needs a way to tell the I<user> how to pass information back to the I<consumer> in a secure way. That is provided by the I<redirect_uri> which can be anything from a C<https://...> URL that the I<consumer> controls to an instruction that lets the I<service provider> know that it should tell the I<user> to cut and paste some information. It is up to the I<service provider> what values of are acceptable for the I<redirect_uri>, and whether it is a piece of information that is remembered or passed in during the authorization process. =item state The I<state> is an optional piece of information that can be created by the I<consumer> 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<service provider> and I<client type>, it may be required. =item scope The I<scope> 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<service provider>. Inside of the I<service provider>, what likely happens is that the team which runs a given I<Resource Server> tells the team running the I<Authorization Server> what permissions to their API should be called. And then the I<Authorization Server> can limit a given I<consumer> to just the APIs that the I<user> authorized them for. =item Authorization Endpoint The I<Authorization Endpoint> is the URL provided by the I<service provider> for the purpose of sending requests to authorize the I<consumer> to access the I<user>'s account. This is part of the I<Authorization Server>. =item response_type The I<response_type> tells the I<service provider> what kind of information it is supposed to pass back. I am not aware of a case where a Perl I<client> could usefully use any value other than C<code>. However there are I<flow>s where other things happen. For example the I<flow> for the I<user-agent-based application> I<client type> uses a I<response_type> of I<token>. While the field is not very useful for Perl I<client>s, it is required in the specification. So you have to pass it. =item authorization_url This is the URL on the I<service provider>'s website that the I<user> goes to in order to let the I<service provider> know what authorization is being requested. It is constructed as the I<Authorization Endpoint> with get parameters added for the I<response_type>, I<client_id>, and optionally I<state>. The specification mentions both I<redirect_uri> and I<scope> 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<client type>. An example URL for Google complete with optional extensions is L<https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&state=%2Fprofile&redirect_uri=https%3A%2F%2Foauth2-login-demo.appspot.com%2Fcode&response_type=code&client_id=812741506391.apps.googleusercontent.com&approval_prompt=force> In L<LWP::Authen::OAuth2> the C<authorization_url> method constructs this URL. If your request needs to include the I<state>, I<scope>, or any I<service provider> 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<response_type> is set to C<code> (which should be the case), then on success the I<service provider> will generate a one use I<authorization code> to give to the user to take back to the I<consumer>. Depending on the I<flow> this could happen with no effort on the part of the I<user>. For example the I<user> can be redirected to the I<redirect_uri> with the I<code> 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<code>. But it is described in one interaction as an I<authorization_code>. =item Token Endpoint The I<Token Endpoint> is the URL provided by the I<service provider> for the purpose of sending requests from the I<consumer> to get tokens allowing access to the I<user>'s account. =item grant_type The I<grant_type> is the type of grant you expected to get based on the I<response_type> requested in the I<authorization_url>. For a I<response_type> of C<code> (which is almost certainly what will be used with any I<consumer> written in Perl), the I<grant_type> has to be C<authorization_code>. If they were being consistent, then that would be I<code> like it is everywhere else, but that's what the spec says. We will later encounter the I<grant_type> C<refresh_token>. The specification includes potential requests that can be in a I<flow> that might prove useful. However you are only likely to encounter that if you are subclassing L<LWP::Authen::OAuth2::ServiceProvider>. In that case you will hopefully discover the applicability and details of those I<grant_type>s from the I<service provider>'s documentation. =item Access Token Request Once the I<consumer> has a I<code> the consumer can submit an I<Access Token Request> by sending a POST request to the I<Token Endpoint> with the I<grant_type>, I<code>, I<client_id>, I<client_secret>, I<redirect_uri> and (if in the authorization code) the I<state>. Your I<service provider> 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<service provider> which can decide not to give you tokens for any reason that they want, including that you asked twice, they think the I<user> might be compromised, they don't like the I<client>, or the phase of the Moon. (I am not aware of any I<service provider> that makes failure depend on the phase of the Moon, but the others are not made up.) The C<request_tokens> method of L<LWP::Authen::OAuth2> will make this request for you, read the JSON and create the token or tokens. If you passed in a C<save_tokens> 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<token_type> 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 I<service provider>s 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<LWP::Authen::OAuth2::AccessToken> for advice on how to add support for a new or incorrectly implemented I<token_type>. =item expires_in The number of seconds until you will need a new token because the old one should have expired. L<LWP::Authen::OAuth2> provides the C<should_refresh> 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<access_token> is a temporary token that gives the I<consumer> access to the I<user>'s data in the I<service provider>'s system. In the above response the C<access_token> is the value of the token, C<expires_in> is the number of seconds it is good for in theory (practice tends to be close but not always exact), and C<token_type> specifies how it is supposed to be used. Once the authorization handshake is completed, if the I<access_token> has a supported I<token_type>. then L<LWP::Authen::OAuth2> will automatically sign any requests for you. =item Bearer token If the I<token_type> is C<bearer> (case insensitive), then you should have a I<bearer token> as described by L<RFC 6750|http://tools.ietf.org/html/rfc6750>. 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<access_token=...> as a post or get parameter, though the specification recommends against using a get parameter. If you are using L<LWP::Authen::OAuth2>, then it is signed with the header. =item refresh_token The above example also included a I<refresh_token>. If you were given one, you can use it later to ask for a refreshed I<access_token>. Whether you get one is up to your I<service provider>, who is likely to decide that based on your I<client_type>. =item Refresh Access Token If you have a I<refresh_token>, you can at any time send a I<Refresh Access Token> request. This is a POST to the I<Token Endpoint> with the I<refresh_token>, I<client_id> and I<client_secret> arguments. You also have to send a I<grant_type> of C<refresh_token>. 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<LWP::Authen::OAuth2> this request is made for you transparently behind the scenes if possible. If you're curious when, look in the source for the C<refresh_access_token> 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 I<flow>s offer a I<refresh_token>. If you're on one of those I<flow>s then you need to send the I<user> back to the I<service provider> for authorization renewal. From the I<user>'s point of view this is likely to be painless because it will be done with transparent redirects. But the I<consumer> needs to be aware of it. =back =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/lib/LWP/Authen/OAuth2/Args.pm������������������������������������������������0000644�0001750�0001750�00000004427�14361275571�020103� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package LWP::Authen::OAuth2::Args; # ABSTRACT: Args our $VERSION = '0.20'; # VERSION 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; __END__ =pod =encoding UTF-8 =head1 NAME LWP::Authen::OAuth2::Args - Args =head1 VERSION version 0.20 =head1 AUTHORS =over 4 =item * Ben Tilly, <btilly at gmail.com> =item * Thomas Klausner <domm@plix.at> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 - 2022 by Ben Tilly, Rent.com, Thomas Klausner. 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.20/t/���������������������������������������������������������������������������0000775�0001750�0001750�00000000000�14361275571�013351� 5����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/�����������������������������������������������������������������������0000775�0001750�0001750�00000000000�14361275571�014013� 5����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/Authen/����������������������������������������������������������������0000775�0001750�0001750�00000000000�14361275571�015237� 5����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/Authen/OAuth2.t��������������������������������������������������������0000755�0001750�0001750�00000000345�14361275571�016531� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/���������������������������������������������������������0000775�0001750�0001750�00000000000�14361275571�016341� 5����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/Authen/OAuth2/AccessToken/���������������������������������������������0000775�0001750�0001750�00000000000�14361275571�020543� 5����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/Authen/OAuth2/AccessToken/Bearer.t�������������������������������������0000755�0001750�0001750�00000000400�14361275571�022123� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/ServiceProvider.t����������������������������������������0000755�0001750�0001750�00000001726�14361275571�021650� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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"; use LWP::Authen::OAuth2; my $oauth2 = LWP::Authen::OAuth2->new( is_strict => 0, client_id => 'Test', client_secret => 'Test', ); my $service_provider = LWP::Authen::OAuth2::ServiceProvider->new({ request_default_params => { key1 => 'value1', }, }); my $params = $service_provider->collect_action_params( 'request', $oauth2, ); is_deeply( $params, { client_id => 'Test', client_secret => 'Test', key1 => 'value1', }, 'collect_action_params - no strict mode, keep default values if not specified elsewhere', ); } done_testing(); ������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/Authen/OAuth2/ServiceProvider/�����������������������������������������0000775�0001750�0001750�00000000000�14361275571�021454� 5����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/Authen/OAuth2/ServiceProvider/Line.t�����������������������������������0000755�0001750�0001750�00000001727�14361275571�022540� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/ServiceProvider/Withings.t�������������������������������0000644�0001750�0001750�00000001024�14361275571�023430� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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::Withings' ) || print "Bail out!\n"; use LWP::Authen::OAuth2; my $oauth2 = LWP::Authen::OAuth2->new( client_id => 'Test', client_secret => 'Test', service_provider => "Withings", redirect_uri => "http://127.0.0.1", ); isa_ok($oauth2, 'LWP::Authen::OAuth2'); } done_testing(); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/Authen/OAuth2/ServiceProvider/Strava.t���������������������������������0000755�0001750�0001750�00000001020�14361275571�023073� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/ServiceProvider/Line/������������������������������������0000775�0001750�0001750�00000000000�14361275571�022343� 5����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/t/LWP/Authen/OAuth2/ServiceProvider/Line/AccessToken.t�����������������������0000755�0001750�0001750�00000003243�14361275571�024735� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/ServiceProvider/Dwolla.t���������������������������������0000755�0001750�0001750�00000000744�14361275571�023071� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/ServiceProvider/Google.t���������������������������������0000755�0001750�0001750�00000001020�14361275571�023047� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/ServiceProvider/Yahoo.t����������������������������������0000644�0001750�0001750�00000001232�14361275571�022714� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/AccessToken.t��������������������������������������������0000755�0001750�0001750�00000000365�14361275571�020735� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/LWP/Authen/OAuth2/Args.t���������������������������������������������������0000755�0001750�0001750�00000002653�14361275571�017431� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /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.20/t/00-compile.t���������������������������������������������������������������0000644�0001750�0001750�00000004002�14361275571�015375� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������use 5.006; use strict; use warnings; # this test was generated with Dist::Zilla::Plugin::Test::Compile 2.058 use Test::More; plan tests => 12 + ($ENV{AUTHOR_TESTING} ? 1 : 0); my @module_files = ( 'LWP/Authen/OAuth2.pm', 'LWP/Authen/OAuth2/AccessToken.pm', 'LWP/Authen/OAuth2/AccessToken/Bearer.pm', 'LWP/Authen/OAuth2/Args.pm', 'LWP/Authen/OAuth2/ServiceProvider.pm', 'LWP/Authen/OAuth2/ServiceProvider/Dwolla.pm', 'LWP/Authen/OAuth2/ServiceProvider/Google.pm', 'LWP/Authen/OAuth2/ServiceProvider/Line.pm', 'LWP/Authen/OAuth2/ServiceProvider/Line/AccessToken.pm', 'LWP/Authen/OAuth2/ServiceProvider/Strava.pm', 'LWP/Authen/OAuth2/ServiceProvider/Withings.pm', 'LWP/Authen/OAuth2/ServiceProvider/Yahoo.pm' ); # fake home for cpan-testers use File::Temp; local $ENV{HOME} = File::Temp::tempdir( CLEANUP => 1 ); my @switches = ( -d 'blib' ? '-Mblib' : '-Ilib', ); use File::Spec; use IPC::Open3; use IO::Handle; open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!"; my @warnings; for my $lib (@module_files) { # see L<perlfaq8/How can I capture STDERR from an external command?> my $stderr = IO::Handle->new; diag('Running: ', join(', ', map { my $str = $_; $str =~ s/'/\\'/g; q{'} . $str . q{'} } $^X, @switches, '-e', "require q[$lib]")) if $ENV{PERL_COMPILE_TEST_DEBUG}; my $pid = open3($stdin, '>&STDERR', $stderr, $^X, @switches, '-e', "require q[$lib]"); binmode $stderr, ':crlf' if $^O eq 'MSWin32'; my @_warnings = <$stderr>; waitpid($pid, 0); is($?, 0, "$lib loaded ok"); shift @_warnings if @_warnings and $_warnings[0] =~ /^Using .*\bblib/ and not eval { +require blib; blib->VERSION('1.01') }; if (@_warnings) { warn @_warnings; push @warnings, @_warnings; } } is(scalar(@warnings), 0, 'no warnings found') or diag 'got warnings: ', ( Test::More->can('explain') ? Test::More::explain(\@warnings) : join("\n", '', @warnings) ) if $ENV{AUTHOR_TESTING}; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/cpanfile���������������������������������������������������������������������0000644�0001750�0001750�00000001521�14361275571�014607� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is generated by Dist::Zilla::Plugin::CPANFile v6.024 # Do not edit this file directly. To change prereqs, edit the `dist.ini` file. requires "Carp" => "0"; requires "Exporter" => "0"; requires "HTTP::Request::Common" => "0"; requires "JSON" => "0"; requires "LWP::UserAgent" => "0"; requires "Memoize" => "0"; requires "Module::Load" => "0"; requires "Storable" => "0"; requires "URI" => "0"; requires "base" => "0"; requires "parent" => "0"; requires "perl" => "5.006"; on 'build' => sub { requires "Module::Build" => "0.28"; }; on 'test' => sub { requires "File::Spec" => "0"; requires "File::Temp" => "0"; requires "FindBin" => "0"; requires "IO::Handle" => "0"; requires "IPC::Open3" => "0"; requires "Test::More" => "0"; requires "lib" => "0"; }; on 'configure' => sub { requires "Module::Build" => "0.28"; }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/MANIFEST���������������������������������������������������������������������0000644�0001750�0001750�00000002303�14361275571�014233� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.024. Build.PL Changes INSTALL LICENSE MANIFEST META.json README.md cpanfile dist.ini 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/Withings.pm lib/LWP/Authen/OAuth2/ServiceProvider/Yahoo.pm t/00-compile.t 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/Withings.t t/LWP/Authen/OAuth2/ServiceProvider/Yahoo.t �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������LWP-Authen-OAuth2-0.20/Build.PL���������������������������������������������������������������������0000644�0001750�0001750�00000002756�14361275571�014412� 0����������������������������������������������������������������������������������������������������ustar �domm����������������������������domm������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # This file was automatically generated by Dist::Zilla::Plugin::ModuleBuild v6.024. use strict; use warnings; use Module::Build 0.28; my %module_build_args = ( "build_requires" => { "Module::Build" => "0.28" }, "configure_requires" => { "Module::Build" => "0.28" }, "dist_abstract" => "Make requests to OAuth2 APIs.", "dist_author" => [ "Ben Tilly, <btilly at gmail.com>", "Thomas Klausner <domm\@plix.at>" ], "dist_name" => "LWP-Authen-OAuth2", "dist_version" => "0.20", "license" => "perl", "module_name" => "LWP::Authen::OAuth2", "recursive_test_files" => 1, "requires" => { "Carp" => 0, "Exporter" => 0, "HTTP::Request::Common" => 0, "JSON" => 0, "LWP::UserAgent" => 0, "Memoize" => 0, "Module::Load" => 0, "Storable" => 0, "URI" => 0, "base" => 0, "parent" => 0, "perl" => "5.006" }, "test_requires" => { "File::Spec" => 0, "File::Temp" => 0, "FindBin" => 0, "IO::Handle" => 0, "IPC::Open3" => 0, "Test::More" => 0, "lib" => 0 } ); my %fallback_build_requires = ( "File::Spec" => 0, "File::Temp" => 0, "FindBin" => 0, "IO::Handle" => 0, "IPC::Open3" => 0, "Module::Build" => "0.28", "Test::More" => 0, "lib" => 0 ); unless ( eval { Module::Build->VERSION(0.4004) } ) { delete $module_build_args{test_requires}; $module_build_args{build_requires} = \%fallback_build_requires; } my $build = Module::Build->new(%module_build_args); $build->create_build_script; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������