Changes to allow for dropping of the 'realms' config hash and instead
[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
bf4d93a4 16our $VERSION = "0.10008";
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
578into the authentication system through the C<<$c->authenticate()>>
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 };
62f27384 654
5e91c057 655
16ef3eb8 656This tells the authentication plugin what realms are available, which
657credential and store modules are used, and the configuration of each. With
658this code loaded, we can now attempt to authenticate users.
5e91c057 659
62f27384 660To show an example of this, let's create an authentication controller:
5e91c057 661
662 package MyApp::Controller::Auth;
663
664 sub login : Local {
665 my ( $self, $c ) = @_;
666
4e143b06 667 if ( my $user = $c->req->params->{user}
34088132 668 and my $password = $c->req->params->{password} )
5e91c057 669 {
62f27384 670 if ( $c->authenticate( { username => $user,
671 password => $password } ) ) {
672 $c->res->body( "hello " . $c->user->get("name") );
5e91c057 673 } else {
674 # login incorrect
675 }
676 }
677 else {
678 # invalid form input
679 }
680 }
681
4e86cf54 682This code should be self-explanatory. If all the necessary fields are supplied,
683call the C<authenticate> method on the context object. If it succeeds the
c5fbff80 684user is logged in.
5e91c057 685
4e86cf54 686The credential verifier will attempt to retrieve the user whose
687details match the authentication information provided to
688C<<$c->authenticate()>>. Once it fetches the user the password is
689checked and if it matches the user will be B<authenticated> and
690C<<$c->user>> will contain the user object retrieved from the store.
5e91c057 691
62f27384 692In the above case, the default realm is checked, but we could just as easily
693check an alternate realm. If this were an admin login, for example, we could
4e86cf54 694authenticate on the admin realm by simply changing the C<<$c->authenticate()>>
62f27384 695call:
5e91c057 696
62f27384 697 if ( $c->authenticate( { username => $user,
4e86cf54 698 password => $password }, 'admin' ) ) {
62f27384 699 $c->res->body( "hello " . $c->user->get("name") );
700 } ...
5e91c057 701
5e91c057 702
c5fbff80 703Now suppose we want to restrict the ability to edit to a user with an
704'editor' value of yes.
5e91c057 705
62f27384 706The restricted action might look like this:
5e91c057 707
62f27384 708 sub edit : Local {
5e91c057 709 my ( $self, $c ) = @_;
710
711 $c->detach("unauthorized")
712 unless $c->user_exists
c5fbff80 713 and $c->user->get('editor') eq 'yes';
5e91c057 714
715 # do something restricted here
716 }
717
4e86cf54 718(Note that if you have multiple realms, you can use
719C<<$c->user_in_realm('realmname')>>) in place of
720C<<$c->user_exists();>> This will essentially perform the same
721verification as user_exists, with the added requirement that if there
722is a user, it must have come from the realm specified.)
c5fbff80 723
724The above example is somewhat similar to role based access control.
5c5af345 725L<Catalyst::Authentication::Store::Minimal> treats the roles field as
62f27384 726an array of role names. Let's leverage this. Add the role authorization
727plugin:
5e91c057 728
729 use Catalyst qw/
730 ...
731 Authorization::Roles
732 /;
733
62f27384 734 sub edit : Local {
5e91c057 735 my ( $self, $c ) = @_;
736
128321cc 737 $c->detach("unauthorized") unless $c->check_user_roles("edit");
5e91c057 738
739 # do something restricted here
740 }
741
742This is somewhat simpler and will work if you change your store, too, since the
743role interface is consistent.
744
34088132 745Let's say your app grows, and you now have 10,000 users. It's no longer
0cc778ab 746efficient to maintain a hash of users, so you move this data to a database.
4e86cf54 747You can accomplish this simply by installing the L<DBIx::Class|Catalyst::Authentication::Store::DBIx::Class> Store and
0cc778ab 748changing your config:
5e91c057 749
7c4d44af 750 __PACKAGE__->config->{'Plugin::Authentication'} =
0cc778ab 751 {
752 default_realm => 'members',
0cc778ab 753 members => {
754 credential => {
c5fbff80 755 class => 'Password',
756 password_field => 'password',
757 password_type => 'clear'
0cc778ab 758 },
759 store => {
760 class => 'DBIx::Class',
761 user_class => 'MyApp::Users',
762 role_column => 'roles'
763 }
0cc778ab 764 }
bf4d93a4 765 };
766
767The authentication system works behind the scenes to load your data from the
768new source. The rest of your application is completely unchanged.
769
770
771=head1 CONFIGURATION
772
773 # example
774 __PACKAGE__->config->{'Plugin::Authentication'} =
775 {
776 default_realm => 'members',
777
778 members => {
779 credential => {
780 class => 'Password',
781 password_field => 'password',
782 password_type => 'clear'
783 },
784 store => {
785 class => 'DBIx::Class',
786 user_class => 'MyApp::Users',
787 role_column => 'roles'
788 }
789 },
790 admins => {
791 credential => {
792 class => 'Password',
793 password_field => 'password',
794 password_type => 'clear'
795 },
796 store => {
797 class => '+MyApp::Authentication::Store::NetAuth',
798 authserver => '192.168.10.17'
799 }
800 }
0cc778ab 801 };
802
078c2289 803=over 4
804
189b5b0c 805=item use_session
806
807Whether or not to store the user's logged in state in the session, if the
e8919861 808application is also using L<Catalyst::Plugin::Session>. This
e7522758 809value is set to true per default.
810
0cc778ab 811=item default_realm
fe4cf44a 812
0cc778ab 813This defines which realm should be used as when no realm is provided to methods
814that require a realm such as authenticate or find_user.
7d0922d8 815
bf4d93a4 816=item realm refs
7d0922d8 817
bf4d93a4 818The Plugin::Authentication config hash contains the series of realm
819configurations you want to use for your app. The only rule here is
820that there must be at least one. A realm consists of a name, which is used
821to reference the realm, a credential and a store. You may also put your
822realm configurations within a subelement called 'realms' if you desire to
823separate them from the remainder of your configuration. Note that if you use
824a 'realms' subelement, you must put ALL of your realms within it.
4fbe2e14 825
7c3d201d 826You can also specify a realm class to instantiate instead of the default
827L<Catalyst::Authentication::Realm> class using the 'class' element within the
828realm config.
5afc0dde 829
0cc778ab 830Each realm config contains two hashes, one called 'credential' and one called
831'store', each of which provide configuration details to the respective modules.
832The contents of these hashes is specific to the module being used, with the
833exception of the 'class' element, which tells the core Authentication module the
e8919861 834classname to instantiate.
4fbe2e14 835
0cc778ab 836The 'class' element follows the standard Catalyst mechanism of class
837specification. If a class is prefixed with a +, it is assumed to be a complete
838class name. Otherwise it is considered to be a portion of the class name. For
e8919861 839credentials, the classname 'B<Password>', for example, is expanded to
5c5af345 840Catalyst::Authentication::Credential::B<Password>. For stores, the
e8919861 841classname 'B<storename>' is expanded to:
5c5af345 842Catalyst::Authentication::Store::B<storename>.
4fbe2e14 843
fe4cf44a 844=back
845
e8919861 846=head1 METHODS
847
34088132 848=head2 $c->authenticate( $userinfo [, $realm ])
e8919861 849
850Attempts to authenticate the user using the information in the $userinfo hash
851reference using the realm $realm. $realm may be omitted, in which case the
852default realm is checked.
853
4e86cf54 854=head2 $c->user( )
e8919861 855
34088132 856Returns the currently logged in user, or undef if there is none.
e8919861 857
4e86cf54 858=head2 $c->user_exists( )
e8919861 859
860Returns true if a user is logged in right now. The difference between
861user_exists and user is that user_exists will return true if a user is logged
45c7644b 862in, even if it has not been yet retrieved from the storage backend. If you only
e8919861 863need to know if the user is logged in, depending on the storage mechanism this
864can be much more efficient.
865
4e86cf54 866=head2 $c->user_in_realm( $realm )
45c7644b 867
868Works like user_exists, except that it only returns true if a user is both
c5fbff80 869logged in right now and was retrieved from the realm provided.
45c7644b 870
4e86cf54 871=head2 $c->logout( )
e8919861 872
34088132 873Logs the user out. Deletes the currently logged in user from C<<$c->user>> and the session.
e8919861 874
4e86cf54 875=head2 $c->find_user( $userinfo, $realm )
e8919861 876
877Fetch a particular users details, matching the provided user info, from the realm
878specified in $realm.
879
8a7bd676 880=head2 persist_user()
881
882Under normal circumstances the user data is only saved to the session during
883initial authentication. This call causes the auth system to save the
34088132 884currently authenticated user's data across requests. Useful if you have
8a7bd676 885changed the user data and want to ensure that future requests reflect the
886most current data. Assumes that at the time of this call, $c->user
887contains the most current data.
888
06675d2e 889=head1 INTERNAL METHODS
890
e8919861 891These methods are for Catalyst::Plugin::Authentication B<INTERNAL USE> only.
892Please do not use them in your own code, whether application or credential /
893store modules. If you do, you will very likely get the nasty shock of having
894to fix / rewrite your code when things change. They are documented here only
895for reference.
06675d2e 896
4e86cf54 897=head2 $c->set_authenticated( $user, $realmname )
06675d2e 898
e8919861 899Marks a user as authenticated. This is called from within the authenticate
900routine when a credential returns a user. $realmname defaults to 'default'
06675d2e 901
4e86cf54 902=head2 $c->auth_restore_user( $user, $realmname )
e300c5b6 903
e8919861 904Used to restore a user from the session. In most cases this is called without
905arguments to restore the user via the session. Can be called with arguments
906when restoring a user from some other method. Currently not used in this way.
e300c5b6 907
4e86cf54 908=head2 $c->auth_realms( )
06675d2e 909
e8919861 910Returns a hashref containing realmname -> realm instance pairs. Realm
911instances contain an instantiated store and credential object as the 'store'
912and 'credential' elements, respectively
06675d2e 913
4e86cf54 914=head2 $c->get_auth_realm( $realmname )
06675d2e 915
e8919861 916Retrieves the realm instance for the realmname provided.
06675d2e 917
96671824 918=head2 $c->update_user_in_session
919
34088132 920This was a short-lived method to update user information - you should use persist_user instead.
96671824 921
fbe577ac 922=head1 SEE ALSO
923
649de93b 924This list might not be up to date. Below are modules known to work with the updated
925API of 0.10 and are therefore compatible with realms.
4bb9b01c 926
5afc0dde 927=head2 Realms
928
5c5af345 929L<Catalyst::Authentication::Realm>
5afc0dde 930
4bb9b01c 931=head2 User Storage Backends
932
5c5af345 933L<Catalyst::Authentication::Store::Minimal>,
934L<Catalyst::Authentication::Store::DBIx::Class>,
4bb9b01c 935
936=head2 Credential verification
937
5c5af345 938L<Catalyst::Authentication::Credential::Password>,
4bb9b01c 939
940=head2 Authorization
941
fbe577ac 942L<Catalyst::Plugin::Authorization::ACL>,
4bb9b01c 943L<Catalyst::Plugin::Authorization::Roles>
944
5e91c057 945=head2 Internals Documentation
946
649de93b 947L<Catalyst::Plugin::Authentication::Internals>
5e91c057 948
4bb9b01c 949=head2 Misc
950
951L<Catalyst::Plugin::Session>,
952L<Catalyst::Plugin::Session::PerUser>
fbe577ac 953
93f08fb0 954=head1 DON'T SEE ALSO
955
1a05e6ed 956This module along with its sub plugins deprecate a great number of other
957modules. These include L<Catalyst::Plugin::Authentication::Simple>,
958L<Catalyst::Plugin::Authentication::CDBI>.
93f08fb0 959
960At the time of writing these plugins have not yet been replaced or updated, but
1a05e6ed 961should be eventually: L<Catalyst::Plugin::Authentication::OpenID>,
1a05e6ed 962L<Catalyst::Plugin::Authentication::CDBI::Basic>,
963L<Catalyst::Plugin::Authentication::Basic::Remote>.
93f08fb0 964
649de93b 965=head1 INCOMPATABILITIES
966
34088132 967The realms-based configuration and functionality of the 0.10 update
649de93b 968of L<Catalyst::Plugin::Authentication> required a change in the API used by
969credentials and stores. It has a compatibility mode which allows use of
970modules that have not yet been updated. This, however, completely mimics the
34088132 971older api and disables the new realm-based features. In other words you cannot
972mix the older credential and store modules with realms, or realm-based
649de93b 973configs. The changes required to update modules are relatively minor and are
974covered in L<Catalyst::Plugin::Authentication::Internals>. We hope that most
975modules will move to the compatible list above very quickly.
0cc778ab 976
977=head1 COMPATIBILITY ROUTINES
978
e8919861 979In version 0.10 of L<Catalyst::Plugin::Authentication>, the API
980changed. For app developers, this change is fairly minor, but for
981Credential and Store authors, the changes are significant.
982
983Please see the documentation in version 0.09 of
30e90c6f 984Catalyst::Plugin::Authentication for a better understanding of how the old API
e8919861 985functioned.
986
987The items below are still present in the plugin, though using them is
988deprecated. They remain only as a transition tool, for those sites which can
30e90c6f 989not yet be upgraded to use the new system due to local customizations or use
990of Credential / Store modules that have not yet been updated to work with the
45c7644b 991new API.
e8919861 992
993These routines should not be used in any application using realms
994functionality or any of the methods described above. These are for reference
995purposes only.
0cc778ab 996
4e86cf54 997=head2 $c->login( )
e8919861 998
999This method is used to initiate authentication and user retrieval. Technically
649de93b 1000this is part of the old Password credential module and it still resides in the
1001L<Password|Catalyst::Plugin::Authentication::Credential::Password> class. It is
1002included here for reference only.
e8919861 1003
4e86cf54 1004=head2 $c->default_auth_store( )
0cc778ab 1005
1006Return the store whose name is 'default'.
1007
7c4d44af 1008This is set to C<< $c->config->{'Plugin::Authentication'}{store} >> if that value exists,
0cc778ab 1009or by using a Store plugin:
1010
30e90c6f 1011 # load the Minimal authentication store.
0cc778ab 1012 use Catalyst qw/Authentication Authentication::Store::Minimal/;
1013
1014Sets the default store to
45c7644b 1015L<Catalyst::Plugin::Authentication::Store::Minimal>.
0cc778ab 1016
4e86cf54 1017=head2 $c->get_auth_store( $name )
0cc778ab 1018
1019Return the store whose name is $name.
1020
4e86cf54 1021=head2 $c->get_auth_store_name( $store )
0cc778ab 1022
1023Return the name of the store $store.
1024
4e86cf54 1025=head2 $c->auth_stores( )
0cc778ab 1026
1027A hash keyed by name, with the stores registered in the app.
1028
4e86cf54 1029=head2 $c->register_auth_stores( %stores_by_name )
0cc778ab 1030
1031Register stores into the application.
1032
4e86cf54 1033=head2 $c->auth_store_names( )
078c2289 1034
4e86cf54 1035=head2 $c->get_user( )
078c2289 1036
4e86cf54 1037=head2 $c->setup( )
078c2289 1038
4e86cf54 1039=head2 $c->setup_auth_realm( )
078c2289 1040
2bcde605 1041=head1 AUTHORS
fbe577ac 1042
1043Yuval Kogman, C<nothingmuch@woobling.org>
2bcde605 1044
649de93b 1045Jay Kuri, C<jayk@cpan.org>
1046
7d2f34eb 1047Jess Robinson
2bcde605 1048
7d2f34eb 1049David Kamholz
06675d2e 1050
ff46c00b 1051=head1 COPYRIGHT & LICENSE
fbe577ac 1052
1053 Copyright (c) 2005 the aforementioned authors. All rights
1054 reserved. This program is free software; you can redistribute
1055 it and/or modify it under the same terms as Perl itself.
1056
1057=cut
06675d2e 1058