Making Realm a bonafide object. No change to docs yet, but passes all
[catagits/Catalyst-Plugin-Authentication.git] / lib / Catalyst / Plugin / Authentication.pm
index 7a50835..f139712 100644 (file)
@@ -1,12 +1,9 @@
-#!/usr/bin/perl
-
 package Catalyst::Plugin::Authentication;
 
 use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
 
 BEGIN {
     __PACKAGE__->mk_accessors(qw/_user/);
-    __PACKAGE__->mk_classdata($_) for qw/_auth_realms/;
 }
 
 use strict;
@@ -14,6 +11,7 @@ use warnings;
 
 use Tie::RefHash;
 use Class::Inspector;
+use Catalyst::Plugin::Authentication::Realm;
 
 # this optimization breaks under Template::Toolkit
 # use user_exists instead
@@ -22,7 +20,7 @@ use Class::Inspector;
 #      constant->import(have_want => eval { require Want });
 #}
 
-our $VERSION = "0.10";
+our $VERSION = "0.10003";
 
 sub set_authenticated {
     my ( $c, $user, $realmname ) = @_;
@@ -33,39 +31,24 @@ sub set_authenticated {
     if (!$realmname) {
         $realmname = 'default';
     }
+    my $realm = $c->get_auth_realm($realmname);
+    
+    if (!$realm) {
+        Catalyst::Exception->throw(
+                "set_authenticated called with nonexistant realm: '$realmname'.");
+    }
     
     if (    $c->isa("Catalyst::Plugin::Session")
         and $c->config->{authentication}{use_session}
         and $user->supports("session") )
     {
-        $c->save_user_in_session($realmname, $user);
+        $realm->save_user_in_session($c, $user);
     }
-    $user->_set_auth_realm($realmname);
+    $user->auth_realm($realm->name);
     
     $c->NEXT::set_authenticated($user, $realmname);
 }
 
-sub _should_save_user_in_session {
-    my ( $c, $user ) = @_;
-
-    $c->_auth_sessions_supported
-    and $c->config->{authentication}{use_session}
-    and $user->supports("session");
-}
-
-sub _should_load_user_from_session {
-    my ( $c, $user ) = @_;
-
-    $c->_auth_sessions_supported
-    and $c->config->{authentication}{use_session}
-    and $c->session_is_valid;
-}
-
-sub _auth_sessions_supported {
-    my $c = shift;
-    $c->isa("Catalyst::Plugin::Session");
-}
-
 sub user {
     my $c = shift;
 
@@ -73,8 +56,8 @@ sub user {
         return $c->_user(@_);
     }
 
-    if ( defined(my $user = $c->_user) ) {
-        return $user;
+    if ( defined($c->_user) ) {
+        return $c->_user;
     } else {
         return $c->auth_restore_user;
     }
@@ -87,15 +70,28 @@ sub user_exists {
        return defined($c->_user) || defined($c->_user_in_session);
 }
 
+# works like user_exists - except only returns true if user 
+# exists AND is in the realm requested.
+sub user_in_realm {
+    my ($c, $realmname) = @_;
+
+    if (defined($c->_user)) {
+        return ($c->_user->auth_realm eq $realmname);
+    } elsif (defined($c->_user_in_session)) {
+        return ($c->session->{__user_realm} eq $realmname);  
+    } else {
+        return undef;
+    }
+}
 
-sub save_user_in_session {
-    my ( $c, $realmname, $user ) = @_;
+sub __old_save_user_in_session {
+    my ( $c, $user, $realmname ) = @_;
 
     $c->session->{__user_realm} = $realmname;
     
-    # we want to ask the backend for a user prepared for the session.
+    # we want to ask the store for a user prepared for the session.
     # but older modules split this functionality between the user and the
-    # backend.  We try the store first.  If not, we use the old method.
+    # store.  We try the store first.  If not, we use the old method.
     my $realm = $c->get_auth_realm($realmname);
     if ($realm->{'store'}->can('for_session')) {
         $c->session->{__user} = $realm->{'store'}->for_session($c, $user);
@@ -125,30 +121,26 @@ sub find_user {
     
     $realmname ||= 'default';
     my $realm = $c->get_auth_realm($realmname);
-    if ( $realm->{'store'} ) {
-        return $realm->{'store'}->find_user($userinfo, $c);
-    } else {
-        $c->log->debug('find_user: unable to locate a store matching the requested realm');
+    
+    if (!$realm) {
+        Catalyst::Exception->throw(
+                "find_user called with nonexistant realm: '$realmname'.");
     }
+    return $realm->find_user($userinfo, $c);
 }
 
 
 sub _user_in_session {
     my $c = shift;
 
-    return unless $c->_should_load_user_from_session;
+    return unless
+        $c->isa("Catalyst::Plugin::Session")
+        and $c->config->{authentication}{use_session}
+        and $c->session_is_valid;
 
     return $c->session->{__user};
 }
 
-sub _store_in_session {
-    my $c = shift;
-    
-    # we don't need verification, it's only called if _user_in_session returned something useful
-
-    return $c->session->{__user_store};
-}
-
 sub auth_restore_user {
     my ( $c, $frozen_user, $realmname ) = @_;
 
@@ -159,10 +151,11 @@ sub auth_restore_user {
     return unless $realmname; # FIXME die unless? This is an internal inconsistency
 
     my $realm = $c->get_auth_realm($realmname);
-    $c->_user( my $user = $realm->{'store'}->from_session( $c, $frozen_user ) );
+    $c->_user( my $user = $realm->from_session( $c, $frozen_user ) );
     
     # this sets the realm the user originated in.
-    $user->_set_auth_realm($realmname);
+    $user->auth_realm($realmname);
+        
     return $user;
 
 }
@@ -170,114 +163,73 @@ sub auth_restore_user {
 # we can't actually do our setup in setup because the model has not yet been loaded.  
 # So we have to trigger off of setup_finished.  :-(
 sub setup {
-    my $c = shift;
+    my $app = shift;
 
-    $c->_authentication_initialize();
-    $c->NEXT::setup(@_);
+    $app->_authentication_initialize();
+    $app->NEXT::setup(@_);
 }
 
 ## the actual initialization routine. whee.
 sub _authentication_initialize {
-    my $c = shift;
+    my $app = shift;
 
-    if ($c->_auth_realms) { return };
-    
-    my $cfg = $c->config->{'authentication'} || {};
+    ## let's avoid recreating / configuring everything if we have already done it, eh?
+    if ($app->can('_auth_realms')) { return };
 
-    %$cfg = (
-        use_session => 1,
-        %$cfg,
-    );
+    ## make classdata where it is used.  
+    $app->mk_classdata( '_auth_realms' => {});
+    
+    my $cfg = $app->config->{'authentication'} ||= {};
 
-    my $realmhash = {};
-    $c->_auth_realms($realmhash);
+    $cfg->{use_session} = 1;
     
-    ## BACKWARDS COMPATIBILITY - if realm is not defined - then we are probably dealing
-    ## with an old-school config.  The only caveat here is that we must add a classname 
     if (exists($cfg->{'realms'})) {
-        
         foreach my $realm (keys %{$cfg->{'realms'}}) {
-            $c->setup_auth_realm($realm, $cfg->{'realms'}{$realm});
+            $app->setup_auth_realm($realm, $cfg->{'realms'}{$realm});
         }
-
-        #  if we have a 'default-realm' in the config hash and we don't already 
+        #  if we have a 'default_realm' in the config hash and we don't already 
         # have a realm called 'default', we point default at the realm specified
-        if (exists($cfg->{'default_realm'}) && !$c->get_auth_realm('default')) {
-            $c->set_default_auth_realm($cfg->{'default_realm'});
+        if (exists($cfg->{'default_realm'}) && !$app->get_auth_realm('default')) {
+            $app->_set_default_auth_realm($cfg->{'default_realm'});
         }
     } else {
+        
+        ## BACKWARDS COMPATIBILITY - if realms is not defined - then we are probably dealing
+        ## with an old-school config.  The only caveat here is that we must add a classname
+        
+        ## also - we have to treat {store} as {stores}{default} - because 
+        ## while it is not a clear as a valid config in the docs, it 
+        ## is functional with the old api. Whee!
+        if (exists($cfg->{'store'}) && !exists($cfg->{'stores'}{'default'})) {
+            $cfg->{'stores'}{'default'} = $cfg->{'store'};
+        }
+
         foreach my $storename (keys %{$cfg->{'stores'}}) {
             my $realmcfg = {
-                store => $cfg->{'stores'}{$storename},
+                store => { class => $cfg->{'stores'}{$storename} },
             };
-            $c->setup_auth_realm($storename, $realmcfg);
+            $app->setup_auth_realm($storename, $realmcfg);
         }
     }
     
 }
 
-
 # set up realmname.
 sub setup_auth_realm {
     my ($app, $realmname, $config) = @_;
     
-    $app->log->debug("Setting up $realmname");
-    if (!exists($config->{'store'}{'class'})) {
-        Carp::croak "Couldn't setup the authentication realm named '$realmname', no class defined";
-    } 
-        
-    # use the 
-    my $storeclass = $config->{'store'}{'class'};
-    
-    ## follow catalyst class naming - a + prefix means a fully qualified class, otherwise it's
-    ## taken to mean C::P::A::Store::(specifiedclass)::Backend
-    if ($storeclass !~ /^\+(.*)$/ ) {
-        $storeclass = "Catalyst::Plugin::Authentication::Store::${storeclass}::Backend";
-    } else {
-        $storeclass = $1;
+    my $realmclass = 'Catalyst::Plugin::Authentication::Realm';
+    if (defined($config->{'class'})) {
+        $realmclass = $config->{'class'};
+        Catalyst::Utils::ensure_class_loaded( $realmclass );
     }
-    
-
-    # a little niceness - since most systems seem to use the password credential class, 
-    # if no credential class is specified we use password.
-    $config->{credential}{class} ||= "Catalyst::Plugin::Authentication::Credential::Password";
-
-    my $credentialclass = $config->{'credential'}{'class'};
-    
-    ## follow catalyst class naming - a + prefix means a fully qualified class, otherwise it's
-    ## taken to mean C::P::A::Credential::(specifiedclass)
-    if ($credentialclass !~ /^\+(.*)$/ ) {
-        $credentialclass = "Catalyst::Plugin::Authentication::Credential::${credentialclass}";
+    my $realm = $realmclass->new($realmname, $config, $app);
+    if ($realm) {
+        $app->auth_realms->{$realmname} = $realm;
     } else {
-        $credentialclass = $1;
-    }
-    
-    # if we made it here - we have what we need to load the classes;
-    Catalyst::Utils::ensure_class_loaded( $credentialclass );
-    Catalyst::Utils::ensure_class_loaded( $storeclass );
-    
-    # BACKWARDS COMPATIBILITY - if the store class does not define find_user, we define it in terms 
-    # of get_user and add it to the class.  this is because the auth routines use find_user, 
-    # and rely on it being present. (this avoids per-call checks)
-    if (!$storeclass->can('find_user')) {
-        no strict 'refs';
-        *{"${storeclass}::find_user"} = sub {
-                                                my ($self, $info) = @_;
-                                                my @rest = @{$info->{rest}} if exists($info->{rest});
-                                                $self->get_user($info->{id}, @rest);
-                                            };
-    }
-    
-    $app->auth_realms->{$realmname}{'store'} = $storeclass->new($config->{'store'}, $app);
-    if ($credentialclass->can('new')) {
-        $app->auth_realms->{$realmname}{'credential'} = $credentialclass->new($config->{'credential'}, $app);
-    } else {
-        # if the credential class is not actually a class - has no 'new' operator, we wrap it, 
-        # once again - to allow our code to be simple at runtime and allow non-OO packages to function.
-        my $wrapperclass = 'Catalyst::Plugin::Authentication::Credential::Wrapper';
-        Catalyst::Utils::ensure_class_loaded( $wrapperclass );
-        $app->auth_realms->{$realmname}{'credential'} = $wrapperclass->new($config->{'credential'}, $app);
+        $app->log->debug("realm initialization for '$realmname' failed.");
     }
+    return $realm;
 }
 
 sub auth_realms {
@@ -290,7 +242,13 @@ sub get_auth_realm {
     return $app->auth_realms->{$realmname};
 }
 
-sub set_default_auth_realm {
+
+# Very internal method.  Vital Valuable Urgent, Do not touch on pain of death.
+# Using this method just assigns the default realm to be the value associated
+# with the realmname provided.  It WILL overwrite any real realm called 'default'
+# so can be very confusing if used improperly.  It's used properly already. 
+# Translation: don't use it.
+sub _set_default_auth_realm {
     my ($app, $realmname) = @_;
     
     if (exists($app->auth_realms->{$realmname})) {
@@ -308,17 +266,16 @@ sub authenticate {
         
     my $realm = $app->get_auth_realm($realmname);
     
-    if ($realm && exists($realm->{'credential'})) {
-        my $user = $realm->{'credential'}->authenticate($app, $realm->{store}, $userinfo);
-        if ($user) {
-            $app->set_authenticated($user, $realmname);
-            return $user;
-        }
+    ## note to self - make authenticate throw an exception if realm is invalid.
+    
+    if ($realm) {
+        return $realm->authenticate($app, $userinfo);
     } else {
-        $app->log->debug("The realm requested, '$realmname' does not exist," .
-                         " or there is no credential associated with it.")
+        Catalyst::Exception->throw(
+                "authenticate called with nonexistant realm: '$realmname'.");
+
     }
-    return 0;
+    return undef;
 }
 
 ## BACKWARDS COMPATIBILITY  -- Warning:  Here be monsters!
@@ -339,7 +296,7 @@ sub get_user {
     return $c->find_user( {'id' => $uid, 'rest'=>\@rest }, 'default' );
 }
 
-##
+
 ## this should only be called when using old-style authentication plugins.  IF this gets
 ## called in a new-style config - it will OVERWRITE the store of your default realm.  Don't do it.
 ## also - this is a partial setup - because no credential is instantiated... in other words it ONLY
@@ -348,9 +305,19 @@ sub get_user {
 sub default_auth_store {
     my $self = shift;
 
+    my $realm = $self->get_auth_realm('default');
+    if (!$realm) {
+        $realm = $self->setup_auth_realm('default', { class => "Catalyst::Plugin::Authentication::Realm::Compatibility" });
+    }
     if ( my $new = shift ) {
-        $self->auth_realms->{'default'}{'store'} = $new;
-        my $storeclass = ref($new);
+        $realm->store($new);
+        
+        my $storeclass;
+        if (ref($new)) {
+            $storeclass = ref($new);
+        } else {
+            $storeclass = $new;
+        }
         
         # BACKWARDS COMPATIBILITY - if the store class does not define find_user, we define it in terms 
         # of get_user and add it to the class.  this is because the auth routines use find_user, 
@@ -365,7 +332,7 @@ sub default_auth_store {
         }
     }
 
-    return $self->get_auth_realm('default')->{'store'};
+    return $self->get_auth_realm('default')->store;
 }
 
 ## BACKWARDS COMPATIBILITY
@@ -374,7 +341,7 @@ sub default_auth_store {
 sub auth_store_names {
     my $self = shift;
 
-    my %hash = (  $self->get_auth_realm('default')->{'store'} => 'default' );
+    my %hash = (  $self->get_auth_realm('default')->store => 'default' );
 }
 
 sub get_auth_store {
@@ -396,14 +363,9 @@ sub get_auth_store_name {
 sub auth_stores {
     my $self = shift;
 
-    my %hash = ( 'default' => $self->get_auth_realm('default')->{'store'});
+    my %hash = ( 'default' => $self->get_auth_realm('default')->store);
 }
 
-
-
-
-
-
 __PACKAGE__;
 
 __END__
@@ -422,28 +384,34 @@ authentication framework.
     /;
 
     # later on ...
-    $c->authenticate({ username => 'myusername', password => 'mypassword' });
+    $c->authenticate({ username => 'myusername', 
+                       password => 'mypassword' });
     my $age = $c->user->get('age');
     $c->logout;
 
 =head1 DESCRIPTION
 
-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).
+The authentication plugin provides generic user support for Catalyst apps. 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.
+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. Credentials are used to verify users, using information from the store,
+given data from the frontend. A Credential and a Store are paired to form a
+'Realm'. A Catalyst application using the authentication framework must have
+at least one realm, and may have several.
 
 To implement authentication in a Catalyst application you need to add this 
-module, plus at least one store and one credential module.
+module, and specify at least one realm in the configuration. 
 
 Authentication data can also be stored in a session, if the application 
 is using the L<Catalyst::Plugin::Session> module.
 
+B<NOTE> in version 0.10 of this module, the interface to this module changed.
+Please see L</COMPATIBILITY ROUTINES> for more information.
+
 =head1 INTRODUCTION
 
 =head2 The Authentication/Authorization Process
@@ -465,38 +433,55 @@ identity theft for now). Checking the password, or any other proof is called
 B<credential verification>.
 
 By this time you know exactly who the user is - the user's identity is
-B<authenticated>. This is where this module's job stops, and other plugins step
-in. The next logical step is B<authorization>, 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
+B<authenticated>. This is where this module's job stops, and your application
+or other plugins step in.  
+
+The next logical step is B<authorization>, 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 want to 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.
+actions in an administrative part of your application. These decisions may be
+made within your application code using just the information available after
+authentication, or it may be facilitated by a number of plugins.  
 
 =head2 The Components In This Framework
 
+=head3 Realms
+
+Configuration of the Catalyst::Plugin::Authentication framework is done in
+terms of realms. In simplest terms, a realm is a pairing of a Credential
+verifier and a User storage (Store) backend.
+
+An application can have any number of Realms, each of which operates
+independant of the others. Each realm has a name, which is used to identify it
+as the target of an authentication request. This name can be anything, such as
+'users' or 'members'. One realm must be defined as the default_realm, which is
+used when no realm name is specified. More information about configuring
+realms is available in the configuration section.
+
 =head3 Credential Verifiers
 
 When user input is transferred to the L<Catalyst> application (typically via
-form inputs) this data then enters the authentication framework through these
-plugins.
+form inputs) the application may pass this information into the authentication
+system through the $c->authenticate() method.  From there, it is passed to the
+appropriate Credential verifier.
 
 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.
+The authentication data also identifies a user, and the Storage backend modules
+use this data to locate and return a standardized object-oriented
+representation of a user.
 
 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.
+Credential verifiers accept a set of authentication data and use this
+information to retrieve the user from the store they are paired with.
 
 =head3 The Core Plugin
 
-This plugin on its own is the glue, providing store registration, session
+This plugin on its own is the glue, providing realm configuration, session
 integration, and other goodness for the other plugins.
 
 =head3 Other Plugins
@@ -510,9 +495,8 @@ 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.
+Let's say we were storing users in a simple perl hash. Users are
+verified by supplying a password which is matched within the hash.
 
 This means that our application will begin like this:
 
@@ -520,19 +504,42 @@ This means that our application will begin like this:
 
     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.
+    __PACKAGE__->config->{authentication} = 
+                {  
+                    default_realm => 'members',
+                    realms => {
+                        members => {
+                            credential => {
+                                class => 'Password',
+                                password_field => 'password',
+                                password_type => 'clear'
+                            },
+                            store => {
+                                class => 'Minimal',
+                               users = {
+                                   bob => {
+                                       password => "s00p3r",                                       
+                                       editor => 'yes',
+                                       roles => [qw/edit delete/],
+                                   },
+                                   william => {
+                                       password => "s3cr3t",
+                                       roles => [qw/comment/],
+                                   }
+                               }                       
+                           }
+                       }
+                       }
+                };
     
-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:
+This tells the authentication plugin what realms are available, which
+credential and store modules are used, and the configuration of each. With
+this code loaded, we can now attempt to authenticate users.
+
+To show an example of this, let's create an authentication controller:
 
     package MyApp::Controller::Auth;
 
@@ -542,8 +549,9 @@ Let's create an authentication controller:
         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 );
+            if ( $c->authenticate( { username => $user, 
+                                     password => $password } ) ) {
+                $c->res->body( "hello " . $c->user->get("name") );
             } else {
                 # login incorrect
             }
@@ -554,60 +562,60 @@ Let's create an authentication controller:
     }
 
 This code should be very readable. If all the necessary fields are supplied,
-call the L<Authentication::Credential::Password/login> method on the
-controller. If that succeeds the user is logged in.
+call the "authenticate" method from the controller. If it succeeds the 
+user is logged in.
 
-It could be simplified though:
+The credential verifier will attempt to retrieve the user whose details match
+the authentication information provided to $c->authenticate(). Once it fetches
+the user the password is checked and if it matches the user will be
+B<authenticated> and C<< $c->user >> will contain the user object retrieved
+from the store.
 
-    sub login : Local {
-        my ( $self, $c ) = @_;
-
-        if ( $c->login ) {
-            ...
-        }
-    }
-
-Since the C<login> method knows how to find logically named parameters on its
-own.
+In the above case, the default realm is checked, but we could just as easily
+check an alternate realm. If this were an admin login, for example, we could
+authenticate on the admin realm by simply changing the $c->authenticate()
+call:
 
-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.
+    if ( $c->authenticate( { username => $user, 
+                             password => $password }, 'admin' )l ) {
+        $c->res->body( "hello " . $c->user->get("name") );
+    } ...
 
-We can also pass a user object to the credential verifier manually, if we have
-several stores per app. This is discussed in
-L<Catalyst::Plugin::Authentication::Store>.
 
-Now imagine each admin user has a comment set in the htpasswd file saying
-"admin".
+Now suppose we want to restrict the ability to edit to a user with an 
+'editor' value of yes.
 
-A restricted action might look like this:
+The restricted action might look like this:
 
-    sub restricted : Local {
+    sub edit : Local {
         my ( $self, $c ) = @_;
 
         $c->detach("unauthorized")
           unless $c->user_exists
-          and $c->user->extra_info() eq "admin";
+          and $c->user->get('editor') eq 'yes';
 
         # do something restricted here
     }
 
-This is somewhat similar to role based access control.
-L<Catalyst::Plugin::Authentication::Store::Htpasswd> 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:
+(Note that if you have multiple realms, you can use $c->user_in_realm('realmname')
+in place of $c->user_exists(); This will essentially perform the same 
+verification as user_exists, with the added requirement that if there is a 
+user, it must have come from the realm specified.)
+
+The above example is somewhat similar to role based access control.  
+L<Catalyst::Plugin::Authentication::Store::Minimal> treats the roles field as
+an array of role names. Let's leverage this. Add the role authorization
+plugin:
 
     use Catalyst qw/
         ...
         Authorization::Roles
     /;
 
-    sub restricted : Local {
+    sub edit : Local {
         my ( $self, $c ) = @_;
 
-        $c->detach("unauthorized") unless $c->check_roles("admin");
+        $c->detach("unauthorized") unless $c->check_roles("edit");
 
         # do something restricted here
     }
@@ -615,180 +623,178 @@ leverage this. Add the role authorization plugin:
 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.
-
-    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.
+Let's say your app grew, and you now have 10000 users. It's no longer
+efficient to maintain a hash of users, so you move this data to a database.
+You can accomplish this simply by installing the DBIx::Class Store and
+changing your config:
+
+    __PACKAGE__->config->{authentication} = 
+                    {  
+                        default_realm => 'members',
+                        realms => {
+                            members => {
+                                credential => {
+                                    class => 'Password',
+                                    password_field => 'password',
+                                    password_type => 'clear'
+                                },
+                                store => {
+                                    class => 'DBIx::Class',
+                                   user_class => 'MyApp::Users',
+                                   role_column => 'roles'                      
+                               }
+                               }
+                       }
+                    };
+
+The authentication system works behind the scenes to load your data from the
+new source. The rest of your application is completely unchanged.
 
-=item logout
-
-Delete the currently logged in user from C<user> and the session.
-
-=item get_user $uid
-
-Fetch a particular users details, defined by the given ID, via the default store.
-
-=back
 
 =head1 CONFIGURATION
 
 =over 4
 
+    # example
+    __PACKAGE__->config->{authentication} = 
+                {  
+                    default_realm => 'members',
+                    realms => {
+                        members => {
+                            credential => {
+                                class => 'Password',
+                                password_field => 'password',
+                                password_type => 'clear'
+                            },
+                            store => {
+                                class => 'DBIx::Class',
+                                   user_class => 'MyApp::Users',
+                                   role_column => 'roles'                      
+                               }
+                       },
+                       admins => {
+                           credential => {
+                               class => 'Password',
+                               password_field => 'password',
+                                password_type => 'clear'
+                           },
+                           store => {
+                               class => '+MyApp::Authentication::Store::NetAuth',
+                               authserver => '192.168.10.17'
+                           }
+                       }
+                       
+                       }
+                };
+
 =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<Catalyst::Plugin::Session> plugin. This 
+application is also using L<Catalyst::Plugin::Session>. This 
 value is set to true per default.
 
-=item store
+=item default_realm
 
-If multiple stores are being used, set the module you want as default here.
+This defines which realm should be used as when no realm is provided to methods
+that require a realm such as authenticate or find_user.
 
-=item stores
+=item realms
 
-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.
+This contains the series of realm configurations you want to use for your app.
+The only rule here is that there must be at least one.  A realm consists of a
+name, which is used to reference the realm, a credential and a store.  
 
- # example
- __PACKAGE__->config( authentication => {
-                        store => 'Catalyst::Plugin::Authentication::Store::HtPasswd',
-                        stores => { 
-                           'dbic' => 'Catalyst::Plugin::Authentication::Store::DBIC'
-                                  }
-                                         });
+Each realm config contains two hashes, one called 'credential' and one called 
+'store', each of which provide configuration details to the respective modules.
+The contents of these hashes is specific to the module being used, with the 
+exception of the 'class' element, which tells the core Authentication module the
+classname to instantiate.  
 
-=back
+The 'class' element follows the standard Catalyst mechanism of class
+specification. If a class is prefixed with a +, it is assumed to be a complete
+class name. Otherwise it is considered to be a portion of the class name. For
+credentials, the classname 'B<Password>', for example, is expanded to
+Catalyst::Plugin::Authentication::Credential::B<Password>. For stores, the
+classname 'B<storename>' is expanded to:
+Catalyst::Plugin::Authentication::Store::B<storename>.
 
-=head1 METHODS FOR STORE MANAGEMENT
 
-=over 4
+=back
 
-=item default_auth_store
 
-Return the store whose name is 'default'.
+=head1 METHODS
 
-This is set to C<< $c->config->{authentication}{store} >> if that value exists,
-or by using a Store plugin:
+=over 4 
 
-       use Catalyst qw/Authentication Authentication::Store::Minimal/;
+=item authenticate( $userinfo, $realm )
 
-Sets the default store to
-L<Catalyst::Plugin::Authentication::Store::Minimal::Backend>.
+Attempts to authenticate the user using the information in the $userinfo hash
+reference using the realm $realm. $realm may be omitted, in which case the
+default realm is checked.
 
+=item user
 
-=item get_auth_store $name
+Returns the currently logged in user or undef if there is none.
 
-Return the store whose name is $name.
+=item user_exists
 
-=item get_auth_store_name $store
+Returns true if a user is logged in right now. The difference between
+user_exists and user is that user_exists will return true if a user is logged
+in, even if it has not been yet retrieved from the storage backend. If you only
+need to know if the user is logged in, depending on the storage mechanism this
+can be much more efficient.
 
-Return the name of the store $store.
+=item user_in_realm ( $realm )
 
-=item auth_stores
+Works like user_exists, except that it only returns true if a user is both 
+logged in right now and was retrieved from the realm provided.  
 
-A hash keyed by name, with the stores registered in the app.
+=item logout
 
-=item auth_store_names
+Logs the user out, Deletes the currently logged in user from $c->user and the session.
 
-A ref-hash keyed by store, which contains the names of the stores.
+=item find_user( $userinfo, $realm )
 
-=item register_auth_stores %stores_by_name
-
-Register stores into the application.
+Fetch a particular users details, matching the provided user info, from the realm 
+specified in $realm.
 
 =back
 
 =head1 INTERNAL METHODS
 
-=over 4
+These methods are for Catalyst::Plugin::Authentication B<INTERNAL USE> only.
+Please do not use them in your own code, whether application or credential /
+store modules. If you do, you will very likely get the nasty shock of having
+to fix / rewrite your code when things change. They are documented here only
+for reference.
 
-=item set_authenticated $user
+=over 4
 
-Marks a user as authenticated. Should be called from a
-C<Catalyst::Plugin::Authentication::Credential> plugin after successful
-authentication.
+=item set_authenticated ( $user, $realmname )
 
-This involves setting C<user> and the internal data in C<session> if
-L<Catalyst::Plugin::Session> is loaded.
+Marks a user as authenticated. This is called from within the authenticate
+routine when a credential returns a user. $realmname defaults to 'default'
 
-=item auth_restore_user $user
+=item auth_restore_user ( $user, $realmname )
 
-Used to restore a user from the session, by C<user> only when it's actually
-needed.
+Used to restore a user from the session. In most cases this is called without
+arguments to restore the user via the session. Can be called with arguments
+when restoring a user from some other method.  Currently not used in this way.
 
-=item save_user_in_session $user
+=item save_user_in_session ( $user, $realmname )
 
-Used to save the user in a session.
+Used to save the user in a session. Saves $user in session, marked as
+originating in $realmname. Both arguments are required.
 
-=item prepare
+=item auth_realms
 
-Revives a user from the session object if there is one.
+Returns a hashref containing realmname -> realm instance pairs. Realm
+instances contain an instantiated store and credential object as the 'store'
+and 'credential' elements, respectively
 
-=item setup
+=item get_auth_realm ( $realmname )
 
-Sets the default configuration parameters.
+Retrieves the realm instance for the realmname provided.
 
 =item 
 
@@ -796,19 +802,17 @@ Sets the default configuration parameters.
 
 =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
 
 L<Catalyst::Plugin::Authentication::Store::Minimal>,
-L<Catalyst::Plugin::Authentication::Store::Htpasswd>,
-L<Catalyst::Plugin::Authentication::Store::DBIC> (also works with Class::DBI).
+L<Catalyst::Plugin::Authentication::Store::DBIx::Class>,
 
 =head2 Credential verification
 
 L<Catalyst::Plugin::Authentication::Credential::Password>,
-L<Catalyst::Plugin::Authentication::Credential::HTTP>,
-L<Catalyst::Plugin::Authentication::Credential::TypeKey>
 
 =head2 Authorization
 
@@ -817,7 +821,7 @@ L<Catalyst::Plugin::Authorization::Roles>
 
 =head2 Internals Documentation
 
-L<Catalyst::Plugin::Authentication::Store>
+L<Catalyst::Plugin::Authentication::Internals>
 
 =head2 Misc
 
@@ -836,14 +840,91 @@ L<Catalyst::Plugin::Authentication::LDAP>,
 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
+changed. For app developers, this change is fairly minor, but for
+Credential and Store authors, the changes are significant. 
+
+Please see the documentation in version 0.09 of
+Catalyst::Plugin::Authentication for a better understanding of how the old API
+functioned.
+
+The items below are still present in the plugin, though using them is
+deprecated. They remain only as a transition tool, for those sites which can
+not yet be upgraded to use the new system due to local customizations or use
+of Credential / Store modules that have not yet been updated to work with the 
+new API.
+
+These routines should not be used in any application using realms
+functionality or any of the methods described above. These are for reference
+purposes only.
+
+=over 4
+
+=item login
+
+This method is used to initiate authentication and user retrieval. Technically
+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
+
+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:
+
+    # load the Minimal authentication store.
+       use Catalyst qw/Authentication Authentication::Store::Minimal/;
+
+Sets the default store to
+L<Catalyst::Plugin::Authentication::Store::Minimal>.
+
+=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 register_auth_stores %stores_by_name
+
+Register stores into the application.
+
+=back
+
+
+
 =head1 AUTHORS
 
 Yuval Kogman, C<nothingmuch@woobling.org>
 
+Jay Kuri, C<jayk@cpan.org>
+
 Jess Robinson
 
 David Kamholz
 
+
 =head1 COPYRIGHT & LICENSE
 
         Copyright (c) 2005 the aforementioned authors. All rights