Commit | Line | Data |
06675d2e |
1 | #!/usr/bin/perl |
2 | |
3 | package Catalyst::Plugin::Authentication; |
4 | |
b003080b |
5 | use base qw/Class::Accessor::Fast Class::Data::Inheritable/; |
06675d2e |
6 | |
b003080b |
7 | BEGIN { |
7bb06c91 |
8 | __PACKAGE__->mk_accessors(qw/_user/); |
54c8dc06 |
9 | __PACKAGE__->mk_classdata($_) for qw/_auth_realms/; |
b003080b |
10 | } |
06675d2e |
11 | |
12 | use strict; |
13 | use warnings; |
14 | |
96777f3a |
15 | use Tie::RefHash; |
12dae309 |
16 | use Class::Inspector; |
96777f3a |
17 | |
bbf1cb39 |
18 | # this optimization breaks under Template::Toolkit |
19 | # use user_exists instead |
e145babc |
20 | #BEGIN { |
21 | # require constant; |
22 | # constant->import(have_want => eval { require Want }); |
23 | #} |
a1e5bd36 |
24 | |
54c8dc06 |
25 | our $VERSION = "0.10"; |
c7c003d3 |
26 | |
06675d2e |
27 | sub set_authenticated { |
54c8dc06 |
28 | my ( $c, $user, $realmname ) = @_; |
06675d2e |
29 | |
30 | $c->user($user); |
e300c5b6 |
31 | $c->request->{user} = $user; # compatibility kludge |
06675d2e |
32 | |
54c8dc06 |
33 | if (!$realmname) { |
34 | $realmname = 'default'; |
06675d2e |
35 | } |
54c8dc06 |
36 | |
37 | if ( $c->isa("Catalyst::Plugin::Session") |
38 | and $c->config->{authentication}{use_session} |
39 | and $user->supports("session") ) |
40 | { |
41 | $c->save_user_in_session($realmname, $user); |
42 | } |
43 | $user->_set_auth_realm($realmname); |
44 | |
45 | $c->NEXT::set_authenticated($user, $realmname); |
06675d2e |
46 | } |
47 | |
488433fd |
48 | sub _should_save_user_in_session { |
49 | my ( $c, $user ) = @_; |
50 | |
51 | $c->_auth_sessions_supported |
52 | and $c->config->{authentication}{use_session} |
53 | and $user->supports("session"); |
54 | } |
55 | |
56 | sub _should_load_user_from_session { |
57 | my ( $c, $user ) = @_; |
58 | |
59 | $c->_auth_sessions_supported |
60 | and $c->config->{authentication}{use_session} |
61 | and $c->session_is_valid; |
62 | } |
63 | |
64 | sub _auth_sessions_supported { |
65 | my $c = shift; |
66 | $c->isa("Catalyst::Plugin::Session"); |
67 | } |
68 | |
7bb06c91 |
69 | sub user { |
e300c5b6 |
70 | my $c = shift; |
7bb06c91 |
71 | |
e300c5b6 |
72 | if (@_) { |
73 | return $c->_user(@_); |
74 | } |
7bb06c91 |
75 | |
56e23e7a |
76 | if ( defined(my $user = $c->_user) ) { |
77 | return $user; |
78 | } else { |
47c6643f |
79 | return $c->auth_restore_user; |
e300c5b6 |
80 | } |
7bb06c91 |
81 | } |
82 | |
54c8dc06 |
83 | # change this to allow specification of a realm - to verify the user is part of that realm |
84 | # in addition to verifying that they exist. |
ce0b058d |
85 | sub user_exists { |
86 | my $c = shift; |
9deccb83 |
87 | return defined($c->_user) || defined($c->_user_in_session); |
56e23e7a |
88 | } |
89 | |
54c8dc06 |
90 | |
12dae309 |
91 | sub save_user_in_session { |
54c8dc06 |
92 | my ( $c, $realmname, $user ) = @_; |
12dae309 |
93 | |
54c8dc06 |
94 | $c->session->{__user_realm} = $realmname; |
95 | |
96 | # we want to ask the backend for a user prepared for the session. |
97 | # but older modules split this functionality between the user and the |
98 | # backend. We try the store first. If not, we use the old method. |
99 | my $realm = $c->get_auth_realm($realmname); |
100 | if ($realm->{'store'}->can('for_session')) { |
101 | $c->session->{__user} = $realm->{'store'}->for_session($c, $user); |
102 | } else { |
103 | $c->session->{__user} = $user->for_session; |
104 | } |
12dae309 |
105 | } |
106 | |
06675d2e |
107 | sub logout { |
108 | my $c = shift; |
109 | |
110 | $c->user(undef); |
b003080b |
111 | |
54c8dc06 |
112 | if ( |
113 | $c->isa("Catalyst::Plugin::Session") |
114 | and $c->config->{authentication}{use_session} |
115 | and $c->session_is_valid |
116 | ) { |
117 | delete @{ $c->session }{qw/__user __user_realm/}; |
b003080b |
118 | } |
351e2a82 |
119 | |
120 | $c->NEXT::logout(@_); |
06675d2e |
121 | } |
122 | |
54c8dc06 |
123 | sub find_user { |
124 | my ( $c, $userinfo, $realmname ) = @_; |
125 | |
126 | $realmname ||= 'default'; |
127 | my $realm = $c->get_auth_realm($realmname); |
128 | if ( $realm->{'store'} ) { |
129 | return $realm->{'store'}->find_user($userinfo, $c); |
130 | } else { |
131 | $c->log->debug('find_user: unable to locate a store matching the requested realm'); |
7d0922d8 |
132 | } |
133 | } |
134 | |
54c8dc06 |
135 | |
47c6643f |
136 | sub _user_in_session { |
137 | my $c = shift; |
138 | |
488433fd |
139 | return unless $c->_should_load_user_from_session; |
47c6643f |
140 | |
141 | return $c->session->{__user}; |
488433fd |
142 | } |
47c6643f |
143 | |
488433fd |
144 | sub _store_in_session { |
145 | my $c = shift; |
146 | |
147 | # we don't need verification, it's only called if _user_in_session returned something useful |
148 | |
149 | return $c->session->{__user_store}; |
47c6643f |
150 | } |
151 | |
7bb06c91 |
152 | sub auth_restore_user { |
54c8dc06 |
153 | my ( $c, $frozen_user, $realmname ) = @_; |
7bb06c91 |
154 | |
47c6643f |
155 | $frozen_user ||= $c->_user_in_session; |
156 | return unless defined($frozen_user); |
4402d92d |
157 | |
54c8dc06 |
158 | $realmname ||= $c->session->{__user_realm}; |
159 | return unless $realmname; # FIXME die unless? This is an internal inconsistency |
7bb06c91 |
160 | |
54c8dc06 |
161 | my $realm = $c->get_auth_realm($realmname); |
162 | $c->_user( my $user = $realm->{'store'}->from_session( $c, $frozen_user ) ); |
163 | |
164 | # this sets the realm the user originated in. |
165 | $user->_set_auth_realm($realmname); |
e300c5b6 |
166 | return $user; |
7bb06c91 |
167 | |
168 | } |
169 | |
54c8dc06 |
170 | # we can't actually do our setup in setup because the model has not yet been loaded. |
171 | # So we have to trigger off of setup_finished. :-( |
06675d2e |
172 | sub setup { |
173 | my $c = shift; |
174 | |
54c8dc06 |
175 | $c->_authentication_initialize(); |
176 | $c->NEXT::setup(@_); |
177 | } |
178 | |
179 | ## the actual initialization routine. whee. |
180 | sub _authentication_initialize { |
181 | my $c = shift; |
182 | |
183 | if ($c->_auth_realms) { return }; |
184 | |
185 | my $cfg = $c->config->{'authentication'} || {}; |
06675d2e |
186 | |
187 | %$cfg = ( |
188 | use_session => 1, |
189 | %$cfg, |
190 | ); |
b003080b |
191 | |
54c8dc06 |
192 | my $realmhash = {}; |
193 | $c->_auth_realms($realmhash); |
194 | |
195 | ## BACKWARDS COMPATIBILITY - if realm is not defined - then we are probably dealing |
196 | ## with an old-school config. The only caveat here is that we must add a classname |
197 | if (exists($cfg->{'realms'})) { |
198 | |
199 | foreach my $realm (keys %{$cfg->{'realms'}}) { |
200 | $c->setup_auth_realm($realm, $cfg->{'realms'}{$realm}); |
201 | } |
202 | |
203 | # if we have a 'default-realm' in the config hash and we don't already |
204 | # have a realm called 'default', we point default at the realm specified |
205 | if (exists($cfg->{'default_realm'}) && !$c->get_auth_realm('default')) { |
206 | $c->set_default_auth_realm($cfg->{'default_realm'}); |
207 | } |
208 | } else { |
209 | foreach my $storename (keys %{$cfg->{'stores'}}) { |
210 | my $realmcfg = { |
211 | store => $cfg->{'stores'}{$storename}, |
212 | }; |
213 | $c->setup_auth_realm($storename, $realmcfg); |
214 | } |
215 | } |
216 | |
06675d2e |
217 | } |
218 | |
54c8dc06 |
219 | |
220 | # set up realmname. |
221 | sub setup_auth_realm { |
222 | my ($app, $realmname, $config) = @_; |
223 | |
224 | $app->log->debug("Setting up $realmname"); |
225 | if (!exists($config->{'store'}{'class'})) { |
226 | Carp::croak "Couldn't setup the authentication realm named '$realmname', no class defined"; |
227 | } |
228 | |
229 | # use the |
230 | my $storeclass = $config->{'store'}{'class'}; |
231 | |
232 | ## follow catalyst class naming - a + prefix means a fully qualified class, otherwise it's |
233 | ## taken to mean C::P::A::Store::(specifiedclass)::Backend |
234 | if ($storeclass !~ /^\+(.*)$/ ) { |
235 | $storeclass = "Catalyst::Plugin::Authentication::Store::${storeclass}::Backend"; |
236 | } else { |
237 | $storeclass = $1; |
238 | } |
239 | |
240 | |
241 | # a little niceness - since most systems seem to use the password credential class, |
242 | # if no credential class is specified we use password. |
243 | $config->{credential}{class} ||= "Catalyst::Plugin::Authentication::Credential::Password"; |
244 | |
245 | my $credentialclass = $config->{'credential'}{'class'}; |
246 | |
247 | ## follow catalyst class naming - a + prefix means a fully qualified class, otherwise it's |
248 | ## taken to mean C::P::A::Credential::(specifiedclass) |
249 | if ($credentialclass !~ /^\+(.*)$/ ) { |
250 | $credentialclass = "Catalyst::Plugin::Authentication::Credential::${credentialclass}"; |
251 | } else { |
252 | $credentialclass = $1; |
253 | } |
254 | |
255 | # if we made it here - we have what we need to load the classes; |
256 | Catalyst::Utils::ensure_class_loaded( $credentialclass ); |
257 | Catalyst::Utils::ensure_class_loaded( $storeclass ); |
258 | |
259 | # BACKWARDS COMPATIBILITY - if the store class does not define find_user, we define it in terms |
260 | # of get_user and add it to the class. this is because the auth routines use find_user, |
261 | # and rely on it being present. (this avoids per-call checks) |
262 | if (!$storeclass->can('find_user')) { |
263 | no strict 'refs'; |
264 | *{"${storeclass}::find_user"} = sub { |
265 | my ($self, $info) = @_; |
266 | my @rest = @{$info->{rest}} if exists($info->{rest}); |
267 | $self->get_user($info->{id}, @rest); |
268 | }; |
269 | } |
270 | |
271 | $app->auth_realms->{$realmname}{'store'} = $storeclass->new($config->{'store'}, $app); |
272 | if ($credentialclass->can('new')) { |
273 | $app->auth_realms->{$realmname}{'credential'} = $credentialclass->new($config->{'credential'}, $app); |
274 | } else { |
275 | # if the credential class is not actually a class - has no 'new' operator, we wrap it, |
276 | # once again - to allow our code to be simple at runtime and allow non-OO packages to function. |
277 | my $wrapperclass = 'Catalyst::Plugin::Authentication::Credential::Wrapper'; |
278 | Catalyst::Utils::ensure_class_loaded( $wrapperclass ); |
279 | $app->auth_realms->{$realmname}{'credential'} = $wrapperclass->new($config->{'credential'}, $app); |
280 | } |
96777f3a |
281 | } |
282 | |
54c8dc06 |
283 | sub auth_realms { |
284 | my $self = shift; |
285 | return($self->_auth_realms); |
96777f3a |
286 | } |
287 | |
54c8dc06 |
288 | sub get_auth_realm { |
289 | my ($app, $realmname) = @_; |
290 | return $app->auth_realms->{$realmname}; |
291 | } |
96777f3a |
292 | |
54c8dc06 |
293 | sub set_default_auth_realm { |
294 | my ($app, $realmname) = @_; |
295 | |
296 | if (exists($app->auth_realms->{$realmname})) { |
297 | $app->auth_realms->{'default'} = $app->auth_realms->{$realmname}; |
12dae309 |
298 | } |
54c8dc06 |
299 | return $app->get_auth_realm('default'); |
96777f3a |
300 | } |
301 | |
54c8dc06 |
302 | sub authenticate { |
303 | my ($app, $userinfo, $realmname) = @_; |
304 | |
305 | if (!$realmname) { |
306 | $realmname = 'default'; |
307 | } |
308 | |
309 | my $realm = $app->get_auth_realm($realmname); |
310 | |
311 | if ($realm && exists($realm->{'credential'})) { |
312 | my $user = $realm->{'credential'}->authenticate($app, $realm->{store}, $userinfo); |
313 | if ($user) { |
314 | $app->set_authenticated($user, $realmname); |
315 | return $user; |
316 | } |
317 | } else { |
318 | $app->log->debug("The realm requested, '$realmname' does not exist," . |
319 | " or there is no credential associated with it.") |
320 | } |
321 | return 0; |
96777f3a |
322 | } |
323 | |
54c8dc06 |
324 | ## BACKWARDS COMPATIBILITY -- Warning: Here be monsters! |
325 | # |
326 | # What follows are backwards compatibility routines - for use with Stores and Credentials |
327 | # that have not been updated to work with C::P::Authentication v0.10. |
328 | # These are here so as to not break people's existing installations, but will go away |
329 | # in a future version. |
330 | # |
331 | # The old style of configuration only supports a single store, as each store module |
332 | # sets itself as the default store upon being loaded. This is the only supported |
333 | # 'compatibility' mode. |
334 | # |
335 | |
336 | sub get_user { |
337 | my ( $c, $uid, @rest ) = @_; |
96777f3a |
338 | |
54c8dc06 |
339 | return $c->find_user( {'id' => $uid, 'rest'=>\@rest }, 'default' ); |
96777f3a |
340 | } |
341 | |
54c8dc06 |
342 | ## |
343 | ## this should only be called when using old-style authentication plugins. IF this gets |
344 | ## called in a new-style config - it will OVERWRITE the store of your default realm. Don't do it. |
345 | ## also - this is a partial setup - because no credential is instantiated... in other words it ONLY |
346 | ## works with old-style auth plugins and C::P::Authentication in compatibility mode. Trying to combine |
347 | ## this with a realm-type config will probably crash your app. |
96777f3a |
348 | sub default_auth_store { |
12dae309 |
349 | my $self = shift; |
96777f3a |
350 | |
12dae309 |
351 | if ( my $new = shift ) { |
54c8dc06 |
352 | $self->auth_realms->{'default'}{'store'} = $new; |
353 | my $storeclass = ref($new); |
354 | |
355 | # BACKWARDS COMPATIBILITY - if the store class does not define find_user, we define it in terms |
356 | # of get_user and add it to the class. this is because the auth routines use find_user, |
357 | # and rely on it being present. (this avoids per-call checks) |
358 | if (!$storeclass->can('find_user')) { |
359 | no strict 'refs'; |
360 | *{"${storeclass}::find_user"} = sub { |
361 | my ($self, $info) = @_; |
362 | my @rest = @{$info->{rest}} if exists($info->{rest}); |
363 | $self->get_user($info->{id}, @rest); |
364 | }; |
365 | } |
12dae309 |
366 | } |
96777f3a |
367 | |
54c8dc06 |
368 | return $self->get_auth_realm('default')->{'store'}; |
96777f3a |
369 | } |
370 | |
54c8dc06 |
371 | ## BACKWARDS COMPATIBILITY |
372 | ## this only ever returns a hash containing 'default' - as that is the only |
373 | ## supported mode of calling this. |
374 | sub auth_store_names { |
375 | my $self = shift; |
376 | |
377 | my %hash = ( $self->get_auth_realm('default')->{'store'} => 'default' ); |
378 | } |
379 | |
380 | sub get_auth_store { |
381 | my ( $self, $name ) = @_; |
382 | |
383 | if ($name ne 'default') { |
384 | Carp::croak "get_auth_store called on non-default realm '$name'. Only default supported in compatibility mode"; |
385 | } else { |
386 | $self->default_auth_store(); |
387 | } |
388 | } |
389 | |
390 | sub get_auth_store_name { |
391 | my ( $self, $store ) = @_; |
392 | return 'default'; |
393 | } |
394 | |
395 | # sub auth_stores is only used internally - here for completeness |
396 | sub auth_stores { |
397 | my $self = shift; |
398 | |
399 | my %hash = ( 'default' => $self->get_auth_realm('default')->{'store'}); |
400 | } |
401 | |
402 | |
403 | |
404 | |
405 | |
406 | |
06675d2e |
407 | __PACKAGE__; |
408 | |
409 | __END__ |
410 | |
411 | =pod |
412 | |
413 | =head1 NAME |
414 | |
55395841 |
415 | Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst |
416 | authentication framework. |
06675d2e |
417 | |
418 | =head1 SYNOPSIS |
419 | |
189b5b0c |
420 | use Catalyst qw/ |
421 | Authentication |
189b5b0c |
422 | /; |
423 | |
424 | # later on ... |
54c8dc06 |
425 | $c->authenticate({ username => 'myusername', password => 'mypassword' }); |
426 | my $age = $c->user->get('age'); |
189b5b0c |
427 | $c->logout; |
06675d2e |
428 | |
429 | =head1 DESCRIPTION |
430 | |
e7522758 |
431 | The authentication plugin provides generic user support. It is the basis |
432 | for both authentication (checking the user is who they claim to be), and |
433 | authorization (allowing the user to do what the system authorises them to do). |
06675d2e |
434 | |
e7522758 |
435 | Using authentication is split into two parts. A Store is used to actually |
436 | store the user information, and can store any amount of data related to |
437 | the user. Multiple stores can be accessed from within one application. |
62f27384 |
438 | Credentials are used to verify users, using information from the store, |
439 | given data from the frontend. |
189b5b0c |
440 | |
6a36933d |
441 | To implement authentication in a Catalyst application you need to add this |
e7522758 |
442 | module, plus at least one store and one credential module. |
189b5b0c |
443 | |
e7522758 |
444 | Authentication data can also be stored in a session, if the application |
445 | is using the L<Catalyst::Plugin::Session> module. |
06675d2e |
446 | |
4bb9b01c |
447 | =head1 INTRODUCTION |
448 | |
449 | =head2 The Authentication/Authorization Process |
450 | |
451 | Web applications typically need to identify a user - to tell the user apart |
452 | from other users. This is usually done in order to display private information |
453 | that is only that user's business, or to limit access to the application so |
454 | that only certain entities can access certain parts. |
455 | |
456 | This process is split up into several steps. First you ask the user to identify |
457 | themselves. At this point you can't be sure that the user is really who they |
458 | claim to be. |
459 | |
6a36933d |
460 | Then the user tells you who they are, and backs this claim with some piece of |
4bb9b01c |
461 | information that only the real user could give you. For example, a password is |
462 | a secret that is known to both the user and you. When the user tells you this |
463 | password you can assume they're in on the secret and can be trusted (ignore |
464 | identity theft for now). Checking the password, or any other proof is called |
465 | B<credential verification>. |
466 | |
467 | By this time you know exactly who the user is - the user's identity is |
468 | B<authenticated>. This is where this module's job stops, and other plugins step |
469 | in. The next logical step is B<authorization>, the process of deciding what a |
470 | user is (or isn't) allowed to do. For example, say your users are split into |
471 | two main groups - regular users and administrators. You should verify that the |
472 | currently logged in user is indeed an administrator before performing the |
6a36933d |
473 | actions of an administrative part of your application. One way to do this is |
4bb9b01c |
474 | with role based access control. |
475 | |
476 | =head2 The Components In This Framework |
477 | |
62f27384 |
478 | =head3 Realms |
479 | |
480 | Configuration of the Catalyst::Plugin::Authentication framework is done in |
481 | terms of realms. In simplest terms, a realm is a pairing of a Credential |
482 | verifier and a User storage (Store) backend. |
483 | |
484 | An application can have any number of Realms, each of which operates |
485 | independant of the others. Each realm has a name, which is used to identify it |
486 | as the target of an authentication request. This name can be anything, such as |
487 | 'users' or 'members'. One realm must be defined as the default_realm, which is |
488 | used when no realm name is specified. More about this in the configuration |
489 | section. |
490 | |
4bb9b01c |
491 | =head3 Credential Verifiers |
492 | |
493 | When user input is transferred to the L<Catalyst> application (typically via |
62f27384 |
494 | form inputs) this authentication data then enters the authentication framework |
495 | through the $c->authenticate() routine. From there, it is passed to the |
496 | appropriate Credential verifier. |
4bb9b01c |
497 | |
498 | These plugins check the data, and ensure that it really proves the user is who |
499 | they claim to be. |
500 | |
501 | =head3 Storage Backends |
502 | |
62f27384 |
503 | The authentication data also identify a user, and the Storage Backend modules |
504 | use this data to locate and return a standardized object-oriented |
505 | representation of a user. |
4bb9b01c |
506 | |
507 | When a user is retrieved from a store it is not necessarily authenticated. |
62f27384 |
508 | Credential verifiers accept a set of authentication data and use this |
509 | information to retrieve the user from the store they are paired with. |
4bb9b01c |
510 | |
511 | =head3 The Core Plugin |
512 | |
62f27384 |
513 | This plugin on its own is the glue, providing realm configuration, session |
4bb9b01c |
514 | integration, and other goodness for the other plugins. |
515 | |
516 | =head3 Other Plugins |
517 | |
518 | More layers of plugins can be stacked on top of the authentication code. For |
519 | example, L<Catalyst::Plugin::Session::PerUser> provides an abstraction of |
520 | browser sessions that is more persistent per users. |
521 | L<Catalyst::Plugin::Authorization::Roles> provides an accepted way to separate |
522 | and group users into categories, and then check which categories the current |
523 | user belongs to. |
524 | |
5e91c057 |
525 | =head1 EXAMPLE |
526 | |
62f27384 |
527 | Let's say we were storing users in an simple perl hash. Users are |
6a36933d |
528 | stored in that file, with a hashed password and some extra comments. Users are |
529 | verified by supplying a password which is matched with the file. |
5e91c057 |
530 | |
531 | This means that our application will begin like this: |
532 | |
533 | package MyApp; |
534 | |
535 | use Catalyst qw/ |
536 | Authentication |
5e91c057 |
537 | /; |
538 | |
62f27384 |
539 | __PACKAGE__->config->{authentication} = |
540 | { |
541 | default_realm => 'members', |
542 | realms => { |
543 | members => { |
544 | credential => { |
545 | class => 'Password' |
546 | }, |
547 | store => { |
548 | class => 'Minimal', |
549 | users = { |
550 | bob => { |
551 | password => "s3cr3t", |
552 | editor => 'yes', |
553 | roles => [qw/edit delete/], |
554 | }, |
555 | william => { |
556 | password => "s00p3r", |
557 | roles => [qw/comment/], |
558 | } |
559 | } |
560 | } |
561 | } |
562 | } |
563 | }; |
564 | |
5e91c057 |
565 | |
62f27384 |
566 | This tells the authentication plugin what realms are available, which credential |
567 | and store modules are used, and the configuration of each. |
5e91c057 |
568 | |
62f27384 |
569 | With this code loaded, we can now attempt to authenticate users. |
5e91c057 |
570 | |
62f27384 |
571 | To show an example of this, let's create an authentication controller: |
5e91c057 |
572 | |
573 | package MyApp::Controller::Auth; |
574 | |
575 | sub login : Local { |
576 | my ( $self, $c ) = @_; |
577 | |
578 | if ( my $user = $c->req->param("user") |
579 | and my $password = $c->req->param("password") ) |
580 | { |
62f27384 |
581 | if ( $c->authenticate( { username => $user, |
582 | password => $password } ) ) { |
583 | $c->res->body( "hello " . $c->user->get("name") ); |
5e91c057 |
584 | } else { |
585 | # login incorrect |
586 | } |
587 | } |
588 | else { |
589 | # invalid form input |
590 | } |
591 | } |
592 | |
593 | This code should be very readable. If all the necessary fields are supplied, |
62f27384 |
594 | call the L<Catalyst::Plugin::Authentication/authenticate> method in the |
595 | controller. If it succeeds the user is logged in. |
5e91c057 |
596 | |
62f27384 |
597 | The credential verifier will attempt to retrieve the user whose details match |
598 | the authentication information provided to $c->authenticate(). Once it fetches |
599 | the user the password is checked and if it matches the user will be |
600 | 'authenticated' and C<< $c->user >> will contain the user object retrieved |
601 | from the store. |
5e91c057 |
602 | |
62f27384 |
603 | In the above case, the default realm is checked, but we could just as easily |
604 | check an alternate realm. If this were an admin login, for example, we could |
605 | authenticate on the admin realm by simply changing the $c->authenticate() |
606 | call: |
5e91c057 |
607 | |
62f27384 |
608 | if ( $c->authenticate( { username => $user, |
609 | password => $password }, 'admin' )l ) { |
610 | $c->res->body( "hello " . $c->user->get("name") ); |
611 | } ... |
5e91c057 |
612 | |
5e91c057 |
613 | |
62f27384 |
614 | Now suppose we want to restrict the ability to edit to a user with 'edit' |
615 | in it's roles list. |
5e91c057 |
616 | |
62f27384 |
617 | The restricted action might look like this: |
5e91c057 |
618 | |
62f27384 |
619 | sub edit : Local { |
5e91c057 |
620 | my ( $self, $c ) = @_; |
621 | |
622 | $c->detach("unauthorized") |
623 | unless $c->user_exists |
62f27384 |
624 | and $c->user->get('editor') == 'yes'; |
5e91c057 |
625 | |
626 | # do something restricted here |
627 | } |
628 | |
629 | This is somewhat similar to role based access control. |
62f27384 |
630 | L<Catalyst::Plugin::Authentication::Store::Minimal> treats the roles field as |
631 | an array of role names. Let's leverage this. Add the role authorization |
632 | plugin: |
5e91c057 |
633 | |
634 | use Catalyst qw/ |
635 | ... |
636 | Authorization::Roles |
637 | /; |
638 | |
62f27384 |
639 | sub edit : Local { |
5e91c057 |
640 | my ( $self, $c ) = @_; |
641 | |
62f27384 |
642 | $c->detach("unauthorized") unless $c->check_roles("edit"); |
5e91c057 |
643 | |
644 | # do something restricted here |
645 | } |
646 | |
647 | This is somewhat simpler and will work if you change your store, too, since the |
648 | role interface is consistent. |
649 | |
62f27384 |
650 | .... This is the end of the updated documentation for v0.10 - more soon .... |
651 | |
5e91c057 |
652 | Let's say your app grew, and you now have 10000 users. It's no longer efficient |
653 | to maintain an htpasswd file, so you move this data to a database. |
654 | |
655 | use Catalyst qw/ |
656 | Authentication |
657 | Authentication::Credential::Password |
658 | Authentication::Store::DBIC |
659 | Authorization::Roles |
660 | /; |
661 | |
662 | __PACKAGE__->config->{authentication}{dbic} = ...; # see the DBIC store docs |
663 | |
664 | The rest of your code should be unchanged. Now let's say you are integrating |
665 | typekey authentication to your system. For simplicity's sake we'll assume that |
666 | the user's are still keyed in the same way. |
667 | |
668 | use Catalyst qw/ |
669 | Authentication |
670 | Authentication::Credential::Password |
671 | Authentication::Credential::TypeKey |
672 | Authentication::Store::DBIC |
673 | Authorization::Roles |
674 | /; |
675 | |
676 | And in your auth controller add a new action: |
677 | |
678 | sub typekey : Local { |
679 | my ( $self, $c ) = @_; |
680 | |
681 | if ( $c->authenticate_typekey) { # uses $c->req and Authen::TypeKey |
682 | # same stuff as the $c->login method |
683 | # ... |
684 | } |
685 | } |
686 | |
687 | You've now added a new credential verification mechanizm orthogonally to the |
688 | other components. All you have to do is make sure that the credential verifiers |
689 | pass on the same types of parameters to the store in order to retrieve user |
690 | objects. |
691 | |
06675d2e |
692 | =head1 METHODS |
693 | |
694 | =over 4 |
695 | |
06675d2e |
696 | =item user |
697 | |
189b5b0c |
698 | Returns the currently logged in user or undef if there is none. |
06675d2e |
699 | |
ce0b058d |
700 | =item user_exists |
701 | |
702 | Whether or not a user is logged in right now. |
703 | |
8bcb3a4b |
704 | The reason this method exists is that C<< $c->user >> may needlessly load the |
ce0b058d |
705 | user from the auth store. |
706 | |
707 | If you're just going to say |
708 | |
4359cfe3 |
709 | if ( $c->user_exists ) { |
ce0b058d |
710 | # foo |
711 | } else { |
712 | $c->forward("login"); |
713 | } |
714 | |
4359cfe3 |
715 | it should be more efficient than C<< $c->user >> when a user is marked in the |
716 | session but C<< $c->user >> hasn't been called yet. |
ce0b058d |
717 | |
4402d92d |
718 | =item logout |
719 | |
720 | Delete the currently logged in user from C<user> and the session. |
721 | |
7d0922d8 |
722 | =item get_user $uid |
723 | |
189b5b0c |
724 | Fetch a particular users details, defined by the given ID, via the default store. |
725 | |
726 | =back |
727 | |
728 | =head1 CONFIGURATION |
729 | |
730 | =over 4 |
731 | |
732 | =item use_session |
733 | |
734 | Whether or not to store the user's logged in state in the session, if the |
e7522758 |
735 | application is also using the L<Catalyst::Plugin::Session> plugin. This |
736 | value is set to true per default. |
737 | |
738 | =item store |
739 | |
1e055395 |
740 | If multiple stores are being used, set the module you want as default here. |
7d0922d8 |
741 | |
1e055395 |
742 | =item stores |
743 | |
744 | If multiple stores are being used, you need to provide a name for each store |
745 | here, as a hash, the keys are the names you wish to use, and the values are |
746 | the the names of the plugins. |
747 | |
748 | # example |
749 | __PACKAGE__->config( authentication => { |
750 | store => 'Catalyst::Plugin::Authentication::Store::HtPasswd', |
751 | stores => { |
752 | 'dbic' => 'Catalyst::Plugin::Authentication::Store::DBIC' |
753 | } |
754 | }); |
755 | |
2bcde605 |
756 | =back |
1e055395 |
757 | |
4fbe2e14 |
758 | =head1 METHODS FOR STORE MANAGEMENT |
759 | |
fe4cf44a |
760 | =over 4 |
761 | |
7d0922d8 |
762 | =item default_auth_store |
763 | |
4fbe2e14 |
764 | Return the store whose name is 'default'. |
7d0922d8 |
765 | |
189b5b0c |
766 | This is set to C<< $c->config->{authentication}{store} >> if that value exists, |
4fbe2e14 |
767 | or by using a Store plugin: |
768 | |
769 | use Catalyst qw/Authentication Authentication::Store::Minimal/; |
770 | |
771 | Sets the default store to |
772 | L<Catalyst::Plugin::Authentication::Store::Minimal::Backend>. |
773 | |
a1e5bd36 |
774 | |
4fbe2e14 |
775 | =item get_auth_store $name |
776 | |
777 | Return the store whose name is $name. |
778 | |
779 | =item get_auth_store_name $store |
780 | |
781 | Return the name of the store $store. |
782 | |
783 | =item auth_stores |
784 | |
785 | A hash keyed by name, with the stores registered in the app. |
786 | |
787 | =item auth_store_names |
788 | |
789 | A ref-hash keyed by store, which contains the names of the stores. |
790 | |
791 | =item register_auth_stores %stores_by_name |
792 | |
793 | Register stores into the application. |
06675d2e |
794 | |
fe4cf44a |
795 | =back |
796 | |
06675d2e |
797 | =head1 INTERNAL METHODS |
798 | |
799 | =over 4 |
800 | |
801 | =item set_authenticated $user |
802 | |
803 | Marks a user as authenticated. Should be called from a |
804 | C<Catalyst::Plugin::Authentication::Credential> plugin after successful |
805 | authentication. |
806 | |
807 | This involves setting C<user> and the internal data in C<session> if |
808 | L<Catalyst::Plugin::Session> is loaded. |
809 | |
e300c5b6 |
810 | =item auth_restore_user $user |
811 | |
812 | Used to restore a user from the session, by C<user> only when it's actually |
813 | needed. |
814 | |
815 | =item save_user_in_session $user |
816 | |
817 | Used to save the user in a session. |
818 | |
06675d2e |
819 | =item prepare |
820 | |
821 | Revives a user from the session object if there is one. |
822 | |
823 | =item setup |
824 | |
825 | Sets the default configuration parameters. |
826 | |
827 | =item |
828 | |
829 | =back |
830 | |
fbe577ac |
831 | =head1 SEE ALSO |
832 | |
4bb9b01c |
833 | This list might not be up to date. |
834 | |
835 | =head2 User Storage Backends |
836 | |
fbe577ac |
837 | L<Catalyst::Plugin::Authentication::Store::Minimal>, |
4bb9b01c |
838 | L<Catalyst::Plugin::Authentication::Store::Htpasswd>, |
839 | L<Catalyst::Plugin::Authentication::Store::DBIC> (also works with Class::DBI). |
840 | |
841 | =head2 Credential verification |
842 | |
843 | L<Catalyst::Plugin::Authentication::Credential::Password>, |
844 | L<Catalyst::Plugin::Authentication::Credential::HTTP>, |
845 | L<Catalyst::Plugin::Authentication::Credential::TypeKey> |
846 | |
847 | =head2 Authorization |
848 | |
fbe577ac |
849 | L<Catalyst::Plugin::Authorization::ACL>, |
4bb9b01c |
850 | L<Catalyst::Plugin::Authorization::Roles> |
851 | |
5e91c057 |
852 | =head2 Internals Documentation |
853 | |
854 | L<Catalyst::Plugin::Authentication::Store> |
855 | |
4bb9b01c |
856 | =head2 Misc |
857 | |
858 | L<Catalyst::Plugin::Session>, |
859 | L<Catalyst::Plugin::Session::PerUser> |
fbe577ac |
860 | |
93f08fb0 |
861 | =head1 DON'T SEE ALSO |
862 | |
1a05e6ed |
863 | This module along with its sub plugins deprecate a great number of other |
864 | modules. These include L<Catalyst::Plugin::Authentication::Simple>, |
865 | L<Catalyst::Plugin::Authentication::CDBI>. |
93f08fb0 |
866 | |
867 | At the time of writing these plugins have not yet been replaced or updated, but |
1a05e6ed |
868 | should be eventually: L<Catalyst::Plugin::Authentication::OpenID>, |
869 | L<Catalyst::Plugin::Authentication::LDAP>, |
870 | L<Catalyst::Plugin::Authentication::CDBI::Basic>, |
871 | L<Catalyst::Plugin::Authentication::Basic::Remote>. |
93f08fb0 |
872 | |
2bcde605 |
873 | =head1 AUTHORS |
fbe577ac |
874 | |
875 | Yuval Kogman, C<nothingmuch@woobling.org> |
2bcde605 |
876 | |
7d2f34eb |
877 | Jess Robinson |
2bcde605 |
878 | |
7d2f34eb |
879 | David Kamholz |
06675d2e |
880 | |
ff46c00b |
881 | =head1 COPYRIGHT & LICENSE |
fbe577ac |
882 | |
883 | Copyright (c) 2005 the aforementioned authors. All rights |
884 | reserved. This program is free software; you can redistribute |
885 | it and/or modify it under the same terms as Perl itself. |
886 | |
887 | =cut |
06675d2e |
888 | |