-#!/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;
use Tie::RefHash;
use Class::Inspector;
+use Catalyst::Plugin::Authentication::Realm;
# this optimization breaks under Template::Toolkit
# use user_exists instead
# constant->import(have_want => eval { require Want });
#}
-our $VERSION = "0.10";
+our $VERSION = "0.10003";
sub set_authenticated {
my ( $c, $user, $realmname ) = @_;
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($user, $realmname);
+ $realm->save_user_in_session($c, $user);
}
- $user->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;
}
}
-sub save_user_in_session {
+sub __old_save_user_in_session {
my ( $c, $user, $realmname ) = @_;
$c->session->{__user_realm} = $realmname;
$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};
}
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->auth_realm($realmname);
+
return $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 };
-
- if (!exists($c->config->{'authentication'}) {
- $c->config->{'authentication'} = {};
- }
-
- 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);
}
}
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)
- if ($storeclass !~ /^\+(.*)$/ ) {
- $storeclass = "Catalyst::Plugin::Authentication::Store::${storeclass}";
- } 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;
+ $app->log->debug("realm initialization for '$realmname' failed.");
}
-
- # 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);
- $app->auth_realms->{$realmname}{'credential'} = $credentialclass->new($config->{'credential'}, $app);
-
+ return $realm;
}
sub auth_realms {
## note to self - make authenticate throw an exception if realm is invalid.
- if ($realm && exists($realm->{'credential'})) {
- my $user = $realm->{'credential'}->authenticate($app, $realm->{store}, $userinfo);
- if (ref($user)) {
- $app->set_authenticated($user, $realmname);
- return $user;
- }
+ 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 undef;
}
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,
}
}
- return $self->get_auth_realm('default')->{'store'};
+ return $self->get_auth_realm('default')->store;
}
## BACKWARDS COMPATIBILITY
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 {
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__;
/;
# later on ...
- $c->authenticate({ username => 'myusername', password => 'mypassword' });
+ $c->authenticate({ username => 'myusername',
+ password => 'mypassword' });
my $age = $c->user->get('age');
$c->logout;
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 in an administrative part of your application. These decisionsmay be
+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.
/;
__PACKAGE__->config->{authentication} =
- {
- default_realm => 'members',
- realms => {
- members => {
- credential => {
- class => 'Password'
- },
- store => {
- class => 'Minimal',
- users = {
- bob => {
- password => "s00p3r",
- editor => 'yes',
- roles => [qw/edit delete/],
- },
- william => {
- password => "s3cr3t",
- roles => [qw/comment/],
- }
- }
- }
- }
- }
- };
+ {
+ 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/],
+ }
+ }
+ }
+ }
+ }
+ };
This tells the authentication plugin what realms are available, which
}
This code should be very readable. If all the necessary fields are supplied,
-call the L<Catalyst::Plugin::Authentication/authenticate> method in the
-controller. If it succeeds the user is logged in.
+call the "authenticate" method from the controller. If it succeeds the
+user is logged in.
The credential verifier will attempt to retrieve the user whose details match
the authentication information provided to $c->authenticate(). Once it fetches
} ...
-Now suppose we want to restrict the ability to edit to a user with 'edit'
-in it's roles list.
+Now suppose we want to restrict the ability to edit to a user with an
+'editor' value of yes.
The restricted action might look like this:
$c->detach("unauthorized")
unless $c->user_exists
- and $c->user->get('editor') == 'yes';
+ and $c->user->get('editor') eq 'yes';
# do something restricted here
}
-This is somewhat similar to role based access control.
+(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:
realms => {
members => {
credential => {
- class => 'Password'
+ class => 'Password',
+ password_field => 'password',
+ password_type => 'clear'
},
store => {
class => 'DBIx::Class',
realms => {
members => {
credential => {
- class => 'Password'
+ class => 'Password',
+ password_field => 'password',
+ password_type => 'clear'
},
store => {
class => 'DBIx::Class',
},
admins => {
credential => {
- class => 'Password'
+ class => 'Password',
+ password_field => 'password',
+ password_type => 'clear'
},
store => {
class => '+MyApp::Authentication::Store::NetAuth',
=item user_in_realm ( $realm )
Works like user_exists, except that it only returns true if a user is both
-logged in right now and is from the realm provided.
+logged in right now and was retrieved from the realm provided.
=item logout