1#!/usr/bin/perl
2
3use strict;
4use warnings;
5
6use Test::More;
7use Test::MockObject::Extends;
8use Test::Exception;
9use Net::LDAP::Entry;
10use lib 't/lib';
11
12use_ok("Catalyst::Authentication::Store::LDAP::Backend");
13
14
15my $back_without_use_roles = Catalyst::Authentication::Store::LDAP::Backend->new({
16    ldap_server => 'ldap://127.0.0.1:555',
17    binddn      => 'anonymous',
18    bindpw      => 'dontcarehow',
19    user_basedn => 'ou=foobar',
20    user_filter => '(&(objectClass=inetOrgPerson)(uid=%s))',
21    user_scope  => 'one',
22    user_field  => 'uid',
23});
24is $back_without_use_roles->use_roles, 1, 'use_roles enabled be default';
25
26my $back_with_use_roles_disabled = Catalyst::Authentication::Store::LDAP::Backend->new({
27    ldap_server => 'ldap://127.0.0.1:555',
28    binddn      => 'anonymous',
29    bindpw      => 'dontcarehow',
30    user_basedn => 'ou=foobar',
31    user_filter => '(&(objectClass=inetOrgPerson)(uid=%s))',
32    user_scope  => 'one',
33    user_field  => 'uid',
34    use_roles   => 0,
35});
36is $back_with_use_roles_disabled->use_roles, 0, 'use_roles disabled when set
37to 0';
38
39my $back_with_use_roles_enabled = Catalyst::Authentication::Store::LDAP::Backend->new({
40    ldap_server => 'ldap://127.0.0.1:555',
41    binddn      => 'anonymous',
42    bindpw      => 'dontcarehow',
43    user_basedn => 'ou=foobar',
44    user_filter => '(&(objectClass=inetOrgPerson)(uid=%s))',
45    user_scope  => 'one',
46    user_field  => 'uid',
47    use_roles   => 1,
48});
49is $back_with_use_roles_enabled->use_roles, 1, 'use_roles enabled when set to
501';
51
52my (@searches, @binds);
53for my $i (0..1) {
54
55    my $back = Catalyst::Authentication::Store::LDAP::Backend->new({
56        'ldap_server' => 'ldap://127.0.0.1:555',
57        'binddn'      => 'anonymous',
58        'bindpw'      => 'dontcarehow',
59        'start_tls'   => 0,
60        'user_basedn' => 'ou=foobar',
61        'user_filter' => '(&(objectClass=inetOrgPerson)(uid=%s))',
62        'user_scope'  => 'one',
63        'user_field'  => 'uid',
64        'use_roles'   => 1,
65        'role_basedn' => 'ou=roles',
66        'role_filter' => '(&(objectClass=posixGroup)(memberUid=%s))',
67        'role_scope'  => 'one',
68        'role_field'  => 'userinrole',
69        'role_value'  => 'cn',
70        'role_search_as_user' => $i,
71    });
72    $back = Test::MockObject::Extends->new($back);
73    my $bind_msg = Test::MockObject->new;
74    $bind_msg->mock(is_error => sub {}); # Cause bind call to always succeed
75    my $ldap = Test::MockObject->new;
76    $ldap->mock('bind', sub { shift; push (@binds, [@_]); return $bind_msg});
77    $ldap->mock('unbind' => sub {});
78    $ldap->mock('disconnect' => sub {});
79    my $search_res = Test::MockObject->new();
80    my $search_is_error = 0;
81    $search_res->mock(is_error => sub { $search_is_error });
82    $search_res->mock(entries => sub {
83        return map
84            {   my $id = $_;
85                Test::MockObject->new->mock(
86                    get_value => sub { "quux$id" }
87                )
88            }
89            qw/one two/
90    });
91    my @user_entries;
92    $search_res->mock(pop_entry => sub { return pop @user_entries });
93    $ldap->mock('search', sub { shift; push(@searches, [@_]); return $search_res; });
94    $back->mock('ldap_connect' => sub { $ldap });
95    my $user_entry = Net::LDAP::Entry->new;
96    push(@user_entries, $user_entry);
97    $user_entry->dn('ou=foobar');
98    $user_entry->add(
99        uid => 'somebody',
100        cn => 'test',
101    );
102    my $user = $back->find_user( { username => 'somebody' } );
103    isa_ok( $user, "Catalyst::Authentication::Store::LDAP::User" );
104    $user->check_password('password');
105    is_deeply( [sort $user->roles],
106               [sort qw/quuxone quuxtwo/],
107                "User has the expected set of roles" );
108
109    $search_is_error = 1;
110    lives_ok {
111        ok !$back->find_user( { username => 'doesnotexist' } ),
112            'Nonexistent user returns undef';
113    } 'No exception thrown for nonexistent user';
114
115}
116is_deeply(\@searches, [
117    ['base', 'ou=foobar', 'filter', '(&(objectClass=inetOrgPerson)(uid=somebody))', 'scope', 'one'],
118    ['base', 'ou=roles', 'filter', '(&(objectClass=posixGroup)(memberUid=test))', 'scope', 'one', 'attrs', [ 'userinrole' ]],
119    ['base', 'ou=foobar', 'filter', '(&(objectClass=inetOrgPerson)(uid=doesnotexist))', 'scope', 'one'],
120    ['base', 'ou=foobar', 'filter', '(&(objectClass=inetOrgPerson)(uid=somebody))', 'scope', 'one'],
121    ['base', 'ou=roles', 'filter', '(&(objectClass=posixGroup)(memberUid=test))', 'scope', 'one', 'attrs', [ 'userinrole' ]],
122    ['base', 'ou=foobar', 'filter', '(&(objectClass=inetOrgPerson)(uid=doesnotexist))', 'scope', 'one'],
123], 'User searches as expected');
124is_deeply(\@binds, [
125    [ undef ], # First user search
126    [
127        'ou=foobar',
128        'password',
129        'password'
130    ], # Rebind to confirm user
131    [
132        undef
133    ], # Rebind with initial credentials to find roles
134    [ undef ], # Second user search
135    # 2nd pass round main loop
136    [  undef ], # First user search
137    [
138        'ou=foobar',
139        'password',
140        'password'
141    ], # Rebind to confirm user
142    [
143        'ou=foobar',
144        'password',
145        'password'
146    ], # Rebind with user credentials to find roles
147    [ undef ], # Second user search
148], 'Binds as expected');
149
150done_testing;
151