LDAP authentication support (#815)

This commit is contained in:
Kevin Papst
2019-06-07 22:48:39 +02:00
committed by GitHub
parent bcf1ebd778
commit 0c0e9c2f71
99 changed files with 3542 additions and 926 deletions

View File

@@ -21,11 +21,7 @@ use Symfony\Component\HttpFoundation\Response;
*/
abstract class APIControllerBaseTest extends ControllerBaseTest
{
/**
* @param string $role
* @return Client
*/
protected function getClientForAuthenticatedUser(string $role = User::ROLE_USER)
protected function getClientForAuthenticatedUser(string $role = User::ROLE_USER): Client
{
switch ($role) {
case User::ROLE_SUPER_ADMIN:
@@ -57,7 +53,7 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
break;
default:
$client = null;
throw new \Exception(sprintf('Unknown role "%s"', $role));
break;
}
@@ -74,11 +70,6 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
return '/' . ltrim($url, '/') . ($json ? '.json' : '');
}
/**
* @param Client $client
* @param string $url
* @param string $method
*/
protected function assertRequestIsSecured(Client $client, string $url, $method = 'GET')
{
$this->request($client, $url, $method);
@@ -134,26 +125,13 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
);
}
/**
* @param Client $client
* @param string $url
* @param string $method
* @param array $parameters
* @param string $content
* @return Crawler
*/
protected function request(Client $client, string $url, $method = 'GET', array $parameters = [], string $content = null)
protected function request(Client $client, string $url, $method = 'GET', array $parameters = [], string $content = null): Crawler
{
$server = ['HTTP_CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json'];
return $client->request($method, $this->createUrl($url), $parameters, [], $server, $content);
}
/**
* @param string $role
* @param string $url
* @param string $method
*/
protected function assertEntityNotFound(string $role, string $url, string $method = 'GET')
{
$client = $this->getClientForAuthenticatedUser($role);
@@ -172,11 +150,6 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
);
}
/**
* @param string $role
* @param string $url
* @param array $data
*/
protected function assertEntityNotFoundForPatch(string $role, string $url, array $data)
{
$client = $this->getClientForAuthenticatedUser($role);
@@ -198,11 +171,6 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
);
}
/**
* @param string $role
* @param string $url
* @param array $data
*/
protected function assertEntityNotFoundForDelete(string $role, string $url, array $data)
{
$client = $this->getClientForAuthenticatedUser($role);
@@ -224,10 +192,6 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
);
}
/**
* @param Response $response
* @param string $message
*/
protected function assertApiException(Response $response, string $message)
{
$this->assertFalse($response->isSuccessful());
@@ -235,22 +199,12 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
$this->assertEquals(['code' => 500, 'message' => $message], json_decode($response->getContent(), true));
}
/**
* @param Client $client
* @param string $url
* @param string $message
*/
protected function assertApiAccessDenied(Client $client, string $url, string $message)
{
$this->request($client, $url);
$this->assertApiResponseAccessDenied($client->getResponse(), $message);
}
/**
* @param Client $client
* @param string $url
* @param string $message
*/
protected function assertApiResponseAccessDenied(Response $response, string $message)
{
$this->assertFalse($response->isSuccessful());

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\API;
namespace App\Tests\API\Model;
use App\API\Model\I18n;
use PHPUnit\Framework\TestCase;

View File

@@ -13,6 +13,7 @@ use App\Command\InstallCommand;
use App\Utils\File;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandTester;
/**
@@ -26,7 +27,7 @@ class InstallCommandTest extends KernelTestCase
*/
protected $application;
protected function getCommand($permission = 0777): InstallCommand
protected function getCommand($permission = 0777): Command
{
$fileMock = $this->getMockBuilder(File::class)->setMethods(['getPermissions'])->getMock();
$fileMock->expects($this->exactly(5))->method('getPermissions')->willReturn($permission);

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Configuration;
use App\Configuration\LdapConfiguration;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Configuration\LdapConfiguration
*/
class LdapConfigurationTest extends TestCase
{
protected function getSut(array $settings)
{
return new LdapConfiguration($settings);
}
protected function getDefaultSettings()
{
return [
'active' => true,
'connection' => [
'host' => '1.2.3.4',
],
'user' => [
'foo' => 'bar',
],
'role' => [
'bar' => 'foo',
],
];
}
public function testMapping()
{
$sut = $this->getSut($this->getDefaultSettings());
$this->assertTrue($sut->isActivated());
$this->assertEquals(['foo' => 'bar'], $sut->getUserParameters());
$this->assertEquals(['bar' => 'foo'], $sut->getRoleParameters());
$this->assertEquals(['host' => '1.2.3.4'], $sut->getConnectionParameters());
$sut = $this->getSut(['active' => false]);
$this->assertFalse($sut->isActivated());
}
}

View File

@@ -39,7 +39,7 @@ class ActivityControllerTest extends ControllerBaseTest
$this->assertAccessIsGranted($client, '/admin/activity/create');
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]'));
$this->assertNull($form->get('activity_edit_form[create_more]')->getValue());
$this->assertFalse($form->get('activity_edit_form[create_more]')->hasValue());
$client->submit($form, [
'activity_edit_form' => [
'name' => 'An AcTiVitY Name',
@@ -87,6 +87,7 @@ class ActivityControllerTest extends ControllerBaseTest
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]'));
$this->assertTrue($form->get('activity_edit_form[create_more]')->hasValue());
$this->assertEquals(1, $form->get('activity_edit_form[create_more]')->getValue());
$this->assertEquals($selectedProject, $form->get('activity_edit_form[project]')->getValue());
}

View File

@@ -95,12 +95,13 @@ abstract class ControllerBaseTest extends WebTestCase
* @param string $url
* @param string $method
*/
protected function assertRequestIsSecured(Client $client, string $url, $method = 'GET')
protected function assertRequestIsSecured(Client $client, string $url, ?string $method = 'GET')
{
$this->request($client, $url, $method);
/* @var RedirectResponse $response */
/** @var RedirectResponse $response */
$response = $client->getResponse();
self::assertInstanceOf(RedirectResponse::class, $response);
$this->assertTrue(
$response->isRedirect(),
@@ -153,32 +154,19 @@ abstract class ControllerBaseTest extends WebTestCase
);
}
/**
* @param Client $client
* @param $url
* @param string $method
* @param array $parameters
*/
protected function assertAccessIsGranted(Client $client, $url, $method = 'GET', array $parameters = [])
protected function assertAccessIsGranted(Client $client, string $url, string $method = 'GET', array $parameters = [])
{
$this->request($client, $url, $method, $parameters);
$this->assertTrue($client->getResponse()->isSuccessful());
}
/**
* @param Client $client
*/
protected function assertRouteNotFound(Client $client)
{
$this->assertFalse($client->getResponse()->isSuccessful());
$this->assertEquals(404, $client->getResponse()->getStatusCode());
}
/**
* @param Client $client
* @param string $classname
*/
protected function assertMainContentClass(Client $client, $classname)
protected function assertMainContentClass(Client $client, string $classname)
{
$this->assertContains('<section class="content ' . $classname . '">', $client->getResponse()->getContent());
}
@@ -279,19 +267,11 @@ abstract class ControllerBaseTest extends WebTestCase
$this->assertContains($message, $node->text());
}
/**
* @param Client $client
* @param string|null $message
*/
protected function assertHasFlashDeleteSuccess(Client $client)
{
$this->assertHasFlashSuccess($client, 'Entry was deleted');
}
/**
* @param Client $client
* @param string|null $message
*/
protected function assertHasFlashSaveSuccess(Client $client)
{
$this->assertHasFlashSuccess($client, 'Saved changes');

View File

@@ -39,7 +39,7 @@ class ProjectControllerTest extends ControllerBaseTest
$this->assertAccessIsGranted($client, '/admin/project/create');
$form = $client->getCrawler()->filter('form[name=project_edit_form]')->form();
$this->assertTrue($form->has('project_edit_form[create_more]'));
$this->assertNull($form->get('project_edit_form[create_more]')->getValue());
$this->assertFalse($form->get('project_edit_form[create_more]')->hasValue());
$client->submit($form, [
'project_edit_form' => [
'name' => 'Test 2',
@@ -80,6 +80,7 @@ class ProjectControllerTest extends ControllerBaseTest
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=project_edit_form]')->form();
$this->assertTrue($form->has('project_edit_form[create_more]'));
$this->assertTrue($form->get('project_edit_form[create_more]')->hasValue());
$this->assertEquals(1, $form->get('project_edit_form[create_more]')->getValue());
$this->assertEquals($selectedCustomer, $form->get('project_edit_form[customer]')->getValue());
}

View File

@@ -38,7 +38,9 @@ class TimesheetControllerTest extends ControllerBaseTest
foreach ($result as $item) {
$this->assertContains('btn btn-default', $item->getAttribute('class'));
$this->assertEquals('i', $item->firstChild->tagName);
/** @var \DOMElement $domElement */
$domElement = $item->firstChild;
$this->assertEquals('i', $domElement->tagName);
}
}

View File

@@ -39,7 +39,9 @@ class TimesheetTeamControllerTest extends ControllerBaseTest
foreach ($result as $item) {
$this->assertContains('btn btn-default', $item->getAttribute('class'));
$this->assertEquals('i', $item->firstChild->tagName);
/** @var \DOMElement $domElement */
$domElement = $item->firstChild;
$this->assertEquals('i', $domElement->tagName);
}
}

View File

@@ -40,7 +40,6 @@ class UserControllerTest extends ControllerBaseTest
$form = $client->getCrawler()->filter('form[name=user_create]')->form();
$this->assertTrue($form->has('user_create[create_more]'));
$this->assertFalse($form->get('user_create[create_more]')->hasValue());
$this->assertNull($form->get('user_create[create_more]')->getValue());
$client->submit($form, [
'user_create' => [
'username' => $username,
@@ -86,6 +85,7 @@ class UserControllerTest extends ControllerBaseTest
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=user_create]')->form();
$this->assertTrue($form->has('user_create[create_more]'));
$this->assertTrue($form->get('user_create[create_more]')->hasValue());
$this->assertEquals(1, $form->get('user_create[create_more]')->getValue());
}

View File

@@ -54,11 +54,7 @@ class TagFixtures extends Fixture
$manager->flush();
}
/**
* @param $tagName
* @return Tag
*/
protected function createTagEntry($tagName)
protected function createTagEntry(string $tagName): Tag
{
$tagObject = new Tag();
$tagObject->setName($tagName);

View File

@@ -58,7 +58,7 @@ class TimesheetFixtures extends Fixture
*/
protected $allowEmptyDescriptions = true;
/**
* @var int
* @var bool
*/
protected $exported = false;
/**
@@ -265,11 +265,7 @@ class TimesheetFixtures extends Fixture
$manager->flush();
}
/**
* @param $cnt
* @return array
*/
protected function getTagObjectList($cnt)
protected function getTagObjectList(int $cnt): array
{
if (true === $this->useTags) {
$tagObject = new Tag();
@@ -281,11 +277,7 @@ class TimesheetFixtures extends Fixture
return [];
}
/**
* @param $i
* @return bool|\DateTime
*/
protected function getDateTime($i)
protected function getDateTime(int $i): \DateTime
{
$start = \DateTime::createFromFormat('Y-m-d', $this->startDate);
$start->modify("+ $i days");

View File

@@ -0,0 +1,316 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\DependencyInjection;
use App\DependencyInjection\AppExtension;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @covers \App\DependencyInjection\AppExtension
*/
class AppExtensionTest extends TestCase
{
/**
* @var AppExtension
*/
private $extension;
public function setUp()
{
parent::setUp();
$this->extension = new AppExtension();
}
/**
* @return ContainerBuilder
*/
private function getContainer()
{
$container = new ContainerBuilder();
return $container;
}
protected function getMinConfig()
{
return [
'kimai' => [
'data_dir' => '/tmp/',
'plugin_dir' => '/tmp/',
'timesheet' => [],
]
];
}
public function testDefaultValues()
{
$minConfig = $this->getMinConfig();
$this->extension->load($minConfig, $container = $this->getContainer());
$expected = [
'kimai.data_dir' => '/tmp/',
'kimai.plugin_dir' => '/tmp/',
'kimai.languages' => [],
'kimai.calendar' => [
'week_numbers' => true,
'day_limit' => 4,
'businessHours' => [
'days' => [1, 2, 3, 4, 5],
'begin' => '08:00',
'end' => '20:00',
],
'visibleHours' => [
'begin' => '00:00',
'end' => '24:00',
],
'google' => [
'api_key' => null,
'sources' => [],
],
'weekends' => true
],
'kimai.dashboard' => [],
'kimai.widgets' => [],
'kimai.invoice.documents' => [
'var/invoices/',
'templates/invoice/renderer/',
],
'kimai.defaults' => [
'customer' => [
'timezone' => 'Europe/Berlin',
'country' => 'DE',
'currency' => 'EUR',
]
],
'kimai.theme' => [
'active_warning' => 3,
'box_color' => 'green',
'select_type' => null,
'show_about' => true,
],
'kimai.theme.select_type' => null,
'kimai.theme.show_about' => true,
'kimai.fosuser' => [
'registration' => true,
'password_reset' => true,
],
'kimai.timesheet' => [
'mode' => 'default',
'markdown_content' => false,
'rounding' => [],
'rates' => [],
'active_entries' => [
'soft_limit' => 1,
'hard_limit' => 1,
],
'rules' => [
'allow_future_times' => true,
],
],
'kimai.timesheet.rates' => [],
'kimai.timesheet.rounding' => [],
'kimai.ldap' => [
'active' => false,
'user' => [
'baseDn' => null,
'filter' => '',
'usernameAttribute' => 'uid',
'attributes' => [],
],
'role' => [
'baseDn' => null,
'nameAttribute' => 'cn',
'userDnAttribute' => 'member',
'groups' => [],
'usernameAttribute' => 'dn',
],
'connection' => [
'baseDn' => null,
'host' => null,
'port' => 389,
'useStartTls' => false,
'useSsl' => false,
'bindRequiresDn' => true,
'accountFilterFormat' => '(&(uid=%s))',
],
],
'kimai.permissions' => [
'ROLE_USER' => [],
'ROLE_TEAMLEAD' => [],
'ROLE_ADMIN' => [],
'ROLE_SUPER_ADMIN' => [],
],
];
// nasty parameter, should be removed!!!
$this->assertTrue($container->hasParameter('kimai.config'));
foreach ($expected as $key => $value) {
$this->assertTrue($container->hasParameter($key), 'Could not find config: ' . $key);
$this->assertEquals($value, $container->getParameter($key), 'Invalid config: ' . $key);
}
}
public function testAdditionalAuthenticationRoutes()
{
$minConfig = $this->getMinConfig();
$adminLte = [
'adminlte_registration' => 'foo',
'adminlte_password_reset' => 'bar',
];
$container = $this->getContainer();
$container->setParameter('admin_lte_theme.routes', $adminLte);
$this->extension->load($minConfig, $container);
$this->assertEquals(
[
'adminlte_registration' => 'foo',
'adminlte_password_reset' => 'bar',
],
$container->getParameter('admin_lte_theme.routes')
);
}
public function testDeactivateAdditionalAuthenticationRoutes()
{
$minConfig = $this->getMinConfig();
$minConfig['kimai']['user'] = [
'registration' => false,
'password_reset' => false,
];
$adminLte = [
'adminlte_registration' => 'foo',
'adminlte_password_reset' => 'bar',
];
$container = $this->getContainer();
$container->setParameter('admin_lte_theme.routes', $adminLte);
$this->extension->load($minConfig, $container);
$this->assertEquals(
[
'adminlte_registration' => null,
'adminlte_password_reset' => null,
],
$container->getParameter('admin_lte_theme.routes')
);
}
/**
* @expectedException \PHPUnit\Framework\Error\Notice
* @expectedExceptionMessage Found ambiguous configuration. Please remove "kimai.timesheet.duration_only" and set "kimai.timesheet.mode" instead.
* @expectedDeprecation Configuration "kimai.timesheet.duration_only" is deprecated, please remove it
* @group legacy
*/
public function testDurationOnlyDeprecationIsTriggered()
{
$minConfig = $this->getMinConfig();
$minConfig['kimai']['timesheet']['duration_only'] = true;
$minConfig['kimai']['timesheet']['mode'] = 'punch';
$this->extension->load($minConfig, $container = $this->getContainer());
}
public function testLdapDefaultValues()
{
$minConfig = $this->getMinConfig();
$minConfig['kimai']['ldap'] = [
'active' => true,
'connection' => [
'host' => '9.9.9.9',
'baseDn' => 'lkhiuzhkj',
'accountFilterFormat' => '(uid=%s)'
],
'user' => [
'baseDn' => '123123123',
'usernameAttribute' => 'xxx',
'filter' => '(..........)'
],
];
$this->extension->load($minConfig, $container = $this->getContainer());
$ldapConfig = $container->getParameter('kimai.ldap');
$this->assertEquals('123123123', $ldapConfig['user']['baseDn']);
$this->assertEquals('(..........)', $ldapConfig['user']['filter']);
$this->assertEquals('xxx', $ldapConfig['user']['usernameAttribute']);
$this->assertEquals('lkhiuzhkj', $ldapConfig['connection']['baseDn']);
$this->assertEquals('(uid=%s)', $ldapConfig['connection']['accountFilterFormat']);
}
public function testLdapFallbackValue()
{
$minConfig = $this->getMinConfig();
$minConfig['kimai']['ldap'] = [
'active' => true,
'connection' => [
'host' => '9.9.9.9',
],
'user' => [
'baseDn' => '123123123',
'usernameAttribute' => 'xxx',
],
];
$this->extension->load($minConfig, $container = $this->getContainer());
$ldapConfig = $container->getParameter('kimai.ldap');
$this->assertEquals('123123123', $ldapConfig['user']['baseDn']);
$this->assertEquals('xxx', $ldapConfig['user']['usernameAttribute']);
$this->assertEquals('123123123', $ldapConfig['connection']['baseDn']);
$this->assertEquals('(&(xxx=%s))', $ldapConfig['connection']['accountFilterFormat']);
$this->assertEquals('', $ldapConfig['user']['filter']);
}
public function testLdapMoreFallbackValue()
{
$minConfig = $this->getMinConfig();
$minConfig['kimai']['ldap'] = [
'active' => true,
'connection' => [
'host' => '9.9.9.9',
'baseDn' => '7658765',
],
'user' => [
'baseDn' => '123123123',
'usernameAttribute' => 'zzzz',
'filter' => '(&(objectClass=inetOrgPerson))',
],
];
$this->extension->load($minConfig, $container = $this->getContainer());
$ldapConfig = $container->getParameter('kimai.ldap');
$this->assertEquals('123123123', $ldapConfig['user']['baseDn']);
$this->assertEquals('zzzz', $ldapConfig['user']['usernameAttribute']);
$this->assertEquals('7658765', $ldapConfig['connection']['baseDn']);
$this->assertEquals('(&(&(objectClass=inetOrgPerson))(zzzz=%s))', $ldapConfig['connection']['accountFilterFormat']);
$this->assertEquals('(&(objectClass=inetOrgPerson))', $ldapConfig['user']['filter']);
}
/**
* @expectedException \PHPUnit\Framework\Error\Notice
* @expectedExceptionMessage Found invalid "kimai" configuration: The child node "data_dir" at path "kimai" must be configured.
*/
public function testInvalidConfiguration()
{
$this->extension->load([], $container = $this->getContainer());
}
// TODO test permissions
}

View File

@@ -0,0 +1,246 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\DependencyInjection;
use App\DependencyInjection\Configuration;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\DependencyInjection\Configuration
*/
class ConfigurationTest extends TestCase
{
protected function getMinConfig($dataDir = '/tmp/', $pluginDir = '/tmp/')
{
return [
'data_dir' => $dataDir,
'plugin_dir' => $pluginDir,
'timesheet' => [],
];
}
protected function assertConfig($inputConfig, $expectedConfig)
{
$finalizedConfig = $this->getCompiledConfig($inputConfig);
self::assertEquals($expectedConfig, $finalizedConfig);
}
protected function getCompiledConfig($inputConfig)
{
$configuration = new Configuration();
$node = $configuration->getConfigTreeBuilder()->buildTree();
$normalizedConfig = $node->normalize($inputConfig);
return $node->finalize($normalizedConfig);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.data_dir": Data directory does not exist
*/
public function testValidateDataDir()
{
$this->assertConfig($this->getMinConfig('sdfsdfsdfds'), []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.plugin_dir": Plugin directory does not exist
*/
public function testValidatePluginDir()
{
$this->assertConfig($this->getMinConfig('/tmp/', 'sdfsdfs'), []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap": The "ldap.user.baseDn" config must be set if LDAP is activated.
*/
public function testValidateLdapConfigUserBaseDn()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => true,
'connection' => [
'host' => 'foo'
],
];
$this->assertConfig($config, []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap": The "ldap.connection.host" config must be set if LDAP is activated.
*/
public function testValidateLdapConfigConnectionHost()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => true,
'connection' => [
],
];
$this->assertConfig($config, []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap.connection": The ldap.connection.useSsl and ldap.connection.useStartTls options are mutually exclusive.
*/
public function testValidateLdapConfig()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => false,
'connection' => [
'useSsl' => true,
'useStartTls' => true,
],
];
$this->assertConfig($config, []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap.user.filter": The ldap.user.filter must be enclosed by a matching number of parentheses "()" and must NOT contain a "%s" replacer
*/
public function testValidateLdapFilterIncludingReplacer()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => true,
'user' => [
'filter' => '(sdfsdfsdf)(uid=%s)',
],
];
$this->assertConfig($config, []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap.user.filter": The ldap.user.filter must be enclosed by a matching number of parentheses "()" and must NOT contain a "%s" replacer
*/
public function testValidateLdapFilterMissingStartingParenthesis()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => true,
'user' => [
'filter' => 's(dfsdfsdf)',
],
];
$this->assertConfig($config, []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap.user.filter": The ldap.user.filter must be enclosed by a matching number of parentheses "()" and must NOT contain a "%s" replacer
*/
public function testValidateLdapFilterInvalidParenthesisCounter()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => true,
'user' => [
'filter' => '(dfsdfsdf))',
],
];
$this->assertConfig($config, []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap.connection.accountFilterFormat": The accountFilterFormat must be enclosed by a matching number of parentheses "()" and contain one "%s" replacer for the username
*/
public function testValidateLdapAccountFilterFormatMissingUserAttributeReplacer()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => false,
'connection' => [
'accountFilterFormat' => '(sdfsdfsdf)(uid=xx)',
],
];
$this->assertConfig($config, []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap.connection.accountFilterFormat": The accountFilterFormat must be enclosed by a matching number of parentheses "()" and contain one "%s" replacer for the username
*/
public function testValidateLdapAccountFilterFormatMissingStartingParenthesis()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => true,
'connection' => [
'accountFilterFormat' => 's(dfsdfsdf)',
],
];
$this->assertConfig($config, []);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "kimai.ldap.connection.accountFilterFormat": The accountFilterFormat must be enclosed by a matching number of parentheses "()" and contain one "%s" replacer for the username
*/
public function testValidateLdapAccountFilterFormatInvalidParenthesisCounter()
{
$config = $this->getMinConfig();
$config['ldap'] = [
'active' => true,
'connection' => [
'accountFilterFormat' => '(dfsdfsdf))',
],
];
$this->assertConfig($config, []);
}
public function testDefaultLdapSettings()
{
$finalizedConfig = $this->getCompiledConfig($this->getMinConfig());
$expected = [
'active' => false,
'user' => [
'baseDn' => '',
'filter' => '',
'usernameAttribute' => 'uid',
'attributes' => [],
],
'role' => [
'baseDn' => null,
'usernameAttribute' => 'dn',
'nameAttribute' => 'cn',
'userDnAttribute' => 'member',
'groups' => [],
],
'connection' => [
'host' => null,
'port' => 389,
'useStartTls' => false,
'useSsl' => false,
'bindRequiresDn' => true,
'accountFilterFormat' => '',
]
];
self::assertEquals($expected, $finalizedConfig['ldap']);
}
}

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\EventSubscriber;
namespace App\Tests\Doctrine;
use App\Doctrine\SqliteSessionInitSubscriber;
use Doctrine\DBAL\Connection;

View File

@@ -18,10 +18,10 @@ use Symfony\Component\Validator\ConstraintViolationInterface;
abstract class AbstractEntityTest extends KernelTestCase
{
/**
* @param $entity
* @param object $entity
* @param array|string $fieldNames
*/
protected function assertHasViolationForField($entity, $fieldNames)
protected function assertHasViolationForField(object $entity, $fieldNames)
{
self::bootKernel();
$validator = static::$kernel->getContainer()->get('validator');

View File

@@ -24,7 +24,7 @@ class ActivityTest extends AbstractEntityTest
$this->assertNull($sut->getName());
$this->assertNull($sut->getComment());
$this->assertTrue($sut->getVisible());
// timesheets
self::assertIsIterable($sut->getTimesheets());
$this->assertNull($sut->getFixedRate());
$this->assertNull($sut->getHourlyRate());
$this->assertNull($sut->getColor());

View File

@@ -10,7 +10,6 @@
namespace App\Tests\Entity;
use App\Entity\Customer;
use App\Entity\Project;
/**
* @covers \App\Entity\Customer
@@ -24,7 +23,8 @@ class CustomerTest extends AbstractEntityTest
$this->assertNull($sut->getName());
$this->assertNull($sut->getNumber());
$this->assertNull($sut->getComment());
// projects
self::assertIsIterable($sut->getProjects());
self::assertEmpty($sut->getProjects());
$this->assertTrue($sut->getVisible());
$this->assertNull($sut->getCompany());
@@ -61,10 +61,6 @@ class CustomerTest extends AbstractEntityTest
$this->assertInstanceOf(Customer::class, $sut->setColor('#fffccc'));
$this->assertEquals('#fffccc', $sut->getColor());
$projects = [(new Project())->setName('Test')];
$this->assertInstanceOf(Customer::class, $sut->setProjects($projects));
$this->assertSame($projects, $sut->getProjects());
$this->assertInstanceOf(Customer::class, $sut->setCompany('test company'));
$this->assertEquals('test company', $sut->getCompany());

View File

@@ -9,10 +9,8 @@
namespace App\Tests\Entity;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\Timesheet;
/**
* @covers \App\Entity\Project
@@ -29,10 +27,12 @@ class ProjectTest extends AbstractEntityTest
$this->assertNull($sut->getComment());
$this->assertTrue($sut->getVisible());
$this->assertEquals(0.0, $sut->getBudget());
// activities
$this->assertNull($sut->getFixedRate());
$this->assertNull($sut->getHourlyRate());
$this->assertNull($sut->getTimesheets());
self::assertIsIterable($sut->getTimesheets());
self::assertEmpty($sut->getTimesheets());
self::assertIsIterable($sut->getActivities());
self::assertEmpty($sut->getActivities());
$this->assertNull($sut->getColor());
}
@@ -62,17 +62,9 @@ class ProjectTest extends AbstractEntityTest
$this->assertInstanceOf(Project::class, $sut->setBudget(12345.67));
$this->assertEquals(12345.67, $sut->getBudget());
$activities = [(new Activity())->setName('foo')];
$this->assertInstanceOf(Project::class, $sut->setActivities($activities));
$this->assertSame($activities, $sut->getActivities());
$this->assertInstanceOf(Project::class, $sut->setFixedRate(13.47));
$this->assertEquals(13.47, $sut->getFixedRate());
$this->assertInstanceOf(Project::class, $sut->setHourlyRate(99));
$this->assertEquals(99, $sut->getHourlyRate());
$timesheets = [(new Timesheet())->setDescription('foo'), (new Timesheet())->setDescription('bar')];
$this->assertInstanceOf(Project::class, $sut->setTimesheets($timesheets));
$this->assertSame($timesheets, $sut->getTimesheets());
}
}

View File

@@ -116,6 +116,13 @@ class UserTest extends AbstractEntityTest
$user->addPreference($preference);
$this->assertEquals('foobar', $user->getPreferenceValue('test', 'foo'));
$this->assertEquals($preference, $user->getPreference('test'));
$user->setPreferenceValue('test', 'Hello World');
$this->assertEquals('Hello World', $user->getPreferenceValue('test', 'foo'));
$this->assertNull($user->getPreferenceValue('test2'));
$user->setPreferenceValue('test2', 'I like rain');
$this->assertEquals('I like rain', $user->getPreferenceValue('test2'));
}
public function testToString()

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\EventSubscriber;
namespace App\Tests\Event;
use App\Event\ConfigureAdminMenuEvent;
use KevinPapst\AdminLTEBundle\Model\MenuItemModel;

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\EventSubscriber;
namespace App\Tests\Event;
use App\Entity\User;
use App\Event\DashboardEvent;

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\EventSubscriber;
namespace App\Tests\Event;
use App\Entity\User;
use App\Event\ThemeEvent;

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\EventSubscriber;
namespace App\Tests\Event;
use App\Entity\User;
use App\Entity\UserPreference;

View File

@@ -7,16 +7,17 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Export\Renderer;
namespace App\Tests\Form\DataTransformer;
use App\Entity\Tag;
use App\Form\DataTransformer\TagArrayToStringTransformer;
use App\Repository\TagRepository;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Form\DataTransformer\TagArrayToStringTransformer
*/
class TagArrayToStringTransformerTest extends AbstractRendererTest
class TagArrayToStringTransformerTest extends TestCase
{
public function testTransform()
{

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Invoice\Calculator;
namespace App\Tests\Invoice\NumberGenerator;
use App\Invoice\NumberGenerator\DateNumberGenerator;
use App\Model\InvoiceModel;

View File

@@ -190,11 +190,7 @@ abstract class AbstractRendererTest extends KernelTestCase
return $model;
}
/**
* @param Timesheet[] $timesheets
* @return InvoiceModel
*/
protected function getInvoiceModelOneEntry()
protected function getInvoiceModelOneEntry(): InvoiceModel
{
$customer = new Customer();
$customer->setCurrency('USD');

View File

@@ -54,7 +54,7 @@ class DebugRenderer implements RendererInterface
}
/**
* @param $amount
* @param mixed $amount
* @return mixed
*/
protected function getFormattedMoney($amount)
@@ -72,7 +72,7 @@ class DebugRenderer implements RendererInterface
}
/**
* @param $seconds
* @param mixed $seconds
* @return mixed
*/
protected function getFormattedDuration($seconds)

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Ldap\FormLoginLdapFactory;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @covers \App\Ldap\FormLoginLdapFactory
*/
class FormLoginLdapFactoryTest extends TestCase
{
public function testStaticValues()
{
$sut = new FormLoginLdapFactory();
self::assertEquals('kimai_ldap', $sut->getKey());
self::assertEquals('pre_auth', $sut->getPosition());
}
public function testCreate()
{
$container = new ContainerBuilder();
$sut = new FormLoginLdapFactory();
$result = $sut->create($container, 'test', ['foo' => 'bar'], 'fosuserbundle', 'secured_area');
self::assertEquals([
'kimai_ldap.security.authentication.provider.test',
'security.authentication.listener.form.test',
'secured_area'
], $result);
$definition = $container->getDefinition('kimai_ldap.security.authentication.provider.test');
self::assertInstanceOf(ChildDefinition::class, $definition);
self::assertEquals('test', $definition->getArguments()['index_1']);
$definition = $container->getDefinition('security.authentication.listener.form.test');
self::assertInstanceOf(ChildDefinition::class, $definition);
self::assertEquals('test', $definition->getArguments()['index_4']);
self::assertEquals(['foo' => 'bar'], $definition->getArguments()['index_5']);
}
}

View File

@@ -0,0 +1,238 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Configuration\LdapConfiguration;
use App\Entity\User;
use App\Ldap\LdapAuthenticationProvider;
use App\Ldap\LdapManager;
use App\Ldap\LdapUserProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserChecker;
/**
* @covers \App\Ldap\LdapAuthenticationProvider
*/
class LdapAuthenticationProviderTest extends TestCase
{
public function testDeactivatedSupports()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => false]);
$userProvider = new LdapUserProvider($manager, $config);
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken('foo', 'bar', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$result = $sut->supports($token);
self::assertFalse($result);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationException
* @expectedExceptionMessage The token is not supported by this authentication provider.
*/
public function testDeactivatedAuthenticate()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => false]);
$userProvider = new LdapUserProvider($manager, $config);
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken('foo', 'bar', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$sut->authenticate($token);
}
public function testSupports()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => true]);
$userProvider = new LdapUserProvider($manager, $config);
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken('foo', 'bar', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$result = $sut->supports($token);
self::assertTrue($result);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
* @expectedExceptionMessage The password in the token is empty. Check `erase_credentials` in your `security.yaml`
*/
public function testAuthenticateWithTokenUserButEnptyPasswordThrowsException()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => true]);
$userProvider = new LdapUserProvider($manager, $config);
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$user = (new User())->setUsername('foo')->setEnabled(true);
$token = new UsernamePasswordToken($user, '', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$actual = $sut->authenticate($token);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
* @expectedExceptionMessage The presented password cannot be empty.
*/
public function testAuthenticateWithUsernameReturnsUser()
{
$user = (new User())->setUsername('foo')->setEnabled(true);
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => true]);
$userProvider = $this->getMockBuilder(LdapUserProvider::class)->disableOriginalConstructor()->setMethods(['loadUserByUsername'])->getMock();
$userProvider->expects($this->once())->method('loadUserByUsername')->willReturn($user);
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken('foo', '', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$actual = $sut->authenticate($token);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
* @expectedExceptionMessage The presented password is invalid.
*/
public function testAuthenticateWithUsernameThrowsExceptionOnFailedBind()
{
$user = (new User())->setUsername('foo')->setEnabled(true);
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->setMethods(['bind'])->getMock();
$manager->expects($this->once())->method('bind')->willReturn(false);
$config = new LdapConfiguration(['active' => true]);
$userProvider = $this->getMockBuilder(LdapUserProvider::class)->disableOriginalConstructor()->setMethods(['loadUserByUsername'])->getMock();
$userProvider->expects($this->once())->method('loadUserByUsername')->willReturn($user);
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken('foo', 'sdfsdf', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$actual = $sut->authenticate($token);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
* @expectedExceptionMessage The credentials were changed from another session.
*/
public function testAuthenticateWithUserThrowsExceptionOnFailedBind()
{
$user = (new User())->setUsername('foo')->setEnabled(true);
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->setMethods(['bind'])->getMock();
$manager->expects($this->once())->method('bind')->willReturn(false);
$config = new LdapConfiguration(['active' => true]);
$userProvider = $this->getMockBuilder(LdapUserProvider::class)->disableOriginalConstructor()->setMethods(['loadUserByUsername'])->getMock();
$userProvider->expects($this->never())->method('loadUserByUsername');
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken($user, 'sdfsdf', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$actual = $sut->authenticate($token);
}
public function testAuthenticateWithUsernameReturnsUserAndBinds()
{
$user = (new User())->setUsername('foo')->setEnabled(true);
$user->setPreferenceValue('ldap.dn', 'blub');
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->setMethods(['bind', 'updateUser'])->getMock();
$manager->expects($this->once())->method('bind')->willReturn(true);
$manager->expects($this->once())->method('updateUser')->willReturnCallback(function ($updateUser) use ($user) {
self::assertSame($updateUser, $user);
});
$config = new LdapConfiguration(['active' => true]);
$userProvider = $this->getMockBuilder(LdapUserProvider::class)->disableOriginalConstructor()->setMethods(['loadUserByUsername'])->getMock();
$userProvider->expects($this->once())->method('loadUserByUsername')->willReturn($user);
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken('foo', 'test', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$token = $sut->authenticate($token);
self::assertSame($token->getUser(), $user);
}
public function testAuthenticateWithUserReturnsUserAndBinds()
{
$user = (new User())->setUsername('foo')->setEnabled(true);
$user->setPreferenceValue('ldap.dn', 'blub');
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->setMethods(['bind', 'updateUser'])->getMock();
$manager->expects($this->once())->method('bind')->willReturn(true);
$manager->expects($this->once())->method('updateUser')->willReturnCallback(function ($updateUser) use ($user) {
self::assertSame($updateUser, $user);
});
$config = new LdapConfiguration(['active' => true]);
$userProvider = $this->getMockBuilder(LdapUserProvider::class)->disableOriginalConstructor()->setMethods(['loadUserByUsername'])->getMock();
$userProvider->expects($this->never())->method('loadUserByUsername');
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken($user, 'test', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$token = $sut->authenticate($token);
self::assertSame($token->getUser(), $user);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException
* @expectedExceptionMessage blub foo bar
*/
public function testAuthenticateThrowsExceptionOnLdapNotFound()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => true]);
$userProvider = $this->getMockBuilder(LdapUserProvider::class)->disableOriginalConstructor()->setMethods(['loadUserByUsername'])->getMock();
$userProvider->expects($this->once())->method('loadUserByUsername')->willThrowException(new UsernameNotFoundException('blub foo bar'));
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken('foo', 'test', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$sut->authenticate($token);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationServiceException
* @expectedExceptionMessage server away
* @expectedExceptionCode 1234
*/
public function testAuthenticateThrowsExceptionOnLdapDown()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => true]);
$userProvider = $this->getMockBuilder(LdapUserProvider::class)->disableOriginalConstructor()->setMethods(['loadUserByUsername'])->getMock();
$userProvider->expects($this->once())->method('loadUserByUsername')->willThrowException(new \Exception('server away', 1234));
$providerKey = 'secured_area';
$userChecker = new UserChecker();
$token = new UsernamePasswordToken('foo', 'test', $providerKey);
$sut = new LdapAuthenticationProvider($userChecker, $providerKey, $userProvider, $manager, $config, false);
$sut->authenticate($token);
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Ldap\LdapDriverException;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Ldap\LdapDriverException
*/
class LdapDriverExceptionTest extends TestCase
{
public function testConstruct()
{
$sut = new LdapDriverException('Whooops');
self::assertInstanceOf(\Exception::class, $sut);
self::assertEquals('Whooops', $sut->getMessage());
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Entity\User;
use App\Ldap\LdapDriver;
use PHPUnit\Framework\TestCase;
use Zend\Ldap\Exception\LdapException;
use Zend\Ldap\Ldap;
/**
* @covers \App\Ldap\LdapDriver
*/
class LdapDriverTest extends TestCase
{
public function testBindSuccess()
{
$zendLdap = $this->getMockBuilder(Ldap::class)->disableOriginalConstructor()->setMethods(['bind'])->getMock();
$zendLdap->expects($this->once())->method('bind')->willReturnSelf();
$user = new User();
$sut = new LdapDriver($zendLdap);
$result = $sut->bind($user, 'test123');
self::assertTrue($result);
}
public function testBindException()
{
$zendLdap = $this->getMockBuilder(Ldap::class)->disableOriginalConstructor()->setMethods(['bind'])->getMock();
$zendLdap->expects($this->once())->method('bind')->willThrowException(new LdapException());
$user = new User();
$sut = new LdapDriver($zendLdap);
$result = $sut->bind($user, 'test123');
self::assertFalse($result);
}
public function testSearchSuccess()
{
$zendLdap = $this->getMockBuilder(Ldap::class)->disableOriginalConstructor()->setMethods(['bind', 'searchEntries'])->getMock();
$zendLdap->expects($this->once())->method('bind');
$zendLdap->expects($this->once())->method('searchEntries')->willReturn([1, 2, 3]);
$sut = new LdapDriver($zendLdap);
$result = $sut->search('', '', []);
self::assertEquals(['count' => 3, 1, 2, 3], $result);
}
/**
* @expectedException \App\Ldap\LdapDriverException
* @expectedExceptionMessage An error occurred with the search operation.
*/
public function testSearchException()
{
$zendLdap = $this->getMockBuilder(Ldap::class)->disableOriginalConstructor()->setMethods(['bind', 'searchEntries'])->getMock();
$zendLdap->expects($this->once())->method('bind');
$zendLdap->expects($this->once())->method('searchEntries')->willThrowException(
new LdapException($zendLdap, '', LdapException::LDAP_SERVER_DOWN)
);
$sut = new LdapDriver($zendLdap);
$sut->search('', '', []);
}
}

View File

@@ -0,0 +1,454 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Configuration\LdapConfiguration;
use App\Entity\User;
use App\Ldap\LdapDriver;
use App\Ldap\LdapManager;
use App\Ldap\LdapUserHydrator;
use App\Security\RoleService;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Ldap\LdapManager
*/
class LdapManagerTest extends TestCase
{
protected function getLdapManager(LdapDriver $driver, $roleConfig = null)
{
if (null === $roleConfig) {
$roleConfig = [
'baseDn' => 'ou=groups, dc=kimai, dc=org',
'nameAttribute' => 'cn',
'userDnAttribute' => 'member',
'groups' => [
['ldap_value' => 'group1', 'role' => 'ROLE_TEAMLEAD'],
['ldap_value' => 'group2', 'role' => 'ROLE_ADMIN'],
['ldap_value' => 'group3', 'role' => 'ROLE_CUSTOMER'], // not existing!
['ldap_value' => 'group4', 'role' => 'ROLE_SUPER_ADMIN'],
],
];
}
$config = new LdapConfiguration([
'user' => [
'attributes' => [],
'filter' => '(&(objectClass=inetOrgPerson))',
'usernameAttribute' => 'uid',
'baseDn' => 'ou=users, dc=kimai, dc=org',
],
'role' => $roleConfig,
]);
$hydrator = new LdapUserHydrator($config, new RoleService([
'ROLE_TEAMLEAD' => ['ROLE_USER'],
'ROLE_ADMIN' => ['ROLE_TEAMLEAD'],
'ROLE_SUPER_ADMIN' => ['ROLE_ADMIN']
]));
return new LdapManager($driver, $hydrator, $config);
}
public function testFindUserByUsernameOnZeroResults()
{
$expected = [
'count' => 0
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->once())->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
self::assertEquals('ou=users, dc=kimai, dc=org', $baseDn);
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=foo))', $filter);
return $expected;
});
$sut = $this->getLdapManager($driver);
$actual = $sut->findUserByUsername('foo');
self::assertNull($actual);
}
/**
* @expectedException \App\Ldap\LdapDriverException
* @expectedExceptionMessage This search must only return a single user
*/
public function testFindUserByUsernameOnMultiResults()
{
$expected = [
'count' => 3
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->once())->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
self::assertEquals('ou=users, dc=kimai, dc=org', $baseDn);
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=foo))', $filter);
return $expected;
});
$sut = $this->getLdapManager($driver);
$sut->findUserByUsername('foo');
}
public function testFindUserByUsernameOnValidResult()
{
$expected = [
0 => ['dn' => 'foo'],
'count' => 1,
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->once())->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
self::assertEquals('ou=users, dc=kimai, dc=org', $baseDn);
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=foo))', $filter);
return $expected;
});
$sut = $this->getLdapManager($driver);
$actual = $sut->findUserByUsername('foo');
self::assertInstanceOf(User::class, $actual);
}
public function testFindUserByOnZeroResults()
{
$expected = [
'count' => 0
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->once())->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
self::assertEquals('ou=users, dc=kimai, dc=org', $baseDn);
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=foo))', $filter);
return $expected;
});
$sut = $this->getLdapManager($driver);
$actual = $sut->findUserBy(['uid' => 'foo']);
self::assertNull($actual);
}
/**
* @expectedException \App\Ldap\LdapDriverException
* @expectedExceptionMessage This search must only return a single user
*/
public function testFindUserByOnMultiResults()
{
$expected = [
'count' => 3
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->once())->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
self::assertEquals('ou=users, dc=kimai, dc=org', $baseDn);
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=foo))', $filter);
return $expected;
});
$sut = $this->getLdapManager($driver);
$sut->findUserBy(['uid' => 'foo']);
}
public function testFindUserByOnValidResult()
{
$expected = [
0 => ['dn' => 'foo'],
'count' => 1,
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->once())->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
self::assertEquals('ou=users, dc=kimai, dc=org', $baseDn);
self::assertEquals('(&(&(objectClass=inetOrgPerson))(träl=alß#\\\aa=XY\5cZ0)(test=fu=n))', $filter);
return $expected;
});
$sut = $this->getLdapManager($driver);
$actual = $sut->findUserBy(['träl=alß#\\\aa' => 'XY\Z0', 'test' => 'fu=n']);
self::assertInstanceOf(User::class, $actual);
}
public function testBind()
{
$user = (new User())->setUsername('foobar');
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['bind'])->getMock();
$driver->expects($this->once())->method('bind')->willReturnCallback(function ($bindUser, $password) use ($user) {
self::assertSame($user, $bindUser);
self::assertEquals('a-very-secret-secret', $password);
return true;
});
$sut = $this->getLdapManager($driver);
$actual = $sut->bind($user, 'a-very-secret-secret');
self::assertTrue($actual);
}
public function testUpdateUserOnZeroResults()
{
$user = (new User())->setUsername('foobar');
$user->setPreferenceValue('ldap.dn', 'fooooooooooo');
$expected = [
[
0 => ['dn' => 'blub'],
'count' => 1,
],
[
'count' => 0,
],
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->exactly(2))->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
if ($baseDn === 'ou=users, dc=kimai, dc=org') {
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=foobar))', $filter);
return $expected[0];
} elseif ($baseDn === 'blub') {
self::assertEquals('(objectClass=*)', $filter);
return $expected[1];
}
$this->fail(sprintf('Unexpected search with baseDn %s', $baseDn));
});
$sut = $this->getLdapManager($driver);
$userOrig = clone $user;
$sut->updateUser($user);
self::assertEquals($userOrig, $user);
}
/**
* @expectedException \App\Ldap\LdapDriverException
* @expectedExceptionMessage This search must only return a single user
*/
public function testUpdateUserOnMultiResults()
{
$user = (new User())->setUsername('foobar');
$user->setPreferenceValue('ldap.dn', 'xxxxxxx');
$expected = [
[
0 => ['dn' => 'blub'],
'count' => 1,
],
[
'count' => 3,
],
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->exactly(2))->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
if ($baseDn === 'ou=users, dc=kimai, dc=org') {
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=foobar))', $filter);
return $expected[0];
} elseif ($baseDn === 'blub') {
self::assertEquals('(objectClass=*)', $filter);
return $expected[1];
}
$this->fail(sprintf('Unexpected search with baseDn %s', $baseDn));
});
$sut = $this->getLdapManager($driver);
$sut->updateUser($user);
}
public function testUpdateUserOnValidResultWithEmptyRoleBaseDn()
{
$user = (new User())->setUsername('foobar');
$user->setPreferenceValue('ldap.dn', 'sssssss');
$expected = [
[
0 => ['dn' => 'blub'],
'count' => 1,
],
[
0 => ['dn' => 'blub-updated'],
'count' => 1,
],
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->exactly(2))->method('search')->willReturnCallback(function ($baseDn, $filter) use ($expected) {
if ($baseDn === 'ou=users, dc=kimai, dc=org') {
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=foobar))', $filter);
return $expected[0];
} elseif ($baseDn === 'blub') {
self::assertEquals('(objectClass=*)', $filter);
return $expected[1];
}
$this->fail(sprintf('Unexpected search with baseDn %s', $baseDn));
});
$sut = $this->getLdapManager($driver, [
'baseDn' => null,
'nameAttribute' => 'cn',
'userDnAttribute' => 'member',
'groups' => [
['ldap_value' => 'group1', 'role' => 'ROLE_TEAMLEAD'],
['ldap_value' => 'group2', 'role' => 'ROLE_ADMIN'],
['ldap_value' => 'group3', 'role' => 'ROLE_CUSTOMER'], // not existing!
['ldap_value' => 'group4', 'role' => 'ROLE_SUPER_ADMIN'],
],
]);
$userOrig = clone $user;
$sut->updateUser($user);
self::assertEquals($userOrig->setEmail('foobar'), $user);
self::assertEquals($user->getPreferenceValue('ldap.dn'), 'blub-updated');
}
public function getValidConfigsTestData()
{
return [
[
[
0 => [
'dn' => 'blub',
'uid' => ['Karl-Heinz'],
// just some rubbish data
'blub' => ['dfsdfsdf'],
'foo' => ['count' => 1, 'bar'],
'bar' => ['count' => 1, 'foo', 'xxx'],
'xxxxxxxx' => ['https://www.example.com'],
'blub1' => ['dfsdfsdf'],
],
'count' => 1,
],
[
'baseDn' => 'ou=groups, dc=kimai, dc=org',
'nameAttribute' => 'cn',
'usernameAttribute' => 'cn', // test that "cn" is not set and fallback to "dn" happens
'userDnAttribute' => 'member',
'groups' => [
['ldap_value' => 'group1', 'role' => 'ROLE_TEAMLEAD'],
['ldap_value' => 'group2', 'role' => 'ROLE_ADMIN'],
['ldap_value' => 'group3', 'role' => 'ROLE_CUSTOMER'], // not existing!
['ldap_value' => 'group4', 'role' => 'ROLE_SUPER_ADMIN'],
],
],
'(&(member=blub))'
],
[
[
0 => [
'dn' => 'blub',
'uid' => ['Karl-Heinz'],
// just some rubbish data
'blub' => ['dfsdfsdf'],
'foo' => ['count' => 1, 'bar'],
'bar' => ['count' => 1, 'foo', 'xxx'],
'xxxxxxxx' => ['https://www.example.com'],
'blub1' => ['dfsdfsdf'],
],
'count' => 1,
],
[
'baseDn' => 'ou=groups, dc=kimai, dc=org',
'nameAttribute' => 'cn',
'usernameAttribute' => 'blub1',
'userDnAttribute' => 'memberuid',
'groups' => [
['ldap_value' => 'group1', 'role' => 'ROLE_TEAMLEAD'],
['ldap_value' => 'group2', 'role' => 'ROLE_ADMIN'],
['ldap_value' => 'group3', 'role' => 'ROLE_CUSTOMER'], // not existing!
['ldap_value' => 'group4', 'role' => 'ROLE_SUPER_ADMIN'],
],
],
'(&(memberuid=dfsdfsdf))'
],
];
}
/**
* @dataProvider getValidConfigsTestData
*/
public function testUpdateUserOnValidResultWithRolesResult(array $expectedUsers, array $groupConfig, string $expectedGroupQuery)
{
$expected = [
0 => ['dn' => 'blub'],
'count' => 1,
];
$expectedGroups = [
// ROLE_TEAMLEAD
0 => [
'cn' => [0 => 'group1'],
'member' => [0 => 'uid=user1,ou=users,dc=kimai,dc=org', 1 => 'uid=user2,ou=users,dc=kimai,dc=org'],
],
// ROLE_ADMIN
1 => [
'cn' => [0 => 'admin'],
'member' => [0 => 'uid=user2,ou=users,dc=kimai,dc=org', 1 => 'uid=user3,ou=users,dc=kimai,dc=org'],
],
// will be ignored: unknown group
2 => [
'cn' => [0 => 'kimai_admin'],
'member' => [0 => 'uid=user2,ou=users,dc=kimai,dc=org', 1 => 'uid=user3,ou=users,dc=kimai,dc=org'],
],
// will be ignored: unknown group
3 => [
'cn' => [0 => 'group3'],
'member' => [0 => 'uid=user2,ou=users,dc=kimai,dc=org', 1 => 'uid=user3,ou=users,dc=kimai,dc=org'],
],
// will be ignored: the counter below does not announce this group!
4 => [
'cn' => [0 => 'group4'],
'member' => [0 => 'uid=user2,ou=users,dc=kimai,dc=org', 1 => 'uid=user3,ou=users,dc=kimai,dc=org'],
],
'count' => 4
];
$driver = $this->getMockBuilder(LdapDriver::class)->disableOriginalConstructor()->setMethods(['search'])->getMock();
$driver->expects($this->exactly(3))->method('search')->willReturnCallback(function ($baseDn, $filter, $attributes) use ($expectedUsers, $expectedGroups, $expectedGroupQuery, $expected) {
if ($baseDn === 'ou=users, dc=kimai, dc=org') {
self::assertEquals('(&(&(objectClass=inetOrgPerson))(uid=Karl-Heinz))', $filter);
return $expected;
} elseif ($baseDn === 'blub') {
// user attributes search
self::assertEquals('(objectClass=*)', $filter);
return $expectedUsers;
} elseif ($baseDn === 'ou=groups, dc=kimai, dc=org') {
// roles search
self::assertEquals($expectedGroupQuery, $filter);
self::assertEquals([0 => 'cn'], $attributes);
return $expectedGroups;
}
$this->fail(sprintf('Unexpected search with baseDn %s', $baseDn));
});
$sut = $this->getLdapManager($driver, $groupConfig);
$user = (new User())->setUsername('Karl-Heinz');
$user->setPreferenceValue('ldap.dn', 'blub');
$userOrig = clone $user;
$userOrig->setEmail('Karl-Heinz')->setRoles(['ROLE_TEAMLEAD', 'ROLE_ADMIN']);
$sut->updateUser($user);
self::assertEquals($userOrig, $user);
self::assertEquals(['ROLE_TEAMLEAD', 'ROLE_ADMIN', 'ROLE_USER'], $user->getRoles());
}
}

View File

@@ -0,0 +1,189 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Configuration\LdapConfiguration;
use App\Entity\User;
use App\Ldap\LdapUserHydrator;
use App\Security\RoleService;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Ldap\LdapUserHydrator
*/
class LdapUserHydratorTest extends TestCase
{
public function testEmptyHydrate()
{
$config = new LdapConfiguration([
'active' => false,
'connection' => [
'host' => '1.1.1.1'
],
'user' => [
'usernameAttribute' => 'foo',
'attributes' => []
],
'role' => [],
]);
$sut = new LdapUserHydrator($config, new RoleService([]));
$user = $sut->hydrate(['dn' => 'blub']);
self::assertInstanceOf(User::class, $user);
self::assertEmpty($user->getUsername());
self::assertEmpty($user->getEmail());
}
public function testHydrate()
{
$config = new LdapConfiguration([
'active' => false,
'connection' => [
'host' => '1.1.1.1'
],
'user' => [
'usernameAttribute' => 'foo',
'attributes' => [
['ldap_attr' => 'uid', 'user_method' => 'setUsername'],
['ldap_attr' => 'foo', 'user_method' => 'setAlias'],
['ldap_attr' => 'bar', 'user_method' => 'setTitle'],
['ldap_attr' => 'roles', 'user_method' => 'setRoles'],
['ldap_attr' => 'xxxxxxxx', 'user_method' => 'setAvatar'],
['ldap_attr' => 'blubXX', 'user_method' => 'setAvatar'],
]
],
'role' => [],
]);
$ldapEntry = [
'uid' => ['Karl-Heinz'],
'blub' => ['dfsdfsdf'],
'foo' => ['count' => 1, 0 => 'bar'],
'bar' => ['foo'],
'roles' => ['count' => 2, 0 => 'ROLE_TEAMLEAD', 1 => 'ROLE_ADMIN'],
'xxxxxxxx' => ['https://www.example.com'],
'blub1' => ['dfsdfsdf'],
'dn' => 'blub',
];
$sut = new LdapUserHydrator($config, new RoleService([]));
$user = $sut->hydrate($ldapEntry);
self::assertInstanceOf(User::class, $user);
self::assertEquals('Karl-Heinz', $user->getUsername());
self::assertEquals('bar', $user->getAlias());
self::assertEquals('foo', $user->getTitle());
self::assertEquals(['ROLE_TEAMLEAD', 'ROLE_ADMIN', 'ROLE_USER'], $user->getRoles());
self::assertEquals('https://www.example.com', $user->getAvatar());
self::assertEquals('Karl-Heinz', $user->getEmail());
}
public function testHydrateUser()
{
$config = new LdapConfiguration([
'active' => false,
'connection' => [
'host' => '1.1.1.1'
],
'user' => [
'usernameAttribute' => 'foo',
'attributes' => [
['ldap_attr' => 'uid', 'user_method' => 'setUsername'],
['ldap_attr' => 'foo', 'user_method' => 'setAlias'],
['ldap_attr' => 'bar', 'user_method' => 'setTitle'],
['ldap_attr' => 'xxxxxxxx', 'user_method' => 'setAvatar'],
]
],
'role' => [],
]);
$ldapEntry = [
'uid' => ['Karl-Heinz'],
'blub' => ['dfsdfsdf'],
'foo' => ['bar'],
'bar' => ['foo'],
'xxxxxxxx' => ['https://www.example.com'],
'blub1' => ['dfsdfsdf'],
'dn' => 'blub',
];
$sut = new LdapUserHydrator($config, new RoleService([]));
$user = new User();
$user->setPassword('foobar');
$sut->hydrateUser($user, $ldapEntry);
self::assertEquals('Karl-Heinz', $user->getUsername());
self::assertEquals('bar', $user->getAlias());
self::assertEquals('foo', $user->getTitle());
self::assertEquals('https://www.example.com', $user->getAvatar());
self::assertEquals('Karl-Heinz', $user->getEmail());
// make sure that the password was resetted in hydrate
$pwdCheck = clone $user;
$pwdCheck->setPassword('');
self::assertEquals($pwdCheck, $user);
}
public function testHydrateRoles()
{
$config = new LdapConfiguration([
'user' => [
'attributes' => []
],
'role' => [
'nameAttribute' => 'cn',
'userDnAttribute' => 'member',
'groups' => [
['ldap_value' => 'group1', 'role' => 'ROLE_TEAMLEAD'],
['ldap_value' => 'group2', 'role' => 'ROLE_ADMIN'],
['ldap_value' => 'group3', 'role' => 'ROLE_CUSTOMER'], // not existing!
['ldap_value' => 'group4', 'role' => 'ROLE_SUPER_ADMIN'],
],
],
]);
$ldapGroups = [
// ROLE_TEAMLEAD
0 => [
'cn' => [0 => 'group1'],
'member' => [0 => 'uid=user1,ou=users,dc=kimai,dc=org', 1 => 'uid=user2,ou=users,dc=kimai,dc=org'],
],
// ROLE_ADMIN
1 => [
'cn' => [0 => 'admin'],
'member' => [0 => 'uid=user2,ou=users,dc=kimai,dc=org', 1 => 'uid=user3,ou=users,dc=kimai,dc=org'],
],
// will be ignored: unknown group
2 => [
'cn' => [0 => 'kimai_admin'],
'member' => [0 => 'uid=user2,ou=users,dc=kimai,dc=org', 1 => 'uid=user3,ou=users,dc=kimai,dc=org'],
],
// will be ignored: unknown group
3 => [
'cn' => [0 => 'group3'],
'member' => [0 => 'uid=user2,ou=users,dc=kimai,dc=org', 1 => 'uid=user3,ou=users,dc=kimai,dc=org'],
],
// will be ignored: the counter below does not announce this group!
4 => [
'cn' => [0 => 'group4'],
'member' => [0 => 'uid=user2,ou=users,dc=kimai,dc=org', 1 => 'uid=user3,ou=users,dc=kimai,dc=org'],
],
'count' => 4
];
$sut = new LdapUserHydrator($config, new RoleService([
'ROLE_TEAMLEAD' => ['ROLE_USER'],
'ROLE_ADMIN' => ['ROLE_TEAMLEAD'],
'ROLE_SUPER_ADMIN' => ['ROLE_ADMIN']
]));
$user = new User();
$sut->hydrateRoles($user, $ldapGroups);
self::assertEquals(['ROLE_TEAMLEAD', 'ROLE_ADMIN', 'ROLE_USER'], $user->getRoles());
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Configuration\LdapConfiguration;
use App\Entity\User;
use App\Ldap\LdapManager;
use App\Ldap\LdapUserProvider;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Ldap\LdapUserProvider
*/
class LdapUserProviderTest extends TestCase
{
public function testDeactivatedSupportsClass()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => false]);
$sut = new LdapUserProvider($manager, $config);
self::assertFalse($sut->supportsClass(User::class));
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException
* @expectedExceptionMessage LDAP is deactivated, user "test" not searched
*/
public function testDeactivatedLoadUserByUsername()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => false]);
$sut = new LdapUserProvider($manager, $config);
$sut->loadUserByUsername('test');
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\UnsupportedUserException
* @expectedExceptionMessage Instances of "App\Entity\User" are not supported.
*/
public function testDeactivatedRefreshUser()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->getMock();
$config = new LdapConfiguration(['active' => false]);
$sut = new LdapUserProvider($manager, $config);
$sut->refreshUser(new User());
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException
* @expectedExceptionMessage User "test" not found
*/
public function testLoadUserByUsernameReturnsNull()
{
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->setMethods(['findUserByUsername'])->getMock();
$manager->expects($this->once())->method('findUserByUsername')->willReturn(null);
$config = new LdapConfiguration(['active' => true]);
$sut = new LdapUserProvider($manager, $config);
$sut->loadUserByUsername('test');
}
public function testLoadUserByUsernameReturnsUser()
{
$user = new User();
$user->setUsername('foobar');
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->setMethods(['findUserByUsername'])->getMock();
$manager->expects($this->once())->method('findUserByUsername')->willReturn($user);
$config = new LdapConfiguration(['active' => true]);
$sut = new LdapUserProvider($manager, $config);
$actual = $sut->loadUserByUsername('test');
self::assertInstanceOf(User::class, $actual);
self::assertSame($user, $actual);
}
public function testRefreshUserReturnsUser()
{
$user = new User();
$user->setUsername('foobar');
$user->setPreferenceValue('ldap.dn', 'sdfdsf');
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->setMethods(['updateUser'])->getMock();
$config = new LdapConfiguration(['active' => true]);
$sut = new LdapUserProvider($manager, $config);
$actual = $sut->refreshUser($user);
self::assertInstanceOf(User::class, $actual);
self::assertSame($user, $actual);
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Ldap\SanitizingException;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Ldap\SanitizingException
*/
class SanitizingExceptionTest extends TestCase
{
public function testMessagesAreSanitized()
{
$ex = new \Exception('Could not find user foo with password bar in your LDAP');
$sut = new SanitizingException($ex, 'bar');
self::assertInstanceOf(\Exception::class, $sut);
self::assertNotContains('bar', $sut->getMessage());
self::assertNotContains('bar', (string) $sut);
self::assertEquals('Could not find user foo with password **** in your LDAP', $sut->getMessage());
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Ldap;
use App\Configuration\LdapConfiguration;
use App\Ldap\ZendLdap;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Ldap\ZendLdap
*/
class ZendLdapTest extends TestCase
{
public function testConstructDeactivated()
{
$config = new LdapConfiguration([
'active' => false,
'connection' => [
'host' => '1.1.1.1'
]
]);
$sut = new ZendLdap($config);
$options = $sut->getOptions();
self::assertNull($options['host']);
}
public function testConstructActivatedPassesOptions()
{
$config = new LdapConfiguration([
'active' => true,
'connection' => [
'host' => '1.1.1.1'
]
]);
$sut = new ZendLdap($config);
$options = $sut->getOptions();
self::assertEquals('1.1.1.1', $options['host']);
}
}

View File

@@ -21,7 +21,7 @@ abstract class AbstractRepositoryTest extends KernelTestCase
use KernelTestTrait;
/**
* @var EntityManager
* @var EntityManager|null
*/
private $entityManager;

View File

@@ -10,6 +10,7 @@
namespace App\Tests\Repository;
use App\Entity\Tag;
use App\Repository\TagRepository;
use App\Tests\DataFixtures\TagFixtures;
/**
@@ -29,6 +30,7 @@ class TagRepositoryTest extends AbstractRepositoryTest
public function testFindIds()
{
$em = $this->getEntityManager();
/** @var TagRepository $repository */
$repository = $em->getRepository(Tag::class);
$result = $repository->findIdsByTagNameList('2018,Test');
@@ -47,6 +49,7 @@ class TagRepositoryTest extends AbstractRepositoryTest
public function testFindNoIds()
{
$em = $this->getEntityManager();
/** @var TagRepository $repository */
$repository = $em->getRepository(Tag::class);
$result = $repository->findIdsByTagNameList('Simply');
@@ -58,6 +61,7 @@ class TagRepositoryTest extends AbstractRepositoryTest
public function testFindAllTagNames()
{
$em = $this->getEntityManager();
/** @var TagRepository $repository */
$repository = $em->getRepository(Tag::class);
$result = $repository->findAllTagNames('2018');
@@ -75,6 +79,7 @@ class TagRepositoryTest extends AbstractRepositoryTest
public function testFindNoTagNames()
{
$em = $this->getEntityManager();
/** @var TagRepository $repository */
$repository = $em->getRepository(Tag::class);
$result = $repository->findAllTagNames('Nothing');

View File

@@ -14,9 +14,12 @@ use App\Entity\Project;
use App\Entity\Tag;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Repository\ActivityRepository;
use App\Repository\ProjectRepository;
use App\Repository\Query\BaseQuery;
use App\Repository\Query\TimesheetQuery;
use App\Repository\RepositoryException;
use App\Repository\TimesheetRepository;
use App\Tests\DataFixtures\TimesheetFixtures;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Pagerfanta;
@@ -29,6 +32,7 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest
public function testResultTypeForQueryState()
{
$em = $this->getEntityManager();
/** @var TimesheetRepository $repository */
$repository = $em->getRepository(Timesheet::class);
$query = new TimesheetQuery();
@@ -53,6 +57,7 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest
{
$em = $this->getEntityManager();
$user = $this->getUserByRole($em, User::ROLE_USER);
/** @var TimesheetRepository $repository */
$repository = $em->getRepository(Timesheet::class);
$fixtures = new TimesheetFixtures();
@@ -66,6 +71,7 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest
$query->setUser($user);
$query->setState(TimesheetQuery::STATE_STOPPED);
/** @var array $entities */
$entities = $repository->findByQuery($query);
$this->assertCount(1, $entities);
@@ -81,6 +87,7 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest
{
$em = $this->getEntityManager();
$user = $this->getUserByRole($em, User::ROLE_USER);
/** @var TimesheetRepository $repository */
$repository = $em->getRepository(Timesheet::class);
$fixtures = new TimesheetFixtures();
@@ -100,12 +107,15 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest
public function testSave()
{
$em = $this->getEntityManager();
/** @var ActivityRepository $activityRepository */
$activityRepository = $em->getRepository(Activity::class);
$activity = $activityRepository->find(1);
/** @var ProjectRepository $projectRepository */
$projectRepository = $em->getRepository(Project::class);
$project = $projectRepository->find(1);
$user = $this->getUserByRole($em, User::ROLE_USER);
/** @var TimesheetRepository $repository */
$repository = $em->getRepository(Timesheet::class);
$timesheet = new Timesheet();
$timesheet
@@ -124,12 +134,15 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest
public function testSaveWithTags()
{
$em = $this->getEntityManager();
/** @var ActivityRepository $activityRepository */
$activityRepository = $em->getRepository(Activity::class);
$activity = $activityRepository->find(1);
/** @var ProjectRepository $projectRepository */
$projectRepository = $em->getRepository(Project::class);
$project = $projectRepository->find(1);
$user = $this->getUserByRole($em, User::ROLE_USER);
/** @var TimesheetRepository $repository */
$repository = $em->getRepository(Timesheet::class);
$tagOne = new Tag();
$tagOne->setName('Travel');

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Security;
use App\Security\RoleService;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Security\RoleService
*/
class RoleServiceTest extends TestCase
{
public function testGetAvailableNames()
{
$real = [
'ROLE_TEAMLEAD' => [0 => 'ROLE_USER'],
'ROLE_ADMIN' => [0 => 'ROLE_TEAMLEAD'],
'ROLE_SUPER_ADMIN' => [0 => 'ROLE_ADMIN']
];
$sut = new RoleService($real);
$expected = ['ROLE_TEAMLEAD', 'ROLE_USER', 'ROLE_ADMIN', 'ROLE_SUPER_ADMIN'];
self::assertEquals($expected, $sut->getAvailableNames());
}
}

View File

@@ -7,51 +7,57 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Model;
namespace App\Tests\Security;
use App\Entity\User;
use App\Security\UserChecker;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\User as SymfonyUser;
/**
* @covers \App\Security\UserChecker
*/
class UserCheckerTest extends TestCase
{
public function testCheckPreAuth()
public function testCheckPreAuthReturnsOnUnknownUserClass()
{
$sut = new UserChecker();
$user = new User();
try {
$sut->checkPreAuth($user);
$sut->checkPreAuth(new SymfonyUser('sdf', null));
} catch (\Exception $ex) {
$this->fail('UserChecker should not throw exception in checkPreAuth()');
$this->fail('UserChecker should not throw exception in checkPreAuth(), ' . $ex->getMessage());
}
$this->assertTrue(true);
}
public function testCheckPostAuthReturnsOnUnknownUserClass()
{
$sut = new UserChecker();
try {
$sut->checkPostAuth(new SymfonyUser('sdf', null));
} catch (\Exception $ex) {
$this->fail('UserChecker should not throw exception in checkPostAuth(), ' . $ex->getMessage());
}
$this->assertTrue(true);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\LockedException
* @expectedException \Symfony\Component\Security\Core\Exception\DisabledException
* @expectedExceptionMessage User account is disabled.
*/
public function testDisabledCannotLogin()
public function testDisabledCannotLoginInCheckPreAuth()
{
$sut = new UserChecker();
$user = new User();
$user->setEnabled(false);
$sut->checkPostAuth($user);
(new UserChecker())->checkPreAuth((new User())->setEnabled(false));
}
public function testCheckPostAuth()
/**
* @expectedException \Symfony\Component\Security\Core\Exception\DisabledException
* @expectedExceptionMessage User account is disabled.
*/
public function testDisabledCannotLoginInCheckPostAuth()
{
$sut = new UserChecker();
$mock = $this->getMockBuilder(UserInterface::class)->setMethods(['isEnabled'])->getMockForAbstractClass();
$mock->expects($this->never())->method('isEnabled')->willReturn(false);
$sut->checkPostAuth($mock);
$this->assertTrue(true);
(new UserChecker())->checkPostAuth((new User())->setEnabled(false));
}
}

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Timesheet\Calculator;
namespace App\Tests\Timesheet\Rounding;
use App\Entity\Timesheet;
use App\Timesheet\Rounding\CeilRounding;

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Timesheet\Calculator;
namespace App\Tests\Timesheet\Rounding;
use App\Entity\Timesheet;
use App\Timesheet\Rounding\ClosestRounding;

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Timesheet\Calculator;
namespace App\Tests\Timesheet\Rounding;
use App\Entity\Timesheet;
use App\Timesheet\Rounding\DefaultRounding;

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Timesheet\Calculator;
namespace App\Tests\Timesheet\Rounding;
use App\Entity\Timesheet;
use App\Timesheet\Rounding\FloorRounding;

View File

@@ -242,7 +242,6 @@ class ExtensionsTest extends TestCase
'timesheet.html#duration-format' => 'https://www.kimai.org/documentation/timesheet.html#duration-format',
'invoice.html' => 'https://www.kimai.org/documentation/invoice.html',
'' => 'https://www.kimai.org/documentation/',
null => 'https://www.kimai.org/documentation/',
];
$sut = $this->getSut($this->localeEn);

View File

@@ -28,18 +28,18 @@ abstract class AbstractVoterTest extends TestCase
$isAuthenticated = empty($user->getRoles());
$accessManager = $this->getMockBuilder(AclDecisionManager::class)->disableOriginalConstructor()->getMock();
$accessManager->method('isFullyAuthenticated')->willReturn($isAuthenticated);
$accessManager->method('hasRole')->willReturnCallback(function ($role) use ($user) {
return in_array($role, $user->getRoles());
});
$class = new \ReflectionClass($voterClass);
/** @var AbstractVoter $voter */
$voter = $class->newInstance($accessManager, $this->getRolePermissionManager());
self::assertInstanceOf(AbstractVoter::class, $voter);
return $class->newInstance($accessManager, $this->getRolePermissionManager());
return $voter;
}
/**
* @param $id
* @param $role
* @param int $id
* @param string $role
* @return User
*/
protected function getUser($id, $role)

View File

@@ -148,8 +148,8 @@ class TimesheetVoterTest extends AbstractVoterTest
}
/**
* @param $id
* @param $role
* @param int $id
* @param string $role
* @return User
*/
protected function getUser($id, $role)

10
tests/phpstan.neon Normal file
View File

@@ -0,0 +1,10 @@
includes:
- %rootDir%/../phpstan-symfony/extension.neon
- %rootDir%/../phpstan-doctrine/extension.neon
- %rootDir%/../phpstan-phpunit/extension.neon
parameters:
ignoreErrors:
- '#Access to an undefined property Faker\\Generator::\$stateAbbr.#'
- '#Access to an undefined property Faker\\Generator::\$catchPhrase.#'
- '#Access to an undefined property Faker\\Generator::\$bs.#'