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