File Coverage

blib/lib/OpenID/Lite/RelyingParty.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package OpenID::Lite::RelyingParty;
2              
3 1     1   7 use Any::Moose;
  1         3  
  1         10  
4              
5 1     1   633 use OpenID::Lite::Identifier;
  1         2  
  1         9  
6 1     1   94 use OpenID::Lite::Message;
  1         2  
  1         7  
7 1     1   63 use OpenID::Lite::RelyingParty::Discover;
  0            
  0            
8             use OpenID::Lite::RelyingParty::Associator;
9             use OpenID::Lite::RelyingParty::CheckID::Request;
10              
11             use OpenID::Lite::RelyingParty::IDResHandler;
12             use OpenID::Lite::RelyingParty::Store::OnMemory;
13              
14             use URI;
15             use Params::Validate;
16              
17             has '_discoverer' => (
18             is => 'ro',
19             lazy_build => 1,
20             );
21              
22             has '_associator' => (
23             is => 'ro',
24             lazy_build => 1,
25             );
26              
27             has '_id_res_handler' => (
28             is => 'ro',
29             lazy_build => 1,
30             );
31              
32             has 'session' => (
33             is => 'rw',
34             # isa => 'HTTP::Session',
35             );
36              
37             has 'store' => (
38             is => 'rw',
39             does => 'OpenID::Lite::Role::Storable',
40             default => sub { OpenID::Lite::RelyingParty::Store::OnMemory->new },
41             );
42              
43             with 'OpenID::Lite::Role::ErrorHandler';
44             with 'OpenID::Lite::Role::AgentHandler';
45             with 'OpenID::Lite::Role::Associator';
46             with 'OpenID::Lite::Role::Discoverer';
47              
48             sub begin {
49             my ( $self, $user_suplied_identifier, $anonymous ) = @_;
50              
51             my $identifier = $self->normalize_identifier($user_suplied_identifier);
52             return unless $identifier;
53              
54             my $services = $self->discover($identifier);
55             return unless $services;
56              
57             my $service = $services->[0];
58              
59             $self->begin_without_discovery( $service, $anonymous );
60             }
61              
62             sub begin_without_discovery {
63             my ( $self, $service, $anonymous ) = @_;
64              
65             my $association = $self->associate($service) or return;
66              
67             #return unless $association;
68              
69             my %params = ( service => $service );
70             $params{association} = $association if $association;
71             $params{anonymous} = 1 if $anonymous;
72              
73             my $checkid_req
74             = OpenID::Lite::RelyingParty::CheckID::Request->new(%params);
75             $self->last_requested_endpoint($service);
76              
77             return $checkid_req;
78             }
79              
80             sub last_requested_endpoint {
81             my ( $self, $endpoint ) = @_;
82             return unless $self->session;
83             if ($endpoint) {
84             $self->session->set( 'openid.last_requested_endpoint', $endpoint );
85             }
86             else {
87             $endpoint = $self->session->get('openid.last_requested_endpoint');
88             }
89             return $endpoint;
90             }
91              
92             sub normalize_identifier {
93             my ( $self, $user_suplied_identifier ) = @_;
94             my $identifier
95             = OpenID::Lite::Identifier->normalize($user_suplied_identifier);
96             return $identifier
97             ? $identifier
98             : $self->ERROR( sprintf q{Invalid identifier: %s},
99             $user_suplied_identifier );
100             }
101              
102             sub discover {
103             my ( $self, $identifier ) = @_;
104              
105             # execute discovery
106             my $services = $self->_discoverer->discover($identifier)
107             or return $self->ERROR( $self->_discoverer->errstr );
108             return $self->ERROR( sprintf q{Service not found for identifier %s},
109             $identifier )
110             unless @$services > 0;
111              
112             # pick up op-identifier information if it exists
113             my @op_identifiers = grep { $_->is_op_identifier } @$services;
114             return \@op_identifiers if @op_identifiers > 0;
115              
116             # sorted by priority
117             # we like 2.0 service endpoint rather than 1.X.
118             my @sorted_services
119             = sort { $a->type_priority <=> $b->type_priority } @$services;
120              
121             return \@sorted_services;
122             }
123              
124             sub associate {
125             my ( $self, $service ) = @_;
126             my $server_url = $service->url;
127              
128             # find association related to passed server-url
129             my $association = $self->store->get_association($server_url);
130              
131             # if there isn't available association,
132             # it starts to negotiate with provider to obtain new association.
133             if ( !$association || $association->is_expired ) {
134             $association = $self->_associator->associate($service)
135             or return $self->ERROR( $self->_associator->errstr );
136              
137             # if it finished successfully, save it.
138             $self->store->store_association( $server_url => $association )
139             if $association;
140             }
141             return $association;
142             }
143              
144             sub complete {
145             my ( $self, $request, $current_url ) = @_;
146             my $params = OpenID::Lite::Message->from_request($request);
147             my $service = $self->last_requested_endpoint;
148             my %args = (
149             current_url => $current_url,
150             params => $params,
151             );
152             $args{service} = $service if $service;
153             my $result = $self->idres(%args);
154             return $result;
155             }
156              
157             sub idres {
158             my $self = shift;
159             my %args = Params::Validate::validate(
160             @_,
161             { current_url => 1,
162             params => {
163             isa => 'OpenID::Lite::Message',
164             },
165             service => {
166             isa => 'OpenID::Lite::RelyingParty::Discover::Service',
167             optional => 1
168             },
169             }
170             );
171             return $self->_id_res_handler->idres(%args)
172             || $self->ERROR( $self->_id_res_handler->errstr );
173             }
174              
175             sub _build__discoverer {
176             my $self = shift;
177             return OpenID::Lite::RelyingParty::Discover->new( agent => $self->agent,
178             );
179             }
180              
181             sub _build__associator {
182             my $self = shift;
183             return OpenID::Lite::RelyingParty::Associator->new(
184             agent => $self->agent,
185             assoc_type => $self->assoc_type,
186             session_type => $self->session_type,
187             );
188             }
189              
190             sub _build__id_res_handler {
191             my $self = shift;
192             return OpenID::Lite::RelyingParty::IDResHandler->new(
193             agent => $self->agent,
194             store => $self->store,
195             );
196             }
197              
198             no Any::Moose;
199             __PACKAGE__->meta->make_immutable;
200             1;
201              
202             =head1 NAME
203              
204             OpenID::Lite::RelyingParty - OpenID RelyingParty support module
205              
206             =head1 SYNOPSIS
207              
208             my $openid = OpenID::Lite::RelyingParty->new();
209              
210             sub login {
211              
212             my $self = shift;
213              
214             my $user_suplied_identifier = $self->req->param('openid_identifier');
215             return unless $self->validate( $user_suplied_identifier );
216              
217             my $checkid_request = $openid->begin( $user_suplied_identifier )
218             or $self->show_error( $openid->errstr );
219              
220             my $sreg = OpenID::Lite::Extension::SREG::Request->new;
221             $sreg->request_fields(qw(nickname));
222              
223             $checkid_request->add_extension($sreg);
224              
225             my $redirect_url = $checkid_request->redirect_url(
226             return_to => q{http://example.com/return_to},
227             realm => q{http://example.com/},
228             );
229              
230             return $self->redirect_to( $redirect_url );
231             }
232              
233             sub return_to {
234             my $self = shift;
235             my $openid = OpenID::Lite::RelyingParty->new;
236              
237             my $res = $openid->complete( $self->request, q{http://myapp/return_to} );
238              
239             if ( $res->is_success ) {
240              
241             # openid login successfully completed.
242             # you should save the verified identifier.
243              
244             my $display_identifier = $res->display_identifier;
245             my $identity_url = $res->identity_url;
246              
247             } elsif ( $res->is_canceled ) {
248              
249             # user canceled openid-login.
250             #
251             # redirect back to top-page or login-page.
252             return $your_app->redirect('http://yourapp.com/');
253              
254             } elsif ( $res->is_setup_needed ) {
255              
256             # you requested as immediate-mode.
257             # but OP requires setup.
258              
259             # so, then redirect to the indicated url
260             return $your_app->redirect( $res->url );
261              
262             # if you accept OP with white-list,
263             # You can know whether the OP accepts immedate mode or not.
264             # So, it's better to change not to use immediate-mode.
265              
266             } elsif ( $res->is_not_openid ) {
267              
268             return $your_app->error('request is not for openid.');
269              
270             } elsif ( $res->is_invalid ) {
271              
272             # failed to verify returned assertion
273             $your_app->log( $res->message );
274             $your_app->error('Failed to verify assertion.');
275              
276             } elsif ( $res->is_error ) {
277              
278             # error response.
279             $your_app->log( $res->message );
280             $your_app->log( $res->contact );
281             $your_app->log( $res->referrence );
282              
283             $your_app->error('Got error response from OP');
284              
285             }
286              
287             }
288              
289             =head1 DESCRIPTION
290              
291             This module allows you to make OpenID RelyingParty easily.
292             This supports OpenID 2.0.
293             Most of these interface is borrowd from ruby-openid which is provided by OpenID Enabled.
294              
295             You only have to execute 'begin' and 'complete'.
296             These methods automatically and properly execute each OpenID communication.
297              
298             But if you want to customize behavior in detail,
299             You alco can use rower API of this module, for example,
300             'discover', 'associate', 'idres', and so on.
301              
302             'Lite' means nothing. It's to escape namespace confliction.
303              
304             =head1 PREPARE
305              
306             You should start with preparing OpenID::Lite::RelyingParty object with defualt set.
307              
308             my $openid_rp = OpenID::Lite::RelyingParty->new();
309              
310             Or set options.
311              
312             use OpenID::Lite::Constants::AssocType qw(HMAC_SHA256);
313             use OpenID::Lite::Constants::SessionType qw(DH_SHA256);
314             my $openid_rp = OpenID::Lite::RelyingParty->new(
315             assoc_type => HMAC_SHA256,
316             session_type => DH_SHA256,
317             session => $myapp->session, # HTTP::Session object or other object which has same interface.
318             agent => $agent,
319             store => OpenID::Lite::RelyingParty::Store::Cache->new,
320             );
321              
322             =head2 new
323              
324             =over4
325              
326             =item assoc_type
327              
328             HMAC_SHA1 is set by default.
329             L
330              
331             =item session_type
332              
333             NO_ENCRYPTION is set by default.
334             L
335              
336             =item agent
337              
338             If you omit, L is set by default.
339              
340             To keep your application secure, you'd better set agent with more-secure one.
341             like L or L.
342              
343             L dump the request and response object for debug.
344              
345             =item session
346              
347             To reduce the cost on 'idres', you can set session object.
348             This object must behaves as same as L object.
349             (Just requires only 'get' and 'set' methods.)
350              
351             If session is set and discovery is executed by claimed-id,
352             On 'idres' process, it need'nt execute re-discovery to verify
353             returned information.
354              
355             See Also
356             L
357              
358             =item store
359              
360             This if for saving associations which is established between RP and OP.
361             If this is undef, OpenID process is carried out as 'stateless mode'.
362             In stateless case, check_authentication http request is required
363             to verify signature included in checkid-response.
364              
365             You had better to set this to reduce networking cost.
366             If your RP site allows OP with white-list (maybe this is a standard way now),
367             Not so much associations are build. So Hash object on memory is enough to store them.
368             L fits for this case.
369              
370             But your site accepts all the OpenID Providers,
371             More assocations will be established with them.
372             Then you may need another solution.
373              
374             If you omit, L is set by default.
375             In future OpenID::Lite::RelyingParty::Store::Cache will be pushed into this package.
376              
377             See Also
378             L
379              
380             =back
381              
382             =head1 BEGIN
383              
384             You should
385              
386             1. normalize user suplied identifier
387             2. execute discovery with identifier ( if arleady you obtained OP's information, you can omit this phase )
388             3. established association with OP ( if stateless mode, this phase is omitted )
389             4. make check-id request
390             5. redirect user to OP's endpoint as checkid-request.
391              
392             There are methods corresponding to each phase.
393             You can use them or simple 'begin' methods that execute
394             most of these process automatically.
395              
396             simple API example
397              
398             sub login {
399             my $your_app = shift;
400             my $identifier = $your_app->req->param('openid_identifier');
401             # $your_app->validate_identifier( $identifier );
402              
403             my $checkid_request = $openid_rp->begin( $identifier )
404             or $your_app->show_error( $your_app->openid->errstr );
405              
406             my $endpoint_url = $checkid_request->redirect_url(
407             return_to => q{http://myapp.com/return_to},
408             realm => q{http://myapp.com/},
409             );
410              
411             return $your_app->redirect( $endpoint_url );
412             }
413              
414             simple API and limiting OP and reducing discovery-cost.
415              
416             use OpenID::Lite::Constants::Namespace qw(SERVER_2_0);
417              
418             sub login {
419             my $your_app = shift;
420              
421             my $service = OpenID::Lite::RelyingParty::Discover::Service->new;
422             $service->add_type( SERVER_2_0 );
423             $service->add_uri( q{http://example.com/op/endpoint} );
424              
425             my $checkid_request = $openid_rp->begin_without_discovery( $service );
426              
427             my $endpoint_url = $checkid_request->redirect_url(
428             return_to => q{http://myapp.com/return_to},
429             realm => q{http://myapp.com/},
430             );
431              
432             return $your_app->redirect( $endpoint_url );
433             }
434              
435             raw API example
436              
437             sub login {
438             my $your_app = shift;
439             my $identifier = $your_app->req->param('openid_identifier');
440             # $your_app->validate_identifier( $identifier );
441              
442             $identifier = $openid_rp->normalize_identifier( $identifier )
443             or return $your_app->error( $openid_rp->errstr );
444              
445             my $services = $openid_rp->discover( $identifier )
446             or return $your_app->error( $openid_rp->errstr );
447              
448             unless ( @$services > 0 ) {
449             return $your_app->error('No proper OpenID Provider found.');
450             }
451              
452             my $service = $services->[0];
453              
454             my $association = $openid_rp->associate( $services->[0] )
455             or return $your_app->error( $openid_rp->errstr );
456              
457             $your_app->save_association( $service, $association );
458             $your_app->session->set( 'openid.last_requested_endpoint', $service );
459              
460             my $checkid_request = OpenID::Lite::RelyingParty::CheckID::Request->new(
461             service => $service,
462             association => $association,
463             );
464              
465             my $endpoint_url = $checkid_request->redirect_url(
466             return_to => q{http://myapp.com/return_to},
467             realm => q{http://myapp.com/},
468             );
469              
470             return $your_app->redirect( $endpoint_url );
471             }
472              
473             =head2 normalize_identifier($identifier)
474              
475             Normalize user suplied identifier,
476             and return OpenID::Lite::Identifier object.
477              
478             my $normalized_identifier = $openid_rp->normalized_identifier($user_suplied_identifier)
479             or die $openid_rp->errstr;
480              
481             =head2 discover($normalized_identifier)
482              
483             Do discovery and return found service informations(Array of OpenID::Lite::RelyingParty::Discover::Service)
484              
485             $services = $openid_rp->discover( $normalized_identifier )
486             or die $openid_rp->errstr;
487              
488             =head2 associate($service)
489              
490             Establish association with OP.
491             Returns object.
492              
493             my $association = $openid_rp->associate( $service )
494             or die $openid_rp->errstr;
495              
496             =head2 begin($user_suplied_identifier)
497              
498             Return object.
499              
500             my $checkid_req = $openid_rp->begin( $identifier )
501             or die $openid_rp->errstr;
502              
503             =head2 begin_without_discovery($service)
504              
505             Return object.
506              
507             my $checkid_req = $openid_rp->begin_without_discovery( $service )
508             or die $openid_rp->errstr;
509              
510             =head1 APPEND EXTENSION
511              
512             You can add extension request onto checkid request.
513              
514             my $chekid_req = $openid_rp->begin(...);
515              
516             my $sreg_req = OpenID::Lite::Extension::SREG::Request->new;
517             $sreg_req->request_field('nickname');
518             $sreg_req->request_field('fullname');
519             $checkid_req->add_extension( $sreg_req );
520              
521             my $ui_req = OpenID::Lite::Extension::UI::Request->new;
522             $ui_req->mode('popup');
523             $checkid_req->add_extension( $ui_req );
524              
525             my $url = $checkid_req->redirect_url(
526             ...
527             );
528              
529             =head1 COMPLETE
530              
531             When OP redirect back user to the return_to url you
532             defined on checkid-request, you should execute 'idres'.
533             You can choose row API and simple wrapper here too.
534              
535              
536             row API example
537              
538             sub complete {
539             my $your_app = shift;
540              
541             my $params = OpenID::Lite::Message->from_request( $your_app->request );
542             my $service = $your_app->session->get('openid.last_requested_endpoint');
543             my $association = $your_app->load_association_for( $service );
544              
545             my $res = $openid_rp->idres(
546             service => $service,
547             association => $association,
548             params => $params,
549             current_url => q{http://yourapp.com/return_to},
550             );
551              
552             if ( $res->is_success ) {
553              
554             # openid login successfully completed.
555             # you should save the verified identifier.
556              
557             my $display_identifier = $res->display_identifier;
558             my $identity_url = $res->identity_url;
559              
560             } elsif ( $res->is_canceled ) {
561              
562             # user canceled openid-login.
563             #
564             # redirect back to top-page or login-page.
565             return $your_app->redirect('http://yourapp.com/');
566              
567             } elsif ( $res->is_setup_needed ) {
568              
569             # you requested as immediate-mode.
570             # but OP requires setup.
571              
572             # so, then redirect to the indicated url
573             return $your_app->redirect( $res->url );
574              
575             # if you accept OP with white-list,
576             # You can know whether the OP accepts immedate mode or not.
577             # So, it's better to change not to use immediate-mode.
578              
579             } elsif ( $res->is_not_openid ) {
580              
581             return $your_app->error('request is not for openid.');
582              
583             } elsif ( $res->is_invalid ) {
584              
585             # failed to verify returned assertion
586             $your_app->log( $res->message );
587             $your_app->error('Failed to verify assertion.');
588              
589             } elsif ( $res->is_error ) {
590              
591             # error response.
592             $your_app->log( $res->message );
593             $your_app->log( $res->contact );
594             $your_app->log( $res->referrence );
595              
596             $your_app->error('Got error response from OP');
597              
598             }
599              
600             }
601              
602             simple API example
603              
604             sub complete {
605             my $your_app = shift;
606              
607             my $current_url = q{http://yourapp.com/return_to};
608             my $res = $openid_rp->complete( $your_app->request, $current_url );
609              
610             # same as row API example above
611             if ( $res->is_success ) {
612             ...
613             } elsif ( $res->is_canceled ) {
614             ...
615             } elsif ( $res->is_setup_needed ) {
616             ...
617             } elsif ( $res->is_not_openid ) {
618             ...
619             } elsif ( $res->is_invalid ) {
620             ...
621             } elsif ( $res->is_error ) {
622             ...
623             }
624              
625             }
626              
627             =head2 idres(%args)
628              
629             Returns OpenID::Lite::RelyingParty::CheckID::Result object.
630              
631             =over 4
632              
633             =item params(required)
634              
635             OpenID::Lite::Message object.
636             You should encode your request to OpenID::Lite::Message.
637              
638             =item current_url(required)
639              
640             URL string that represents the endpoint you indicate
641             as return_to on checkid-request.
642              
643             =item services(optional)
644              
645             =item association(optional)
646              
647             =back
648              
649             =head2 complete($request, $current_url)
650              
651             Returns OpenID::Lite::RelyingParty::CheckID::Result object.
652              
653             =head1 EXTRACT EXTENSION RESPONSE
654              
655             In successfull response,
656             You can extract extension data you requested.
657              
658             } elsif ( $res->is_success ) {
659              
660             my $sreg_res = OpenID::Lite::Extension::SREG::Response->from_success_response($res);
661             my $data = $sreg_res->data;
662             my $nickname = $data->{nickname}||'';
663             my $fullname = $data->{fullname}||'';
664             ...
665             }
666              
667             =head1 SEE ALSO
668              
669             http://openid.net/specs/openid-authentication-2_0.html
670             http://openidenabled.com/
671              
672             =head2 TODO
673              
674             =over 4
675              
676             =item Improve an interoperability with majour services.
677              
678             =back
679              
680             =head1 AUTHOR
681              
682             Lyo Kato, Elyo.kato@gmail.comE
683              
684             =head1 COPYRIGHT AND LICENSE
685              
686             Copyright (C) 2009 by Lyo Kato
687              
688             This library is free software; you can redistribute it and/or modify
689             it under the same terms as Perl itself, either Perl version 5.8.8 or,
690             at your option, any later version of Perl 5 you may have available.
691              
692             =cut