Skip Build.PL
[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 {
9deccb83 53 my $frozen = $c->_user_in_session;
56e23e7a 54 return $c->auth_restore_user($frozen);
e300c5b6 55 }
7bb06c91 56}
57
ce0b058d 58sub user_exists {
59 my $c = shift;
9deccb83 60 return defined($c->_user) || defined($c->_user_in_session);
56e23e7a 61}
62
63sub _user_in_session {
64 my $c = shift;
65
c97ddac2 66 if ( $c->isa("Catalyst::Plugin::Session") and $c->session_is_valid ) {
67 return $c->session->{__user};
56e23e7a 68 }
69
70 return;
ce0b058d 71}
72
12dae309 73sub save_user_in_session {
e300c5b6 74 my ( $c, $user ) = @_;
12dae309 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
06675d2e 81sub logout {
82 my $c = shift;
83
84 $c->user(undef);
b003080b 85
86 if ( $c->isa("Catalyst::Plugin::Session")
87 and $c->config->{authentication}{use_session} )
88 {
96777f3a 89 delete @{ $c->session }{qw/__user __user_store/};
b003080b 90 }
351e2a82 91
92 $c->NEXT::logout(@_);
06675d2e 93}
94
7d0922d8 95sub get_user {
2f7d8b59 96 my ( $c, $uid, @rest ) = @_;
7d0922d8 97
98 if ( my $store = $c->default_auth_store ) {
2f7d8b59 99 return $store->get_user( $uid, @rest );
7d0922d8 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
7bb06c91 108sub auth_restore_user {
e300c5b6 109 my ( $c, $frozen_user, $store_name ) = @_;
7bb06c91 110
4fbe2e14 111 return
cde43a59 112 unless $c->isa("Catalyst::Plugin::Session")
4fbe2e14 113 and $c->config->{authentication}{use_session}
114 and $c->sessionid;
4402d92d 115
e300c5b6 116 $store_name ||= $c->session->{__user_store};
117 $frozen_user ||= $c->session->{__user};
7bb06c91 118
e300c5b6 119 my $store = $c->get_auth_store($store_name);
120 $c->_user( my $user = $store->from_session( $c, $frozen_user ) );
7bb06c91 121
e300c5b6 122 return $user;
7bb06c91 123
124}
125
06675d2e 126sub setup {
127 my $c = shift;
128
712a35bf 129 my $cfg = $c->config->{authentication} || {};
06675d2e 130
131 %$cfg = (
132 use_session => 1,
133 %$cfg,
134 );
b003080b 135
12dae309 136 $c->register_auth_stores(
137 default => $cfg->{store},
138 %{ $cfg->{stores} || {} },
139 );
96777f3a 140
b003080b 141 $c->NEXT::setup(@_);
06675d2e 142}
143
96777f3a 144sub get_auth_store {
12dae309 145 my ( $self, $name ) = @_;
146 $self->auth_stores->{$name} || ( Class::Inspector->loaded($name) && $name );
96777f3a 147}
148
149sub get_auth_store_name {
12dae309 150 my ( $self, $store ) = @_;
151 $self->auth_store_names->{$store};
96777f3a 152}
153
154sub register_auth_stores {
12dae309 155 my ( $self, %new ) = @_;
96777f3a 156
12dae309 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 }
96777f3a 162}
163
164sub auth_stores {
12dae309 165 my $self = shift;
166 $self->_auth_stores(@_) || $self->_auth_stores( {} );
96777f3a 167}
168
169sub auth_store_names {
12dae309 170 my $self = shift;
96777f3a 171
4402d92d 172 $self->_auth_store_names || do {
12dae309 173 tie my %hash, 'Tie::RefHash';
174 $self->_auth_store_names( \%hash );
4fbe2e14 175 }
96777f3a 176}
177
178sub default_auth_store {
12dae309 179 my $self = shift;
96777f3a 180
12dae309 181 if ( my $new = shift ) {
182 $self->register_auth_stores( default => $new );
183 }
96777f3a 184
12dae309 185 $self->get_auth_store("default");
96777f3a 186}
187
06675d2e 188__PACKAGE__;
189
190__END__
191
192=pod
193
194=head1 NAME
195
55395841 196Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst
197authentication framework.
06675d2e 198
199=head1 SYNOPSIS
200
189b5b0c 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;
06675d2e 212
213=head1 DESCRIPTION
214
e7522758 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).
06675d2e 218
e7522758 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.
189b5b0c 224
6a36933d 225To implement authentication in a Catalyst application you need to add this
e7522758 226module, plus at least one store and one credential module.
189b5b0c 227
e7522758 228Authentication data can also be stored in a session, if the application
229is using the L<Catalyst::Plugin::Session> module.
06675d2e 230
4bb9b01c 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
6a36933d 244Then the user tells you who they are, and backs this claim with some piece of
4bb9b01c 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
6a36933d 257actions of an administrative part of your application. One way to do this is
4bb9b01c 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
6a36933d 283This plugin on its own is the glue, providing store registration, session
4bb9b01c 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
5e91c057 295=head1 EXAMPLE
296
6a36933d 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.
5e91c057 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
b840d11c 354Since the C<login> method knows how to find logically named parameters on its
5e91c057 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
a93f1197 394 $c->detach("unauthorized") unless $c->check_roles("admin");
5e91c057 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
06675d2e 442=head1 METHODS
443
444=over 4
445
06675d2e 446=item user
447
189b5b0c 448Returns the currently logged in user or undef if there is none.
06675d2e 449
ce0b058d 450=item user_exists
451
452Whether or not a user is logged in right now.
453
8bcb3a4b 454The reason this method exists is that C<< $c->user >> may needlessly load the
ce0b058d 455user from the auth store.
456
457If you're just going to say
458
4359cfe3 459 if ( $c->user_exists ) {
ce0b058d 460 # foo
461 } else {
462 $c->forward("login");
463 }
464
4359cfe3 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.
ce0b058d 467
4402d92d 468=item logout
469
470Delete the currently logged in user from C<user> and the session.
471
7d0922d8 472=item get_user $uid
473
189b5b0c 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
e7522758 485application is also using the L<Catalyst::Plugin::Session> plugin. This
486value is set to true per default.
487
488=item store
489
1e055395 490If multiple stores are being used, set the module you want as default here.
7d0922d8 491
1e055395 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
2bcde605 506=back
1e055395 507
4fbe2e14 508=head1 METHODS FOR STORE MANAGEMENT
509
fe4cf44a 510=over 4
511
7d0922d8 512=item default_auth_store
513
4fbe2e14 514Return the store whose name is 'default'.
7d0922d8 515
189b5b0c 516This is set to C<< $c->config->{authentication}{store} >> if that value exists,
4fbe2e14 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
a1e5bd36 524
4fbe2e14 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.
06675d2e 544
fe4cf44a 545=back
546
06675d2e 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
e300c5b6 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
06675d2e 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
fbe577ac 581=head1 SEE ALSO
582
4bb9b01c 583This list might not be up to date.
584
585=head2 User Storage Backends
586
fbe577ac 587L<Catalyst::Plugin::Authentication::Store::Minimal>,
4bb9b01c 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
fbe577ac 599L<Catalyst::Plugin::Authorization::ACL>,
4bb9b01c 600L<Catalyst::Plugin::Authorization::Roles>
601
5e91c057 602=head2 Internals Documentation
603
604L<Catalyst::Plugin::Authentication::Store>
605
4bb9b01c 606=head2 Misc
607
608L<Catalyst::Plugin::Session>,
609L<Catalyst::Plugin::Session::PerUser>
fbe577ac 610
93f08fb0 611=head1 DON'T SEE ALSO
612
1a05e6ed 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>.
93f08fb0 616
617At the time of writing these plugins have not yet been replaced or updated, but
1a05e6ed 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>.
93f08fb0 622
2bcde605 623=head1 AUTHORS
fbe577ac 624
625Yuval Kogman, C<nothingmuch@woobling.org>
2bcde605 626
7d2f34eb 627Jess Robinson
2bcde605 628
7d2f34eb 629David Kamholz
06675d2e 630
ff46c00b 631=head1 COPYRIGHT & LICENSE
fbe577ac 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
06675d2e 638