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