mirror of
https://github.com/nextcloud/server.git
synced 2025-03-14 00:13:56 +00:00

When using nested groups without a memberof overlay, then fetchListOfGroups is called from getGroupsByMember without applying the group filter. In some setups, the "unfiltered" result is then written back to the group mapping table. That might cause random "An administrator removed you from group" activities. I was unable to replicate it locally, but we got the feedback that the random activities stopped with the patch applied. Ref: https://github.com/nextcloud/server/issues/42195 Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
771 lines
21 KiB
PHP
771 lines
21 KiB
PHP
<?php
|
|
/**
|
|
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
namespace OCA\User_LDAP\Tests;
|
|
|
|
use OC\ServerNotAvailableException;
|
|
use OCA\User_LDAP\Access;
|
|
use OCA\User_LDAP\Connection;
|
|
use OCA\User_LDAP\Exceptions\ConstraintViolationException;
|
|
use OCA\User_LDAP\Helper;
|
|
use OCA\User_LDAP\ILDAPWrapper;
|
|
use OCA\User_LDAP\LDAP;
|
|
use OCA\User_LDAP\Mapping\GroupMapping;
|
|
use OCA\User_LDAP\Mapping\UserMapping;
|
|
use OCA\User_LDAP\User\Manager;
|
|
use OCA\User_LDAP\User\OfflineUser;
|
|
use OCA\User_LDAP\User\User;
|
|
use OCP\EventDispatcher\IEventDispatcher;
|
|
use OCP\HintException;
|
|
use OCP\IAppConfig;
|
|
use OCP\IAvatarManager;
|
|
use OCP\IConfig;
|
|
use OCP\IDBConnection;
|
|
use OCP\Image;
|
|
use OCP\IUserManager;
|
|
use OCP\Notification\IManager as INotificationManager;
|
|
use OCP\Server;
|
|
use OCP\Share\IManager;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use Psr\Log\LoggerInterface;
|
|
use Test\TestCase;
|
|
|
|
/**
|
|
* Class AccessTest
|
|
*
|
|
* @group DB
|
|
*
|
|
* @package OCA\User_LDAP\Tests
|
|
*/
|
|
class AccessTest extends TestCase {
|
|
/** @var UserMapping|MockObject */
|
|
protected $userMapper;
|
|
/** @var IManager|MockObject */
|
|
protected $shareManager;
|
|
/** @var GroupMapping|MockObject */
|
|
protected $groupMapper;
|
|
/** @var Connection|MockObject */
|
|
private $connection;
|
|
/** @var LDAP|MockObject */
|
|
private $ldap;
|
|
/** @var Manager|MockObject */
|
|
private $userManager;
|
|
/** @var Helper|MockObject */
|
|
private $helper;
|
|
/** @var IConfig|MockObject */
|
|
private $config;
|
|
/** @var IUserManager|MockObject */
|
|
private $ncUserManager;
|
|
|
|
private LoggerInterface&MockObject $logger;
|
|
|
|
private IAppConfig&MockObject $appConfig;
|
|
|
|
/** @var IEventDispatcher|MockObject */
|
|
private $dispatcher;
|
|
private Access $access;
|
|
|
|
protected function setUp(): void {
|
|
$this->connection = $this->createMock(Connection::class);
|
|
$this->ldap = $this->createMock(LDAP::class);
|
|
$this->userManager = $this->createMock(Manager::class);
|
|
$this->helper = $this->createMock(Helper::class);
|
|
$this->config = $this->createMock(IConfig::class);
|
|
$this->userMapper = $this->createMock(UserMapping::class);
|
|
$this->groupMapper = $this->createMock(GroupMapping::class);
|
|
$this->ncUserManager = $this->createMock(IUserManager::class);
|
|
$this->shareManager = $this->createMock(IManager::class);
|
|
$this->logger = $this->createMock(LoggerInterface::class);
|
|
$this->appConfig = $this->createMock(IAppConfig::class);
|
|
$this->dispatcher = $this->createMock(IEventDispatcher::class);
|
|
|
|
$this->access = new Access(
|
|
$this->ldap,
|
|
$this->connection,
|
|
$this->userManager,
|
|
$this->helper,
|
|
$this->config,
|
|
$this->ncUserManager,
|
|
$this->logger,
|
|
$this->appConfig,
|
|
$this->dispatcher,
|
|
);
|
|
$this->dispatcher->expects($this->any())->method('dispatchTyped');
|
|
$this->access->setUserMapper($this->userMapper);
|
|
$this->access->setGroupMapper($this->groupMapper);
|
|
}
|
|
|
|
private function getConnectorAndLdapMock() {
|
|
/** @var ILDAPWrapper&MockObject */
|
|
$lw = $this->createMock(ILDAPWrapper::class);
|
|
/** @var Connection&MockObject */
|
|
$connector = $this->getMockBuilder(Connection::class)
|
|
->setConstructorArgs([$lw, '', null])
|
|
->getMock();
|
|
$connector->expects($this->any())
|
|
->method('getConnectionResource')
|
|
->willReturn(ldap_connect('ldap://example.com'));
|
|
/** @var Manager&MockObject */
|
|
$um = $this->getMockBuilder(Manager::class)
|
|
->setConstructorArgs([
|
|
$this->createMock(IConfig::class),
|
|
$this->createMock(LoggerInterface::class),
|
|
$this->createMock(IAvatarManager::class),
|
|
$this->createMock(Image::class),
|
|
$this->createMock(IUserManager::class),
|
|
$this->createMock(INotificationManager::class),
|
|
$this->shareManager])
|
|
->getMock();
|
|
$helper = new Helper(Server::get(IConfig::class), Server::get(IDBConnection::class));
|
|
|
|
return [$lw, $connector, $um, $helper];
|
|
}
|
|
|
|
public function testEscapeFilterPartValidChars(): void {
|
|
$input = 'okay';
|
|
$this->assertTrue($input === $this->access->escapeFilterPart($input));
|
|
}
|
|
|
|
public function testEscapeFilterPartEscapeWildcard(): void {
|
|
$input = '*';
|
|
$expected = '\\2a';
|
|
$this->assertTrue($expected === $this->access->escapeFilterPart($input));
|
|
}
|
|
|
|
public function testEscapeFilterPartEscapeWildcard2(): void {
|
|
$input = 'foo*bar';
|
|
$expected = 'foo\\2abar';
|
|
$this->assertTrue($expected === $this->access->escapeFilterPart($input));
|
|
}
|
|
|
|
/**
|
|
* @dataProvider convertSID2StrSuccessData
|
|
* @param array $sidArray
|
|
* @param $sidExpected
|
|
*/
|
|
public function testConvertSID2StrSuccess(array $sidArray, $sidExpected): void {
|
|
$sidBinary = implode('', $sidArray);
|
|
$this->assertSame($sidExpected, $this->access->convertSID2Str($sidBinary));
|
|
}
|
|
|
|
public function convertSID2StrSuccessData() {
|
|
return [
|
|
[
|
|
[
|
|
"\x01",
|
|
"\x04",
|
|
"\x00\x00\x00\x00\x00\x05",
|
|
"\x15\x00\x00\x00",
|
|
"\xa6\x81\xe5\x0e",
|
|
"\x4d\x6c\x6c\x2b",
|
|
"\xca\x32\x05\x5f",
|
|
],
|
|
'S-1-5-21-249921958-728525901-1594176202',
|
|
],
|
|
[
|
|
[
|
|
"\x01",
|
|
"\x02",
|
|
"\xFF\xFF\xFF\xFF\xFF\xFF",
|
|
"\xFF\xFF\xFF\xFF",
|
|
"\xFF\xFF\xFF\xFF",
|
|
],
|
|
'S-1-281474976710655-4294967295-4294967295',
|
|
],
|
|
];
|
|
}
|
|
|
|
public function testConvertSID2StrInputError(): void {
|
|
$sidIllegal = 'foobar';
|
|
$sidExpected = '';
|
|
|
|
$this->assertSame($sidExpected, $this->access->convertSID2Str($sidIllegal));
|
|
}
|
|
|
|
public function testGetDomainDNFromDNSuccess(): void {
|
|
$inputDN = 'uid=zaphod,cn=foobar,dc=my,dc=server,dc=com';
|
|
$domainDN = 'dc=my,dc=server,dc=com';
|
|
|
|
$this->ldap->expects($this->once())
|
|
->method('explodeDN')
|
|
->with($inputDN, 0)
|
|
->willReturn(explode(',', $inputDN));
|
|
|
|
$this->assertSame($domainDN, $this->access->getDomainDNFromDN($inputDN));
|
|
}
|
|
|
|
public function testGetDomainDNFromDNError(): void {
|
|
$inputDN = 'foobar';
|
|
$expected = '';
|
|
|
|
$this->ldap->expects($this->once())
|
|
->method('explodeDN')
|
|
->with($inputDN, 0)
|
|
->willReturn(false);
|
|
|
|
$this->assertSame($expected, $this->access->getDomainDNFromDN($inputDN));
|
|
}
|
|
|
|
public function dnInputDataProvider() {
|
|
return [[
|
|
[
|
|
'input' => 'foo=bar,bar=foo,dc=foobar',
|
|
'interResult' => [
|
|
'count' => 3,
|
|
0 => 'foo=bar',
|
|
1 => 'bar=foo',
|
|
2 => 'dc=foobar'
|
|
],
|
|
'expectedResult' => true
|
|
],
|
|
[
|
|
'input' => 'foobarbarfoodcfoobar',
|
|
'interResult' => false,
|
|
'expectedResult' => false
|
|
]
|
|
]];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider dnInputDataProvider
|
|
* @param array $case
|
|
*/
|
|
public function testStringResemblesDN($case): void {
|
|
[$lw, $con, $um, $helper] = $this->getConnectorAndLdapMock();
|
|
/** @var IConfig|MockObject $config */
|
|
$config = $this->createMock(IConfig::class);
|
|
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->logger, $this->appConfig, $this->dispatcher);
|
|
|
|
$lw->expects($this->exactly(1))
|
|
->method('explodeDN')
|
|
->willReturnCallback(function ($dn) use ($case) {
|
|
if ($dn === $case['input']) {
|
|
return $case['interResult'];
|
|
}
|
|
return null;
|
|
});
|
|
|
|
$this->assertSame($case['expectedResult'], $access->stringResemblesDN($case['input']));
|
|
}
|
|
|
|
/**
|
|
* @dataProvider dnInputDataProvider
|
|
* @param $case
|
|
*/
|
|
public function testStringResemblesDNLDAPmod($case): void {
|
|
[, $con, $um, $helper] = $this->getConnectorAndLdapMock();
|
|
/** @var IConfig|MockObject $config */
|
|
$config = $this->createMock(IConfig::class);
|
|
$lw = new LDAP();
|
|
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->logger, $this->appConfig, $this->dispatcher);
|
|
|
|
if (!function_exists('ldap_explode_dn')) {
|
|
$this->markTestSkipped('LDAP Module not available');
|
|
}
|
|
|
|
$this->assertSame($case['expectedResult'], $access->stringResemblesDN($case['input']));
|
|
}
|
|
|
|
public function testCacheUserHome(): void {
|
|
$this->connection->expects($this->once())
|
|
->method('writeToCache');
|
|
|
|
$this->access->cacheUserHome('foobar', '/foobars/path');
|
|
}
|
|
|
|
public function testBatchApplyUserAttributes(): void {
|
|
$this->ldap->expects($this->any())
|
|
->method('isResource')
|
|
->willReturn(true);
|
|
|
|
$this->connection
|
|
->expects($this->any())
|
|
->method('getConnectionResource')
|
|
->willReturn(ldap_connect('ldap://example.com'));
|
|
|
|
$this->ldap->expects($this->any())
|
|
->method('getAttributes')
|
|
->willReturn(['displayname' => ['bar', 'count' => 1]]);
|
|
|
|
/** @var UserMapping|MockObject $mapperMock */
|
|
$mapperMock = $this->createMock(UserMapping::class);
|
|
$mapperMock->expects($this->any())
|
|
->method('getNameByDN')
|
|
->willReturn(false);
|
|
$mapperMock->expects($this->any())
|
|
->method('map')
|
|
->willReturn(true);
|
|
|
|
$userMock = $this->createMock(User::class);
|
|
|
|
// also returns for userUuidAttribute
|
|
$this->access->connection->expects($this->any())
|
|
->method('__get')
|
|
->willReturn('displayName');
|
|
|
|
$this->access->setUserMapper($mapperMock);
|
|
|
|
$displayNameAttribute = strtolower($this->access->connection->ldapUserDisplayName);
|
|
$data = [
|
|
[
|
|
'dn' => ['foobar'],
|
|
$displayNameAttribute => 'barfoo'
|
|
],
|
|
[
|
|
'dn' => ['foo'],
|
|
$displayNameAttribute => 'bar'
|
|
],
|
|
[
|
|
'dn' => ['raboof'],
|
|
$displayNameAttribute => 'oofrab'
|
|
]
|
|
];
|
|
|
|
$userMock->expects($this->exactly(count($data)))
|
|
->method('processAttributes');
|
|
|
|
$this->userManager->expects($this->exactly(count($data) * 2))
|
|
->method('get')
|
|
->willReturn($userMock);
|
|
|
|
$this->access->batchApplyUserAttributes($data);
|
|
}
|
|
|
|
public function testBatchApplyUserAttributesSkipped(): void {
|
|
/** @var UserMapping|MockObject $mapperMock */
|
|
$mapperMock = $this->createMock(UserMapping::class);
|
|
$mapperMock->expects($this->any())
|
|
->method('getNameByDN')
|
|
->willReturn('a_username');
|
|
|
|
$userMock = $this->createMock(User::class);
|
|
|
|
$this->access->connection->expects($this->any())
|
|
->method('__get')
|
|
->willReturn('displayName');
|
|
|
|
$this->access->setUserMapper($mapperMock);
|
|
|
|
$displayNameAttribute = strtolower($this->access->connection->ldapUserDisplayName);
|
|
$data = [
|
|
[
|
|
'dn' => ['foobar'],
|
|
$displayNameAttribute => 'barfoo'
|
|
],
|
|
[
|
|
'dn' => ['foo'],
|
|
$displayNameAttribute => 'bar'
|
|
],
|
|
[
|
|
'dn' => ['raboof'],
|
|
$displayNameAttribute => 'oofrab'
|
|
]
|
|
];
|
|
|
|
$userMock->expects($this->never())
|
|
->method('processAttributes');
|
|
|
|
$this->userManager->expects($this->any())
|
|
->method('get')
|
|
->willReturn($this->createMock(User::class));
|
|
|
|
$this->access->batchApplyUserAttributes($data);
|
|
}
|
|
|
|
public function testBatchApplyUserAttributesDontSkip(): void {
|
|
/** @var UserMapping|MockObject $mapperMock */
|
|
$mapperMock = $this->createMock(UserMapping::class);
|
|
$mapperMock->expects($this->any())
|
|
->method('getNameByDN')
|
|
->willReturn('a_username');
|
|
|
|
$userMock = $this->createMock(User::class);
|
|
|
|
$this->access->connection->expects($this->any())
|
|
->method('__get')
|
|
->willReturn('displayName');
|
|
|
|
$this->access->setUserMapper($mapperMock);
|
|
|
|
$displayNameAttribute = strtolower($this->access->connection->ldapUserDisplayName);
|
|
$data = [
|
|
[
|
|
'dn' => ['foobar'],
|
|
$displayNameAttribute => 'barfoo'
|
|
],
|
|
[
|
|
'dn' => ['foo'],
|
|
$displayNameAttribute => 'bar'
|
|
],
|
|
[
|
|
'dn' => ['raboof'],
|
|
$displayNameAttribute => 'oofrab'
|
|
]
|
|
];
|
|
|
|
$userMock->expects($this->exactly(count($data)))
|
|
->method('processAttributes');
|
|
|
|
$this->userManager->expects($this->exactly(count($data) * 2))
|
|
->method('get')
|
|
->willReturn($userMock);
|
|
|
|
$this->access->batchApplyUserAttributes($data);
|
|
}
|
|
|
|
public function dNAttributeProvider() {
|
|
// corresponds to Access::resemblesDN()
|
|
return [
|
|
'dn' => ['dn'],
|
|
'uniqueMember' => ['uniquemember'],
|
|
'member' => ['member'],
|
|
'memberOf' => ['memberof']
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider dNAttributeProvider
|
|
* @param $attribute
|
|
*/
|
|
public function testSanitizeDN($attribute): void {
|
|
[$lw, $con, $um, $helper] = $this->getConnectorAndLdapMock();
|
|
/** @var IConfig|MockObject $config */
|
|
$config = $this->createMock(IConfig::class);
|
|
|
|
$dnFromServer = 'cn=Mixed Cases,ou=Are Sufficient To,ou=Test,dc=example,dc=org';
|
|
|
|
$lw->expects($this->any())
|
|
->method('isResource')
|
|
->willReturn(true);
|
|
$lw->expects($this->any())
|
|
->method('getAttributes')
|
|
->willReturn([
|
|
$attribute => ['count' => 1, $dnFromServer]
|
|
]);
|
|
|
|
$access = new Access($lw, $con, $um, $helper, $config, $this->ncUserManager, $this->logger, $this->appConfig, $this->dispatcher);
|
|
$values = $access->readAttribute('uid=whoever,dc=example,dc=org', $attribute);
|
|
$this->assertSame($values[0], strtolower($dnFromServer));
|
|
}
|
|
|
|
|
|
public function testSetPasswordWithDisabledChanges(): void {
|
|
$this->expectException(\Exception::class);
|
|
$this->expectExceptionMessage('LDAP password changes are disabled');
|
|
|
|
$this->connection
|
|
->method('__get')
|
|
->willReturn(false);
|
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */
|
|
$this->access->setPassword('CN=foo', 'MyPassword');
|
|
}
|
|
|
|
public function testSetPasswordWithLdapNotAvailable(): void {
|
|
$this->connection
|
|
->method('__get')
|
|
->willReturn(true);
|
|
$connection = ldap_connect('ldap://example.com');
|
|
$this->connection
|
|
->expects($this->once())
|
|
->method('getConnectionResource')
|
|
->willThrowException(new ServerNotAvailableException('Connection to LDAP server could not be established'));
|
|
$this->ldap
|
|
->expects($this->never())
|
|
->method('isResource');
|
|
|
|
$this->expectException(ServerNotAvailableException::class);
|
|
$this->expectExceptionMessage('Connection to LDAP server could not be established');
|
|
$this->access->setPassword('CN=foo', 'MyPassword');
|
|
}
|
|
|
|
|
|
public function testSetPasswordWithRejectedChange(): void {
|
|
$this->expectException(HintException::class);
|
|
$this->expectExceptionMessage('Password change rejected.');
|
|
|
|
$this->connection
|
|
->method('__get')
|
|
->willReturn(true);
|
|
$connection = ldap_connect('ldap://example.com');
|
|
$this->connection
|
|
->expects($this->any())
|
|
->method('getConnectionResource')
|
|
->willReturn($connection);
|
|
$this->ldap
|
|
->expects($this->once())
|
|
->method('modReplace')
|
|
->with($connection, 'CN=foo', 'MyPassword')
|
|
->willThrowException(new ConstraintViolationException());
|
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */
|
|
$this->access->setPassword('CN=foo', 'MyPassword');
|
|
}
|
|
|
|
public function testSetPassword(): void {
|
|
$this->connection
|
|
->method('__get')
|
|
->willReturn(true);
|
|
$connection = ldap_connect('ldap://example.com');
|
|
$this->connection
|
|
->expects($this->any())
|
|
->method('getConnectionResource')
|
|
->willReturn($connection);
|
|
$this->ldap
|
|
->expects($this->once())
|
|
->method('modReplace')
|
|
->with($connection, 'CN=foo', 'MyPassword')
|
|
->willReturn(true);
|
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */
|
|
$this->assertTrue($this->access->setPassword('CN=foo', 'MyPassword'));
|
|
}
|
|
|
|
protected function prepareMocksForSearchTests(
|
|
$base,
|
|
$fakeConnection,
|
|
$fakeSearchResultResource,
|
|
$fakeLdapEntries,
|
|
) {
|
|
$this->connection
|
|
->expects($this->any())
|
|
->method('getConnectionResource')
|
|
->willReturn($fakeConnection);
|
|
$this->connection->expects($this->any())
|
|
->method('__get')
|
|
->willReturnCallback(function ($key) use ($base) {
|
|
if (stripos($key, 'base') !== false) {
|
|
return [$base];
|
|
}
|
|
return null;
|
|
});
|
|
|
|
$this->ldap
|
|
->expects($this->any())
|
|
->method('isResource')
|
|
->willReturnCallback(function ($resource) {
|
|
return is_object($resource);
|
|
});
|
|
$this->ldap
|
|
->expects($this->any())
|
|
->method('errno')
|
|
->willReturn(0);
|
|
$this->ldap
|
|
->expects($this->once())
|
|
->method('search')
|
|
->willReturn($fakeSearchResultResource);
|
|
$this->ldap
|
|
->expects($this->exactly(1))
|
|
->method('getEntries')
|
|
->willReturn($fakeLdapEntries);
|
|
|
|
$this->helper->expects($this->any())
|
|
->method('sanitizeDN')
|
|
->willReturnArgument(0);
|
|
}
|
|
|
|
public function testSearchNoPagedSearch(): void {
|
|
// scenario: no pages search, 1 search base
|
|
$filter = 'objectClass=nextcloudUser';
|
|
$base = 'ou=zombies,dc=foobar,dc=nextcloud,dc=com';
|
|
|
|
$fakeConnection = ldap_connect();
|
|
$fakeSearchResultResource = ldap_connect();
|
|
$fakeLdapEntries = [
|
|
'count' => 2,
|
|
[
|
|
'dn' => 'uid=sgarth,' . $base,
|
|
],
|
|
[
|
|
'dn' => 'uid=wwilson,' . $base,
|
|
]
|
|
];
|
|
|
|
$expected = $fakeLdapEntries;
|
|
unset($expected['count']);
|
|
|
|
$this->prepareMocksForSearchTests($base, $fakeConnection, $fakeSearchResultResource, $fakeLdapEntries);
|
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */
|
|
$result = $this->access->search($filter, $base);
|
|
$this->assertSame($expected, $result);
|
|
}
|
|
|
|
public function testFetchListOfUsers(): void {
|
|
$filter = 'objectClass=nextcloudUser';
|
|
$base = 'ou=zombies,dc=foobar,dc=nextcloud,dc=com';
|
|
$attrs = ['dn', 'uid'];
|
|
|
|
$fakeConnection = ldap_connect();
|
|
$fakeSearchResultResource = ldap_connect();
|
|
$fakeLdapEntries = [
|
|
'count' => 2,
|
|
[
|
|
'dn' => 'uid=sgarth,' . $base,
|
|
'uid' => [ 'sgarth' ],
|
|
],
|
|
[
|
|
'dn' => 'uid=wwilson,' . $base,
|
|
'uid' => [ 'wwilson' ],
|
|
]
|
|
];
|
|
$expected = $fakeLdapEntries;
|
|
unset($expected['count']);
|
|
array_walk($expected, function (&$v): void {
|
|
$v['dn'] = [$v['dn']]; // dn is translated into an array internally for consistency
|
|
});
|
|
|
|
$this->prepareMocksForSearchTests($base, $fakeConnection, $fakeSearchResultResource, $fakeLdapEntries);
|
|
|
|
// Called twice per user, for userExists and userExistsOnLdap
|
|
$this->connection->expects($this->exactly(2 * $fakeLdapEntries['count']))
|
|
->method('writeToCache')
|
|
->with($this->stringStartsWith('userExists'), true);
|
|
|
|
$this->userMapper->expects($this->exactly($fakeLdapEntries['count']))
|
|
->method('getNameByDN')
|
|
->willReturnCallback(function ($fdn) {
|
|
$parts = ldap_explode_dn($fdn, false);
|
|
return $parts[0];
|
|
});
|
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */
|
|
$list = $this->access->fetchListOfUsers($filter, $attrs);
|
|
$this->assertSame($expected, $list);
|
|
}
|
|
|
|
public function testFetchListOfGroupsKnown(): void {
|
|
$filter = 'objectClass=nextcloudGroup';
|
|
$attributes = ['cn', 'gidNumber', 'dn'];
|
|
$base = 'ou=SomeGroups,dc=my,dc=directory';
|
|
|
|
$fakeConnection = ldap_connect();
|
|
$fakeSearchResultResource = ldap_connect();
|
|
$fakeLdapEntries = [
|
|
'count' => 2,
|
|
[
|
|
'dn' => 'cn=Good Team,' . $base,
|
|
'cn' => ['Good Team'],
|
|
],
|
|
[
|
|
'dn' => 'cn=Another Good Team,' . $base,
|
|
'cn' => ['Another Good Team'],
|
|
]
|
|
];
|
|
|
|
$this->prepareMocksForSearchTests($base, $fakeConnection, $fakeSearchResultResource, $fakeLdapEntries);
|
|
|
|
$this->groupMapper->expects($this->any())
|
|
->method('getListOfIdsByDn')
|
|
->willReturn([
|
|
'cn=Good Team,' . $base => 'Good_Team',
|
|
'cn=Another Good Team,' . $base => 'Another_Good_Team',
|
|
]);
|
|
$this->groupMapper->expects($this->never())
|
|
->method('getNameByDN');
|
|
|
|
$this->connection->expects($this->exactly(1))
|
|
->method('writeToCache');
|
|
|
|
$groups = $this->access->fetchListOfGroups($filter, $attributes);
|
|
$this->assertSame(2, count($groups));
|
|
$this->assertSame('Good Team', $groups[0]['cn'][0]);
|
|
$this->assertSame('Another Good Team', $groups[1]['cn'][0]);
|
|
}
|
|
|
|
public function intUsernameProvider() {
|
|
return [
|
|
['alice', 'alice'],
|
|
['b/ob', 'bob'],
|
|
['charly🐬', 'charly'],
|
|
['debo rah', 'debo_rah'],
|
|
['epost@poste.test', 'epost@poste.test'],
|
|
['fränk', 'frank'],
|
|
[' UPPÉR Case/[\]^`', 'UPPER_Case'],
|
|
[' gerda ', 'gerda'],
|
|
['🕱🐵🐘🐑', null],
|
|
[
|
|
'OneNameToRuleThemAllOneNameToFindThemOneNameToBringThemAllAndInTheDarknessBindThem',
|
|
'81ff71b5dd0f0092e2dc977b194089120093746e273f2ef88c11003762783127'
|
|
]
|
|
];
|
|
}
|
|
|
|
public function groupIDCandidateProvider() {
|
|
return [
|
|
['alice', 'alice'],
|
|
['b/ob', 'b/ob'],
|
|
['charly🐬', 'charly🐬'],
|
|
['debo rah', 'debo rah'],
|
|
['epost@poste.test', 'epost@poste.test'],
|
|
['fränk', 'fränk'],
|
|
[' gerda ', 'gerda'],
|
|
['🕱🐵🐘🐑', '🕱🐵🐘🐑'],
|
|
[
|
|
'OneNameToRuleThemAllOneNameToFindThemOneNameToBringThemAllAndInTheDarknessBindThem',
|
|
'81ff71b5dd0f0092e2dc977b194089120093746e273f2ef88c11003762783127'
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider intUsernameProvider
|
|
*
|
|
* @param $name
|
|
* @param $expected
|
|
*/
|
|
public function testSanitizeUsername($name, $expected): void {
|
|
if ($expected === null) {
|
|
$this->expectException(\InvalidArgumentException::class);
|
|
}
|
|
$sanitizedName = $this->access->sanitizeUsername($name);
|
|
$this->assertSame($expected, $sanitizedName);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider groupIDCandidateProvider
|
|
*/
|
|
public function testSanitizeGroupIDCandidate(string $name, string $expected): void {
|
|
$sanitizedName = $this->access->sanitizeGroupIDCandidate($name);
|
|
$this->assertSame($expected, $sanitizedName);
|
|
}
|
|
|
|
public function testUserStateUpdate(): void {
|
|
$this->connection->expects($this->any())
|
|
->method('__get')
|
|
->willReturnMap([
|
|
[ 'ldapUserDisplayName', 'displayName' ],
|
|
[ 'ldapUserDisplayName2', null],
|
|
]);
|
|
|
|
$offlineUserMock = $this->createMock(OfflineUser::class);
|
|
$offlineUserMock->expects($this->once())
|
|
->method('unmark');
|
|
|
|
$regularUserMock = $this->createMock(User::class);
|
|
|
|
$this->userManager->expects($this->atLeastOnce())
|
|
->method('get')
|
|
->with('detta')
|
|
->willReturnOnConsecutiveCalls($offlineUserMock, $regularUserMock);
|
|
|
|
/** @var UserMapping|MockObject $mapperMock */
|
|
$mapperMock = $this->createMock(UserMapping::class);
|
|
$mapperMock->expects($this->any())
|
|
->method('getNameByDN')
|
|
->with('uid=detta,ou=users,dc=hex,dc=ample')
|
|
->willReturn('detta');
|
|
$this->access->setUserMapper($mapperMock);
|
|
|
|
$records = [
|
|
[
|
|
'dn' => ['uid=detta,ou=users,dc=hex,dc=ample'],
|
|
'displayName' => ['Detta Detkova'],
|
|
]
|
|
];
|
|
$this->access->nextcloudUserNames($records);
|
|
}
|
|
}
|