Merge branch 'persist_in_session'
Dagfinn Ilmari Mannsåker [Thu, 11 Feb 2016 17:36:35 +0000 (17:36 +0000)]
12 files changed:
Changes
README
lib/Catalyst/Authentication/Store/LDAP.pm
lib/Catalyst/Authentication/Store/LDAP/Backend.pm
lib/Catalyst/Authentication/Store/LDAP/User.pm
t/02-realms_api.t
t/03-entry_class.t
t/04-user_class.t
t/05-user_attributes.t
t/10-roles-mock.t
t/20-persist_in_session.t [new file with mode: 0644]
t/50.auth.case.sensitivity.t

diff --git a/Changes b/Changes
index 693f9af..153914e 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,4 +1,7 @@
   - 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
diff --git a/README b/README
index d72cb63..a495d71 100644 (file)
--- a/README
+++ b/README
@@ -43,6 +43,7 @@ SYNOPSIS
                      attrs => [qw( distinguishedname name mail )],
                    },
                    user_results_filter => sub { return shift->pop_entry },
+                   persist_in_session  => 'all',
                  },
                },
              },
@@ -273,6 +274,22 @@ CONFIGURATION OPTIONS
     *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
@@ -287,12 +304,13 @@ METHODS
     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.
index 275e763..196f180 100644 (file)
@@ -19,6 +19,8 @@ __END__
 
 =pod
 
+=encoding utf-8
+
 =head1 NAME
 
 Catalyst::Authentication::Store::LDAP
@@ -66,6 +68,7 @@ Catalyst::Authentication::Store::LDAP
                  attrs => [qw( distinguishedname name mail )],
                },
                user_results_filter => sub { return shift->pop_entry },
+               persist_in_session  => 'all',
              },
            },
          },
@@ -313,6 +316,28 @@ by binding to the directory with the details in the I<binddn> and I<bindpw>
 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
@@ -333,11 +358,13 @@ L<Catalyst::Plugin::Authentication/default_auth_store> with this object.
 =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
 
index 7da9dd6..bd7bae0 100644 (file)
@@ -50,6 +50,7 @@ Catalyst::Authentication::Store::LDAP::Backend
                 'deref' => 'always',
             },
             'role_search_as_user' => 0,
+            'persist_in_session'  => 'all',
     );
 
     our $users = Catalyst::Authentication::Store::LDAP::Backend->new(\%config);
@@ -78,6 +79,7 @@ our $VERSION = '1.015';
 use Catalyst::Authentication::Store::LDAP::User;
 use Net::LDAP;
 use Catalyst::Utils ();
+use Catalyst::Exception;
 
 BEGIN {
     __PACKAGE__->mk_accessors(
@@ -88,6 +90,7 @@ BEGIN {
             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
             )
     );
 }
@@ -118,12 +121,16 @@ sub new {
     $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;
@@ -385,7 +392,7 @@ objects that match it's criteria.
 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;
@@ -443,15 +450,28 @@ sub user_supports {
     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;
index 1451c64..547e7c9 100644 (file)
@@ -49,6 +49,7 @@ use base qw( Catalyst::Authentication::User Class::Accessor::Fast );
 use strict;
 use warnings;
 use Scalar::Util qw/refaddr/;
+use Net::LDAP::Entry;
 
 our $VERSION = '1.015';
 
@@ -74,11 +75,11 @@ Returns a L<Catalyst::Authentication::Store::LDAP::User> object.
 =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
@@ -164,12 +165,28 @@ sub roles {
 
 =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;
 }
 
index 3683373..5412ab6 100644 (file)
@@ -2,7 +2,6 @@
 
 use strict;
 use warnings;
-use Catalyst::Exception;
 
 use Test::More;
 use lib 't/lib';
index ebfce1f..3adc314 100644 (file)
@@ -2,7 +2,6 @@
 
 use strict;
 use warnings;
-use Catalyst::Exception;
 
 use Test::More;
 use lib 't/lib';
index 8689024..d0601fe 100644 (file)
@@ -2,7 +2,6 @@
 
 use strict;
 use warnings;
-use Catalyst::Exception;
 
 use Test::More;
 use lib 't/lib';
@@ -10,9 +9,6 @@ use LDAPTest;
 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");
index 54d79c9..01cf244 100644 (file)
@@ -2,15 +2,11 @@
 
 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");
index d4a4a43..8f564db 100644 (file)
@@ -2,7 +2,6 @@
 
 use strict;
 use warnings;
-use Catalyst::Exception;
 
 use Test::More;
 use Test::MockObject::Extends;
@@ -10,11 +9,46 @@ use Test::Exception;
 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) {
 
diff --git a/t/20-persist_in_session.t b/t/20-persist_in_session.t
new file mode 100644 (file)
index 0000000..c28c6ab
--- /dev/null
@@ -0,0 +1,87 @@
+#!/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;
index c4d669d..8cbaba3 100644 (file)
@@ -2,7 +2,6 @@
 # vim: ts=8 sts=4 et sw=4 sr sta
 use strict;
 use warnings;
-use Catalyst::Exception;
 
 use Test::More;