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