5 use BookStack\Access\Ldap;
6 use BookStack\Access\LdapService;
7 use BookStack\Exceptions\LdapException;
8 use BookStack\Users\Models\Role;
9 use BookStack\Users\Models\User;
10 use Illuminate\Testing\TestResponse;
11 use Mockery\MockInterface;
14 class LdapTest extends TestCase
16 protected MockInterface $mockLdap;
18 protected User $mockUser;
19 protected string $resourceId = 'resource-test';
21 protected function setUp(): void
24 if (!defined('LDAP_OPT_REFERRALS')) {
25 define('LDAP_OPT_REFERRALS', 1);
28 'auth.method' => 'ldap',
29 'auth.defaults.guard' => 'ldap',
30 'services.ldap.base_dn' => 'dc=ldap,dc=local',
31 'services.ldap.email_attribute' => 'mail',
32 'services.ldap.display_name_attribute' => 'cn',
33 'services.ldap.id_attribute' => 'uid',
34 'services.ldap.user_to_groups' => false,
35 'services.ldap.version' => '3',
36 'services.ldap.user_filter' => '(&(uid=${user}))',
37 'services.ldap.follow_referrals' => false,
38 'services.ldap.tls_insecure' => false,
39 'services.ldap.tls_ca_cert' => false,
40 'services.ldap.thumbnail_attribute' => null,
42 $this->mockLdap = $this->mock(Ldap::class);
43 $this->mockUser = User::factory()->make();
46 protected function runFailedAuthLogin()
48 $this->commonLdapMocks(1, 1, 1, 1, 1);
49 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
50 ->andReturn(['count' => 0]);
51 $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
54 protected function mockEscapes($times = 1)
56 $this->mockLdap->shouldReceive('escape')->times($times)->andReturnUsing(function ($val) {
57 return ldap_escape($val);
61 protected function mockExplodes($times = 1)
63 $this->mockLdap->shouldReceive('explodeDn')->times($times)->andReturnUsing(function ($dn, $withAttrib) {
64 return ldap_explode_dn($dn, $withAttrib);
68 protected function mockUserLogin(?string $email = null): TestResponse
70 return $this->post('/login', [
71 'username' => $this->mockUser->name,
72 'password' => $this->mockUser->password,
73 ] + ($email ? ['email' => $email] : []));
77 * Set LDAP method mocks for things we commonly call without altering.
79 protected function commonLdapMocks(int $connects = 1, int $versions = 1, int $options = 2, int $binds = 4, int $escapes = 2, int $explodes = 0)
81 $this->mockLdap->shouldReceive('connect')->times($connects)->andReturn($this->resourceId);
82 $this->mockLdap->shouldReceive('setVersion')->times($versions);
83 $this->mockLdap->shouldReceive('setOption')->times($options);
84 $this->mockLdap->shouldReceive('bind')->times($binds)->andReturn(true);
85 $this->mockEscapes($escapes);
86 $this->mockExplodes($explodes);
89 public function test_login()
91 $this->commonLdapMocks(1, 1, 2, 4, 2);
92 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
93 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
94 ->andReturn(['count' => 1, 0 => [
95 'uid' => [$this->mockUser->name],
96 'cn' => [$this->mockUser->name],
97 'dn' => 'dc=test' . config('services.ldap.base_dn'),
100 $resp = $this->mockUserLogin();
101 $resp->assertRedirect('/login');
102 $resp = $this->followRedirects($resp);
103 $resp->assertSee('Please enter an email to use for this account.');
104 $resp->assertSee($this->mockUser->name);
106 $resp = $this->followingRedirects()->mockUserLogin($this->mockUser->email);
107 $this->withHtml($resp)->assertElementExists('#home-default');
108 $resp->assertSee($this->mockUser->name);
109 $this->assertDatabaseHas('users', [
110 'email' => $this->mockUser->email,
111 'email_confirmed' => false,
112 'external_auth_id' => $this->mockUser->name,
116 public function test_email_domain_restriction_active_on_new_ldap_login()
119 'registration-restrict' => 'testing.com',
122 $this->commonLdapMocks(1, 1, 2, 4, 2);
123 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
124 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
125 ->andReturn(['count' => 1, 0 => [
126 'uid' => [$this->mockUser->name],
127 'cn' => [$this->mockUser->name],
128 'dn' => 'dc=test' . config('services.ldap.base_dn'),
131 $resp = $this->mockUserLogin();
132 $resp->assertRedirect('/login');
133 $this->followRedirects($resp)->assertSee('Please enter an email to use for this account.');
136 $resp = $this->mockUserLogin($email);
137 $resp->assertRedirect('/login');
138 $this->followRedirects($resp)->assertSee('That email domain does not have access to this application');
140 $this->assertDatabaseMissing('users', ['email' => $email]);
143 public function test_login_works_when_no_uid_provided_by_ldap_server()
145 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
147 $this->commonLdapMocks(1, 1, 1, 2, 1);
148 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
149 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
150 ->andReturn(['count' => 1, 0 => [
151 'cn' => [$this->mockUser->name],
153 'mail' => [$this->mockUser->email],
156 $resp = $this->mockUserLogin();
157 $resp->assertRedirect('/');
158 $this->followRedirects($resp)->assertSee($this->mockUser->name);
159 $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $ldapDn]);
162 public function test_a_custom_uid_attribute_can_be_specified_and_is_used_properly()
164 config()->set(['services.ldap.id_attribute' => 'my_custom_id']);
166 $this->commonLdapMocks(1, 1, 1, 2, 1);
167 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
168 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
169 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
170 ->andReturn(['count' => 1, 0 => [
171 'cn' => [$this->mockUser->name],
173 'my_custom_id' => ['cooluser456'],
174 'mail' => [$this->mockUser->email],
177 $resp = $this->mockUserLogin();
178 $resp->assertRedirect('/');
179 $this->followRedirects($resp)->assertSee($this->mockUser->name);
180 $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => 'cooluser456']);
183 public function test_initial_incorrect_credentials()
185 $this->commonLdapMocks(1, 1, 1, 0, 1);
186 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
187 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
188 ->andReturn(['count' => 1, 0 => [
189 'uid' => [$this->mockUser->name],
190 'cn' => [$this->mockUser->name],
191 'dn' => 'dc=test' . config('services.ldap.base_dn'),
193 $this->mockLdap->shouldReceive('bind')->times(2)->andReturn(true, false);
195 $resp = $this->mockUserLogin();
196 $resp->assertRedirect('/login');
197 $this->followRedirects($resp)->assertSee('These credentials do not match our records.');
198 $this->assertDatabaseMissing('users', ['external_auth_id' => $this->mockUser->name]);
201 public function test_login_not_found_username()
203 $this->commonLdapMocks(1, 1, 1, 1, 1);
204 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
205 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
206 ->andReturn(['count' => 0]);
208 $resp = $this->mockUserLogin();
209 $resp->assertRedirect('/login');
210 $this->followRedirects($resp)->assertSee('These credentials do not match our records.');
211 $this->assertDatabaseMissing('users', ['external_auth_id' => $this->mockUser->name]);
214 public function test_create_user_form()
216 $userForm = $this->asAdmin()->get('/settings/users/create');
217 $userForm->assertDontSee('Password');
219 $save = $this->post('/settings/users/create', [
220 'name' => $this->mockUser->name,
221 'email' => $this->mockUser->email,
223 $save->assertSessionHasErrors(['external_auth_id' => 'The external auth id field is required.']);
225 $save = $this->post('/settings/users/create', [
226 'name' => $this->mockUser->name,
227 'email' => $this->mockUser->email,
228 'external_auth_id' => $this->mockUser->name,
230 $save->assertRedirect('/settings/users');
231 $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'external_auth_id' => $this->mockUser->name, 'email_confirmed' => true]);
234 public function test_user_edit_form()
236 $editUser = $this->users->viewer();
237 $editPage = $this->asAdmin()->get("/settings/users/{$editUser->id}");
238 $editPage->assertSee('Edit User');
239 $editPage->assertDontSee('Password');
241 $update = $this->put("/settings/users/{$editUser->id}", [
242 'name' => $editUser->name,
243 'email' => $editUser->email,
244 'external_auth_id' => 'test_auth_id',
246 $update->assertRedirect('/settings/users');
247 $this->assertDatabaseHas('users', ['email' => $editUser->email, 'external_auth_id' => 'test_auth_id']);
250 public function test_registration_disabled()
252 $resp = $this->followingRedirects()->get('/register');
253 $this->withHtml($resp)->assertElementContains('#content', 'Log In');
256 public function test_non_admins_cannot_change_auth_id()
258 $testUser = $this->users->viewer();
259 $this->actingAs($testUser)
260 ->get('/settings/users/' . $testUser->id)
261 ->assertDontSee('External Authentication');
264 public function test_login_maps_roles_and_retains_existing_roles()
266 $roleToReceive = Role::factory()->create(['display_name' => 'LdapTester']);
267 $roleToReceive2 = Role::factory()->create(['display_name' => 'LdapTester Second']);
268 $existingRole = Role::factory()->create(['display_name' => 'ldaptester-existing']);
269 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
270 $this->mockUser->attachRole($existingRole);
273 'services.ldap.user_to_groups' => true,
274 'services.ldap.group_attribute' => 'memberOf',
275 'services.ldap.remove_from_groups' => false,
278 $this->commonLdapMocks(1, 1, 4, 5, 4, 6);
279 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
280 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
281 ->andReturn(['count' => 1, 0 => [
282 'uid' => [$this->mockUser->name],
283 'cn' => [$this->mockUser->name],
284 'dn' => 'dc=test' . config('services.ldap.base_dn'),
285 'mail' => [$this->mockUser->email],
288 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
289 1 => 'cn=ldaptester-second,ou=groups,dc=example,dc=com',
293 $this->mockUserLogin()->assertRedirect('/');
295 $user = User::where('email', $this->mockUser->email)->first();
296 $this->assertDatabaseHas('role_user', [
297 'user_id' => $user->id,
298 'role_id' => $roleToReceive->id,
300 $this->assertDatabaseHas('role_user', [
301 'user_id' => $user->id,
302 'role_id' => $roleToReceive2->id,
304 $this->assertDatabaseHas('role_user', [
305 'user_id' => $user->id,
306 'role_id' => $existingRole->id,
310 public function test_login_maps_roles_and_removes_old_roles_if_set()
312 $roleToReceive = Role::factory()->create(['display_name' => 'LdapTester']);
313 $existingRole = Role::factory()->create(['display_name' => 'ldaptester-existing']);
314 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
315 $this->mockUser->attachRole($existingRole);
318 'services.ldap.user_to_groups' => true,
319 'services.ldap.group_attribute' => 'memberOf',
320 'services.ldap.remove_from_groups' => true,
323 $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
324 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(3)
325 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
326 ->andReturn(['count' => 1, 0 => [
327 'uid' => [$this->mockUser->name],
328 'cn' => [$this->mockUser->name],
329 'dn' => 'dc=test' . config('services.ldap.base_dn'),
330 'mail' => [$this->mockUser->email],
333 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
337 $this->mockUserLogin()->assertRedirect('/');
339 $user = User::query()->where('email', $this->mockUser->email)->first();
340 $this->assertDatabaseHas('role_user', [
341 'user_id' => $user->id,
342 'role_id' => $roleToReceive->id,
344 $this->assertDatabaseMissing('role_user', [
345 'user_id' => $user->id,
346 'role_id' => $existingRole->id,
350 public function test_dump_user_groups_shows_group_related_details_as_json()
353 'services.ldap.user_to_groups' => true,
354 'services.ldap.group_attribute' => 'memberOf',
355 'services.ldap.remove_from_groups' => true,
356 'services.ldap.dump_user_groups' => true,
359 $userResp = ['count' => 1, 0 => [
360 'uid' => [$this->mockUser->name],
361 'cn' => [$this->mockUser->name],
362 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
363 'mail' => [$this->mockUser->email],
365 $this->commonLdapMocks(1, 1, 4, 5, 4, 2);
366 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
367 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
368 ->andReturn($userResp, ['count' => 1,
370 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
373 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
379 'dn' => 'cn=ldaptester,ou=groups,dc=example,dc=com',
382 0 => 'cn=monsters,ou=groups,dc=example,dc=com',
387 $resp = $this->mockUserLogin();
389 'details_from_ldap' => [
390 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
392 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
396 'parsed_direct_user_groups' => [
399 'parsed_recursive_user_groups' => [
406 public function test_external_auth_id_visible_in_roles_page_when_ldap_active()
408 $role = Role::factory()->create(['display_name' => 'ldaptester', 'external_auth_id' => 'ex-auth-a, test-second-param']);
409 $this->asAdmin()->get('/settings/roles/' . $role->id)
410 ->assertSee('ex-auth-a');
413 public function test_login_maps_roles_using_external_auth_ids_if_set()
415 $roleToReceive = Role::factory()->create(['display_name' => 'ldaptester', 'external_auth_id' => 'test-second-param, ex-auth-a']);
416 $roleToNotReceive = Role::factory()->create(['display_name' => 'ex-auth-a', 'external_auth_id' => 'test-second-param']);
419 'services.ldap.user_to_groups' => true,
420 'services.ldap.group_attribute' => 'memberOf',
421 'services.ldap.remove_from_groups' => true,
424 $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
425 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(3)
426 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
427 ->andReturn(['count' => 1, 0 => [
428 'uid' => [$this->mockUser->name],
429 'cn' => [$this->mockUser->name],
430 'dn' => 'dc=test' . config('services.ldap.base_dn'),
431 'mail' => [$this->mockUser->email],
434 0 => 'cn=ex-auth-a,ou=groups,dc=example,dc=com',
438 $this->mockUserLogin()->assertRedirect('/');
440 $user = User::query()->where('email', $this->mockUser->email)->first();
441 $this->assertDatabaseHas('role_user', [
442 'user_id' => $user->id,
443 'role_id' => $roleToReceive->id,
445 $this->assertDatabaseMissing('role_user', [
446 'user_id' => $user->id,
447 'role_id' => $roleToNotReceive->id,
451 public function test_login_group_mapping_does_not_conflict_with_default_role()
453 $roleToReceive = Role::factory()->create(['display_name' => 'LdapTester']);
454 $roleToReceive2 = Role::factory()->create(['display_name' => 'LdapTester Second']);
455 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
457 setting()->put('registration-role', $roleToReceive->id);
460 'services.ldap.user_to_groups' => true,
461 'services.ldap.group_attribute' => 'memberOf',
462 'services.ldap.remove_from_groups' => true,
465 $this->commonLdapMocks(1, 1, 4, 5, 4, 6);
466 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
467 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
468 ->andReturn(['count' => 1, 0 => [
469 'uid' => [$this->mockUser->name],
470 'cn' => [$this->mockUser->name],
471 'dn' => 'dc=test' . config('services.ldap.base_dn'),
472 'mail' => [$this->mockUser->email],
475 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
476 1 => 'cn=ldaptester-second,ou=groups,dc=example,dc=com',
480 $this->mockUserLogin()->assertRedirect('/');
482 $user = User::query()->where('email', $this->mockUser->email)->first();
483 $this->assertDatabaseHas('role_user', [
484 'user_id' => $user->id,
485 'role_id' => $roleToReceive->id,
487 $this->assertDatabaseHas('role_user', [
488 'user_id' => $user->id,
489 'role_id' => $roleToReceive2->id,
493 public function test_login_uses_specified_display_name_attribute()
496 'services.ldap.display_name_attribute' => 'displayName',
499 $this->commonLdapMocks(1, 1, 2, 4, 2);
500 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
501 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
502 ->andReturn(['count' => 1, 0 => [
503 'uid' => [$this->mockUser->name],
504 'cn' => [$this->mockUser->name],
505 'dn' => 'dc=test' . config('services.ldap.base_dn'),
506 'displayname' => 'displayNameAttribute',
509 $this->mockUserLogin()->assertRedirect('/login');
510 $this->get('/login')->assertSee('Please enter an email to use for this account.');
512 $resp = $this->mockUserLogin($this->mockUser->email);
513 $resp->assertRedirect('/');
514 $this->get('/')->assertSee('displayNameAttribute');
515 $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name, 'name' => 'displayNameAttribute']);
518 public function test_login_uses_default_display_name_attribute_if_specified_not_present()
521 'services.ldap.display_name_attribute' => 'displayName',
524 $this->commonLdapMocks(1, 1, 2, 4, 2);
525 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
526 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
527 ->andReturn(['count' => 1, 0 => [
528 'uid' => [$this->mockUser->name],
529 'cn' => [$this->mockUser->name],
530 'dn' => 'dc=test' . config('services.ldap.base_dn'),
533 $this->mockUserLogin()->assertRedirect('/login');
534 $this->get('/login')->assertSee('Please enter an email to use for this account.');
536 $resp = $this->mockUserLogin($this->mockUser->email);
537 $resp->assertRedirect('/');
538 $this->get('/')->assertSee($this->mockUser->name);
539 $this->assertDatabaseHas('users', [
540 'email' => $this->mockUser->email,
541 'email_confirmed' => false,
542 'external_auth_id' => $this->mockUser->name,
543 'name' => $this->mockUser->name,
547 protected function checkLdapReceivesCorrectDetails($serverString, $expectedHostString): void
549 app('config')->set(['services.ldap.server' => $serverString]);
551 $this->mockLdap->shouldReceive('connect')
553 ->with($expectedHostString)
556 $this->mockUserLogin();
559 public function test_ldap_receives_correct_connect_host_from_config()
561 $expectedResultByInput = [
562 'ldaps://bookstack:8080' => 'ldaps://bookstack:8080',
563 'ldap.bookstack.com:8080' => 'ldap://ldap.bookstack.com:8080',
564 'ldap.bookstack.com' => 'ldap://ldap.bookstack.com',
565 'ldaps://ldap.bookstack.com' => 'ldaps://ldap.bookstack.com',
566 'ldaps://ldap.bookstack.com ldap://a.b.com' => 'ldaps://ldap.bookstack.com ldap://a.b.com',
569 foreach ($expectedResultByInput as $input => $expectedResult) {
570 $this->checkLdapReceivesCorrectDetails($input, $expectedResult);
571 $this->refreshApplication();
576 public function test_forgot_password_routes_inaccessible()
578 $resp = $this->get('/password/email');
579 $this->assertPermissionError($resp);
581 $resp = $this->post('/password/email');
582 $this->assertPermissionError($resp);
584 $resp = $this->get('/password/reset/abc123');
585 $this->assertPermissionError($resp);
587 $resp = $this->post('/password/reset');
588 $this->assertPermissionError($resp);
591 public function test_user_invite_routes_inaccessible()
593 $resp = $this->get('/register/invite/abc123');
594 $this->assertPermissionError($resp);
596 $resp = $this->post('/register/invite/abc123');
597 $this->assertPermissionError($resp);
600 public function test_user_register_routes_inaccessible()
602 $resp = $this->get('/register');
603 $this->assertPermissionError($resp);
605 $resp = $this->post('/register');
606 $this->assertPermissionError($resp);
609 public function test_dump_user_details_option_works()
611 config()->set(['services.ldap.dump_user_details' => true, 'services.ldap.thumbnail_attribute' => 'jpegphoto']);
613 $this->commonLdapMocks(1, 1, 1, 1, 1);
614 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
615 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
616 ->andReturn(['count' => 1, 0 => [
617 'uid' => [$this->mockUser->name],
618 'cn' => [$this->mockUser->name],
619 // Test dumping binary data for avatar responses
620 'jpegphoto' => base64_decode('/9j/4AAQSkZJRg=='),
621 'dn' => 'dc=test' . config('services.ldap.base_dn'),
624 $resp = $this->post('/login', [
625 'username' => $this->mockUser->name,
626 'password' => $this->mockUser->password,
628 $resp->assertJsonStructure([
629 'details_from_ldap' => [],
630 'details_bookstack_parsed' => [],
634 public function test_start_tls_called_if_option_set()
636 config()->set(['services.ldap.start_tls' => true]);
637 $this->mockLdap->shouldReceive('startTls')->once()->andReturn(true);
638 $this->runFailedAuthLogin();
641 public function test_connection_fails_if_tls_fails()
643 config()->set(['services.ldap.start_tls' => true]);
644 $this->mockLdap->shouldReceive('startTls')->once()->andReturn(false);
645 $this->commonLdapMocks(1, 1, 0, 0, 0);
646 $resp = $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
647 $resp->assertStatus(500);
650 public function test_ldap_attributes_can_be_binary_decoded_if_marked()
652 config()->set(['services.ldap.id_attribute' => 'BIN;uid']);
653 $ldapService = app()->make(LdapService::class);
654 $this->commonLdapMocks(1, 1, 1, 1, 1);
655 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
656 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), ['cn', 'dn', 'uid', 'mail', 'cn'])
657 ->andReturn(['count' => 1, 0 => [
658 'uid' => [hex2bin('FFF8F7')],
659 'cn' => [$this->mockUser->name],
660 'dn' => 'dc=test' . config('services.ldap.base_dn'),
663 $details = $ldapService->getUserDetails('test');
664 $this->assertEquals('fff8f7', $details['uid']);
667 public function test_new_ldap_user_login_with_already_used_email_address_shows_error_message_to_user()
669 $this->commonLdapMocks(1, 1, 2, 4, 2);
670 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
671 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
672 ->andReturn(['count' => 1, 0 => [
673 'uid' => [$this->mockUser->name],
674 'cn' => [$this->mockUser->name],
675 'dn' => 'dc=test' . config('services.ldap.base_dn'),
677 ]], ['count' => 1, 0 => [
680 'dn' => 'dc=bscott' . config('services.ldap.base_dn'),
685 $this->mockUserLogin()->assertRedirect('/');
689 $resp = $this->followingRedirects()->post('/login', ['username' => 'bscott', 'password' => 'pass']);
690 $resp->assertSee('A user with the email
[email protected] already exists but with different credentials');
693 public function test_login_with_email_confirmation_required_maps_groups_but_shows_confirmation_screen()
695 $roleToReceive = Role::factory()->create(['display_name' => 'LdapTester']);
696 $user = User::factory()->make();
697 setting()->put('registration-confirmation', 'true');
700 'services.ldap.user_to_groups' => true,
701 'services.ldap.group_attribute' => 'memberOf',
702 'services.ldap.remove_from_groups' => true,
705 $this->commonLdapMocks(1, 1, 6, 8, 6, 4);
706 $this->mockLdap->shouldReceive('searchAndGetEntries')
708 ->andReturn(['count' => 1, 0 => [
709 'uid' => [$user->name],
710 'cn' => [$user->name],
711 'dn' => 'dc=test' . config('services.ldap.base_dn'),
712 'mail' => [$user->email],
715 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
719 $login = $this->followingRedirects()->mockUserLogin();
720 $login->assertSee('Thanks for registering!');
721 $this->assertDatabaseHas('users', [
722 'email' => $user->email,
723 'email_confirmed' => false,
726 $user = User::query()->where('email', '=', $user->email)->first();
727 $this->assertDatabaseHas('role_user', [
728 'user_id' => $user->id,
729 'role_id' => $roleToReceive->id,
732 $this->assertNull(auth()->user());
734 $homePage = $this->get('/');
735 $homePage->assertRedirect('/login');
737 $login = $this->followingRedirects()->mockUserLogin();
738 $login->assertSee('Email Address Not Confirmed');
741 public function test_failed_logins_are_logged_when_message_configured()
743 $log = $this->withTestLogger();
744 config()->set(['logging.failed_login.message' => 'Failed login for %u']);
745 $this->runFailedAuthLogin();
746 $this->assertTrue($log->hasWarningThatContains('Failed login for timmyjenkins'));
749 public function test_thumbnail_attribute_used_as_user_avatar_if_configured()
751 config()->set(['services.ldap.thumbnail_attribute' => 'jpegPhoto']);
753 $this->commonLdapMocks(1, 1, 1, 2, 1);
754 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
755 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
756 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
757 ->andReturn(['count' => 1, 0 => [
758 'cn' => [$this->mockUser->name],
760 'jpegphoto' => [base64_decode('/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8Q
761 EBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=')],
762 'mail' => [$this->mockUser->email],
765 $this->mockUserLogin()
766 ->assertRedirect('/');
768 $user = User::query()->where('email', '=', $this->mockUser->email)->first();
769 $this->assertNotNull($user->avatar);
770 $this->assertEquals('8c90748342f19b195b9c6b4eff742ded', md5_file(public_path($user->avatar->path)));
773 public function test_tls_ca_cert_option_throws_if_set_to_invalid_location()
775 $path = 'non_found_' . time();
776 config()->set(['services.ldap.tls_ca_cert' => $path]);
778 $this->commonLdapMocks(0, 0, 0, 0, 0);
780 $this->assertThrows(function () {
781 $this->withoutExceptionHandling()->mockUserLogin();
782 }, LdapException::class, "Provided path [{$path}] for LDAP TLS CA certs could not be resolved to an existing location");
785 public function test_tls_ca_cert_option_used_if_set_to_a_folder()
787 $path = $this->files->testFilePath('');
788 config()->set(['services.ldap.tls_ca_cert' => $path]);
790 $this->mockLdap->shouldReceive('setOption')->once()->with(null, LDAP_OPT_X_TLS_CACERTDIR, rtrim($path, '/'))->andReturn(true);
791 $this->runFailedAuthLogin();
794 public function test_tls_ca_cert_option_used_if_set_to_a_file()
796 $path = $this->files->testFilePath('test-file.txt');
797 config()->set(['services.ldap.tls_ca_cert' => $path]);
799 $this->mockLdap->shouldReceive('setOption')->once()->with(null, LDAP_OPT_X_TLS_CACERTFILE, $path)->andReturn(true);
800 $this->runFailedAuthLogin();