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