Version 0.10021
[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
994cb911 14our $VERSION = "0.10021";
b8b7f68e 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
798af89b 485Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst authentication framework.
06675d2e 486
487=head1 SYNOPSIS
488
189b5b0c 489 use Catalyst qw/
490 Authentication
189b5b0c 491 /;
492
493 # later on ...
c7b6526a 494 $c->authenticate({ username => 'myusername',
c5fbff80 495 password => 'mypassword' });
54c8dc06 496 my $age = $c->user->get('age');
189b5b0c 497 $c->logout;
06675d2e 498
499=head1 DESCRIPTION
500
16ef3eb8 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
e8919861 510'Realm'. A Catalyst application using the authentication framework must have
511at least one realm, and may have several.
189b5b0c 512
c7b6526a 513To implement authentication in a Catalyst application you need to add this
514module, and specify at least one realm in the configuration.
189b5b0c 515
c7b6526a 516Authentication data can also be stored in a session, if the application
e7522758 517is using the L<Catalyst::Plugin::Session> module.
06675d2e 518
30e90c6f 519B<NOTE> in version 0.10 of this module, the interface to this module changed.
520Please see L</COMPATIBILITY ROUTINES> for more information.
e8919861 521
4bb9b01c 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
6a36933d 535Then the user tells you who they are, and backs this claim with some piece of
4bb9b01c 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
16ef3eb8 543B<authenticated>. This is where this module's job stops, and your application
c7b6526a 544or other plugins step in.
16ef3eb8 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
4bb9b01c 549currently logged in user is indeed an administrator before performing the
c5fbff80 550actions in an administrative part of your application. These decisions may be
16ef3eb8 551made within your application code using just the information available after
c7b6526a 552authentication, or it may be facilitated by a number of plugins.
4bb9b01c 553
554=head2 The Components In This Framework
555
62f27384 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
5afc0dde 560verifier and a User storage (Store) backend. As of version 0.10003, realms are
561now objects that you can create and customize.
62f27384 562
563An application can have any number of Realms, each of which operates
34088132 564independent of the others. Each realm has a name, which is used to identify it
62f27384 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
16ef3eb8 567used when no realm name is specified. More information about configuring
568realms is available in the configuration section.
62f27384 569
4bb9b01c 570=head3 Credential Verifiers
571
4e86cf54 572When user input is transferred to the L<Catalyst> application
573(typically via form inputs) the application may pass this information
e979170b 574into the authentication system through the C<< $c->authenticate() >>
4e86cf54 575method. From there, it is passed to the appropriate Credential
576verifier.
4bb9b01c 577
578These plugins check the data, and ensure that it really proves the user is who
579they claim to be.
580
4e86cf54 581Credential verifiers compatible with versions of this module 0.10x and
582upwards should be in the namespace
583C<Catalyst::Authentication::Credential>.
584
4bb9b01c 585=head3 Storage Backends
586
45c7644b 587The authentication data also identifies a user, and the Storage backend modules
62f27384 588use this data to locate and return a standardized object-oriented
589representation of a user.
4bb9b01c 590
591When a user is retrieved from a store it is not necessarily authenticated.
62f27384 592Credential verifiers accept a set of authentication data and use this
593information to retrieve the user from the store they are paired with.
4bb9b01c 594
34088132 595Storage backends compatible with versions of this module 0.10x and
4e86cf54 596upwards should be in the namespace
597C<Catalyst::Authentication::Store>.
598
4bb9b01c 599=head3 The Core Plugin
600
62f27384 601This plugin on its own is the glue, providing realm configuration, session
4bb9b01c 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
4e86cf54 608browser sessions that is more persistent per user.
4bb9b01c 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
5e91c057 613=head1 EXAMPLE
614
34088132 615Let's say we were storing users in a simple Perl hash. Users are
16ef3eb8 616verified by supplying a password which is matched within the hash.
5e91c057 617
618This means that our application will begin like this:
619
620 package MyApp;
621
622 use Catalyst qw/
623 Authentication
5e91c057 624 /;
625
c7b6526a 626 __PACKAGE__->config( 'Plugin::Authentication' =>
627 {
bf4d93a4 628 default => {
629 credential => {
630 class => 'Password',
631 password_field => 'password',
632 password_type => 'clear'
633 },
634 store => {
635 class => 'Minimal',
99747fe1 636 users => {
637 bob => {
638 password => "s00p3r",
639 editor => 'yes',
640 roles => [qw/edit delete/],
641 },
642 william => {
643 password => "s3cr3t",
644 roles => [qw/comment/],
645 }
646 }
647 }
648 }
c83ea211 649 }
650 );
5e91c057 651
16ef3eb8 652This tells the authentication plugin what realms are available, which
653credential and store modules are used, and the configuration of each. With
654this code loaded, we can now attempt to authenticate users.
5e91c057 655
62f27384 656To show an example of this, let's create an authentication controller:
5e91c057 657
658 package MyApp::Controller::Auth;
659
660 sub login : Local {
661 my ( $self, $c ) = @_;
662
4e143b06 663 if ( my $user = $c->req->params->{user}
34088132 664 and my $password = $c->req->params->{password} )
5e91c057 665 {
c7b6526a 666 if ( $c->authenticate( { username => $user,
62f27384 667 password => $password } ) ) {
668 $c->res->body( "hello " . $c->user->get("name") );
5e91c057 669 } else {
670 # login incorrect
671 }
672 }
673 else {
674 # invalid form input
675 }
676 }
677
4e86cf54 678This code should be self-explanatory. If all the necessary fields are supplied,
c7b6526a 679call the C<authenticate> method on the context object. If it succeeds the
c5fbff80 680user is logged in.
5e91c057 681
4e86cf54 682The credential verifier will attempt to retrieve the user whose
683details match the authentication information provided to
e979170b 684C<< $c->authenticate() >>. Once it fetches the user the password is
4e86cf54 685checked and if it matches the user will be B<authenticated> and
e979170b 686C<< $c->user >> will contain the user object retrieved from the store.
5e91c057 687
62f27384 688In the above case, the default realm is checked, but we could just as easily
689check an alternate realm. If this were an admin login, for example, we could
e979170b 690authenticate on the admin realm by simply changing the C<< $c->authenticate() >>
62f27384 691call:
5e91c057 692
c7b6526a 693 if ( $c->authenticate( { username => $user,
4e86cf54 694 password => $password }, 'admin' ) ) {
62f27384 695 $c->res->body( "hello " . $c->user->get("name") );
696 } ...
5e91c057 697
5e91c057 698
c7b6526a 699Now suppose we want to restrict the ability to edit to a user with an
c5fbff80 700'editor' value of yes.
5e91c057 701
62f27384 702The restricted action might look like this:
5e91c057 703
62f27384 704 sub edit : Local {
5e91c057 705 my ( $self, $c ) = @_;
706
707 $c->detach("unauthorized")
708 unless $c->user_exists
c5fbff80 709 and $c->user->get('editor') eq 'yes';
5e91c057 710
711 # do something restricted here
712 }
713
4e86cf54 714(Note that if you have multiple realms, you can use
e979170b 715C<< $c->user_in_realm('realmname') >> in place of
716C<< $c->user_exists(); >> This will essentially perform the same
4e86cf54 717verification as user_exists, with the added requirement that if there
718is a user, it must have come from the realm specified.)
c5fbff80 719
c7b6526a 720The above example is somewhat similar to role based access control.
5c5af345 721L<Catalyst::Authentication::Store::Minimal> treats the roles field as
62f27384 722an array of role names. Let's leverage this. Add the role authorization
723plugin:
5e91c057 724
725 use Catalyst qw/
726 ...
727 Authorization::Roles
728 /;
729
62f27384 730 sub edit : Local {
5e91c057 731 my ( $self, $c ) = @_;
732
128321cc 733 $c->detach("unauthorized") unless $c->check_user_roles("edit");
5e91c057 734
735 # do something restricted here
736 }
737
738This is somewhat simpler and will work if you change your store, too, since the
739role interface is consistent.
740
34088132 741Let's say your app grows, and you now have 10,000 users. It's no longer
0cc778ab 742efficient to maintain a hash of users, so you move this data to a database.
4e86cf54 743You can accomplish this simply by installing the L<DBIx::Class|Catalyst::Authentication::Store::DBIx::Class> Store and
0cc778ab 744changing your config:
5e91c057 745
47074ecb 746 __PACKAGE__->config( 'Plugin::Authentication' =>
c7b6526a 747 {
0cc778ab 748 default_realm => 'members',
0cc778ab 749 members => {
750 credential => {
c5fbff80 751 class => 'Password',
752 password_field => 'password',
753 password_type => 'clear'
0cc778ab 754 },
755 store => {
756 class => 'DBIx::Class',
99747fe1 757 user_model => 'MyApp::Users',
758 role_column => 'roles',
759 }
760 }
c83ea211 761 }
762 );
bf4d93a4 763
764The authentication system works behind the scenes to load your data from the
765new source. The rest of your application is completely unchanged.
766
767
768=head1 CONFIGURATION
769
770 # example
c7b6526a 771 __PACKAGE__->config( 'Plugin::Authentication' =>
772 {
bf4d93a4 773 default_realm => 'members',
774
775 members => {
776 credential => {
777 class => 'Password',
778 password_field => 'password',
779 password_type => 'clear'
780 },
781 store => {
782 class => 'DBIx::Class',
99747fe1 783 user_model => 'MyApp::Users',
784 role_column => 'roles',
785 }
786 },
787 admins => {
788 credential => {
789 class => 'Password',
790 password_field => 'password',
bf4d93a4 791 password_type => 'clear'
99747fe1 792 },
793 store => {
794 class => '+MyApp::Authentication::Store::NetAuth',
795 authserver => '192.168.10.17'
796 }
797 }
c83ea211 798 }
799 );
0cc778ab 800
03a89311 801NOTE: Until version 0.10008 of this module, you would need to put all the
c7b6526a 802realms inside a "realms" key in the configuration. Please see
ff87fb62 803L</COMPATIBILITY CONFIGURATION> for more information
03a89311 804
078c2289 805=over 4
806
189b5b0c 807=item use_session
808
809Whether or not to store the user's logged in state in the session, if the
0c4fbc79 810application is also using L<Catalyst::Plugin::Session>. This
e7522758 811value is set to true per default.
812
0c4fbc79 813However, even if use_session is disabled, if any code touches $c->session, a session
814object will be auto-vivified and session Cookies will be sent in the headers. To
815prevent accidental session creation, check if a session already exists with
816if ($c->sessionid) { ... }. If the session doesn't exist, then don't place
817anything in the session to prevent an unecessary session from being created.
818
0cc778ab 819=item default_realm
fe4cf44a 820
0cc778ab 821This defines which realm should be used as when no realm is provided to methods
822that require a realm such as authenticate or find_user.
7d0922d8 823
bf4d93a4 824=item realm refs
7d0922d8 825
c7b6526a 826The Plugin::Authentication config hash contains the series of realm
827configurations you want to use for your app. The only rule here is
828that there must be at least one. A realm consists of a name, which is used
829to reference the realm, a credential and a store. You may also put your
830realm configurations within a subelement called 'realms' if you desire to
bf4d93a4 831separate them from the remainder of your configuration. Note that if you use
c7b6526a 832a 'realms' subelement, you must put ALL of your realms within it.
4fbe2e14 833
7c3d201d 834You can also specify a realm class to instantiate instead of the default
835L<Catalyst::Authentication::Realm> class using the 'class' element within the
836realm config.
5afc0dde 837
c7b6526a 838Each realm config contains two hashes, one called 'credential' and one called
0cc778ab 839'store', each of which provide configuration details to the respective modules.
c7b6526a 840The contents of these hashes is specific to the module being used, with the
0cc778ab 841exception of the 'class' element, which tells the core Authentication module the
c7b6526a 842classname to instantiate.
4fbe2e14 843
0cc778ab 844The 'class' element follows the standard Catalyst mechanism of class
845specification. If a class is prefixed with a +, it is assumed to be a complete
846class name. Otherwise it is considered to be a portion of the class name. For
e8919861 847credentials, the classname 'B<Password>', for example, is expanded to
5c5af345 848Catalyst::Authentication::Credential::B<Password>. For stores, the
e8919861 849classname 'B<storename>' is expanded to:
5c5af345 850Catalyst::Authentication::Store::B<storename>.
4fbe2e14 851
fe4cf44a 852=back
853
e8919861 854=head1 METHODS
855
34088132 856=head2 $c->authenticate( $userinfo [, $realm ])
e8919861 857
858Attempts to authenticate the user using the information in the $userinfo hash
859reference using the realm $realm. $realm may be omitted, in which case the
860default realm is checked.
861
4e86cf54 862=head2 $c->user( )
e8919861 863
34088132 864Returns the currently logged in user, or undef if there is none.
c51e5a95 865Normally the user is re-retrieved from the store.
8f57bf96 866For L<Catalyst::Authentication::Store::DBIx::Class> the user is re-restored
867using the primary key of the user table.
c51e5a95 868Thus B<user> can throw an error even though B<user_exists>
869returned true.
e8919861 870
4e86cf54 871=head2 $c->user_exists( )
e8919861 872
873Returns true if a user is logged in right now. The difference between
c51e5a95 874B<user_exists> and B<user> is that user_exists will return true if a user is logged
45c7644b 875in, even if it has not been yet retrieved from the storage backend. If you only
e8919861 876need to know if the user is logged in, depending on the storage mechanism this
877can be much more efficient.
c51e5a95 878B<user_exists> only looks into the session while B<user> is trying to restore the user.
e8919861 879
4e86cf54 880=head2 $c->user_in_realm( $realm )
45c7644b 881
c7b6526a 882Works like user_exists, except that it only returns true if a user is both
883logged in right now and was retrieved from the realm provided.
45c7644b 884
4e86cf54 885=head2 $c->logout( )
e8919861 886
18b5ff80 887Logs the user out. Deletes the currently logged in user from C<< $c->user >>
888and the session. It does not delete the session.
e8919861 889
4e86cf54 890=head2 $c->find_user( $userinfo, $realm )
e8919861 891
c7b6526a 892Fetch a particular users details, matching the provided user info, from the realm
e8919861 893specified in $realm.
894
4b4bfe2f 895 $user = $c->find_user({ id => $id });
896 $c->set_authenticated($user); # logs the user in and calls persist_user
897
8a7bd676 898=head2 persist_user()
899
900Under normal circumstances the user data is only saved to the session during
c7b6526a 901initial authentication. This call causes the auth system to save the
34088132 902currently authenticated user's data across requests. Useful if you have
8a7bd676 903changed the user data and want to ensure that future requests reflect the
c7b6526a 904most current data. Assumes that at the time of this call, $c->user
8a7bd676 905contains the most current data.
906
95114c34 907=head2 find_realm_for_persisted_user()
908
909Private method, do not call from user code!
910
06675d2e 911=head1 INTERNAL METHODS
912
e8919861 913These methods are for Catalyst::Plugin::Authentication B<INTERNAL USE> only.
914Please do not use them in your own code, whether application or credential /
915store modules. If you do, you will very likely get the nasty shock of having
916to fix / rewrite your code when things change. They are documented here only
917for reference.
06675d2e 918
4e86cf54 919=head2 $c->set_authenticated( $user, $realmname )
06675d2e 920
e8919861 921Marks a user as authenticated. This is called from within the authenticate
4b4bfe2f 922routine when a credential returns a user. $realmname defaults to 'default'.
923You can use find_user to get $user
06675d2e 924
4e86cf54 925=head2 $c->auth_restore_user( $user, $realmname )
e300c5b6 926
e8919861 927Used to restore a user from the session. In most cases this is called without
928arguments to restore the user via the session. Can be called with arguments
929when restoring a user from some other method. Currently not used in this way.
e300c5b6 930
4e86cf54 931=head2 $c->auth_realms( )
06675d2e 932
e8919861 933Returns a hashref containing realmname -> realm instance pairs. Realm
934instances contain an instantiated store and credential object as the 'store'
935and 'credential' elements, respectively
06675d2e 936
4e86cf54 937=head2 $c->get_auth_realm( $realmname )
06675d2e 938
e8919861 939Retrieves the realm instance for the realmname provided.
06675d2e 940
96671824 941=head2 $c->update_user_in_session
942
34088132 943This was a short-lived method to update user information - you should use persist_user instead.
96671824 944
eda12589 945=head2 $c->setup_auth_realm( )
946
947=head1 OVERRIDDEN METHODS
948
949=head2 $c->setup( )
950
fbe577ac 951=head1 SEE ALSO
952
649de93b 953This list might not be up to date. Below are modules known to work with the updated
c7b6526a 954API of 0.10 and are therefore compatible with realms.
4bb9b01c 955
5afc0dde 956=head2 Realms
957
5c5af345 958L<Catalyst::Authentication::Realm>
5afc0dde 959
4bb9b01c 960=head2 User Storage Backends
961
eda12589 962=over
963
964=item L<Catalyst::Authentication::Store::Minimal>
965
966=item L<Catalyst::Authentication::Store::DBIx::Class>
967
968=item L<Catalyst::Authentication::Store::LDAP>
969
970=item L<Catalyst::Authentication::Store::RDBO>
971
972=item L<Catalyst::Authentication::Store::Model::KiokuDB>
973
974=item L<Catalyst::Authentication::Store::Jifty::DBI>
975
976=item L<Catalyst::Authentication::Store::Htpasswd>
977
978=back
4bb9b01c 979
980=head2 Credential verification
981
eda12589 982=over
983
984=item L<Catalyst::Authentication::Credential::Password>
985
986=item L<Catalyst::Authentication::Credential::HTTP>
987
988=item L<Catalyst::Authentication::Credential::OpenID>
989
990=item L<Catalyst::Authentication::Credential::Authen::Simple>
991
992=item L<Catalyst::Authentication::Credential::Flickr>
993
994=item L<Catalyst::Authentication::Credential::Testing>
995
996=item L<Catalyst::Authentication::Credential::AuthTkt>
997
998=item L<Catalyst::Authentication::Credential::Kerberos>
4bb9b01c 999
fdf33503 1000=back
1001
4bb9b01c 1002=head2 Authorization
1003
fbe577ac 1004L<Catalyst::Plugin::Authorization::ACL>,
4bb9b01c 1005L<Catalyst::Plugin::Authorization::Roles>
1006
5e91c057 1007=head2 Internals Documentation
1008
649de93b 1009L<Catalyst::Plugin::Authentication::Internals>
5e91c057 1010
4bb9b01c 1011=head2 Misc
1012
1013L<Catalyst::Plugin::Session>,
1014L<Catalyst::Plugin::Session::PerUser>
fbe577ac 1015
93f08fb0 1016=head1 DON'T SEE ALSO
1017
1a05e6ed 1018This module along with its sub plugins deprecate a great number of other
1019modules. These include L<Catalyst::Plugin::Authentication::Simple>,
1020L<Catalyst::Plugin::Authentication::CDBI>.
93f08fb0 1021
649de93b 1022=head1 INCOMPATABILITIES
1023
c7b6526a 1024The realms-based configuration and functionality of the 0.10 update
649de93b 1025of L<Catalyst::Plugin::Authentication> required a change in the API used by
1026credentials and stores. It has a compatibility mode which allows use of
1027modules that have not yet been updated. This, however, completely mimics the
c7b6526a 1028older api and disables the new realm-based features. In other words you cannot
34088132 1029mix the older credential and store modules with realms, or realm-based
649de93b 1030configs. The changes required to update modules are relatively minor and are
1031covered in L<Catalyst::Plugin::Authentication::Internals>. We hope that most
1032modules will move to the compatible list above very quickly.
0cc778ab 1033
ff87fb62 1034=head1 COMPATIBILITY CONFIGURATION
1035
1036Until version 0.10008 of this module, you needed to put all the
c7b6526a 1037realms inside a "realms" key in the configuration.
ff87fb62 1038
1039 # example
47074ecb 1040 __PACKAGE__->config( 'Plugin::Authentication' =>
c7b6526a 1041 {
ff87fb62 1042 default_realm => 'members',
1043 realms => {
1044 members => {
1045 ...
1046 },
1047 },
c83ea211 1048 }
1049 );
ff87fb62 1050
c83ea211 1051If you use the old, deprecated C<< __PACKAGE__->config( 'authentication' ) >>
ff87fb62 1052configuration key, then the realms key is still required.
1053
0cc778ab 1054=head1 COMPATIBILITY ROUTINES
1055
e8919861 1056In version 0.10 of L<Catalyst::Plugin::Authentication>, the API
1057changed. For app developers, this change is fairly minor, but for
c7b6526a 1058Credential and Store authors, the changes are significant.
e8919861 1059
1060Please see the documentation in version 0.09 of
30e90c6f 1061Catalyst::Plugin::Authentication for a better understanding of how the old API
e8919861 1062functioned.
1063
1064The items below are still present in the plugin, though using them is
1065deprecated. They remain only as a transition tool, for those sites which can
30e90c6f 1066not yet be upgraded to use the new system due to local customizations or use
c7b6526a 1067of Credential / Store modules that have not yet been updated to work with the
45c7644b 1068new API.
e8919861 1069
1070These routines should not be used in any application using realms
1071functionality or any of the methods described above. These are for reference
1072purposes only.
0cc778ab 1073
4e86cf54 1074=head2 $c->login( )
e8919861 1075
1076This method is used to initiate authentication and user retrieval. Technically
649de93b 1077this is part of the old Password credential module and it still resides in the
1078L<Password|Catalyst::Plugin::Authentication::Credential::Password> class. It is
1079included here for reference only.
e8919861 1080
4e86cf54 1081=head2 $c->default_auth_store( )
0cc778ab 1082
1083Return the store whose name is 'default'.
1084
c83ea211 1085This is set to C<< $c->config( 'Plugin::Authentication' => { store => # Store} ) >> if that value exists,
0cc778ab 1086or by using a Store plugin:
1087
30e90c6f 1088 # load the Minimal authentication store.
99747fe1 1089 use Catalyst qw/Authentication Authentication::Store::Minimal/;
0cc778ab 1090
1091Sets the default store to
45c7644b 1092L<Catalyst::Plugin::Authentication::Store::Minimal>.
0cc778ab 1093
4e86cf54 1094=head2 $c->get_auth_store( $name )
0cc778ab 1095
1096Return the store whose name is $name.
1097
4e86cf54 1098=head2 $c->get_auth_store_name( $store )
0cc778ab 1099
1100Return the name of the store $store.
1101
4e86cf54 1102=head2 $c->auth_stores( )
0cc778ab 1103
1104A hash keyed by name, with the stores registered in the app.
1105
4e86cf54 1106=head2 $c->register_auth_stores( %stores_by_name )
0cc778ab 1107
1108Register stores into the application.
1109
4e86cf54 1110=head2 $c->auth_store_names( )
078c2289 1111
4e86cf54 1112=head2 $c->get_user( )
078c2289 1113
efea013f 1114=head1 SUPPORT
1115
1116Please use the rt.cpan.org bug tracker, and git patches are wecome.
1117
1118Questions on usage should be directed to the Catalyst mailing list
1119or the #catalyst irc channel.
fbe577ac 1120
efea013f 1121=head1 AUTHORS
2bcde605 1122
efea013f 1123Yuval Kogman, C<nothingmuch@woobling.org> - original author
649de93b 1124
efea013f 1125Jay Kuri, C<jayk@cpan.org> - Large rewrite
2bcde605 1126
efea013f 1127=head1 PRIMARY MAINTAINER
06675d2e 1128
7289cea0 1129Tomas Doran (t0m), C<bobtfish@bobtfish.net>
1130
83418d1e 1131=head1 ADDITIONAL CONTRIBUTORS
efea013f 1132
1133=over
7289cea0 1134
efea013f 1135=item Jess Robinson
36540b6c 1136
efea013f 1137=item David Kamholz
44bb43eb 1138
83418d1e 1139=item kmx
44bb43eb 1140
efea013f 1141=item Nigel Metheringham
8a31d23d 1142
efea013f 1143=item Florian Ragwitz C<rafl@debian.org>
1144
1145=item Stephan Jauernick C<stephanj@cpan.org>
1146
1147=item Oskari Ojala (Okko), C<perl@okko.net>
1148
1149=item John Napiorkowski (jnap) C<jjnapiork@cpan.org>
1150
1151=back
2ded293d 1152
ff46c00b 1153=head1 COPYRIGHT & LICENSE
fbe577ac 1154
2ded293d 1155Copyright (c) 2005 - 2012
efea013f 1156the Catalyst::Plugin::Authentication L</AUTHORS>,
1157L</PRIMARY MAINTAINER> and L</ADDITIONAL CONTRIBUTORS>
87d186b1 1158as listed above.
1159
1160This program is free software; you can redistribute
1161it and/or modify it under the same terms as Perl itself.
fbe577ac 1162
1163=cut
06675d2e 1164