From: Matt S Trout Date: Tue, 17 Jul 2007 16:58:17 +0000 (+0000) Subject: Various minor adjustments to code and a WHOLE lot of documentation X-Git-Tag: v0.10009_01~76 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=649de93b6c447769b5b4ccea225953b3e1f0e6ad;p=catagits%2FCatalyst-Plugin-Authentication.git Various minor adjustments to code and a WHOLE lot of documentation r34155@cain (orig r5598): jayk | 2006-11-27 08:21:47 +0000 --- diff --git a/lib/Catalyst/Plugin/Authentication.pm b/lib/Catalyst/Plugin/Authentication.pm index cf96704..73f19ba 100644 --- a/lib/Catalyst/Plugin/Authentication.pm +++ b/lib/Catalyst/Plugin/Authentication.pm @@ -316,7 +316,7 @@ sub authenticate { 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 @@ Retrieves the realm instance for the realmname provided. =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 @@ L, =head2 Credential verification L, -L, -L =head2 Authorization @@ -842,7 +841,7 @@ L =head2 Internals Documentation -L +L =head2 Misc @@ -861,6 +860,17 @@ L, L, L. +=head1 INCOMPATABILITIES + +The realms based configuration and functionality of the 0.10 update +of L 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. We hope that most +modules will move to the compatible list above very quickly. =head1 COMPATIBILITY ROUTINES @@ -887,8 +897,9 @@ purposes only. =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 class. It is +included here for reference only. =item default_auth_store @@ -927,11 +938,12 @@ Register stores into the application. Yuval Kogman, C +Jay Kuri, C + Jess Robinson David Kamholz -Jay Kuri C =head1 COPYRIGHT & LICENSE diff --git a/lib/Catalyst/Plugin/Authentication/Credential/Password.pm b/lib/Catalyst/Plugin/Authentication/Credential/Password.pm index 0a161fc..329197d 100644 --- a/lib/Catalyst/Plugin/Authentication/Credential/Password.pm +++ b/lib/Catalyst/Plugin/Authentication/Credential/Password.pm @@ -28,7 +28,7 @@ sub authenticate { 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; } diff --git a/lib/Catalyst/Plugin/Authentication/Internals.pod b/lib/Catalyst/Plugin/Authentication/Internals.pod new file mode 100644 index 0000000..61688d9 --- /dev/null +++ b/lib/Catalyst/Plugin/Authentication/Internals.pod @@ -0,0 +1,364 @@ + +=head1 NAME + +Catalyst::Plugin::Authentication::Internals - All about authentication Stores and Credentials + +=head1 INTRODUCTION + +L 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 +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 and storage backends, or +B, 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. 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 +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 +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-Eauthenticate()> is called from within an application, the +objects created in the initialization process come into play. +C<$c-Eauthenticate()> 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 +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-Eauthenticate> 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-Euser> to access the authenticated user, etc. + +Astute readers will note that the main +L 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 method is called only once, during the setup process of +L. 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 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 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 +necessarily constitute successful authentication. Your store class should +never assume this and in most cases C<$c> B 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 method). It should therefore +ensure that whatever information provided can be used by the C +method to locate the unique user being saved. Note that there is no guarantee +that the same Catalyst instance will receive both the C and +C 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 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. C 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 method. Note that +this is used as a B 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 +inherit from +L. + +=head3 USER METHODS + +The routines required by the +L plugin +are below. Note that of these, only get_object is strictly required, as the +L +base class contains reasonable implementations of the rest. If you do choose +to implement only the C 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 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 routine as C $user-Eid> so you should ensure that your +store's C 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 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. + +This method should accept an arbitrary list of parameters (determined by you or +the credential verifyer), and return an object inheriting +L. + +For introspection purposes you can also define the C 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 method. When the user object is saved in the session the +C 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). + +=head2 Optional Features + +Each user has the C 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 wraps around +L, 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(@_); + } diff --git a/lib/Catalyst/Plugin/Authentication/Store/Minimal/Backend.pm b/lib/Catalyst/Plugin/Authentication/Store/Minimal/Backend.pm index 1514959..56bf8d1 100644 --- a/lib/Catalyst/Plugin/Authentication/Store/Minimal/Backend.pm +++ b/lib/Catalyst/Plugin/Authentication/Store/Minimal/Backend.pm @@ -37,11 +37,7 @@ sub find_user { 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 @@ hash ref as it's backing structure. 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. diff --git a/lib/Catalyst/Plugin/Authentication/User.pm b/lib/Catalyst/Plugin/Authentication/User.pm index 5961220..f77fdd2 100644 --- a/lib/Catalyst/Plugin/Authentication/User.pm +++ b/lib/Catalyst/Plugin/Authentication/User.pm @@ -16,19 +16,24 @@ sub auth_realm { } - +## 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; }