5 use BookStack\Access\Ldap;
6 use BookStack\Access\LdapService;
7 use BookStack\Users\Models\Role;
8 use BookStack\Users\Models\User;
9 use Illuminate\Testing\TestResponse;
10 use Mockery\MockInterface;
13 class LdapTest extends TestCase
15 protected MockInterface $mockLdap;
17 protected User $mockUser;
18 protected string $resourceId = 'resource-test';
20 protected function setUp(): void
23 if (!defined('LDAP_OPT_REFERRALS')) {
24 define('LDAP_OPT_REFERRALS', 1);
27 'auth.method' => 'ldap',
28 'auth.defaults.guard' => 'ldap',
29 'services.ldap.base_dn' => 'dc=ldap,dc=local',
30 'services.ldap.email_attribute' => 'mail',
31 'services.ldap.display_name_attribute' => 'cn',
32 'services.ldap.id_attribute' => 'uid',
33 'services.ldap.user_to_groups' => false,
34 'services.ldap.version' => '3',
35 'services.ldap.user_filter' => '(&(uid={user}))',
36 'services.ldap.follow_referrals' => false,
37 'services.ldap.tls_insecure' => false,
38 'services.ldap.thumbnail_attribute' => null,
40 $this->mockLdap = $this->mock(Ldap::class);
41 $this->mockUser = User::factory()->make();
44 protected function runFailedAuthLogin()
46 $this->commonLdapMocks(1, 1, 1, 1, 1);
47 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
48 ->andReturn(['count' => 0]);
49 $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
52 protected function mockEscapes($times = 1)
54 $this->mockLdap->shouldReceive('escape')->times($times)->andReturnUsing(function ($val) {
55 return ldap_escape($val);
59 protected function mockExplodes($times = 1)
61 $this->mockLdap->shouldReceive('explodeDn')->times($times)->andReturnUsing(function ($dn, $withAttrib) {
62 return ldap_explode_dn($dn, $withAttrib);
66 protected function mockUserLogin(?string $email = null): TestResponse
68 return $this->post('/login', [
69 'username' => $this->mockUser->name,
70 'password' => $this->mockUser->password,
71 ] + ($email ? ['email' => $email] : []));
75 * Set LDAP method mocks for things we commonly call without altering.
77 protected function commonLdapMocks(int $connects = 1, int $versions = 1, int $options = 2, int $binds = 4, int $escapes = 2, int $explodes = 0)
79 $this->mockLdap->shouldReceive('connect')->times($connects)->andReturn($this->resourceId);
80 $this->mockLdap->shouldReceive('setVersion')->times($versions);
81 $this->mockLdap->shouldReceive('setOption')->times($options);
82 $this->mockLdap->shouldReceive('bind')->times($binds)->andReturn(true);
83 $this->mockEscapes($escapes);
84 $this->mockExplodes($explodes);
87 public function test_login()
89 $this->commonLdapMocks(1, 1, 2, 4, 2);
90 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
91 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
92 ->andReturn(['count' => 1, 0 => [
93 'uid' => [$this->mockUser->name],
94 'cn' => [$this->mockUser->name],
95 'dn' => 'dc=test' . config('services.ldap.base_dn'),
98 $resp = $this->mockUserLogin();
99 $resp->assertRedirect('/login');
100 $resp = $this->followRedirects($resp);
101 $resp->assertSee('Please enter an email to use for this account.');
102 $resp->assertSee($this->mockUser->name);
104 $resp = $this->followingRedirects()->mockUserLogin($this->mockUser->email);
105 $this->withHtml($resp)->assertElementExists('#home-default');
106 $resp->assertSee($this->mockUser->name);
107 $this->assertDatabaseHas('users', [
108 'email' => $this->mockUser->email,
109 'email_confirmed' => false,
110 'external_auth_id' => $this->mockUser->name,
114 public function test_email_domain_restriction_active_on_new_ldap_login()
117 'registration-restrict' => 'testing.com',
120 $this->commonLdapMocks(1, 1, 2, 4, 2);
121 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
122 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
123 ->andReturn(['count' => 1, 0 => [
124 'uid' => [$this->mockUser->name],
125 'cn' => [$this->mockUser->name],
126 'dn' => 'dc=test' . config('services.ldap.base_dn'),
129 $resp = $this->mockUserLogin();
130 $resp->assertRedirect('/login');
131 $this->followRedirects($resp)->assertSee('Please enter an email to use for this account.');
134 $resp = $this->mockUserLogin($email);
135 $resp->assertRedirect('/login');
136 $this->followRedirects($resp)->assertSee('That email domain does not have access to this application');
138 $this->assertDatabaseMissing('users', ['email' => $email]);
141 public function test_login_works_when_no_uid_provided_by_ldap_server()
143 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
145 $this->commonLdapMocks(1, 1, 1, 2, 1);
146 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
147 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
148 ->andReturn(['count' => 1, 0 => [
149 'cn' => [$this->mockUser->name],
151 'mail' => [$this->mockUser->email],
154 $resp = $this->mockUserLogin();
155 $resp->assertRedirect('/');
156 $this->followRedirects($resp)->assertSee($this->mockUser->name);
157 $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $ldapDn]);
160 public function test_a_custom_uid_attribute_can_be_specified_and_is_used_properly()
162 config()->set(['services.ldap.id_attribute' => 'my_custom_id']);
164 $this->commonLdapMocks(1, 1, 1, 2, 1);
165 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
166 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
167 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
168 ->andReturn(['count' => 1, 0 => [
169 'cn' => [$this->mockUser->name],
171 'my_custom_id' => ['cooluser456'],
172 'mail' => [$this->mockUser->email],
175 $resp = $this->mockUserLogin();
176 $resp->assertRedirect('/');
177 $this->followRedirects($resp)->assertSee($this->mockUser->name);
178 $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => 'cooluser456']);
181 public function test_user_filter_default_placeholder_format()
183 config()->set('services.ldap.user_filter', '(&(uid={user}))');
184 $this->mockUser->name = 'barryldapuser';
185 $expectedFilter = '(&(uid=\62\61\72\72\79\6c\64\61\70\75\73\65\72))';
187 $this->commonLdapMocks(1, 1, 1, 1, 1);
188 $this->mockLdap->shouldReceive('searchAndGetEntries')
190 ->with($this->resourceId, config('services.ldap.base_dn'), $expectedFilter, \Mockery::type('array'))
191 ->andReturn(['count' => 0, 0 => []]);
193 $resp = $this->mockUserLogin();
194 $resp->assertRedirect('/login');
197 public function test_user_filter_old_placeholder_format()
199 config()->set('services.ldap.user_filter', '(&(username=${user}))');
200 $this->mockUser->name = 'barryldapuser';
201 $expectedFilter = '(&(username=\62\61\72\72\79\6c\64\61\70\75\73\65\72))';
203 $this->commonLdapMocks(1, 1, 1, 1, 1);
204 $this->mockLdap->shouldReceive('searchAndGetEntries')
206 ->with($this->resourceId, config('services.ldap.base_dn'), $expectedFilter, \Mockery::type('array'))
207 ->andReturn(['count' => 0, 0 => []]);
209 $resp = $this->mockUserLogin();
210 $resp->assertRedirect('/login');
213 public function test_initial_incorrect_credentials()
215 $this->commonLdapMocks(1, 1, 1, 0, 1);
216 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
217 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
218 ->andReturn(['count' => 1, 0 => [
219 'uid' => [$this->mockUser->name],
220 'cn' => [$this->mockUser->name],
221 'dn' => 'dc=test' . config('services.ldap.base_dn'),
223 $this->mockLdap->shouldReceive('bind')->times(2)->andReturn(true, false);
225 $resp = $this->mockUserLogin();
226 $resp->assertRedirect('/login');
227 $this->followRedirects($resp)->assertSee('These credentials do not match our records.');
228 $this->assertDatabaseMissing('users', ['external_auth_id' => $this->mockUser->name]);
231 public function test_login_not_found_username()
233 $this->commonLdapMocks(1, 1, 1, 1, 1);
234 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
235 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
236 ->andReturn(['count' => 0]);
238 $resp = $this->mockUserLogin();
239 $resp->assertRedirect('/login');
240 $this->followRedirects($resp)->assertSee('These credentials do not match our records.');
241 $this->assertDatabaseMissing('users', ['external_auth_id' => $this->mockUser->name]);
244 public function test_create_user_form()
246 $userForm = $this->asAdmin()->get('/settings/users/create');
247 $userForm->assertDontSee('Password');
249 $save = $this->post('/settings/users/create', [
250 'name' => $this->mockUser->name,
251 'email' => $this->mockUser->email,
253 $save->assertSessionHasErrors(['external_auth_id' => 'The external auth id field is required.']);
255 $save = $this->post('/settings/users/create', [
256 'name' => $this->mockUser->name,
257 'email' => $this->mockUser->email,
258 'external_auth_id' => $this->mockUser->name,
260 $save->assertRedirect('/settings/users');
261 $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'external_auth_id' => $this->mockUser->name, 'email_confirmed' => true]);
264 public function test_user_edit_form()
266 $editUser = $this->users->viewer();
267 $editPage = $this->asAdmin()->get("/settings/users/{$editUser->id}");
268 $editPage->assertSee('Edit User');
269 $editPage->assertDontSee('Password');
271 $update = $this->put("/settings/users/{$editUser->id}", [
272 'name' => $editUser->name,
273 'email' => $editUser->email,
274 'external_auth_id' => 'test_auth_id',
276 $update->assertRedirect('/settings/users');
277 $this->assertDatabaseHas('users', ['email' => $editUser->email, 'external_auth_id' => 'test_auth_id']);
280 public function test_registration_disabled()
282 $resp = $this->followingRedirects()->get('/register');
283 $this->withHtml($resp)->assertElementContains('#content', 'Log In');
286 public function test_non_admins_cannot_change_auth_id()
288 $testUser = $this->users->viewer();
289 $this->actingAs($testUser)
290 ->get('/settings/users/' . $testUser->id)
291 ->assertDontSee('External Authentication');
294 public function test_login_maps_roles_and_retains_existing_roles()
296 $roleToReceive = Role::factory()->create(['display_name' => 'LdapTester']);
297 $roleToReceive2 = Role::factory()->create(['display_name' => 'LdapTester Second']);
298 $existingRole = Role::factory()->create(['display_name' => 'ldaptester-existing']);
299 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
300 $this->mockUser->attachRole($existingRole);
303 'services.ldap.user_to_groups' => true,
304 'services.ldap.group_attribute' => 'memberOf',
305 'services.ldap.remove_from_groups' => false,
308 $this->commonLdapMocks(1, 1, 4, 5, 4, 6);
309 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
310 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
311 ->andReturn(['count' => 1, 0 => [
312 'uid' => [$this->mockUser->name],
313 'cn' => [$this->mockUser->name],
314 'dn' => 'dc=test' . config('services.ldap.base_dn'),
315 'mail' => [$this->mockUser->email],
318 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
319 1 => 'cn=ldaptester-second,ou=groups,dc=example,dc=com',
323 $this->mockUserLogin()->assertRedirect('/');
325 $user = User::where('email', $this->mockUser->email)->first();
326 $this->assertDatabaseHas('role_user', [
327 'user_id' => $user->id,
328 'role_id' => $roleToReceive->id,
330 $this->assertDatabaseHas('role_user', [
331 'user_id' => $user->id,
332 'role_id' => $roleToReceive2->id,
334 $this->assertDatabaseHas('role_user', [
335 'user_id' => $user->id,
336 'role_id' => $existingRole->id,
340 public function test_login_maps_roles_and_removes_old_roles_if_set()
342 $roleToReceive = Role::factory()->create(['display_name' => 'LdapTester']);
343 $existingRole = Role::factory()->create(['display_name' => 'ldaptester-existing']);
344 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
345 $this->mockUser->attachRole($existingRole);
348 'services.ldap.user_to_groups' => true,
349 'services.ldap.group_attribute' => 'memberOf',
350 'services.ldap.remove_from_groups' => true,
353 $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
354 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(3)
355 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
356 ->andReturn(['count' => 1, 0 => [
357 'uid' => [$this->mockUser->name],
358 'cn' => [$this->mockUser->name],
359 'dn' => 'dc=test' . config('services.ldap.base_dn'),
360 'mail' => [$this->mockUser->email],
363 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
367 $this->mockUserLogin()->assertRedirect('/');
369 $user = User::query()->where('email', $this->mockUser->email)->first();
370 $this->assertDatabaseHas('role_user', [
371 'user_id' => $user->id,
372 'role_id' => $roleToReceive->id,
374 $this->assertDatabaseMissing('role_user', [
375 'user_id' => $user->id,
376 'role_id' => $existingRole->id,
380 public function test_dump_user_groups_shows_group_related_details_as_json()
383 'services.ldap.user_to_groups' => true,
384 'services.ldap.group_attribute' => 'memberOf',
385 'services.ldap.remove_from_groups' => true,
386 'services.ldap.dump_user_groups' => true,
389 $userResp = ['count' => 1, 0 => [
390 'uid' => [$this->mockUser->name],
391 'cn' => [$this->mockUser->name],
392 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
393 'mail' => [$this->mockUser->email],
395 $this->commonLdapMocks(1, 1, 4, 5, 4, 2);
396 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
397 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
398 ->andReturn($userResp, ['count' => 1,
400 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
403 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
409 'dn' => 'cn=ldaptester,ou=groups,dc=example,dc=com',
412 0 => 'cn=monsters,ou=groups,dc=example,dc=com',
417 $resp = $this->mockUserLogin();
419 'details_from_ldap' => [
420 'dn' => 'dc=test,' . config('services.ldap.base_dn'),
422 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
426 'parsed_direct_user_groups' => [
429 'parsed_recursive_user_groups' => [
436 public function test_external_auth_id_visible_in_roles_page_when_ldap_active()
438 $role = Role::factory()->create(['display_name' => 'ldaptester', 'external_auth_id' => 'ex-auth-a, test-second-param']);
439 $this->asAdmin()->get('/settings/roles/' . $role->id)
440 ->assertSee('ex-auth-a');
443 public function test_login_maps_roles_using_external_auth_ids_if_set()
445 $roleToReceive = Role::factory()->create(['display_name' => 'ldaptester', 'external_auth_id' => 'test-second-param, ex-auth-a']);
446 $roleToNotReceive = Role::factory()->create(['display_name' => 'ex-auth-a', 'external_auth_id' => 'test-second-param']);
449 'services.ldap.user_to_groups' => true,
450 'services.ldap.group_attribute' => 'memberOf',
451 'services.ldap.remove_from_groups' => true,
454 $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
455 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(3)
456 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
457 ->andReturn(['count' => 1, 0 => [
458 'uid' => [$this->mockUser->name],
459 'cn' => [$this->mockUser->name],
460 'dn' => 'dc=test' . config('services.ldap.base_dn'),
461 'mail' => [$this->mockUser->email],
464 0 => 'cn=ex-auth-a,ou=groups,dc=example,dc=com',
468 $this->mockUserLogin()->assertRedirect('/');
470 $user = User::query()->where('email', $this->mockUser->email)->first();
471 $this->assertDatabaseHas('role_user', [
472 'user_id' => $user->id,
473 'role_id' => $roleToReceive->id,
475 $this->assertDatabaseMissing('role_user', [
476 'user_id' => $user->id,
477 'role_id' => $roleToNotReceive->id,
481 public function test_login_group_mapping_does_not_conflict_with_default_role()
483 $roleToReceive = Role::factory()->create(['display_name' => 'LdapTester']);
484 $roleToReceive2 = Role::factory()->create(['display_name' => 'LdapTester Second']);
485 $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
487 setting()->put('registration-role', $roleToReceive->id);
490 'services.ldap.user_to_groups' => true,
491 'services.ldap.group_attribute' => 'memberOf',
492 'services.ldap.remove_from_groups' => true,
495 $this->commonLdapMocks(1, 1, 4, 5, 4, 6);
496 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
497 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
498 ->andReturn(['count' => 1, 0 => [
499 'uid' => [$this->mockUser->name],
500 'cn' => [$this->mockUser->name],
501 'dn' => 'dc=test' . config('services.ldap.base_dn'),
502 'mail' => [$this->mockUser->email],
505 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
506 1 => 'cn=ldaptester-second,ou=groups,dc=example,dc=com',
510 $this->mockUserLogin()->assertRedirect('/');
512 $user = User::query()->where('email', $this->mockUser->email)->first();
513 $this->assertDatabaseHas('role_user', [
514 'user_id' => $user->id,
515 'role_id' => $roleToReceive->id,
517 $this->assertDatabaseHas('role_user', [
518 'user_id' => $user->id,
519 'role_id' => $roleToReceive2->id,
523 public function test_login_uses_specified_display_name_attribute()
526 'services.ldap.display_name_attribute' => 'displayName',
529 $this->commonLdapMocks(1, 1, 2, 4, 2);
530 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
531 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
532 ->andReturn(['count' => 1, 0 => [
533 'uid' => [$this->mockUser->name],
534 'cn' => [$this->mockUser->name],
535 'dn' => 'dc=test' . config('services.ldap.base_dn'),
536 'displayname' => 'displayNameAttribute',
539 $this->mockUserLogin()->assertRedirect('/login');
540 $this->get('/login')->assertSee('Please enter an email to use for this account.');
542 $resp = $this->mockUserLogin($this->mockUser->email);
543 $resp->assertRedirect('/');
544 $this->get('/')->assertSee('displayNameAttribute');
545 $this->assertDatabaseHas('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name, 'name' => 'displayNameAttribute']);
548 public function test_login_uses_default_display_name_attribute_if_specified_not_present()
551 'services.ldap.display_name_attribute' => 'displayName',
554 $this->commonLdapMocks(1, 1, 2, 4, 2);
555 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
556 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
557 ->andReturn(['count' => 1, 0 => [
558 'uid' => [$this->mockUser->name],
559 'cn' => [$this->mockUser->name],
560 'dn' => 'dc=test' . config('services.ldap.base_dn'),
563 $this->mockUserLogin()->assertRedirect('/login');
564 $this->get('/login')->assertSee('Please enter an email to use for this account.');
566 $resp = $this->mockUserLogin($this->mockUser->email);
567 $resp->assertRedirect('/');
568 $this->get('/')->assertSee($this->mockUser->name);
569 $this->assertDatabaseHas('users', [
570 'email' => $this->mockUser->email,
571 'email_confirmed' => false,
572 'external_auth_id' => $this->mockUser->name,
573 'name' => $this->mockUser->name,
577 protected function checkLdapReceivesCorrectDetails($serverString, $expectedHostString): void
579 app('config')->set(['services.ldap.server' => $serverString]);
581 $this->mockLdap->shouldReceive('connect')
583 ->with($expectedHostString)
586 $this->mockUserLogin();
589 public function test_ldap_receives_correct_connect_host_from_config()
591 $expectedResultByInput = [
592 'ldaps://bookstack:8080' => 'ldaps://bookstack:8080',
593 'ldap.bookstack.com:8080' => 'ldap://ldap.bookstack.com:8080',
594 'ldap.bookstack.com' => 'ldap://ldap.bookstack.com',
595 'ldaps://ldap.bookstack.com' => 'ldaps://ldap.bookstack.com',
596 'ldaps://ldap.bookstack.com ldap://a.b.com' => 'ldaps://ldap.bookstack.com ldap://a.b.com',
599 foreach ($expectedResultByInput as $input => $expectedResult) {
600 $this->checkLdapReceivesCorrectDetails($input, $expectedResult);
601 $this->refreshApplication();
606 public function test_forgot_password_routes_inaccessible()
608 $resp = $this->get('/password/email');
609 $this->assertPermissionError($resp);
611 $resp = $this->post('/password/email');
612 $this->assertPermissionError($resp);
614 $resp = $this->get('/password/reset/abc123');
615 $this->assertPermissionError($resp);
617 $resp = $this->post('/password/reset');
618 $this->assertPermissionError($resp);
621 public function test_user_invite_routes_inaccessible()
623 $resp = $this->get('/register/invite/abc123');
624 $this->assertPermissionError($resp);
626 $resp = $this->post('/register/invite/abc123');
627 $this->assertPermissionError($resp);
630 public function test_user_register_routes_inaccessible()
632 $resp = $this->get('/register');
633 $this->assertPermissionError($resp);
635 $resp = $this->post('/register');
636 $this->assertPermissionError($resp);
639 public function test_dump_user_details_option_works()
641 config()->set(['services.ldap.dump_user_details' => true, 'services.ldap.thumbnail_attribute' => 'jpegphoto']);
643 $this->commonLdapMocks(1, 1, 1, 1, 1);
644 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
645 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
646 ->andReturn(['count' => 1, 0 => [
647 'uid' => [$this->mockUser->name],
648 'cn' => [$this->mockUser->name],
649 // Test dumping binary data for avatar responses
650 'jpegphoto' => base64_decode('/9j/4AAQSkZJRg=='),
651 'dn' => 'dc=test' . config('services.ldap.base_dn'),
654 $resp = $this->post('/login', [
655 'username' => $this->mockUser->name,
656 'password' => $this->mockUser->password,
658 $resp->assertJsonStructure([
659 'details_from_ldap' => [],
660 'details_bookstack_parsed' => [],
664 public function test_start_tls_called_if_option_set()
666 config()->set(['services.ldap.start_tls' => true]);
667 $this->mockLdap->shouldReceive('startTls')->once()->andReturn(true);
668 $this->runFailedAuthLogin();
671 public function test_connection_fails_if_tls_fails()
673 config()->set(['services.ldap.start_tls' => true]);
674 $this->mockLdap->shouldReceive('startTls')->once()->andReturn(false);
675 $this->commonLdapMocks(1, 1, 0, 0, 0);
676 $resp = $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
677 $resp->assertStatus(500);
680 public function test_ldap_attributes_can_be_binary_decoded_if_marked()
682 config()->set(['services.ldap.id_attribute' => 'BIN;uid']);
683 $ldapService = app()->make(LdapService::class);
684 $this->commonLdapMocks(1, 1, 1, 1, 1);
685 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
686 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), ['cn', 'dn', 'uid', 'mail', 'cn'])
687 ->andReturn(['count' => 1, 0 => [
688 'uid' => [hex2bin('FFF8F7')],
689 'cn' => [$this->mockUser->name],
690 'dn' => 'dc=test' . config('services.ldap.base_dn'),
693 $details = $ldapService->getUserDetails('test');
694 $this->assertEquals('fff8f7', $details['uid']);
697 public function test_new_ldap_user_login_with_already_used_email_address_shows_error_message_to_user()
699 $this->commonLdapMocks(1, 1, 2, 4, 2);
700 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
701 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
702 ->andReturn(['count' => 1, 0 => [
703 'uid' => [$this->mockUser->name],
704 'cn' => [$this->mockUser->name],
705 'dn' => 'dc=test' . config('services.ldap.base_dn'),
707 ]], ['count' => 1, 0 => [
710 'dn' => 'dc=bscott' . config('services.ldap.base_dn'),
715 $this->mockUserLogin()->assertRedirect('/');
719 $resp = $this->followingRedirects()->post('/login', ['username' => 'bscott', 'password' => 'pass']);
720 $resp->assertSee('A user with the email
[email protected] already exists but with different credentials');
723 public function test_login_with_email_confirmation_required_maps_groups_but_shows_confirmation_screen()
725 $roleToReceive = Role::factory()->create(['display_name' => 'LdapTester']);
726 $user = User::factory()->make();
727 setting()->put('registration-confirmation', 'true');
730 'services.ldap.user_to_groups' => true,
731 'services.ldap.group_attribute' => 'memberOf',
732 'services.ldap.remove_from_groups' => true,
735 $this->commonLdapMocks(1, 1, 6, 8, 6, 4);
736 $this->mockLdap->shouldReceive('searchAndGetEntries')
738 ->andReturn(['count' => 1, 0 => [
739 'uid' => [$user->name],
740 'cn' => [$user->name],
741 'dn' => 'dc=test' . config('services.ldap.base_dn'),
742 'mail' => [$user->email],
745 0 => 'cn=ldaptester,ou=groups,dc=example,dc=com',
749 $login = $this->followingRedirects()->mockUserLogin();
750 $login->assertSee('Thanks for registering!');
751 $this->assertDatabaseHas('users', [
752 'email' => $user->email,
753 'email_confirmed' => false,
756 $user = User::query()->where('email', '=', $user->email)->first();
757 $this->assertDatabaseHas('role_user', [
758 'user_id' => $user->id,
759 'role_id' => $roleToReceive->id,
762 $this->assertNull(auth()->user());
764 $homePage = $this->get('/');
765 $homePage->assertRedirect('/login');
767 $login = $this->followingRedirects()->mockUserLogin();
768 $login->assertSee('Email Address Not Confirmed');
771 public function test_failed_logins_are_logged_when_message_configured()
773 $log = $this->withTestLogger();
774 config()->set(['logging.failed_login.message' => 'Failed login for %u']);
775 $this->runFailedAuthLogin();
776 $this->assertTrue($log->hasWarningThatContains('Failed login for timmyjenkins'));
779 public function test_thumbnail_attribute_used_as_user_avatar_if_configured()
781 config()->set(['services.ldap.thumbnail_attribute' => 'jpegPhoto']);
783 $this->commonLdapMocks(1, 1, 1, 2, 1);
784 $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
785 $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
786 ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
787 ->andReturn(['count' => 1, 0 => [
788 'cn' => [$this->mockUser->name],
790 'jpegphoto' => [base64_decode('/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8Q
791 EBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=')],
792 'mail' => [$this->mockUser->email],
795 $this->mockUserLogin()
796 ->assertRedirect('/');
798 $user = User::query()->where('email', '=', $this->mockUser->email)->first();
799 $this->assertNotNull($user->avatar);
800 $this->assertEquals('8c90748342f19b195b9c6b4eff742ded', md5_file(public_path($user->avatar->path)));