diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php index b1f1753564..a1a18634cc 100644 --- a/app/Console/Commands/RestoreFromBackup.php +++ b/app/Console/Commands/RestoreFromBackup.php @@ -91,6 +91,7 @@ class RestoreFromBackup extends Command 'storage/private_uploads/users', 'storage/private_uploads/licenses', 'storage/private_uploads/signatures', + 'storage/private_uploads/eula-pdfs', ]; $private_files = [ 'storage/oauth-private.key', diff --git a/composer.json b/composer.json index 020b2f9ca7..2a456999e9 100644 --- a/composer.json +++ b/composer.json @@ -74,12 +74,16 @@ "unicodeveloper/laravel-password": "^1.0", "watson/validating": "^6.1" }, + "suggest": { + "ext-ldap": "*" + }, "require-dev": { "brianium/paratest": "^6.6", "fakerphp/faker": "^1.16", "mockery/mockery": "^1.4", "nunomaduro/larastan": "^1.0", "nunomaduro/phpinsights": "^2.7", + "php-mock/php-mock-phpunit": "^2.8", "phpunit/php-token-stream": "^3.1", "phpunit/phpunit": "^9.0", "squizlabs/php_codesniffer": "^3.5", diff --git a/composer.lock b/composer.lock index 7ca88da10f..3d43426260 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "348f96db24a0f8dfb595ee38b38b34eb", + "content-hash": "f4f3b6b02d044ed3e54cdd509b01c3dc", "packages": [ { "name": "alek13/slack", @@ -7011,16 +7011,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.14", + "version": "3.0.34", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef" + "reference": "56c79f16a6ae17e42089c06a2144467acc35348a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2f0b7af658cbea265cbb4a791d6c29a6613f98ef", - "reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56c79f16a6ae17e42089c06a2144467acc35348a", + "reference": "56c79f16a6ae17e42089c06a2144467acc35348a", "shasum": "" }, "require": { @@ -7032,6 +7032,7 @@ "phpunit/phpunit": "*" }, "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", @@ -7100,7 +7101,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.14" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.34" }, "funding": [ { @@ -7116,7 +7117,7 @@ "type": "tidelift" } ], - "time": "2022-04-04T05:15:45+00:00" + "time": "2023-11-27T11:13:31+00:00" }, { "name": "phpspec/prophecy", @@ -14100,6 +14101,213 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-mock/php-mock", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock.git", + "reference": "6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d", + "reference": "6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0", + "phpunit/php-text-template": "^1 || ^2 || ^3" + }, + "replace": { + "malkusch/php-mock": "*" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "phpmock\\": [ + "classes/", + "tests/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/php-mock/php-mock/issues", + "source": "https://github.com/php-mock/php-mock/tree/2.4.1" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2023-06-12T20:48:52+00:00" + }, + { + "name": "php-mock/php-mock-integration", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-integration.git", + "reference": "04f4a8d5442ca457b102b5204673f77323e3edb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/04f4a8d5442ca457b102b5204673f77323e3edb5", + "reference": "04f4a8d5442ca457b102b5204673f77323e3edb5", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "php-mock/php-mock": "^2.4", + "phpunit/php-text-template": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\integration\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Integration package for PHP-Mock", + "homepage": "https://github.com/php-mock/php-mock-integration", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double" + ], + "support": { + "issues": "https://github.com/php-mock/php-mock-integration/issues", + "source": "https://github.com/php-mock/php-mock-integration/tree/2.2.1" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2023-02-13T09:51:29+00:00" + }, + { + "name": "php-mock/php-mock-phpunit", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-phpunit.git", + "reference": "56edee85ad3232caa0202f98f2a3c899ab16bdb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/56edee85ad3232caa0202f98f2a3c899ab16bdb7", + "reference": "56edee85ad3232caa0202f98f2a3c899ab16bdb7", + "shasum": "" + }, + "require": { + "php": ">=7", + "php-mock/php-mock-integration": "^2.2.1", + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17" + }, + "require-dev": { + "mockery/mockery": "^1.3.6" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "phpmock\\phpunit\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock-phpunit", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "phpunit", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/php-mock/php-mock-phpunit/issues", + "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.8.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2023-10-30T07:06:12+00:00" + }, { "name": "php-parallel-lint/php-parallel-lint", "version": "v1.3.2", @@ -16600,5 +16808,5 @@ "ext-pdo": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/public/css/dist/skins/skin-black-dark.css b/public/css/dist/skins/skin-black-dark.css index 2a32df6c9c..bdab8393c5 100644 Binary files a/public/css/dist/skins/skin-black-dark.css and b/public/css/dist/skins/skin-black-dark.css differ diff --git a/public/css/dist/skins/skin-black-dark.min.css b/public/css/dist/skins/skin-black-dark.min.css index d4bd93ca18..fa7c210124 100644 Binary files a/public/css/dist/skins/skin-black-dark.min.css and b/public/css/dist/skins/skin-black-dark.min.css differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 76e272ab0a..3fbbb10fe3 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -12,7 +12,7 @@ "/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=9f944e8021781af1ce45d27765d1c0c2", "/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=cf6c8c340420724b02d6e787ef9bded5", "/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=7f0eb9e355b36b41c61c3af3b4d41143", - "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=f4d95ad9d0944587549e35b6929b4b04", + "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=6cf460bed48ab738041f60231a3f005a", "/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=1f33ca3d860461c1127ec465ab3ebb6b", "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=0ed42b67f9b02a74815e885bfd9e3f66", "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f", @@ -37,7 +37,7 @@ "/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f", "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=0ed42b67f9b02a74815e885bfd9e3f66", "/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b", - "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=f4d95ad9d0944587549e35b6929b4b04", + "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=6cf460bed48ab738041f60231a3f005a", "/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=392cc93cfc0be0349bab9697669dd091", "/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=4a9e8c5e7b09506fa3e3a3f42849e07f", "/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=fc7adb943668ac69fe4b646625a7571f", diff --git a/resources/assets/less/skins/skin-black-dark.less b/resources/assets/less/skins/skin-black-dark.less index 69a393e80d..6f0a2204b2 100644 --- a/resources/assets/less/skins/skin-black-dark.less +++ b/resources/assets/less/skins/skin-black-dark.less @@ -124,11 +124,11 @@ a { --button-primary: darken(@black, 25%); --button-hover: darken(@black, 30%); --header: @black; /* Use same as Header picker */ - --text-main: #BBB; + --text-main: #fff; --text-sub: #9b9b9b; - --link: #AAA; /* Use same as Header picker, lighten by 70% */ - --visited-link: lighten(@black, 40%); /* Use same as Header picker, lighten by 70% */ - --hover-link: lighten(@black, 45%); /* Use same as Header picker, lighten by 70% */ + --link: #fff; /* Use same as Header picker, lighten by 70% */ + --visited-link: #fff; /* Use same as Header picker, lighten by 70% */ + --hover-link: #949494; /* Use same as Header picker, lighten by 70% */ --nav-link: #FFF; /* Use same as Header picker */ --light-link: #fff; /* Use same as Header picker */ } @@ -193,18 +193,6 @@ h2.task_menu{ color: var(--text-main); } -a:link { - color: var(--link); -} - -a:visited { - color: var(--nav-link); -} - -a:hover { - color: var(--hover-link); -} - .btn-primary.hover { color: var(--nav-link); } @@ -318,8 +306,11 @@ input[type=text], input[type=search] { background-color: var(--back-sub); color: var(--text-main); } +.search-highlight, .search-highlight:hover{ + background-color: var(--back-sub) !important; +} .input-group, .input-group-addon { - background-color: var(--back-sub)!important; + background-color: var(--back-sub); color: var(--text-main); } #licensesTable>tbody>tr>td>nobr>a>i.fa { @@ -363,11 +354,11 @@ input[type=text], input[type=search] { } .select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover { background-color: var(--back-sub); - color: var(--header); + color: var(--nav-link); } .select2-container--default .select2-results__option--highlighted[aria-selected] { - background-color: var(--header); - color: var(--back-main); + background-color: var(--back-sub); + color: var(--visited-link); } .select2-container--default .select2-selection--single .select2-selection__rendered { color: var(--text-main); @@ -426,9 +417,6 @@ a { color: var(--hover-link); text-decoration: underline; } - &:visited { - color: var(--visited-link) - } } .row-striped { @@ -463,4 +451,7 @@ a { } div.container.row-new-striped{ background-color: var(--back-sub); +} +.table > thead > tr > td.danger, .table > tbody > tr > td.danger, .table > tfoot > tr > td.danger, .table > thead > tr > th.danger, .table > tbody > tr > th.danger, .table > tfoot > tr > th.danger, .table > thead > tr.danger > td, .table > tbody > tr.danger > td, .table > tfoot > tr.danger > td, .table > thead > tr.danger > th, .table > tbody > tr.danger > th, .table > tfoot > tr.danger > th { + background-color: var(--back-sub); } \ No newline at end of file diff --git a/resources/views/licenses/checkin.blade.php b/resources/views/licenses/checkin.blade.php index 075eaa6206..8eeea6f9c4 100755 --- a/resources/views/licenses/checkin.blade.php +++ b/resources/views/licenses/checkin.blade.php @@ -36,7 +36,7 @@
- +

@can('viewKeys', $licenseSeat->license) diff --git a/resources/views/licenses/checkout.blade.php b/resources/views/licenses/checkout.blade.php index a2672be427..e85c677420 100755 --- a/resources/views/licenses/checkout.blade.php +++ b/resources/views/licenses/checkout.blade.php @@ -43,7 +43,7 @@

- +

@can('viewKeys', $license) diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index 0ed5c93b80..5de2d754fb 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -727,7 +727,7 @@ {{ trans('general.name') }} - {{ trans('admin/hardware/form.serial') }} + {{ trans('admin/licenses/form.license_key') }} {{ trans('general.purchase_cost') }} {{ trans('admin/licenses/form.purchase_order') }} {{ trans('general.order_number') }} diff --git a/tests/Support/Settings.php b/tests/Support/Settings.php index 17f8af23d2..30fe8c7017 100644 --- a/tests/Support/Settings.php +++ b/tests/Support/Settings.php @@ -3,6 +3,7 @@ namespace Tests\Support; use App\Models\Setting; +use Illuminate\Support\Facades\Crypt; class Settings { @@ -67,6 +68,39 @@ class Settings } + public function enableLdap(): Settings + { + return $this->update([ + 'ldap_enabled' => 1, + 'ldap_server' => 'ldaps://ldap.example.com', + 'ldap_uname' => 'fake_username', + 'ldap_pword' => Crypt::encrypt("fake_password"), + 'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com' + ]); + } + + public function enableAnonymousLdap(): Settings + { + return $this->update([ + 'ldap_enabled' => 1, + 'ldap_server' => 'ldaps://ldap.example.com', +// 'ldap_uname' => 'fake_username', + 'ldap_pword' => Crypt::encrypt("fake_password"), + 'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com' + ]); + } + + public function enableBadPasswordLdap(): Settings + { + return $this->update([ + 'ldap_enabled' => 1, + 'ldap_server' => 'ldaps://ldap.example.com', + 'ldap_uname' => 'fake_username', + 'ldap_pword' => "badly_encrypted_password!", + 'ldap_basedn' => 'CN=Users,DC=ad,DC=example,Dc=com' + ]); + } + /** * @param array $attributes Attributes to modify in the application's settings. */ diff --git a/tests/Unit/LdapTest.php b/tests/Unit/LdapTest.php new file mode 100644 index 0000000000..bae4f3ff4c --- /dev/null +++ b/tests/Unit/LdapTest.php @@ -0,0 +1,210 @@ +settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + + $blah = Ldap::connectToLdap(); + $this->assertEquals('hello',$blah,"LDAP_connect should return 'hello'"); + } + + // other test cases - with/without client-side certs? + // with/without LDAP version 3? + // with/without ignore cert validation? + // test (and mock) ldap_start_tls() ? + + public function testBindAdmin() + { + $this->settings->enableLdap(); + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + + public function testBindBad() + { + $this->settings->enableLdap(); + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(false); + $this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception"); + $this->expectExceptionMessage("Could not bind to LDAP:"); + + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + // other test cases - test donked password? + + public function testAnonymousBind() + { + //todo - would be nice to introspect somehow to make sure the right parameters were passed? + $this->settings->enableAnonymousLdap(); + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + + public function testBadAnonymousBind() + { + $this->settings->enableAnonymousLdap(); + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(false); + $this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception"); + $this->expectExceptionMessage("Could not bind to LDAP:"); + + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + + public function testBadEncryptedPassword() + { + $this->settings->enableBadPasswordLdap(); + + $this->expectExceptionMessage("Your app key has changed"); + $this->assertNull(Ldap::bindAdminToLdap("dummy")); + } + + public function testFindAndBind() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_first_entry")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_get_attributes")->expects($this->once())->willReturn( + [ + "count" => 1, + 0 => [ + 'sn' => 'Surname', + 'firstName' => 'FirstName' + ] + ] + ); + + $results = Ldap::findAndBindUserLdap("username","password"); + $this->assertEqualsCanonicalizing(["count" =>1,0 =>['sn' => 'Surname','firstname' => 'FirstName']],$results); + } + + public function testFindAndBindBadPassword() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + // note - we return FALSE first, to simulate a bad-bind, then TRUE the second time to simulate a successful admin bind + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->exactly(2))->willReturn(false, true); + +// $this->getFunctionMock("App\\Models","ldap_error")->expects($this->once())->willReturn("exception"); + + +// $this->expectExceptionMessage("exception"); + $results = Ldap::findAndBindUserLdap("username","password"); + $this->assertFalse($results); + } + + public function testFindAndBindCannotFindSelf() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(false); + + $this->expectExceptionMessage("Could not search LDAP:"); + $results = Ldap::findAndBindUserLdap("username","password"); + $this->assertFalse($results); + } + + //maybe should do an AD test as well? + + public function testFindLdapUsers() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_search")->expects($this->once())->willReturn(["stuff"]); + + $this->getFunctionMock("App\\Models", "ldap_parse_result")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_get_entries")->expects($this->once())->willReturn(["count" => 1]); + + $results = Ldap::findLdapUsers(); + + $this->assertEqualsCanonicalizing(["count" => 1], $results); + } + + public function testFindLdapUsersPaginated() + { + $this->settings->enableLdap(); + + $ldap_connect = $this->getFunctionMock("App\\Models", "ldap_connect"); + $ldap_connect->expects($this->once())->willReturn('hello'); + + $ldap_set_option = $this->getFunctionMock("App\\Models", "ldap_set_option"); + $ldap_set_option->expects($this->exactly(3)); + + $this->getFunctionMock("App\\Models", "ldap_bind")->expects($this->once())->willReturn(true); + + $this->getFunctionMock("App\\Models", "ldap_search")->expects($this->exactly(2))->willReturn(["stuff"]); + + $this->getFunctionMock("App\\Models", "ldap_parse_result")->expects($this->exactly(2))->willReturnCallback( + function ($ldapconn, $search_results, $errcode , $matcheddn , $errmsg , $referrals, &$controls) { + static $count = 0; + if($count == 0) { + $count++; + $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] = "cookie"; + return ["count" => 1]; + } else { + $controls = []; + return ["count" => 1]; + } + + } + ); + + $this->getFunctionMock("App\\Models", "ldap_get_entries")->expects($this->exactly(2))->willReturn(["count" => 1]); + + $results = Ldap::findLdapUsers(); + + $this->assertEqualsCanonicalizing(["count" => 2], $results); + } + +}