File Coverage

lib/Net/OAuth2/Profile.pm
Criterion Covered Total %
statement 127 177 71.7
branch 25 52 48.0
condition 21 63 33.3
subroutine 30 42 71.4
pod 16 30 53.3
total 219 364 60.1


line stmt bran cond sub pod time code
1             # Copyrights 2013-2019 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution Net-OAuth2. Meta-POD processed
6             # with OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Net::OAuth2::Profile;
10 4     4   34 use vars '$VERSION';
  4         6  
  4         202  
11             $VERSION = '0.67';
12              
13              
14 4     4   25 use warnings;
  4         6  
  4         121  
15 4     4   19 use strict;
  4         8  
  4         102  
16              
17 4     4   35 use Carp qw(carp croak confess);
  4         24  
  4         286  
18 4     4   1962 use MIME::Base64 qw(encode_base64);
  4         2808  
  4         236  
19 4     4   2029 use LWP::UserAgent ();
  4         113240  
  4         105  
20 4     4   41 use URI ();
  4         8  
  4         94  
21 4     4   497 use JSON::MaybeXS qw/decode_json/;
  4         5761  
  4         324  
22 4     4   33 use Scalar::Util qw/blessed/;
  4         9  
  4         212  
23 4     4   2329 use Encode qw/encode/;
  4         60874  
  4         418  
24              
25 4     4   47 use constant MIME_URLENC => 'application/x-www-form-urlencoded';
  4         15  
  4         9238  
26              
27             # old names still supported:
28             # bearer_token_scheme => token_scheme
29              
30             sub new(@)
31 2     2 1 1436 { my $class = shift;
32 2 50       8 $class ne __PACKAGE__
33             or carp 'you need to create an extension, not base-class '.__PACKAGE__;
34 2         17 (bless {}, $class)->init( {@_} );
35             }
36              
37             # rfc6849 Appendix B, http://www.w3.org/TR/1999/REC-html401-19991224
38             sub _url_enc($)
39 4     4   24 { my $x = encode 'utf8', shift; # make bytes
40 4         99 $x =~ s/([^A-Za-z0-9 ])/sprintf("%%%02x", ord $1)/ge;
  4         24  
41 4         8 $x =~ s/ /+/g;
42 4         12 $x;
43             }
44              
45             sub init($)
46 2     2 0 4 { my ($self, $args) = @_;
47             my $id = $self->{NOP_id} = $args->{client_id}
48 2 50       12 or carp "profile needs id";
49             my $secret = $self->{NOP_secret} = $args->{client_secret}
50 2 50       7 or carp "profile needs secret";
51              
52 2         6 $self->{NOP_id_enc} = _url_enc $id;
53 2         6 $self->{NOP_secret_enc} = _url_enc $secret;
54              
55 2   33     21 $self->{NOP_agent} = $args->{user_agent} || LWP::UserAgent->new;
56             $self->{NOP_scheme} = $args->{token_scheme}
57 2   50     5853 || $args->{bearer_token_scheme} || 'auth-header:Bearer';
58 2         6 $self->{NOP_scope} = $args->{scope};
59 2         5 $self->{NOP_state} = $args->{state};
60 2         5 $self->{NOP_hd} = $args->{hd};
61 2   50     8 $self->{NOP_method} = $args->{access_token_method} || 'POST';
62 2   50     10 $self->{NOP_acc_param} = $args->{access_token_param} || [];
63 2         3 $self->{NOP_init_params} = $args->{init_params};
64 2         5 $self->{NOP_grant_type} = $args->{grant_type};
65             $self->{NOP_show_secret} = exists $args->{secrets_in_params}
66 2 50       7 ? $args->{secrets_in_params} : 1;
67              
68 2         5 my $site = $self->{NOP_site} = $args->{site};
69 2         5 foreach my $c (qw/access_token protected_resource authorize refresh_token/)
70 8   66     60 { my $link = $args->{$c.'_url'} || $args->{$c.'_path'} || "/oauth/$c";
71 8         32 $self->{"NOP_${c}_url"} = $self->site_url($link);
72 8   100     41 $self->{"NOP_${c}_method"} = $args->{$c.'_method'} || 'POST';
73 8   50     42 $self->{"NOP_${c}_param"} = $args->{$c.'_param'} || [];
74             }
75              
76 2         8 $self;
77             }
78              
79             #----------------
80              
81 3     3 1 448 sub id() {shift->{NOP_id}}
82 0     0 0 0 sub id_enc() {shift->{NOP_id_enc}}
83 2     2 1 7 sub secret() {shift->{NOP_secret}}
84 0     0 0 0 sub secret_enc() {shift->{NOP_secret_enc}}
85 1     1 1 664 sub user_agent() {shift->{NOP_agent}}
86 11     11 1 23 sub site() {shift->{NOP_site}}
87 0     0 1 0 sub scope() {shift->{NOP_scope}}
88 1     1 1 4 sub state() {shift->{NOP_state}}
89 1     1 1 3 sub hd() {shift->{NOP_hd}}
90 1     1 1 3 sub grant_type() {shift->{NOP_grant_type}}
91              
92 0     0 1 0 sub bearer_token_scheme() {shift->{NOP_scheme}}
93              
94             #----------------
95              
96             sub request($@)
97 0     0 1 0 { my ($self, $request) = (shift, shift);
98             #print $request->as_string;
99 0         0 my $response = $self->user_agent->request($request, @_);
100             #print $response->as_string;
101             #$response;
102             }
103              
104              
105             sub request_auth(@)
106 0     0 1 0 { my ($self, $token) = (shift, shift);
107 0         0 my $request;
108 0 0       0 if(@_==1) { $request = shift }
  0         0  
109             else
110 0         0 { my ($method, $uri, $header, $content) = @_;
111 0         0 $request = HTTP::Request->new
112             ( $method => $self->site_url($uri)
113             , $header, $content
114             );
115             }
116 0         0 $self->add_token($request, $token, $self->bearer_token_scheme);
117 0         0 $self->request($request);
118             }
119              
120             #--------------------
121              
122             sub site_url($@)
123 10     10 1 24 { my ($self, $path) = (shift, shift);
124 10 100 66     42 my @params = @_==1 && ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  1         4  
125 10         26 my $site = $self->site;
126 10 100       38 my $uri = $site ? URI->new_abs($path, $site) : URI->new($path);
127 10 100       13504 $uri->query_form($uri->query_form, @params) if @params;
128 10         233 $uri;
129             }
130              
131              
132             sub add_token($$$)
133 0     0 1 0 { my ($self, $request, $token, $bearer) = @_;
134 0         0 my $access = $token->access_token;
135              
136 0         0 my ($scheme, $opt) = split ':', $bearer;
137 0         0 $scheme = lc $scheme;
138 0 0       0 if($scheme eq 'auth-header')
    0          
    0          
139             { # Specs suggest using Bearer or OAuth2 for this value, but OAuth
140             # appears to be the de facto accepted value.
141             # Going to use OAuth until there is wide acceptance of something else.
142 0   0     0 my $auth_scheme = $opt || 'OAuth';
143 0         0 $request->headers->header(Authorization => "$auth_scheme $access");
144             }
145             elsif($scheme eq 'uri-query')
146 0   0     0 { my $query_param = $opt || 'oauth_token';
147 0         0 $request->uri->query_form($request->uri->query_form
148             , $query_param => $access);
149             }
150             elsif($scheme eq 'form-body')
151 0 0       0 { $request->headers->content_type eq MIME_URLENC
152             or croak "embedding access token in request body is only valid "
153             . "for 'MIME_URLENC' content type";
154              
155 0   0     0 my $query_param = $opt || 'oauth_token';
156 0         0 my $content = $request->content;
157 0 0 0     0 $request->add_content(($content && length $content ? '&' : '')
158             . uri_escape($query_param).'='.uri_escape($access));
159             }
160             else
161 0         0 { carp "unknown bearer schema $bearer";
162             }
163              
164 0         0 $request;
165             }
166              
167              
168             sub build_request($$$)
169 3     3 1 8 { my ($self, $method, $uri_base, $params) = @_;
170 3 100       16 my %params = ref $params eq 'HASH' ? %$params : @$params;
171              
172 3         5 my $basic;
173              
174             # rfc6749 section "2.3.1. Client Password"
175             # The Auth Header is always supported, but client_id/client_secret as
176             # parameters may be as well. We do the latter when ->new(secrets_in_params)
177             # to support old servers.
178 3 50       9 unless ($self->{NOP_show_secret})
179             {
180 0         0 $basic = encode_base64("$params{client_id}:$params{client_secret}", '');
181 0         0 delete @params{qw/client_id client_secret/};
182             }
183              
184 3         5 my $request;
185              
186 3 100       9 if($method eq 'POST')
    50          
187 1         6 { my $p = URI->new('http:'); # taken from HTTP::Request::Common
188 1         96 $p->query_form(%params);
189              
190 1         84 $request = HTTP::Request->new
191             ( $method => $uri_base
192             , [Content_Type => MIME_URLENC]
193             , $p->query
194             );
195             }
196             elsif($method eq 'GET')
197 2 50 33     14 { my $uri = blessed $uri_base && $uri_base->isa('URI')
198             ? $uri_base->clone : URI->new($uri_base);
199              
200 2         3144 $uri->query_form($uri->query_form, %params);
201 2         269 $request = HTTP::Request->new($method, $uri);
202             }
203             else
204 0         0 { confess "unknown request method $method";
205             }
206              
207 3         345 my $uri = $request->uri;
208 3         25 my $head = $request->headers;
209 3         24 $request->protocol('HTTP/1.1');
210              
211             # 2016-01-15 Instagram does not like the portnumber to appear
212             # my ($host, $port) = ($uri->host, $uri->port);
213             # $host .= ':'.$port if $port != $uri->default_port;
214 3         36 $head->header(Host => $uri->host);
215              
216 3         261 $head->header(Connection => 'Keep-Alive');
217 3 50       116 $head->header(Authorization => "Basic $basic") if $basic;
218              
219 3         10 $request;
220             }
221              
222              
223             sub params_from_response($$)
224 2     2 1 2620 { my ($self, $response, $why) = @_;
225 2         4 my ($error, $content);
226 2 50 33     14 $content = $response->decoded_content || $response->content if $response;
227              
228 2 50       393 if(!$response)
    50          
229 0         0 { $error = 'no response received';
230             }
231             elsif(!$response->is_success)
232 0         0 { $error = 'received error: '.$response->status_line;
233             }
234             else
235             { # application/json is often not correctly configured: is not
236             # (yet) an apache pre-configured extension :(
237 2 100       20 if(my $params = eval {decode_json $content} )
  2         24  
238             { # content is JSON
239 1 50       10 return ref $params eq 'HASH' ? %$params : @$params;
240             }
241              
242             # otherwise form-encoded parameters (I hope)
243 1         5 my $uri = URI->new;
244 1         81 $uri->query($content);
245 1         74 my @res_params = $uri->query_form;
246 1 50       94 return @res_params if @res_params;
247              
248 0         0 $error = "cannot read parameters from response";
249             }
250            
251 0 0       0 substr($content, 200) = '...' if length $content > 200;
252 0         0 croak "failed oauth call $why: $error\n$content\n";
253             }
254              
255             sub authorize_method() {panic} # user must use autorize url
256 0     0 0 0 sub access_token_method() {shift->{NOP_access_token_method} }
257 1     1 0 1815 sub refresh_token_method() {shift->{NOP_refresh_token_method} }
258 0     0 0 0 sub protected_resource_method() {shift->{NOP_protected_resource_method} }
259              
260 1     1 0 3 sub authorize_url() {shift->{NOP_authorize_url}}
261 1     1 0 1428 sub access_token_url() {shift->{NOP_access_token_url}}
262 1     1 0 9 sub refresh_token_url() {shift->{NOP_refresh_token_url}}
263 0     0 0 0 sub protected_resource_url() {shift->{NOP_protected_resource_url}}
264              
265             sub authorize_params(%)
266 1     1 0 1 { my $self = shift;
267 1         1 my %params = (@{$self->{NOP_authorize_param}}, @_);
  1         4  
268 1   33     3 $params{scope} ||= $self->scope;
269 1   33     7 $params{state} ||= $self->state;
270 1   33     22 $params{hd} ||= $self->hd;
271 1   33     6 $params{client_id} ||= $self->id;
272 1         2 \%params;
273             }
274              
275             sub access_token_params(%)
276 1     1 0 2 { my $self = shift;
277 1         1 my %params = (@{$self->{NOP_access_token_param}}, @_);
  1         7  
278 1   50     6 $params{code} ||= '';
279 1   33     4 $params{client_id} ||= $self->id;
280 1   33     4 $params{client_secret} ||= $self->secret;
281 1   33     6 $params{grant_type} ||= $self->grant_type;
282 1         3 \%params;
283             }
284              
285             sub refresh_token_params(%)
286 0     0 0   { my $self = shift;
287 0           my %params = (@{$self->{NOP_refresh_token_param}}, @_);
  0            
288 0   0       $params{client_id} ||= $self->id;
289 0   0       $params{client_secret} ||= $self->secret;
290 0           \%params;
291             }
292              
293             sub protected_resource_params(%)
294 0     0 0   { my $self = shift;
295 0           my %params = (@{$self->{NOP_protected_resource_param}}, @_);
  0            
296 0           \%params;
297             }
298              
299             1;