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