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