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