- Document how to limit the attributes returned from the LDAP search
+ - Add persist_in_session config option to allow storing of user and its
+ roles in the session without hitting the LDAP store on each request
+ - fix use_roles enabled if explicitly disabled
1.015 20 February 2015
- Escape special characters in user/role names
attrs => [qw( distinguishedname name mail )],
},
user_results_filter => sub { return shift->pop_entry },
+ persist_in_session => 'all',
},
},
},
*bindpw* fields. If this is set to false, then the role search will
instead be performed when bound as the user you authenticated as.
+ persist_in_session
+ Can take one of the following values, defaults to "username":
+
+ "username"
+ Only store the username in the session and lookup the user and its
+ roles on every request. That was how the module worked until version
+ 1.015 and is also the default for backwards compatibility.
+
+ "all"
+ Store the user object and its roles in the session and never look it
+ up in the store after login.
+
+ NOTE: It's recommended to limit the user attributes fetched from
+ LDAP using user_search_options / attrs to not exhaust the session
+ store.
+
entry_class
The name of the class of LDAP entries returned. This class should exist
and is expected to be a subclass of Net::LDAP::Entry
Catalyst::Plugin::Authentication with this object.
AUTHORS
- Adam Jacob <holoway@cpan.org>
+ Adam Jacob <holoway@cpan.org> Peter Karman <karman@cpan.org> Alexander
+ Hartmaier <abraxxa@cpan.org>
Some parts stolen shamelessly and entirely from
Catalyst::Plugin::Authentication::Store::Htpasswd.
- Currently maintained by Peter Karman <karman@cpan.org>.
+ Currently maintained by Dagfinn Ilmari Mannsåker <ilmari@cpan.org>.
THANKS
To nothingmuch, ghenry, castaway and the rest of #catalyst for the help.
=pod
+=encoding utf-8
+
=head1 NAME
Catalyst::Authentication::Store::LDAP
attrs => [qw( distinguishedname name mail )],
},
user_results_filter => sub { return shift->pop_entry },
+ persist_in_session => 'all',
},
},
},
fields. If this is set to false, then the role search will instead be
performed when bound as the user you authenticated as.
+=head2 persist_in_session
+
+Can take one of the following values, defaults to C<username>:
+
+=over
+
+=item C<username>
+
+Only store the username in the session and lookup the user and its roles
+on every request. That was how the module worked until version 1.015 and is
+also the default for backwards compatibility.
+
+=item C<all>
+
+Store the user object and its roles in the session and never look it up in
+the store after login.
+
+B<NOTE:> It's recommended to limit the user attributes fetched from LDAP
+using L<user_search_options> / attrs to not exhaust the session store.
+
+=back
+
=head2 entry_class
The name of the class of LDAP entries returned. This class should
=head1 AUTHORS
Adam Jacob <holoway@cpan.org>
+Peter Karman <karman@cpan.org>
+Alexander Hartmaier <abraxxa@cpan.org>
Some parts stolen shamelessly and entirely from
L<Catalyst::Plugin::Authentication::Store::Htpasswd>.
-Currently maintained by Peter Karman <karman@cpan.org>.
+Currently maintained by Dagfinn Ilmari Mannsåker <ilmari@cpan.org>.
=head1 THANKS
'deref' => 'always',
},
'role_search_as_user' => 0,
+ 'persist_in_session' => 'all',
);
our $users = Catalyst::Authentication::Store::LDAP::Backend->new(\%config);
use Catalyst::Authentication::Store::LDAP::User;
use Net::LDAP;
use Catalyst::Utils ();
+use Catalyst::Exception;
BEGIN {
__PACKAGE__->mk_accessors(
role_filter role_scope role_field role_value
role_search_options start_tls start_tls_options
user_results_filter user_class role_search_as_user
+ persist_in_session
)
);
}
$config_hash{'role_filter'} ||= '(memberUid=%s)';
$config_hash{'role_scope'} ||= 'sub';
$config_hash{'role_field'} ||= 'cn';
- $config_hash{'use_roles'} ||= '1';
+ $config_hash{'use_roles'} = '1'
+ unless exists $config_hash{use_roles};
$config_hash{'start_tls'} ||= '0';
$config_hash{'entry_class'} ||= 'Catalyst::Model::LDAP::Entry';
$config_hash{'user_class'}
||= 'Catalyst::Authentication::Store::LDAP::User';
$config_hash{'role_search_as_user'} ||= 0;
+ $config_hash{'persist_in_session'} ||= 'username';
+ Catalyst::Exception->throw('persist_in_session must be either username or all')
+ unless $config_hash{'persist_in_session'} =~ /\A(?:username|all)\z/;
Catalyst::Utils::ensure_class_loaded( $config_hash{'user_class'} );
my $self = \%config_hash;
sub lookup_roles {
my ( $self, $userobj, $ldap ) = @_;
if ( $self->use_roles == 0 || $self->use_roles =~ /^false$/i ) {
- return undef;
+ return ();
}
$ldap ||= $self->role_search_as_user
? $userobj->ldap_connection : $self->ldap_bind;
Catalyst::Authentication::Store::LDAP::User->supports(@_);
}
-=head2 from_session( I<id>, I<$c> )
+=head2 from_session( I<id>, I<$c>, $frozenuser )
+
+Revives a serialized user from storage in the session.
-Returns get_user() for I<id>.
+Supports users stored with a different persist_in_session setting.
=cut
sub from_session {
- my ( $self, $c, $id ) = @_;
- $self->get_user( $id, $c );
+ my ( $self, $c, $frozenuser ) = @_;
+
+ # we need to restore the user depending on the current storage of the
+ # user in the session store which might differ from what
+ # persist_in_session is set to now
+ if ( ref $frozenuser eq 'HASH' ) {
+ # we can rely on the existance of this key if the user is a hashref
+ if ( $frozenuser->{persist_in_session} eq 'all' ) {
+ return $self->user_class->new( $self, $frozenuser->{user}, $c, $frozenuser->{_roles} );
+ }
+ }
+
+ return $self->get_user( $frozenuser, $c );
}
1;
use strict;
use warnings;
use Scalar::Util qw/refaddr/;
+use Net::LDAP::Entry;
our $VERSION = '1.015';
=cut
sub new {
- my ( $class, $store, $user, $c ) = @_;
+ my ( $class, $store, $user, $c, $roles ) = @_;
return unless $user;
- bless { store => $store, user => $user, }, $class;
+ bless { store => $store, user => $user, _roles => $roles }, $class;
}
=head2 id
=head2 for_session
-Returns the User object, stringified.
+Returns the user for persistence in the session depending on the
+persist_in_session config option.
+
+Stores the persist_in_session setting so it can be used to revive the user
+even if the setting has been changed.
=cut
sub for_session {
my $self = shift;
+
+ if ( $self->store->persist_in_session eq 'all' ) {
+ # use the roles accessor to ensure the roles are fetched
+ return {
+ # store the persistance setting in the session to know how to
+ # restore the user
+ persist_in_session => $self->store->persist_in_session,
+ user => $self->user,
+ _roles => [ $self->roles ],
+ };
+ }
+
return $self->stringify;
}
use strict;
use warnings;
-use Catalyst::Exception;
use Test::More;
use lib 't/lib';
use strict;
use warnings;
-use Catalyst::Exception;
use Test::More;
use lib 't/lib';
use strict;
use warnings;
-use Catalyst::Exception;
use Test::More;
use lib 't/lib';
use Storable qw/ freeze /;
use Test::Exception;
-eval "use Catalyst::Model::LDAP";
-plan skip_all => "Catalyst::Model::LDAP not installed" if $@;
-
my $server = LDAPTest::spawn_server();
use_ok("Catalyst::Authentication::Store::LDAP::Backend");
use strict;
use warnings;
-use Catalyst::Exception;
use Test::More;
use lib 't/lib';
use LDAPTest;
-eval "use Catalyst::Model::LDAP";
-plan skip_all => "Catalyst::Model::LDAP not installed" if $@;
-
my $server = LDAPTest::spawn_server();
use_ok("Catalyst::Authentication::Store::LDAP::Backend");
use strict;
use warnings;
-use Catalyst::Exception;
use Test::More;
use Test::MockObject::Extends;
use Net::LDAP::Entry;
use lib 't/lib';
-eval "use Catalyst::Model::LDAP";
-plan skip_all => "Catalyst::Model::LDAP not installed" if $@;
-
use_ok("Catalyst::Authentication::Store::LDAP::Backend");
+
+my $back_without_use_roles = Catalyst::Authentication::Store::LDAP::Backend->new({
+ ldap_server => 'ldap://127.0.0.1:555',
+ binddn => 'anonymous',
+ bindpw => 'dontcarehow',
+ user_basedn => 'ou=foobar',
+ user_filter => '(&(objectClass=inetOrgPerson)(uid=%s))',
+ user_scope => 'one',
+ user_field => 'uid',
+});
+is $back_without_use_roles->use_roles, 1, 'use_roles enabled be default';
+
+my $back_with_use_roles_disabled = Catalyst::Authentication::Store::LDAP::Backend->new({
+ ldap_server => 'ldap://127.0.0.1:555',
+ binddn => 'anonymous',
+ bindpw => 'dontcarehow',
+ user_basedn => 'ou=foobar',
+ user_filter => '(&(objectClass=inetOrgPerson)(uid=%s))',
+ user_scope => 'one',
+ user_field => 'uid',
+ use_roles => 0,
+});
+is $back_with_use_roles_disabled->use_roles, 0, 'use_roles disabled when set
+to 0';
+
+my $back_with_use_roles_enabled = Catalyst::Authentication::Store::LDAP::Backend->new({
+ ldap_server => 'ldap://127.0.0.1:555',
+ binddn => 'anonymous',
+ bindpw => 'dontcarehow',
+ user_basedn => 'ou=foobar',
+ user_filter => '(&(objectClass=inetOrgPerson)(uid=%s))',
+ user_scope => 'one',
+ user_field => 'uid',
+ use_roles => 1,
+});
+is $back_with_use_roles_enabled->use_roles, 1, 'use_roles enabled when set to
+1';
+
my (@searches, @binds);
for my $i (0..1) {
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Catalyst::Authentication::Store::LDAP::Backend;
+use lib 't/lib';
+use LDAPTest;
+
+my $server = LDAPTest::spawn_server();
+
+# the tests currently don't require a real Catalyst app instance
+my $c;
+
+my $stringy_session_value;
+subtest "persist_in_session unset" => sub {
+ my $back = Catalyst::Authentication::Store::LDAP::Backend->new(
+ { 'ldap_server' => LDAPTest::server_host(),
+ 'binddn' => 'anonymous',
+ 'bindpw' => 'dontcarehow',
+ 'start_tls' => 0,
+ 'user_basedn' => 'ou=foobar',
+ 'user_filter' => '(&(objectClass=person)(uid=%s))',
+ 'user_scope' => 'one',
+ 'user_field' => 'uid',
+ 'use_roles' => 0,
+ }
+ );
+
+ my $user = $back->find_user( { username => 'somebody' } );
+ ok($stringy_session_value = $user->for_session, 'for_session ok');
+ is($stringy_session_value, 'somebody', 'for_session returns correct data');
+ ok($back->from_session($c, $stringy_session_value), 'from_session ok');
+};
+
+my $hash_session_value;
+subtest "persist_in_session 'all'" => sub {
+ my $back = Catalyst::Authentication::Store::LDAP::Backend->new(
+ { ldap_server => LDAPTest::server_host(),
+ binddn => 'anonymous',
+ bindpw => 'dontcarehow',
+ start_tls => 0,
+ user_basedn => 'ou=foobar',
+ user_filter => '(&(objectClass=person)(uid=%s))',
+ user_scope => 'one',
+ user_field => 'uid',
+ use_roles => 0,
+ persist_in_session => 'all',
+ }
+ );
+ my $user = $back->find_user( { username => 'somebody' } );
+ ok($hash_session_value = $user->for_session, 'for_session ok');
+ is_deeply($hash_session_value,
+ {
+ persist_in_session => 'all',
+ user => $user->user,
+ _roles => [],
+ },
+ "for_session returns correct data");
+ ok($back->from_session($c, $hash_session_value), 'from_session ok');
+ ok($back->from_session($c, $stringy_session_value), 'from_session ok for stringy value');
+};
+
+subtest "persist_in_session 'username'" => sub {
+ my $back = Catalyst::Authentication::Store::LDAP::Backend->new(
+ { ldap_server => LDAPTest::server_host(),
+ binddn => 'anonymous',
+ bindpw => 'dontcarehow',
+ start_tls => 0,
+ user_basedn => 'ou=foobar',
+ user_filter => '(&(objectClass=person)(uid=%s))',
+ user_scope => 'one',
+ user_field => 'uid',
+ use_roles => 0,
+ persist_in_session => 'username',
+ }
+ );
+ my $user = $back->find_user( { username => 'somebody' } );
+ ok(my $session = $stringy_session_value = $user->for_session, 'for_session ok');
+ is($session, 'somebody', 'for_session returns correct data');
+ ok($back->from_session($c, $session), 'from_session ok');
+ ok($back->from_session($c, $hash_session_value), 'from_session ok for hash value')
+ or diag explain $hash_session_value;
+};
+
+done_testing;
# vim: ts=8 sts=4 et sw=4 sr sta
use strict;
use warnings;
-use Catalyst::Exception;
use Test::More;