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