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