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