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