X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FPlugin%2FAuthentication.pm;h=7b872afa613e18ead7ab1802e6499b004c0d0aae;hb=a3bf437a38fe00ccd875947ee09eb0e53ecc3396;hp=9a34f446c7346f5640bb456bf9d6624533cabe93;hpb=7bb06c917947fac855ed24e728a09ec63d9fd58c;p=catagits%2FCatalyst-Plugin-Authentication.git diff --git a/lib/Catalyst/Plugin/Authentication.pm b/lib/Catalyst/Plugin/Authentication.pm index 9a34f44..7b872af 100644 --- a/lib/Catalyst/Plugin/Authentication.pm +++ b/lib/Catalyst/Plugin/Authentication.pm @@ -15,12 +15,20 @@ use warnings; use Tie::RefHash; use Class::Inspector; -our $VERSION = "0.01"; +# this optimization breaks under Template::Toolkit +# use user_exists instead +#BEGIN { +# require constant; +# constant->import(have_want => eval { require Want }); +#} + +our $VERSION = "0.08"; sub set_authenticated { my ( $c, $user ) = @_; $c->user($user); + $c->request->{user} = $user; # compatibility kludge if ( $c->isa("Catalyst::Plugin::Session") and $c->config->{authentication}{use_session} @@ -28,26 +36,34 @@ sub set_authenticated { { $c->save_user_in_session($user); } + + $c->NEXT::set_authenticated($user); } sub user { - my $c = shift; + my $c = shift; - if ( @_ ) { - return $c->_user( @_ ); - } + if (@_) { + return $c->_user(@_); + } - my $user = $c->_user; + my $user = $c->_user; - if ( $user and !Scalar::Util::blessed( $user ) ) { - return $c->auth_restore_user( $user ); - } + if ( $user and !Scalar::Util::blessed($user) ) { +# return 1 if have_want() && Want::want("BOOL"); + return $c->auth_restore_user($user); + } + + return $user; +} - return $user; +sub user_exists { + my $c = shift; + return defined($c->_user); } sub save_user_in_session { - my ( $c, $user ) = @_; + my ( $c, $user ) = @_; my $store = $user->store || ref $user; $c->session->{__user_store} = $c->get_auth_store_name($store) || $store; @@ -64,13 +80,15 @@ sub logout { { delete @{ $c->session }{qw/__user __user_store/}; } + + $c->NEXT::logout(@_); } sub get_user { - my ( $c, $uid ) = @_; + my ( $c, $uid, @rest ) = @_; if ( my $store = $c->default_auth_store ) { - return $store->get_user($uid); + return $store->get_user( $uid, @rest ); } else { Catalyst::Exception->throw( @@ -82,12 +100,11 @@ sub get_user { sub prepare { my $c = shift->NEXT::prepare(@_); - if ( $c->isa("Catalyst::Plugin::Session") - and $c->default_auth_store + if ( $c->isa("Catalyst::Plugin::Session") and !$c->user ) { if ( $c->sessionid and my $frozen_user = $c->session->{__user} ) { - $c->_user( $frozen_user ); + $c->_user($frozen_user); } } @@ -95,16 +112,20 @@ sub prepare { } sub auth_restore_user { - my ( $c, $frozen_user, $store_name ) = @_; + my ( $c, $frozen_user, $store_name ) = @_; - $store_name ||= $c->session->{__user_store}; - $frozen_user ||= $c->session->{__user}; + return + unless $c->isa("Catalyst::Plugin::Session") + and $c->config->{authentication}{use_session} + and $c->sessionid; - my $store = $c->get_auth_store( $store_name ); - $c->_user( my $user = $store->from_session( $c, $frozen_user ) ); - $c->request->{user} = $user; # compatibility kludge + $store_name ||= $c->session->{__user_store}; + $frozen_user ||= $c->session->{__user}; - return $user; + my $store = $c->get_auth_store($store_name); + $c->_user( my $user = $store->from_session( $c, $frozen_user ) ); + + return $user; } @@ -154,12 +175,10 @@ sub auth_stores { sub auth_store_names { my $self = shift; - unless ( $self->_auth_store_names ) { + $self->_auth_store_names || do { tie my %hash, 'Tie::RefHash'; $self->_auth_store_names( \%hash ); - } - - $self->_auth_store_names; + } } sub default_auth_store { @@ -180,42 +199,354 @@ __END__ =head1 NAME -Catalyst::Plugin::Authentication - +Catalyst::Plugin::Authentication - Infrastructure plugin for the Catalyst +authentication framework. =head1 SYNOPSIS - use Catalyst qw/ - Authentication - Authentication::Store::Foo - Authentication::Credential::Password - /; + use Catalyst qw/ + Authentication + Authentication::Store::Foo + Authentication::Credential::Password + /; + + # later on ... + # ->login is provided by the Credential::Password module + $c->login('myusername', 'mypassword'); + my $age = $c->user->age; + $c->logout; =head1 DESCRIPTION -The authentication plugin is used by the various authentication and -authorization plugins in catalyst. +The authentication plugin provides generic user support. It is the basis +for both authentication (checking the user is who they claim to be), and +authorization (allowing the user to do what the system authorises them to do). + +Using authentication is split into two parts. A Store is used to actually +store the user information, and can store any amount of data related to +the user. Multiple stores can be accessed from within one application. +Credentials are used to verify users, using the store, given data from +the frontend. + +To implement authentication in a Catalyst application you need to add this +module, plus at least one store and one credential module. + +Authentication data can also be stored in a session, if the application +is using the L module. + +=head1 INTRODUCTION + +=head2 The Authentication/Authorization Process + +Web applications typically need to identify a user - to tell the user apart +from other users. This is usually done in order to display private information +that is only that user's business, or to limit access to the application so +that only certain entities can access certain parts. + +This process is split up into several steps. First you ask the user to identify +themselves. At this point you can't be sure that the user is really who they +claim to be. + +Then the user tells you who they are, and backs this claim with some piece of +information that only the real user could give you. For example, a password is +a secret that is known to both the user and you. When the user tells you this +password you can assume they're in on the secret and can be trusted (ignore +identity theft for now). Checking the password, or any other proof is called +B. + +By this time you know exactly who the user is - the user's identity is +B. This is where this module's job stops, and other plugins step +in. The next logical step is B, the process of deciding what a +user is (or isn't) allowed to do. For example, say your users are split into +two main groups - regular users and administrators. You should verify that the +currently logged in user is indeed an administrator before performing the +actions of an administrative part of your application. One way to do this is +with role based access control. + +=head2 The Components In This Framework + +=head3 Credential Verifiers + +When user input is transferred to the L application (typically via +form inputs) this data then enters the authentication framework through these +plugins. + +These plugins check the data, and ensure that it really proves the user is who +they claim to be. + +=head3 Storage Backends + +The credentials also identify a user, and this family of modules is supposed to +take this identification data and return a standardized object oriented +representation of users. + +When a user is retrieved from a store it is not necessarily authenticated. +Credential verifiers can either accept a user object, or fetch the object +themselves from the default store. + +=head3 The Core Plugin + +This plugin on its own is the glue, providing store registration, session +integration, and other goodness for the other plugins. + +=head3 Other Plugins + +More layers of plugins can be stacked on top of the authentication code. For +example, L provides an abstraction of +browser sessions that is more persistent per users. +L provides an accepted way to separate +and group users into categories, and then check which categories the current +user belongs to. + +=head1 EXAMPLE + +Let's say we were storing users in an Apache style htpasswd file. Users are +stored in that file, with a hashed password and some extra comments. Users are +verified by supplying a password which is matched with the file. + +This means that our application will begin like this: + + package MyApp; + + use Catalyst qw/ + Authentication + Authentication::Credential::Password + Authentication::Store::Htpasswd + /; + + __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile"; + +This loads the appropriate methods and also sets the htpasswd store as the +default store. + +So, now that we have the code loaded we need to get data from the user into the +credential verifier. + +Let's create an authentication controller: + + package MyApp::Controller::Auth; + + sub login : Local { + my ( $self, $c ) = @_; + + if ( my $user = $c->req->param("user") + and my $password = $c->req->param("password") ) + { + if ( $c->login( $user, $password ) ) { + $c->res->body( "hello " . $c->user->name ); + } else { + # login incorrect + } + } + else { + # invalid form input + } + } + +This code should be very readable. If all the necessary fields are supplied, +call the L method on the +controller. If that succeeds the user is logged in. + +It could be simplified though: + + sub login : Local { + my ( $self, $c ) = @_; + + if ( $c->login ) { + ... + } + } + +Since the C method knows how to find logically named parameters on it's +own. + +The credential verifier will ask the default store to get the user whose ID is +the user parameter. In this case the default store is the htpasswd one. Once it +fetches the user from the store the password is checked and if it's OK +C<< $c->user >> will contain the user object returned from the htpasswd store. + +We can also pass a user object to the credential verifier manually, if we have +several stores per app. This is discussed in +L. + +Now imagine each admin user has a comment set in the htpasswd file saying +"admin". + +A restricted action might look like this: + + sub restricted : Local { + my ( $self, $c ) = @_; + + $c->detach("unauthorized") + unless $c->user_exists + and $c->user->extra_info() eq "admin"; + + # do something restricted here + } + +This is somewhat similar to role based access control. +L treats the extra info +field as a comma separated list of roles if it's treated that way. Let's +leverage this. Add the role authorization plugin: + + use Catalyst qw/ + ... + Authorization::Roles + /; + + sub restricted : Local { + my ( $self, $c ) = @_; + + $c->detach("unauthorized") unless $c->check_roles("admin"); + + # do something restricted here + } + +This is somewhat simpler and will work if you change your store, too, since the +role interface is consistent. + +Let's say your app grew, and you now have 10000 users. It's no longer efficient +to maintain an htpasswd file, so you move this data to a database. + + use Catalyst qw/ + Authentication + Authentication::Credential::Password + Authentication::Store::DBIC + Authorization::Roles + /; + + __PACKAGE__->config->{authentication}{dbic} = ...; # see the DBIC store docs + +The rest of your code should be unchanged. Now let's say you are integrating +typekey authentication to your system. For simplicity's sake we'll assume that +the user's are still keyed in the same way. -It defines the notion of a logged in user, and provides integration with the + use Catalyst qw/ + Authentication + Authentication::Credential::Password + Authentication::Credential::TypeKey + Authentication::Store::DBIC + Authorization::Roles + /; + +And in your auth controller add a new action: + + sub typekey : Local { + my ( $self, $c ) = @_; + + if ( $c->authenticate_typekey) { # uses $c->req and Authen::TypeKey + # same stuff as the $c->login method + # ... + } + } + +You've now added a new credential verification mechanizm orthogonally to the +other components. All you have to do is make sure that the credential verifiers +pass on the same types of parameters to the store in order to retrieve user +objects. =head1 METHODS =over 4 +=item user + +Returns the currently logged in user or undef if there is none. + +=item user_exists + +Whether or not a user is logged in right now. + +The reason this method exists is that C<< $c->user >> may needlessly load the +user from the auth store. + +If you're just going to say + + if ( $c->user_exists ) { + # foo + } else { + $c->forward("login"); + } + +it should be more efficient than C<< $c->user >> when a user is marked in the +session but C<< $c->user >> hasn't been called yet. + =item logout Delete the currently logged in user from C and the session. -=item user +=item get_user $uid -Returns the currently logged user or undef if there is none. +Fetch a particular users details, defined by the given ID, via the default store. -=item get_user $uid +=back -Delegate C to the default store. +=head1 CONFIGURATION + +=over 4 + +=item use_session + +Whether or not to store the user's logged in state in the session, if the +application is also using the L plugin. This +value is set to true per default. + +=item store + +If multiple stores are being used, set the module you want as default here. + +=item stores + +If multiple stores are being used, you need to provide a name for each store +here, as a hash, the keys are the names you wish to use, and the values are +the the names of the plugins. + + # example + __PACKAGE__->config( authentication => { + store => 'Catalyst::Plugin::Authentication::Store::HtPasswd', + stores => { + 'dbic' => 'Catalyst::Plugin::Authentication::Store::DBIC' + } + }); + +=back + +=head1 METHODS FOR STORE MANAGEMENT + +=over 4 =item default_auth_store -Returns C<< $c->config->{authentication}{store} >>. +Return the store whose name is 'default'. + +This is set to C<< $c->config->{authentication}{store} >> if that value exists, +or by using a Store plugin: + + use Catalyst qw/Authentication Authentication::Store::Minimal/; + +Sets the default store to +L. + + +=item get_auth_store $name + +Return the store whose name is $name. + +=item get_auth_store_name $store + +Return the name of the store $store. + +=item auth_stores + +A hash keyed by name, with the stores registered in the app. + +=item auth_store_names + +A ref-hash keyed by store, which contains the names of the stores. + +=item register_auth_stores %stores_by_name + +Register stores into the application. =back @@ -232,6 +563,15 @@ authentication. This involves setting C and the internal data in C if L is loaded. +=item auth_restore_user $user + +Used to restore a user from the session, by C only when it's actually +needed. + +=item save_user_in_session $user + +Used to save the user in a session. + =item prepare Revives a user from the session object if there is one. @@ -244,17 +584,61 @@ Sets the default configuration parameters. =back -=head1 CONFIGURATION +=head1 SEE ALSO -=over 4 +This list might not be up to date. -=item use_session +=head2 User Storage Backends -Whether or not to store the user's logged in state in the session, if the -application is also using the L plugin. +L, +L, +L (also works with Class::DBI). -=back +=head2 Credential verification -=cut +L, +L, +L + +=head2 Authorization + +L, +L + +=head2 Internals Documentation + +L + +=head2 Misc + +L, +L + +=head1 DON'T SEE ALSO +This module along with its sub plugins deprecate a great number of other +modules. These include L, +L. + +At the time of writing these plugins have not yet been replaced or updated, but +should be eventually: L, +L, +L, +L. + +=head1 AUTHORS + +Yuval Kogman, C + +Jess Robinson + +David Kamholz + +=head1 COPYRIGHT & LICENSE + + Copyright (c) 2005 the aforementioned authors. All rights + reserved. This program is free software; you can redistribute + it and/or modify it under the same terms as Perl itself. + +=cut