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