]> BookStack Code Mirror - bookstack/blob - tests/Auth/LdapTest.php
Fleshed out and checked over theme system docs
[bookstack] / tests / Auth / LdapTest.php
1 <?php namespace Tests\Auth;
2
3 use BookStack\Auth\Access\LdapService;
4 use BookStack\Auth\Role;
5 use BookStack\Auth\Access\Ldap;
6 use BookStack\Auth\User;
7 use BookStack\Exceptions\LdapException;
8 use Mockery\MockInterface;
9 use Tests\BrowserKitTest;
10
11 class LdapTest extends BrowserKitTest
12 {
13
14     /**
15      * @var MockInterface
16      */
17     protected $mockLdap;
18
19     protected $mockUser;
20     protected $resourceId = 'resource-test';
21
22     public function setUp(): void
23     {
24         parent::setUp();
25         if (!defined('LDAP_OPT_REFERRALS')) define('LDAP_OPT_REFERRALS', 1);
26         config()->set([
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         ]);
39         $this->mockLdap = \Mockery::mock(Ldap::class);
40         $this->app[Ldap::class] = $this->mockLdap;
41         $this->mockUser = factory(User::class)->make();
42     }
43
44     protected function runFailedAuthLogin()
45     {
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']);
50     }
51
52     protected function mockEscapes($times = 1)
53     {
54         $this->mockLdap->shouldReceive('escape')->times($times)->andReturnUsing(function($val) {
55             return ldap_escape($val);
56         });
57     }
58
59     protected function mockExplodes($times = 1)
60     {
61         $this->mockLdap->shouldReceive('explodeDn')->times($times)->andReturnUsing(function($dn, $withAttrib) {
62             return ldap_explode_dn($dn, $withAttrib);
63         });
64     }
65
66     protected function mockUserLogin()
67     {
68         return $this->visit('/login')
69             ->see('Username')
70             ->type($this->mockUser->name, '#username')
71             ->type($this->mockUser->password, '#password')
72             ->press('Log In');
73     }
74
75     /**
76      * Set LDAP method mocks for things we commonly call without altering.
77      */
78     protected function commonLdapMocks(int $connects = 1, int $versions = 1, int $options = 2, int $binds = 4, int $escapes = 2, int $explodes = 0)
79     {
80         $this->mockLdap->shouldReceive('connect')->times($connects)->andReturn($this->resourceId);
81         $this->mockLdap->shouldReceive('setVersion')->times($versions);
82         $this->mockLdap->shouldReceive('setOption')->times($options);
83         $this->mockLdap->shouldReceive('bind')->times($binds)->andReturn(true);
84         $this->mockEscapes($escapes);
85         $this->mockExplodes($explodes);
86     }
87
88     public function test_login()
89     {
90         $this->commonLdapMocks(1, 1, 2, 4, 2);
91         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
92             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
93             ->andReturn(['count' => 1, 0 => [
94                 'uid' => [$this->mockUser->name],
95                 'cn' => [$this->mockUser->name],
96                 'dn' => ['dc=test' . config('services.ldap.base_dn')]
97             ]]);
98
99         $this->mockUserLogin()
100             ->seePageIs('/login')->see('Please enter an email to use for this account.');
101
102         $this->type($this->mockUser->email, '#email')
103             ->press('Log In')
104             ->seePageIs('/')
105             ->see($this->mockUser->name)
106             ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name]);
107     }
108
109     public function test_email_domain_restriction_active_on_new_ldap_login()
110     {
111         $this->setSettings([
112             'registration-restrict' => 'testing.com'
113         ]);
114
115         $this->commonLdapMocks(1, 1, 2, 4, 2);
116         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
117             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
118             ->andReturn(['count' => 1, 0 => [
119                 'uid' => [$this->mockUser->name],
120                 'cn' => [$this->mockUser->name],
121                 'dn' => ['dc=test' . config('services.ldap.base_dn')]
122             ]]);
123
124         $this->mockUserLogin()
125             ->seePageIs('/login')
126             ->see('Please enter an email to use for this account.');
127
128         $email = '[email protected]';
129
130         $this->type($email, '#email')
131             ->press('Log In')
132             ->seePageIs('/login')
133             ->see('That email domain does not have access to this application')
134             ->dontSeeInDatabase('users', ['email' => $email]);
135     }
136
137     public function test_login_works_when_no_uid_provided_by_ldap_server()
138     {
139         $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
140
141         $this->commonLdapMocks(1, 1, 1, 2, 1);
142         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
143             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
144             ->andReturn(['count' => 1, 0 => [
145                 'cn' => [$this->mockUser->name],
146                 'dn' => $ldapDn,
147                 'mail' => [$this->mockUser->email]
148             ]]);
149
150         $this->mockUserLogin()
151             ->seePageIs('/')
152             ->see($this->mockUser->name)
153             ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $ldapDn]);
154     }
155
156     public function test_a_custom_uid_attribute_can_be_specified_and_is_used_properly()
157     {
158         config()->set(['services.ldap.id_attribute' => 'my_custom_id']);
159
160         $this->commonLdapMocks(1, 1, 1, 2, 1);
161         $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
162         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
163             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
164             ->andReturn(['count' => 1, 0 => [
165                 'cn' => [$this->mockUser->name],
166                 'dn' => $ldapDn,
167                 'my_custom_id' => ['cooluser456'],
168                 'mail' => [$this->mockUser->email]
169             ]]);
170
171
172         $this->mockUserLogin()
173             ->seePageIs('/')
174             ->see($this->mockUser->name)
175             ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => 'cooluser456']);
176     }
177
178     public function test_initial_incorrect_credentials()
179     {
180         $this->commonLdapMocks(1, 1, 1, 0, 1);
181         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
182             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
183             ->andReturn(['count' => 1, 0 => [
184                 'uid' => [$this->mockUser->name],
185                 'cn' => [$this->mockUser->name],
186                 'dn' => ['dc=test' . config('services.ldap.base_dn')]
187             ]]);
188         $this->mockLdap->shouldReceive('bind')->times(2)->andReturn(true, false);
189
190         $this->mockUserLogin()
191             ->seePageIs('/login')->see('These credentials do not match our records.')
192             ->dontSeeInDatabase('users', ['external_auth_id' => $this->mockUser->name]);
193     }
194
195     public function test_login_not_found_username()
196     {
197         $this->commonLdapMocks(1, 1, 1, 1, 1);
198         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
199             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
200             ->andReturn(['count' => 0]);
201
202         $this->mockUserLogin()
203             ->seePageIs('/login')->see('These credentials do not match our records.')
204             ->dontSeeInDatabase('users', ['external_auth_id' => $this->mockUser->name]);
205     }
206
207
208     public function test_create_user_form()
209     {
210         $this->asAdmin()->visit('/settings/users/create')
211             ->dontSee('Password')
212             ->type($this->mockUser->name, '#name')
213             ->type($this->mockUser->email, '#email')
214             ->press('Save')
215             ->see('The external auth id field is required.')
216             ->type($this->mockUser->name, '#external_auth_id')
217             ->press('Save')
218             ->seePageIs('/settings/users')
219             ->seeInDatabase('users', ['email' => $this->mockUser->email, 'external_auth_id' => $this->mockUser->name, 'email_confirmed' => true]);
220     }
221
222     public function test_user_edit_form()
223     {
224         $editUser = $this->getNormalUser();
225         $this->asAdmin()->visit('/settings/users/' . $editUser->id)
226             ->see('Edit User')
227             ->dontSee('Password')
228             ->type('test_auth_id', '#external_auth_id')
229             ->press('Save')
230             ->seePageIs('/settings/users')
231             ->seeInDatabase('users', ['email' => $editUser->email, 'external_auth_id' => 'test_auth_id']);
232     }
233
234     public function test_registration_disabled()
235     {
236         $this->visit('/register')
237             ->seePageIs('/login');
238     }
239
240     public function test_non_admins_cannot_change_auth_id()
241     {
242         $testUser = $this->getNormalUser();
243         $this->actingAs($testUser)->visit('/settings/users/' . $testUser->id)
244             ->dontSee('External Authentication');
245     }
246
247     public function test_login_maps_roles_and_retains_existing_roles()
248     {
249         $roleToReceive = factory(Role::class)->create(['display_name' => 'LdapTester']);
250         $roleToReceive2 = factory(Role::class)->create(['display_name' => 'LdapTester Second']);
251         $existingRole = factory(Role::class)->create(['display_name' => 'ldaptester-existing']);
252         $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
253         $this->mockUser->attachRole($existingRole);
254
255         app('config')->set([
256             'services.ldap.user_to_groups' => true,
257             'services.ldap.group_attribute' => 'memberOf',
258             'services.ldap.remove_from_groups' => false,
259         ]);
260
261         $this->commonLdapMocks(1, 1, 4, 5, 4, 6);
262         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
263             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
264             ->andReturn(['count' => 1, 0 => [
265                 'uid' => [$this->mockUser->name],
266                 'cn' => [$this->mockUser->name],
267                 'dn' => ['dc=test' . config('services.ldap.base_dn')],
268                 'mail' => [$this->mockUser->email],
269                 'memberof' => [
270                     'count' => 2,
271                     0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
272                     1 => "cn=ldaptester-second,ou=groups,dc=example,dc=com",
273                 ]
274             ]]);
275
276         $this->mockUserLogin()->seePageIs('/');
277
278         $user = User::where('email', $this->mockUser->email)->first();
279         $this->seeInDatabase('role_user', [
280             'user_id' => $user->id,
281             'role_id' => $roleToReceive->id
282         ]);
283         $this->seeInDatabase('role_user', [
284             'user_id' => $user->id,
285             'role_id' => $roleToReceive2->id
286         ]);
287         $this->seeInDatabase('role_user', [
288             'user_id' => $user->id,
289             'role_id' => $existingRole->id
290         ]);
291     }
292
293     public function test_login_maps_roles_and_removes_old_roles_if_set()
294     {
295         $roleToReceive = factory(Role::class)->create(['display_name' => 'LdapTester']);
296         $existingRole = factory(Role::class)->create(['display_name' => 'ldaptester-existing']);
297         $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
298         $this->mockUser->attachRole($existingRole);
299
300         app('config')->set([
301             'services.ldap.user_to_groups' => true,
302             'services.ldap.group_attribute' => 'memberOf',
303             'services.ldap.remove_from_groups' => true,
304         ]);
305
306         $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
307         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(3)
308             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
309             ->andReturn(['count' => 1, 0 => [
310                 'uid' => [$this->mockUser->name],
311                 'cn' => [$this->mockUser->name],
312                 'dn' => ['dc=test' . config('services.ldap.base_dn')],
313                 'mail' => [$this->mockUser->email],
314                 'memberof' => [
315                     'count' => 1,
316                     0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
317                 ]
318             ]]);
319
320         $this->mockUserLogin()->seePageIs('/');
321
322         $user = User::where('email', $this->mockUser->email)->first();
323         $this->seeInDatabase('role_user', [
324             'user_id' => $user->id,
325             'role_id' => $roleToReceive->id
326         ]);
327         $this->dontSeeInDatabase('role_user', [
328             'user_id' => $user->id,
329             'role_id' => $existingRole->id
330         ]);
331     }
332
333     public function test_external_auth_id_visible_in_roles_page_when_ldap_active()
334     {
335         $role = factory(Role::class)->create(['display_name' => 'ldaptester', 'external_auth_id' => 'ex-auth-a, test-second-param']);
336         $this->asAdmin()->visit('/settings/roles/' . $role->id)
337             ->see('ex-auth-a');
338     }
339
340     public function test_login_maps_roles_using_external_auth_ids_if_set()
341     {
342         $roleToReceive = factory(Role::class)->create(['display_name' => 'ldaptester', 'external_auth_id' => 'test-second-param, ex-auth-a']);
343         $roleToNotReceive = factory(Role::class)->create(['display_name' => 'ex-auth-a', 'external_auth_id' => 'test-second-param']);
344
345         app('config')->set([
346             'services.ldap.user_to_groups' => true,
347             'services.ldap.group_attribute' => 'memberOf',
348             'services.ldap.remove_from_groups' => true,
349         ]);
350
351         $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
352         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(3)
353             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
354             ->andReturn(['count' => 1, 0 => [
355                 'uid' => [$this->mockUser->name],
356                 'cn' => [$this->mockUser->name],
357                 'dn' => ['dc=test' . config('services.ldap.base_dn')],
358                 'mail' => [$this->mockUser->email],
359                 'memberof' => [
360                     'count' => 1,
361                     0 => "cn=ex-auth-a,ou=groups,dc=example,dc=com",
362                 ]
363             ]]);
364
365         $this->mockUserLogin()->seePageIs('/');
366
367         $user = User::where('email', $this->mockUser->email)->first();
368         $this->seeInDatabase('role_user', [
369             'user_id' => $user->id,
370             'role_id' => $roleToReceive->id
371         ]);
372         $this->dontSeeInDatabase('role_user', [
373             'user_id' => $user->id,
374             'role_id' => $roleToNotReceive->id
375         ]);
376     }
377
378     public function test_login_group_mapping_does_not_conflict_with_default_role()
379     {
380         $roleToReceive = factory(Role::class)->create(['display_name' => 'LdapTester']);
381         $roleToReceive2 = factory(Role::class)->create(['display_name' => 'LdapTester Second']);
382         $this->mockUser->forceFill(['external_auth_id' => $this->mockUser->name])->save();
383
384         setting()->put('registration-role', $roleToReceive->id);
385
386         app('config')->set([
387             'services.ldap.user_to_groups' => true,
388             'services.ldap.group_attribute' => 'memberOf',
389             'services.ldap.remove_from_groups' => true,
390         ]);
391
392         $this->commonLdapMocks(1, 1, 4, 5, 4, 6);
393         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
394             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
395             ->andReturn(['count' => 1, 0 => [
396                 'uid' => [$this->mockUser->name],
397                 'cn' => [$this->mockUser->name],
398                 'dn' => ['dc=test' . config('services.ldap.base_dn')],
399                 'mail' => [$this->mockUser->email],
400                 'memberof' => [
401                     'count' => 2,
402                     0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
403                     1 => "cn=ldaptester-second,ou=groups,dc=example,dc=com",
404                 ]
405             ]]);
406
407         $this->mockUserLogin()->seePageIs('/');
408
409         $user = User::where('email', $this->mockUser->email)->first();
410         $this->seeInDatabase('role_user', [
411             'user_id' => $user->id,
412             'role_id' => $roleToReceive->id
413         ]);
414         $this->seeInDatabase('role_user', [
415             'user_id' => $user->id,
416             'role_id' => $roleToReceive2->id
417         ]);
418     }
419
420     public function test_login_uses_specified_display_name_attribute()
421     {
422         app('config')->set([
423             'services.ldap.display_name_attribute' => 'displayName'
424         ]);
425
426         $this->commonLdapMocks(1, 1, 2, 4, 2);
427         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
428             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
429             ->andReturn(['count' => 1, 0 => [
430                 'uid' => [$this->mockUser->name],
431                 'cn' => [$this->mockUser->name],
432                 'dn' => ['dc=test' . config('services.ldap.base_dn')],
433                 'displayname' => 'displayNameAttribute'
434             ]]);
435
436         $this->mockUserLogin()
437             ->seePageIs('/login')->see('Please enter an email to use for this account.');
438
439         $this->type($this->mockUser->email, '#email')
440             ->press('Log In')
441             ->seePageIs('/')
442             ->see('displayNameAttribute')
443             ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name, 'name' => 'displayNameAttribute']);
444     }
445
446     public function test_login_uses_default_display_name_attribute_if_specified_not_present()
447     {
448         app('config')->set([
449             'services.ldap.display_name_attribute' => 'displayName'
450         ]);
451
452         $this->commonLdapMocks(1, 1, 2, 4, 2);
453         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
454             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
455             ->andReturn(['count' => 1, 0 => [
456                 'uid' => [$this->mockUser->name],
457                 'cn' => [$this->mockUser->name],
458                 'dn' => ['dc=test' . config('services.ldap.base_dn')]
459             ]]);
460
461         $this->mockUserLogin()
462             ->seePageIs('/login')->see('Please enter an email to use for this account.');
463
464         $this->type($this->mockUser->email, '#email')
465             ->press('Log In')
466             ->seePageIs('/')
467             ->see($this->mockUser->name)
468             ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name, 'name' => $this->mockUser->name]);
469     }
470
471     protected function checkLdapReceivesCorrectDetails($serverString, $expectedHost, $expectedPort)
472     {
473         app('config')->set([
474             'services.ldap.server' => $serverString
475         ]);
476
477         // Standard mocks
478         $this->commonLdapMocks(0, 1, 1, 2, 1);
479         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)->andReturn(['count' => 1, 0 => [
480             'uid' => [$this->mockUser->name],
481             'cn' => [$this->mockUser->name],
482             'dn' => ['dc=test' . config('services.ldap.base_dn')]
483         ]]);
484
485         $this->mockLdap->shouldReceive('connect')->once()
486             ->with($expectedHost, $expectedPort)->andReturn($this->resourceId);
487         $this->mockUserLogin();
488     }
489
490     public function test_ldap_port_provided_on_host_if_host_is_full_uri()
491     {
492         $hostName = 'ldaps://bookstack:8080';
493         $this->checkLdapReceivesCorrectDetails($hostName, $hostName, 389);
494     }
495
496     public function test_ldap_port_parsed_from_server_if_host_is_not_full_uri()
497     {
498         $this->checkLdapReceivesCorrectDetails('ldap.bookstack.com:8080', 'ldap.bookstack.com', 8080);
499     }
500
501     public function test_default_ldap_port_used_if_not_in_server_string_and_not_uri()
502     {
503         $this->checkLdapReceivesCorrectDetails('ldap.bookstack.com', 'ldap.bookstack.com', 389);
504     }
505
506     public function test_forgot_password_routes_inaccessible()
507     {
508         $resp = $this->get('/password/email');
509         $this->assertPermissionError($resp);
510
511         $resp = $this->post('/password/email');
512         $this->assertPermissionError($resp);
513
514         $resp = $this->get('/password/reset/abc123');
515         $this->assertPermissionError($resp);
516
517         $resp = $this->post('/password/reset');
518         $this->assertPermissionError($resp);
519     }
520
521     public function test_user_invite_routes_inaccessible()
522     {
523         $resp = $this->get('/register/invite/abc123');
524         $this->assertPermissionError($resp);
525
526         $resp = $this->post('/register/invite/abc123');
527         $this->assertPermissionError($resp);
528     }
529
530     public function test_user_register_routes_inaccessible()
531     {
532         $resp = $this->get('/register');
533         $this->assertPermissionError($resp);
534
535         $resp = $this->post('/register');
536         $this->assertPermissionError($resp);
537     }
538
539     public function test_dump_user_details_option_works()
540     {
541         config()->set(['services.ldap.dump_user_details' => true]);
542
543         $this->commonLdapMocks(1, 1, 1, 1, 1);
544         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
545             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
546             ->andReturn(['count' => 1, 0 => [
547                 'uid' => [$this->mockUser->name],
548                 'cn' => [$this->mockUser->name],
549                 'dn' => ['dc=test' . config('services.ldap.base_dn')]
550             ]]);
551
552         $this->post('/login', [
553             'username' => $this->mockUser->name,
554             'password' => $this->mockUser->password,
555         ]);
556         $this->seeJsonStructure([
557             'details_from_ldap' => [],
558             'details_bookstack_parsed' => [],
559         ]);
560     }
561
562     public function test_start_tls_called_if_option_set()
563     {
564         config()->set(['services.ldap.start_tls' => true]);
565         $this->mockLdap->shouldReceive('startTls')->once()->andReturn(true);
566         $this->runFailedAuthLogin();
567     }
568
569     public function test_connection_fails_if_tls_fails()
570     {
571         config()->set(['services.ldap.start_tls' => true]);
572         $this->mockLdap->shouldReceive('startTls')->once()->andReturn(false);
573         $this->commonLdapMocks(1, 1, 0, 0, 0);
574         $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
575         $this->assertResponseStatus(500);
576     }
577
578     public function test_ldap_attributes_can_be_binary_decoded_if_marked()
579     {
580         config()->set(['services.ldap.id_attribute' => 'BIN;uid']);
581         $ldapService = app()->make(LdapService::class);
582         $this->commonLdapMocks(1, 1, 1, 1, 1);
583         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
584             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), ['cn', 'dn', 'uid', 'mail', 'cn'])
585             ->andReturn(['count' => 1, 0 => [
586                 'uid' => [hex2bin('FFF8F7')],
587                 'cn' => [$this->mockUser->name],
588                 'dn' => ['dc=test' . config('services.ldap.base_dn')]
589             ]]);
590
591         $details = $ldapService->getUserDetails('test');
592         $this->assertEquals('fff8f7', $details['uid']);
593     }
594
595     public function test_new_ldap_user_login_with_already_used_email_address_shows_error_message_to_user()
596     {
597         $this->commonLdapMocks(1, 1, 2, 4, 2);
598         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
599             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
600             ->andReturn(['count' => 1, 0 => [
601                 'uid' => [$this->mockUser->name],
602                 'cn' => [$this->mockUser->name],
603                 'dn' => ['dc=test' . config('services.ldap.base_dn')],
604                 'mail' => '[email protected]',
605             ]], ['count' => 1, 0 => [
606                 'uid' => ['Barry'],
607                 'cn' => ['Scott'],
608                 'dn' => ['dc=bscott' . config('services.ldap.base_dn')],
609                 'mail' => '[email protected]',
610             ]]);
611
612         // First user login
613         $this->mockUserLogin()->seePageIs('/');
614
615         // Second user login
616         auth()->logout();
617         $this->post('/login', ['username' => 'bscott', 'password' => 'pass'])->followRedirects();
618
619         $this->see('A user with the email [email protected] already exists but with different credentials');
620     }
621
622     public function test_login_with_email_confirmation_required_maps_groups_but_shows_confirmation_screen()
623     {
624         $roleToReceive = factory(Role::class)->create(['display_name' => 'LdapTester']);
625         $user = factory(User::class)->make();
626         setting()->put('registration-confirmation', 'true');
627
628         app('config')->set([
629             'services.ldap.user_to_groups' => true,
630             'services.ldap.group_attribute' => 'memberOf',
631             'services.ldap.remove_from_groups' => true,
632         ]);
633
634         $this->commonLdapMocks(1, 1, 3, 4, 3, 2);
635         $this->mockLdap->shouldReceive('searchAndGetEntries')
636             ->times(3)
637             ->andReturn(['count' => 1, 0 => [
638                 'uid' => [$user->name],
639                 'cn' => [$user->name],
640                 'dn' => ['dc=test' . config('services.ldap.base_dn')],
641                 'mail' => [$user->email],
642                 'memberof' => [
643                     'count' => 1,
644                     0 => "cn=ldaptester,ou=groups,dc=example,dc=com",
645                 ]
646             ]]);
647
648         $this->mockUserLogin()->seePageIs('/register/confirm');
649         $this->seeInDatabase('users', [
650             'email' => $user->email,
651             'email_confirmed' => false,
652         ]);
653
654         $user  = User::query()->where('email', '=', $user->email)->first();
655         $this->seeInDatabase('role_user', [
656             'user_id' => $user->id,
657             'role_id' => $roleToReceive->id
658         ]);
659
660         $homePage = $this->get('/');
661         $homePage->assertRedirectedTo('/register/confirm/awaiting');
662     }
663
664     public function test_failed_logins_are_logged_when_message_configured()
665     {
666         $log = $this->withTestLogger();
667         config()->set(['logging.failed_login.message' => 'Failed login for %u']);
668         $this->runFailedAuthLogin();
669         $this->assertTrue($log->hasWarningThatContains('Failed login for timmyjenkins'));
670     }
671 }