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