added support for saml login (#1408)
This commit is contained in:
@@ -57,6 +57,9 @@ class AppExtensionTest extends TestCase
|
||||
'data_dir' => '/tmp/',
|
||||
'plugin_dir' => '/tmp/',
|
||||
'timesheet' => [],
|
||||
'saml' => [
|
||||
'connection' => []
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -181,6 +181,46 @@ class ConfigurationTest extends TestCase
|
||||
$this->assertConfig($config, []);
|
||||
}
|
||||
|
||||
public function testValidateSamlIsMissingMappingForEmail()
|
||||
{
|
||||
$this->expectException(InvalidConfigurationException::class);
|
||||
$this->expectExceptionMessage('Invalid configuration for path "kimai.saml": You need to configure a SAML mapping for the email attribute.');
|
||||
|
||||
$config = $this->getMinConfig();
|
||||
$config['saml'] = [
|
||||
'activate' => true,
|
||||
'mapping' => [],
|
||||
];
|
||||
|
||||
$this->assertConfig($config, []);
|
||||
}
|
||||
|
||||
public function testValidateSamlDoesNotTriggerOnDeactivatedSaml()
|
||||
{
|
||||
$finalizedConfig = $this->getCompiledConfig($this->getMinConfig());
|
||||
$config = $this->getMinConfig();
|
||||
$config['saml'] = [
|
||||
'activate' => false,
|
||||
'mapping' => [],
|
||||
];
|
||||
|
||||
$this->assertConfig($config, $finalizedConfig);
|
||||
}
|
||||
|
||||
public function testValidateSamlDoesNotTriggerWhenEmailMappingExists()
|
||||
{
|
||||
$config = $this->getMinConfig();
|
||||
$config['saml'] = [
|
||||
'activate' => true,
|
||||
'mapping' => [
|
||||
['saml' => 'email', 'kimai' => 'email']
|
||||
],
|
||||
];
|
||||
$finalizedConfig = $this->getCompiledConfig($config);
|
||||
|
||||
$this->assertConfig($config, $finalizedConfig);
|
||||
}
|
||||
|
||||
public function testDefaultLdapSettings()
|
||||
{
|
||||
$finalizedConfig = $this->getCompiledConfig($this->getMinConfig());
|
||||
@@ -349,6 +389,18 @@ class ConfigurationTest extends TestCase
|
||||
'userDnAttribute' => 'member',
|
||||
'groups' => [],
|
||||
],
|
||||
],
|
||||
'saml' => [
|
||||
'activate' => false,
|
||||
'title' => 'Login with SAML',
|
||||
'roles' => [
|
||||
'attribute' => null,
|
||||
'mapping' => []
|
||||
],
|
||||
'mapping' => [],
|
||||
'connection' => [
|
||||
'organization' => []
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
@@ -24,23 +24,54 @@ class UserTest extends TestCase
|
||||
{
|
||||
$user = new User();
|
||||
$this->assertInstanceOf(ArrayCollection::class, $user->getPreferences());
|
||||
$this->assertNull($user->getTitle());
|
||||
$this->assertNull($user->getDisplayName());
|
||||
$this->assertNull($user->getAvatar());
|
||||
$this->assertNull($user->getAlias());
|
||||
$this->assertNull($user->getId());
|
||||
$this->assertNull($user->getApiToken());
|
||||
$this->assertNull($user->getPlainApiToken());
|
||||
$this->assertEquals(User::DEFAULT_LANGUAGE, $user->getLocale());
|
||||
self::assertNull($user->getTitle());
|
||||
self::assertNull($user->getDisplayName());
|
||||
self::assertNull($user->getAvatar());
|
||||
self::assertNull($user->getAlias());
|
||||
self::assertNull($user->getId());
|
||||
self::assertNull($user->getApiToken());
|
||||
self::assertNull($user->getPlainApiToken());
|
||||
self::assertEquals(User::DEFAULT_LANGUAGE, $user->getLocale());
|
||||
|
||||
$user->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y');
|
||||
$this->assertEquals('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', $user->getAvatar());
|
||||
self::assertEquals('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', $user->getAvatar());
|
||||
|
||||
$user->setApiToken('nbvfdswe34567ujko098765rerfghbgvfcdsert');
|
||||
$this->assertEquals('nbvfdswe34567ujko098765rerfghbgvfcdsert', $user->getApiToken());
|
||||
self::assertEquals('nbvfdswe34567ujko098765rerfghbgvfcdsert', $user->getApiToken());
|
||||
|
||||
$user->setPlainApiToken('https://www.gravatar.com/avatar/nbvfdswe34567ujko098765rerfghbgvfcdsert');
|
||||
$this->assertEquals('https://www.gravatar.com/avatar/nbvfdswe34567ujko098765rerfghbgvfcdsert', $user->getPlainApiToken());
|
||||
self::assertEquals('https://www.gravatar.com/avatar/nbvfdswe34567ujko098765rerfghbgvfcdsert', $user->getPlainApiToken());
|
||||
|
||||
$user->setTitle('Mr. Code Blaster');
|
||||
$this->assertEquals('Mr. Code Blaster', $user->getTitle());
|
||||
self::assertEquals('Mr. Code Blaster', $user->getTitle());
|
||||
}
|
||||
|
||||
public function testAuth()
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
self::assertEquals(User::AUTH_INTERNAL, $user->getAuth());
|
||||
self::assertFalse($user->isLdapUser());
|
||||
self::assertFalse($user->isSamlUser());
|
||||
self::assertTrue($user->isInternalUser());
|
||||
|
||||
$user->setAuth(User::AUTH_LDAP);
|
||||
self::assertEquals(User::AUTH_LDAP, $user->getAuth());
|
||||
self::assertTrue($user->isLdapUser());
|
||||
self::assertFalse($user->isSamlUser());
|
||||
self::assertFalse($user->isInternalUser());
|
||||
|
||||
$user->setAuth(User::AUTH_SAML);
|
||||
self::assertEquals(User::AUTH_SAML, $user->getAuth());
|
||||
self::assertFalse($user->isLdapUser());
|
||||
self::assertTrue($user->isSamlUser());
|
||||
self::assertFalse($user->isInternalUser());
|
||||
|
||||
$user->setAuth(User::AUTH_INTERNAL);
|
||||
self::assertEquals(User::AUTH_INTERNAL, $user->getAuth());
|
||||
self::assertFalse($user->isLdapUser());
|
||||
self::assertFalse($user->isSamlUser());
|
||||
self::assertTrue($user->isInternalUser());
|
||||
}
|
||||
|
||||
public function testDatetime()
|
||||
@@ -48,30 +79,30 @@ class UserTest extends TestCase
|
||||
$date = new \DateTime('+1 day');
|
||||
$user = new User();
|
||||
$user->setRegisteredAt($date);
|
||||
$this->assertEquals($date, $user->getRegisteredAt());
|
||||
self::assertEquals($date, $user->getRegisteredAt());
|
||||
}
|
||||
|
||||
public function testPreferences()
|
||||
{
|
||||
$user = new User();
|
||||
$this->assertNull($user->getPreference('test'));
|
||||
$this->assertNull($user->getPreferenceValue('test'));
|
||||
$this->assertEquals('foo', $user->getPreferenceValue('test', 'foo'));
|
||||
self::assertNull($user->getPreference('test'));
|
||||
self::assertNull($user->getPreferenceValue('test'));
|
||||
self::assertEquals('foo', $user->getPreferenceValue('test', 'foo'));
|
||||
|
||||
$preference = new UserPreference();
|
||||
$preference
|
||||
->setName('test')
|
||||
->setValue('foobar');
|
||||
$user->addPreference($preference);
|
||||
$this->assertEquals('foobar', $user->getPreferenceValue('test', 'foo'));
|
||||
$this->assertEquals($preference, $user->getPreference('test'));
|
||||
self::assertEquals('foobar', $user->getPreferenceValue('test', 'foo'));
|
||||
self::assertEquals($preference, $user->getPreference('test'));
|
||||
|
||||
$user->setPreferenceValue('test', 'Hello World');
|
||||
$this->assertEquals('Hello World', $user->getPreferenceValue('test', 'foo'));
|
||||
self::assertEquals('Hello World', $user->getPreferenceValue('test', 'foo'));
|
||||
|
||||
$this->assertNull($user->getPreferenceValue('test2'));
|
||||
self::assertNull($user->getPreferenceValue('test2'));
|
||||
$user->setPreferenceValue('test2', 'I like rain');
|
||||
$this->assertEquals('I like rain', $user->getPreferenceValue('test2'));
|
||||
self::assertEquals('I like rain', $user->getPreferenceValue('test2'));
|
||||
}
|
||||
|
||||
public function testDisplayName()
|
||||
@@ -79,28 +110,28 @@ class UserTest extends TestCase
|
||||
$user = new User();
|
||||
|
||||
$user->setUsername('bar');
|
||||
$this->assertEquals('bar', $user->getDisplayName());
|
||||
$this->assertEquals('bar', $user->getUsername());
|
||||
$this->assertEquals('bar', (string) $user);
|
||||
self::assertEquals('bar', $user->getDisplayName());
|
||||
self::assertEquals('bar', $user->getUsername());
|
||||
self::assertEquals('bar', (string) $user);
|
||||
|
||||
$user->setAlias('foo');
|
||||
$this->assertEquals('foo', $user->getAlias());
|
||||
$this->assertEquals('bar', $user->getUsername());
|
||||
$this->assertEquals('foo', $user->getDisplayName());
|
||||
$this->assertEquals('foo', (string) $user);
|
||||
self::assertEquals('foo', $user->getAlias());
|
||||
self::assertEquals('bar', $user->getUsername());
|
||||
self::assertEquals('foo', $user->getDisplayName());
|
||||
self::assertEquals('foo', (string) $user);
|
||||
}
|
||||
|
||||
public function testGetLocale()
|
||||
{
|
||||
$sut = new User();
|
||||
$this->assertEquals(User::DEFAULT_LANGUAGE, $sut->getLocale());
|
||||
self::assertEquals(User::DEFAULT_LANGUAGE, $sut->getLocale());
|
||||
|
||||
$language = new UserPreference();
|
||||
$language->setName(UserPreference::LOCALE);
|
||||
$language->setValue('fr');
|
||||
$sut->addPreference($language);
|
||||
|
||||
$this->assertEquals('fr', $sut->getLocale());
|
||||
self::assertEquals('fr', $sut->getLocale());
|
||||
}
|
||||
|
||||
public function testTeams()
|
||||
@@ -142,4 +173,25 @@ class UserTest extends TestCase
|
||||
$sut->addRole(User::ROLE_TEAMLEAD);
|
||||
self::assertTrue($sut->isTeamlead());
|
||||
}
|
||||
|
||||
public function testPreferencesCollectionIsCreatedOnBrokenUser()
|
||||
{
|
||||
// this code is only used in some rare edge cases, maybe even only in development ...
|
||||
// lets keep it, as it occured during the work on SAML authentication
|
||||
$sut = new User();
|
||||
|
||||
$preference = new UserPreference();
|
||||
$preference
|
||||
->setName('test')
|
||||
->setValue('foobar');
|
||||
|
||||
$property = new \ReflectionProperty(User::class, 'preferences');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($sut, null);
|
||||
|
||||
// make sure that addPreference will work, even if the internal collection was set to null
|
||||
$sut->addPreference($preference);
|
||||
|
||||
self::assertEquals('foobar', $sut->getPreferenceValue('test'));
|
||||
}
|
||||
}
|
||||
|
||||
89
tests/EventSubscriber/ResetPasswordSubscriberTest.php
Normal file
89
tests/EventSubscriber/ResetPasswordSubscriberTest.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?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\EventSubscriber;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\EventSubscriber\ResetPasswordSubscriber;
|
||||
use App\Tests\Security\TestUserEntity;
|
||||
use FOS\UserBundle\Event\GetResponseNullableUserEvent;
|
||||
use FOS\UserBundle\FOSUserEvents;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* @covers \App\EventSubscriber\ResetPasswordSubscriber
|
||||
*/
|
||||
class ResetPasswordSubscriberTest extends TestCase
|
||||
{
|
||||
public function testGetSubscribedEvents()
|
||||
{
|
||||
$events = ResetPasswordSubscriber::getSubscribedEvents();
|
||||
self::assertCount(1, $events);
|
||||
$this->assertArrayHasKey(FOSUserEvents::RESETTING_SEND_EMAIL_INITIALIZE, $events);
|
||||
$methodName = $events[FOSUserEvents::RESETTING_SEND_EMAIL_INITIALIZE][0];
|
||||
$this->assertTrue(method_exists(ResetPasswordSubscriber::class, $methodName));
|
||||
}
|
||||
|
||||
public function testUnknownUserTypeIsIgnored()
|
||||
{
|
||||
$user = new TestUserEntity();
|
||||
$user->setUsername('foo@bar');
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$event = new GetResponseNullableUserEvent($user, $request);
|
||||
|
||||
$sut = new ResetPasswordSubscriber();
|
||||
$sut->onInitializeResetPassword($event);
|
||||
|
||||
self::assertNull($event->getResponse());
|
||||
}
|
||||
|
||||
public function testInternalAuthTypeIsIgnored()
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('foo@bar');
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$event = new GetResponseNullableUserEvent($user, $request);
|
||||
|
||||
$sut = new ResetPasswordSubscriber();
|
||||
$sut->onInitializeResetPassword($event);
|
||||
|
||||
self::assertNull($event->getResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getAuthTypeData
|
||||
*/
|
||||
public function testNonInternalAuthTypeThrowsAccessDeniedException(string $authType)
|
||||
{
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
$this->expectExceptionMessage(sprintf('The user "foo@bar" tried to reset the password, but it is registered as "%s" auth-type.', $authType));
|
||||
|
||||
$user = new User();
|
||||
$user->setUsername('foo@bar');
|
||||
$user->setAuth($authType);
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$event = new GetResponseNullableUserEvent($user, $request);
|
||||
|
||||
$sut = new ResetPasswordSubscriber();
|
||||
$sut->onInitializeResetPassword($event);
|
||||
}
|
||||
|
||||
public function getAuthTypeData()
|
||||
{
|
||||
return [
|
||||
[User::AUTH_SAML],
|
||||
[User::AUTH_LDAP],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,12 @@ class FormLoginLdapFactoryTest extends TestCase
|
||||
$result = $sut->create($container, 'test', ['foo' => 'bar'], 'fosuserbundle', 'secured_area');
|
||||
|
||||
self::assertEquals([
|
||||
'kimai_ldap.security.authentication.provider.test',
|
||||
'security.authentication.provider.kimai_ldap.test',
|
||||
'security.authentication.listener.form.test',
|
||||
'secured_area'
|
||||
], $result);
|
||||
|
||||
$definition = $container->getDefinition('kimai_ldap.security.authentication.provider.test');
|
||||
$definition = $container->getDefinition('security.authentication.provider.kimai_ldap.test');
|
||||
self::assertInstanceOf(ChildDefinition::class, $definition);
|
||||
self::assertEquals('test', $definition->getArguments()['index_1']);
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ class LdapManagerTest extends TestCase
|
||||
|
||||
$userOrig = clone $user;
|
||||
$sut->updateUser($user);
|
||||
self::assertEquals($userOrig->setEmail('foobar'), $user);
|
||||
self::assertEquals($userOrig->setEmail('foobar')->setAuth(User::AUTH_LDAP), $user);
|
||||
self::assertEquals($user->getPreferenceValue('ldap.dn'), 'blub-updated');
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ class LdapManagerTest extends TestCase
|
||||
$user = (new User())->setUsername('Karl-Heinz');
|
||||
$user->setPreferenceValue('ldap.dn', 'blub');
|
||||
$userOrig = clone $user;
|
||||
$userOrig->setEmail('Karl-Heinz')->setRoles(['ROLE_TEAMLEAD', 'ROLE_ADMIN']);
|
||||
$userOrig->setEmail('Karl-Heinz')->setRoles(['ROLE_TEAMLEAD', 'ROLE_ADMIN'])->setAuth(User::AUTH_LDAP);
|
||||
|
||||
$sut->updateUser($user);
|
||||
self::assertEquals($userOrig, $user);
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
namespace App\Tests\Ldap;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Ldap\LdapDriverException;
|
||||
use App\Ldap\LdapManager;
|
||||
use App\Ldap\LdapUserProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
|
||||
/**
|
||||
@@ -51,6 +53,7 @@ class LdapUserProviderTest extends TestCase
|
||||
$user = new User();
|
||||
$user->setUsername('foobar');
|
||||
$user->setPreferenceValue('ldap.dn', 'sdfdsf');
|
||||
self::assertFalse($user->isLdapUser());
|
||||
|
||||
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->onlyMethods(['updateUser'])->getMock();
|
||||
|
||||
@@ -59,5 +62,36 @@ class LdapUserProviderTest extends TestCase
|
||||
|
||||
self::assertInstanceOf(User::class, $actual);
|
||||
self::assertSame($user, $actual);
|
||||
self::assertTrue($user->isLdapUser());
|
||||
}
|
||||
|
||||
public function testRefreshUserThrowsExceptionOnNonLdapUser()
|
||||
{
|
||||
$this->expectException(UnsupportedUserException::class);
|
||||
$this->expectExceptionMessage('Account "foobar" is not a registered LDAP user.');
|
||||
|
||||
$user = new User();
|
||||
$user->setUsername('foobar');
|
||||
|
||||
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->onlyMethods(['updateUser'])->getMock();
|
||||
|
||||
$sut = new LdapUserProvider($manager);
|
||||
$actual = $sut->refreshUser($user);
|
||||
}
|
||||
|
||||
public function testRefreshUserThrowsExceptionOnBrokenUpdateUser()
|
||||
{
|
||||
$this->expectException(UnsupportedUserException::class);
|
||||
$this->expectExceptionMessage('Failed to refresh user "foobar", probably DN is expired.');
|
||||
|
||||
$user = new User();
|
||||
$user->setUsername('foobar');
|
||||
$user->setPreferenceValue('ldap.dn', 'sdfdsf');
|
||||
|
||||
$manager = $this->getMockBuilder(LdapManager::class)->disableOriginalConstructor()->onlyMethods(['updateUser'])->getMock();
|
||||
$manager->expects($this->once())->method('updateUser')->willThrowException(new LdapDriverException('blub'));
|
||||
|
||||
$sut = new LdapUserProvider($manager);
|
||||
$actual = $sut->refreshUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
84
tests/Mocks/Saml/SamlAuthFactory.php
Normal file
84
tests/Mocks/Saml/SamlAuthFactory.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?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\Mocks\Saml;
|
||||
|
||||
use App\Saml\SamlAuth;
|
||||
use App\Tests\Mocks\AbstractMockFactory;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class SamlAuthFactory extends AbstractMockFactory
|
||||
{
|
||||
public function create(?array $connection = null): SamlAuth
|
||||
{
|
||||
if (null === $connection) {
|
||||
$connection = [
|
||||
'idp' => [
|
||||
'entityId' => 'https://accounts.google.com/o/saml2?idpid=',
|
||||
'singleSignOnService' => [
|
||||
'url' => 'https://accounts.google.com/o/saml2/idp?idpid=',
|
||||
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
|
||||
],
|
||||
'x509cert' => 'asdf',
|
||||
],
|
||||
'sp' => [
|
||||
'entityId' => 'https://127.0.0.1:8010/auth/saml/metadata',
|
||||
'assertionConsumerService' => [
|
||||
'url' => 'https://127.0.0.1:8010/auth/saml/acs',
|
||||
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
|
||||
],
|
||||
'singleLogoutService' => [
|
||||
'url' => 'https://127.0.0.1:8010/auth/saml/logout',
|
||||
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
|
||||
],
|
||||
'privateKey' => ''
|
||||
],
|
||||
'strict' => true,
|
||||
'debug' => true,
|
||||
'security' => [
|
||||
'nameIdEncrypted' => false,
|
||||
'authnRequestsSigned' => false,
|
||||
'logoutRequestSigned' => false,
|
||||
'logoutResponseSigned' => false,
|
||||
'wantMessagesSigned' => false,
|
||||
'wantAssertionsSigned' => false,
|
||||
'wantNameIdEncrypted' => false,
|
||||
'requestedAuthnContext' => true,
|
||||
'signMetadata' => false,
|
||||
'wantXMLValidation' => true,
|
||||
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
|
||||
'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256',
|
||||
],
|
||||
'contactPerson' => [
|
||||
'technical' => [
|
||||
'givenName' => 'Kimai Admin',
|
||||
'emailAddress' => 'kimai-tech@example.com',
|
||||
],
|
||||
'support' => [
|
||||
'givenName' => 'Kimai Support',
|
||||
'emailAddress' => 'kimai-support@example.com',
|
||||
]
|
||||
],
|
||||
'organization' => [
|
||||
'en' => [
|
||||
'name' => 'Kimai',
|
||||
'displayname' => 'Kimai',
|
||||
'url' => 'https://www.kimai.org',
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push(new Request());
|
||||
|
||||
return new SamlAuth($requestStack, $connection);
|
||||
}
|
||||
}
|
||||
45
tests/Saml/Controller/SamlControllerTest.php
Normal file
45
tests/Saml/Controller/SamlControllerTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Saml\Controller;
|
||||
|
||||
use App\Saml\Controller\SamlController;
|
||||
use App\Tests\Mocks\Saml\SamlAuthFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
class SamlControllerTest extends TestCase
|
||||
{
|
||||
protected function getAuth()
|
||||
{
|
||||
return (new SamlAuthFactory($this))->create();
|
||||
}
|
||||
|
||||
public function testAssertionConsumerServiceAction()
|
||||
{
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('You must configure the check path in your firewall.');
|
||||
|
||||
$oauth = $this->getAuth();
|
||||
$sut = new SamlController($oauth);
|
||||
$sut->assertionConsumerServiceAction();
|
||||
}
|
||||
|
||||
public function testLogoutAction()
|
||||
{
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('You must configure the logout path in your firewall.');
|
||||
|
||||
$oauth = $this->getAuth();
|
||||
$sut = new SamlController($oauth);
|
||||
$sut->logoutAction();
|
||||
}
|
||||
}
|
||||
62
tests/Saml/Logout/SamlLogoutHandlerTest.php
Normal file
62
tests/Saml/Logout/SamlLogoutHandlerTest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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\Saml\Logout;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Saml\Logout\SamlLogoutHandler;
|
||||
use App\Saml\SamlAuth;
|
||||
use Hslavich\OneloginSamlBundle\Security\Authentication\Token\SamlToken;
|
||||
use OneLogin\Saml2\Error;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @covers \App\Saml\Logout\SamlLogoutHandler
|
||||
*/
|
||||
class SamlLogoutHandlerTest extends TestCase
|
||||
{
|
||||
public function testLogout()
|
||||
{
|
||||
$auth = $this->getMockBuilder(SamlAuth::class)->disableOriginalConstructor()->getMock();
|
||||
$auth->expects($this->once())->method('processSLO')->willThrowException(new Error('blub'));
|
||||
$auth->expects($this->once())->method('getSLOurl')->willReturn('');
|
||||
|
||||
$request = new Request();
|
||||
$response = new Response();
|
||||
$token = new SamlToken([]);
|
||||
|
||||
$sut = new SamlLogoutHandler($auth);
|
||||
$sut->logout($request, $response, $token);
|
||||
}
|
||||
|
||||
public function testLogoutWithLogoutUrl()
|
||||
{
|
||||
$auth = $this->getMockBuilder(SamlAuth::class)->disableOriginalConstructor()->getMock();
|
||||
$auth->expects($this->once())->method('processSLO')->willThrowException(new Error('blub'));
|
||||
$auth->expects($this->once())->method('getSLOurl')->willReturn('/logout');
|
||||
$auth->expects($this->once())->method('logout')->willReturnCallback(function () {
|
||||
$args = func_get_args();
|
||||
self::assertEquals(null, $args[0]);
|
||||
self::assertEquals([], $args[1]);
|
||||
self::assertEquals('tony', $args[2]);
|
||||
self::assertEquals('foo-bar', $args[3]);
|
||||
});
|
||||
|
||||
$request = new Request();
|
||||
$response = new Response();
|
||||
$token = new SamlToken([]);
|
||||
$token->setUser((new User())->setUsername('tony'));
|
||||
$token->setAttribute('sessionIndex', 'foo-bar');
|
||||
|
||||
$sut = new SamlLogoutHandler($auth);
|
||||
$sut->logout($request, $response, $token);
|
||||
}
|
||||
}
|
||||
111
tests/Saml/Provider/SamlProviderTest.php
Normal file
111
tests/Saml/Provider/SamlProviderTest.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?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\Saml\Provider;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Saml\Provider\SamlProvider;
|
||||
use App\Saml\SamlTokenFactory;
|
||||
use App\Saml\User\SamlUserFactory;
|
||||
use App\Security\DoctrineUserProvider;
|
||||
use Hslavich\OneloginSamlBundle\Security\Authentication\Token\SamlToken;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\User\ChainUserProvider;
|
||||
|
||||
/**
|
||||
* @covers \App\Saml\Provider\SamlProvider
|
||||
*/
|
||||
class SamlProviderTest extends TestCase
|
||||
{
|
||||
protected function getSamlProvider($mapping = null, $loadUser = false): SamlProvider
|
||||
{
|
||||
if (null === $mapping) {
|
||||
$mapping = [
|
||||
'mapping' => [
|
||||
['saml' => '$Email', 'kimai' => 'email'],
|
||||
['saml' => '$title', 'kimai' => 'title'],
|
||||
],
|
||||
'roles' => [
|
||||
'attribute' => '',
|
||||
'mapping' => []
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$repository = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock();
|
||||
if ($loadUser !== false) {
|
||||
$repository->expects($this->once())->method('loadUserByUsername')->willReturn($loadUser);
|
||||
}
|
||||
$userProvider = new ChainUserProvider([new DoctrineUserProvider($repository)]);
|
||||
$provider = new SamlProvider($repository, $userProvider, new SamlTokenFactory(), new SamlUserFactory($mapping));
|
||||
|
||||
return $provider;
|
||||
}
|
||||
|
||||
public function testSupportsToken()
|
||||
{
|
||||
$sut = $this->getSamlProvider();
|
||||
self::assertFalse($sut->supports(new AnonymousToken('ads', 'ads')));
|
||||
self::assertFalse($sut->supports(new UsernamePasswordToken('ads', 'ads', 'asd')));
|
||||
self::assertTrue($sut->supports(new SamlToken([])));
|
||||
}
|
||||
|
||||
public function testAuthenticateHydratesUser()
|
||||
{
|
||||
$user = new User();
|
||||
$user->setAuth(User::AUTH_SAML);
|
||||
|
||||
$token = new SamlToken([]);
|
||||
$token->setUser('foo1@example.com');
|
||||
$token->setAttributes([
|
||||
'Email' => ['foo@example.com'],
|
||||
'title' => ['Tralalala'],
|
||||
]);
|
||||
self::assertFalse($token->isAuthenticated());
|
||||
|
||||
$sut = $this->getSamlProvider(null, $user);
|
||||
$authToken = $sut->authenticate($token);
|
||||
|
||||
self::assertTrue($authToken->isAuthenticated());
|
||||
|
||||
/** @var User $tokenUser */
|
||||
$tokenUser = $authToken->getUser();
|
||||
|
||||
self::assertSame($user, $tokenUser);
|
||||
self::assertEquals('foo1@example.com', $tokenUser->getUsername());
|
||||
self::assertEquals('Tralalala', $tokenUser->getTitle());
|
||||
self::assertEquals('foo@example.com', $tokenUser->getEmail());
|
||||
}
|
||||
|
||||
public function testAuthenticatCreatesNewUser()
|
||||
{
|
||||
$token = new SamlToken([]);
|
||||
$token->setUser('foo1@example.com');
|
||||
$token->setAttributes([
|
||||
'Email' => ['foo@example.com'],
|
||||
'title' => ['Tralalala'],
|
||||
]);
|
||||
self::assertFalse($token->isAuthenticated());
|
||||
|
||||
$sut = $this->getSamlProvider(null);
|
||||
$authToken = $sut->authenticate($token);
|
||||
|
||||
self::assertTrue($authToken->isAuthenticated());
|
||||
|
||||
/** @var User $tokenUser */
|
||||
$tokenUser = $authToken->getUser();
|
||||
|
||||
self::assertEquals('foo1@example.com', $tokenUser->getUsername());
|
||||
self::assertEquals('Tralalala', $tokenUser->getTitle());
|
||||
self::assertEquals('foo@example.com', $tokenUser->getEmail());
|
||||
}
|
||||
}
|
||||
37
tests/Saml/SamlTokenFactoryTest.php
Normal file
37
tests/Saml/SamlTokenFactoryTest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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\Saml;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Saml\SamlTokenFactory;
|
||||
use Hslavich\OneloginSamlBundle\Security\Authentication\Token\SamlToken;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Saml\SamlTokenFactory
|
||||
*/
|
||||
class SamlTokenFactoryTest extends TestCase
|
||||
{
|
||||
public function testCreateToken()
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('foobar');
|
||||
|
||||
$factory = new SamlTokenFactory();
|
||||
$sut = $factory->createToken($user, ['foo' => 'bar', 'bar' => 'world'], ['ROLE_ADMIN', 'ROLE_TEST']);
|
||||
|
||||
self::assertInstanceOf(SamlToken::class, $sut);
|
||||
self::assertEquals('bar', $sut->getAttribute('foo'));
|
||||
self::assertEquals('world', $sut->getAttribute('bar'));
|
||||
self::assertEquals(['ROLE_ADMIN', 'ROLE_TEST'], $sut->getRoleNames());
|
||||
self::assertSame($user, $sut->getUser());
|
||||
self::assertEquals('foobar', $sut->getUsername());
|
||||
}
|
||||
}
|
||||
104
tests/Saml/Security/SamlAuthenticationSuccessHandlerTest.php
Normal file
104
tests/Saml/Security/SamlAuthenticationSuccessHandlerTest.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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\Saml\Security;
|
||||
|
||||
use App\Saml\Security\SamlAuthenticationSuccessHandler;
|
||||
use Hslavich\OneloginSamlBundle\Security\Authentication\Token\SamlToken;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
|
||||
/**
|
||||
* @covers \App\Saml\Security\SamlAuthenticationSuccessHandler
|
||||
*/
|
||||
class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
{
|
||||
private $handler;
|
||||
|
||||
public function testWithAlwaysUseDefaultTargetPath()
|
||||
{
|
||||
$httpUtils = new HttpUtils($this->getUrlGenerator());
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils, ['always_use_default_target_path' => true]);
|
||||
$defaultTargetPath = $httpUtils->generateUri($this->getRequest('/sso/login'), $this->getOption($handler, 'default_target_path', '/'));
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest('/login', 'http://localhost/relayed'), $this->getSamlToken());
|
||||
$this->assertTrue($response->isRedirect($defaultTargetPath));
|
||||
}
|
||||
|
||||
public function testRelayState()
|
||||
{
|
||||
$handler = new SamlAuthenticationSuccessHandler(new HttpUtils($this->getUrlGenerator()), ['always_use_default_target_path' => false]);
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest('/sso/login', 'http://localhost/relayed'), $this->getSamlToken());
|
||||
$this->assertTrue($response->isRedirect('http://localhost/relayed'));
|
||||
}
|
||||
|
||||
public function testWithoutRelayState()
|
||||
{
|
||||
$httpUtils = new HttpUtils($this->getUrlGenerator());
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils, ['always_use_default_target_path' => false]);
|
||||
$defaultTargetPath = $httpUtils->generateUri($this->getRequest('/sso/login'), $this->getOption($handler, 'default_target_path', '/'));
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest(), $this->getSamlToken());
|
||||
$this->assertTrue($response->isRedirect($defaultTargetPath));
|
||||
}
|
||||
|
||||
public function testRelayStateLoop()
|
||||
{
|
||||
$httpUtils = new HttpUtils($this->getUrlGenerator());
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils, ['always_use_default_target_path' => false]);
|
||||
$loginPath = $httpUtils->generateUri($this->getRequest('/sso/login'), $this->getOption($handler, 'login_path', '/login'));
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest($loginPath), $this->getSamlToken());
|
||||
$this->assertTrue(!$response->isRedirect($loginPath));
|
||||
}
|
||||
|
||||
private function getUrlGenerator()
|
||||
{
|
||||
$urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock();
|
||||
$urlGenerator
|
||||
->expects($this->any())
|
||||
->method('generate')
|
||||
->will($this->returnCallback(function ($name) {
|
||||
return (string) $name;
|
||||
}))
|
||||
;
|
||||
|
||||
return $urlGenerator;
|
||||
}
|
||||
|
||||
private function getRequest($path = '/', $relayState = null)
|
||||
{
|
||||
$params = [];
|
||||
if (null !== $relayState) {
|
||||
$params['RelayState'] = $relayState;
|
||||
}
|
||||
|
||||
return Request::create($path, 'get', $params);
|
||||
}
|
||||
|
||||
private function getSamlToken()
|
||||
{
|
||||
$token = new SamlToken([]);
|
||||
$token->setAttributes(['foo' => 'bar']);
|
||||
$token->setUser('admin');
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
private function getOption($handler, $name, $default = null)
|
||||
{
|
||||
$reflection = new \ReflectionObject($handler);
|
||||
$options = $reflection->getProperty('options');
|
||||
$options->setAccessible(true);
|
||||
$arr = $options->getValue($handler);
|
||||
if (!is_array($arr) || !isset($arr[$name])) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $arr[$name];
|
||||
}
|
||||
}
|
||||
45
tests/Saml/Security/SamlFactoryTest.php
Normal file
45
tests/Saml/Security/SamlFactoryTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Saml\Security;
|
||||
|
||||
use App\Saml\Security\SamlFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* @covers \App\Saml\Security\SamlFactory
|
||||
*/
|
||||
class SamlFactoryTest extends TestCase
|
||||
{
|
||||
public function testStaticValues()
|
||||
{
|
||||
$sut = new SamlFactory();
|
||||
self::assertEquals('kimai_saml', $sut->getKey());
|
||||
self::assertEquals('pre_auth', $sut->getPosition());
|
||||
}
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$sut = new SamlFactory();
|
||||
$result = $sut->create($container, 'test', ['foo' => 'bar', 'login_path' => null, 'use_forward' => null], 'fosuserbundle', 'secured_area');
|
||||
|
||||
self::assertEquals([
|
||||
'security.authentication.provider.saml.test',
|
||||
'kimai.saml_listener.test',
|
||||
'secured_area'
|
||||
], $result);
|
||||
|
||||
$definition = $container->getDefinition('security.authentication.provider.saml.test');
|
||||
self::assertInstanceOf(ChildDefinition::class, $definition);
|
||||
self::assertCount(1, $definition->getArguments());
|
||||
}
|
||||
}
|
||||
184
tests/Saml/User/SamlUserFactoryTest.php
Normal file
184
tests/Saml/User/SamlUserFactoryTest.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?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\Saml\User;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Saml\User\SamlUserFactory;
|
||||
use Hslavich\OneloginSamlBundle\Security\Authentication\Token\SamlToken;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Saml\User\SamlUserFactory
|
||||
*/
|
||||
class SamlUserFactoryTest extends TestCase
|
||||
{
|
||||
public function testCreateUserThrowsExceptionOnMissingAttribute()
|
||||
{
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('Missing user attribute: title');
|
||||
|
||||
$mapping = [
|
||||
'mapping' => [
|
||||
['saml' => '$Email', 'kimai' => 'email'],
|
||||
['saml' => '$title', 'kimai' => 'title'],
|
||||
],
|
||||
'roles' => [
|
||||
'attribute' => '',
|
||||
'mapping' => []
|
||||
]
|
||||
];
|
||||
|
||||
$attributes = [
|
||||
'Email' => ['test@example.com'],
|
||||
];
|
||||
|
||||
$token = new SamlToken();
|
||||
$token->setAttributes($attributes);
|
||||
|
||||
$sut = new SamlUserFactory($mapping);
|
||||
$user = $sut->createUser($token);
|
||||
}
|
||||
|
||||
public function testCreateUserThrowsExceptionOnMissingAttributeInMultiple()
|
||||
{
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('Missing user attribute: test');
|
||||
|
||||
$mapping = [
|
||||
'mapping' => [
|
||||
['saml' => '$Email $test', 'kimai' => 'email'],
|
||||
],
|
||||
'roles' => [
|
||||
'attribute' => '',
|
||||
'mapping' => []
|
||||
]
|
||||
];
|
||||
|
||||
$attributes = [
|
||||
'Email' => ['test@example.com'],
|
||||
];
|
||||
|
||||
$token = new SamlToken();
|
||||
$token->setAttributes($attributes);
|
||||
|
||||
$sut = new SamlUserFactory($mapping);
|
||||
$user = $sut->createUser($token);
|
||||
}
|
||||
|
||||
public function testCreateUserThrowsExceptionOnInvalidMapping()
|
||||
{
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('Invalid mapping field given: foo');
|
||||
|
||||
$mapping = [
|
||||
'mapping' => [
|
||||
['saml' => '$Email', 'kimai' => 'email'],
|
||||
['saml' => '$Email', 'kimai' => 'foo'],
|
||||
],
|
||||
'roles' => [
|
||||
'attribute' => '',
|
||||
'mapping' => []
|
||||
]
|
||||
];
|
||||
|
||||
$attributes = [
|
||||
'Email' => ['test@example.com'],
|
||||
];
|
||||
|
||||
$token = new SamlToken();
|
||||
$token->setAttributes($attributes);
|
||||
|
||||
$sut = new SamlUserFactory($mapping);
|
||||
$user = $sut->createUser($token);
|
||||
}
|
||||
|
||||
public function testCreateUser()
|
||||
{
|
||||
$mapping = [
|
||||
'mapping' => [
|
||||
['saml' => '$avatar', 'kimai' => 'avatar'],
|
||||
['saml' => '$Email', 'kimai' => 'email'],
|
||||
['saml' => 'A static super title', 'kimai' => 'title'],
|
||||
// double space between "$LastName $FOOO" on purpose!!!
|
||||
['saml' => '$FirstName $LastName $FOOO me', 'kimai' => 'alias'],
|
||||
],
|
||||
'roles' => [
|
||||
'attribute' => 'RoLeS',
|
||||
'mapping' => [
|
||||
['saml' => 'fooobar', 'kimai' => 'ROLE_ADMIN'],
|
||||
['saml' => 'ROLE_1', 'kimai' => 'ROLE_TEAMLEAD'],
|
||||
['saml' => 'ROLE_2', 'kimai' => 'ROLE_2'],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$attributes = [
|
||||
'RoLeS' => ['ROLE_1', 'ROLE_2', 'ROLE_3'],
|
||||
'Email' => ['test@example.com'],
|
||||
'FOOO' => ['test', 'test2'],
|
||||
'FirstName' => ['Kevin'],
|
||||
'LastName' => ['Papst'],
|
||||
'avatar' => ['http://www.example.com/test.jpg'],
|
||||
];
|
||||
|
||||
$token = new SamlToken();
|
||||
$token->setUser('foo@example.com');
|
||||
$token->setAttributes($attributes);
|
||||
|
||||
$sut = new SamlUserFactory($mapping);
|
||||
$user = $sut->createUser($token);
|
||||
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertTrue($user->isEnabled());
|
||||
self::assertEquals('', $user->getPassword());
|
||||
self::assertEquals('test@example.com', $user->getEmail());
|
||||
self::assertEquals('foo@example.com', $user->getUsername());
|
||||
self::assertEquals('A static super title', $user->getTitle());
|
||||
self::assertEquals('Kevin Papst test me', $user->getAlias());
|
||||
self::assertEquals(['ROLE_TEAMLEAD', 'ROLE_2', 'ROLE_USER'], $user->getRoles());
|
||||
}
|
||||
|
||||
public function testCreateUserDoesOverwriteUsername()
|
||||
{
|
||||
$mapping = [
|
||||
'mapping' => [
|
||||
['saml' => '$avatar', 'kimai' => 'avatar'],
|
||||
['saml' => '$Email', 'kimai' => 'email'],
|
||||
['saml' => 'A static super title', 'kimai' => 'title'],
|
||||
['saml' => 'Mr. T', 'kimai' => 'username'],
|
||||
],
|
||||
'roles' => [
|
||||
'attribute' => null,
|
||||
'mapping' => []
|
||||
]
|
||||
];
|
||||
|
||||
$attributes = [
|
||||
'Email' => ['test@example.com'],
|
||||
'FOOO' => ['test', 'test2'],
|
||||
'avatar' => ['http://www.example.com/test.jpg'],
|
||||
];
|
||||
|
||||
$token = new SamlToken();
|
||||
$token->setUser('foo@example.com');
|
||||
$token->setAttributes($attributes);
|
||||
|
||||
$sut = new SamlUserFactory($mapping);
|
||||
$user = $sut->createUser($token);
|
||||
|
||||
self::assertInstanceOf(User::class, $user);
|
||||
self::assertTrue($user->isEnabled());
|
||||
self::assertEquals('', $user->getPassword());
|
||||
self::assertEquals('test@example.com', $user->getEmail());
|
||||
self::assertEquals('foo@example.com', $user->getUsername());
|
||||
self::assertEquals('A static super title', $user->getTitle());
|
||||
self::assertEquals(['ROLE_USER'], $user->getRoles());
|
||||
}
|
||||
}
|
||||
126
tests/Security/DoctrineUserProviderTest.php
Normal file
126
tests/Security/DoctrineUserProviderTest.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?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\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Security\DoctrineUserProvider;
|
||||
use Hslavich\OneloginSamlBundle\Security\User\SamlUserInterface;
|
||||
use KevinPapst\AdminLTEBundle\Model\UserInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
|
||||
/**
|
||||
* @covers \App\Security\DoctrineUserProvider
|
||||
*/
|
||||
class DoctrineUserProviderTest extends TestCase
|
||||
{
|
||||
public function testLoadUserByUsernameReturnsNullThrowsException()
|
||||
{
|
||||
$this->expectException(UsernameNotFoundException::class);
|
||||
$this->expectExceptionMessage('User "test" not found.');
|
||||
|
||||
$repository = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock();
|
||||
$repository->expects($this->once())->method('loadUserByUsername')->willReturn(null);
|
||||
|
||||
$sut = new DoctrineUserProvider($repository);
|
||||
$sut->loadUserByUsername('test');
|
||||
}
|
||||
|
||||
public function testLoadUserByUsernameReturnsUser()
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('foobar');
|
||||
|
||||
$repository = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock();
|
||||
$repository->expects($this->once())->method('loadUserByUsername')->willReturn($user);
|
||||
|
||||
$sut = new DoctrineUserProvider($repository);
|
||||
$actual = $sut->loadUserByUsername('test');
|
||||
|
||||
self::assertInstanceOf(User::class, $actual);
|
||||
self::assertSame($user, $actual);
|
||||
}
|
||||
|
||||
public function testSupportsClass()
|
||||
{
|
||||
$repository = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$sut = new DoctrineUserProvider($repository);
|
||||
|
||||
self::assertTrue($sut->supportsClass(User::class));
|
||||
self::assertTrue($sut->supportsClass('App\Entity\User'));
|
||||
self::assertFalse($sut->supportsClass(UserInterface::class));
|
||||
self::assertFalse($sut->supportsClass(SamlUserInterface::class));
|
||||
self::assertFalse($sut->supportsClass(\FOS\UserBundle\Model\User::class));
|
||||
self::assertFalse($sut->supportsClass(TestUserEntity::class));
|
||||
}
|
||||
|
||||
public function testRefreshUserThrowsExceptionOnUnsupportedUserClass()
|
||||
{
|
||||
$this->expectException(UnsupportedUserException::class);
|
||||
$this->expectExceptionMessage('Expected an instance of App\Entity\User, but got "App\Tests\Security\TestUserEntity".');
|
||||
|
||||
$user = new TestUserEntity();
|
||||
$user->setUsername('foobar');
|
||||
|
||||
$repository = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$sut = new DoctrineUserProvider($repository);
|
||||
$actual = $sut->refreshUser($user);
|
||||
}
|
||||
|
||||
public function testRefreshUserThrowsExceptionOnNonFoundUser()
|
||||
{
|
||||
$this->expectException(UsernameNotFoundException::class);
|
||||
$this->expectExceptionMessage('User with ID "" could not be reloaded');
|
||||
|
||||
$user = new User();
|
||||
$user->setUsername('foobar');
|
||||
|
||||
$repository = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock();
|
||||
$repository->expects($this->once())->method('getUserById')->willReturn(null);
|
||||
|
||||
$sut = new DoctrineUserProvider($repository);
|
||||
$actual = $sut->refreshUser($user);
|
||||
}
|
||||
|
||||
public function testRefreshUserThrowsNoExceptionOnLdapUser()
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('foobar');
|
||||
$user->setAuth(User::AUTH_LDAP);
|
||||
|
||||
$repository = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock();
|
||||
$repository->expects($this->once())->method('getUserById')->willReturn($user);
|
||||
|
||||
$sut = new DoctrineUserProvider($repository);
|
||||
$actual = $sut->refreshUser($user);
|
||||
|
||||
self::assertSame($user, $actual);
|
||||
}
|
||||
|
||||
public function testRefreshUserReturnsUser()
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('foobar');
|
||||
|
||||
$repository = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock();
|
||||
$repository->expects($this->once())->method('getUserById')->willReturn($user);
|
||||
|
||||
$sut = new DoctrineUserProvider($repository);
|
||||
$actual = $sut->refreshUser($user);
|
||||
|
||||
self::assertInstanceOf(User::class, $actual);
|
||||
self::assertSame($user, $actual);
|
||||
self::assertTrue($user->isInternalUser());
|
||||
}
|
||||
}
|
||||
16
tests/Security/TestUserEntity.php
Normal file
16
tests/Security/TestUserEntity.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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 FOS\UserBundle\Model\User;
|
||||
|
||||
class TestUserEntity extends User
|
||||
{
|
||||
}
|
||||
@@ -85,4 +85,30 @@ class UserVoterTest extends AbstractVoterTest
|
||||
yield [$user, null, 'delete', $result];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTestDataForAuthType
|
||||
*/
|
||||
public function testPasswordIsDeniedForNonInternalUser(string $authType, int $result)
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('admin');
|
||||
$user->addRole('ROLE_SUPER_ADMIN');
|
||||
|
||||
$subject = new User();
|
||||
$subject->setUsername('foo');
|
||||
$subject->addRole('ROLE_USER');
|
||||
$subject->setAuth($authType);
|
||||
|
||||
$this->testVote($user, $subject, 'password', $result);
|
||||
}
|
||||
|
||||
public function getTestDataForAuthType()
|
||||
{
|
||||
return [
|
||||
[User::AUTH_LDAP, VoterInterface::ACCESS_DENIED],
|
||||
[User::AUTH_INTERNAL, VoterInterface::ACCESS_GRANTED],
|
||||
[User::AUTH_SAML, VoterInterface::ACCESS_DENIED],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user