File Coverage

lib/OAuthomatic.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2             package OAuthomatic;
3             # ABSTRACT: automate setup of access to OAuth-secured resources. Intended especially for use in console scripts, ad hoc applications etc.
4              
5             # FIXME: option to hardcode client_cred
6              
7              
8 1     1   1001 use Moose;
  0            
  0            
9             our $VERSION = '0.01'; # VERSION
10             use namespace::sweep;
11             # FIXME: switch to Moo
12             use MooseX::AttributeShortcuts;
13             use Carp;
14             use Path::Tiny;
15             use Encode qw/encode decode/;
16             use Const::Fast 0.014;
17             use Try::Tiny;
18             use Scalar::Util qw/reftype/;
19              
20             use OAuthomatic::Server;
21             use OAuthomatic::Config;
22             use OAuthomatic::Caller;
23             use OAuthomatic::SecretStorage;
24             use OAuthomatic::OAuthInteraction;
25             use OAuthomatic::UserInteraction;
26             use OAuthomatic::Internal::UsageGuard;
27             use OAuthomatic::Internal::Util;
28             use OAuthomatic::ServerDef;
29              
30             ###########################################################################
31             # Construction support, fixed attributes
32             ###########################################################################
33              
34             const my @_CONFIG_ATTRIBUTES => (
35             'app_name', 'password_group', 'browser',
36             'html_dir', 'debug',
37             );
38              
39             # FIXME: sample below can be bad
40              
41              
42             has 'config' => (
43             is => 'ro', isa => 'OAuthomatic::Config', required => 1,
44             handles => \@_CONFIG_ATTRIBUTES);
45              
46              
47             has 'server' => (
48             is => 'ro', isa => 'OAuthomatic::Server', required => 1,
49             handles => ['site_name']);
50              
51              
52             # Promoting params to config if necessary and remapping server
53             around BUILDARGS => sub {
54             my $orig = shift;
55             my $class = shift;
56             my $objargs = $class->$orig(@_);
57             unless(exists $objargs->{config}) {
58             my @ctx_args;
59             foreach my $attr (@_CONFIG_ATTRIBUTES) {
60             if(exists $objargs->{$attr}) {
61             push @ctx_args, ($attr => $objargs->{$attr});
62             delete $objargs->{attr};
63             }
64             }
65             $objargs->{config} = OAuthomatic::Config->new(@ctx_args);
66             } else {
67             foreach my $attr (@_CONFIG_ATTRIBUTES) {
68             if(exists $objargs->{$attr}) {
69             OAuthomatic::Error::Generic->throw(
70             ident => "Bad parameter",
71             extra => "You can not specify config and $attr at the same time");
72             }
73             }
74             }
75              
76             if(exists($objargs->{server})) {
77             my $server = $objargs->{server};
78             unless( ref($server) ) {
79             $objargs->{server} = oauthomatic_predefined_for_name($server);
80             }
81             elsif( reftype($server) eq 'HASH') {
82             $objargs->{server} = OAuthomatic::Server->new(%{$objargs->{server}});
83             }
84             }
85              
86             return $objargs;
87             };
88              
89             ###########################################################################
90             # Pluggable behaviours
91             ###########################################################################
92              
93              
94             has 'secret_storage' => (is => 'lazy', does => 'OAuthomatic::SecretStorage');
95              
96             has 'oauth_interaction' => (is => 'lazy', does => 'OAuthomatic::OAuthInteraction');
97              
98             has 'user_interaction' => (is => 'lazy', does => 'OAuthomatic::UserInteraction');
99              
100             # Helper object used and shared by both default interactions
101             has '_micro_web' => (is => 'lazy');
102              
103             sub _build_secret_storage {
104             my $self = shift;
105             require OAuthomatic::SecretStorage::Keyring;
106             print "[OAuthomatic] Constructing default secret_storage\n" if $self->debug;
107             return OAuthomatic::SecretStorage::Keyring->new(
108             config => $self->config, server => $self->server);
109             }
110              
111             sub _build_user_interaction {
112             my $self = shift;
113             require OAuthomatic::UserInteraction::ViaMicroWeb;
114             print "[OAuthomatic] Constructing default user_interaction\n" if $self->debug;
115             return OAuthomatic::UserInteraction::ViaMicroWeb->new(
116             micro_web => $self->_micro_web);
117             }
118              
119             sub _build_oauth_interaction {
120             my $self = shift;
121             require OAuthomatic::OAuthInteraction::ViaMicroWeb;
122             print "[OAuthomatic] Constructing default oauth_interaction\n" if $self->debug;
123             return OAuthomatic::OAuthInteraction::ViaMicroWeb->new(
124             micro_web => $self->_micro_web);
125             }
126              
127             sub _build__micro_web {
128             my $self = shift;
129             require OAuthomatic::Internal::MicroWeb;
130             print "[OAuthomatic] Constructing MicroWeb object\n" if $self->debug;
131             return OAuthomatic::Internal::MicroWeb->new(
132             config => $self->config, server => $self->server);
133             }
134              
135             ###########################################################################
136             # Calling object and basic credentials management
137             ###########################################################################
138              
139              
140             # This is communicating object. It may be in various states
141             # modelled by client_cred and token_cred (both set - it is authorized
142             # and ready for any use, only client_cred - it has defined app tokens
143             # but must be authorized, none set - it is useless)
144             has '_caller' => (is => 'lazy', isa => 'OAuthomatic::Caller',
145             handles => [
146             'client_cred', 'token_cred',
147             ]);
148              
149             sub _build__caller {
150             my ($self) = @_;
151              
152             my $restored_client_cred = $self->secret_storage->get_client_cred();
153             if($restored_client_cred) {
154             print "[OAuthomatic] Loaded saved client (app) tokens. Key: ",
155             $restored_client_cred->key, "\n" if $self->debug;
156             }
157              
158             my $restored_token_cred = $self->secret_storage->get_token_cred();
159             if($restored_token_cred) {
160             print "[OAuthomatic] Loaded saved access tokens. Token: ",
161             $restored_token_cred->token, "\n" if $self->debug;
162             }
163              
164             my $caller = OAuthomatic::Caller->new(
165             config => $self->config,
166             server => $self->server,
167             client_cred => $restored_client_cred,
168             token_cred => $restored_token_cred);
169              
170             return $caller;
171             }
172              
173             # Updates client_cred both in-memory and in storage
174             sub _update_client_cred {
175             my ($self, $new_cred) = @_;
176             return if OAuthomatic::Types::ClientCred->equal($new_cred, $self->client_cred);
177              
178             if($new_cred) {
179             $self->secret_storage->save_client_cred($new_cred);
180             print "[OAuthomatic] Saved client credentials for future. Key: ", $new_cred->key, "\n" if $self->debug;
181             } else {
182             $self->secret_storage->clear_client_cred;
183             print "[OAuthomatic] Dropped saved client credentials\n" if $self->debug;
184             }
185              
186             $self->client_cred($new_cred);
187              
188             # Changed client means access is no longer valid
189             $self->_update_token_cred(undef);
190             }
191              
192             # Updates token_cred both in-memory and in storage. $force param ignores identity check
193             # (to be used if we know we did incomplete update)
194             sub _update_token_cred {
195             my ($self, $new_cred, $force) = @_;
196             return if !$force && OAuthomatic::Types::TokenCred->equal($new_cred, $self->token_cred);
197              
198             if($new_cred) {
199             $self->secret_storage->save_token_cred($new_cred);
200             print "[OAuthomatic] Saved access credentials for future. Token: ", $new_cred->token, "\n" if $self->debug;
201             } else {
202             $self->secret_storage->clear_token_cred;
203             print "[OAuthomatic] Dropped saved access credentials\n" if $self->debug;
204             }
205              
206             $self->token_cred($new_cred);
207             }
208              
209              
210             sub erase_client_cred {
211             my $self = shift;
212              
213             $self->_update_client_cred(undef);
214             }
215              
216              
217             sub erase_token_cred {
218             my $self = shift;
219             $self->_update_token_cred(undef);
220             }
221              
222             ###########################################################################
223             # Actual OAuth setup
224             ###########################################################################
225              
226             # Those are guards to keep track of supporting objects (mostly
227             # in-process web) activity (we may initiate supporting objects at
228             # various moments but close them after we know we are authorized)
229             has '_user_interaction_guard' => (
230             is=>'lazy', builder => sub {
231             return OAuthomatic::Internal::UsageGuard->new(obj => $_[0]->user_interaction);
232             });
233             has '_oauth_interaction_guard' => (
234             is=>'lazy', builder => sub {
235             return OAuthomatic::Internal::UsageGuard->new(obj => $_[0]->oauth_interaction);
236             });
237              
238             # Ensures app tokens are known
239             sub _ensure_client_cred_known {
240             my $self = shift;
241              
242             return if $self->_caller->client_cred;
243              
244             print "[OAuthomatic] Application tokens not available, prompting user\n" if $self->debug;
245              
246             $self->_user_interaction_guard->prepare;
247              
248             my $client_cred = $self->user_interaction->prompt_client_credentials()
249             or OAuthomatic::Error::Generic->throw(
250             ident => "Client credentials missing",
251             extra => "Can't proceed without client credentials. Restart app and supply them.");
252              
253             # We save them straight away, in memory to use, in storage to keep them in case of crash
254             # or Ctrl-C (later we will clear them if they turn out wrong).
255             $self->_update_client_cred($client_cred);
256             }
257              
258             # Ensures access tokens are known
259             sub _ensure_token_cred_known {
260             my $self = shift;
261             return if $self->_caller->token_cred;
262              
263             # To proceed we must have client credentials
264             $self->_ensure_client_cred_known;
265              
266             my $site_name = $self->site_name;
267             my $oauth_interaction = $self->oauth_interaction;
268             my $user_interaction = $self->user_interaction;
269              
270             print "[OAuthomatic] Application is not authorized to $site_name, initiating access-granting sequence\n" if $self->debug;
271              
272             $self->_oauth_interaction_guard->prepare;
273             $self->_user_interaction_guard->prepare;
274              
275             my $temporary_cred;
276             # We loop to retry in case entered app tokens turn out wrong
277             while(! $temporary_cred) {
278             $self->_ensure_client_cred_known; # Get new app keys if old were dropped
279             print "[OAuthomatic] Constructing authorization url\n" if $self->debug;
280             try {
281             $temporary_cred = $self->_caller->create_authorization_url(
282             $oauth_interaction->callback_url);
283             } catch {
284             my $error = $_;
285             if($error->isa("OAuthomatic::Error::HTTPFailure")) {
286             if($error->is_new_client_key_required) {
287             print STDERR $error, "\n";
288             print "\n\nReceived error suggests wrong client key.\nDropping it and retrying initialization.\n\n";
289             $self->erase_client_cred;
290             } else {
291             $error->throw;
292             }
293             } elsif($error->isa("OAuthomatic::Error")) {
294             $error->throw;
295             } else {
296             OAuthomatic::Error::Generic->throw(
297             ident => "Unknown error during authorization",
298             extra => $error);
299             }
300             };
301             }
302              
303             print "[OAuthomatic] Leading user to authorization page\n" if $self->debug;
304             $user_interaction->visit_oauth_authorize_page($temporary_cred->authorize_page);
305              
306             # Wait for post-auth redirect
307             my $verifier_cred = $oauth_interaction->wait_for_oauth_grant;
308              
309             print "[OAuthomatic] Got authorization (verification for token: " . $verifier_cred->token . "), requesting access token\n" if $self->debug;
310              
311             my $token_cred = $self->_caller->create_token_cred(
312             $temporary_cred, $verifier_cred);
313              
314             print "[OAuthomatic] Got access token: " . $token_cred->token, "\n" if $self->debug;
315              
316             # Now save those values
317             $self->_update_token_cred($token_cred, 'force');
318              
319             # Close supporting objects if they were started
320             $self->_user_interaction_guard->finish;
321             $self->_oauth_interaction_guard->finish;
322             }
323              
324              
325             sub ensure_authorized {
326             my ($self) = @_;
327             $self->_ensure_client_cred_known;
328             $self->_ensure_token_cred_known;
329             return;
330             }
331              
332             # FIXME: invalidate saved tokens when they are cancelled on the website
333              
334             ######################################################################
335             # Making requests
336             ######################################################################
337              
338              
339             sub execute_request {
340             my $self = shift;
341             $self->ensure_authorized;
342             my @args = @_;
343             my $reply;
344              
345             # Loop to retry on some failures
346             while(1) {
347             try {
348             $reply = $self->_caller->execute_oauth_request(@args);
349             } catch {
350             my $error = $_;
351             if($error->isa("OAuthomatic::Error::HTTPFailure")) {
352             if($error->is_new_client_key_required) {
353             print STDERR $error, "\n";
354             print "\n\nReceived error suggests wrong client key.\nDropping it to enforce re-initialization.\n\n";
355             $self->erase_client_cred;
356             # Will redo loop
357             } elsif($error->is_new_token_required) {
358             print STDERR $error, "\n";
359             print "\n\nReceived error suggests wrong token.\nDropping it to enforce re-initialization.\n\n";
360             $self->erase_token_cred;
361             # will redo loop
362             } else {
363             $error->throw;
364             }
365             } elsif($error->isa("OAuthomatic::Error")) {
366             $error->throw;
367             } else {
368             OAuthomatic::Error::Generic->throw(
369             ident => "Unknown error during execution",
370             extra => $error);
371             }
372             };
373             last if $reply;
374             };
375             return $reply;
376             }
377              
378              
379             sub build_request {
380             my $self = shift;
381             $self->ensure_authorized;
382             return $self->_caller->build_oauth_request(@_);
383             }
384              
385              
386             sub get {
387             my ($self, $url, $url_args) = @_;
388             my $r = $self->execute_request(
389             method => "GET", url => $url, url_args => $url_args);
390             return $r->decoded_content;
391             }
392              
393              
394             sub get_xml {
395             my ($self, $url, $url_args) = @_;
396             my $r = $self->execute_request(
397             method => "GET", url => $url, url_args => $url_args,
398             content_type => "application/xml; charset=utf-8");
399             return $r->decoded_content;
400             }
401              
402              
403             sub get_json {
404             my ($self, $url, $url_args) = @_;
405              
406             my $r = $self->execute_request(
407             method => "GET", url => $url, url_args => $url_args);
408              
409             return parse_httpmsg_json($r, 'force'); # FIXME: or error on content-type mismatch?
410             }
411              
412              
413             sub post {
414             my $self = shift;
415             my $url = shift;
416             my @args = (method => "POST", url => $url);
417             if(@_ > 1) {
418             push @args, (url_args => shift);
419             }
420             my $body = shift;
421             if(reftype($body) eq 'HASH') {
422             push @args, (body_form => $body);
423             } else {
424             push @args, (body => $body);
425             }
426              
427             my $r = $self->execute_request(@args);
428             return $r->decoded_content;
429             }
430              
431              
432             sub post_xml {
433             my $self = shift;
434             my $url = shift;
435             my @args = (method => "POST",
436             url => $url,
437             content_type => 'application/xml; charset=utf-8');
438             if(@_ > 1) {
439             push @args, (url_args => shift);
440             }
441             my $body = shift;
442             # FIXME: encode if unicode?
443             push @args, (body => $body);
444              
445             my $r = $self->execute_request(@args);
446             return $r->decoded_content;
447             }
448              
449              
450             sub post_json {
451             my $self = shift;
452             my $url = shift;
453             my @args = (method => "POST",
454             url => $url,
455             content_type => 'application/json; charset=utf-8');
456             if(@_ > 1) {
457             push @args, (url_args => shift);
458             }
459             push @args, (body => serialize_json(shift));
460              
461             my $r = $self->execute_request(@args);
462              
463             return parse_httpmsg_json($r);
464             }
465              
466              
467             sub put {
468             my $self = shift;
469             my $url = shift;
470             my @args = (method => "PUT", url => $url);
471             if(@_ > 1) {
472             push @args, (url_args => shift);
473             }
474             my $body = shift;
475             if(reftype($body) eq 'HASH') {
476             push @args, (body_form => $body);
477             } else {
478             push @args, (body => $body);
479             }
480              
481             my $r = $self->execute_request(@args);
482             return $r->decoded_content;
483             }
484              
485              
486             sub put_xml {
487             my $self = shift;
488             my $url = shift;
489             my @args = (method => "PUT",
490             url => $url,
491             content_type => 'application/xml; charset=utf-8');
492             if(@_ > 1) {
493             push @args, (url_args => shift);
494             }
495             my $body = shift;
496             push @args, (body => $body);
497              
498             my $r = $self->execute_request(@args);
499             return $r->decoded_content;
500             }
501              
502              
503             sub put_json {
504             my $self = shift;
505             my $url = shift;
506             my @args = (method => "PUT",
507             url => $url,
508             content_type => 'application/json; charset=utf-8');
509             if(@_ > 1) {
510             push @args, (url_args => shift);
511             }
512             push @args, (body => serialize_json(shift));
513              
514             my $r = $self->execute_request(@args);
515              
516             return parse_httpmsg_json($r);
517             }
518              
519              
520             sub delete_ {
521             my ($self, $url, $url_args) = @_;
522             my $r = $self->execute_request(
523             method => "DELETE", url => $url, url_args => $url_args);
524             return $r->decoded_content;
525             }
526              
527              
528             # FIXME: base url prepended to urls not starting with http?
529              
530             1;
531              
532             __END__
533              
534             =pod
535              
536             =encoding UTF-8
537              
538             =head1 NAME
539              
540             OAuthomatic - automate setup of access to OAuth-secured resources. Intended especially for use in console scripts, ad hoc applications etc.
541              
542             =head1 VERSION
543              
544             version 0.01
545              
546             =head1 SYNOPSIS
547              
548             Construct the object:
549              
550             my $oauthomatic = OAuthomatic->new(
551             app_name => "News trend parser",
552             password_group => "OAuth tokens (personal)",
553             server => OAuthomatic::Server->new(
554             # OAuth protocol URLs, formally used in the protocol
555             oauth_temporary_url => 'https://some.site/api/oauth/request_token',
556             oauth_authorize_page => 'https://some.site/api/oauth/authorize',
557             oauth_token_url => 'https://some.site/api/oauth/access_token',
558             # Extra info about remote site, not required (but may make users happier)
559             site_name => "SomeSite.com",
560             site_client_creation_page => "https://some.site.com/settings/oauth_apps",
561             site_client_creation_desc => "SomeSite applications page",
562             site_client_creation_help => <<"HELP",
563             Click Create App button and fill the form. Use AppToken as client key
564             and AppSecret as client secret. For application created in the past,
565             click application name and visit Tokens tab.
566             HELP
567             );
568              
569             and profit:
570              
571             my $info = $oauthomatic->get_json($PROTECTED_URL, par1=>'v1', par2=>'v2');
572              
573             On first run user (maybe just you) will be led through OAuth
574             initialization sequence, but the script need not care.
575              
576             =head1 DESCRIPTION
577              
578             B<WARNING:> I<This is early release. Things may change (although I won't
579             change crucial APIs without good reason).>
580              
581             Main purpose of this module: make it easy to start scripting around
582             some OAuth-controlled site (at the moment, OAuth 1.0a is
583             supported). The user needs only to check site docs for appropriate
584             URLs, construct OAuthomatic object, and go.
585              
586             I wrote this module as I always struggled with using OAuth-secured
587             APIs from perl. Modules I found on CPAN were mostly low-level,
588             not-too-well documented, and - worst of all - required my scripts to
589             handle whole „get keys, acquire permissions, save tokens” sequence.
590              
591             OAuthomatic is very opinionated. It shows instructions in English. It
592             uses L<Passwd::Keyring::Auto> to save (and restore) sensitive data. It
593             assumes application keys are to be provided by the user on first run
594             (not distributed with the script). It spawns web browser (and
595             temporary in-process webserver to back it). It provides a few HTML
596             pages and they are black-on-white, 14pt font, without pictures.
597              
598             Thanks to all those assumptions it usually just works, letting the
599             script author to think about job at hand instead of thinking about
600             authorization. And, once script grows to application, all those
601             opinionated parts can be tweaked or substituted where necessary.
602              
603             =head1 PARAMETERS
604              
605             =head2 server
606              
607             Server-related parameters (in particular, all crucial URLs), usually
608             found in appropriate server developer docs.
609              
610             There are three ways to specify this parameter
611              
612             =over 4
613              
614             =item *
615              
616             by providing L<OAuthomatic::Server> object instance. For example:
617              
618             OAuthomatic->new(
619             # ... other params
620             server => OAuthomatic::Server->new(
621             oauth_temporary_url => 'https://api.linkedin.com/uas/oauth/requestToken',
622             oauth_authorize_page => 'https://api.linkedin.com/uas/oauth/authenticate',
623             oauth_token_url => 'https://api.linkedin.com/uas/oauth/accessToken',
624             # ...
625             ));
626              
627             See L<OAuthomatic::Server> for detailed description of all parameters.
628              
629             =item *
630              
631             by providing hash reference of parameters. This is equivalent to
632             example above, but about 20 characters shorter:
633              
634             OAuthomatic->new(
635             # ... other params
636             server => {
637             oauth_temporary_url => 'https://api.linkedin.com/uas/oauth/requestToken',
638             oauth_authorize_page => 'https://api.linkedin.com/uas/oauth/authenticate',
639             oauth_token_url => 'https://api.linkedin.com/uas/oauth/accessToken',
640             # ...
641             });
642              
643             =item *
644              
645             by providing name of predefined server. As there exists L<OAuthomatic::ServerDef::LinkedIn> module:
646              
647             OAuthomatic->new(
648             # ... other params
649             server => 'LinkedIn',
650             );
651              
652             See L<OAuthomatic::ServerDef> for more details about predefined servers.
653              
654             =back
655              
656             =head2 app_name
657              
658             Symbolic application name. Used in various prompts. Set to something
659             script users will recognize (script name, application window name etc).
660              
661             Examples: C<build_publisher.pl>, C<XyZ sync scripts>.
662              
663             =head2 password_group
664              
665             Password group/folder used to distinguish saved tokens (a few
666             scripts/apps will share the same tokens if they refer to the same
667             password_group). Ignored if you provide your own L</secret_storage>.
668              
669             Default value: C<OAuthomatic tokens> (remember to change if you have
670             scripts working on few different accounts of the same website).
671              
672             =head2 browser
673              
674             Command used to spawn the web browser.
675              
676             Default value: best guess (using L<Browser::Open>).
677              
678             Set to empty string to avoid spawning browser at all and show
679             instructions (I<Open web browser on https://....>) on the console
680             instead.
681              
682             =head2 html_dir
683              
684             Directory containing HTML templates and related resources for pages
685             generated by OAuthomatic (post-authorization page, application tokens
686             prompt and confirmation).
687              
688             To modify their look and feel, copy C<oauthomatic_html> directory from
689             OAuthomatic distribution somewhere, edit to your taste and provide
690             resulting directory as C<html_dir>.
691              
692             By default, files distributed with OAuthomatic are used.
693              
694             =head2 debug
695              
696             Make object print various info to STDERR. Useful while diagnosing
697             problems.
698              
699             =head1 ADDITIONAL PARAMETERS
700              
701             =head2 config
702              
703             Object gathering all parameters except server. Usually constructed
704             under the hood, but may be useful if you need those params for sth else
705             (especially, if you customize object behaviour). For example:
706              
707             my $server = OAuthomatic::Server->new(...);
708             my $config = OAuthomatic::Config->new(
709             app_name => ...,
710             password_group => ...,
711             ... and the rest ...);
712             my $oauthomatic = OAuthomatic->new(
713             server => $server,
714             config => $config, # instead of normal params
715             user_interaction => OAuthomatic::UserInteraction::ConsolePrompts->new(
716             config => $config, server => $server));
717              
718             =head2 secret_storage
719              
720             Pluggable behaviour: modify the method used to persistently save and
721             restore various OAuth tokens. By default
722             L<OAuthomatic::SecretStorage::Keyring> (which uses
723             L<Passwd::Keyring::Auto> storage) is used, but any object implementing
724             L<OAuthomatic::SecretStorage> role can be substituted instead.
725              
726             =head2 oauth_interaction
727              
728             Pluggable behaviour: modify the way application uses to capture return
729             redirect after OAuth access is granted. By default temporary web
730             server is started on local address (it suffices to handle redirect to
731             localhost) and used to capture traffic, but any object implementing
732             L<OAuthomatic::OAuthInteraction> role can be substituted instead.
733              
734             In case default is used, look and feel of the final page can be
735             modified using L</html_dir>.
736              
737             =head2 user_interaction
738              
739             Pluggable behaviour: modify the way application uses to prompt user
740             for application keys. By default form is shown in the browser, but any object
741             implementing L<OAuthomatic::UserInteraction> role can be substituted instead.
742              
743             Note: you can use L<OAuthomatic::UserInteraction::ConsolePrompts>
744             to be prompted in the console.
745              
746             In case default is used, look and feel of the pages can be
747             modified using L</html_dir>.
748              
749             =head1 METHODS
750              
751             =head2 erase_client_cred
752              
753             $oa->erase_client_cred();
754              
755             Drops current client (app) credentials both from the object and, possibly, from storage.
756              
757             Use if you detect error which prove they are wrong, or if you want to forget them for privacy/security reasons.
758              
759             =head2 erase_token_cred
760              
761             $oa->erase_token_cred();
762              
763             Drops access (app) credentials both from the object and, possibly, from storage.
764              
765             Use if you detect error which prove they are wrong.
766              
767             =head2 ensure_authorized
768              
769             $oa->ensure_authorized();
770              
771             Ensure object is ready to make calls.
772              
773             If initialization sequence happened in the past and appropriate tokens
774             are available, this method restores them.
775              
776             If not, it performs all the work required to setup OAuth access to
777             given website: asks user for application keys (or loads them if
778             already known), leads the user through application authorization
779             sequence, preserve acquired tokens for future runs.
780              
781             Having done all that, it leaves object ready to make OAuth-signed
782             calls (actual signatures are calculated using L<Net::OAuth>.
783              
784             Calling this method is not necessary - it will be called automatically
785             before first request is executed, if not done earlier.
786              
787             =head2 execute_request
788              
789             $oa->execute_request(
790             method => $method, url => $url, url_args => $args,
791             body => $body,
792             content_type => $content_type)
793              
794             $oa->execute_request(
795             method => $method, url => $url, url_args => $args,
796             body_form => $body_form,
797             content_type => $content_type)
798              
799             Make OAuth-signed request to given url. Lowest level method, see below
800             for methods which add additional glue or require less typing.
801              
802             Parameters:
803              
804             =over 4
805              
806             =item method
807              
808             One of C<'GET'>, C<'POST'>, C<'PUT'>, C<'DELETE'>.
809              
810             =item url
811              
812             Actual URL to call (C<'http://some.site.com/api/...'>)
813              
814             =item url_args (optional)
815              
816             Additional arguments to escape and add to the URL. This is simply shortcut,
817             three calls below are equivalent:
818              
819             $c->execute_oauth_request(method => "GET",
820             url => "http://some.where/api?x=1&y=2&z=a+b");
821              
822             $c->execute_oauth_request(method => "GET",
823             url => "http://some.where/api",
824             url_args => {x => 1, y => 2, z => 'a b'});
825              
826             $c->execute_oauth_request(method => "GET",
827             url => "http://some.where/api?x=1",
828             url_args => {y => 2, z => 'a b'});
829              
830             =item body_form OR body
831              
832             Exactly one of those must be specified for POST and PUT (none for GET or DELETE).
833              
834             Specifying C<body_form> means, that we are creating www-urlencoded
835             form. Specified values will be rendered appropriately and whole message
836             will get proper content type. Example:
837              
838             $c->execute_oauth_request(method => "POST",
839             url => "http://some.where/api",
840             body_form => {par1 => 'abc', par2 => 'd f'});
841              
842             Note that this is not just a shortcut for setting body to already
843             serialized form. Case of urlencoded form is treated in a special way
844             by OAuth (those values impact OAuth signature). To avoid signature
845             verification errors, OAuthomatic will reject such attempts:
846              
847             # WRONG AND WILL FAIL. Use body_form if you post form.
848             $c->execute_oauth_request(method => "POST",
849             url => "http://some.where/api",
850             body => 'par1=abc&par2=d+f',
851             content_type => 'application/x-www-form-urlencoded');
852              
853             Specifying C<body> means, that we post non-form body (for example
854             JSON, XML or even binary data). Example:
855              
856             $c->execute_oauth_request(method => "POST",
857             url => "http://some.where/api",
858             body => "<product><item-no>3434</item-no><price>334.22</price></product>",
859             content_type => "application/xml; charset=utf-8");
860              
861             Value of body can be either binary string (which will be posted as-is), or
862             perl unicode string (which will be encoded according to the content type, what by
863             default means utf-8).
864              
865             Such content is not covered by OAuth signature, so less secure (at
866             least if it is posted over non-SSL connection).
867              
868             For longer bodies, references are supported:
869              
870             $c->execute_oauth_request(method => "POST",
871             url => "http://some.where/api",
872             body => \$body_string,
873             content_type => "application/xml; charset=utf-8");
874              
875             =item content_type
876              
877             Used to set content type of the request. If missing, it is set to
878             C<text/plain; charset=utf-8> if C<body> param is specified and to
879             C<application/x-www-form-urlencoded; charset=utf-8> if C<body_form>
880             param is specified.
881              
882             Note that module author does not test behaviour on encodings different
883             than utf-8 (although they may work).
884              
885             =back
886              
887             Returns L<HTTP::Response> object.
888              
889             Throws structural exception on HTTP (40x, 5xx) and technical (like
890             network) failures.
891              
892             Example:
893              
894             my $result = $oauthomatic->make_request(
895             method => "GET", url => "https://some.api/get/things",
896             url_args => {name => "Thingy", count => 4});
897             # $result is HTTP::Response object and we know request succeeded
898             # on HTTP level
899              
900             =head2 build_request
901              
902             $oa->build_request(method => $method, url => $url, url_args => $args,
903             body_form => $body_form, body => $body,
904             content_type => $content_type)
905              
906             Build appropriate HTTP::Request, ready to be executed, with proper
907             headers and signature, but do not execute it. Useful if you prefer
908             to use your own HTTP client.
909              
910             See L<OAuthomatic::Caller/build_oauth_request> for the meaning of
911             parameters.
912              
913             Note: if you are executing requests yourself, consider detecting cases
914             of wrong client credentials, obsolete token credentials etc, and
915             calling or L</erase_client_cred> or L</erase_token_cred>.
916              
917             FIXME: description how to check.
918              
919             =head2 get
920              
921             my $reply = $ua->get($url, { url => 'args', ...);
922              
923             Shortcut. Make OAuth-signed GET request, ensure request succeeded and
924             return it's body without parsing it (but decoding it from transport encoding).
925              
926             =head2 get_xml
927              
928             my $reply = $ua->get($url, { url => 'args', ...);
929              
930             Shortcut. Make OAuth-signed GET request, ensure request succeeded and
931             return it's body. Body is not parsed, it remains to be done in the outer program (there are
932             so many XML parsers I did not want to vote for one).
933              
934             This is almost equivalent to L</get> (except it sets request content
935             type to C<application/xml>), mainly used to clearly signal intent.
936              
937             =head2 get_json
938              
939             my $reply = $oa->get_json($url, {url=>args, ...});
940             # $reply is hash or array ref
941              
942             Shortcut. Make OAuth-signed GET request, ensure it succeeded, parse result as JSON,
943             return resulting structure.
944              
945             Example:
946              
947             my $result = $oauthomatic->get_json(
948             "https://some.api/things", {filter => "Thingy", count => 4});
949             # Grabs https://some.api/things?filter=Thingy&count=4 and parses as JSON
950             # $result is hash or array ref
951              
952             =head2 post
953              
954             my $reply = $ua->post($url, { body=>args, ... });
955             my $reply = $ua->post($url, { url=>args, ...}, { body=>args, ... });
956             my $reply = $ua->post($url, "body content");
957             my $reply = $ua->post($url, { url=>args, ...}, "body content");
958             my $reply = $ua->post($url, $ref_to_body_content);
959             my $reply = $ua->post($url, { url=>args, ...}, $ref_to_body_content);
960              
961             Shortcut. Make OAuth-signed POST request, ensure request succeeded and
962             return reply body without parsing it.
963              
964             May take two or three parameters. In two-parameter form it takes URL
965             to POST and body. In three-parameter, it takes URL, additional URL
966             params (to be added to URI), and body.
967              
968             Body may be specified as:
969              
970             =over 4
971              
972             =item *
973              
974             Hash reference, in which case contents of this hash are treated as
975             form fields, urlencoded and whole request is executed as urlencoded
976             POST.
977              
978             =item *
979              
980             Scalar or reference to scalar, in which case it is pasted verbatim as post body.
981              
982             =back
983              
984             Note: use use L</execute_request> for more control on parameters (in
985             particular, content type).
986              
987             =head2 post_xml
988              
989             my $reply = $ua->post($url, "<xml>content</xml>");
990             my $reply = $ua->post($url, { url=>args, ...}, "<xml>content</xml>");
991             my $reply = $ua->post($url, $ref_to_xml_content);
992             my $reply = $ua->post($url, { url=>args, ...}, $ref_to_xml_content);
993              
994             Shortcut. Make OAuth-signed POST request, ensure request succeeded and
995             return reply body without parsing it.
996              
997             May take two or three parameters. In two-parameter form it takes URL
998             to POST and body. In three-parameter, it takes URL, additional URL
999             params (to be added to URI), and body.
1000              
1001             This is very close to L</post> (XML is neither rendered, nor parsed here),
1002             used mostly to set proper content-type and to clearly signal intent in the code.
1003              
1004             =head2 post_json
1005              
1006             my $reply = $oa->post_json($url, { json=>args, ... });
1007             my $reply = $oa->post_json($url, { url=>args, ...}, { json=>args, ... });
1008             my $reply = $oa->post_json($url, "json content");
1009             my $reply = $oa->post_json($url, { url=>args, ...}, "json content");
1010             # $reply is hash or arrayref constructed by parsing output
1011              
1012             Make OAuth-signed POST request. Parameter is formatted as JSON, result
1013             also i parsed as JSON.
1014              
1015             May take two or three parameters. In two-parameter form it takes URL
1016             and JSON body. In three-parameter, it takes URL, additional URL params
1017             (to be added to URI), and JSON body.
1018              
1019             JSON body may be specified as:
1020              
1021             =over 4
1022              
1023             =item *
1024              
1025             Hash or array reference, in which case contents of this reference are serialized to JSON
1026             and then used as request body.
1027              
1028             =item *
1029              
1030             Scalar or reference to scalar, in which case it is treated as already serialized JSON
1031             and posted verbatim as post body.
1032              
1033             =back
1034              
1035             Example:
1036              
1037             my $result = $oauthomatic->post_json(
1038             "https://some.api/things/prettything", {
1039             mode => 'simple',
1040             }, {
1041             name => "Pretty Thingy",
1042             description => "This is very pretty",
1043             tags => ['secret', 'pretty', 'most-important'],
1044             }, count => 4);
1045             # Posts to https://some.api/things/prettything?mode=simple
1046             # the following body (formatting and ordering may be different):
1047             # {
1048             # "name": "Pretty Thingy",
1049             # "description": "This is very pretty",
1050             # "tags": ['secret', 'pretty', 'most-important'],
1051             # }
1052              
1053             =head2 put
1054              
1055             my $reply = $ua->put($url, { body=>args, ... });
1056             my $reply = $ua->put($url, { url=>args, ...}, { body=>args, ... });
1057             my $reply = $ua->put($url, "body content");
1058             my $reply = $ua->put($url, { url=>args, ...}, "body content");
1059             my $reply = $ua->put($url, $ref_to_body_content);
1060             my $reply = $ua->put($url, { url=>args, ...}, $ref_to_body_content);
1061              
1062             Shortcut. Make OAuth-signed PUT request, ensure request succeeded and
1063             return reply body without parsing it.
1064              
1065             May take two or three parameters. In two-parameter form it takes URL
1066             to PUT and body. In three-parameter, it takes URL, additional URL
1067             params (to be added to URI), and body.
1068              
1069             Body may be specified in the same way as in L</post>: as scalar, scalar
1070             reference, or as hash reference which would be urlencoded.
1071              
1072             =head2 put_xml
1073              
1074             my $reply = $ua->put($url, "<xml>content</xml>");
1075             my $reply = $ua->put($url, { url=>args, ...}, "<xml>content</xml>");
1076             my $reply = $ua->put($url, $ref_to_xml_content);
1077             my $reply = $ua->put($url, { url=>args, ...}, $ref_to_xml_content);
1078              
1079             Shortcut. Make OAuth-signed PUT request, ensure request succeeded and
1080             return reply body without parsing it.
1081              
1082             May take two or three parameters. In two-parameter form it takes URL
1083             to PUT and body. In three-parameter, it takes URL, additional URL
1084             params (to be added to URI), and body.
1085              
1086             This is very close to L</put> (XML is neither rendered, nor parsed here),
1087             used mostly to set proper content-type and to clearly signal intent in the code.
1088              
1089             =head2 put_json
1090              
1091             my $reply = $oa->put_json($url, { json=>args, ... });
1092             my $reply = $oa->put_json($url, { url=>args, ...}, { json=>args, ... });
1093             my $reply = $oa->put_json($url, "json content");
1094             my $reply = $oa->put_json($url, { url=>args, ...}, "json content");
1095             # $reply is hash or arrayref constructed by parsing output
1096              
1097             Make OAuth-signed PUT request. Parameter is formatted as JSON, result
1098             also i parsed as JSON.
1099              
1100             May take two or three parameters. In two-parameter form it takes URL
1101             and JSON body. In three-parameter, it takes URL, additional URL params
1102             (to be added to URI), and JSON body.
1103              
1104             JSON body may be specified just as in L</post_json>: as hash or array
1105             reference (to be serialized) or as scalar or scalar reference
1106             (treated as already serialized).
1107              
1108             Example:
1109              
1110             my $result = $oauthomatic->put_json(
1111             "https://some.api/things/prettything", {
1112             mode => 'simple',
1113             }, {
1114             name => "Pretty Thingy",
1115             description => "This is very pretty",
1116             tags => ['secret', 'pretty', 'most-important'],
1117             }, count => 4);
1118             # PUTs to https://some.api/things/prettything?mode=simple
1119             # the following body (formatting and ordering may be different):
1120             # {
1121             # "name": "Pretty Thingy",
1122             # "description": "This is very pretty",
1123             # "tags": ['secret', 'pretty', 'most-important'],
1124             # }
1125              
1126             =head2 delete_
1127              
1128             $oa->delete_($url);
1129             $oa->delete_($url, {url => args, ...});
1130              
1131             Shortcut. Executes C<DELETE> on given URL. Note trailing underscore in the name
1132             (to avoid naming conflict with core perl function).
1133              
1134             Returns reply body content, if any.
1135              
1136             =head1 ATTRIBUTES
1137              
1138             =head2 client_cred
1139              
1140             OAuth application identifiers - client_key and client_secret.
1141             As L<OAuthomatic::Types::ClientCred> object.
1142              
1143             Mostly used internally but can be of use if you need (or prefer) to
1144             use OAuthomatic only for initialization, but make actual calls using
1145             some other means.
1146              
1147             Note that you must call L</ensure_authorized> to bo be sure this object is set.
1148              
1149             =head2 token_cred
1150              
1151             OAuth application identifiers - access_token and access_token_secret.
1152             As L<OAuthomatic::Types::TokenCred> object.
1153              
1154             Mostly used internally but can be of use if you need (or prefer) to
1155             use OAuthomatic only for initialization, but make actual calls using
1156             some other means.
1157              
1158             Note that you must call L</ensure_authorized> to bo be sure this object is set.
1159              
1160             =head1 THANKS
1161              
1162             Keith Grennan, for writing L<Net::OAuth>, which this module uses to
1163             calculate and verify OAuth signatures.
1164              
1165             Simon Wistow, for writing L<Net::OAuth::Simple>, which inspired some
1166             parts of my module.
1167              
1168             E. Hammer-Lahav for well written and understandable RFC 5849.
1169              
1170             =head1 AUTHOR
1171              
1172             Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
1173              
1174             =head1 COPYRIGHT AND LICENSE
1175              
1176             This software is copyright (c) 2015 by Marcin Kasperski.
1177              
1178             This is free software; you can redistribute it and/or modify it under
1179             the same terms as the Perl 5 programming language system itself.
1180              
1181             =cut