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