Dinks pre release
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Plugin / Authentication.pm
CommitLineData
06675d2e 1package Catalyst::Plugin::Authentication;
2
b003080b 3use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
06675d2e 4
b003080b 5BEGIN {
7bb06c91 6 __PACKAGE__->mk_accessors(qw/_user/);
b003080b 7}
06675d2e 8
9use strict;
10use warnings;
11
96777f3a 12use Tie::RefHash;
12dae309 13use Class::Inspector;
5c5af345 14use Catalyst::Authentication::Realm;
96777f3a 15
5d3b0def 16our $VERSION = "0.10009_01";
c7c003d3 17
06675d2e 18sub set_authenticated {
54c8dc06 19 my ( $c, $user, $realmname ) = @_;
06675d2e 20
21 $c->user($user);
e300c5b6 22 $c->request->{user} = $user; # compatibility kludge
06675d2e 23
54c8dc06 24 if (!$realmname) {
25 $realmname = 'default';
06675d2e 26 }
646ea5b1 27 my $realm = $c->get_auth_realm($realmname);
28
29 if (!$realm) {
30 Catalyst::Exception->throw(
31 "set_authenticated called with nonexistant realm: '$realmname'.");
32 }
646ea5b1 33 $user->auth_realm($realm->name);
8a7bd676 34
35 $c->persist_user();
54c8dc06 36
37 $c->NEXT::set_authenticated($user, $realmname);
06675d2e 38}
39
7bb06c91 40sub user {
e300c5b6 41 my $c = shift;
7bb06c91 42
e300c5b6 43 if (@_) {
44 return $c->_user(@_);
45 }
7bb06c91 46
45c7644b 47 if ( defined($c->_user) ) {
48 return $c->_user;
56e23e7a 49 } else {
47c6643f 50 return $c->auth_restore_user;
e300c5b6 51 }
7bb06c91 52}
53
54c8dc06 54# change this to allow specification of a realm - to verify the user is part of that realm
8a7bd676 55# in addition to verifying that they exist.
ce0b058d 56sub user_exists {
57 my $c = shift;
bf4d93a4 58 return defined($c->_user) || defined($c->find_realm_for_persisted_user);
56e23e7a 59}
60
45c7644b 61# works like user_exists - except only returns true if user
62# exists AND is in the realm requested.
63sub user_in_realm {
64 my ($c, $realmname) = @_;
65
66 if (defined($c->_user)) {
67 return ($c->_user->auth_realm eq $realmname);
45c7644b 68 } else {
bf4d93a4 69 my $realm = $c->find_realm_for_persisted_user;
8a7bd676 70 if ($realm) {
71 return ($realm->name eq $realmname);
72 } else {
73 return undef;
74 }
45c7644b 75 }
76}
54c8dc06 77
646ea5b1 78sub __old_save_user_in_session {
e8919861 79 my ( $c, $user, $realmname ) = @_;
12dae309 80
54c8dc06 81 $c->session->{__user_realm} = $realmname;
82
45c7644b 83 # we want to ask the store for a user prepared for the session.
54c8dc06 84 # but older modules split this functionality between the user and the
45c7644b 85 # store. We try the store first. If not, we use the old method.
54c8dc06 86 my $realm = $c->get_auth_realm($realmname);
87 if ($realm->{'store'}->can('for_session')) {
88 $c->session->{__user} = $realm->{'store'}->for_session($c, $user);
89 } else {
90 $c->session->{__user} = $user->for_session;
91 }
12dae309 92}
93
8a7bd676 94sub persist_user {
95 my $c = shift;
96
97 if ($c->user_exists) {
98
99 ## if we have a valid session handler - we store the
100 ## realm in the session. If not - we have to hope that
5812e0a3 101 ## the realm can recognize its frozen user somehow.
8a7bd676 102 if ($c->isa("Catalyst::Plugin::Session") &&
103 $c->config->{'Plugin::Authentication'}{'use_session'} &&
104 $c->session_is_valid) {
105
106 $c->session->{'__user_realm'} = $c->_user->auth_realm;
107 }
108
109 my $realm = $c->get_auth_realm($c->_user->auth_realm);
110
111 # used to call $realm->save_user_in_session
112 $realm->persist_user($c, $c->user);
113 }
114}
115
116
117## this was a short lived method to update user information -
118## you should use persist_user instead.
119sub update_user_in_session {
120 my $c = shift;
121
122 return $c->persist_user;
123}
124
06675d2e 125sub logout {
126 my $c = shift;
127
128 $c->user(undef);
b003080b 129
bf4d93a4 130 my $realm = $c->find_realm_for_persisted_user;
8a7bd676 131 if ($realm) {
132 $realm->remove_persisted_user($c);
b003080b 133 }
351e2a82 134
135 $c->NEXT::logout(@_);
06675d2e 136}
137
54c8dc06 138sub find_user {
139 my ( $c, $userinfo, $realmname ) = @_;
140
141 $realmname ||= 'default';
142 my $realm = $c->get_auth_realm($realmname);
646ea5b1 143
144 if (!$realm) {
145 Catalyst::Exception->throw(
146 "find_user called with nonexistant realm: '$realmname'.");
7d0922d8 147 }
646ea5b1 148 return $realm->find_user($userinfo, $c);
7d0922d8 149}
150
bf4d93a4 151## Consider making this a public method. - would make certain things easier when
152## dealing with things pre-auth restore.
153sub find_realm_for_persisted_user {
47c6643f 154 my $c = shift;
8a7bd676 155
156 my $realm;
157 if ($c->isa("Catalyst::Plugin::Session")
7c4d44af 158 and $c->config->{'Plugin::Authentication'}{'use_session'}
8a7bd676 159 and $c->session_is_valid
160 and exists($c->session->{'__user_realm'})) {
161
162 $realm = $c->auth_realms->{$c->session->{'__user_realm'}};
163 if ($realm->user_is_restorable($c)) {
164 return $realm;
165 }
166 } else {
167 ## we have no choice but to ask each realm whether it has a persisted user.
168 foreach my $realmname (@{$c->_auth_realm_restore_order}) {
169 my $ret = $c->auth_realms->{$realmname}->user_is_restorable($c);
170 if ($ret) {
171 return $c->auth_realms->{$realmname};
172 }
173 }
174 }
175 return undef;
488433fd 176}
47c6643f 177
7bb06c91 178sub auth_restore_user {
54c8dc06 179 my ( $c, $frozen_user, $realmname ) = @_;
7bb06c91 180
8a7bd676 181 my $realm;
182 if (defined($realmname)) {
183 $realm = $c->get_auth_realm($realmname);
184 } else {
bf4d93a4 185 $realm = $c->find_realm_for_persisted_user;
8a7bd676 186 }
bf4d93a4 187 return undef unless $realm; # FIXME die unless? This is an internal inconsistency
188
8a7bd676 189 $c->_user( my $user = $realm->restore_user( $c, $frozen_user ) );
54c8dc06 190
191 # this sets the realm the user originated in.
8a7bd676 192 $user->auth_realm($realm->name);
daed2d14 193
e300c5b6 194 return $user;
7bb06c91 195
196}
197
54c8dc06 198# we can't actually do our setup in setup because the model has not yet been loaded.
199# So we have to trigger off of setup_finished. :-(
06675d2e 200sub setup {
c5fbff80 201 my $app = shift;
06675d2e 202
c5fbff80 203 $app->_authentication_initialize();
204 $app->NEXT::setup(@_);
54c8dc06 205}
206
207## the actual initialization routine. whee.
208sub _authentication_initialize {
c5fbff80 209 my $app = shift;
54c8dc06 210
d6209239 211 ## let's avoid recreating / configuring everything if we have already done it, eh?
212 if ($app->can('_auth_realms')) { return };
c5fbff80 213
d6209239 214 ## make classdata where it is used.
215 $app->mk_classdata( '_auth_realms' => {});
937d5ab9 216
8a7bd676 217 ## the order to attempt restore in - If we don't have session - we have
218 ## no way to be sure where a frozen user came from - so we have to
219 ## ask each realm if it can restore the user. Unfortunately it is possible
220 ## that multiple realms could restore the user from the data we have -
221 ## So we have to determine at setup time what order to ask the realms in.
222 ## The default is to use the user_restore_priority values defined in the realm
223 ## config. if they are not defined - we go by alphabetical order. Note that
224 ## the 'default' realm always gets first chance at it unless it is explicitly
225 ## placed elsewhere by user_restore_priority. Remember this only comes
226 ## into play if session is disabled.
227
228 $app->mk_classdata( '_auth_realm_restore_order' => []);
bf4d93a4 229
7c4d44af 230 my $cfg = $app->config->{'Plugin::Authentication'};
bf4d93a4 231 my $realmshash;
7c4d44af 232 if (!defined($cfg)) {
233 if (exists($app->config->{'authentication'})) {
234 $cfg = $app->config->{'authentication'};
235 $app->config->{'Plugin::Authentication'} = $app->config->{'authentication'};
236 } else {
237 $cfg = {};
238 }
bf4d93a4 239 } else {
240 # the realmshash contains the various configured realms. By default this is
241 # the main $app->config->{'Plugin::Authentication'} hash - but if that is
242 # not defined, or there is a subkey {'realms'} then we use that.
243 $realmshash = $cfg;
244 }
245
246 ## If we have a sub-key of {'realms'} then we use that for realm configuration
247 if (exists($cfg->{'realms'})) {
248 $realmshash = $cfg->{'realms'};
249 }
06675d2e 250
7c4d44af 251 # old default was to force use_session on. This must remain for that
5812e0a3 252 # reason - but if use_session is already in the config, we respect its setting.
7c4d44af 253 if (!exists($cfg->{'use_session'})) {
254 $cfg->{'use_session'} = 1;
255 }
c5fbff80 256
bf4d93a4 257 ## if we have a realms hash
258 if (ref($realmshash) eq 'HASH') {
8a7bd676 259
260 my %auth_restore_order;
261 my $authcount = 2;
262 my $defaultrealm = 'default';
bf4d93a4 263
264 foreach my $realm (sort keys %{$realmshash}) {
265 if (ref($realmshash->{$realm}) eq 'HASH' &&
266 (exists($realmshash->{$realm}{credential}) || exists($realmshash->{$realm}{class}))) {
267
268 $app->setup_auth_realm($realm, $realmshash->{$realm});
8a7bd676 269
bf4d93a4 270 if (exists($realmshash->{$realm}{'user_restore_priority'})) {
271 $auth_restore_order{$realm} = $realmshash->{$realm}{'user_restore_priority'};
272 } else {
273 $auth_restore_order{$realm} = $authcount++;
274 }
275 }
54c8dc06 276 }
8a7bd676 277
278 # if we have a 'default_realm' in the config hash and we don't already
54c8dc06 279 # have a realm called 'default', we point default at the realm specified
c5fbff80 280 if (exists($cfg->{'default_realm'}) && !$app->get_auth_realm('default')) {
8a7bd676 281 if ($app->_set_default_auth_realm($cfg->{'default_realm'})) {
282 $defaultrealm = $cfg->{'default_realm'};
283 $auth_restore_order{'default'} = $auth_restore_order{$cfg->{'default_realm'}};
284 delete($auth_restore_order{$cfg->{'default_realm'}});
285 }
54c8dc06 286 }
8a7bd676 287
5812e0a3 288 ## if the default realm did not have a defined priority in its config - we put it at the front.
bf4d93a4 289 if (!exists($realmshash->{$defaultrealm}{'user_restore_priority'})) {
8a7bd676 290 $auth_restore_order{'default'} = 1;
291 }
292
293 @{$app->_auth_realm_restore_order} = sort { $auth_restore_order{$a} <=> $auth_restore_order{$b} } keys %auth_restore_order;
294
54c8dc06 295 } else {
c5fbff80 296
58db3441 297 ## BACKWARDS COMPATIBILITY - if realms is not defined - then we are probably dealing
c5fbff80 298 ## with an old-school config. The only caveat here is that we must add a classname
299
58db3441 300 ## also - we have to treat {store} as {stores}{default} - because
301 ## while it is not a clear as a valid config in the docs, it
302 ## is functional with the old api. Whee!
303 if (exists($cfg->{'store'}) && !exists($cfg->{'stores'}{'default'})) {
304 $cfg->{'stores'}{'default'} = $cfg->{'store'};
305 }
306
8a7bd676 307 push @{$app->_auth_realm_restore_order}, 'default';
54c8dc06 308 foreach my $storename (keys %{$cfg->{'stores'}}) {
309 my $realmcfg = {
58db3441 310 store => { class => $cfg->{'stores'}{$storename} },
54c8dc06 311 };
128321cc 312 print STDERR "Foo, ok?\n";
c5fbff80 313 $app->setup_auth_realm($storename, $realmcfg);
54c8dc06 314 }
8a7bd676 315 }
54c8dc06 316
06675d2e 317}
318
54c8dc06 319# set up realmname.
320sub setup_auth_realm {
321 my ($app, $realmname, $config) = @_;
322
e05c457e 323 my $realmclass = $config->{class};
324
325 if( !$realmclass ) {
5c5af345 326 $realmclass = 'Catalyst::Authentication::Realm';
e05c457e 327 } elsif ($realmclass !~ /^\+(.*)$/ ) {
5c5af345 328 $realmclass = "Catalyst::Authentication::Realm::${realmclass}";
e05c457e 329 } else {
330 $realmclass = $1;
54c8dc06 331 }
e05c457e 332
333 Catalyst::Utils::ensure_class_loaded( $realmclass );
334
646ea5b1 335 my $realm = $realmclass->new($realmname, $config, $app);
336 if ($realm) {
337 $app->auth_realms->{$realmname} = $realm;
58db3441 338 } else {
646ea5b1 339 $app->log->debug("realm initialization for '$realmname' failed.");
58db3441 340 }
646ea5b1 341 return $realm;
96777f3a 342}
343
54c8dc06 344sub auth_realms {
345 my $self = shift;
346 return($self->_auth_realms);
96777f3a 347}
348
54c8dc06 349sub get_auth_realm {
350 my ($app, $realmname) = @_;
8a7bd676 351
54c8dc06 352 return $app->auth_realms->{$realmname};
8a7bd676 353
54c8dc06 354}
96777f3a 355
e8919861 356
357# Very internal method. Vital Valuable Urgent, Do not touch on pain of death.
358# Using this method just assigns the default realm to be the value associated
359# with the realmname provided. It WILL overwrite any real realm called 'default'
360# so can be very confusing if used improperly. It's used properly already.
361# Translation: don't use it.
362sub _set_default_auth_realm {
54c8dc06 363 my ($app, $realmname) = @_;
364
365 if (exists($app->auth_realms->{$realmname})) {
366 $app->auth_realms->{'default'} = $app->auth_realms->{$realmname};
12dae309 367 }
54c8dc06 368 return $app->get_auth_realm('default');
96777f3a 369}
370
54c8dc06 371sub authenticate {
372 my ($app, $userinfo, $realmname) = @_;
373
374 if (!$realmname) {
375 $realmname = 'default';
376 }
377
378 my $realm = $app->get_auth_realm($realmname);
379
45c7644b 380 ## note to self - make authenticate throw an exception if realm is invalid.
381
646ea5b1 382 if ($realm) {
383 return $realm->authenticate($app, $userinfo);
54c8dc06 384 } else {
646ea5b1 385 Catalyst::Exception->throw(
386 "authenticate called with nonexistant realm: '$realmname'.");
387
54c8dc06 388 }
0cc778ab 389 return undef;
96777f3a 390}
391
54c8dc06 392## BACKWARDS COMPATIBILITY -- Warning: Here be monsters!
393#
394# What follows are backwards compatibility routines - for use with Stores and Credentials
395# that have not been updated to work with C::P::Authentication v0.10.
396# These are here so as to not break people's existing installations, but will go away
397# in a future version.
398#
399# The old style of configuration only supports a single store, as each store module
400# sets itself as the default store upon being loaded. This is the only supported
401# 'compatibility' mode.
402#
403
404sub get_user {
405 my ( $c, $uid, @rest ) = @_;
96777f3a 406
54c8dc06 407 return $c->find_user( {'id' => $uid, 'rest'=>\@rest }, 'default' );
96777f3a 408}
409
e8919861 410
54c8dc06 411## this should only be called when using old-style authentication plugins. IF this gets
412## called in a new-style config - it will OVERWRITE the store of your default realm. Don't do it.
413## also - this is a partial setup - because no credential is instantiated... in other words it ONLY
414## works with old-style auth plugins and C::P::Authentication in compatibility mode. Trying to combine
415## this with a realm-type config will probably crash your app.
96777f3a 416sub default_auth_store {
12dae309 417 my $self = shift;
96777f3a 418
646ea5b1 419 my $realm = $self->get_auth_realm('default');
420 if (!$realm) {
e05c457e 421 $realm = $self->setup_auth_realm('default', { class => 'Compatibility' });
646ea5b1 422 }
12dae309 423 if ( my $new = shift ) {
646ea5b1 424 $realm->store($new);
58db3441 425
426 my $storeclass;
427 if (ref($new)) {
428 $storeclass = ref($new);
429 } else {
430 $storeclass = $new;
431 }
54c8dc06 432
433 # BACKWARDS COMPATIBILITY - if the store class does not define find_user, we define it in terms
434 # of get_user and add it to the class. this is because the auth routines use find_user,
435 # and rely on it being present. (this avoids per-call checks)
436 if (!$storeclass->can('find_user')) {
437 no strict 'refs';
438 *{"${storeclass}::find_user"} = sub {
439 my ($self, $info) = @_;
440 my @rest = @{$info->{rest}} if exists($info->{rest});
441 $self->get_user($info->{id}, @rest);
442 };
443 }
12dae309 444 }
96777f3a 445
646ea5b1 446 return $self->get_auth_realm('default')->store;
96777f3a 447}
448
54c8dc06 449## BACKWARDS COMPATIBILITY
450## this only ever returns a hash containing 'default' - as that is the only
451## supported mode of calling this.
452sub auth_store_names {
453 my $self = shift;
454
646ea5b1 455 my %hash = ( $self->get_auth_realm('default')->store => 'default' );
54c8dc06 456}
457
458sub get_auth_store {
459 my ( $self, $name ) = @_;
460
461 if ($name ne 'default') {
462 Carp::croak "get_auth_store called on non-default realm '$name'. Only default supported in compatibility mode";
463 } else {
464 $self->default_auth_store();
465 }
466}
467
468sub get_auth_store_name {
469 my ( $self, $store ) = @_;
470 return 'default';
471}
472
473# sub auth_stores is only used internally - here for completeness
474sub auth_stores {
475 my $self = shift;
476
646ea5b1 477 my %hash = ( 'default' => $self->get_auth_realm('default')->store);
54c8dc06 478}
479
06675d2e 480__PACKAGE__;
481
482__END__
483
484=pod
485
486=head1 NAME
487
55395841 488Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst
489authentication framework.
06675d2e 490
491=head1 SYNOPSIS
492
189b5b0c 493 use Catalyst qw/
494 Authentication
189b5b0c 495 /;
496
497 # later on ...
c5fbff80 498 $c->authenticate({ username => 'myusername',
499 password => 'mypassword' });
54c8dc06 500 my $age = $c->user->get('age');
189b5b0c 501 $c->logout;
06675d2e 502
503=head1 DESCRIPTION
504
16ef3eb8 505The authentication plugin provides generic user support for Catalyst apps. It
506is the basis for both authentication (checking the user is who they claim to
507be), and authorization (allowing the user to do what the system authorises
508them to do).
509
510Using authentication is split into two parts. A Store is used to actually
511store the user information, and can store any amount of data related to the
512user. Credentials are used to verify users, using information from the store,
513given data from the frontend. A Credential and a Store are paired to form a
e8919861 514'Realm'. A Catalyst application using the authentication framework must have
515at least one realm, and may have several.
189b5b0c 516
6a36933d 517To implement authentication in a Catalyst application you need to add this
16ef3eb8 518module, and specify at least one realm in the configuration.
189b5b0c 519
e7522758 520Authentication data can also be stored in a session, if the application
521is using the L<Catalyst::Plugin::Session> module.
06675d2e 522
30e90c6f 523B<NOTE> in version 0.10 of this module, the interface to this module changed.
524Please see L</COMPATIBILITY ROUTINES> for more information.
e8919861 525
4bb9b01c 526=head1 INTRODUCTION
527
528=head2 The Authentication/Authorization Process
529
530Web applications typically need to identify a user - to tell the user apart
531from other users. This is usually done in order to display private information
532that is only that user's business, or to limit access to the application so
533that only certain entities can access certain parts.
534
535This process is split up into several steps. First you ask the user to identify
536themselves. At this point you can't be sure that the user is really who they
537claim to be.
538
6a36933d 539Then the user tells you who they are, and backs this claim with some piece of
4bb9b01c 540information that only the real user could give you. For example, a password is
541a secret that is known to both the user and you. When the user tells you this
542password you can assume they're in on the secret and can be trusted (ignore
543identity theft for now). Checking the password, or any other proof is called
544B<credential verification>.
545
546By this time you know exactly who the user is - the user's identity is
16ef3eb8 547B<authenticated>. This is where this module's job stops, and your application
548or other plugins step in.
549
550The next logical step is B<authorization>, the process of deciding what a user
551is (or isn't) allowed to do. For example, say your users are split into two
552main groups - regular users and administrators. You want to verify that the
4bb9b01c 553currently logged in user is indeed an administrator before performing the
c5fbff80 554actions in an administrative part of your application. These decisions may be
16ef3eb8 555made within your application code using just the information available after
556authentication, or it may be facilitated by a number of plugins.
4bb9b01c 557
558=head2 The Components In This Framework
559
62f27384 560=head3 Realms
561
562Configuration of the Catalyst::Plugin::Authentication framework is done in
563terms of realms. In simplest terms, a realm is a pairing of a Credential
5afc0dde 564verifier and a User storage (Store) backend. As of version 0.10003, realms are
565now objects that you can create and customize.
62f27384 566
567An application can have any number of Realms, each of which operates
34088132 568independent of the others. Each realm has a name, which is used to identify it
62f27384 569as the target of an authentication request. This name can be anything, such as
570'users' or 'members'. One realm must be defined as the default_realm, which is
16ef3eb8 571used when no realm name is specified. More information about configuring
572realms is available in the configuration section.
62f27384 573
4bb9b01c 574=head3 Credential Verifiers
575
4e86cf54 576When user input is transferred to the L<Catalyst> application
577(typically via form inputs) the application may pass this information
e979170b 578into the authentication system through the C<< $c->authenticate() >>
4e86cf54 579method. From there, it is passed to the appropriate Credential
580verifier.
4bb9b01c 581
582These plugins check the data, and ensure that it really proves the user is who
583they claim to be.
584
4e86cf54 585Credential verifiers compatible with versions of this module 0.10x and
586upwards should be in the namespace
587C<Catalyst::Authentication::Credential>.
588
4bb9b01c 589=head3 Storage Backends
590
45c7644b 591The authentication data also identifies a user, and the Storage backend modules
62f27384 592use this data to locate and return a standardized object-oriented
593representation of a user.
4bb9b01c 594
595When a user is retrieved from a store it is not necessarily authenticated.
62f27384 596Credential verifiers accept a set of authentication data and use this
597information to retrieve the user from the store they are paired with.
4bb9b01c 598
34088132 599Storage backends compatible with versions of this module 0.10x and
4e86cf54 600upwards should be in the namespace
601C<Catalyst::Authentication::Store>.
602
4bb9b01c 603=head3 The Core Plugin
604
62f27384 605This plugin on its own is the glue, providing realm configuration, session
4bb9b01c 606integration, and other goodness for the other plugins.
607
608=head3 Other Plugins
609
610More layers of plugins can be stacked on top of the authentication code. For
611example, L<Catalyst::Plugin::Session::PerUser> provides an abstraction of
4e86cf54 612browser sessions that is more persistent per user.
4bb9b01c 613L<Catalyst::Plugin::Authorization::Roles> provides an accepted way to separate
614and group users into categories, and then check which categories the current
615user belongs to.
616
5e91c057 617=head1 EXAMPLE
618
34088132 619Let's say we were storing users in a simple Perl hash. Users are
16ef3eb8 620verified by supplying a password which is matched within the hash.
5e91c057 621
622This means that our application will begin like this:
623
624 package MyApp;
625
626 use Catalyst qw/
627 Authentication
5e91c057 628 /;
629
7c4d44af 630 __PACKAGE__->config->{'Plugin::Authentication'} =
c5fbff80 631 {
bf4d93a4 632 default => {
633 credential => {
634 class => 'Password',
635 password_field => 'password',
636 password_type => 'clear'
637 },
638 store => {
639 class => 'Minimal',
640 users => {
641 bob => {
642 password => "s00p3r",
643 editor => 'yes',
644 roles => [qw/edit delete/],
645 },
646 william => {
647 password => "s3cr3t",
648 roles => [qw/comment/],
649 }
650 }
651 }
652 }
c5fbff80 653 };
5e91c057 654
16ef3eb8 655This tells the authentication plugin what realms are available, which
656credential and store modules are used, and the configuration of each. With
657this code loaded, we can now attempt to authenticate users.
5e91c057 658
62f27384 659To show an example of this, let's create an authentication controller:
5e91c057 660
661 package MyApp::Controller::Auth;
662
663 sub login : Local {
664 my ( $self, $c ) = @_;
665
4e143b06 666 if ( my $user = $c->req->params->{user}
34088132 667 and my $password = $c->req->params->{password} )
5e91c057 668 {
62f27384 669 if ( $c->authenticate( { username => $user,
670 password => $password } ) ) {
671 $c->res->body( "hello " . $c->user->get("name") );
5e91c057 672 } else {
673 # login incorrect
674 }
675 }
676 else {
677 # invalid form input
678 }
679 }
680
4e86cf54 681This code should be self-explanatory. If all the necessary fields are supplied,
682call the C<authenticate> method on the context object. If it succeeds the
c5fbff80 683user is logged in.
5e91c057 684
4e86cf54 685The credential verifier will attempt to retrieve the user whose
686details match the authentication information provided to
e979170b 687C<< $c->authenticate() >>. Once it fetches the user the password is
4e86cf54 688checked and if it matches the user will be B<authenticated> and
e979170b 689C<< $c->user >> will contain the user object retrieved from the store.
5e91c057 690
62f27384 691In the above case, the default realm is checked, but we could just as easily
692check an alternate realm. If this were an admin login, for example, we could
e979170b 693authenticate on the admin realm by simply changing the C<< $c->authenticate() >>
62f27384 694call:
5e91c057 695
62f27384 696 if ( $c->authenticate( { username => $user,
4e86cf54 697 password => $password }, 'admin' ) ) {
62f27384 698 $c->res->body( "hello " . $c->user->get("name") );
699 } ...
5e91c057 700
5e91c057 701
c5fbff80 702Now suppose we want to restrict the ability to edit to a user with an
703'editor' value of yes.
5e91c057 704
62f27384 705The restricted action might look like this:
5e91c057 706
62f27384 707 sub edit : Local {
5e91c057 708 my ( $self, $c ) = @_;
709
710 $c->detach("unauthorized")
711 unless $c->user_exists
c5fbff80 712 and $c->user->get('editor') eq 'yes';
5e91c057 713
714 # do something restricted here
715 }
716
4e86cf54 717(Note that if you have multiple realms, you can use
e979170b 718C<< $c->user_in_realm('realmname') >> in place of
719C<< $c->user_exists(); >> This will essentially perform the same
4e86cf54 720verification as user_exists, with the added requirement that if there
721is a user, it must have come from the realm specified.)
c5fbff80 722
723The above example is somewhat similar to role based access control.
5c5af345 724L<Catalyst::Authentication::Store::Minimal> treats the roles field as
62f27384 725an array of role names. Let's leverage this. Add the role authorization
726plugin:
5e91c057 727
728 use Catalyst qw/
729 ...
730 Authorization::Roles
731 /;
732
62f27384 733 sub edit : Local {
5e91c057 734 my ( $self, $c ) = @_;
735
128321cc 736 $c->detach("unauthorized") unless $c->check_user_roles("edit");
5e91c057 737
738 # do something restricted here
739 }
740
741This is somewhat simpler and will work if you change your store, too, since the
742role interface is consistent.
743
34088132 744Let's say your app grows, and you now have 10,000 users. It's no longer
0cc778ab 745efficient to maintain a hash of users, so you move this data to a database.
4e86cf54 746You can accomplish this simply by installing the L<DBIx::Class|Catalyst::Authentication::Store::DBIx::Class> Store and
0cc778ab 747changing your config:
5e91c057 748
7c4d44af 749 __PACKAGE__->config->{'Plugin::Authentication'} =
0cc778ab 750 {
751 default_realm => 'members',
0cc778ab 752 members => {
753 credential => {
c5fbff80 754 class => 'Password',
755 password_field => 'password',
756 password_type => 'clear'
0cc778ab 757 },
758 store => {
759 class => 'DBIx::Class',
760 user_class => 'MyApp::Users',
761 role_column => 'roles'
762 }
0cc778ab 763 }
bf4d93a4 764 };
765
766The authentication system works behind the scenes to load your data from the
767new source. The rest of your application is completely unchanged.
768
769
770=head1 CONFIGURATION
771
772 # example
773 __PACKAGE__->config->{'Plugin::Authentication'} =
774 {
775 default_realm => 'members',
776
777 members => {
778 credential => {
779 class => 'Password',
780 password_field => 'password',
781 password_type => 'clear'
782 },
783 store => {
784 class => 'DBIx::Class',
785 user_class => 'MyApp::Users',
786 role_column => 'roles'
787 }
788 },
789 admins => {
790 credential => {
791 class => 'Password',
792 password_field => 'password',
793 password_type => 'clear'
794 },
795 store => {
796 class => '+MyApp::Authentication::Store::NetAuth',
797 authserver => '192.168.10.17'
798 }
799 }
0cc778ab 800 };
801
078c2289 802=over 4
803
189b5b0c 804=item use_session
805
806Whether or not to store the user's logged in state in the session, if the
e8919861 807application is also using L<Catalyst::Plugin::Session>. This
e7522758 808value is set to true per default.
809
0cc778ab 810=item default_realm
fe4cf44a 811
0cc778ab 812This defines which realm should be used as when no realm is provided to methods
813that require a realm such as authenticate or find_user.
7d0922d8 814
bf4d93a4 815=item realm refs
7d0922d8 816
bf4d93a4 817The Plugin::Authentication config hash contains the series of realm
818configurations you want to use for your app. The only rule here is
819that there must be at least one. A realm consists of a name, which is used
820to reference the realm, a credential and a store. You may also put your
821realm configurations within a subelement called 'realms' if you desire to
822separate them from the remainder of your configuration. Note that if you use
823a 'realms' subelement, you must put ALL of your realms within it.
4fbe2e14 824
7c3d201d 825You can also specify a realm class to instantiate instead of the default
826L<Catalyst::Authentication::Realm> class using the 'class' element within the
827realm config.
5afc0dde 828
0cc778ab 829Each realm config contains two hashes, one called 'credential' and one called
830'store', each of which provide configuration details to the respective modules.
831The contents of these hashes is specific to the module being used, with the
832exception of the 'class' element, which tells the core Authentication module the
e8919861 833classname to instantiate.
4fbe2e14 834
0cc778ab 835The 'class' element follows the standard Catalyst mechanism of class
836specification. If a class is prefixed with a +, it is assumed to be a complete
837class name. Otherwise it is considered to be a portion of the class name. For
e8919861 838credentials, the classname 'B<Password>', for example, is expanded to
5c5af345 839Catalyst::Authentication::Credential::B<Password>. For stores, the
e8919861 840classname 'B<storename>' is expanded to:
5c5af345 841Catalyst::Authentication::Store::B<storename>.
4fbe2e14 842
fe4cf44a 843=back
844
e8919861 845=head1 METHODS
846
34088132 847=head2 $c->authenticate( $userinfo [, $realm ])
e8919861 848
849Attempts to authenticate the user using the information in the $userinfo hash
850reference using the realm $realm. $realm may be omitted, in which case the
851default realm is checked.
852
4e86cf54 853=head2 $c->user( )
e8919861 854
34088132 855Returns the currently logged in user, or undef if there is none.
e8919861 856
4e86cf54 857=head2 $c->user_exists( )
e8919861 858
859Returns true if a user is logged in right now. The difference between
860user_exists and user is that user_exists will return true if a user is logged
45c7644b 861in, even if it has not been yet retrieved from the storage backend. If you only
e8919861 862need to know if the user is logged in, depending on the storage mechanism this
863can be much more efficient.
864
4e86cf54 865=head2 $c->user_in_realm( $realm )
45c7644b 866
867Works like user_exists, except that it only returns true if a user is both
c5fbff80 868logged in right now and was retrieved from the realm provided.
45c7644b 869
4e86cf54 870=head2 $c->logout( )
e8919861 871
e979170b 872Logs the user out. Deletes the currently logged in user from C<< $c->user >> and the session.
e8919861 873
4e86cf54 874=head2 $c->find_user( $userinfo, $realm )
e8919861 875
876Fetch a particular users details, matching the provided user info, from the realm
877specified in $realm.
878
8a7bd676 879=head2 persist_user()
880
881Under normal circumstances the user data is only saved to the session during
882initial authentication. This call causes the auth system to save the
34088132 883currently authenticated user's data across requests. Useful if you have
8a7bd676 884changed the user data and want to ensure that future requests reflect the
885most current data. Assumes that at the time of this call, $c->user
886contains the most current data.
887
06675d2e 888=head1 INTERNAL METHODS
889
e8919861 890These methods are for Catalyst::Plugin::Authentication B<INTERNAL USE> only.
891Please do not use them in your own code, whether application or credential /
892store modules. If you do, you will very likely get the nasty shock of having
893to fix / rewrite your code when things change. They are documented here only
894for reference.
06675d2e 895
4e86cf54 896=head2 $c->set_authenticated( $user, $realmname )
06675d2e 897
e8919861 898Marks a user as authenticated. This is called from within the authenticate
899routine when a credential returns a user. $realmname defaults to 'default'
06675d2e 900
4e86cf54 901=head2 $c->auth_restore_user( $user, $realmname )
e300c5b6 902
e8919861 903Used to restore a user from the session. In most cases this is called without
904arguments to restore the user via the session. Can be called with arguments
905when restoring a user from some other method. Currently not used in this way.
e300c5b6 906
4e86cf54 907=head2 $c->auth_realms( )
06675d2e 908
e8919861 909Returns a hashref containing realmname -> realm instance pairs. Realm
910instances contain an instantiated store and credential object as the 'store'
911and 'credential' elements, respectively
06675d2e 912
4e86cf54 913=head2 $c->get_auth_realm( $realmname )
06675d2e 914
e8919861 915Retrieves the realm instance for the realmname provided.
06675d2e 916
96671824 917=head2 $c->update_user_in_session
918
34088132 919This was a short-lived method to update user information - you should use persist_user instead.
96671824 920
fbe577ac 921=head1 SEE ALSO
922
649de93b 923This list might not be up to date. Below are modules known to work with the updated
924API of 0.10 and are therefore compatible with realms.
4bb9b01c 925
5afc0dde 926=head2 Realms
927
5c5af345 928L<Catalyst::Authentication::Realm>
5afc0dde 929
4bb9b01c 930=head2 User Storage Backends
931
5c5af345 932L<Catalyst::Authentication::Store::Minimal>,
933L<Catalyst::Authentication::Store::DBIx::Class>,
4bb9b01c 934
935=head2 Credential verification
936
5c5af345 937L<Catalyst::Authentication::Credential::Password>,
4bb9b01c 938
939=head2 Authorization
940
fbe577ac 941L<Catalyst::Plugin::Authorization::ACL>,
4bb9b01c 942L<Catalyst::Plugin::Authorization::Roles>
943
5e91c057 944=head2 Internals Documentation
945
649de93b 946L<Catalyst::Plugin::Authentication::Internals>
5e91c057 947
4bb9b01c 948=head2 Misc
949
950L<Catalyst::Plugin::Session>,
951L<Catalyst::Plugin::Session::PerUser>
fbe577ac 952
93f08fb0 953=head1 DON'T SEE ALSO
954
1a05e6ed 955This module along with its sub plugins deprecate a great number of other
956modules. These include L<Catalyst::Plugin::Authentication::Simple>,
957L<Catalyst::Plugin::Authentication::CDBI>.
93f08fb0 958
959At the time of writing these plugins have not yet been replaced or updated, but
1a05e6ed 960should be eventually: L<Catalyst::Plugin::Authentication::OpenID>,
1a05e6ed 961L<Catalyst::Plugin::Authentication::CDBI::Basic>,
962L<Catalyst::Plugin::Authentication::Basic::Remote>.
93f08fb0 963
649de93b 964=head1 INCOMPATABILITIES
965
34088132 966The realms-based configuration and functionality of the 0.10 update
649de93b 967of L<Catalyst::Plugin::Authentication> required a change in the API used by
968credentials and stores. It has a compatibility mode which allows use of
969modules that have not yet been updated. This, however, completely mimics the
34088132 970older api and disables the new realm-based features. In other words you cannot
971mix the older credential and store modules with realms, or realm-based
649de93b 972configs. The changes required to update modules are relatively minor and are
973covered in L<Catalyst::Plugin::Authentication::Internals>. We hope that most
974modules will move to the compatible list above very quickly.
0cc778ab 975
976=head1 COMPATIBILITY ROUTINES
977
e8919861 978In version 0.10 of L<Catalyst::Plugin::Authentication>, the API
979changed. For app developers, this change is fairly minor, but for
980Credential and Store authors, the changes are significant.
981
982Please see the documentation in version 0.09 of
30e90c6f 983Catalyst::Plugin::Authentication for a better understanding of how the old API
e8919861 984functioned.
985
986The items below are still present in the plugin, though using them is
987deprecated. They remain only as a transition tool, for those sites which can
30e90c6f 988not yet be upgraded to use the new system due to local customizations or use
989of Credential / Store modules that have not yet been updated to work with the
45c7644b 990new API.
e8919861 991
992These routines should not be used in any application using realms
993functionality or any of the methods described above. These are for reference
994purposes only.
0cc778ab 995
4e86cf54 996=head2 $c->login( )
e8919861 997
998This method is used to initiate authentication and user retrieval. Technically
649de93b 999this is part of the old Password credential module and it still resides in the
1000L<Password|Catalyst::Plugin::Authentication::Credential::Password> class. It is
1001included here for reference only.
e8919861 1002
4e86cf54 1003=head2 $c->default_auth_store( )
0cc778ab 1004
1005Return the store whose name is 'default'.
1006
7c4d44af 1007This is set to C<< $c->config->{'Plugin::Authentication'}{store} >> if that value exists,
0cc778ab 1008or by using a Store plugin:
1009
30e90c6f 1010 # load the Minimal authentication store.
0cc778ab 1011 use Catalyst qw/Authentication Authentication::Store::Minimal/;
1012
1013Sets the default store to
45c7644b 1014L<Catalyst::Plugin::Authentication::Store::Minimal>.
0cc778ab 1015
4e86cf54 1016=head2 $c->get_auth_store( $name )
0cc778ab 1017
1018Return the store whose name is $name.
1019
4e86cf54 1020=head2 $c->get_auth_store_name( $store )
0cc778ab 1021
1022Return the name of the store $store.
1023
4e86cf54 1024=head2 $c->auth_stores( )
0cc778ab 1025
1026A hash keyed by name, with the stores registered in the app.
1027
4e86cf54 1028=head2 $c->register_auth_stores( %stores_by_name )
0cc778ab 1029
1030Register stores into the application.
1031
4e86cf54 1032=head2 $c->auth_store_names( )
078c2289 1033
4e86cf54 1034=head2 $c->get_user( )
078c2289 1035
4e86cf54 1036=head2 $c->setup( )
078c2289 1037
4e86cf54 1038=head2 $c->setup_auth_realm( )
078c2289 1039
2bcde605 1040=head1 AUTHORS
fbe577ac 1041
1042Yuval Kogman, C<nothingmuch@woobling.org>
2bcde605 1043
649de93b 1044Jay Kuri, C<jayk@cpan.org>
1045
7d2f34eb 1046Jess Robinson
2bcde605 1047
7d2f34eb 1048David Kamholz
06675d2e 1049
ff46c00b 1050=head1 COPYRIGHT & LICENSE
fbe577ac 1051
1052 Copyright (c) 2005 the aforementioned authors. All rights
1053 reserved. This program is free software; you can redistribute
1054 it and/or modify it under the same terms as Perl itself.
1055
1056=cut
06675d2e 1057