File Coverage

blib/lib/OpenID/Lite/RelyingParty/IDResHandler/Verifier.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package OpenID::Lite::RelyingParty::IDResHandler::Verifier;
2 1     1   6 use Any::Moose;
  1         2  
  1         9  
3              
4 1     1   574 use URI;
  1         2  
  1         6  
5 1     1   28 use List::MoreUtils qw(any);
  1         2  
  1         58  
6 1     1   7 use OpenID::Lite::Nonce;
  1         2  
  1         11  
7 1     1   3259 use OpenID::Lite::Util::URI;
  1         4  
  1         18  
8 1     1   50 use OpenID::Lite::Util::XRI;
  1         2  
  1         9  
9 1     1   23 use OpenID::Lite::SignatureMethods;
  1         1  
  1         25  
10 1     1   25 use OpenID::Lite::Identifier;
  1         2  
  1         9  
11 1     1   820 use OpenID::Lite::RelyingParty::DirectCommunication;
  1         2  
  1         9  
12 1     1   114 use OpenID::Lite::RelyingParty::Discover;
  0            
  0            
13             use OpenID::Lite::RelyingParty::Discover::Service;
14             use OpenID::Lite::Constants::Namespace
15             qw(SPEC_1_0 SPEC_2_0 SIGNON_1_1 SIGNON_1_0 SIGNON_2_0 SERVER_2_0);
16             use OpenID::Lite::Constants::ModeType qw(CHECK_AUTHENTICATION);
17              
18             has 'params' => (
19             is => 'ro',
20             isa => 'OpenID::Lite::Message',
21             required => 1,
22             );
23              
24             has 'current_url' => (
25             is => 'ro',
26             isa => 'Str',
27             required => 1,
28             );
29              
30             has 'service' => (
31             is => 'rw',
32             isa => 'OpenID::Lite::RelyingParty::Discover::Service',
33             predicate => 'has_service',
34             );
35              
36             has 'store' => (
37             is => 'ro',
38              
39             #does => 'OpenID::Lite::Role::Storable',
40             );
41              
42             has '_discoverer' => (
43             is => 'ro',
44             lazy_build => 1,
45             );
46              
47             has '_direct_communication' => (
48             is => 'ro',
49             isa => 'OpenID::Lite::RelyingParty::DirectCommunication',
50             lazy_build => 1,
51             );
52              
53             with 'OpenID::Lite::Role::AgentHandler';
54             with 'OpenID::Lite::Role::ErrorHandler';
55              
56             my @OP1_FIELDS = qw(return_to assoc_handle sig signed identity);
57             my @OP1_SIG_FIELDS = qw(return_to identity);
58              
59             my @OP2_FIELDS = qw(return_to assoc_handle sig signed op_endpoint);
60             my @OP2_SIG_FIELDS = qw(return_to response_nonce assoc_handle);
61              
62             sub verify {
63             my $self = shift;
64             $self->_check_for_fields() or return;
65             $self->_verify_return_to() or return;
66              
67             $self->_verify_discovery_results() or return;
68             $self->_check_nonce() or return;
69             $self->_check_signature() or return;
70              
71             return 1;
72             }
73              
74             sub _check_for_fields {
75             my $self = shift;
76              
77             my ( @fields, @signed_fields );
78             if ( $self->params->is_openid1 ) {
79             @fields = @OP1_FIELDS;
80             @signed_fields = @OP1_SIG_FIELDS;
81             }
82             else {
83             @fields = @OP2_FIELDS;
84             @signed_fields = @OP2_SIG_FIELDS;
85             }
86              
87             my $signed = $self->params->get('signed')
88             or $self->ERROR(q{signed key not found.});
89             my @signed = split /,/, $signed;
90              
91             for my $field (@fields) {
92             unless ( $self->params->has_key($field) ) {
93             return $self->ERROR( sprintf q{"%s" key not found}, $field );
94             }
95             }
96             for my $field (@signed_fields) {
97             unless ( $self->params->has_key($field)
98             && ( any { $field eq $_ } @signed ) )
99             {
100             return $self->ERROR( sprintf q{"%s" key not found}, $field );
101             }
102             }
103             return 1;
104             }
105              
106             sub _verify_return_to {
107             my $self = shift;
108              
109             my $url = $self->params->get('return_to');
110             return unless $url;
111             $url = OpenID::Lite::Util::URI->normalize($url);
112             return unless $url;
113             return unless OpenID::Lite::Util::URI->is_uri($url);
114              
115             $self->_verify_return_to_args($url)
116             or return;
117             $self->_verify_return_to_base($url)
118             or return;
119             return 1;
120             }
121              
122             sub _verify_return_to_args {
123             my ( $self, $return_to ) = @_;
124             $return_to = URI->new($return_to);
125             my %parsed = $return_to->query_form();
126              
127             for my $key ( keys %parsed ) {
128             my $msg_val = $self->params->get_extra($key);
129             return $self->ERROR(
130             sprintf q{Message missing return_to argument, "%s"}, $key )
131             unless $msg_val;
132             return $self->ERROR(
133             sprintf
134             q{Parameter [%s]s value [%s] doesn't match return_to's value [%s]},
135             $key, $msg_val, $parsed{$key} )
136             if ( $msg_val ne $parsed{$key} );
137             }
138              
139             for my $key ( @{ $self->params->get_extra_keys() } ) {
140             my $msg_val = $self->params->get_extra($key);
141             return $self->ERROR(
142             q{Unexpected parameter (not on return_to), [%s = %s]},
143             $key, $msg_val )
144             if not exists $parsed{$key};
145             if ( $msg_val ne $parsed{$key} ) {
146             return $self->ERROR(
147             sprintf
148             q{Parameter [%s]s value [%s] doesn't match return_to's value [%s]},
149             $key, $msg_val, $parsed{$key} );
150             }
151             }
152              
153             return 1;
154             }
155              
156             sub _verify_return_to_base {
157             my ( $self, $return_to ) = @_;
158             $return_to = URI->new($return_to);
159             my $current_url = URI->new( $self->current_url );
160             for my $meth (qw(scheme host port path)) {
161             return $self->ERROR(
162             sprintf q{"%s" in response parameter wasn't match to "%s"},
163             $return_to->as_string, $current_url->as_string )
164             unless $current_url->$meth eq $return_to->$meth;
165             }
166             return 1;
167             }
168              
169             sub _verify_discovery_single {
170             my ( $self, $endpoint, $to_match ) = @_;
171             for my $type_uri ( @{ $to_match->types } ) {
172             return $self->ERROR(q{Type uri mismatch.})
173             unless $endpoint->has_type($type_uri);
174             }
175              
176             my $defragged_claimed_id;
177             my $scheme = OpenID::Lite::Util::XRI->identifier_scheme(
178             $to_match->claimed_identifier );
179             if ( $scheme eq 'xri' ) {
180             $defragged_claimed_id = $to_match->claimed_identifier;
181             }
182             elsif ( $scheme eq 'uri' ) {
183             if (OpenID::Lite::Util::URI->is_uri( $to_match->claimed_identifier ) )
184             {
185             my $parsed = URI->new( $to_match->claimed_identifier );
186             $parsed->fragment(undef);
187             $defragged_claimed_id = $parsed->as_string;
188             }
189             else {
190             $defragged_claimed_id = $to_match->claimed_identifier;
191             }
192             }
193             else {
194             return $self->ERROR(
195             sprintf q{Invalid claimed_id, "%s"},
196             $to_match->claimed_identifier || ''
197             );
198             }
199              
200             if ( $defragged_claimed_id ne $endpoint->claimed_identifier ) {
201             return $self->ERROR(
202             sprintf q{Claimed IDs don't match, "%s" and "%s" },
203             $defragged_claimed_id, $endpoint->claimed_identifier );
204             }
205              
206             if ($endpoint->find_local_identifier ne $to_match->find_local_identifier )
207             {
208             return $self->ERROR(
209             sprintf q{Local IDs don't match, "%s" and "%s" },
210             $to_match->find_local_identifier,
211             $endpoint->find_local_identifier
212             );
213             }
214              
215             if ( !$to_match->url ) {
216             if ( $to_match->preferred_namespace ne SPEC_1_0 ) {
217             return $self->ERROR(
218             q{ensure that endpoint is openid2.0 without openid.op_endpoint?}
219             );
220             }
221             }
222             elsif ( $endpoint->url ne $to_match->url ) {
223             return $self->ERROR( sprintf q{OP endpoint mismatch, "%s" and "%s"},
224             $endpoint->url, $to_match->url );
225             }
226             return 1;
227             }
228              
229             sub _verify_discovered_services {
230             my ( $self, $claimed_id, $services, $to_match_endpoints ) = @_;
231              
232             my @failure_messages;
233             for my $service ( @$services ) {
234             for my $to_match ( @$to_match_endpoints ) {
235             if ( $self->_verify_discovery_single($service, $to_match) ) {
236             $self->service($service);
237             return 1;
238             } else {
239             push(@failure_messages, $self->errstr);
240             $self->ERROR(undef);
241             }
242             }
243             }
244             return $self->ERROR(
245             sprintf q{No matching endpoint found for claimed_id "%s".}, $claimed_id);
246             }
247              
248             sub _discover_and_verify {
249             my ( $self, $claimed_id, $to_match_endpoints ) = @_;
250             $claimed_id = OpenID::Lite::Identifier->normalize($claimed_id);
251             my $services = $self->_discoverer->discover($claimed_id)
252             or return $self->ERROR( $self->_discoverer->errstr );
253             unless ( @$services > 0 ) {
254             return $self->ERROR( sprintf q{No OpenID information found at %s},
255             $claimed_id );
256             }
257             return $self->_verify_discovered_services( $claimed_id, $services,
258             $to_match_endpoints );
259             }
260              
261             sub _verify_discovery_results_openid1 {
262             my $self = shift;
263              
264             my $claimed_id = $self->params->get_extra('openid1_claimed_id');
265              
266             unless ($claimed_id) {
267             if ( $self->has_service && $self->service->claimed_identifier ) {
268             $claimed_id = $self->service->claimed_identifier;
269             }
270             else {
271             return $self->ERROR(q{When using OpenID 1, the claimed id must be supplied,
272             either by passing it through as a return_to parameter or by using a
273             session, and supplied to the IDResHandler when it is constructed.});
274             }
275             }
276              
277             my $to_match = OpenID::Lite::RelyingParty::Discover::Service->new;
278             $to_match->op_local_identifier( $self->params->get('identity') );
279             $to_match->claimed_identifier($claimed_id);
280              
281             my $to_match_1_0 = $to_match->copy();
282             $to_match->add_type(SIGNON_1_1);
283             $to_match_1_0->add_type(SIGNON_1_0);
284              
285             if ( $self->has_service ) {
286             my $verified = $self->_verify_discovery_single( $self->service, $to_match )
287             || $self->_verify_discovery_single( $self->service, $to_match_1_0 );
288             return 1 if $verified;
289             }
290              
291             return $self->_discover_and_verify(
292             $to_match->claimed_identifier,
293             [$to_match, $to_match_1_0]
294             );
295             }
296              
297             sub _verify_discovery_results_openid2 {
298             my $self = shift;
299             my $to_match = OpenID::Lite::RelyingParty::Discover::Service->new;
300             $to_match->add_type(SIGNON_2_0);
301             $to_match->add_uri( $self->params->get('op_endpoint') );
302              
303             # claimed_id && identity both or none?
304             my $claimed_id = $self->params->get('claimed_id');
305             my $identity = $self->params->get('identity');
306              
307             $to_match->claimed_identifier($claimed_id);
308             $to_match->op_local_identifier($identity);
309              
310             if ( $claimed_id && !$identity ) {
311             return $self->ERROR(
312             q{openid.claimed_id is present without openid.identity});
313             }
314             elsif ( !$claimed_id && $identity ) {
315             return $self->ERROR(
316             q{openid.identity is present without openid.claimed_id});
317             }
318             elsif ( !$claimed_id ) {
319              
320             my $service = OpenID::Lite::RelyingParty::Discover::Service->new;
321             $service->add_type(SERVER_2_0);
322             $service->add_uri( $self->params->get('op_endpoint') );
323             $self->service($service);
324             return 1;
325             }
326              
327             if ( $self->has_service ) {
328             unless ( $self->_verify_discovery_single( $self->service, $to_match ) ) {
329             return unless $claimed_id;
330             return unless $self->_discover_and_verify( $claimed_id, [$to_match] )
331             }
332             }
333             else {
334             $self->_discover_and_verify( $claimed_id, [$to_match] )
335             or return;
336             }
337              
338             if ( $self->service->claimed_identifier ne $to_match->claimed_identifier )
339             {
340             my $copied = $self->service->copy();
341             $copied->claimed_identifier( $to_match->claimed_identifier );
342             $self->service($copied);
343             }
344              
345             return 1;
346             }
347              
348             sub _verify_discovery_results {
349             my $self = shift;
350             if ( $self->params->is_openid1 ) {
351             $self->_verify_discovery_results_openid1()
352             or return;
353             }
354             elsif ( $self->params->is_openid2 ) {
355             $self->_verify_discovery_results_openid2()
356             or return;
357             }
358             else {
359             return $self->ERROR(q{No reached});
360             }
361             }
362              
363             sub _check_nonce {
364             my $self = shift;
365             my ( $nonce, $server_url );
366             if ( $self->params->is_openid1 ) {
367             $nonce
368             = $self->params->get_extra('rp_nonce'); # TODO: get extra params
369             $server_url = '';
370             }
371             elsif ( $self->params->is_openid2 ) {
372             $nonce = $self->params->get('response_nonce');
373             $server_url = $self->has_service ? $self->service->url : undef;
374             }
375             else {
376             return $self->ERROR(q{IdRes Response doesn't have proper ns value.});
377             }
378              
379             unless ($nonce) {
380             return $self->ERROR(q{No proper nonce found.});
381             }
382              
383             my ( $timestamp, $unique ) = OpenID::Lite::Nonce->split_nonce($nonce)
384             or
385             return $self->ERROR( sprintf q{Invalid response_nonce format. "%s"},
386             $nonce );
387              
388             if ( $self->store
389             && !$self->store->use_nonce( $server_url, $timestamp, $unique ) )
390             {
391             return $self->ERROR(
392             sprintf q{Nonce already used or out of range: "%s"}, $nonce );
393             }
394              
395             return 1;
396             }
397              
398             sub _check_signature {
399              
400             my $self = shift;
401              
402             my $assoc;
403             my $server_url = $self->service->url;
404              
405             if ( $self->store ) {
406             my $assoc_handle = $self->params->get('assoc_handle');
407             $assoc = $self->store->get_association( $server_url, $assoc_handle );
408             }
409              
410             if ( !$assoc ) {
411             return $self->_check_auth();
412             }
413             else {
414             if ( $assoc->is_expired ) {
415             return $self->ERROR(q{Association expired});
416             }
417             else {
418             my $secret = $assoc->secret;
419             my $assoc_type = $assoc->type;
420             my $method
421             = OpenID::Lite::SignatureMethods->select_method($assoc_type);
422             return $self->ERROR( sprintf q{Bad signature in response from %s},
423             $server_url )
424             unless $method->verify( $secret, $self->params );
425             }
426             return 1;
427             }
428             }
429              
430             sub _check_auth {
431             my $self = shift;
432             my $params = $self->_create_check_auth_request()
433             or return;
434             my $server_url = $self->service->url;
435             my $res_params
436             = $self->_direct_communication->send_request( $server_url, $params )
437             or return $self->ERROR(q{Failed direct-communication, 'checkauth' request});
438             return $self->_process_check_auth_response($res_params);
439             }
440              
441             sub _process_check_auth_response {
442             my ( $self, $res_params ) = @_;
443             my $is_valid = $res_params->get('is_valid') || 'false';
444             my $invalidate_handle = $res_params->get('invalidate_handle');
445             my $server_url = $self->service->url;
446             if ($invalidate_handle) {
447             if ( $self->store ) {
448             $self->store->remove_association( $server_url,
449             $invalidate_handle );
450             }
451             }
452              
453             if ( $is_valid ne 'true' ) {
454             return $self->ERROR(
455             sprintf
456             q{Server %s responds that check_authentication call is not valid.},
457             $server_url,
458             );
459             }
460             return 1;
461             }
462              
463             sub _create_check_auth_request {
464             my $self = shift;
465             my $ca_message = $self->params->copy();
466             $ca_message->set( mode => CHECK_AUTHENTICATION );
467             return $ca_message;
468              
469             }
470              
471             sub _build__discoverer {
472             my $self = shift;
473             return OpenID::Lite::RelyingParty::Discover->new(
474             agent => $self->agent, );
475             }
476              
477             sub _build__direct_communication {
478             my $self = shift;
479             return OpenID::Lite::RelyingParty::DirectCommunication->new(
480             agent => $self->agent, );
481             }
482              
483             no Any::Moose;
484             __PACKAGE__->meta->make_immutable;
485             1;
486