Merge remote-tracking branch 'origin/develop'

Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/dist/skins/skin-black-dark.css
#	public/css/dist/skins/skin-black-dark.min.css
#	public/mix-manifest.json
This commit is contained in:
snipe 2023-12-04 15:42:58 +00:00
commit 09ec8d1d5c
12 changed files with 484 additions and 36 deletions

View file

@ -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',

View file

@ -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",

224
composer.lock generated
View file

@ -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"
}

Binary file not shown.

Binary file not shown.

View file

@ -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",

View file

@ -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);
}

View file

@ -36,7 +36,7 @@
<!-- Serial -->
<div class="form-group">
<label class="col-sm-2 control-label">{{ trans('admin/hardware/form.serial') }}</label>
<label class="col-sm-2 control-label">{{ trans('admin/licenses/form.license_key') }}</label>
<div class="col-md-6">
<p class="form-control-static">
@can('viewKeys', $licenseSeat->license)

View file

@ -43,7 +43,7 @@
<!-- Serial -->
<div class="form-group">
<label class="col-sm-3 control-label">{{ trans('admin/hardware/form.serial') }}</label>
<label class="col-sm-3 control-label">{{ trans('admin/licenses/form.license_key') }}</label>
<div class="col-md-9">
<p class="form-control-static" style="word-wrap: break-word;">
@can('viewKeys', $license)

View file

@ -727,7 +727,7 @@
<thead>
<tr>
<th class="col-md-5">{{ trans('general.name') }}</th>
<th>{{ trans('admin/hardware/form.serial') }}</th>
<th>{{ trans('admin/licenses/form.license_key') }}</th>
<th data-footer-formatter="sumFormatter" data-fieldname="purchase_cost">{{ trans('general.purchase_cost') }}</th>
<th>{{ trans('admin/licenses/form.purchase_order') }}</th>
<th>{{ trans('general.order_number') }}</th>

View file

@ -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.
*/

210
tests/Unit/LdapTest.php Normal file
View file

@ -0,0 +1,210 @@
<?php
namespace Tests\Unit;
use App\Models\Ldap;
use Exception;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class LdapTest extends TestCase
{
use InteractsWithSettings;
use \phpmock\phpunit\PHPMock;
public function testConnect()
{
$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));
$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);
}
}