Remove _config of evil
[catagits/Catalyst-Authentication-Credential-OpenID.git] / lib / Catalyst / Authentication / Credential / OpenID.pm
CommitLineData
e5b6823d 1package Catalyst::Authentication::Credential::OpenID;
d214d0c0 2use strict;
4c4e7c7e 3use warnings;
4use base "Class::Accessor::Fast";
788804c2 5
0638ecfb 6__PACKAGE__->mk_accessors(qw/
7 realm debug secret
8 openid_field
9 consumer_secret
10 ua_class
11 ua_args
12 extension_args
13 errors_are_fatal
14 extensions
15/);
788804c2 16
47a60d41 17our $VERSION = "0.16";
788804c2 18
19use Net::OpenID::Consumer;
20use Catalyst::Exception ();
21
4c4e7c7e 22sub new {
788804c2 23 my ( $class, $config, $c, $realm ) = @_;
0638ecfb 24 my $self = {
25 %{ $config },
26 %{ $realm->{config} }
788804c2 27 };
28 bless $self, $class;
29
30 # 2.0 spec says "SHOULD" be named "openid_identifier."
0638ecfb 31 $self->{openid_field} ||= "openid_identifier";
e5b6823d 32
0638ecfb 33 my $secret = $self->{consumer_secret} ||= join("+",
e5b6823d 34 __PACKAGE__,
35 $VERSION,
36 sort keys %{ $c->config }
37 );
38
39 $secret = substr($secret,0,255) if length $secret > 255;
a404fd1a 40 $self->secret($secret);
1c4d7c1f 41 # If user has no preference we prefer L::PA b/c it can prevent DoS attacks.
0638ecfb 42 my $ua_class = $self->{ua_class} ||= eval "use LWPx::ParanoidAgent" ?
1c4d7c1f 43 "LWPx::ParanoidAgent" : "LWP::UserAgent";
e5b6823d 44
0638ecfb 45 my $agent_class = $self->ua_class;
ab944aad 46 eval "require $agent_class"
47 or Catalyst::Exception->throw("Could not 'require' user agent class " .
0638ecfb 48 $self->ua_class);
e5b6823d 49
50 $c->log->debug("Setting consumer secret: " . $secret) if $self->debug;
51
52 return $self;
53}
54
4c4e7c7e 55sub authenticate {
e5b6823d 56 my ( $self, $c, $realm, $authinfo ) = @_;
57
58 $c->log->debug("authenticate() called from " . $c->request->uri) if $self->debug;
59
0638ecfb 60 my $field = $self->openid_field;
e5b6823d 61
62 my $claimed_uri = $authinfo->{ $field };
63
64 # Its security related so we want to be explicit about GET/POST param retrieval.
65 $claimed_uri ||= $c->req->method eq 'GET' ?
66 $c->req->query_params->{ $field } : $c->req->body_params->{ $field };
67
06f54255 68
e5b6823d 69 my $csr = Net::OpenID::Consumer->new(
0638ecfb 70 ua => $self->ua_class->new(%{$self->ua_args || {}}),
e5b6823d 71 args => $c->req->params,
bf16184a 72 consumer_secret => $self->secret,
e5b6823d 73 );
74
0638ecfb 75 if ( $self->extension_args and $self->debug )
06f54255 76 {
0638ecfb 77 # FIXME - Only on startup, remove extension_args accessor
78 $c->log->warn("The configuration key 'extension_args' is deprecated; use 'extensions'");
06f54255 79 }
80
0638ecfb 81 my @extensions = $self->extensions ?
82 @{ $self->extensions } : $self->extension_args ?
83 @{ $self->extension_args } : ();
1c4d7c1f 84
e5b6823d 85 if ( $claimed_uri )
86 {
87 my $current = $c->uri_for($c->req->uri->path); # clear query/fragment...
88
41427aaf 89 my $identity = $csr->claimed_identity($claimed_uri);
90 unless ( $identity )
91 {
0638ecfb 92 if ( $self->errors_are_fatal )
06f54255 93 {
94 Catalyst::Exception->throw($csr->err);
95 }
96 else
97 {
98 $c->log->error($csr->err . " -- $claimed_uri");
99 $c->detach();
100 }
41427aaf 101 }
e5b6823d 102
5bd16806 103 $identity->set_extension_args(@extensions)
1c4d7c1f 104 if @extensions;
a404fd1a 105
e5b6823d 106 my $check_url = $identity->check_url(
107 return_to => $current . '?openid-check=1',
108 trust_root => $current,
109 delayed_return => 1,
110 );
111 $c->res->redirect($check_url);
85db8ed7 112 $c->detach();
e5b6823d 113 }
114 elsif ( $c->req->params->{'openid-check'} )
115 {
116 if ( my $setup_url = $csr->user_setup_url )
117 {
118 $c->res->redirect($setup_url);
119 return;
120 }
121 elsif ( $csr->user_cancel )
122 {
123 return;
124 }
125 elsif ( my $identity = $csr->verified_identity )
126 {
127 # This is where we ought to build an OpenID user and verify against the spec.
128 my $user = +{ map { $_ => scalar $identity->$_ }
129 qw( url display rss atom foaf declared_rss declared_atom declared_foaf foafmaker ) };
1c4d7c1f 130 # Dude, I did not design the array as hash spec. Don't curse me [apv].
131 my %flat = @extensions;
132 for my $key ( keys %flat )
133 {
134 $user->{extensions}->{$key} = $identity->signed_extension_fields($key);
a404fd1a 135 }
e5b6823d 136
137 my $user_obj = $realm->find_user($user, $c);
138
139 if ( ref $user_obj )
140 {
141 return $user_obj;
142 }
143 else
144 {
06f54255 145 $c->log->debug("Verified OpenID identity failed to load with find_user; bad user_class? Try 'Null.'") if $self->debug;
e5b6823d 146 return;
147 }
148 }
149 else
150 {
0638ecfb 151 $self->errors_are_fatal ?
06f54255 152 Catalyst::Exception->throw("Error validating identity: " . $csr->err)
153 :
154 $c->log->error( $csr->err);
e5b6823d 155 }
156 }
d214d0c0 157 return;
e5b6823d 158}
159
1601;
161
162__END__
163
e5b6823d 164=head1 NAME
165
ab944aad 166Catalyst::Authentication::Credential::OpenID - OpenID credential for Catalyst::Plugin::Authentication framework.
e5b6823d 167
d214d0c0 168=head1 VERSION
169
47a60d41 1700.16
1c4d7c1f 171
06f54255 172=head1 BACKWARDS COMPATIBILITY CHANGES
173
29b37787 174=head2 EXTENSION_ARGS v EXTENSIONS
1c4d7c1f 175
29b37787 176B<NB>: The extensions were previously configured under the key C<extension_args>. They are now configured under C<extensions>. This prevents the need for double configuration but it breaks extensions in your application if you do not change the name. The old version is supported for now but may be phased out at any time.
1c4d7c1f 177
178As previously noted, L</EXTENSIONS TO OPENID>, I have not tested the extensions. I would be grateful for any feedback or, better, tests.
d214d0c0 179
06f54255 180=head2 FATALS
181
182The problems encountered by failed OpenID operations have always been fatals in the past. This is unexpected behavior for most users as it differs from other credentials. Authentication errors here are no longer fatal. Debug/error output is improved to offset the loss of information. If for some reason you would prefer the legacy/fatal behavior, set the configuration variable C<errors_are_fatal> to a true value.
183
e5b6823d 184=head1 SYNOPSIS
185
ab944aad 186In MyApp.pm-
d214d0c0 187
e5b6823d 188 use Catalyst qw/
189 Authentication
190 Session
191 Session::Store::FastMmap
192 Session::State::Cookie
193 /;
194
ab944aad 195Somewhere in myapp.conf-
d214d0c0 196
197 <Plugin::Authentication>
198 default_realm openid
199 <realms>
200 <openid>
d214d0c0 201 <credential>
d214d0c0 202 class OpenID
29b37787 203 ua_class LWP::UserAgent
d214d0c0 204 </credential>
205 </openid>
206 </realms>
207 </Plugin::Authentication>
208
ab944aad 209Or in your myapp.yml if you're using L<YAML> instead-
d214d0c0 210
e5b6823d 211 Plugin::Authentication:
212 default_realm: openid
213 realms:
214 openid:
215 credential:
216 class: OpenID
29b37787 217 ua_class: LWP::UserAgent
d214d0c0 218
ab944aad 219In a controller, perhaps C<Root::openid>-
e5b6823d 220
bf16184a 221 sub openid : Local {
e5b6823d 222 my($self, $c) = @_;
223
224 if ( $c->authenticate() )
225 {
226 $c->flash(message => "You signed in with OpenID!");
227 $c->res->redirect( $c->uri_for('/') );
228 }
229 else
230 {
231 # Present OpenID form.
232 }
bf16184a 233 }
e5b6823d 234
ab944aad 235And a L<Template> to match in C<openid.tt>-
d214d0c0 236
bf16184a 237 <form action="[% c.uri_for('/openid') %]" method="GET" name="openid">
238 <input type="text" name="openid_identifier" class="openid" />
239 <input type="submit" value="Sign in with OpenID" />
240 </form>
e5b6823d 241
e5b6823d 242=head1 DESCRIPTION
243
244This is the B<third> OpenID related authentication piece for
6342195d 245L<Catalyst>. The first E<mdash> L<Catalyst::Plugin::Authentication::OpenID>
246by Benjamin Trott E<mdash> was deprecated by the second E<mdash>
e5b6823d 247L<Catalyst::Plugin::Authentication::Credential::OpenID> by Tatsuhiko
6342195d 248Miyagawa E<mdash> and this is an attempt to deprecate both by conforming to
e5b6823d 249the newish, at the time of this module's inception, realm-based
250authentication in L<Catalyst::Plugin::Authentication>.
251
d214d0c0 252 1. Catalyst::Plugin::Authentication::OpenID
253 2. Catalyst::Plugin::Authentication::Credential::OpenID
254 3. Catalyst::Authentication::Credential::OpenID
e5b6823d 255
256The benefit of this version is that you can use an arbitrary number of
257authentication systems in your L<Catalyst> application and configure
258and call all of them in the same way.
259
d214d0c0 260Note that both earlier versions of OpenID authentication use the method
e5b6823d 261C<authenticate_openid()>. This module uses C<authenticate()> and
262relies on you to specify the realm. You can specify the realm as the
263default in the configuration or inline with each
264C<authenticate()> call; more below.
265
266This module functions quite differently internally from the others.
267See L<Catalyst::Plugin::Authentication::Internals> for more about this
268implementation.
269
a404fd1a 270=head1 METHODS
e5b6823d 271
272=over 4
273
d214d0c0 274=item $c->authenticate({},"your_openid_realm");
e5b6823d 275
276Call to authenticate the user via OpenID. Returns false if
277authorization is unsuccessful. Sets the user into the session and
278returns the user object if authentication succeeds.
279
280You can see in the call above that the authentication hash is empty.
281The implicit OpenID parameter is, as the 2.0 specification says it
282SHOULD be, B<openid_identifier>. You can set it anything you like in
283your realm configuration, though, under the key C<openid_field>. If
284you call C<authenticate()> with the empty info hash and no configured
285C<openid_field> then only C<openid_identifier> is checked.
286
287It implicitly does this (sort of, it checks the request method too)-
288
289 my $claimed_uri = $c->req->params->{openid_identifier};
290 $c->authenticate({openid_identifier => $claimed_uri});
291
d214d0c0 292=item Catalyst::Authentication::Credential::OpenID->new()
bf16184a 293
294You will never call this. Catalyst does it for you. The only important
295thing you might like to know about it is that it merges its realm
296configuration with its configuration proper. If this doesn't mean
297anything to you, don't worry.
e5b6823d 298
bf16184a 299=back
e5b6823d 300
301=head2 USER METHODS
302
303Currently the only supported user class is L<Catalyst::Plugin::Authentication::User::Hash>.
304
bf16184a 305=over 4
e5b6823d 306
d214d0c0 307=item $c->user->url
e5b6823d 308
d214d0c0 309=item $c->user->display
e5b6823d 310
d214d0c0 311=item $c->user->rss
e5b6823d 312
d214d0c0 313=item $c->user->atom
e5b6823d 314
d214d0c0 315=item $c->user->foaf
e5b6823d 316
d214d0c0 317=item $c->user->declared_rss
e5b6823d 318
d214d0c0 319=item $c->user->declared_atom
e5b6823d 320
d214d0c0 321=item $c->user->declared_foaf
e5b6823d 322
d214d0c0 323=item $c->user->foafmaker
e5b6823d 324
325=back
326
327See L<Net::OpenID::VerifiedIdentity> for details.
328
329=head1 CONFIGURATION
330
331Catalyst authentication is now configured entirely from your
332application's configuration. Do not, for example, put
333C<Credential::OpenID> into your C<use Catalyst ...> statement.
334Instead, tell your application that in one of your authentication
335realms you will use the credential.
336
337In your application the following will give you two different
338authentication realms. One called "members" which authenticates with
339clear text passwords and one called "openid" which uses... uh, OpenID.
340
341 __PACKAGE__->config
342 ( name => "MyApp",
343 "Plugin::Authentication" => {
344 default_realm => "members",
345 realms => {
346 members => {
347 credential => {
348 class => "Password",
349 password_field => "password",
350 password_type => "clear"
351 },
352 store => {
353 class => "Minimal",
354 users => {
355 paco => {
356 password => "l4s4v3n7ur45",
357 },
358 }
359 }
360 },
361 openid => {
e5b6823d 362 credential => {
363 class => "OpenID",
364 store => {
365 class => "OpenID",
366 },
29b37787 367 consumer_secret => "Don't bother setting",
368 ua_class => "LWP::UserAgent",
369 # whitelist is only relevant for LWPx::ParanoidAgent
370 ua_args => {
371 whitelisted_hosts => [qw/ 127.0.0.1 localhost /],
a404fd1a 372 },
29b37787 373 extensions => [
374 'http://openid.net/extensions/sreg/1.1',
375 {
376 required => 'email',
377 optional => 'fullname,nickname,timezone',
378 },
379 ],
380 },
e5b6823d 381 },
382 },
a404fd1a 383 }
384 );
e5b6823d 385
d214d0c0 386This is the same configuration in the default L<Catalyst> configuration format from L<Config::General>.
387
388 name MyApp
389 <Plugin::Authentication>
390 default_realm members
391 <realms>
392 <members>
393 <store>
394 class Minimal
395 <users>
396 <paco>
397 password l4s4v3n7ur45
398 </paco>
399 </users>
400 </store>
401 <credential>
402 password_field password
403 password_type clear
404 class Password
405 </credential>
406 </members>
407 <openid>
d214d0c0 408 <credential>
409 <store>
410 class OpenID
411 </store>
412 class OpenID
29b37787 413 <ua_args>
414 whitelisted_hosts 127.0.0.1
415 whitelisted_hosts localhost
416 </ua_args>
417 consumer_secret Don't bother setting
418 ua_class LWP::UserAgent
419 <extensions>
420 http://openid.net/extensions/sreg/1.1
421 required email
422 optional fullname,nickname,timezone
423 </extensions>
d214d0c0 424 </credential>
425 </openid>
426 </realms>
427 </Plugin::Authentication>
428
429And now, the same configuration in L<YAML>. B<NB>: L<YAML> is whitespace sensitive.
e5b6823d 430
431 name: MyApp
432 Plugin::Authentication:
433 default_realm: members
434 realms:
435 members:
436 credential:
437 class: Password
438 password_field: password
439 password_type: clear
440 store:
441 class: Minimal
442 users:
443 paco:
444 password: l4s4v3n7ur45
445 openid:
446 credential:
447 class: OpenID
448 store:
449 class: OpenID
29b37787 450 consumer_secret: Don't bother setting
451 ua_class: LWP::UserAgent
452 ua_args:
453 # whitelist is only relevant for LWPx::ParanoidAgent
454 whitelisted_hosts:
455 - 127.0.0.1
456 - localhost
457 extensions:
458 - http://openid.net/extensions/sreg/1.1
459 - required: email
460 optional: fullname,nickname,timezone
e5b6823d 461
6342195d 462B<NB>: There is no OpenID store yet.
bf16184a 463
a404fd1a 464=head2 EXTENSIONS TO OPENID
465
1c4d7c1f 466The Simple Registration--L<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.
a404fd1a 467
d214d0c0 468=head2 MORE ON CONFIGURATION
bf16184a 469
47a60d41 470=over 4
9881e141 471
d214d0c0 472=item ua_args and ua_class
bf16184a 473
06f54255 474L<LWPx::ParanoidAgent> is the default agent E<mdash> C<ua_class> E<mdash> if it's available, L<LWP::UserAgent> if not. You don't have to set
475it. I recommend that you do B<not> override it. You can with any well behaved L<LWP::UserAgent>. You probably should not.
476L<LWPx::ParanoidAgent> buys you many defenses and extra security checks. When you allow your application users freedom to initiate external
477requests, you open an avenue for DoS (denial of service) attacks. L<LWPx::ParanoidAgent> defends against this. L<LWP::UserAgent> and any
478regular subclass of it will not.
e5b6823d 479
d214d0c0 480=item consumer_secret
bf16184a 481
06f54255 482The underlying L<Net::OpenID::Consumer> object is seeded with a secret. If it's important to you to set your own, you can. The default uses
483this package name + its version + the sorted configuration keys of your Catalyst application (chopped at 255 characters if it's longer).
484This should generally be superior to any fixed string.
bf16184a 485
486=back
487
e5b6823d 488=head1 TODO
489
1c4d7c1f 490Option to suppress fatals.
491
a404fd1a 492Support more of the new methods in the L<Net::OpenID> kit.
493
06f54255 494There are some interesting implications with this sort of setup. Does a user aggregate realms or can a user be signed in under more than one
495realm? The documents could contain a recipe of the self-answering OpenID end-point that is in the tests.
e5b6823d 496
06f54255 497Debug statements need to be both expanded and limited via realm configuration.
e5b6823d 498
bf16184a 499Better diagnostics in errors. Debug info at all consumer calls.
e5b6823d 500
bf16184a 501Roles from provider domains? Mapped? Direct? A generic "openid" auto_role?
e5b6823d 502
f29585f9 503=head1 THANKS
504
a404fd1a 505To 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.
506
1c4d7c1f 507Menno Blom provided a bug fix and the hook to use OpenID extensions.
f29585f9 508
e5b6823d 509=head1 LICENSE AND COPYRIGHT
510
1c4d7c1f 511Copyright (c) 2008-2009, Ashley Pond V C<< <ashley@cpan.org> >>. Some of Tatsuhiko Miyagawa's work is reused here.
e5b6823d 512
a404fd1a 513This module is free software; you can redistribute it and modify it under the same terms as Perl itself. See L<perlartistic>.
e5b6823d 514
e5b6823d 515=head1 DISCLAIMER OF WARRANTY
516
06f54255 517Because this software is licensed free of charge, there is no warranty for the software, to the extent permitted by applicable law. Except
518when otherwise stated in writing the copyright holders and other parties provide the software "as is" without warranty of any kind, either
519expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The
520entire risk as to the quality and performance of the software is with you. Should the software prove defective, you assume the cost of all
e5b6823d 521necessary servicing, repair, or correction.
522
06f54255 523In no event unless required by applicable law or agreed to in writing will any copyright holder, or any other party who may modify or
524redistribute the software as permitted by the above license, be liable to you for damages, including any general, special, incidental, or
525consequential damages arising out of the use or inability to use the software (including but not limited to loss of data or data being
526rendered inaccurate or losses sustained by you or third parties or a failure of the software to operate with any other software), even if
527such holder or other party has been advised of the possibility of such damages.
e5b6823d 528
e5b6823d 529=head1 SEE ALSO
530
d214d0c0 531=over 4
e5b6823d 532
d214d0c0 533=item OpenID
e5b6823d 534
06f54255 535L<Net::OpenID::Server>, L<Net::OpenID::VerifiedIdentity>, L<Net::OpenID::Consumer>, L<http://openid.net/>,
536L<http://openid.net/developers/specs/>, and L<http://openid.net/extensions/sreg/1.1>.
e5b6823d 537
d214d0c0 538=item Catalyst Authentication
539
06f54255 540L<Catalyst>, L<Catalyst::Plugin::Authentication>, L<Catalyst::Manual::Tutorial::Authorization>, and
541L<Catalyst::Manual::Tutorial::Authentication>.
d214d0c0 542
1200a1ee 543=item Catalyst Configuration
d214d0c0 544
545L<Catalyst::Plugin::ConfigLoader>, L<Config::General>, and L<YAML>.
546
547=item Miscellaneous
548
f29585f9 549L<Catalyst::Manual::Tutorial>, L<Template>, L<LWPx::ParanoidAgent>.
d214d0c0 550
551=back
552
e5b6823d 553=cut