Changelog, aesthetic cleanup
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Plugin / Authentication.pm
1 #!/usr/bin/perl
2
3 package Catalyst::Plugin::Authentication;
4
5 use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
6
7 BEGIN {
8     __PACKAGE__->mk_accessors(qw/_user/);
9     __PACKAGE__->mk_classdata($_) for qw/_auth_stores _auth_store_names/;
10 }
11
12 use strict;
13 use warnings;
14
15 use Tie::RefHash;
16 use Class::Inspector;
17
18 # this optimization breaks under Template::Toolkit
19 # use user_exists instead
20 #BEGIN {
21 #       require constant;
22 #       constant->import(have_want => eval { require Want });
23 #}
24
25 our $VERSION = "0.08";
26
27 sub set_authenticated {
28     my ( $c, $user ) = @_;
29
30     $c->user($user);
31     $c->request->{user} = $user;    # compatibility kludge
32
33     if (    $c->isa("Catalyst::Plugin::Session")
34         and $c->config->{authentication}{use_session}
35         and $user->supports("session") )
36     {
37         $c->save_user_in_session($user);
38     }
39
40     $c->NEXT::set_authenticated($user);
41 }
42
43 sub user {
44     my $c = shift;
45
46     if (@_) {
47         return $c->_user(@_);
48     }
49
50     if ( defined(my $user = $c->_user) ) {
51         return $user;
52     } else {
53         my $frozen = $c->_user_in_session;
54         return $c->auth_restore_user($frozen);
55     }
56 }
57
58 sub user_exists {
59         my $c = shift;
60         return defined($c->_user) || defined($c->_user_in_session);
61 }
62
63 sub _user_in_session {
64     my $c = shift;
65
66     if ( $c->isa("Catalyst::Plugin::Session") and $c->session_is_valid ) {
67         return $c->session->{__user};
68     }
69
70     return;
71 }
72
73 sub save_user_in_session {
74     my ( $c, $user ) = @_;
75
76     my $store = $user->store || ref $user;
77     $c->session->{__user_store} = $c->get_auth_store_name($store) || $store;
78     $c->session->{__user} = $user->for_session;
79 }
80
81 sub logout {
82     my $c = shift;
83
84     $c->user(undef);
85
86     if (    $c->isa("Catalyst::Plugin::Session")
87         and $c->config->{authentication}{use_session} )
88     {
89         delete @{ $c->session }{qw/__user __user_store/};
90     }
91     
92     $c->NEXT::logout(@_);
93 }
94
95 sub get_user {
96     my ( $c, $uid, @rest ) = @_;
97
98     if ( my $store = $c->default_auth_store ) {
99         return $store->get_user( $uid, @rest );
100     }
101     else {
102         Catalyst::Exception->throw(
103                 "The user id $uid was passed to an authentication "
104               . "plugin, but no default store was specified" );
105     }
106 }
107
108 sub auth_restore_user {
109     my ( $c, $frozen_user, $store_name ) = @_;
110
111     return
112       unless $c->isa("Catalyst::Plugin::Session")
113       and $c->config->{authentication}{use_session}
114       and $c->sessionid;
115
116     $store_name  ||= $c->session->{__user_store};
117     $frozen_user ||= $c->session->{__user};
118
119     my $store = $c->get_auth_store($store_name);
120     $c->_user( my $user = $store->from_session( $c, $frozen_user ) );
121
122     return $user;
123
124 }
125
126 sub setup {
127     my $c = shift;
128
129     my $cfg = $c->config->{authentication} || {};
130
131     %$cfg = (
132         use_session => 1,
133         %$cfg,
134     );
135
136     $c->register_auth_stores(
137         default => $cfg->{store},
138         %{ $cfg->{stores} || {} },
139     );
140
141     $c->NEXT::setup(@_);
142 }
143
144 sub get_auth_store {
145     my ( $self, $name ) = @_;
146     $self->auth_stores->{$name} || ( Class::Inspector->loaded($name) && $name );
147 }
148
149 sub get_auth_store_name {
150     my ( $self, $store ) = @_;
151     $self->auth_store_names->{$store};
152 }
153
154 sub register_auth_stores {
155     my ( $self, %new ) = @_;
156
157     foreach my $name ( keys %new ) {
158         my $store = $new{$name} or next;
159         $self->auth_stores->{$name}       = $store;
160         $self->auth_store_names->{$store} = $name;
161     }
162 }
163
164 sub auth_stores {
165     my $self = shift;
166     $self->_auth_stores(@_) || $self->_auth_stores( {} );
167 }
168
169 sub auth_store_names {
170     my $self = shift;
171
172     $self->_auth_store_names || do {
173         tie my %hash, 'Tie::RefHash';
174         $self->_auth_store_names( \%hash );
175       }
176 }
177
178 sub default_auth_store {
179     my $self = shift;
180
181     if ( my $new = shift ) {
182         $self->register_auth_stores( default => $new );
183     }
184
185     $self->get_auth_store("default");
186 }
187
188 __PACKAGE__;
189
190 __END__
191
192 =pod
193
194 =head1 NAME
195
196 Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst
197 authentication framework.
198
199 =head1 SYNOPSIS
200
201     use Catalyst qw/
202         Authentication
203         Authentication::Store::Foo
204         Authentication::Credential::Password
205     /;
206
207     # later on ...
208     # ->login is provided by the Credential::Password module
209     $c->login('myusername', 'mypassword');
210     my $age = $c->user->age;
211     $c->logout;
212
213 =head1 DESCRIPTION
214
215 The authentication plugin provides generic user support. It is the basis 
216 for both authentication (checking the user is who they claim to be), and 
217 authorization (allowing the user to do what the system authorises them to do).
218
219 Using authentication is split into two parts. A Store is used to actually 
220 store the user information, and can store any amount of data related to 
221 the user. Multiple stores can be accessed from within one application. 
222 Credentials are used to verify users, using the store, given data from 
223 the frontend.
224
225 To implement authentication in a Catalyst application you need to add this 
226 module, plus at least one store and one credential module.
227
228 Authentication data can also be stored in a session, if the application 
229 is using the L<Catalyst::Plugin::Session> module.
230
231 =head1 INTRODUCTION
232
233 =head2 The Authentication/Authorization Process
234
235 Web applications typically need to identify a user - to tell the user apart
236 from other users. This is usually done in order to display private information
237 that is only that user's business, or to limit access to the application so
238 that only certain entities can access certain parts.
239
240 This process is split up into several steps. First you ask the user to identify
241 themselves. At this point you can't be sure that the user is really who they
242 claim to be.
243
244 Then the user tells you who they are, and backs this claim with some piece of
245 information that only the real user could give you. For example, a password is
246 a secret that is known to both the user and you. When the user tells you this
247 password you can assume they're in on the secret and can be trusted (ignore
248 identity theft for now). Checking the password, or any other proof is called
249 B<credential verification>.
250
251 By this time you know exactly who the user is - the user's identity is
252 B<authenticated>. This is where this module's job stops, and other plugins step
253 in. The next logical step is B<authorization>, the process of deciding what a
254 user is (or isn't) allowed to do. For example, say your users are split into
255 two main groups - regular users and administrators. You should verify that the
256 currently logged in user is indeed an administrator before performing the
257 actions of an administrative part of your application. One way to do this is
258 with role based access control.
259
260 =head2 The Components In This Framework
261
262 =head3 Credential Verifiers
263
264 When user input is transferred to the L<Catalyst> application (typically via
265 form inputs) this data then enters the authentication framework through these
266 plugins.
267
268 These plugins check the data, and ensure that it really proves the user is who
269 they claim to be.
270
271 =head3 Storage Backends
272
273 The credentials also identify a user, and this family of modules is supposed to
274 take this identification data and return a standardized object oriented
275 representation of users.
276
277 When a user is retrieved from a store it is not necessarily authenticated.
278 Credential verifiers can either accept a user object, or fetch the object
279 themselves from the default store.
280
281 =head3 The Core Plugin
282
283 This plugin on its own is the glue, providing store registration, session
284 integration, and other goodness for the other plugins.
285
286 =head3 Other Plugins
287
288 More layers of plugins can be stacked on top of the authentication code. For
289 example, L<Catalyst::Plugin::Session::PerUser> provides an abstraction of
290 browser sessions that is more persistent per users.
291 L<Catalyst::Plugin::Authorization::Roles> provides an accepted way to separate
292 and group users into categories, and then check which categories the current
293 user belongs to.
294
295 =head1 EXAMPLE
296
297 Let's say we were storing users in an Apache style htpasswd file. Users are
298 stored in that file, with a hashed password and some extra comments. Users are
299 verified by supplying a password which is matched with the file.
300
301 This means that our application will begin like this:
302
303     package MyApp;
304
305     use Catalyst qw/
306         Authentication
307         Authentication::Credential::Password
308         Authentication::Store::Htpasswd
309     /;
310
311     __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile";
312
313 This loads the appropriate methods and also sets the htpasswd store as the
314 default store.
315     
316 So, now that we have the code loaded we need to get data from the user into the
317 credential verifier.
318
319 Let's create an authentication controller:
320
321     package MyApp::Controller::Auth;
322
323     sub login : Local {
324         my ( $self, $c ) = @_;
325
326         if (    my $user = $c->req->param("user")
327             and my $password = $c->req->param("password") )
328         {
329             if ( $c->login( $user, $password ) ) {
330                 $c->res->body( "hello " . $c->user->name );
331             } else {
332                 # login incorrect
333             }
334         }
335         else {
336             # invalid form input
337         }
338     }
339
340 This code should be very readable. If all the necessary fields are supplied,
341 call the L<Authentication::Credential::Password/login> method on the
342 controller. If that succeeds the user is logged in.
343
344 It could be simplified though:
345
346     sub login : Local {
347         my ( $self, $c ) = @_;
348
349         if ( $c->login ) {
350             ...
351         }
352     }
353
354 Since the C<login> method knows how to find logically named parameters on its
355 own.
356
357 The credential verifier will ask the default store to get the user whose ID is
358 the user parameter. In this case the default store is the htpasswd one. Once it
359 fetches the user from the store the password is checked and if it's OK
360 C<< $c->user >> will contain the user object returned from the htpasswd store.
361
362 We can also pass a user object to the credential verifier manually, if we have
363 several stores per app. This is discussed in
364 L<Catalyst::Plugin::Authentication::Store>.
365
366 Now imagine each admin user has a comment set in the htpasswd file saying
367 "admin".
368
369 A restricted action might look like this:
370
371     sub restricted : Local {
372         my ( $self, $c ) = @_;
373
374         $c->detach("unauthorized")
375           unless $c->user_exists
376           and $c->user->extra_info() eq "admin";
377
378         # do something restricted here
379     }
380
381 This is somewhat similar to role based access control.
382 L<Catalyst::Plugin::Authentication::Store::Htpasswd> treats the extra info
383 field as a comma separated list of roles if it's treated that way. Let's
384 leverage this. Add the role authorization plugin:
385
386     use Catalyst qw/
387         ...
388         Authorization::Roles
389     /;
390
391     sub restricted : Local {
392         my ( $self, $c ) = @_;
393
394         $c->detach("unauthorized") unless $c->check_roles("admin");
395
396         # do something restricted here
397     }
398
399 This is somewhat simpler and will work if you change your store, too, since the
400 role interface is consistent.
401
402 Let's say your app grew, and you now have 10000 users. It's no longer efficient
403 to maintain an htpasswd file, so you move this data to a database.
404
405     use Catalyst qw/
406         Authentication
407         Authentication::Credential::Password
408         Authentication::Store::DBIC
409         Authorization::Roles
410     /;
411
412     __PACKAGE__->config->{authentication}{dbic} = ...; # see the DBIC store docs
413
414 The rest of your code should be unchanged. Now let's say you are integrating
415 typekey authentication to your system. For simplicity's sake we'll assume that
416 the user's are still keyed in the same way.
417
418     use Catalyst qw/
419         Authentication
420         Authentication::Credential::Password
421         Authentication::Credential::TypeKey
422         Authentication::Store::DBIC
423         Authorization::Roles
424     /;
425
426 And in your auth controller add a new action:
427
428     sub typekey : Local {
429         my ( $self, $c ) = @_;
430
431         if ( $c->authenticate_typekey) { # uses $c->req and Authen::TypeKey
432             # same stuff as the $c->login method
433             # ...
434         }
435     }
436
437 You've now added a new credential verification mechanizm orthogonally to the
438 other components. All you have to do is make sure that the credential verifiers
439 pass on the same types of parameters to the store in order to retrieve user
440 objects.
441
442 =head1 METHODS
443
444 =over 4 
445
446 =item user
447
448 Returns the currently logged in user or undef if there is none.
449
450 =item user_exists
451
452 Whether or not a user is logged in right now.
453
454 The reason this method exists is that C<< $c->user >> may needlessly load the
455 user from the auth store.
456
457 If you're just going to say
458
459         if ( $c->user_exists ) {
460                 # foo
461         } else {
462                 $c->forward("login");
463         }
464
465 it should be more efficient than C<< $c->user >> when a user is marked in the
466 session but C<< $c->user >> hasn't been called yet.
467
468 =item logout
469
470 Delete the currently logged in user from C<user> and the session.
471
472 =item get_user $uid
473
474 Fetch a particular users details, defined by the given ID, via the default store.
475
476 =back
477
478 =head1 CONFIGURATION
479
480 =over 4
481
482 =item use_session
483
484 Whether or not to store the user's logged in state in the session, if the
485 application is also using the L<Catalyst::Plugin::Session> plugin. This 
486 value is set to true per default.
487
488 =item store
489
490 If multiple stores are being used, set the module you want as default here.
491
492 =item stores
493
494 If multiple stores are being used, you need to provide a name for each store
495 here, as a hash, the keys are the names you wish to use, and the values are
496 the the names of the plugins.
497
498  # example
499  __PACKAGE__->config( authentication => {
500                         store => 'Catalyst::Plugin::Authentication::Store::HtPasswd',
501                         stores => { 
502                            'dbic' => 'Catalyst::Plugin::Authentication::Store::DBIC'
503                                   }
504                                          });
505
506 =back
507
508 =head1 METHODS FOR STORE MANAGEMENT
509
510 =over 4
511
512 =item default_auth_store
513
514 Return the store whose name is 'default'.
515
516 This is set to C<< $c->config->{authentication}{store} >> if that value exists,
517 or by using a Store plugin:
518
519         use Catalyst qw/Authentication Authentication::Store::Minimal/;
520
521 Sets the default store to
522 L<Catalyst::Plugin::Authentication::Store::Minimal::Backend>.
523
524
525 =item get_auth_store $name
526
527 Return the store whose name is $name.
528
529 =item get_auth_store_name $store
530
531 Return the name of the store $store.
532
533 =item auth_stores
534
535 A hash keyed by name, with the stores registered in the app.
536
537 =item auth_store_names
538
539 A ref-hash keyed by store, which contains the names of the stores.
540
541 =item register_auth_stores %stores_by_name
542
543 Register stores into the application.
544
545 =back
546
547 =head1 INTERNAL METHODS
548
549 =over 4
550
551 =item set_authenticated $user
552
553 Marks a user as authenticated. Should be called from a
554 C<Catalyst::Plugin::Authentication::Credential> plugin after successful
555 authentication.
556
557 This involves setting C<user> and the internal data in C<session> if
558 L<Catalyst::Plugin::Session> is loaded.
559
560 =item auth_restore_user $user
561
562 Used to restore a user from the session, by C<user> only when it's actually
563 needed.
564
565 =item save_user_in_session $user
566
567 Used to save the user in a session.
568
569 =item prepare
570
571 Revives a user from the session object if there is one.
572
573 =item setup
574
575 Sets the default configuration parameters.
576
577 =item 
578
579 =back
580
581 =head1 SEE ALSO
582
583 This list might not be up to date.
584
585 =head2 User Storage Backends
586
587 L<Catalyst::Plugin::Authentication::Store::Minimal>,
588 L<Catalyst::Plugin::Authentication::Store::Htpasswd>,
589 L<Catalyst::Plugin::Authentication::Store::DBIC> (also works with Class::DBI).
590
591 =head2 Credential verification
592
593 L<Catalyst::Plugin::Authentication::Credential::Password>,
594 L<Catalyst::Plugin::Authentication::Credential::HTTP>,
595 L<Catalyst::Plugin::Authentication::Credential::TypeKey>
596
597 =head2 Authorization
598
599 L<Catalyst::Plugin::Authorization::ACL>,
600 L<Catalyst::Plugin::Authorization::Roles>
601
602 =head2 Internals Documentation
603
604 L<Catalyst::Plugin::Authentication::Store>
605
606 =head2 Misc
607
608 L<Catalyst::Plugin::Session>,
609 L<Catalyst::Plugin::Session::PerUser>
610
611 =head1 DON'T SEE ALSO
612
613 This module along with its sub plugins deprecate a great number of other
614 modules. These include L<Catalyst::Plugin::Authentication::Simple>,
615 L<Catalyst::Plugin::Authentication::CDBI>.
616
617 At the time of writing these plugins have not yet been replaced or updated, but
618 should be eventually: L<Catalyst::Plugin::Authentication::OpenID>,
619 L<Catalyst::Plugin::Authentication::LDAP>,
620 L<Catalyst::Plugin::Authentication::CDBI::Basic>,
621 L<Catalyst::Plugin::Authentication::Basic::Remote>.
622
623 =head1 AUTHORS
624
625 Yuval Kogman, C<nothingmuch@woobling.org>
626
627 Jess Robinson
628
629 David Kamholz
630
631 =head1 COPYRIGHT & LICENSE
632
633         Copyright (c) 2005 the aforementioned authors. All rights
634         reserved. This program is free software; you can redistribute
635         it and/or modify it under the same terms as Perl itself.
636
637 =cut
638