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