Version 0.10017
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Plugin / Authentication.pm
CommitLineData
06675d2e 1package Catalyst::Plugin::Authentication;
2
c7b6526a 3use base qw/Class::Accessor::Fast/;
06675d2e 4
9c469e37 5__PACKAGE__->mk_accessors(qw/_user/);
06675d2e 6
7use strict;
8use warnings;
9
47cede3d 10use MRO::Compat;
96777f3a 11use Tie::RefHash;
12dae309 12use Class::Inspector;
5c5af345 13use Catalyst::Authentication::Realm;
96777f3a 14
ecb1afb4 15our $VERSION = "0.10017";
c7c003d3 16
06675d2e 17sub set_authenticated {
54c8dc06 18 my ( $c, $user, $realmname ) = @_;
06675d2e 19
20 $c->user($user);
e300c5b6 21 $c->request->{user} = $user; # compatibility kludge
06675d2e 22
54c8dc06 23 if (!$realmname) {
24 $realmname = 'default';
06675d2e 25 }
646ea5b1 26 my $realm = $c->get_auth_realm($realmname);
c7b6526a 27
646ea5b1 28 if (!$realm) {
29 Catalyst::Exception->throw(
30 "set_authenticated called with nonexistant realm: '$realmname'.");
31 }
646ea5b1 32 $user->auth_realm($realm->name);
8a7bd676 33
c7b6526a 34 $c->persist_user();
35
47cede3d 36 $c->maybe::next::method($user, $realmname);
06675d2e 37}
38
7bb06c91 39sub user {
e300c5b6 40 my $c = shift;
7bb06c91 41
e300c5b6 42 if (@_) {
43 return $c->_user(@_);
44 }
7bb06c91 45
45c7644b 46 if ( defined($c->_user) ) {
47 return $c->_user;
56e23e7a 48 } else {
47c6643f 49 return $c->auth_restore_user;
e300c5b6 50 }
7bb06c91 51}
52
54c8dc06 53# change this to allow specification of a realm - to verify the user is part of that realm
8a7bd676 54# in addition to verifying that they exist.
ce0b058d 55sub user_exists {
99747fe1 56 my $c = shift;
57 return defined($c->_user) || defined($c->find_realm_for_persisted_user);
56e23e7a 58}
59
c7b6526a 60# works like user_exists - except only returns true if user
45c7644b 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);
45c7644b 67 } else {
bf4d93a4 68 my $realm = $c->find_realm_for_persisted_user;
8a7bd676 69 if ($realm) {
70 return ($realm->name eq $realmname);
71 } else {
72 return undef;
73 }
45c7644b 74 }
75}
54c8dc06 76
646ea5b1 77sub __old_save_user_in_session {
e8919861 78 my ( $c, $user, $realmname ) = @_;
12dae309 79
54c8dc06 80 $c->session->{__user_realm} = $realmname;
c7b6526a 81
45c7644b 82 # we want to ask the store for a user prepared for the session.
54c8dc06 83 # but older modules split this functionality between the user and the
45c7644b 84 # store. We try the store first. If not, we use the old method.
54c8dc06 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 }
12dae309 91}
92
8a7bd676 93sub persist_user {
94 my $c = shift;
95
96 if ($c->user_exists) {
c7b6526a 97
98 ## if we have a valid session handler - we store the
99 ## realm in the session. If not - we have to hope that
5812e0a3 100 ## the realm can recognize its frozen user somehow.
c7b6526a 101 if ($c->can('session') &&
102 $c->config->{'Plugin::Authentication'}{'use_session'} &&
8a7bd676 103 $c->session_is_valid) {
c7b6526a 104
105 $c->session->{'__user_realm'} = $c->_user->auth_realm;
8a7bd676 106 }
c7b6526a 107
8a7bd676 108 my $realm = $c->get_auth_realm($c->_user->auth_realm);
c7b6526a 109
8a7bd676 110 # used to call $realm->save_user_in_session
111 $realm->persist_user($c, $c->user);
112 }
113}
114
115
c7b6526a 116## this was a short lived method to update user information -
8a7bd676 117## you should use persist_user instead.
118sub update_user_in_session {
119 my $c = shift;
120
121 return $c->persist_user;
122}
123
06675d2e 124sub logout {
125 my $c = shift;
126
127 $c->user(undef);
b003080b 128
bf4d93a4 129 my $realm = $c->find_realm_for_persisted_user;
8a7bd676 130 if ($realm) {
131 $realm->remove_persisted_user($c);
b003080b 132 }
c7b6526a 133
47cede3d 134 $c->maybe::next::method(@_);
06675d2e 135}
136
54c8dc06 137sub find_user {
138 my ( $c, $userinfo, $realmname ) = @_;
c7b6526a 139
54c8dc06 140 $realmname ||= 'default';
141 my $realm = $c->get_auth_realm($realmname);
c7b6526a 142
646ea5b1 143 if (!$realm) {
144 Catalyst::Exception->throw(
145 "find_user called with nonexistant realm: '$realmname'.");
7d0922d8 146 }
646ea5b1 147 return $realm->find_user($userinfo, $c);
7d0922d8 148}
149
c7b6526a 150## Consider making this a public method. - would make certain things easier when
bf4d93a4 151## dealing with things pre-auth restore.
152sub find_realm_for_persisted_user {
47c6643f 153 my $c = shift;
c7b6526a 154
8a7bd676 155 my $realm;
622e71d9 156 if ($c->can('session')
7c4d44af 157 and $c->config->{'Plugin::Authentication'}{'use_session'}
c7b6526a 158 and $c->session_is_valid
8a7bd676 159 and exists($c->session->{'__user_realm'})) {
c7b6526a 160
8a7bd676 161 $realm = $c->auth_realms->{$c->session->{'__user_realm'}};
162 if ($realm->user_is_restorable($c)) {
c7b6526a 163 return $realm;
8a7bd676 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}) {
9172af6d 168 my $realm = $c->auth_realms->{$realmname}
169 || Catalyst::Exception->throw("Could not find authentication realm '$realmname'");
170 return $realm
171 if $realm->user_is_restorable($c);
8a7bd676 172 }
173 }
174 return undef;
488433fd 175}
47c6643f 176
7bb06c91 177sub auth_restore_user {
54c8dc06 178 my ( $c, $frozen_user, $realmname ) = @_;
7bb06c91 179
8a7bd676 180 my $realm;
181 if (defined($realmname)) {
c7b6526a 182 $realm = $c->get_auth_realm($realmname);
8a7bd676 183 } else {
bf4d93a4 184 $realm = $c->find_realm_for_persisted_user;
8a7bd676 185 }
bf4d93a4 186 return undef unless $realm; # FIXME die unless? This is an internal inconsistency
c7b6526a 187
8a7bd676 188 $c->_user( my $user = $realm->restore_user( $c, $frozen_user ) );
c7b6526a 189
54c8dc06 190 # this sets the realm the user originated in.
9c469e37 191 $user->auth_realm($realm->name) if $user;
c7b6526a 192
e300c5b6 193 return $user;
7bb06c91 194
195}
196
c7b6526a 197# we can't actually do our setup in setup because the model has not yet been loaded.
54c8dc06 198# So we have to trigger off of setup_finished. :-(
06675d2e 199sub setup {
c5fbff80 200 my $app = shift;
06675d2e 201
c5fbff80 202 $app->_authentication_initialize();
47cede3d 203 $app->next::method(@_);
54c8dc06 204}
205
206## the actual initialization routine. whee.
207sub _authentication_initialize {
c5fbff80 208 my $app = shift;
54c8dc06 209
d6209239 210 ## let's avoid recreating / configuring everything if we have already done it, eh?
211 if ($app->can('_auth_realms')) { return };
c5fbff80 212
c7b6526a 213 ## make classdata where it is used.
d6209239 214 $app->mk_classdata( '_auth_realms' => {});
c7b6526a 215
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.
8a7bd676 221 ## The default is to use the user_restore_priority values defined in the realm
c7b6526a 222 ## config. if they are not defined - we go by alphabetical order. Note that
8a7bd676 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
c7b6526a 225 ## into play if session is disabled.
226
8a7bd676 227 $app->mk_classdata( '_auth_realm_restore_order' => []);
bf4d93a4 228
7c4d44af 229 my $cfg = $app->config->{'Plugin::Authentication'};
99747fe1 230 my $realmshash;
7c4d44af 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 }
bf4d93a4 238 } else {
99747fe1 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 }
c7b6526a 244
99747fe1 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 }
06675d2e 249
7c4d44af 250 # old default was to force use_session on. This must remain for that
5812e0a3 251 # reason - but if use_session is already in the config, we respect its setting.
7c4d44af 252 if (!exists($cfg->{'use_session'})) {
253 $cfg->{'use_session'} = 1;
254 }
c7b6526a 255
256 ## if we have a realms hash
bf4d93a4 257 if (ref($realmshash) eq 'HASH') {
c7b6526a 258
8a7bd676 259 my %auth_restore_order;
260 my $authcount = 2;
261 my $defaultrealm = 'default';
c7b6526a 262
bf4d93a4 263 foreach my $realm (sort keys %{$realmshash}) {
264 if (ref($realmshash->{$realm}) eq 'HASH' &&
99747fe1 265 (exists($realmshash->{$realm}{credential}) || exists($realmshash->{$realm}{class}))) {
c7b6526a 266
99747fe1 267 $app->setup_auth_realm($realm, $realmshash->{$realm});
c7b6526a 268
99747fe1 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 }
54c8dc06 275 }
c7b6526a 276
277 # if we have a 'default_realm' in the config hash and we don't already
54c8dc06 278 # have a realm called 'default', we point default at the realm specified
c5fbff80 279 if (exists($cfg->{'default_realm'}) && !$app->get_auth_realm('default')) {
8a7bd676 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 }
54c8dc06 285 }
c7b6526a 286
5812e0a3 287 ## if the default realm did not have a defined priority in its config - we put it at the front.
bf4d93a4 288 if (!exists($realmshash->{$defaultrealm}{'user_restore_priority'})) {
8a7bd676 289 $auth_restore_order{'default'} = 1;
290 }
c7b6526a 291
8a7bd676 292 @{$app->_auth_realm_restore_order} = sort { $auth_restore_order{$a} <=> $auth_restore_order{$b} } keys %auth_restore_order;
c7b6526a 293
54c8dc06 294 } else {
c7b6526a 295
58db3441 296 ## BACKWARDS COMPATIBILITY - if realms is not defined - then we are probably dealing
c5fbff80 297 ## with an old-school config. The only caveat here is that we must add a classname
c7b6526a 298
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
58db3441 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
8a7bd676 306 push @{$app->_auth_realm_restore_order}, 'default';
54c8dc06 307 foreach my $storename (keys %{$cfg->{'stores'}}) {
308 my $realmcfg = {
58db3441 309 store => { class => $cfg->{'stores'}{$storename} },
54c8dc06 310 };
c5fbff80 311 $app->setup_auth_realm($storename, $realmcfg);
54c8dc06 312 }
c7b6526a 313 }
314
06675d2e 315}
316
54c8dc06 317# set up realmname.
318sub setup_auth_realm {
319 my ($app, $realmname, $config) = @_;
c7b6526a 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;
3fd45491 344 $self->_authentication_initialize(); # Ensure _auth_realms created!
54c8dc06 345 return($self->_auth_realms);
96777f3a 346}
347
54c8dc06 348sub get_auth_realm {
349 my ($app, $realmname) = @_;
350 return $app->auth_realms->{$realmname};
351}
96777f3a 352
e8919861 353
354# Very internal method. Vital Valuable Urgent, Do not touch on pain of death.
355# Using this method just assigns the default realm to be the value associated
356# with the realmname provided. It WILL overwrite any real realm called 'default'
c7b6526a 357# so can be very confusing if used improperly. It's used properly already.
e8919861 358# Translation: don't use it.
359sub _set_default_auth_realm {
54c8dc06 360 my ($app, $realmname) = @_;
c7b6526a 361
54c8dc06 362 if (exists($app->auth_realms->{$realmname})) {
363 $app->auth_realms->{'default'} = $app->auth_realms->{$realmname};
12dae309 364 }
54c8dc06 365 return $app->get_auth_realm('default');
96777f3a 366}
367
54c8dc06 368sub authenticate {
369 my ($app, $userinfo, $realmname) = @_;
c7b6526a 370
54c8dc06 371 if (!$realmname) {
372 $realmname = 'default';
373 }
c7b6526a 374
54c8dc06 375 my $realm = $app->get_auth_realm($realmname);
c7b6526a 376
45c7644b 377 ## note to self - make authenticate throw an exception if realm is invalid.
c7b6526a 378
646ea5b1 379 if ($realm) {
380 return $realm->authenticate($app, $userinfo);
54c8dc06 381 } else {
646ea5b1 382 Catalyst::Exception->throw(
383 "authenticate called with nonexistant realm: '$realmname'.");
384
54c8dc06 385 }
0cc778ab 386 return undef;
96777f3a 387}
388
54c8dc06 389## BACKWARDS COMPATIBILITY -- Warning: Here be monsters!
390#
391# What follows are backwards compatibility routines - for use with Stores and Credentials
c7b6526a 392# that have not been updated to work with C::P::Authentication v0.10.
54c8dc06 393# These are here so as to not break people's existing installations, but will go away
394# in a future version.
395#
396# The old style of configuration only supports a single store, as each store module
c7b6526a 397# sets itself as the default store upon being loaded. This is the only supported
398# 'compatibility' mode.
54c8dc06 399#
400
401sub get_user {
402 my ( $c, $uid, @rest ) = @_;
96777f3a 403
54c8dc06 404 return $c->find_user( {'id' => $uid, 'rest'=>\@rest }, 'default' );
96777f3a 405}
406
e8919861 407
54c8dc06 408## this should only be called when using old-style authentication plugins. IF this gets
409## called in a new-style config - it will OVERWRITE the store of your default realm. Don't do it.
410## also - this is a partial setup - because no credential is instantiated... in other words it ONLY
411## works with old-style auth plugins and C::P::Authentication in compatibility mode. Trying to combine
412## this with a realm-type config will probably crash your app.
96777f3a 413sub default_auth_store {
12dae309 414 my $self = shift;
96777f3a 415
646ea5b1 416 my $realm = $self->get_auth_realm('default');
417 if (!$realm) {
e05c457e 418 $realm = $self->setup_auth_realm('default', { class => 'Compatibility' });
646ea5b1 419 }
12dae309 420 if ( my $new = shift ) {
646ea5b1 421 $realm->store($new);
c7b6526a 422
58db3441 423 my $storeclass;
424 if (ref($new)) {
425 $storeclass = ref($new);
426 } else {
427 $storeclass = $new;
428 }
c7b6526a 429
430 # BACKWARDS COMPATIBILITY - if the store class does not define find_user, we define it in terms
431 # of get_user and add it to the class. this is because the auth routines use find_user,
54c8dc06 432 # and rely on it being present. (this avoids per-call checks)
433 if (!$storeclass->can('find_user')) {
434 no strict 'refs';
435 *{"${storeclass}::find_user"} = sub {
436 my ($self, $info) = @_;
437 my @rest = @{$info->{rest}} if exists($info->{rest});
438 $self->get_user($info->{id}, @rest);
439 };
440 }
12dae309 441 }
96777f3a 442
646ea5b1 443 return $self->get_auth_realm('default')->store;
96777f3a 444}
445
54c8dc06 446## BACKWARDS COMPATIBILITY
447## this only ever returns a hash containing 'default' - as that is the only
448## supported mode of calling this.
449sub auth_store_names {
450 my $self = shift;
451
646ea5b1 452 my %hash = ( $self->get_auth_realm('default')->store => 'default' );
54c8dc06 453}
454
455sub get_auth_store {
456 my ( $self, $name ) = @_;
c7b6526a 457
54c8dc06 458 if ($name ne 'default') {
c7b6526a 459 Carp::croak "get_auth_store called on non-default realm '$name'. Only default supported in compatibility mode";
54c8dc06 460 } else {
461 $self->default_auth_store();
462 }
463}
464
465sub get_auth_store_name {
466 my ( $self, $store ) = @_;
467 return 'default';
468}
469
470# sub auth_stores is only used internally - here for completeness
471sub auth_stores {
472 my $self = shift;
473
646ea5b1 474 my %hash = ( 'default' => $self->get_auth_realm('default')->store);
54c8dc06 475}
476
06675d2e 477__PACKAGE__;
478
479__END__
480
481=pod
482
483=head1 NAME
484
55395841 485Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst
486authentication framework.
06675d2e 487
488=head1 SYNOPSIS
489
189b5b0c 490 use Catalyst qw/
491 Authentication
189b5b0c 492 /;
493
494 # later on ...
c7b6526a 495 $c->authenticate({ username => 'myusername',
c5fbff80 496 password => 'mypassword' });
54c8dc06 497 my $age = $c->user->get('age');
189b5b0c 498 $c->logout;
06675d2e 499
500=head1 DESCRIPTION
501
16ef3eb8 502The authentication plugin provides generic user support for Catalyst apps. It
503is the basis for both authentication (checking the user is who they claim to
504be), and authorization (allowing the user to do what the system authorises
505them to do).
506
507Using authentication is split into two parts. A Store is used to actually
508store the user information, and can store any amount of data related to the
509user. Credentials are used to verify users, using information from the store,
510given data from the frontend. A Credential and a Store are paired to form a
e8919861 511'Realm'. A Catalyst application using the authentication framework must have
512at least one realm, and may have several.
189b5b0c 513
c7b6526a 514To implement authentication in a Catalyst application you need to add this
515module, and specify at least one realm in the configuration.
189b5b0c 516
c7b6526a 517Authentication data can also be stored in a session, if the application
e7522758 518is using the L<Catalyst::Plugin::Session> module.
06675d2e 519
30e90c6f 520B<NOTE> in version 0.10 of this module, the interface to this module changed.
521Please see L</COMPATIBILITY ROUTINES> for more information.
e8919861 522
4bb9b01c 523=head1 INTRODUCTION
524
525=head2 The Authentication/Authorization Process
526
527Web applications typically need to identify a user - to tell the user apart
528from other users. This is usually done in order to display private information
529that is only that user's business, or to limit access to the application so
530that only certain entities can access certain parts.
531
532This process is split up into several steps. First you ask the user to identify
533themselves. At this point you can't be sure that the user is really who they
534claim to be.
535
6a36933d 536Then the user tells you who they are, and backs this claim with some piece of
4bb9b01c 537information that only the real user could give you. For example, a password is
538a secret that is known to both the user and you. When the user tells you this
539password you can assume they're in on the secret and can be trusted (ignore
540identity theft for now). Checking the password, or any other proof is called
541B<credential verification>.
542
543By this time you know exactly who the user is - the user's identity is
16ef3eb8 544B<authenticated>. This is where this module's job stops, and your application
c7b6526a 545or other plugins step in.
16ef3eb8 546
547The next logical step is B<authorization>, the process of deciding what a user
548is (or isn't) allowed to do. For example, say your users are split into two
549main groups - regular users and administrators. You want to verify that the
4bb9b01c 550currently logged in user is indeed an administrator before performing the
c5fbff80 551actions in an administrative part of your application. These decisions may be
16ef3eb8 552made within your application code using just the information available after
c7b6526a 553authentication, or it may be facilitated by a number of plugins.
4bb9b01c 554
555=head2 The Components In This Framework
556
62f27384 557=head3 Realms
558
559Configuration of the Catalyst::Plugin::Authentication framework is done in
560terms of realms. In simplest terms, a realm is a pairing of a Credential
5afc0dde 561verifier and a User storage (Store) backend. As of version 0.10003, realms are
562now objects that you can create and customize.
62f27384 563
564An application can have any number of Realms, each of which operates
34088132 565independent of the others. Each realm has a name, which is used to identify it
62f27384 566as the target of an authentication request. This name can be anything, such as
567'users' or 'members'. One realm must be defined as the default_realm, which is
16ef3eb8 568used when no realm name is specified. More information about configuring
569realms is available in the configuration section.
62f27384 570
4bb9b01c 571=head3 Credential Verifiers
572
4e86cf54 573When user input is transferred to the L<Catalyst> application
574(typically via form inputs) the application may pass this information
e979170b 575into the authentication system through the C<< $c->authenticate() >>
4e86cf54 576method. From there, it is passed to the appropriate Credential
577verifier.
4bb9b01c 578
579These plugins check the data, and ensure that it really proves the user is who
580they claim to be.
581
4e86cf54 582Credential verifiers compatible with versions of this module 0.10x and
583upwards should be in the namespace
584C<Catalyst::Authentication::Credential>.
585
4bb9b01c 586=head3 Storage Backends
587
45c7644b 588The authentication data also identifies a user, and the Storage backend modules
62f27384 589use this data to locate and return a standardized object-oriented
590representation of a user.
4bb9b01c 591
592When a user is retrieved from a store it is not necessarily authenticated.
62f27384 593Credential verifiers accept a set of authentication data and use this
594information to retrieve the user from the store they are paired with.
4bb9b01c 595
34088132 596Storage backends compatible with versions of this module 0.10x and
4e86cf54 597upwards should be in the namespace
598C<Catalyst::Authentication::Store>.
599
4bb9b01c 600=head3 The Core Plugin
601
62f27384 602This plugin on its own is the glue, providing realm configuration, session
4bb9b01c 603integration, and other goodness for the other plugins.
604
605=head3 Other Plugins
606
607More layers of plugins can be stacked on top of the authentication code. For
608example, L<Catalyst::Plugin::Session::PerUser> provides an abstraction of
4e86cf54 609browser sessions that is more persistent per user.
4bb9b01c 610L<Catalyst::Plugin::Authorization::Roles> provides an accepted way to separate
611and group users into categories, and then check which categories the current
612user belongs to.
613
5e91c057 614=head1 EXAMPLE
615
34088132 616Let's say we were storing users in a simple Perl hash. Users are
16ef3eb8 617verified by supplying a password which is matched within the hash.
5e91c057 618
619This means that our application will begin like this:
620
621 package MyApp;
622
623 use Catalyst qw/
624 Authentication
5e91c057 625 /;
626
c7b6526a 627 __PACKAGE__->config( 'Plugin::Authentication' =>
628 {
bf4d93a4 629 default => {
630 credential => {
631 class => 'Password',
632 password_field => 'password',
633 password_type => 'clear'
634 },
635 store => {
636 class => 'Minimal',
99747fe1 637 users => {
638 bob => {
639 password => "s00p3r",
640 editor => 'yes',
641 roles => [qw/edit delete/],
642 },
643 william => {
644 password => "s3cr3t",
645 roles => [qw/comment/],
646 }
647 }
648 }
649 }
c83ea211 650 }
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 {
c7b6526a 667 if ( $c->authenticate( { username => $user,
62f27384 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,
c7b6526a 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
c7b6526a 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
c7b6526a 700Now suppose we want to restrict the ability to edit to a user with an
c5fbff80 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
c7b6526a 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
47074ecb 747 __PACKAGE__->config( 'Plugin::Authentication' =>
c7b6526a 748 {
0cc778ab 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',
99747fe1 758 user_model => 'MyApp::Users',
759 role_column => 'roles',
760 }
761 }
c83ea211 762 }
763 );
bf4d93a4 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
c7b6526a 772 __PACKAGE__->config( 'Plugin::Authentication' =>
773 {
bf4d93a4 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',
99747fe1 784 user_model => 'MyApp::Users',
785 role_column => 'roles',
786 }
787 },
788 admins => {
789 credential => {
790 class => 'Password',
791 password_field => 'password',
bf4d93a4 792 password_type => 'clear'
99747fe1 793 },
794 store => {
795 class => '+MyApp::Authentication::Store::NetAuth',
796 authserver => '192.168.10.17'
797 }
798 }
c83ea211 799 }
800 );
0cc778ab 801
03a89311 802NOTE: Until version 0.10008 of this module, you would need to put all the
c7b6526a 803realms inside a "realms" key in the configuration. Please see
ff87fb62 804L</COMPATIBILITY CONFIGURATION> for more information
03a89311 805
078c2289 806=over 4
807
189b5b0c 808=item use_session
809
810Whether or not to store the user's logged in state in the session, if the
0c4fbc79 811application is also using L<Catalyst::Plugin::Session>. This
e7522758 812value is set to true per default.
813
0c4fbc79 814However, even if use_session is disabled, if any code touches $c->session, a session
815object will be auto-vivified and session Cookies will be sent in the headers. To
816prevent accidental session creation, check if a session already exists with
817if ($c->sessionid) { ... }. If the session doesn't exist, then don't place
818anything in the session to prevent an unecessary session from being created.
819
0cc778ab 820=item default_realm
fe4cf44a 821
0cc778ab 822This defines which realm should be used as when no realm is provided to methods
823that require a realm such as authenticate or find_user.
7d0922d8 824
bf4d93a4 825=item realm refs
7d0922d8 826
c7b6526a 827The Plugin::Authentication config hash contains the series of realm
828configurations you want to use for your app. The only rule here is
829that there must be at least one. A realm consists of a name, which is used
830to reference the realm, a credential and a store. You may also put your
831realm configurations within a subelement called 'realms' if you desire to
bf4d93a4 832separate them from the remainder of your configuration. Note that if you use
c7b6526a 833a 'realms' subelement, you must put ALL of your realms within it.
4fbe2e14 834
7c3d201d 835You can also specify a realm class to instantiate instead of the default
836L<Catalyst::Authentication::Realm> class using the 'class' element within the
837realm config.
5afc0dde 838
c7b6526a 839Each realm config contains two hashes, one called 'credential' and one called
0cc778ab 840'store', each of which provide configuration details to the respective modules.
c7b6526a 841The contents of these hashes is specific to the module being used, with the
0cc778ab 842exception of the 'class' element, which tells the core Authentication module the
c7b6526a 843classname to instantiate.
4fbe2e14 844
0cc778ab 845The 'class' element follows the standard Catalyst mechanism of class
846specification. If a class is prefixed with a +, it is assumed to be a complete
847class name. Otherwise it is considered to be a portion of the class name. For
e8919861 848credentials, the classname 'B<Password>', for example, is expanded to
5c5af345 849Catalyst::Authentication::Credential::B<Password>. For stores, the
e8919861 850classname 'B<storename>' is expanded to:
5c5af345 851Catalyst::Authentication::Store::B<storename>.
4fbe2e14 852
fe4cf44a 853=back
854
e8919861 855=head1 METHODS
856
34088132 857=head2 $c->authenticate( $userinfo [, $realm ])
e8919861 858
859Attempts to authenticate the user using the information in the $userinfo hash
860reference using the realm $realm. $realm may be omitted, in which case the
861default realm is checked.
862
4e86cf54 863=head2 $c->user( )
e8919861 864
34088132 865Returns the currently logged in user, or undef if there is none.
c51e5a95 866Normally the user is re-retrieved from the store.
867For L<Catalyst::Authentication::Store::DBIx::Class> the user is re-restored
868using the primary key of the user table.
869Thus B<user> can throw an error even though B<user_exists>
870returned true.
e8919861 871
4e86cf54 872=head2 $c->user_exists( )
e8919861 873
874Returns true if a user is logged in right now. The difference between
c51e5a95 875B<user_exists> and B<user> is that user_exists will return true if a user is logged
45c7644b 876in, even if it has not been yet retrieved from the storage backend. If you only
e8919861 877need to know if the user is logged in, depending on the storage mechanism this
878can be much more efficient.
c51e5a95 879B<user_exists> only looks into the session while B<user> is trying to restore the user.
e8919861 880
4e86cf54 881=head2 $c->user_in_realm( $realm )
45c7644b 882
c7b6526a 883Works like user_exists, except that it only returns true if a user is both
884logged in right now and was retrieved from the realm provided.
45c7644b 885
4e86cf54 886=head2 $c->logout( )
e8919861 887
18b5ff80 888Logs the user out. Deletes the currently logged in user from C<< $c->user >>
889and the session. It does not delete the session.
e8919861 890
4e86cf54 891=head2 $c->find_user( $userinfo, $realm )
e8919861 892
c7b6526a 893Fetch a particular users details, matching the provided user info, from the realm
e8919861 894specified in $realm.
895
4b4bfe2f 896 $user = $c->find_user({ id => $id });
897 $c->set_authenticated($user); # logs the user in and calls persist_user
898
8a7bd676 899=head2 persist_user()
900
901Under normal circumstances the user data is only saved to the session during
c7b6526a 902initial authentication. This call causes the auth system to save the
34088132 903currently authenticated user's data across requests. Useful if you have
8a7bd676 904changed the user data and want to ensure that future requests reflect the
c7b6526a 905most current data. Assumes that at the time of this call, $c->user
8a7bd676 906contains the most current data.
907
95114c34 908=head2 find_realm_for_persisted_user()
909
910Private method, do not call from user code!
911
06675d2e 912=head1 INTERNAL METHODS
913
e8919861 914These methods are for Catalyst::Plugin::Authentication B<INTERNAL USE> only.
915Please do not use them in your own code, whether application or credential /
916store modules. If you do, you will very likely get the nasty shock of having
917to fix / rewrite your code when things change. They are documented here only
918for reference.
06675d2e 919
4e86cf54 920=head2 $c->set_authenticated( $user, $realmname )
06675d2e 921
e8919861 922Marks a user as authenticated. This is called from within the authenticate
4b4bfe2f 923routine when a credential returns a user. $realmname defaults to 'default'.
924You can use find_user to get $user
06675d2e 925
4e86cf54 926=head2 $c->auth_restore_user( $user, $realmname )
e300c5b6 927
e8919861 928Used to restore a user from the session. In most cases this is called without
929arguments to restore the user via the session. Can be called with arguments
930when restoring a user from some other method. Currently not used in this way.
e300c5b6 931
4e86cf54 932=head2 $c->auth_realms( )
06675d2e 933
e8919861 934Returns a hashref containing realmname -> realm instance pairs. Realm
935instances contain an instantiated store and credential object as the 'store'
936and 'credential' elements, respectively
06675d2e 937
4e86cf54 938=head2 $c->get_auth_realm( $realmname )
06675d2e 939
e8919861 940Retrieves the realm instance for the realmname provided.
06675d2e 941
96671824 942=head2 $c->update_user_in_session
943
34088132 944This was a short-lived method to update user information - you should use persist_user instead.
96671824 945
eda12589 946=head2 $c->setup_auth_realm( )
947
948=head1 OVERRIDDEN METHODS
949
950=head2 $c->setup( )
951
fbe577ac 952=head1 SEE ALSO
953
649de93b 954This list might not be up to date. Below are modules known to work with the updated
c7b6526a 955API of 0.10 and are therefore compatible with realms.
4bb9b01c 956
5afc0dde 957=head2 Realms
958
5c5af345 959L<Catalyst::Authentication::Realm>
5afc0dde 960
4bb9b01c 961=head2 User Storage Backends
962
eda12589 963=over
964
965=item L<Catalyst::Authentication::Store::Minimal>
966
967=item L<Catalyst::Authentication::Store::DBIx::Class>
968
969=item L<Catalyst::Authentication::Store::LDAP>
970
971=item L<Catalyst::Authentication::Store::RDBO>
972
973=item L<Catalyst::Authentication::Store::Model::KiokuDB>
974
975=item L<Catalyst::Authentication::Store::Jifty::DBI>
976
977=item L<Catalyst::Authentication::Store::Htpasswd>
978
979=back
4bb9b01c 980
981=head2 Credential verification
982
eda12589 983=over
984
985=item L<Catalyst::Authentication::Credential::Password>
986
987=item L<Catalyst::Authentication::Credential::HTTP>
988
989=item L<Catalyst::Authentication::Credential::OpenID>
990
991=item L<Catalyst::Authentication::Credential::Authen::Simple>
992
993=item L<Catalyst::Authentication::Credential::Flickr>
994
995=item L<Catalyst::Authentication::Credential::Testing>
996
997=item L<Catalyst::Authentication::Credential::AuthTkt>
998
999=item L<Catalyst::Authentication::Credential::Kerberos>
4bb9b01c 1000
fdf33503 1001=back
1002
4bb9b01c 1003=head2 Authorization
1004
fbe577ac 1005L<Catalyst::Plugin::Authorization::ACL>,
4bb9b01c 1006L<Catalyst::Plugin::Authorization::Roles>
1007
5e91c057 1008=head2 Internals Documentation
1009
649de93b 1010L<Catalyst::Plugin::Authentication::Internals>
5e91c057 1011
4bb9b01c 1012=head2 Misc
1013
1014L<Catalyst::Plugin::Session>,
1015L<Catalyst::Plugin::Session::PerUser>
fbe577ac 1016
93f08fb0 1017=head1 DON'T SEE ALSO
1018
1a05e6ed 1019This module along with its sub plugins deprecate a great number of other
1020modules. These include L<Catalyst::Plugin::Authentication::Simple>,
1021L<Catalyst::Plugin::Authentication::CDBI>.
93f08fb0 1022
649de93b 1023=head1 INCOMPATABILITIES
1024
c7b6526a 1025The realms-based configuration and functionality of the 0.10 update
649de93b 1026of L<Catalyst::Plugin::Authentication> required a change in the API used by
1027credentials and stores. It has a compatibility mode which allows use of
1028modules that have not yet been updated. This, however, completely mimics the
c7b6526a 1029older api and disables the new realm-based features. In other words you cannot
34088132 1030mix the older credential and store modules with realms, or realm-based
649de93b 1031configs. The changes required to update modules are relatively minor and are
1032covered in L<Catalyst::Plugin::Authentication::Internals>. We hope that most
1033modules will move to the compatible list above very quickly.
0cc778ab 1034
ff87fb62 1035=head1 COMPATIBILITY CONFIGURATION
1036
1037Until version 0.10008 of this module, you needed to put all the
c7b6526a 1038realms inside a "realms" key in the configuration.
ff87fb62 1039
1040 # example
47074ecb 1041 __PACKAGE__->config( 'Plugin::Authentication' =>
c7b6526a 1042 {
ff87fb62 1043 default_realm => 'members',
1044 realms => {
1045 members => {
1046 ...
1047 },
1048 },
c83ea211 1049 }
1050 );
ff87fb62 1051
c83ea211 1052If you use the old, deprecated C<< __PACKAGE__->config( 'authentication' ) >>
ff87fb62 1053configuration key, then the realms key is still required.
1054
0cc778ab 1055=head1 COMPATIBILITY ROUTINES
1056
e8919861 1057In version 0.10 of L<Catalyst::Plugin::Authentication>, the API
1058changed. For app developers, this change is fairly minor, but for
c7b6526a 1059Credential and Store authors, the changes are significant.
e8919861 1060
1061Please see the documentation in version 0.09 of
30e90c6f 1062Catalyst::Plugin::Authentication for a better understanding of how the old API
e8919861 1063functioned.
1064
1065The items below are still present in the plugin, though using them is
1066deprecated. They remain only as a transition tool, for those sites which can
30e90c6f 1067not yet be upgraded to use the new system due to local customizations or use
c7b6526a 1068of Credential / Store modules that have not yet been updated to work with the
45c7644b 1069new API.
e8919861 1070
1071These routines should not be used in any application using realms
1072functionality or any of the methods described above. These are for reference
1073purposes only.
0cc778ab 1074
4e86cf54 1075=head2 $c->login( )
e8919861 1076
1077This method is used to initiate authentication and user retrieval. Technically
649de93b 1078this is part of the old Password credential module and it still resides in the
1079L<Password|Catalyst::Plugin::Authentication::Credential::Password> class. It is
1080included here for reference only.
e8919861 1081
4e86cf54 1082=head2 $c->default_auth_store( )
0cc778ab 1083
1084Return the store whose name is 'default'.
1085
c83ea211 1086This is set to C<< $c->config( 'Plugin::Authentication' => { store => # Store} ) >> if that value exists,
0cc778ab 1087or by using a Store plugin:
1088
30e90c6f 1089 # load the Minimal authentication store.
99747fe1 1090 use Catalyst qw/Authentication Authentication::Store::Minimal/;
0cc778ab 1091
1092Sets the default store to
45c7644b 1093L<Catalyst::Plugin::Authentication::Store::Minimal>.
0cc778ab 1094
4e86cf54 1095=head2 $c->get_auth_store( $name )
0cc778ab 1096
1097Return the store whose name is $name.
1098
4e86cf54 1099=head2 $c->get_auth_store_name( $store )
0cc778ab 1100
1101Return the name of the store $store.
1102
4e86cf54 1103=head2 $c->auth_stores( )
0cc778ab 1104
1105A hash keyed by name, with the stores registered in the app.
1106
4e86cf54 1107=head2 $c->register_auth_stores( %stores_by_name )
0cc778ab 1108
1109Register stores into the application.
1110
4e86cf54 1111=head2 $c->auth_store_names( )
078c2289 1112
4e86cf54 1113=head2 $c->get_user( )
078c2289 1114
2bcde605 1115=head1 AUTHORS
fbe577ac 1116
1117Yuval Kogman, C<nothingmuch@woobling.org>
2bcde605 1118
649de93b 1119Jay Kuri, C<jayk@cpan.org>
1120
7d2f34eb 1121Jess Robinson
2bcde605 1122
7d2f34eb 1123David Kamholz
06675d2e 1124
7289cea0 1125Tomas Doran (t0m), C<bobtfish@bobtfish.net>
1126
1127kmx
1128
1129Nigel Metheringham
36540b6c 1130
44bb43eb 1131Florian Ragwitz C<rafl@debian.org>
1132
1133Stephan Jauernick C<stephanj@cpan.org>
1134
ff46c00b 1135=head1 COPYRIGHT & LICENSE
fbe577ac 1136
87d186b1 1137Copyright (c) 2005 - 2009
1138the Catalyst::Plugin::Authentication L</AUTHORS>
1139as listed above.
1140
1141This program is free software; you can redistribute
1142it and/or modify it under the same terms as Perl itself.
fbe577ac 1143
1144=cut
06675d2e 1145