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