Commit | Line | Data |
e5b6823d |
1 | package Catalyst::Authentication::Credential::OpenID; |
d214d0c0 |
2 | use strict; |
788804c2 |
3 | use warnings; no warnings "uninitialized"; # for testing, not production 321 |
bf16184a |
4 | use parent "Class::Accessor::Fast"; |
e5b6823d |
5 | |
6 | BEGIN { |
7 | __PACKAGE__->mk_accessors(qw/ _config realm debug secret /); |
8 | } |
9 | |
b8dc66f3 |
10 | our $VERSION = "0.14"; |
e5b6823d |
11 | |
12 | use Net::OpenID::Consumer; |
e5b6823d |
13 | use Catalyst::Exception (); |
14 | |
15 | sub new : method { |
16 | my ( $class, $config, $c, $realm ) = @_; |
17 | my $self = { _config => { %{ $config }, |
18 | %{ $realm->{config} } |
19 | } |
20 | }; |
21 | bless $self, $class; |
22 | |
bf16184a |
23 | # 2.0 spec says "SHOULD" be named "openid_identifier." |
e5b6823d |
24 | $self->_config->{openid_field} ||= "openid_identifier"; |
788804c2 |
25 | |
26 | $self->debug( $self->_config->{debug} ); |
27 | |
28 | my $secret = $self->_config->{consumer_secret} ||= join("+", |
29 | __PACKAGE__, |
30 | $VERSION, |
31 | sort keys %{ $c->config } |
32 | ); |
33 | |
34 | $secret = substr($secret,0,255) if length $secret > 255; |
35 | $self->secret($secret); |
36 | $self->_config->{ua_class} ||= "LWPx::ParanoidAgent"; |
37 | |
38 | my $agent_class = $self->_config->{ua_class}; |
39 | eval "require $agent_class" |
40 | or Catalyst::Exception->throw("Could not 'require' user agent class " . |
41 | $self->_config->{ua_class}); |
42 | |
43 | $c->log->debug("Setting consumer secret: " . $secret) if $self->debug; |
44 | |
45 | return $self; |
46 | } |
47 | |
48 | sub authenticate : method { |
49 | my ( $self, $c, $realm, $authinfo ) = @_; |
50 | |
51 | $c->log->debug("authenticate() called from " . $c->request->uri) if $self->debug; |
52 | return 1; |
53 | my $field = $self->{_config}->{openid_field}; |
54 | |
55 | my $claimed_uri = $authinfo->{ $field }; |
56 | |
57 | # Its security related so we want to be explicit about GET/POST param retrieval. |
58 | $claimed_uri ||= $c->req->method eq 'GET' ? |
59 | $c->req->query_params->{ $field } : $c->req->body_params->{ $field }; |
60 | |
61 | my $csr = Net::OpenID::Consumer->new( |
62 | ua => $self->_config->{ua_class}->new(%{$self->_config->{ua_args} || {}}), |
63 | args => $c->req->params, |
64 | consumer_secret => $self->secret, |
65 | ); |
66 | |
67 | if ( $claimed_uri ) |
68 | { |
69 | my $current = $c->uri_for($c->req->uri->path); # clear query/fragment... |
70 | |
71 | my $identity = $csr->claimed_identity($claimed_uri) |
72 | or Catalyst::Exception->throw($csr->err); |
73 | |
74 | $identity->set_extension_args(@{$self->_config->{extension_args}}) |
75 | if $self->_config->{extension_args}; |
76 | |
77 | my $check_url = $identity->check_url( |
78 | return_to => $current . '?openid-check=1', |
79 | trust_root => $current, |
80 | delayed_return => 1, |
81 | ); |
82 | $c->res->redirect($check_url); |
83 | $c->detach(); |
84 | } |
85 | elsif ( $c->req->params->{'openid-check'} ) |
86 | { |
87 | if ( my $setup_url = $csr->user_setup_url ) |
88 | { |
89 | $c->res->redirect($setup_url); |
90 | $c->detach(); |
91 | } |
92 | elsif ( $csr->user_cancel ) |
93 | { |
94 | return; |
95 | } |
96 | elsif ( my $identity = $csr->verified_identity ) |
97 | { |
98 | # This is where we ought to build an OpenID user and verify against the spec. |
99 | my $user = +{ map { $_ => scalar $identity->$_ } |
100 | qw( url display rss atom foaf declared_rss declared_atom declared_foaf foafmaker ) }; |
101 | |
102 | for(keys %{$self->{_config}->{extensions}}) { |
103 | $user->{extensions}->{$_} = $identity->signed_extension_fields($_); |
104 | } |
105 | |
106 | my $user_obj = $realm->find_user($user, $c); |
107 | |
108 | if ( ref $user_obj ) |
109 | { |
110 | return $user_obj; |
111 | } |
112 | else |
113 | { |
114 | $c->log->debug("Verified OpenID identity failed to load with find_user; bad user_class? Try 'Null.'") if $c->debug; |
115 | return; |
116 | } |
117 | } |
118 | else |
119 | { |
120 | Catalyst::Exception->throw("Error validating identity: " . |
121 | $csr->err); |
122 | } |
123 | } |
124 | return; |
125 | } |
126 | |
127 | 1; |
128 | |
129 | __END__ |
130 | |
131 | =head1 NAME |
132 | |
133 | Catalyst::Authentication::Credential::OpenID - OpenID credential for Catalyst::Plugin::Authentication framework. |
134 | |
135 | =head1 VERSION |
136 | |
137 | 0.14 |
138 | |
139 | =head1 SYNOPSIS |
140 | |
141 | In MyApp.pm- |
142 | |
143 | use Catalyst qw/ |
144 | Authentication |
145 | Session |
146 | Session::Store::FastMmap |
147 | Session::State::Cookie |
148 | /; |
149 | |
150 | Somewhere in myapp.conf- |
151 | |
152 | <Plugin::Authentication> |
153 | default_realm openid |
154 | <realms> |
155 | <openid> |
156 | <credential> |
157 | class OpenID |
158 | </credential> |
159 | ua_class LWPx::ParanoidAgent |
160 | </openid> |
161 | </realms> |
162 | </Plugin::Authentication> |
163 | |
164 | Or in your myapp.yml if you're using L<YAML> instead- |
165 | |
166 | Plugin::Authentication: |
167 | default_realm: openid |
168 | realms: |
169 | openid: |
170 | credential: |
171 | class: OpenID |
172 | ua_class: LWPx::ParanoidAgent |
173 | |
174 | In a controller, perhaps C<Root::openid>- |
175 | |
176 | sub openid : Local { |
177 | my($self, $c) = @_; |
178 | |
179 | if ( $c->authenticate() ) |
180 | { |
181 | $c->flash(message => "You signed in with OpenID!"); |
182 | $c->res->redirect( $c->uri_for('/') ); |
183 | } |
184 | else |
185 | { |
186 | # Present OpenID form. |
187 | } |
188 | } |
189 | |
190 | And a L<Template> to match in C<openid.tt>- |
191 | |
192 | <form action="[% c.uri_for('/openid') %]" method="GET" name="openid"> |
193 | <input type="text" name="openid_identifier" class="openid" /> |
194 | <input type="submit" value="Sign in with OpenID" /> |
195 | </form> |
196 | |
197 | =head1 DESCRIPTION |
198 | |
199 | This is the B<third> OpenID related authentication piece for |
200 | L<Catalyst>. The first E<mdash> L<Catalyst::Plugin::Authentication::OpenID> |
201 | by Benjamin Trott E<mdash> was deprecated by the second E<mdash> |
202 | L<Catalyst::Plugin::Authentication::Credential::OpenID> by Tatsuhiko |
203 | Miyagawa E<mdash> and this is an attempt to deprecate both by conforming to |
204 | the newish, at the time of this module's inception, realm-based |
205 | authentication in L<Catalyst::Plugin::Authentication>. |
206 | |
207 | 1. Catalyst::Plugin::Authentication::OpenID |
208 | 2. Catalyst::Plugin::Authentication::Credential::OpenID |
209 | 3. Catalyst::Authentication::Credential::OpenID |
210 | |
211 | The benefit of this version is that you can use an arbitrary number of |
212 | authentication systems in your L<Catalyst> application and configure |
213 | and call all of them in the same way. |
214 | |
215 | Note that both earlier versions of OpenID authentication use the method |
216 | C<authenticate_openid()>. This module uses C<authenticate()> and |
217 | relies on you to specify the realm. You can specify the realm as the |
218 | default in the configuration or inline with each |
219 | C<authenticate()> call; more below. |
220 | |
221 | This module functions quite differently internally from the others. |
222 | See L<Catalyst::Plugin::Authentication::Internals> for more about this |
223 | implementation. |
224 | |
225 | =head1 METHODS |
226 | |
227 | =over 4 |
228 | |
229 | =item $c->authenticate({},"your_openid_realm"); |
230 | |
231 | Call to authenticate the user via OpenID. Returns false if |
232 | authorization is unsuccessful. Sets the user into the session and |
233 | returns the user object if authentication succeeds. |
234 | |
235 | You can see in the call above that the authentication hash is empty. |
236 | The implicit OpenID parameter is, as the 2.0 specification says it |
237 | SHOULD be, B<openid_identifier>. You can set it anything you like in |
238 | your realm configuration, though, under the key C<openid_field>. If |
239 | you call C<authenticate()> with the empty info hash and no configured |
240 | C<openid_field> then only C<openid_identifier> is checked. |
241 | |
242 | It implicitly does this (sort of, it checks the request method too)- |
243 | |
244 | my $claimed_uri = $c->req->params->{openid_identifier}; |
245 | $c->authenticate({openid_identifier => $claimed_uri}); |
246 | |
247 | =item Catalyst::Authentication::Credential::OpenID->new() |
248 | |
249 | You will never call this. Catalyst does it for you. The only important |
250 | thing you might like to know about it is that it merges its realm |
251 | configuration with its configuration proper. If this doesn't mean |
252 | anything to you, don't worry. |
253 | |
254 | =back |
255 | |
256 | =head2 USER METHODS |
257 | |
258 | Currently the only supported user class is L<Catalyst::Plugin::Authentication::User::Hash>. |
259 | |
260 | =over 4 |
261 | |
262 | =item $c->user->url |
263 | |
264 | =item $c->user->display |
265 | |
266 | =item $c->user->rss |
267 | |
268 | =item $c->user->atom |
269 | |
270 | =item $c->user->foaf |
271 | |
272 | =item $c->user->declared_rss |
273 | |
274 | =item $c->user->declared_atom |
275 | |
276 | =item $c->user->declared_foaf |
277 | |
278 | =item $c->user->foafmaker |
279 | |
280 | =back |
281 | |
282 | See L<Net::OpenID::VerifiedIdentity> for details. |
283 | |
284 | =head1 CONFIGURATION |
285 | |
286 | Catalyst authentication is now configured entirely from your |
287 | application's configuration. Do not, for example, put |
288 | C<Credential::OpenID> into your C<use Catalyst ...> statement. |
289 | Instead, tell your application that in one of your authentication |
290 | realms you will use the credential. |
291 | |
292 | In your application the following will give you two different |
293 | authentication realms. One called "members" which authenticates with |
294 | clear text passwords and one called "openid" which uses... uh, OpenID. |
295 | |
296 | __PACKAGE__->config |
297 | ( name => "MyApp", |
298 | "Plugin::Authentication" => { |
299 | default_realm => "members", |
300 | realms => { |
301 | members => { |
302 | credential => { |
303 | class => "Password", |
304 | password_field => "password", |
305 | password_type => "clear" |
306 | }, |
307 | store => { |
308 | class => "Minimal", |
309 | users => { |
310 | paco => { |
311 | password => "l4s4v3n7ur45", |
312 | }, |
313 | } |
314 | } |
315 | }, |
316 | openid => { |
317 | consumer_secret => "Don't bother setting", |
318 | ua_class => "LWPx::ParanoidAgent", |
319 | ua_args => { |
320 | whitelisted_hosts => [qw/ 127.0.0.1 localhost /], |
321 | }, |
322 | credential => { |
323 | class => "OpenID", |
324 | store => { |
325 | class => "OpenID", |
326 | }, |
327 | }, |
328 | extension_args => [ |
329 | 'http://openid.net/extensions/sreg/1.1', |
330 | { |
331 | required => 'email', |
332 | optional => 'fullname,nickname,timezone', |
333 | }, |
334 | ], |
335 | }, |
336 | }, |
337 | } |
338 | ); |
339 | |
340 | This is the same configuration in the default L<Catalyst> configuration format from L<Config::General>. |
341 | |
342 | name MyApp |
343 | <Plugin::Authentication> |
344 | default_realm members |
345 | <realms> |
346 | <members> |
347 | <store> |
348 | class Minimal |
349 | <users> |
350 | <paco> |
351 | password l4s4v3n7ur45 |
352 | </paco> |
353 | </users> |
354 | </store> |
355 | <credential> |
356 | password_field password |
357 | password_type clear |
358 | class Password |
359 | </credential> |
360 | </members> |
361 | <openid> |
362 | <ua_args> |
363 | whitelisted_hosts 127.0.0.1 |
364 | whitelisted_hosts localhost |
365 | </ua_args> |
366 | consumer_secret Don't bother setting |
367 | ua_class LWPx::ParanoidAgent |
368 | <credential> |
369 | <store> |
370 | class OpenID |
371 | </store> |
372 | class OpenID |
373 | </credential> |
374 | <extension_args> |
375 | http://openid.net/extensions/sreg/1.1 |
376 | required email |
377 | optional fullname,nickname,timezone |
378 | </extension_args> |
379 | </openid> |
380 | </realms> |
381 | </Plugin::Authentication> |
382 | |
383 | And now, the same configuration in L<YAML>. B<NB>: L<YAML> is whitespace sensitive. |
384 | |
385 | name: MyApp |
386 | Plugin::Authentication: |
387 | default_realm: members |
388 | realms: |
389 | members: |
390 | credential: |
391 | class: Password |
392 | password_field: password |
393 | password_type: clear |
394 | store: |
395 | class: Minimal |
396 | users: |
397 | paco: |
398 | password: l4s4v3n7ur45 |
399 | openid: |
400 | credential: |
401 | class: OpenID |
402 | store: |
403 | class: OpenID |
404 | consumer_secret: Don't bother setting |
405 | ua_class: LWPx::ParanoidAgent |
406 | ua_args: |
407 | whitelisted_hosts: |
408 | - 127.0.0.1 |
409 | - localhost |
410 | extension_args: |
411 | - http://openid.net/extensions/sreg/1.1 |
412 | - required: email |
413 | optional: fullname,nickname,timezone |
414 | |
415 | B<NB>: There is no OpenID store yet. |
416 | |
417 | =head2 EXTENSIONS TO OPENID |
418 | |
419 | The L<Simple Registration|http://openid.net/extensions/sreg/1.1> (SREG) extension to OpenID is supported in the L<Net::OpenID> family now. Experimental support for it is included here as of v0.12. SREG is the only supported extension in OpenID 1.1. It's experimental in the sense it's a new interface and barely tested. Support for OpenID extensions is here to stay. |
420 | |
421 | =head2 MORE ON CONFIGURATION |
422 | |
423 | These are set in your realm. See above. |
424 | |
425 | =over 4 |
426 | |
427 | =item ua_args and ua_class |
428 | |
429 | L<LWPx::ParanoidAgent> is the default agent E<mdash> C<ua_class>. You don't |
430 | have to set it. I recommend that you do B<not> override it. You can |
431 | with any well behaved L<LWP::UserAgent>. You probably should not. |
432 | L<LWPx::ParanoidAgent> buys you many defenses and extra security |
433 | checks. When you allow your application users freedom to initiate |
434 | external requests, you open a big avenue for DoS (denial of service) |
435 | attacks. L<LWPx::ParanoidAgent> defends against this. |
436 | L<LWP::UserAgent> and any regular subclass of it will not. |
437 | |
438 | =item consumer_secret |
439 | |
440 | The underlying L<Net::OpenID::Consumer> object is seeded with a |
441 | secret. If it's important to you to set your own, you can. The default |
442 | uses this package name + its version + the sorted configuration keys |
443 | of your Catalyst application (chopped at 255 characters if it's |
444 | longer). This should generally be superior to any fixed string. |
445 | |
446 | =back |
447 | |
448 | =head1 TODO |
449 | |
450 | Support more of the new methods in the L<Net::OpenID> kit. |
451 | |
452 | There are some interesting implications with this sort of setup. Does |
453 | a user aggregate realms or can a user be signed in under more than one |
454 | realm? The documents could contain a recipe of the self-answering |
455 | OpenID end-point that is in the tests. |
456 | |
457 | Debug statements need to be both expanded and limited via realm |
458 | configuration. |
459 | |
460 | Better diagnostics in errors. Debug info at all consumer calls. |
461 | |
462 | Roles from provider domains? Mapped? Direct? A generic "openid" auto_role? |
463 | |
464 | =head1 THANKS |
465 | |
466 | To Benjamin Trott (L<Catalyst::Plugin::Authentication::OpenID>), Tatsuhiko Miyagawa (L<Catalyst::Plugin::Authentication::Credential::OpenID>), Brad Fitzpatrick for the great OpenID stuff, Martin Atkins for picking up the code to handle OpenID 2.0, and Jay Kuri and everyone else who has made Catalyst such a wonderful framework. |
467 | |
468 | L<Menno Blom|http://search.cpan.org/~blom/> provided a bug fix and the hook to use OpenID extensions. |
469 | |
470 | =head1 LICENSE AND COPYRIGHT |
471 | |
472 | Copyright (c) 2008, Ashley Pond V C<< <ashley@cpan.org> >>. Some of Tatsuhiko Miyagawa's work is reused here. |
473 | |
474 | This module is free software; you can redistribute it and modify it under the same terms as Perl itself. See L<perlartistic>. |
475 | |
476 | =head1 DISCLAIMER OF WARRANTY |
477 | |
478 | Because this software is licensed free of charge, there is no warranty |
479 | for the software, to the extent permitted by applicable law. Except when |
480 | otherwise stated in writing the copyright holders and other parties |
481 | provide the software "as is" without warranty of any kind, either |
482 | expressed or implied, including, but not limited to, the implied |
483 | warranties of merchantability and fitness for a particular purpose. The |
484 | entire risk as to the quality and performance of the software is with |
485 | you. Should the software prove defective, you assume the cost of all |
486 | necessary servicing, repair, or correction. |
487 | |
488 | In no event unless required by applicable law or agreed to in writing |
489 | will any copyright holder, or any other party who may modify or |
490 | redistribute the software as permitted by the above license, be |
491 | liable to you for damages, including any general, special, incidental, |
492 | or consequential damages arising out of the use or inability to use |
493 | the software (including but not limited to loss of data or data being |
494 | rendered inaccurate or losses sustained by you or third parties or a |
495 | failure of the software to operate with any other software), even if |
496 | such holder or other party has been advised of the possibility of |
497 | such damages. |
498 | |
499 | =head1 SEE ALSO |
500 | |
501 | =over 4 |
502 | |
503 | =item OpenID |
504 | |
505 | L<Net::OpenID::Server>, L<Net::OpenID::VerifiedIdentity>, L<Net::OpenID::Consumer>, L<http://openid.net/>, L<http://openid.net/developers/specs/>, and L<http://openid.net/extensions/sreg/1.1>. |
506 | |
507 | =item Catalyst Authentication |
508 | |
509 | L<Catalyst>, L<Catalyst::Plugin::Authentication>, L<Catalyst::Manual::Tutorial::Authorization>, and L<Catalyst::Manual::Tutorial::Authentication>. |
510 | |
511 | =item Catalyst Configuration |
512 | |
513 | L<Catalyst::Plugin::ConfigLoader>, L<Config::General>, and L<YAML>. |
514 | |
515 | =item Miscellaneous |
516 | |
517 | L<Catalyst::Manual::Tutorial>, L<Template>, L<LWPx::ParanoidAgent>. |
518 | |
519 | =back |
520 | |
521 | =cut |
522 | package Catalyst::Authentication::Credential::OpenID; |
523 | use strict; |
524 | # use warnings; no warnings "uninitialized"; # for testing, not production |
525 | use parent "Class::Accessor::Fast"; |
526 | |
527 | BEGIN { |
528 | __PACKAGE__->mk_accessors(qw/ _config realm debug secret /); |
529 | } |
530 | |
531 | our $VERSION = "0.13"; |
532 | |
533 | use Net::OpenID::Consumer; |
534 | use Catalyst::Exception (); |
535 | |
536 | sub new : method { |
537 | my ( $class, $config, $c, $realm ) = @_; |
538 | my $self = { _config => { %{ $config }, |
539 | %{ $realm->{config} } |
540 | } |
541 | }; |
542 | bless $self, $class; |
543 | |
544 | # 2.0 spec says "SHOULD" be named "openid_identifier." |
545 | $self->_config->{openid_field} ||= "openid_identifier"; |
e5b6823d |
546 | |
547 | $self->debug( $self->_config->{debug} ); |
548 | |
549 | my $secret = $self->_config->{consumer_secret} ||= join("+", |
550 | __PACKAGE__, |
551 | $VERSION, |
552 | sort keys %{ $c->config } |
553 | ); |
554 | |
555 | $secret = substr($secret,0,255) if length $secret > 255; |
a404fd1a |
556 | $self->secret($secret); |
bf16184a |
557 | $self->_config->{ua_class} ||= "LWPx::ParanoidAgent"; |
e5b6823d |
558 | |
ab944aad |
559 | my $agent_class = $self->_config->{ua_class}; |
560 | eval "require $agent_class" |
561 | or Catalyst::Exception->throw("Could not 'require' user agent class " . |
562 | $self->_config->{ua_class}); |
e5b6823d |
563 | |
564 | $c->log->debug("Setting consumer secret: " . $secret) if $self->debug; |
565 | |
566 | return $self; |
567 | } |
568 | |
569 | sub authenticate : method { |
570 | my ( $self, $c, $realm, $authinfo ) = @_; |
571 | |
572 | $c->log->debug("authenticate() called from " . $c->request->uri) if $self->debug; |
573 | |
574 | my $field = $self->{_config}->{openid_field}; |
575 | |
576 | my $claimed_uri = $authinfo->{ $field }; |
577 | |
578 | # Its security related so we want to be explicit about GET/POST param retrieval. |
579 | $claimed_uri ||= $c->req->method eq 'GET' ? |
580 | $c->req->query_params->{ $field } : $c->req->body_params->{ $field }; |
581 | |
582 | my $csr = Net::OpenID::Consumer->new( |
583 | ua => $self->_config->{ua_class}->new(%{$self->_config->{ua_args} || {}}), |
584 | args => $c->req->params, |
bf16184a |
585 | consumer_secret => $self->secret, |
e5b6823d |
586 | ); |
587 | |
588 | if ( $claimed_uri ) |
589 | { |
590 | my $current = $c->uri_for($c->req->uri->path); # clear query/fragment... |
591 | |
592 | my $identity = $csr->claimed_identity($claimed_uri) |
593 | or Catalyst::Exception->throw($csr->err); |
594 | |
a404fd1a |
595 | $identity->set_extension_args(@{$self->_config->{extension_args}}) |
596 | if $self->_config->{extension_args}; |
597 | |
e5b6823d |
598 | my $check_url = $identity->check_url( |
599 | return_to => $current . '?openid-check=1', |
600 | trust_root => $current, |
601 | delayed_return => 1, |
602 | ); |
603 | $c->res->redirect($check_url); |
85db8ed7 |
604 | $c->detach(); |
e5b6823d |
605 | } |
606 | elsif ( $c->req->params->{'openid-check'} ) |
607 | { |
608 | if ( my $setup_url = $csr->user_setup_url ) |
609 | { |
610 | $c->res->redirect($setup_url); |
611 | return; |
612 | } |
613 | elsif ( $csr->user_cancel ) |
614 | { |
615 | return; |
616 | } |
617 | elsif ( my $identity = $csr->verified_identity ) |
618 | { |
619 | # This is where we ought to build an OpenID user and verify against the spec. |
620 | my $user = +{ map { $_ => scalar $identity->$_ } |
621 | qw( url display rss atom foaf declared_rss declared_atom declared_foaf foafmaker ) }; |
a404fd1a |
622 | |
623 | for(keys %{$self->{_config}->{extensions}}) { |
624 | $user->{extensions}->{$_} = $identity->signed_extension_fields($_); |
625 | } |
e5b6823d |
626 | |
627 | my $user_obj = $realm->find_user($user, $c); |
628 | |
629 | if ( ref $user_obj ) |
630 | { |
631 | return $user_obj; |
632 | } |
633 | else |
634 | { |
635 | $c->log->debug("Verified OpenID identity failed to load with find_user; bad user_class? Try 'Null.'") if $c->debug; |
636 | return; |
637 | } |
638 | } |
639 | else |
640 | { |
641 | Catalyst::Exception->throw("Error validating identity: " . |
642 | $csr->err); |
643 | } |
644 | } |
d214d0c0 |
645 | return; |
e5b6823d |
646 | } |
647 | |
648 | 1; |
649 | |
650 | __END__ |
651 | |
e5b6823d |
652 | =head1 NAME |
653 | |
ab944aad |
654 | Catalyst::Authentication::Credential::OpenID - OpenID credential for Catalyst::Plugin::Authentication framework. |
e5b6823d |
655 | |
d214d0c0 |
656 | =head1 VERSION |
657 | |
995b096c |
658 | 0.13 |
d214d0c0 |
659 | |
e5b6823d |
660 | =head1 SYNOPSIS |
661 | |
ab944aad |
662 | In MyApp.pm- |
d214d0c0 |
663 | |
e5b6823d |
664 | use Catalyst qw/ |
665 | Authentication |
666 | Session |
667 | Session::Store::FastMmap |
668 | Session::State::Cookie |
669 | /; |
670 | |
ab944aad |
671 | Somewhere in myapp.conf- |
d214d0c0 |
672 | |
673 | <Plugin::Authentication> |
674 | default_realm openid |
675 | <realms> |
676 | <openid> |
d214d0c0 |
677 | <credential> |
d214d0c0 |
678 | class OpenID |
679 | </credential> |
f29585f9 |
680 | ua_class LWPx::ParanoidAgent |
d214d0c0 |
681 | </openid> |
682 | </realms> |
683 | </Plugin::Authentication> |
684 | |
ab944aad |
685 | Or in your myapp.yml if you're using L<YAML> instead- |
d214d0c0 |
686 | |
e5b6823d |
687 | Plugin::Authentication: |
688 | default_realm: openid |
689 | realms: |
690 | openid: |
691 | credential: |
692 | class: OpenID |
d214d0c0 |
693 | ua_class: LWPx::ParanoidAgent |
694 | |
ab944aad |
695 | In a controller, perhaps C<Root::openid>- |
e5b6823d |
696 | |
bf16184a |
697 | sub openid : Local { |
e5b6823d |
698 | my($self, $c) = @_; |
699 | |
700 | if ( $c->authenticate() ) |
701 | { |
702 | $c->flash(message => "You signed in with OpenID!"); |
703 | $c->res->redirect( $c->uri_for('/') ); |
704 | } |
705 | else |
706 | { |
707 | # Present OpenID form. |
708 | } |
bf16184a |
709 | } |
e5b6823d |
710 | |
ab944aad |
711 | And a L<Template> to match in C<openid.tt>- |
d214d0c0 |
712 | |
bf16184a |
713 | <form action="[% c.uri_for('/openid') %]" method="GET" name="openid"> |
714 | <input type="text" name="openid_identifier" class="openid" /> |
715 | <input type="submit" value="Sign in with OpenID" /> |
716 | </form> |
e5b6823d |
717 | |
e5b6823d |
718 | =head1 DESCRIPTION |
719 | |
720 | This is the B<third> OpenID related authentication piece for |
6342195d |
721 | L<Catalyst>. The first E<mdash> L<Catalyst::Plugin::Authentication::OpenID> |
722 | by Benjamin Trott E<mdash> was deprecated by the second E<mdash> |
e5b6823d |
723 | L<Catalyst::Plugin::Authentication::Credential::OpenID> by Tatsuhiko |
6342195d |
724 | Miyagawa E<mdash> and this is an attempt to deprecate both by conforming to |
e5b6823d |
725 | the newish, at the time of this module's inception, realm-based |
726 | authentication in L<Catalyst::Plugin::Authentication>. |
727 | |
d214d0c0 |
728 | 1. Catalyst::Plugin::Authentication::OpenID |
729 | 2. Catalyst::Plugin::Authentication::Credential::OpenID |
730 | 3. Catalyst::Authentication::Credential::OpenID |
e5b6823d |
731 | |
732 | The benefit of this version is that you can use an arbitrary number of |
733 | authentication systems in your L<Catalyst> application and configure |
734 | and call all of them in the same way. |
735 | |
d214d0c0 |
736 | Note that both earlier versions of OpenID authentication use the method |
e5b6823d |
737 | C<authenticate_openid()>. This module uses C<authenticate()> and |
738 | relies on you to specify the realm. You can specify the realm as the |
739 | default in the configuration or inline with each |
740 | C<authenticate()> call; more below. |
741 | |
742 | This module functions quite differently internally from the others. |
743 | See L<Catalyst::Plugin::Authentication::Internals> for more about this |
744 | implementation. |
745 | |
a404fd1a |
746 | =head1 METHODS |
e5b6823d |
747 | |
748 | =over 4 |
749 | |
d214d0c0 |
750 | =item $c->authenticate({},"your_openid_realm"); |
e5b6823d |
751 | |
752 | Call to authenticate the user via OpenID. Returns false if |
753 | authorization is unsuccessful. Sets the user into the session and |
754 | returns the user object if authentication succeeds. |
755 | |
756 | You can see in the call above that the authentication hash is empty. |
757 | The implicit OpenID parameter is, as the 2.0 specification says it |
758 | SHOULD be, B<openid_identifier>. You can set it anything you like in |
759 | your realm configuration, though, under the key C<openid_field>. If |
760 | you call C<authenticate()> with the empty info hash and no configured |
761 | C<openid_field> then only C<openid_identifier> is checked. |
762 | |
763 | It implicitly does this (sort of, it checks the request method too)- |
764 | |
765 | my $claimed_uri = $c->req->params->{openid_identifier}; |
766 | $c->authenticate({openid_identifier => $claimed_uri}); |
767 | |
d214d0c0 |
768 | =item Catalyst::Authentication::Credential::OpenID->new() |
bf16184a |
769 | |
770 | You will never call this. Catalyst does it for you. The only important |
771 | thing you might like to know about it is that it merges its realm |
772 | configuration with its configuration proper. If this doesn't mean |
773 | anything to you, don't worry. |
e5b6823d |
774 | |
bf16184a |
775 | =back |
e5b6823d |
776 | |
777 | =head2 USER METHODS |
778 | |
779 | Currently the only supported user class is L<Catalyst::Plugin::Authentication::User::Hash>. |
780 | |
bf16184a |
781 | =over 4 |
e5b6823d |
782 | |
d214d0c0 |
783 | =item $c->user->url |
e5b6823d |
784 | |
d214d0c0 |
785 | =item $c->user->display |
e5b6823d |
786 | |
d214d0c0 |
787 | =item $c->user->rss |
e5b6823d |
788 | |
d214d0c0 |
789 | =item $c->user->atom |
e5b6823d |
790 | |
d214d0c0 |
791 | =item $c->user->foaf |
e5b6823d |
792 | |
d214d0c0 |
793 | =item $c->user->declared_rss |
e5b6823d |
794 | |
d214d0c0 |
795 | =item $c->user->declared_atom |
e5b6823d |
796 | |
d214d0c0 |
797 | =item $c->user->declared_foaf |
e5b6823d |
798 | |
d214d0c0 |
799 | =item $c->user->foafmaker |
e5b6823d |
800 | |
801 | =back |
802 | |
803 | See L<Net::OpenID::VerifiedIdentity> for details. |
804 | |
805 | =head1 CONFIGURATION |
806 | |
807 | Catalyst authentication is now configured entirely from your |
808 | application's configuration. Do not, for example, put |
809 | C<Credential::OpenID> into your C<use Catalyst ...> statement. |
810 | Instead, tell your application that in one of your authentication |
811 | realms you will use the credential. |
812 | |
813 | In your application the following will give you two different |
814 | authentication realms. One called "members" which authenticates with |
815 | clear text passwords and one called "openid" which uses... uh, OpenID. |
816 | |
817 | __PACKAGE__->config |
818 | ( name => "MyApp", |
819 | "Plugin::Authentication" => { |
820 | default_realm => "members", |
821 | realms => { |
822 | members => { |
823 | credential => { |
824 | class => "Password", |
825 | password_field => "password", |
826 | password_type => "clear" |
827 | }, |
828 | store => { |
829 | class => "Minimal", |
830 | users => { |
831 | paco => { |
832 | password => "l4s4v3n7ur45", |
833 | }, |
834 | } |
835 | } |
836 | }, |
837 | openid => { |
bf16184a |
838 | consumer_secret => "Don't bother setting", |
e5b6823d |
839 | ua_class => "LWPx::ParanoidAgent", |
840 | ua_args => { |
841 | whitelisted_hosts => [qw/ 127.0.0.1 localhost /], |
842 | }, |
843 | credential => { |
844 | class => "OpenID", |
845 | store => { |
846 | class => "OpenID", |
847 | }, |
848 | }, |
a404fd1a |
849 | extension_args => [ |
850 | 'http://openid.net/extensions/sreg/1.1', |
851 | { |
852 | required => 'email', |
853 | optional => 'fullname,nickname,timezone', |
854 | }, |
855 | ], |
e5b6823d |
856 | }, |
857 | }, |
a404fd1a |
858 | } |
859 | ); |
e5b6823d |
860 | |
d214d0c0 |
861 | This is the same configuration in the default L<Catalyst> configuration format from L<Config::General>. |
862 | |
863 | name MyApp |
864 | <Plugin::Authentication> |
865 | default_realm members |
866 | <realms> |
867 | <members> |
868 | <store> |
869 | class Minimal |
870 | <users> |
871 | <paco> |
872 | password l4s4v3n7ur45 |
873 | </paco> |
874 | </users> |
875 | </store> |
876 | <credential> |
877 | password_field password |
878 | password_type clear |
879 | class Password |
880 | </credential> |
881 | </members> |
882 | <openid> |
883 | <ua_args> |
884 | whitelisted_hosts 127.0.0.1 |
885 | whitelisted_hosts localhost |
886 | </ua_args> |
887 | consumer_secret Don't bother setting |
888 | ua_class LWPx::ParanoidAgent |
889 | <credential> |
890 | <store> |
891 | class OpenID |
892 | </store> |
893 | class OpenID |
894 | </credential> |
a404fd1a |
895 | <extension_args> |
896 | http://openid.net/extensions/sreg/1.1 |
897 | required email |
898 | optional fullname,nickname,timezone |
899 | </extension_args> |
d214d0c0 |
900 | </openid> |
901 | </realms> |
902 | </Plugin::Authentication> |
903 | |
904 | And now, the same configuration in L<YAML>. B<NB>: L<YAML> is whitespace sensitive. |
e5b6823d |
905 | |
906 | name: MyApp |
907 | Plugin::Authentication: |
908 | default_realm: members |
909 | realms: |
910 | members: |
911 | credential: |
912 | class: Password |
913 | password_field: password |
914 | password_type: clear |
915 | store: |
916 | class: Minimal |
917 | users: |
918 | paco: |
919 | password: l4s4v3n7ur45 |
920 | openid: |
921 | credential: |
922 | class: OpenID |
923 | store: |
924 | class: OpenID |
bf16184a |
925 | consumer_secret: Don't bother setting |
e5b6823d |
926 | ua_class: LWPx::ParanoidAgent |
927 | ua_args: |
928 | whitelisted_hosts: |
929 | - 127.0.0.1 |
930 | - localhost |
a404fd1a |
931 | extension_args: |
932 | - http://openid.net/extensions/sreg/1.1 |
933 | - required: email |
934 | optional: fullname,nickname,timezone |
e5b6823d |
935 | |
6342195d |
936 | B<NB>: There is no OpenID store yet. |
bf16184a |
937 | |
a404fd1a |
938 | =head2 EXTENSIONS TO OPENID |
939 | |
940 | The L<Simple Registration|http://openid.net/extensions/sreg/1.1> (SREG) extension to OpenID is supported in the L<Net::OpenID> family now. Experimental support for it is included here as of v0.12. SREG is the only supported extension in OpenID 1.1. It's experimental in the sense it's a new interface and barely tested. Support for OpenID extensions is here to stay. |
941 | |
d214d0c0 |
942 | =head2 MORE ON CONFIGURATION |
bf16184a |
943 | |
944 | These are set in your realm. See above. |
945 | |
946 | =over 4 |
947 | |
d214d0c0 |
948 | =item ua_args and ua_class |
bf16184a |
949 | |
6342195d |
950 | L<LWPx::ParanoidAgent> is the default agent E<mdash> C<ua_class>. You don't |
bf16184a |
951 | have to set it. I recommend that you do B<not> override it. You can |
952 | with any well behaved L<LWP::UserAgent>. You probably should not. |
e5b6823d |
953 | L<LWPx::ParanoidAgent> buys you many defenses and extra security |
954 | checks. When you allow your application users freedom to initiate |
955 | external requests, you open a big avenue for DoS (denial of service) |
956 | attacks. L<LWPx::ParanoidAgent> defends against this. |
957 | L<LWP::UserAgent> and any regular subclass of it will not. |
958 | |
d214d0c0 |
959 | =item consumer_secret |
bf16184a |
960 | |
961 | The underlying L<Net::OpenID::Consumer> object is seeded with a |
962 | secret. If it's important to you to set your own, you can. The default |
963 | uses this package name + its version + the sorted configuration keys |
964 | of your Catalyst application (chopped at 255 characters if it's |
965 | longer). This should generally be superior to any fixed string. |
966 | |
967 | =back |
968 | |
e5b6823d |
969 | =head1 TODO |
970 | |
a404fd1a |
971 | Support more of the new methods in the L<Net::OpenID> kit. |
972 | |
e5b6823d |
973 | There are some interesting implications with this sort of setup. Does |
974 | a user aggregate realms or can a user be signed in under more than one |
975 | realm? The documents could contain a recipe of the self-answering |
976 | OpenID end-point that is in the tests. |
977 | |
978 | Debug statements need to be both expanded and limited via realm |
979 | configuration. |
980 | |
bf16184a |
981 | Better diagnostics in errors. Debug info at all consumer calls. |
e5b6823d |
982 | |
bf16184a |
983 | Roles from provider domains? Mapped? Direct? A generic "openid" auto_role? |
e5b6823d |
984 | |
f29585f9 |
985 | =head1 THANKS |
986 | |
a404fd1a |
987 | To Benjamin Trott (L<Catalyst::Plugin::Authentication::OpenID>), Tatsuhiko Miyagawa (L<Catalyst::Plugin::Authentication::Credential::OpenID>), Brad Fitzpatrick for the great OpenID stuff, Martin Atkins for picking up the code to handle OpenID 2.0, and Jay Kuri and everyone else who has made Catalyst such a wonderful framework. |
988 | |
995b096c |
989 | L<Menno Blom|http://search.cpan.org/~blom/> provided a bug fix and the hook to use OpenID extensions. |
f29585f9 |
990 | |
e5b6823d |
991 | =head1 LICENSE AND COPYRIGHT |
992 | |
a404fd1a |
993 | Copyright (c) 2008, Ashley Pond V C<< <ashley@cpan.org> >>. Some of Tatsuhiko Miyagawa's work is reused here. |
e5b6823d |
994 | |
a404fd1a |
995 | This module is free software; you can redistribute it and modify it under the same terms as Perl itself. See L<perlartistic>. |
e5b6823d |
996 | |
e5b6823d |
997 | =head1 DISCLAIMER OF WARRANTY |
998 | |
999 | Because this software is licensed free of charge, there is no warranty |
1000 | for the software, to the extent permitted by applicable law. Except when |
1001 | otherwise stated in writing the copyright holders and other parties |
1002 | provide the software "as is" without warranty of any kind, either |
1003 | expressed or implied, including, but not limited to, the implied |
1004 | warranties of merchantability and fitness for a particular purpose. The |
1005 | entire risk as to the quality and performance of the software is with |
1006 | you. Should the software prove defective, you assume the cost of all |
1007 | necessary servicing, repair, or correction. |
1008 | |
1009 | In no event unless required by applicable law or agreed to in writing |
1010 | will any copyright holder, or any other party who may modify or |
1011 | redistribute the software as permitted by the above license, be |
1012 | liable to you for damages, including any general, special, incidental, |
1013 | or consequential damages arising out of the use or inability to use |
1014 | the software (including but not limited to loss of data or data being |
1015 | rendered inaccurate or losses sustained by you or third parties or a |
1016 | failure of the software to operate with any other software), even if |
1017 | such holder or other party has been advised of the possibility of |
1018 | such damages. |
1019 | |
e5b6823d |
1020 | =head1 SEE ALSO |
1021 | |
d214d0c0 |
1022 | =over 4 |
e5b6823d |
1023 | |
d214d0c0 |
1024 | =item OpenID |
e5b6823d |
1025 | |
a404fd1a |
1026 | L<Net::OpenID::Server>, L<Net::OpenID::VerifiedIdentity>, L<Net::OpenID::Consumer>, L<http://openid.net/>, L<http://openid.net/developers/specs/>, and L<http://openid.net/extensions/sreg/1.1>. |
e5b6823d |
1027 | |
d214d0c0 |
1028 | =item Catalyst Authentication |
1029 | |
1030 | L<Catalyst>, L<Catalyst::Plugin::Authentication>, L<Catalyst::Manual::Tutorial::Authorization>, and L<Catalyst::Manual::Tutorial::Authentication>. |
1031 | |
1200a1ee |
1032 | =item Catalyst Configuration |
d214d0c0 |
1033 | |
1034 | L<Catalyst::Plugin::ConfigLoader>, L<Config::General>, and L<YAML>. |
1035 | |
1036 | =item Miscellaneous |
1037 | |
f29585f9 |
1038 | L<Catalyst::Manual::Tutorial>, L<Template>, L<LWPx::ParanoidAgent>. |
d214d0c0 |
1039 | |
1040 | =back |
1041 | |
e5b6823d |
1042 | =cut |