DatabaseProviderTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <?php
  2. namespace Adldap\Laravel\Tests;
  3. use Adldap\AdldapInterface;
  4. use Adldap\Connections\ConnectionInterface;
  5. use Adldap\Connections\Provider;
  6. use Adldap\Connections\ProviderInterface;
  7. use Adldap\Laravel\Commands\Import;
  8. use Adldap\Laravel\Facades\Resolver;
  9. use Adldap\Laravel\Tests\Handlers\LdapAttributeHandler;
  10. use Adldap\Laravel\Tests\Models\TestUser as EloquentUser;
  11. use Adldap\Laravel\Tests\Scopes\JohnDoeScope;
  12. use Adldap\Models\User;
  13. use Illuminate\Foundation\Testing\WithFaker;
  14. use Illuminate\Support\Facades\App;
  15. use Illuminate\Support\Facades\Auth;
  16. use Illuminate\Support\Facades\Hash;
  17. use Mockery as m;
  18. class DatabaseProviderTest extends DatabaseTestCase
  19. {
  20. use WithFaker;
  21. /**
  22. * @test
  23. * @expectedException \RuntimeException
  24. */
  25. public function configuration_not_found_exception_when_config_is_null()
  26. {
  27. config(['ldap' => null]);
  28. App::make(AdldapInterface::class);
  29. }
  30. /** @test */
  31. public function adldap_is_bound_to_interface()
  32. {
  33. $adldap = App::make(AdldapInterface::class);
  34. $this->assertInstanceOf(AdldapInterface::class, $adldap);
  35. }
  36. /** @test */
  37. public function auth_passes($credentials = null)
  38. {
  39. $credentials = $credentials ?: ['email' => 'jdoe@email.com', 'password' => '12345'];
  40. $user = $this->makeLdapUser([
  41. 'objectguid' => [$this->faker->uuid],
  42. 'cn' => ['John Doe'],
  43. 'userprincipalname' => ['jdoe@email.com'],
  44. ]);
  45. Resolver::shouldReceive('byModel')->once()->andReturn($user)
  46. ->shouldReceive('byCredentials')->once()->andReturn($user)
  47. ->shouldReceive('getDatabaseIdColumn')->andReturn('objectguid')
  48. ->shouldReceive('getDatabaseUsernameColumn')->andReturn('email')
  49. ->shouldReceive('getLdapDiscoveryAttribute')->andReturn('userprincipalname')
  50. ->shouldReceive('authenticate')->once()->andReturn(true);
  51. $this->assertTrue(Auth::attempt($credentials));
  52. $this->assertInstanceOf(EloquentUser::class, Auth::user());
  53. $this->assertInstanceOf(User::class, Auth::user()->ldap);
  54. }
  55. /** @test */
  56. public function auth_fails_when_user_found()
  57. {
  58. $user = $this->makeLdapUser([
  59. 'objectguid' => ['cc07cacc-5d9d-fa40-a9fb-3a4d50a172b0'],
  60. 'cn' => ['John Doe'],
  61. 'userprincipalname' => ['jdoe@email.com'],
  62. ]);
  63. Resolver::shouldReceive('byCredentials')->once()->andReturn($user)
  64. ->shouldReceive('getDatabaseIdColumn')->andReturn('objectguid')
  65. ->shouldReceive('getDatabaseUsernameColumn')->andReturn('email')
  66. ->shouldReceive('getLdapDiscoveryAttribute')->andReturn('userprincipalname')
  67. ->shouldReceive('authenticate')->once()->andReturn(false);
  68. $this->assertFalse(Auth::attempt(['email' => 'jdoe@email.com', 'password' => '12345']));
  69. }
  70. /** @test */
  71. public function auth_fails_when_user_not_found()
  72. {
  73. Resolver::shouldReceive('byCredentials')->once()->andReturn(null);
  74. $this->assertFalse(Auth::attempt(['email' => 'jdoe@email.com', 'password' => '12345']));
  75. }
  76. /** @test */
  77. public function config_scopes_are_applied()
  78. {
  79. $ldapMock = m::mock(AdldapInterface::class);
  80. App::instance(AdldapInterface::class, $ldapMock);
  81. /** @var Provider $provider */
  82. $provider = App::make(Provider::class);
  83. config(['ldap_auth.scopes' => [JohnDoeScope::class]]);
  84. $providerMock = m::mock(ProviderInterface::class);
  85. $connectionMock = m::mock(ConnectionInterface::class);
  86. $providerMock->shouldReceive('getConnection')->once()->andReturn($connectionMock);
  87. $connectionMock->shouldReceive('isBound')->once()->andReturn(true);
  88. $ldapMock->shouldReceive('getProvider')->once()->andReturn($providerMock);
  89. $providerMock->shouldReceive('search')->once()->andReturn($provider->search());
  90. $expectedFilter = '(&(objectclass=\75\73\65\72)(objectcategory=\70\65\72\73\6f\6e)(!(objectclass=\63\6f\6e\74\61\63\74))(cn=\4a\6f\68\6e\20\44\6f\65))';
  91. $this->assertEquals($expectedFilter, Resolver::query()->getQuery());
  92. }
  93. /** @test */
  94. public function attribute_handlers_are_used()
  95. {
  96. $default = config('ldap_auth.sync_attributes');
  97. config(['ldap_auth.sync_attributes' => array_merge($default, [LdapAttributeHandler::class])]);
  98. $this->auth_passes();
  99. $user = Auth::user();
  100. $this->assertEquals('handled', $user->name);
  101. }
  102. /** @test */
  103. public function invalid_attribute_handlers_does_not_throw_exception()
  104. {
  105. // Inserting an invalid attribute handler that
  106. // does not contain a `handle` method.
  107. config(['ldap_auth.sync_attributes' => [\stdClass::class]]);
  108. $user = $this->makeLdapUser([
  109. 'objectguid' => ['cc07cacc-5d9d-fa40-a9fb-3a4d50a172b0'],
  110. 'cn' => ['John Doe'],
  111. 'userprincipalname' => ['jdoe@email.com'],
  112. ]);
  113. $importer = new Import($user, new EloquentUser());
  114. Resolver::shouldReceive('getDatabaseIdColumn')->andReturn('objectguid')
  115. ->shouldReceive('getDatabaseUsernameColumn')->once()->andReturn('email')
  116. ->shouldReceive('getLdapDiscoveryAttribute')->once()->andReturn('userprincipalname');
  117. $this->assertInstanceOf(EloquentUser::class, $importer->handle());
  118. }
  119. /** @test */
  120. public function sync_attribute_as_string_will_return_null()
  121. {
  122. config([
  123. 'ldap_auth.sync_attributes' => [
  124. 'email' => 'userprincipalname',
  125. 'name' => 'cn',
  126. ],
  127. ]);
  128. // LDAP user does not have common name.
  129. $user = $this->makeLdapUser([
  130. 'objectguid' => ['cc07cacc-5d9d-fa40-a9fb-3a4d50a172b0'],
  131. 'userprincipalname' => ['jdoe@email.com'],
  132. ]);
  133. $importer = new Import($user, new EloquentUser());
  134. $model = $importer->handle();
  135. $this->assertInstanceOf(EloquentUser::class, $model);
  136. $this->assertNull($model->name);
  137. }
  138. /** @test */
  139. public function sync_attribute_as_int_boolean_or_array_will_be_used()
  140. {
  141. config([
  142. 'ldap_auth.sync_attributes' => [
  143. 'email' => 'userprincipalname',
  144. 'string' => 'not-an-LDAP-attribute',
  145. 'int' => 1,
  146. 'bool' => true,
  147. 'array' => ['one', 'two'],
  148. ],
  149. ]);
  150. // LDAP user does not have common name.
  151. $user = $this->makeLdapUser([
  152. 'objectguid' => ['cc07cacc-5d9d-fa40-a9fb-3a4d50a172b0'],
  153. 'userprincipalname' => ['jdoe@email.com'],
  154. ]);
  155. $importer = new Import($user, new EloquentUser());
  156. $model = $importer->handle();
  157. $this->assertInstanceOf(EloquentUser::class, $model);
  158. $this->assertNull($model->string);
  159. $this->assertEquals($model->int, 1);
  160. $this->assertEquals($model->bool, true);
  161. $this->assertEquals($model->array, ['one', 'two']);
  162. }
  163. /** @test */
  164. public function auth_attempts_fallback_using_config_option()
  165. {
  166. config(['ldap_auth.login_fallback' => true]);
  167. EloquentUser::create([
  168. 'email' => 'jdoe@email.com',
  169. 'name' => 'John Doe',
  170. 'password' => Hash::make('Password123'),
  171. ]);
  172. $credentials = [
  173. 'email' => 'jdoe@email.com',
  174. 'password' => 'Password123',
  175. ];
  176. Resolver::shouldReceive('byCredentials')->times(3)->andReturn(null)
  177. ->shouldReceive('byModel')->times(2)->andReturn(null);
  178. $this->assertTrue(Auth::attempt($credentials));
  179. $this->assertFalse(Auth::attempt(
  180. array_replace($credentials, ['password' => 'Invalid'])
  181. ));
  182. config(['ldap_auth.login_fallback' => false]);
  183. $this->assertFalse(Auth::attempt($credentials));
  184. }
  185. /** @test */
  186. public function auth_attempts_using_fallback_does_not_require_connection()
  187. {
  188. $ldapMock = m::mock(AdldapInterface::class);
  189. App::instance(AdldapInterface::class, $ldapMock);
  190. /** @var Provider $provider */
  191. $provider = App::make(Provider::class);
  192. config(['ldap_auth.login_fallback' => true]);
  193. EloquentUser::create([
  194. 'email' => 'jdoe@email.com',
  195. 'name' => 'John Doe',
  196. 'password' => Hash::make('Password123'),
  197. ]);
  198. $credentials = [
  199. 'email' => 'jdoe@email.com',
  200. 'password' => 'Password123',
  201. ];
  202. $providerMock = m::mock(ProviderInterface::class);
  203. $connectionMock = m::mock(ConnectionInterface::class);
  204. $providerMock->shouldReceive('getConnection')->times(3)->andReturn($connectionMock);
  205. $connectionMock->shouldReceive('isBound')->times(3)->andReturn(true);
  206. $ldapMock->shouldReceive('getProvider')->times(3)->andReturn($providerMock);
  207. $providerMock->shouldReceive('search')->times(3)->andReturn($provider->search());
  208. $this->assertTrue(Auth::attempt($credentials));
  209. $user = Auth::user();
  210. $this->assertInstanceOf('Adldap\Laravel\Tests\Models\TestUser', $user);
  211. $this->assertEquals('jdoe@email.com', $user->email);
  212. }
  213. /** @test */
  214. public function passwords_are_synced_when_enabled()
  215. {
  216. config(['ldap_auth.passwords.sync' => true]);
  217. $credentials = [
  218. 'email' => 'jdoe@email.com',
  219. 'password' => '12345',
  220. ];
  221. $this->auth_passes($credentials);
  222. $user = EloquentUser::first();
  223. // This check will pass due to password synchronization being enabled.
  224. $this->assertTrue(Hash::check($credentials['password'], $user->password));
  225. }
  226. /** @test */
  227. public function passwords_are_not_synced_when_sync_is_disabled()
  228. {
  229. config(['ldap_auth.passwords.sync' => false]);
  230. $credentials = [
  231. 'email' => 'jdoe@email.com',
  232. 'password' => '12345',
  233. ];
  234. $this->auth_passes($credentials);
  235. $user = EloquentUser::first();
  236. // This check will fail due to password synchronization being disabled.
  237. $this->assertFalse(Hash::check($credentials['password'], $user->password));
  238. }
  239. /** @test */
  240. public function users_without_a_guid_are_synchronized_properly()
  241. {
  242. EloquentUser::create([
  243. 'email' => 'jdoe@email.com',
  244. 'name' => 'John Doe',
  245. 'password' => Hash::make('Password123'),
  246. ]);
  247. $credentials = [
  248. 'email' => 'jdoe@email.com',
  249. 'password' => 'Password123',
  250. ];
  251. $ldapUser = $this->makeLdapUser();
  252. Resolver::shouldReceive('byCredentials')->once()->andReturn($ldapUser)
  253. ->shouldReceive('getDatabaseIdColumn')->andReturn('objectguid')
  254. ->shouldReceive('getDatabaseUsernameColumn')->andReturn('email')
  255. ->shouldReceive('getLdapDiscoveryAttribute')->andReturn('userprincipalname')
  256. ->shouldReceive('byModel')->once()->andReturn($ldapUser)
  257. ->shouldReceive('authenticate')->once()->andReturn(true);
  258. $this->assertTrue(Auth::attempt($credentials));
  259. $user = Auth::user();
  260. $this->assertInstanceOf('Adldap\Laravel\Tests\Models\TestUser', $user);
  261. $this->assertEquals($user->objectguid, $ldapUser->getConvertedGuid());
  262. $this->assertEquals('jdoe@email.com', $user->email);
  263. $this->assertEquals(1, EloquentUser::count());
  264. }
  265. /** @test */
  266. public function users_without_a_guid_and_a_changed_username_have_new_record_created()
  267. {
  268. // Create an existing synchronized user.
  269. EloquentUser::create([
  270. 'email' => 'jdoe@email.com',
  271. 'name' => 'John Doe',
  272. 'password' => Hash::make('Password123'),
  273. ]);
  274. $credentials = [
  275. 'email' => 'johndoe@email.com',
  276. 'password' => 'Password123',
  277. ];
  278. // Generate an LDAP user with a changed UPN and Mail.
  279. $ldapUser = $this->makeLdapUser([
  280. 'objectguid' => ['cc07cacc-5d9d-fa40-a9fb-3a4d50a172b0'],
  281. 'samaccountname' => ['jdoe'],
  282. 'userprincipalname' => ['johndoe@email.com'],
  283. 'mail' => ['johndoe@email.com'],
  284. 'cn' => ['John Doe'],
  285. ]);
  286. Resolver::shouldReceive('byCredentials')->once()->andReturn($ldapUser)
  287. ->shouldReceive('getDatabaseIdColumn')->andReturn('objectguid')
  288. ->shouldReceive('getDatabaseUsernameColumn')->andReturn('email')
  289. ->shouldReceive('getLdapDiscoveryAttribute')->andReturn('userprincipalname')
  290. ->shouldReceive('byModel')->once()->andReturn($ldapUser)
  291. ->shouldReceive('authenticate')->once()->andReturn(true);
  292. $this->assertTrue(Auth::attempt($credentials));
  293. $user = Auth::user();
  294. $this->assertInstanceOf('Adldap\Laravel\Tests\Models\TestUser', $user);
  295. $this->assertEquals($user->objectguid, $ldapUser->getConvertedGuid());
  296. $this->assertEquals('johndoe@email.com', $user->email);
  297. $this->assertEquals(2, EloquentUser::count());
  298. }
  299. /** @test */
  300. public function passwords_are_not_updated_when_sync_is_disabled()
  301. {
  302. config(['ldap_auth.passwords.sync' => false]);
  303. $credentials = [
  304. 'email' => 'jdoe@email.com',
  305. 'password' => '12345',
  306. ];
  307. $this->auth_passes($credentials);
  308. $user = EloquentUser::first();
  309. $this->auth_passes($credentials);
  310. $this->assertEquals($user->password, $user->fresh()->password);
  311. }
  312. /** @test */
  313. public function trashed_rule_prevents_deleted_users_from_logging_in()
  314. {
  315. config([
  316. 'ldap_auth.login_fallback' => false,
  317. 'ldap_auth.rules' => [\Adldap\Laravel\Validation\Rules\DenyTrashed::class],
  318. ]);
  319. $credentials = [
  320. 'email' => 'jdoe@email.com',
  321. 'password' => '12345',
  322. ];
  323. $user = $this->makeLdapUser();
  324. Resolver::shouldReceive('byCredentials')->twice()->andReturn($user)
  325. ->shouldReceive('getDatabaseIdColumn')->andReturn('objectguid')
  326. ->shouldReceive('getDatabaseUsernameColumn')->andReturn('email')
  327. ->shouldReceive('getLdapDiscoveryAttribute')->andReturn('userprincipalname')
  328. ->shouldReceive('byModel')->once()->andReturn($user)
  329. ->shouldReceive('authenticate')->twice()->andReturn(true);
  330. $this->assertTrue(Auth::attempt($credentials));
  331. EloquentUser::first()->delete();
  332. $this->assertFalse(Auth::attempt($credentials));
  333. }
  334. /** @test */
  335. public function only_imported_users_are_allowed_to_authenticate_when_rule_is_applied()
  336. {
  337. config([
  338. 'ldap_auth.login_fallback' => false,
  339. 'ldap_auth.rules' => [\Adldap\Laravel\Validation\Rules\OnlyImported::class],
  340. ]);
  341. $credentials = [
  342. 'email' => 'jdoe@email.com',
  343. 'password' => '12345',
  344. ];
  345. $user = $this->makeLdapUser();
  346. Resolver::shouldReceive('byCredentials')->once()->andReturn($user)
  347. ->shouldReceive('getDatabaseIdColumn')->andReturn('objectguid')
  348. ->shouldReceive('getDatabaseUsernameColumn')->andReturn('email')
  349. ->shouldReceive('getLdapDiscoveryAttribute')->andReturn('userprincipalname')
  350. ->shouldReceive('authenticate')->once()->andReturn(true);
  351. $this->assertFalse(Auth::attempt($credentials));
  352. }
  353. /** @test */
  354. public function method_calls_are_passed_to_fallback_provider()
  355. {
  356. $this->assertEquals('Adldap\Laravel\Tests\Models\TestUser', Auth::getProvider()->getModel());
  357. }
  358. }