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/); |
96777f3a |
9 | __PACKAGE__->mk_classdata($_) for qw/_auth_stores _auth_store_names/; |
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 | |
bbf1cb39 |
25 | our $VERSION = "0.04"; |
c7c003d3 |
26 | |
06675d2e |
27 | sub set_authenticated { |
28 | my ( $c, $user ) = @_; |
29 | |
30 | $c->user($user); |
e300c5b6 |
31 | $c->request->{user} = $user; # compatibility kludge |
06675d2e |
32 | |
33 | if ( $c->isa("Catalyst::Plugin::Session") |
96777f3a |
34 | and $c->config->{authentication}{use_session} |
12dae309 |
35 | and $user->supports("session") ) |
06675d2e |
36 | { |
12dae309 |
37 | $c->save_user_in_session($user); |
06675d2e |
38 | } |
55395841 |
39 | |
4fbe2e14 |
40 | $c->NEXT::set_authenticated($user); |
06675d2e |
41 | } |
42 | |
7bb06c91 |
43 | sub user { |
e300c5b6 |
44 | my $c = shift; |
7bb06c91 |
45 | |
e300c5b6 |
46 | if (@_) { |
47 | return $c->_user(@_); |
48 | } |
7bb06c91 |
49 | |
e300c5b6 |
50 | my $user = $c->_user; |
7bb06c91 |
51 | |
e300c5b6 |
52 | if ( $user and !Scalar::Util::blessed($user) ) { |
cde43a59 |
53 | # return 1 if have_want() && Want::want("BOOL"); |
e300c5b6 |
54 | return $c->auth_restore_user($user); |
55 | } |
7bb06c91 |
56 | |
e300c5b6 |
57 | return $user; |
7bb06c91 |
58 | } |
59 | |
ce0b058d |
60 | sub user_exists { |
61 | my $c = shift; |
62 | return defined($c->_user); |
63 | } |
64 | |
12dae309 |
65 | sub save_user_in_session { |
e300c5b6 |
66 | my ( $c, $user ) = @_; |
12dae309 |
67 | |
68 | my $store = $user->store || ref $user; |
69 | $c->session->{__user_store} = $c->get_auth_store_name($store) || $store; |
70 | $c->session->{__user} = $user->for_session; |
71 | } |
72 | |
06675d2e |
73 | sub logout { |
74 | my $c = shift; |
75 | |
76 | $c->user(undef); |
b003080b |
77 | |
78 | if ( $c->isa("Catalyst::Plugin::Session") |
79 | and $c->config->{authentication}{use_session} ) |
80 | { |
96777f3a |
81 | delete @{ $c->session }{qw/__user __user_store/}; |
b003080b |
82 | } |
351e2a82 |
83 | |
84 | $c->NEXT::logout(@_); |
06675d2e |
85 | } |
86 | |
7d0922d8 |
87 | sub get_user { |
88 | my ( $c, $uid ) = @_; |
89 | |
90 | if ( my $store = $c->default_auth_store ) { |
91 | return $store->get_user($uid); |
92 | } |
93 | else { |
94 | Catalyst::Exception->throw( |
95 | "The user id $uid was passed to an authentication " |
96 | . "plugin, but no default store was specified" ); |
97 | } |
98 | } |
99 | |
06675d2e |
100 | sub prepare { |
101 | my $c = shift->NEXT::prepare(@_); |
102 | |
4fbe2e14 |
103 | if ( $c->isa("Catalyst::Plugin::Session") |
06675d2e |
104 | and !$c->user ) |
105 | { |
7bb06c91 |
106 | if ( $c->sessionid and my $frozen_user = $c->session->{__user} ) { |
e300c5b6 |
107 | $c->_user($frozen_user); |
06675d2e |
108 | } |
109 | } |
110 | |
111 | return $c; |
112 | } |
113 | |
7bb06c91 |
114 | sub auth_restore_user { |
e300c5b6 |
115 | my ( $c, $frozen_user, $store_name ) = @_; |
7bb06c91 |
116 | |
4fbe2e14 |
117 | return |
cde43a59 |
118 | unless $c->isa("Catalyst::Plugin::Session") |
4fbe2e14 |
119 | and $c->config->{authentication}{use_session} |
120 | and $c->sessionid; |
4402d92d |
121 | |
e300c5b6 |
122 | $store_name ||= $c->session->{__user_store}; |
123 | $frozen_user ||= $c->session->{__user}; |
7bb06c91 |
124 | |
e300c5b6 |
125 | my $store = $c->get_auth_store($store_name); |
126 | $c->_user( my $user = $store->from_session( $c, $frozen_user ) ); |
7bb06c91 |
127 | |
e300c5b6 |
128 | return $user; |
7bb06c91 |
129 | |
130 | } |
131 | |
06675d2e |
132 | sub setup { |
133 | my $c = shift; |
134 | |
712a35bf |
135 | my $cfg = $c->config->{authentication} || {}; |
06675d2e |
136 | |
137 | %$cfg = ( |
138 | use_session => 1, |
139 | %$cfg, |
140 | ); |
b003080b |
141 | |
12dae309 |
142 | $c->register_auth_stores( |
143 | default => $cfg->{store}, |
144 | %{ $cfg->{stores} || {} }, |
145 | ); |
96777f3a |
146 | |
b003080b |
147 | $c->NEXT::setup(@_); |
06675d2e |
148 | } |
149 | |
96777f3a |
150 | sub get_auth_store { |
12dae309 |
151 | my ( $self, $name ) = @_; |
152 | $self->auth_stores->{$name} || ( Class::Inspector->loaded($name) && $name ); |
96777f3a |
153 | } |
154 | |
155 | sub get_auth_store_name { |
12dae309 |
156 | my ( $self, $store ) = @_; |
157 | $self->auth_store_names->{$store}; |
96777f3a |
158 | } |
159 | |
160 | sub register_auth_stores { |
12dae309 |
161 | my ( $self, %new ) = @_; |
96777f3a |
162 | |
12dae309 |
163 | foreach my $name ( keys %new ) { |
164 | my $store = $new{$name} or next; |
165 | $self->auth_stores->{$name} = $store; |
166 | $self->auth_store_names->{$store} = $name; |
167 | } |
96777f3a |
168 | } |
169 | |
170 | sub auth_stores { |
12dae309 |
171 | my $self = shift; |
172 | $self->_auth_stores(@_) || $self->_auth_stores( {} ); |
96777f3a |
173 | } |
174 | |
175 | sub auth_store_names { |
12dae309 |
176 | my $self = shift; |
96777f3a |
177 | |
4402d92d |
178 | $self->_auth_store_names || do { |
12dae309 |
179 | tie my %hash, 'Tie::RefHash'; |
180 | $self->_auth_store_names( \%hash ); |
4fbe2e14 |
181 | } |
96777f3a |
182 | } |
183 | |
184 | sub default_auth_store { |
12dae309 |
185 | my $self = shift; |
96777f3a |
186 | |
12dae309 |
187 | if ( my $new = shift ) { |
188 | $self->register_auth_stores( default => $new ); |
189 | } |
96777f3a |
190 | |
12dae309 |
191 | $self->get_auth_store("default"); |
96777f3a |
192 | } |
193 | |
06675d2e |
194 | __PACKAGE__; |
195 | |
196 | __END__ |
197 | |
198 | =pod |
199 | |
200 | =head1 NAME |
201 | |
55395841 |
202 | Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst |
203 | authentication framework. |
06675d2e |
204 | |
205 | =head1 SYNOPSIS |
206 | |
189b5b0c |
207 | use Catalyst qw/ |
208 | Authentication |
209 | Authentication::Store::Foo |
210 | Authentication::Credential::Password |
211 | /; |
212 | |
213 | # later on ... |
214 | # ->login is provided by the Credential::Password module |
215 | $c->login('myusername', 'mypassword'); |
216 | my $age = $c->user->age; |
217 | $c->logout; |
06675d2e |
218 | |
219 | =head1 DESCRIPTION |
220 | |
e7522758 |
221 | The authentication plugin provides generic user support. It is the basis |
222 | for both authentication (checking the user is who they claim to be), and |
223 | authorization (allowing the user to do what the system authorises them to do). |
06675d2e |
224 | |
e7522758 |
225 | Using authentication is split into two parts. A Store is used to actually |
226 | store the user information, and can store any amount of data related to |
227 | the user. Multiple stores can be accessed from within one application. |
228 | Credentials are used to verify users, using the store, given data from |
229 | the frontend. |
189b5b0c |
230 | |
e7522758 |
231 | To implement authentication in a catalyst application you need to add this |
232 | module, plus at least one store and one credential module. |
189b5b0c |
233 | |
e7522758 |
234 | Authentication data can also be stored in a session, if the application |
235 | is using the L<Catalyst::Plugin::Session> module. |
06675d2e |
236 | |
4bb9b01c |
237 | =head1 INTRODUCTION |
238 | |
239 | =head2 The Authentication/Authorization Process |
240 | |
241 | Web applications typically need to identify a user - to tell the user apart |
242 | from other users. This is usually done in order to display private information |
243 | that is only that user's business, or to limit access to the application so |
244 | that only certain entities can access certain parts. |
245 | |
246 | This process is split up into several steps. First you ask the user to identify |
247 | themselves. At this point you can't be sure that the user is really who they |
248 | claim to be. |
249 | |
250 | Then the user tells you who they are, and backs this claim with some peice of |
251 | information that only the real user could give you. For example, a password is |
252 | a secret that is known to both the user and you. When the user tells you this |
253 | password you can assume they're in on the secret and can be trusted (ignore |
254 | identity theft for now). Checking the password, or any other proof is called |
255 | B<credential verification>. |
256 | |
257 | By this time you know exactly who the user is - the user's identity is |
258 | B<authenticated>. This is where this module's job stops, and other plugins step |
259 | in. The next logical step is B<authorization>, the process of deciding what a |
260 | user is (or isn't) allowed to do. For example, say your users are split into |
261 | two main groups - regular users and administrators. You should verify that the |
262 | currently logged in user is indeed an administrator before performing the |
263 | actions of an administrative part of your apploication. One way to do this is |
264 | with role based access control. |
265 | |
266 | =head2 The Components In This Framework |
267 | |
268 | =head3 Credential Verifiers |
269 | |
270 | When user input is transferred to the L<Catalyst> application (typically via |
271 | form inputs) this data then enters the authentication framework through these |
272 | plugins. |
273 | |
274 | These plugins check the data, and ensure that it really proves the user is who |
275 | they claim to be. |
276 | |
277 | =head3 Storage Backends |
278 | |
279 | The credentials also identify a user, and this family of modules is supposed to |
280 | take this identification data and return a standardized object oriented |
281 | representation of users. |
282 | |
283 | When a user is retrieved from a store it is not necessarily authenticated. |
284 | Credential verifiers can either accept a user object, or fetch the object |
285 | themselves from the default store. |
286 | |
287 | =head3 The Core Plugin |
288 | |
289 | This plugin on it's own is the glue, providing store registration, session |
290 | integration, and other goodness for the other plugins. |
291 | |
292 | =head3 Other Plugins |
293 | |
294 | More layers of plugins can be stacked on top of the authentication code. For |
295 | example, L<Catalyst::Plugin::Session::PerUser> provides an abstraction of |
296 | browser sessions that is more persistent per users. |
297 | L<Catalyst::Plugin::Authorization::Roles> provides an accepted way to separate |
298 | and group users into categories, and then check which categories the current |
299 | user belongs to. |
300 | |
06675d2e |
301 | =head1 METHODS |
302 | |
303 | =over 4 |
304 | |
06675d2e |
305 | =item user |
306 | |
189b5b0c |
307 | Returns the currently logged in user or undef if there is none. |
06675d2e |
308 | |
ce0b058d |
309 | =item user_exists |
310 | |
311 | Whether or not a user is logged in right now. |
312 | |
8bcb3a4b |
313 | The reason this method exists is that C<< $c->user >> may needlessly load the |
ce0b058d |
314 | user from the auth store. |
315 | |
316 | If you're just going to say |
317 | |
318 | if ( $c->user_user ) { |
319 | # foo |
320 | } else { |
321 | $c->forward("login"); |
322 | } |
323 | |
8bcb3a4b |
324 | it should be more efficient than C<< $c->user >> when a user is marked in the session |
1e055395 |
325 | but C<< $c->user >> hasn't been called yet. |
ce0b058d |
326 | |
4402d92d |
327 | =item logout |
328 | |
329 | Delete the currently logged in user from C<user> and the session. |
330 | |
7d0922d8 |
331 | =item get_user $uid |
332 | |
189b5b0c |
333 | Fetch a particular users details, defined by the given ID, via the default store. |
334 | |
335 | =back |
336 | |
337 | =head1 CONFIGURATION |
338 | |
339 | =over 4 |
340 | |
341 | =item use_session |
342 | |
343 | Whether or not to store the user's logged in state in the session, if the |
e7522758 |
344 | application is also using the L<Catalyst::Plugin::Session> plugin. This |
345 | value is set to true per default. |
346 | |
347 | =item store |
348 | |
1e055395 |
349 | If multiple stores are being used, set the module you want as default here. |
7d0922d8 |
350 | |
1e055395 |
351 | =item stores |
352 | |
353 | If multiple stores are being used, you need to provide a name for each store |
354 | here, as a hash, the keys are the names you wish to use, and the values are |
355 | the the names of the plugins. |
356 | |
357 | # example |
358 | __PACKAGE__->config( authentication => { |
359 | store => 'Catalyst::Plugin::Authentication::Store::HtPasswd', |
360 | stores => { |
361 | 'dbic' => 'Catalyst::Plugin::Authentication::Store::DBIC' |
362 | } |
363 | }); |
364 | |
2bcde605 |
365 | =back |
1e055395 |
366 | |
4fbe2e14 |
367 | =head1 METHODS FOR STORE MANAGEMENT |
368 | |
fe4cf44a |
369 | =over 4 |
370 | |
7d0922d8 |
371 | =item default_auth_store |
372 | |
4fbe2e14 |
373 | Return the store whose name is 'default'. |
7d0922d8 |
374 | |
189b5b0c |
375 | This is set to C<< $c->config->{authentication}{store} >> if that value exists, |
4fbe2e14 |
376 | or by using a Store plugin: |
377 | |
378 | use Catalyst qw/Authentication Authentication::Store::Minimal/; |
379 | |
380 | Sets the default store to |
381 | L<Catalyst::Plugin::Authentication::Store::Minimal::Backend>. |
382 | |
a1e5bd36 |
383 | |
4fbe2e14 |
384 | =item get_auth_store $name |
385 | |
386 | Return the store whose name is $name. |
387 | |
388 | =item get_auth_store_name $store |
389 | |
390 | Return the name of the store $store. |
391 | |
392 | =item auth_stores |
393 | |
394 | A hash keyed by name, with the stores registered in the app. |
395 | |
396 | =item auth_store_names |
397 | |
398 | A ref-hash keyed by store, which contains the names of the stores. |
399 | |
400 | =item register_auth_stores %stores_by_name |
401 | |
402 | Register stores into the application. |
06675d2e |
403 | |
fe4cf44a |
404 | =back |
405 | |
06675d2e |
406 | =head1 INTERNAL METHODS |
407 | |
408 | =over 4 |
409 | |
410 | =item set_authenticated $user |
411 | |
412 | Marks a user as authenticated. Should be called from a |
413 | C<Catalyst::Plugin::Authentication::Credential> plugin after successful |
414 | authentication. |
415 | |
416 | This involves setting C<user> and the internal data in C<session> if |
417 | L<Catalyst::Plugin::Session> is loaded. |
418 | |
e300c5b6 |
419 | =item auth_restore_user $user |
420 | |
421 | Used to restore a user from the session, by C<user> only when it's actually |
422 | needed. |
423 | |
424 | =item save_user_in_session $user |
425 | |
426 | Used to save the user in a session. |
427 | |
06675d2e |
428 | =item prepare |
429 | |
430 | Revives a user from the session object if there is one. |
431 | |
432 | =item setup |
433 | |
434 | Sets the default configuration parameters. |
435 | |
436 | =item |
437 | |
438 | =back |
439 | |
fbe577ac |
440 | =head1 SEE ALSO |
441 | |
4bb9b01c |
442 | This list might not be up to date. |
443 | |
444 | =head2 User Storage Backends |
445 | |
fbe577ac |
446 | L<Catalyst::Plugin::Authentication::Store::Minimal>, |
4bb9b01c |
447 | L<Catalyst::Plugin::Authentication::Store::Htpasswd>, |
448 | L<Catalyst::Plugin::Authentication::Store::DBIC> (also works with Class::DBI). |
449 | |
450 | =head2 Credential verification |
451 | |
452 | L<Catalyst::Plugin::Authentication::Credential::Password>, |
453 | L<Catalyst::Plugin::Authentication::Credential::HTTP>, |
454 | L<Catalyst::Plugin::Authentication::Credential::TypeKey> |
455 | |
456 | =head2 Authorization |
457 | |
fbe577ac |
458 | L<Catalyst::Plugin::Authorization::ACL>, |
4bb9b01c |
459 | L<Catalyst::Plugin::Authorization::Roles> |
460 | |
461 | =head2 Misc |
462 | |
463 | L<Catalyst::Plugin::Session>, |
464 | L<Catalyst::Plugin::Session::PerUser> |
fbe577ac |
465 | |
93f08fb0 |
466 | =head1 DON'T SEE ALSO |
467 | |
468 | This module along with it's sub plugins deprecate a great number of other |
469 | modules. These include Catalyst::Plugin::Authentication::Simple, |
470 | Catalyst::Plugin::Authentication::CDBI. |
471 | |
472 | At the time of writing these plugins have not yet been replaced or updated, but |
473 | should be eventually: Catalyst::Plugin::Authentication::OpenID, |
474 | Catalyst::Plugin::Authentication::LDAP, |
475 | Catalyst::Plugin::Authentication::CDBI::Basic, |
476 | Catalyst::Plugin::Authentication::Basic::Remote |
477 | |
2bcde605 |
478 | =head1 AUTHORS |
fbe577ac |
479 | |
480 | Yuval Kogman, C<nothingmuch@woobling.org> |
2bcde605 |
481 | |
7d2f34eb |
482 | Jess Robinson |
2bcde605 |
483 | |
7d2f34eb |
484 | David Kamholz |
06675d2e |
485 | |
ff46c00b |
486 | =head1 COPYRIGHT & LICENSE |
fbe577ac |
487 | |
488 | Copyright (c) 2005 the aforementioned authors. All rights |
489 | reserved. This program is free software; you can redistribute |
490 | it and/or modify it under the same terms as Perl itself. |
491 | |
492 | =cut |
06675d2e |
493 | |