File Coverage

blib/lib/Dancer2/Plugin/Auth/OAuth/Provider.pm
Criterion Covered Total %
statement 117 140 83.5
branch 24 38 63.1
condition 17 38 44.7
subroutine 23 25 92.0
pod 0 9 0.0
total 181 250 72.4


line stmt bran cond sub pod time code
1              
2             use strict;
3 1     1   426 use warnings;
  1         2  
  1         25  
4 1     1   5  
  1         2  
  1         20  
5             use DateTime;
6 1     1   736 use Digest::MD5 qw(md5_hex);
  1         445858  
  1         54  
7 1     1   11 use HTTP::Request::Common;
  1         2  
  1         80  
8 1     1   7 use JSON::MaybeXS;
  1         1  
  1         63  
9 1     1   7 use LWP::UserAgent;
  1         2  
  1         50  
10 1     1   7 use Net::OAuth;
  1         2  
  1         26  
11 1     1   668 use Scalar::Util qw( blessed );
  1         681  
  1         38  
12 1     1   7 use URI::Query;
  1         2  
  1         47  
13 1     1   455 use Hash::Merge;
  1         1942  
  1         74  
14 1     1   645  
  1         3982  
  1         1661  
15             my ($class, $settings, $dsl) = @_;
16             my $self = bless {
17 12     12 0 212737 settings => $settings,
18 12         56 }, $class;
19              
20             my $merge = Hash::Merge->new('LEFT_PRECEDENT');
21             my $config = $merge->merge($self->{settings}{providers}{$self->_provider}||{}, $self->config);
22 12         127  
23 12   100     1578 $self->{settings}{providers}{$self->_provider} = $config;
24              
25 12         1279 my $protocol_version = $self->provider_settings->{version} || 2;
26             $self->{protocol_version} = $protocol_version;
27 12   100     95  
28 12         34 $self->{ua} ||= LWP::UserAgent->new();
29             $self->{ua}->env_proxy; # c'mon make this default behaviour already!
30 12   33     152  
31 12         3416 $self->{dsl} = $dsl;
32              
33 12         6531 return $self;
34             }
35 12         637  
36             # Provider:: module should override this if needed/wanted
37             my $self = shift;
38              
39             return 1;
40 0     0 0 0 }
41              
42 0         0 return (split '::', blessed($_[0]))[-1];
43             }
44              
45             my ($self, $obj) = @_;
46 105     105   946  
47             while( my ($k, $v) = each %{$obj} ) {
48             $obj->{$k} = $self->_stringify_json_booleans( $v )
49             if( ref($v) && ref($v) eq 'HASH' );
50 7     7   861 $obj->{$k} = "$v"
51             if( blessed( $v ) );
52 7         15 }
  75         173  
53 68 100 100     138  
54             return $obj;
55 68 100       173 }
56              
57             my $self = shift;
58              
59 7         16 return (
60             consumer_key => $self->provider_settings->{tokens}{consumer_key},
61             consumer_secret => $self->provider_settings->{tokens}{consumer_secret},
62             signature_method => $self->provider_settings->{signature_method} || 'HMAC-SHA1',
63 3     3   11437 timestamp => DateTime->now->epoch,
64             nonce => md5_hex(time),
65             );
66             }
67              
68 3   50     12 my $self = shift;
69              
70             # construct the callback url
71             return sprintf "%s%s/%s/callback",
72             $self->settings->{base},
73             $self->settings->{prefix},
74             lc($self->_provider)
75 12     12   24 ;
76             }
77              
78              
79             return $_[0]->{ua};
80             }
81 12         29  
82             return $_[0]->{protocol_version};
83             }
84              
85             return $_[0]->{settings};
86             }
87 3     3 0 19  
88             my $self = shift;
89             return $self->{settings}{providers}{$self->_provider};
90             }
91 13     13 0 62  
92             my ( $self, $base ) = @_;
93              
94             $self->settings->{base} ||= $base;
95 36     36 0 132  
96             if( $self->protocol_version < 2 ) {
97             # oAuth 1.0 / 1.0a
98             $Net::OAuth::PROTOCOL_VERSION = $self->protocol_version;
99 63     63 0 1045 my $request = Net::OAuth->request("request token")->new(
100 63         149 $self->_default_args_v1,
101             request_method => 'POST',
102             request_url => $self->provider_settings->{urls}{request_token_url},
103             callback => $self->_callback_url,
104 6     6 0 2055 );
105             $request->sign;
106 6   66     35  
107             my $res = $self->ua->request(POST $request->to_url);
108 6 100       42 if ($res->is_success) {
109             my $response = Net::OAuth->response('request token')->from_post_body($res->content);
110 1         4 my $uri = URI->new( $self->provider_settings->{urls}{authorize_url} );
111             $uri->query_form( oauth_callback => $self->_callback_url, oauth_token => $response->token );
112              
113             return $uri->as_string;
114             } else {
115 1         10 return $self->settings->{error_url} || '/';
116             }
117 1         878 } else {
118             # oAuth 2 and up
119 1         3849 my $uri = URI->new( $self->provider_settings->{urls}{authorize_url} );
120 1 50       1506 my %query = (
121 1         19 client_id => $self->provider_settings->{tokens}{client_id},
122 1         2707 redirect_uri => $self->_callback_url,
123 1         68 %{ $self->provider_settings->{query_params}{authorize} || {} },
124             );
125 1         153 $uri->query_form( %query );
126             return $uri->as_string;
127 0   0     0 }
128             }
129             my ($self, $request, $session) = @_;
130              
131 5         21 my $provider = lc $self->_provider;
132             my $session_data = $session->read('oauth') || {};
133              
134             if( $self->protocol_version < 2 || !defined $session_data->{$provider} || !defined $session_data->{$provider}{refresh_token}) {
135 5 100       296 if (defined defined $session_data->{$provider}) {
  5         14  
136             $session_data->{$provider} = { };
137 5         30 $session->write('oauth', $session_data);
138 5         694 }
139             $self->{dsl}->app->log(debug => "Auth::OAuth::Provider::".$self->_provider.": Failed to action call to token refresh, refresh_token is not present in session data.");
140             return undef;
141             }
142 0     0 0 0 my $retval = _get_token(@_, { "refresh_token" => $session_data->{$provider}{refresh_token}, grant_type => 'refresh_token' });
143             if (!$retval) {
144 0         0 if (defined defined $session_data->{$provider}) {
145 0   0     0 $session_data->{$provider} = {};
146             $session->write('oauth', $session_data);
147 0 0 0     0 }
      0        
148 0 0       0 }
149 0         0 return $retval;
150 0         0 }
151             my ($self, $request, $session) = @_;
152 0         0 _get_token(@_, { "code" => $request->param('code'), grant_type => 'authorization_code' });
153 0         0 }
154             my ($self, $request, $session, $v2opts) = @_;
155 0         0  
156 0 0       0 # this code may be called before authentication_url()
157 0 0       0 # (multiple processes), so we must make sure the base
158 0         0 # setting isn't undef
159 0         0 $self->settings->{base} ||= $request->uri_base;
160              
161             my $provider = lc $self->_provider;
162 0         0 my $session_data = $session->read('oauth') || {};
163              
164             if( $self->protocol_version < 2 ) {
165 6     6 0 10976 return $self->settings->{error_url} || '/' unless( defined($request->param('oauth_token')) );
166 6         36 my $at_request = Net::OAuth->request( 'access token' )->new(
167             $self->_default_args_v1,
168             token => $request->param('oauth_token'),
169 6     6   119 token_secret => '',
170             verifier => $request->param('oauth_verifier'),
171              
172             request_url => $self->provider_settings->{urls}{access_token_url},
173             request_method => 'POST'
174 6   33     20 );
175             $at_request->sign;
176 6         40  
177 6   50     32 my $ua_response = $self->ua->request(
178             POST $at_request->to_url, [
179 6 100       178 'oauth_verifier', $request->param('oauth_verifier')
180 1 50 0     6 ]
181             );
182              
183             if( $ua_response->is_success ) {
184             my $response = Net::OAuth->response( 'access token' )->from_post_body( $ua_response->content );
185             $session_data->{$provider} = {
186             access_token => $response->token,
187             access_token_secret => $response->token_secret,
188 1         20 extra => $response->extra_params,
189             };
190 1         1799 }
191             } else {
192 1         1693 my $uri = URI->new( $self->provider_settings->{urls}{access_token_url} );
193             my %args = %{$v2opts};
194             $args{client_id} = $self->provider_settings->{tokens}{client_id};
195             $args{client_secret} = $self->provider_settings->{tokens}{client_secret};
196             $args{redirect_uri} = $self->_callback_url;
197             my $response = $self->{ua}->request( POST $uri->as_string, \%args );
198 1 50       1627  
199 1         17 if( $response->is_success ) {
200 1         1301 my $content_type = $response->header('Content-Type');
201             my $params = {};
202             if( $content_type =~ m/json/ || $content_type =~ m/javascript/ ) {
203             $params = decode_json( $response->content );
204             } else {
205             $params = URI::Query->new( $response->content )->hash;
206             }
207 5         18  
208 5         342 # Error checking on the response from the server. If this is a refresh that failed we need to catch and return that fact
  5         27  
209 5         18 my $keys_found = 0;
210 5         19 for my $key (qw/access_token email user_id expires expires_in id_token token_type id issued_at scope instance_url refresh_token signature x_mailru_vid error/) {
211 5         20 if ($params->{$key}) {
212 5         31 $keys_found++;
213             }
214 5 50       4406 }
215 5         60 if (!$keys_found) {
216 5         224 $self->{dsl}->app->log(debug => "Auth::OAuth::Provider::".$self->_provider.": Token request for grant_type ".$args{grant_type}." didn't return any known ID data. Assuming failed, and returning failed response.");
217 5 100 66     36 return undef;
218 2         10 }
219              
220 3         15 # Some servers don't return an issued_at or expires; Dancer app authors might need this to check if a refresh is required
221             if (!defined $params->{"issued_at"}) {
222             $params->{"issued_at"} = DateTime->now->epoch;
223             }
224 5         721 if ($params->{"expires_in"} && !defined $params->{"expires"}) {
225 5         20 $params->{"expires"} = $params->{"issued_at"} + $params->{"expires_in"};
226 75 100       131 }
227 18         28  
228             for my $key (qw/access_token email user_id expires expires_in id_token token_type id issued_at scope instance_url refresh_token signature x_mailru_vid error/) {
229             if ($params->{$key}) {
230 5 50       15 $session_data->{$provider}{$key} = $params->{$key};
231 0         0 }
232 0         0 }
233              
234             } else {
235             $self->{dsl}->app->log(debug => "Auth::OAuth::Provider::".$self->_provider.": Token request for grant_type ".$args{grant_type}." failed with ".$response->status_line);
236 5 50       16 return undef;
237 0         0 }
238             }
239 5 100 66     24 $session->write('oauth', $session_data);
240 2         7  
241             # fetch user info or whatever we want to do at this point
242             $self->post_process( $session );
243 5         19 }
244 75 100       154  
245 20         40 1;