[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