2 use BookStack\Auth\Role;
3 use BookStack\Auth\Access\Ldap;
4 use BookStack\Auth\User;
5 use Mockery\MockInterface;
7 class LdapTest extends BrowserKitTest
16 protected $resourceId = 'resource-test';
18 public function setUp()
21 if (!defined('LDAP_OPT_REFERRALS')) define('LDAP_OPT_REFERRALS', 1);
23 'auth.method' => 'ldap',
24 'services.ldap.base_dn' => 'dc=ldap,dc=local',
25 'services.ldap.email_attribute' => 'mail',
26 'services.ldap.user_to_groups' => false,
27 'auth.providers.users.driver' => 'ldap',
29 $this->mockLdap = \Mockery::mock(Ldap::class);
30 $this->app[Ldap::class] = $this->mockLdap;
31 $this->mockUser = factory(User::class)->make();
34 protected function mockEscapes($times = 1)
36 $this->mockLdap->shouldReceive('escape')->times($times)->andReturnUsing(function($val) {
37 return ldap_escape($val);
41 protected function mockExplodes($times = 1)
43 $this->mockLdap->shouldReceive('explodeDn')->times($times)->andReturnUsing(function($dn, $withAttrib) {
44 return ldap_explode_dn($dn, $withAttrib);
48 public function test_login()
50 $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
51 $this->mockLdap->shouldReceive('setVersion')->once();
52 $this->mockLdap->shouldReceive('setOption')->times(4);
53 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
54 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
55 ->andReturn(['count' => 1, 0 => [
56 'uid' => [$this->mockUser->name],
57 'cn' => [$this->mockUser->name],
58 'dn' => ['dc=test' . config('services.ldap.base_dn')]
60 $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
61 $this->mockEscapes(4);
63 $this->visit('/login')
65 ->type($this->mockUser->name, '#username')
66 ->type($this->mockUser->password, '#password')
68 ->seePageIs('/login')->see('Please enter an email to use for this account.');
70 $this->type($this->mockUser->email, '#email')
73 ->see($this->mockUser->name)
74 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name]);
77 public function test_login_works_when_no_uid_provided_by_ldap_server()
79 $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
80 $this->mockLdap->shouldReceive('setVersion')->once();
81 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
82 $this->mockLdap->shouldReceive('setOption')->times(2);
83 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
84 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
85 ->andReturn(['count' => 1, 0 => [
86 'cn' => [$this->mockUser->name],
88 'mail' => [$this->mockUser->email]
90 $this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true);
91 $this->mockEscapes(2);
93 $this->visit('/login')
95 ->type($this->mockUser->name, '#username')
96 ->type($this->mockUser->password, '#password')
99 ->see($this->mockUser->name)
100 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $ldapDn]);
103 public function test_initial_incorrect_details()
105 $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
106 $this->mockLdap->shouldReceive('setVersion')->once();
107 $this->mockLdap->shouldReceive('setOption')->times(2);
108 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
109 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
110 ->andReturn(['count' => 1, 0 => [
111 'uid' => [$this->mockUser->name],
112 'cn' => [$this->mockUser->name],
113 'dn' => ['dc=test' . config('services.ldap.base_dn')]
115 $this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true, true, false);
116 $this->mockEscapes(2);
118 $this->visit('/login')
120 ->type($this->mockUser->name, '#username')
121 ->type($this->mockUser->password, '#password')
123 ->seePageIs('/login')->see('These credentials do not match our records.')
124 ->dontSeeInDatabase('users', ['external_auth_id' => $this->mockUser->name]);
127 public function test_create_user_form()
129 $this->asAdmin()->visit('/settings/users/create')
130 ->dontSee('Password')
131 ->type($this->mockUser->name, '#name')
132 ->type($this->mockUser->email, '#email')
134 ->see('The external auth id field is required.')
135 ->type($this->mockUser->name, '#external_auth_id')
137 ->seePageIs('/settings/users')
138 ->seeInDatabase('users', ['email' => $this->mockUser->email, 'external_auth_id' => $this->mockUser->name, 'email_confirmed' => true]);
141 public function test_user_edit_form()
143 $editUser = $this->getNormalUser();
144 $this->asAdmin()->visit('/settings/users/' . $editUser->id)
146 ->dontSee('Password')
147 ->type('test_auth_id', '#external_auth_id')
149 ->seePageIs('/settings/users')
150 ->seeInDatabase('users', ['email' => $editUser->email, 'external_auth_id' => 'test_auth_id']);
153 public function test_registration_disabled()
155 $this->visit('/register')
156 ->seePageIs('/login');
159 public function test_non_admins_cannot_change_auth_id()
161 $testUser = $this->getNormalUser();
162 $this->actingAs($testUser)->visit('/settings/users/' . $testUser->id)
163 ->dontSee('External Authentication');
166 public function test_login_maps_roles_and_retains_existing_roles()
168 $roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'display_name' => 'LdapTester']);
169 $roleToReceive2 = factory(Role::class)->create(['name' => 'ldaptester-second', 'display_name' => 'LdapTester Second']);
170 $existingRole = factory(Role::class)->create(['name' => 'ldaptester-existing']);
171 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
172 $this->mockUser->attachRole($existingRole);
175 'services.ldap.user_to_groups' => true,
176 'services.ldap.group_attribute' => 'memberOf',
177 'services.ldap.remove_from_groups' => false,
179 $this->mockLdap->shouldReceive('connect')->times(2)->andReturn($this->resourceId);
180 $this->mockLdap->shouldReceive('setVersion')->times(2);
181 $this->mockLdap->shouldReceive('setOption')->times(5);
182 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(5)
183 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
184 ->andReturn(['count' => 1, 0 => [
185 'uid' => [$this->mockUser->name],
186 'cn' => [$this->mockUser->name],
187 'dn' => ['dc=test' . config('services.ldap.base_dn')],
188 'mail' => [$this->mockUser->email],
191 0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
192 1 => "cn=ldaptester-second,ou=groups,dc=example,dc=com",
195 $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
196 $this->mockEscapes(5);
197 $this->mockExplodes(6);
199 $this->visit('/login')
201 ->type($this->mockUser->name, '#username')
202 ->type($this->mockUser->password, '#password')
206 $user = User::where('email', $this->mockUser->email)->first();
207 $this->seeInDatabase('role_user', [
208 'user_id' => $user->id,
209 'role_id' => $roleToReceive->id
211 $this->seeInDatabase('role_user', [
212 'user_id' => $user->id,
213 'role_id' => $roleToReceive2->id
215 $this->seeInDatabase('role_user', [
216 'user_id' => $user->id,
217 'role_id' => $existingRole->id
221 public function test_login_maps_roles_and_removes_old_roles_if_set()
223 $roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'display_name' => 'LdapTester']);
224 $existingRole = factory(Role::class)->create(['name' => 'ldaptester-existing']);
225 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
226 $this->mockUser->attachRole($existingRole);
229 'services.ldap.user_to_groups' => true,
230 'services.ldap.group_attribute' => 'memberOf',
231 'services.ldap.remove_from_groups' => true,
233 $this->mockLdap->shouldReceive('connect')->times(2)->andReturn($this->resourceId);
234 $this->mockLdap->shouldReceive('setVersion')->times(2);
235 $this->mockLdap->shouldReceive('setOption')->times(4);
236 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
237 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
238 ->andReturn(['count' => 1, 0 => [
239 'uid' => [$this->mockUser->name],
240 'cn' => [$this->mockUser->name],
241 'dn' => ['dc=test' . config('services.ldap.base_dn')],
242 'mail' => [$this->mockUser->email],
245 0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
248 $this->mockLdap->shouldReceive('bind')->times(5)->andReturn(true);
249 $this->mockEscapes(4);
250 $this->mockExplodes(2);
252 $this->visit('/login')
254 ->type($this->mockUser->name, '#username')
255 ->type($this->mockUser->password, '#password')
259 $user = User::where('email', $this->mockUser->email)->first();
260 $this->seeInDatabase('role_user', [
261 'user_id' => $user->id,
262 'role_id' => $roleToReceive->id
264 $this->dontSeeInDatabase('role_user', [
265 'user_id' => $user->id,
266 'role_id' => $existingRole->id
270 public function test_external_auth_id_visible_in_roles_page_when_ldap_active()
272 $role = factory(Role::class)->create(['name' => 'ldaptester', 'external_auth_id' => 'ex-auth-a, test-second-param']);
273 $this->asAdmin()->visit('/settings/roles/' . $role->id)
277 public function test_login_maps_roles_using_external_auth_ids_if_set()
279 $roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'external_auth_id' => 'test-second-param, ex-auth-a']);
280 $roleToNotReceive = factory(Role::class)->create(['name' => 'ldaptester-not-receive', 'display_name' => 'ex-auth-a', 'external_auth_id' => 'test-second-param']);
283 'services.ldap.user_to_groups' => true,
284 'services.ldap.group_attribute' => 'memberOf',
285 'services.ldap.remove_from_groups' => true,
287 $this->mockLdap->shouldReceive('connect')->times(2)->andReturn($this->resourceId);
288 $this->mockLdap->shouldReceive('setVersion')->times(2);
289 $this->mockLdap->shouldReceive('setOption')->times(4);
290 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
291 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
292 ->andReturn(['count' => 1, 0 => [
293 'uid' => [$this->mockUser->name],
294 'cn' => [$this->mockUser->name],
295 'dn' => ['dc=test' . config('services.ldap.base_dn')],
296 'mail' => [$this->mockUser->email],
299 0 => "cn=ex-auth-a,ou=groups,dc=example,dc=com",
302 $this->mockLdap->shouldReceive('bind')->times(5)->andReturn(true);
303 $this->mockEscapes(4);
304 $this->mockExplodes(2);
306 $this->visit('/login')
308 ->type($this->mockUser->name, '#username')
309 ->type($this->mockUser->password, '#password')
313 $user = User::where('email', $this->mockUser->email)->first();
314 $this->seeInDatabase('role_user', [
315 'user_id' => $user->id,
316 'role_id' => $roleToReceive->id
318 $this->dontSeeInDatabase('role_user', [
319 'user_id' => $user->id,
320 'role_id' => $roleToNotReceive->id
324 public function test_login_group_mapping_does_not_conflict_with_default_role()
326 $roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'display_name' => 'LdapTester']);
327 $roleToReceive2 = factory(Role::class)->create(['name' => 'ldaptester-second', 'display_name' => 'LdapTester Second']);
328 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
330 setting()->put('registration-role', $roleToReceive->id);
333 'services.ldap.user_to_groups' => true,
334 'services.ldap.group_attribute' => 'memberOf',
335 'services.ldap.remove_from_groups' => true,
337 $this->mockLdap->shouldReceive('connect')->times(2)->andReturn($this->resourceId);
338 $this->mockLdap->shouldReceive('setVersion')->times(2);
339 $this->mockLdap->shouldReceive('setOption')->times(5);
340 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(5)
341 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
342 ->andReturn(['count' => 1, 0 => [
343 'uid' => [$this->mockUser->name],
344 'cn' => [$this->mockUser->name],
345 'dn' => ['dc=test' . config('services.ldap.base_dn')],
346 'mail' => [$this->mockUser->email],
349 0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
350 1 => "cn=ldaptester-second,ou=groups,dc=example,dc=com",
353 $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
354 $this->mockEscapes(5);
355 $this->mockExplodes(6);
357 $this->visit('/login')
359 ->type($this->mockUser->name, '#username')
360 ->type($this->mockUser->password, '#password')
364 $user = User::where('email', $this->mockUser->email)->first();
365 $this->seeInDatabase('role_user', [
366 'user_id' => $user->id,
367 'role_id' => $roleToReceive->id
369 $this->seeInDatabase('role_user', [
370 'user_id' => $user->id,
371 'role_id' => $roleToReceive2->id