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