X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FPlugin%2FAuthentication.pm;h=ab25422f92737f3715631450ecc91df6743193d2;hb=a5961476f1c57502011250be04fcba816a03b102;hp=1355effb1ab778026496780317fd6f71ca565295;hpb=4fbe2e14e7e9bc91be1af92ce7b119373c1c808c;p=catagits%2FCatalyst-Plugin-Authentication.git diff --git a/lib/Catalyst/Plugin/Authentication.pm b/lib/Catalyst/Plugin/Authentication.pm index 1355eff..ab25422 100644 --- a/lib/Catalyst/Plugin/Authentication.pm +++ b/lib/Catalyst/Plugin/Authentication.pm @@ -15,7 +15,14 @@ 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.05"; sub set_authenticated { my ( $c, $user ) = @_; @@ -43,12 +50,18 @@ sub user { my $user = $c->_user; if ( $user and !Scalar::Util::blessed($user) ) { +# return 1 if have_want() && Want::want("BOOL"); return $c->auth_restore_user($user); } return $user; } +sub user_exists { + my $c = shift; + return defined($c->_user); +} + sub save_user_in_session { my ( $c, $user ) = @_; @@ -67,6 +80,8 @@ sub logout { { delete @{ $c->session }{qw/__user __user_store/}; } + + $c->NEXT::logout(@_); } sub get_user { @@ -100,7 +115,7 @@ sub auth_restore_user { my ( $c, $frozen_user, $store_name ) = @_; return - unless $c->isa("Catalyst::PLugin::Session") + unless $c->isa("Catalyst::Plugin::Session") and $c->config->{authentication}{use_session} and $c->sessionid; @@ -189,19 +204,246 @@ 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 -It defines the notion of a logged in user, and provides integration with the -L plugin, +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. + + 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 @@ -209,7 +451,25 @@ L plugin, =item user -Returns the currently logged user or undef if there is none. +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 @@ -217,17 +477,49 @@ Delete the currently logged in user from C and the session. =item get_user $uid -Delegate C to the default store. +Fetch a particular users details, defined by the given ID, via the default store. + +=back + +=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 Return the store whose name is 'default'. -This is set to C<<$c->config->{authentication}{store}>> if that value exists, +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/; @@ -235,6 +527,7 @@ or by using a Store plugin: Sets the default store to L. + =item get_auth_store $name Return the store whose name is $name. @@ -255,6 +548,8 @@ A ref-hash keyed by store, which contains the names of the stores. Register stores into the application. +=back + =head1 INTERNAL METHODS =over 4 @@ -289,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