[Catalyst-commits] r6550 - in trunk/Catalyst-Plugin-Authentication: . lib/Catalyst/Plugin lib/Catalyst/Plugin/Authentication lib/Catalyst/Plugin/Authentication/Credential lib/Catalyst/Plugin/Authentication/Store/Minimal

matthewt at dev.catalyst.perl.org matthewt at dev.catalyst.perl.org
Tue Jul 17 17:58:18 GMT 2007


Author: matthewt
Date: 2007-07-17 17:58:17 +0100 (Tue, 17 Jul 2007)
New Revision: 6550

Added:
   trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Internals.pod
Modified:
   trunk/Catalyst-Plugin-Authentication/
   trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication.pm
   trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Credential/Password.pm
   trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Store/Minimal/Backend.pm
   trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/User.pm
Log:
 r34155 at cain (orig r5598):  jayk | 2006-11-27 08:21:47 +0000
 Various minor adjustments to code and a WHOLE lot of documentation
 



Property changes on: trunk/Catalyst-Plugin-Authentication
___________________________________________________________________
Name: svk:merge
   - 4ad37cd2-5fec-0310-835f-b3785c72a374:/branches/Catalyst-Plugin-Authentication:5498
   + 4ad37cd2-5fec-0310-835f-b3785c72a374:/branches/Catalyst-Plugin-Authentication:5598

Modified: trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Credential/Password.pm
===================================================================
--- trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Credential/Password.pm	2007-07-17 16:58:09 UTC (rev 6549)
+++ trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Credential/Password.pm	2007-07-17 16:58:17 UTC (rev 6550)
@@ -28,7 +28,7 @@
     my ( $self, $c, $authstore, $authinfo ) = @_;
 
     my $user_obj = $authstore->find_user($authinfo, $c);
-    if ($user_obj) {
+    if (ref($user_obj)) {
         if ($self->check_password($user_obj, $authinfo)) {
             return $user_obj;
         }

Added: trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Internals.pod
===================================================================
--- trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Internals.pod	                        (rev 0)
+++ trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Internals.pod	2007-07-17 16:58:17 UTC (rev 6550)
@@ -0,0 +1,364 @@
+
+=head1 NAME
+
+Catalyst::Plugin::Authentication::Internals - All about authentication Stores and Credentials
+
+=head1 INTRODUCTION
+
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication> provides
+a standard authentication interface to application developers using the
+Catalyst framework. It is designed to allow application developers to use
+various methods of user storage and credential verification. It is also
+designed to provide for minimal change to the application when switching
+between different storage and credential verification methods.
+
+While L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
+provides the interface to the application developer, the actual work of
+verifying the credentials and retrieving users is delegated to separate
+modules. These modules are called B<Credentials> and storage backends, or
+B<Stores>, respectively. For authentication to function there must be at least
+one credential and one storage backend. A pairing of a store and a credential
+is referred to as a B<Realm>. There may be any number of realms defined for an
+application, though most applications will not require more than one or two.
+
+The details of using this module can be found in the
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
+documentation.
+
+What follows is an explanation of how the module functions internally and what
+is required to implement a credential or a store.
+
+=head1 OVERVIEW
+
+There are two main entry points you need to be aware of when writing a store
+or credential module. The first is initialization and the second is during the
+actual call to the Catalyst application's authenticate method.
+
+=head2 INITIALIZATION
+
+When the authentication module is loaded, it reads it's configuration to
+determine the realms to set up for the application and which realm is to be
+the default. For each realm defined in the application's config,
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
+instantiates both a new credential object and a new store object. See below
+for the details of how credentials and stores are instantiated.
+
+    NOTE: The instances created will remain active throughout the entire 
+    lifetime of the application, and so should be relatively lightweight. 
+    Care should be taken to ensure that they do not grow, or retain 
+    information per request, because they will be involved in each 
+    authentication request and could therefore substantially
+    hurt memory consumption over time.  
+
+=head2 AUTHENTICATION
+
+When C<$c-E<gt>authenticate()> is called from within an application, the
+objects created in the initialization process come into play.
+C<$c-E<gt>authenticate()> takes two arguments. The first is a hash reference
+containing all the information available about the user. This will be used to
+locate the user in the store and verify the user's credentials. The second
+argument is the realm to authenticate against. If the second argument is
+omitted, the default realm is assumed. 
+
+The main authentication module then locates the credential and store objects
+for the realm specified and calls the credential object's C<authenticate()>
+method. It provides three arguments, first the application object, or C<$c>,
+then a reference to the store object, and finally the hashref provided in the
+C<$c-E<gt>authenticate> call. The main authentication module expects the
+return value to be a reference to a user object upon successful
+authentication. If it receives anything aside from a reference, it is
+considered to be an authentication failure. Upon success, the returned user is
+marked as authenticated and the application can act accordingly, using
+C<$c-E<gt>user> to access the authenticated user, etc.
+
+Astute readers will note that the main
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication> module
+does not interact with the store in any way, save for passing a reference to
+it to the credential. This is correct. The credential object is responsible
+for obtaining the user from the provided store using information from the
+userinfo hashref and/or data obtained during the credential verification
+process.
+
+=head1 WRITING A STORE
+
+There are two parts to an authentication store, the backend and the user object.
+
+=head2 STORAGE BACKEND
+
+Writing a storage backend is actually quite simple.  There are only five methods
+that must be implemented. They are:
+
+    new()           - instantiates the store object
+    find_user()     - locates a user using data contained in the hashref
+    for_session()   - prepares a user to be stored in the session
+    from_session()  - does any restoration required when obtaining a user from the session
+    user_supports() - provides information about what the user object supports
+
+=head3 STORE METHODS
+
+=over 4
+
+=item new( $config, $app )
+
+The C<new()> method is called only once, during the setup process of
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>. The
+first argument, C<$config>, is a hash reference containing the configuration
+information for the store module. The second argument is a reference to the
+Catalyst application.
+
+    Note that when new() is called, Catalyst has not yet loaded the various 
+    controller and model classes, nor is it definite that other plugins have 
+    been loaded, so your new() method must not rely on any of those being 
+    present.  If any of this is required for your store to function, you should
+    defer that part of initialization until the first method call. 
+    
+The C<new()> method should return a blessed reference to your store object.
+
+=item find_user( $authinfo, $c ) 
+
+This is the workhorse of any authentication store. It's job is to take the
+information provided to it via the C<$authinfo> hashref and locate the user
+that matches it. It should return a reference to a user object. A return value
+of anything else is considered to mean no user was found that matched the
+information provided.
+
+How C<find_user()> accomplishes it's job is entirely up to you, the author, as
+is what $authinfo is required to contain.  Many stores will simply use a 
+username element in $authinfo to locate the user, but more advanced functionality
+is possible and you may bend the $authinfo to your needs.  Be aware, however, that
+both Credentials and Stores work with the same $authinfo hash, so take care to
+avoid overlapping element names.
+
+Please note that this routine may be called numerous times in various
+circumstances, and that a successful match for a user here does B<NOT>
+necessarily constitute successful authentication. Your store class should
+never assume this and in most cases C<$c> B<should not be modified> by your
+store object.
+
+=item for_session( $c, $user )
+
+This method is responsible for preparing a user object for storage in the session.
+It should return information that can be placed in the session and later used to 
+restore a user object (using the C<from_session()> method).  It should therefore
+ensure that whatever information provided can be used by the C<from_session()>
+method to locate the unique user being saved.  Note that there is no guarantee
+that the same Catalyst instance will receive both the C<for_session()> and 
+C<from_session()> calls.  You should take care to provide information that can
+be used to restore a user, regardless of the current state of the application. 
+A good rule of thumb is that if C<from_session()> can revive the user with the
+given information even if the Catalyst application has just started up, you are 
+in good shape.
+
+=item from_session( $c, $frozenuser )
+
+This method is called whenever a user is being restored from the session.  
+C<$frozenuser> contains the information that was stored in the session for the user.
+This will under normal circumstances be the exact data your store returned from 
+the previous call to C<for_session()>.  C<from_session()> should return a valid
+user object.
+
+=item user_supports( $feature, ...  )
+
+This method allows credentials and other objects to inquire as to what the
+underlying user object is capable of. This is pretty-well free-form and the
+main purpose is to allow graceful integration with credentials and
+applications that may provide advanced functionality based on whether the
+underlying user object can do certain things. In most cases you will want to
+pass this directly to the underlying user class' C<supports> method. Note that 
+this is used as a B<class> method against the user class and therefore must 
+be able to function without an instantiated user object.
+
+=back
+
+=head2 USER OBJECT
+
+The user object is an important piece of your store module. It will be the
+part of the system that the application developer will interact with most. As
+such, the API for the user object is very rigid. All user objects B<MUST>
+inherit from
+L<Catalyst::Plugin::Authentication::User|Catalyst::Plugin::Authentication::User>.
+
+=head3 USER METHODS
+
+The routines required by the
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication> plugin
+are below. Note that of these, only get_object is strictly required, as the
+L<Catalyst::Plugin::Authentication::User|Catalyst::Plugin::Authentication::User>
+base class contains reasonable implementations of the rest. If you do choose
+to implement only the C<get_object()> routine, please read the base class
+documentation so that you fully understand how the other routines will be
+implemented for you.
+
+Also, your user object can implement whatever additional methods you require
+to provide the functionality you need. So long as the below are implemented,
+and you don't overlap the base class' methods with incompatible routines, you
+should experience no problems.
+
+=over 4
+
+=item id( )
+
+The C<id()> method should return a unique id (scalar) that can be used to 
+retreive this user from the store.  Often this will be provided to the store's
+C<find_user()> routine as C<id =E<gt> $user-E<gt>id> so you should ensure that your 
+store's C<find_user()> can cope with that. 
+
+=item supports_features( )
+
+This method should return a hashref of 'extra' features supported. This is for
+more flexible integration with some Credentials / applications. It is not
+required that you support anything, and returning C<undef> is perfectly
+acceptable and in most cases what you will do.
+
+=item get( $fieldname )
+
+This method should return the value of the field matching fieldname provided,
+or undef if there is no field matching that fieldname. In most cases this will
+access the underlying storage mechanism for the user data and return the
+information. This is used as a standard method of accessing an authenticated
+user's data, and MUST be implemented by all user objects.  
+
+    Note: There is no equivalent 'set' method. Each user class is likely
+    to vary greatly in how data must be saved and it is therefore impractical to 
+    try to provide a standard way of accomplishing it. When an application 
+    developer needs to save data, they should obtain the underlying object / data
+    by calling get_object, and work with it directly.
+    
+
+=item get_object( )
+
+This method returns the underlying user object. If your user object is backed
+by another object class, this method should return that underlying object.
+This allows the application developer to obtain an editable object. Generally
+speaking this will only be done by developers who know what they are doing and
+require advanced functionality which is either unforeseen or inconsistent
+across user classes. If your object is not backed by another class, or you
+need to provide additional intermediate functionality, it is perfectly
+reasonable to return C<$self>.
+
+=back 
+
+
+... Documentation fairy fell asleep here.  
+
+
+=head1 MULTIPLE BACKENDS
+
+A key issue to understand about authentication stores is that there are
+potentially many of them. Each one is registered into the application, and has
+a name.
+
+For most applications, there is only one, and in this framework it is called
+'default'.
+
+When you use a plugin, like
+
+    use Catalyst qw/
+        Authentication
+        Authentication::Store::Foo
+    /;
+
+the Store plugins typically only act at setup time. They rarely do more than
+check out the configuration, and register e.g. Store::Foo::Backend, and set it
+as the default store.
+
+    __PACKAGE__->default_auth_store( $store );
+
+    # the same as
+
+    __PACKAGE__->register_auth_stores( default => $store );
+
+=head1 WORKING WITH USERS
+
+All credential verifiers should accept either a user object, or a user ID.
+
+If a user ID is provided, then they will fetch the user object from the default
+store, and check against it.
+
+This should be pretty much DWIM all the time.
+
+When you need multiple authentication backends per application then you must
+fetch things yourself. For example:
+
+    my $user = $c->get_auth_store("other_store")->get_user($id);
+
+    $c->login( $user, $supplied_password );
+
+Instead of just:
+
+    $c->login( $id, $supplied_password );
+
+which will go to the default store.
+
+=head1 WRITING A BACKEND
+
+Writing an authentication storage backend is a very simple matter.
+
+The only method you really need to support is C<get_user>.
+
+This method should accept an arbitrary list of parameters (determined by you or
+the credential verifyer), and return an object inheriting
+L<Catalyst::Plugin::Authentication::User>.
+
+For introspection purposes you can also define the C<user_supports> method. See
+below for optional features. This is not necessary, but might be in the future.
+
+=head2 Integrating with Catalyst::Plugin::Session
+
+If your users support sessions, your store should also define the
+C<from_session> method. When the user object is saved in the session the
+C<for_session> method is called, and that is used as the value in the session
+(typically a user id). The store is also saved in the hash. If
+C<<$user->store>> returns something registered, that store's name is used. If
+not, the user's class is used as if it were a store (and must also support
+C<from_session>).
+
+=head2 Optional Features
+
+Each user has the C<supports> method. For example:
+
+    $user->supports(qw/password clear/);
+
+should return a true value if this specific user has a clear text password.
+
+This is on a per user (not necessarily a per store) basis. To make assumptions
+about the store as a whole,
+
+    $store->user_supports(qw/password clear/);
+
+is supposed to be the lowest common denominator.
+
+The standardization of these values is to be goverened by the community,
+typically defined by the credential verification plugins.
+
+=head2 Stores implying certain credentials
+
+Sometimes a store is agnostic to the credentials (DB storage, for example), but
+sometimes it isn't (like an Htpasswd file).
+
+If you are writing a backend that wraps around a module, like
+L<Catalyst::Plugin::Authentication::Store::Htpasswd> wraps around
+L<Authen::Htpasswd>, it makes sense to delegate the credential checks.
+
+This particular example caused the following "feature" to be added:
+
+    $user->supports(qw/password self_check/);
+
+=head2 Writing a plugin to go with the backend
+
+Typically the backend will do the heavy lifting, by registering a store.
+
+These plugins should look something like this:
+
+    sub setup {
+        my $c = shift;
+
+        $c->default_auth_store(
+            # a store can be an object or a class
+            Catalyst::Plugin::Authentication::Store::Foo::Backend->new(
+                ...
+            )
+        );
+
+        $c->NEXT::setup(@_);
+    }

Modified: trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Store/Minimal/Backend.pm
===================================================================
--- trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Store/Minimal/Backend.pm	2007-07-17 16:58:09 UTC (rev 6549)
+++ trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/Store/Minimal/Backend.pm	2007-07-17 16:58:17 UTC (rev 6550)
@@ -37,11 +37,7 @@
     my $user = $self->{'hash'}{$id};
 
     if ( ref $user ) {
-        if ( Scalar::Util::blessed($user) ) {
-			$user->id( $id );
-            return $user;
-        }
-        elsif ( ref $user eq "HASH" ) {
+        if ( ref $user eq "HASH" ) {
             $user->{id} ||= $id;
             return bless $user, "Catalyst::Plugin::Authentication::User::Hash";
         }
@@ -184,6 +180,8 @@
 
 Keys the hash by the 'id' or 'username' element in the authinfo hash and returns the user.
 
+... documentation fairy stopped here. ...
+
 If the return value is unblessed it will be blessed as
 L<Catalyst::Plugin::Authentication::User::Hash>.
 

Modified: trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/User.pm
===================================================================
--- trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/User.pm	2007-07-17 16:58:09 UTC (rev 6549)
+++ trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication/User.pm	2007-07-17 16:58:17 UTC (rev 6550)
@@ -16,19 +16,24 @@
 }
 
 
-
+## this relies on 'supported_features' being implemented by the subclass.. 
+## but it is not an error if it is not.  it just means you support nothing.  
+## nihilist user objects are welcome here.
 sub supports {
     my ( $self, @spec ) = @_;
 
-    my $cursor = $self->supported_features;
+    my $cursor = undef;
+    if ($self->can('supported_features')) {
+        $cursor = $self->supported_features;
 
-    # traverse the feature list,
-    for (@spec) {
-        #die "bad feature spec: @spec" if ref($cursor) ne "HASH";
-        return if ref($cursor) ne "HASH";
+        # traverse the feature list,
+        for (@spec) {
+            #die "bad feature spec: @spec" if ref($cursor) ne "HASH";
+            return if ref($cursor) ne "HASH";
 
-        $cursor = $cursor->{$_};
-    }
+            $cursor = $cursor->{$_};
+        }
+    } 
 
     return $cursor;
 }

Modified: trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication.pm
===================================================================
--- trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication.pm	2007-07-17 16:58:09 UTC (rev 6549)
+++ trunk/Catalyst-Plugin-Authentication/lib/Catalyst/Plugin/Authentication.pm	2007-07-17 16:58:17 UTC (rev 6550)
@@ -316,7 +316,7 @@
     
     if ($realm && exists($realm->{'credential'})) {
         my $user = $realm->{'credential'}->authenticate($app, $realm->{store}, $userinfo);
-        if ($user) {
+        if (ref($user)) {
             $app->set_authenticated($user, $realmname);
             return $user;
         }
@@ -822,7 +822,8 @@
 
 =head1 SEE ALSO
 
-This list might not be up to date.
+This list might not be up to date.  Below are modules known to work with the updated
+API of 0.10 and are therefore compatible with realms.  
 
 =head2 User Storage Backends
 
@@ -832,8 +833,6 @@
 =head2 Credential verification
 
 L<Catalyst::Plugin::Authentication::Credential::Password>,
-L<Catalyst::Plugin::Authentication::Credential::HTTP>,
-L<Catalyst::Plugin::Authentication::Credential::TypeKey>
 
 =head2 Authorization
 
@@ -842,7 +841,7 @@
 
 =head2 Internals Documentation
 
-L<Catalyst::Plugin::Authentication::Store>
+L<Catalyst::Plugin::Authentication::Internals>
 
 =head2 Misc
 
@@ -861,7 +860,18 @@
 L<Catalyst::Plugin::Authentication::CDBI::Basic>,
 L<Catalyst::Plugin::Authentication::Basic::Remote>.
 
+=head1 INCOMPATABILITIES
 
+The realms based configuration and functionality of the 0.10 update 
+of L<Catalyst::Plugin::Authentication> required a change in the API used by
+credentials and stores.  It has a compatibility mode which allows use of
+modules that have not yet been updated. This, however, completely mimics the
+older api and disables the new realm-based features. In other words you can
+not mix the older credential and store modules with realms, or realm-based
+configs. The changes required to update modules are relatively minor and are
+covered in L<Catalyst::Plugin::Authentication::Internals>.  We hope that most
+modules will move to the compatible list above very quickly.
+
 =head1 COMPATIBILITY ROUTINES
 
 In version 0.10 of L<Catalyst::Plugin::Authentication>, the API
@@ -887,8 +897,9 @@
 =item login
 
 This method is used to initiate authentication and user retrieval. Technically
-this is part of the old Password credential module, included here for
-completeness.
+this is part of the old Password credential module and it still resides in the
+L<Password|Catalyst::Plugin::Authentication::Credential::Password> class. It is
+included here for reference only.
 
 =item default_auth_store
 
@@ -927,11 +938,12 @@
 
 Yuval Kogman, C<nothingmuch at woobling.org>
 
+Jay Kuri, C<jayk at cpan.org>
+
 Jess Robinson
 
 David Kamholz
 
-Jay Kuri C<jayk at cpan.org>
 
 =head1 COPYRIGHT & LICENSE
 




More information about the Catalyst-commits mailing list