refactoring + claco's bug fix
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Plugin / Authentication.pm
CommitLineData
07e49bc7 1#!/usr/bin/perl
2
3package Catalyst::Plugin::Authentication;
4
dde93f12 5use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
07e49bc7 6
dde93f12 7BEGIN {
2e3b369c 8 __PACKAGE__->mk_accessors(qw/_user/);
8b52f75e 9 __PACKAGE__->mk_classdata($_) for qw/_auth_stores _auth_store_names/;
dde93f12 10}
07e49bc7 11
12use strict;
13use warnings;
14
8b52f75e 15use Tie::RefHash;
f2fee7ad 16use Class::Inspector;
8b52f75e 17
7605d422 18# this optimization breaks under Template::Toolkit
19# use user_exists instead
c8c961f5 20#BEGIN {
21# require constant;
22# constant->import(have_want => eval { require Want });
23#}
816e5745 24
9583def5 25our $VERSION = "0.09";
49bd39d0 26
07e49bc7 27sub set_authenticated {
28 my ( $c, $user ) = @_;
29
30 $c->user($user);
f0348b1d 31 $c->request->{user} = $user; # compatibility kludge
07e49bc7 32
04c8c049 33 if ( $c->_should_save_user_in_session($user) ) {
f2fee7ad 34 $c->save_user_in_session($user);
07e49bc7 35 }
033d2c24 36
b260654c 37 $c->NEXT::set_authenticated($user);
07e49bc7 38}
39
04c8c049 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
2e3b369c 61sub user {
f0348b1d 62 my $c = shift;
2e3b369c 63
f0348b1d 64 if (@_) {
65 return $c->_user(@_);
66 }
2e3b369c 67
58009177 68 if ( defined(my $user = $c->_user) ) {
69 return $user;
70 } else {
d0d5fbef 71 return $c->auth_restore_user;
f0348b1d 72 }
2e3b369c 73}
74
1a2be169 75sub user_exists {
76 my $c = shift;
30d5659d 77 return defined($c->_user) || defined($c->_user_in_session);
58009177 78}
79
f2fee7ad 80sub save_user_in_session {
f0348b1d 81 my ( $c, $user ) = @_;
f2fee7ad 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
07e49bc7 88sub logout {
89 my $c = shift;
90
91 $c->user(undef);
dde93f12 92
04c8c049 93 if ( $c->_should_load_user_from_session ) {
94 $c->_delete_user_from_session();
dde93f12 95 }
49df63da 96
97 $c->NEXT::logout(@_);
07e49bc7 98}
99
04c8c049 100sub _delete_user_from_session {
101 my $c = shift;
102 delete @{ $c->session }{qw/__user __user_store/};
103}
104
5435c348 105sub get_user {
56716182 106 my ( $c, $uid, @rest ) = @_;
5435c348 107
108 if ( my $store = $c->default_auth_store ) {
56716182 109 return $store->get_user( $uid, @rest );
5435c348 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
d0d5fbef 118sub _user_in_session {
119 my $c = shift;
120
04c8c049 121 return unless $c->_should_load_user_from_session;
d0d5fbef 122
123 return $c->session->{__user};
04c8c049 124}
d0d5fbef 125
04c8c049 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};
d0d5fbef 132}
133
2e3b369c 134sub auth_restore_user {
f0348b1d 135 my ( $c, $frozen_user, $store_name ) = @_;
2e3b369c 136
d0d5fbef 137 $frozen_user ||= $c->_user_in_session;
138 return unless defined($frozen_user);
b7a0db6d 139
04c8c049 140 $store_name ||= $c->_store_in_session;
9583def5 141 return unless $store_name; # FIXME die unless? This is an internal inconsistency
2e3b369c 142
f0348b1d 143 my $store = $c->get_auth_store($store_name);
04c8c049 144
f0348b1d 145 $c->_user( my $user = $store->from_session( $c, $frozen_user ) );
2e3b369c 146
f0348b1d 147 return $user;
2e3b369c 148
149}
150
07e49bc7 151sub setup {
152 my $c = shift;
153
04c8c049 154 my $cfg = $c->config->{authentication} ||= {};
07e49bc7 155
156 %$cfg = (
157 use_session => 1,
158 %$cfg,
159 );
dde93f12 160
f2fee7ad 161 $c->register_auth_stores(
162 default => $cfg->{store},
163 %{ $cfg->{stores} || {} },
164 );
8b52f75e 165
dde93f12 166 $c->NEXT::setup(@_);
07e49bc7 167}
168
8b52f75e 169sub get_auth_store {
f2fee7ad 170 my ( $self, $name ) = @_;
171 $self->auth_stores->{$name} || ( Class::Inspector->loaded($name) && $name );
8b52f75e 172}
173
174sub get_auth_store_name {
f2fee7ad 175 my ( $self, $store ) = @_;
176 $self->auth_store_names->{$store};
8b52f75e 177}
178
179sub register_auth_stores {
f2fee7ad 180 my ( $self, %new ) = @_;
8b52f75e 181
f2fee7ad 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 }
8b52f75e 187}
188
189sub auth_stores {
f2fee7ad 190 my $self = shift;
191 $self->_auth_stores(@_) || $self->_auth_stores( {} );
8b52f75e 192}
193
194sub auth_store_names {
f2fee7ad 195 my $self = shift;
8b52f75e 196
b7a0db6d 197 $self->_auth_store_names || do {
f2fee7ad 198 tie my %hash, 'Tie::RefHash';
199 $self->_auth_store_names( \%hash );
b260654c 200 }
8b52f75e 201}
202
203sub default_auth_store {
f2fee7ad 204 my $self = shift;
8b52f75e 205
f2fee7ad 206 if ( my $new = shift ) {
207 $self->register_auth_stores( default => $new );
208 }
8b52f75e 209
f2fee7ad 210 $self->get_auth_store("default");
8b52f75e 211}
212
07e49bc7 213__PACKAGE__;
214
215__END__
216
217=pod
218
219=head1 NAME
220
033d2c24 221Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst
222authentication framework.
07e49bc7 223
224=head1 SYNOPSIS
225
18a3c897 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;
07e49bc7 237
238=head1 DESCRIPTION
239
14929a35 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).
07e49bc7 243
14929a35 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.
18a3c897 249
f0f9cd72 250To implement authentication in a Catalyst application you need to add this
14929a35 251module, plus at least one store and one credential module.
18a3c897 252
14929a35 253Authentication data can also be stored in a session, if the application
254is using the L<Catalyst::Plugin::Session> module.
07e49bc7 255
e5108df9 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
f0f9cd72 269Then the user tells you who they are, and backs this claim with some piece of
e5108df9 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
f0f9cd72 282actions of an administrative part of your application. One way to do this is
e5108df9 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
f0f9cd72 308This plugin on its own is the glue, providing store registration, session
e5108df9 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
caae740f 320=head1 EXAMPLE
321
f0f9cd72 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.
caae740f 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
0a630893 379Since the C<login> method knows how to find logically named parameters on its
caae740f 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
e96086b6 419 $c->detach("unauthorized") unless $c->check_roles("admin");
caae740f 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
07e49bc7 467=head1 METHODS
468
469=over 4
470
07e49bc7 471=item user
472
18a3c897 473Returns the currently logged in user or undef if there is none.
07e49bc7 474
1a2be169 475=item user_exists
476
477Whether or not a user is logged in right now.
478
4bc70618 479The reason this method exists is that C<< $c->user >> may needlessly load the
1a2be169 480user from the auth store.
481
482If you're just going to say
483
8f8b56b6 484 if ( $c->user_exists ) {
1a2be169 485 # foo
486 } else {
487 $c->forward("login");
488 }
489
8f8b56b6 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.
1a2be169 492
b7a0db6d 493=item logout
494
495Delete the currently logged in user from C<user> and the session.
496
5435c348 497=item get_user $uid
498
18a3c897 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
14929a35 510application is also using the L<Catalyst::Plugin::Session> plugin. This
511value is set to true per default.
512
513=item store
514
d61933f6 515If multiple stores are being used, set the module you want as default here.
5435c348 516
d61933f6 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
51111c81 531=back
d61933f6 532
b260654c 533=head1 METHODS FOR STORE MANAGEMENT
534
b12e226d 535=over 4
536
5435c348 537=item default_auth_store
538
b260654c 539Return the store whose name is 'default'.
5435c348 540
18a3c897 541This is set to C<< $c->config->{authentication}{store} >> if that value exists,
b260654c 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
816e5745 549
b260654c 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.
07e49bc7 569
b12e226d 570=back
571
07e49bc7 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
f0348b1d 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
07e49bc7 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
36fba990 606=head1 SEE ALSO
607
e5108df9 608This list might not be up to date.
609
610=head2 User Storage Backends
611
36fba990 612L<Catalyst::Plugin::Authentication::Store::Minimal>,
e5108df9 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
36fba990 624L<Catalyst::Plugin::Authorization::ACL>,
e5108df9 625L<Catalyst::Plugin::Authorization::Roles>
626
caae740f 627=head2 Internals Documentation
628
629L<Catalyst::Plugin::Authentication::Store>
630
e5108df9 631=head2 Misc
632
633L<Catalyst::Plugin::Session>,
634L<Catalyst::Plugin::Session::PerUser>
36fba990 635
d304b38a 636=head1 DON'T SEE ALSO
637
1db33018 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>.
d304b38a 641
642At the time of writing these plugins have not yet been replaced or updated, but
1db33018 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>.
d304b38a 647
51111c81 648=head1 AUTHORS
36fba990 649
650Yuval Kogman, C<nothingmuch@woobling.org>
51111c81 651
7d4c2ed8 652Jess Robinson
51111c81 653
7d4c2ed8 654David Kamholz
07e49bc7 655
8f86f029 656=head1 COPYRIGHT & LICENSE
36fba990 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
07e49bc7 663