Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EZP-31079: Added way to use login or email (or both) during authentication #2944

Merged
merged 1 commit into from
Mar 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion doc/bc/changes-8.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,17 @@ Changes affecting version compatibility with former or future versions.

* Deprecated `viewLocation` and `embedLocation` actions of `ViewController` have been dropped, along with
related route `_ezpublishLocation`. As stated in controller, use `viewAction` in place of `viewLocation` and
`embedAction` in place od `embedLocation`.
`embedAction` in place of `embedLocation`.

* Obsolete DeferredLegacy Content Type Update handler
(`eZ\Publish\Core\Persistence\Legacy\Content\Type\Update\Handler\DeferredLegacy`) and its optional
Symfony Container Service (`ezpublish.persistence.legacy.content_type.update_handler.deferred`)
have been removed. Subscribe to eZ Platform Symfony Events to handle deferring of updating
of Content items after their Content Type update instead.

* Deprecated `UserService::loadUserByCredentials` method has been dropped. From now on, we rely on
Security package of Symfony framework to provide authenticated user, as it may happen there are
different ways of providing users configured in the application (ref.: https://symfony.com/doc/current/security/user_provider.html).

## Deprecated features

Expand Down
12 changes: 10 additions & 2 deletions eZ/Bundle/EzPublishCoreBundle/Resources/config/security.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
services:
ezpublish.security.user_provider:
class: eZ\Publish\Core\MVC\Symfony\Security\User\Provider
ezpublish.security.user_provider.username:
class: eZ\Publish\Core\MVC\Symfony\Security\User\UsernameProvider
arguments:
- '@eZ\Publish\API\Repository\UserService'
- '@eZ\Publish\API\Repository\PermissionResolver'

ezpublish.security.user_provider.email:
class: eZ\Publish\Core\MVC\Symfony\Security\User\EmailProvider
arguments:
- '@eZ\Publish\API\Repository\UserService'
- '@eZ\Publish\API\Repository\PermissionResolver'
Expand Down Expand Up @@ -40,3 +46,5 @@ services:
- "%fragment.path%"
tags:
- { name: kernel.event_subscriber }

ezpublish.security.user_provider: '@ezpublish.security.user_provider.username'
140 changes: 13 additions & 127 deletions eZ/Publish/API/Repository/Tests/UserServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1252,12 +1252,10 @@ public function testLoadUserThrowsNotFoundException()
}

/**
* Test for the loadUserByCredentials() method.
*
* @see \eZ\Publish\API\Repository\UserService::loadUserByCredentials()
* @depends eZ\Publish\API\Repository\Tests\UserServiceTest::testCreateUser
* @see \eZ\Publish\API\Repository\UserService::checkUserCredentials()
* @depends \eZ\Publish\API\Repository\Tests\UserServiceTest::testCreateUser
*/
public function testLoadUserByCredentials()
public function testCheckUserCredentialsValid(): void
{
$repository = $this->getRepository();

Expand All @@ -1266,103 +1264,31 @@ public function testLoadUserByCredentials()
/* BEGIN: Use Case */
$user = $this->createUserVersion1();

// Load the newly created user
$userReloaded = $userService->loadUserByCredentials('user', 'secret', Language::ALL);
// Load the newly created user credentials
$credentialsValid = $userService->loadUserByCredentials($user, 'secret');
/* END: Use Case */

$this->assertEquals($user, $userReloaded);
}

/**
* Test for the loadUserByCredentials() method.
*
* @see \eZ\Publish\API\Repository\UserService::loadUserByCredentials()
* @depends eZ\Publish\API\Repository\Tests\UserServiceTest::testLoadUserByCredentials
*/
public function testLoadUserByCredentialsThrowsNotFoundExceptionForUnknownPassword()
{
$this->expectException(\eZ\Publish\API\Repository\Exceptions\NotFoundException::class);

$repository = $this->getRepository();

$userService = $repository->getUserService();

/* BEGIN: Use Case */
$this->createUserVersion1();

// This call will fail with a "NotFoundException", because the given
// login/password combination does not exist.
$userService->loadUserByCredentials('user', 'SeCrEt');
/* END: Use Case */
$this->assertTrue($credentialsValid);
}

/**
* Test for the loadUserByCredentials() method.
*
* @see \eZ\Publish\API\Repository\UserService::loadUserByCredentials()
* @depends eZ\Publish\API\Repository\Tests\UserServiceTest::testLoadUserByCredentials
* @see \eZ\Publish\API\Repository\UserService::checkUserCredentials()
* @depends \eZ\Publish\API\Repository\Tests\UserServiceTest::testCreateUser
*/
public function testLoadUserByCredentialsThrowsNotFoundExceptionForUnknownPasswordEmtpy()
public function testCheckUserCredentialsInvalid(): void
{
$this->expectException(\eZ\Publish\API\Repository\Exceptions\NotFoundException::class);

$repository = $this->getRepository();

$userService = $repository->getUserService();

/* BEGIN: Use Case */
$this->createUserVersion1();
$user = $this->createUserVersion1();

// This call will fail with a "NotFoundException", because the given
// login/password combination does not exist.
$userService->loadUserByCredentials('user', '');
// Load the newly created user credentials
$credentialsValid = $userService->loadUserByCredentials($user, '1234');
/* END: Use Case */
}

/**
* Test for the loadUserByCredentials() method.
*
* @see \eZ\Publish\API\Repository\UserService::loadUserByCredentials()
* @depends eZ\Publish\API\Repository\Tests\UserServiceTest::testLoadUserByCredentials
*/
public function testLoadUserByCredentialsThrowsNotFoundExceptionForUnknownLogin()
{
$this->expectException(\eZ\Publish\API\Repository\Exceptions\NotFoundException::class);

$repository = $this->getRepository();

$userService = $repository->getUserService();

/* BEGIN: Use Case */
$this->createUserVersion1();

// This call will fail with a "NotFoundException", because the given
// login/password combination does not exist.
$userService->loadUserByCredentials('üser', 'secret');
/* END: Use Case */
}

/**
* Test for the loadUserByCredentials() method.
*
* @see \eZ\Publish\API\Repository\UserService::loadUserByCredentials()
* @depends eZ\Publish\API\Repository\Tests\UserServiceTest::testLoadUserByCredentials
*/
public function testLoadUserByCredentialsThrowsInvalidArgumentValueForEmptyLogin()
{
$this->expectException(\eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue::class);

$repository = $this->getRepository();

$userService = $repository->getUserService();

/* BEGIN: Use Case */
$this->createUserVersion1();

// This call will fail with a "InvalidArgumentValue", because the given
// login is empty.
$userService->loadUserByCredentials('', 'secret');
/* END: Use Case */
$this->assertFalse($credentialsValid);
}

/**
Expand Down Expand Up @@ -2441,46 +2367,6 @@ public function testLoadUserByLoginWithPrioritizedLanguagesList(
}
}

/**
* Test that multi-language logic for the loadUserByCredentials method respects
* prioritized language list.
*
* @covers \eZ\Publish\API\Repository\UserService::loadUserByCredentials
* @dataProvider getPrioritizedLanguageList
* @param string[] $prioritizedLanguages
* @param string|null $expectedLanguageCode language code of expected translation
*/
public function testLoadUserByCredentialsWithPrioritizedLanguagesList(
array $prioritizedLanguages,
$expectedLanguageCode
) {
$repository = $this->getRepository();
$userService = $repository->getUserService();
$user = $this->createMultiLanguageUser();

// load, with prioritized languages, the newly created user
$loadedUser = $userService->loadUserByCredentials(
$user->login,
'secret',
$prioritizedLanguages
);
if ($expectedLanguageCode === null) {
$expectedLanguageCode = $loadedUser->contentInfo->mainLanguageCode;
}

self::assertEquals(
$loadedUser->getName($expectedLanguageCode),
$loadedUser->getName()
);

foreach (['first_name', 'last_name', 'signature'] as $fieldIdentifier) {
self::assertEquals(
$loadedUser->getFieldValue($fieldIdentifier, $expectedLanguageCode),
$loadedUser->getFieldValue($fieldIdentifier)
);
}
}

/**
* Test that multi-language logic for the loadUsersByEmail method respects
* prioritized language list.
Expand Down
27 changes: 12 additions & 15 deletions eZ/Publish/API/Repository/UserService.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,24 +142,22 @@ public function createUser(UserCreateStruct $userCreateStruct, array $parentGrou
public function loadUser($userId, array $prioritizedLanguages = []);

/**
* Loads a user for the given login and password.
* Loads a user for the given login.
*
* Since 6.1 login is case-insensitive across all storage engines and database backends, however if login
* is part of the password hash this method will essentially be case sensitive.
* Since 6.1 login is case-insensitive across all storage engines and database backends, like was the case
* with mysql before in eZ Publish 3.x/4.x/5.x.
*
* @deprecated since eZ Platform 2.5, will be dropped in the next major version as authentication
* may depend on various user providers. Use UserService::checkUserCredentials() instead.
*
* @param string $login
* @param string $password the plain password
* @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
*
* @return \eZ\Publish\API\Repository\Values\User\User
*
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if credentials are invalid
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
*/
public function loadUserByCredentials($login, $password, array $prioritizedLanguages = []);
public function loadUserByLogin($login, array $prioritizedLanguages = []);

/**
* Checks if credentials are valid for provided User.
Expand All @@ -172,22 +170,19 @@ public function loadUserByCredentials($login, $password, array $prioritizedLangu
public function checkUserCredentials(User $user, string $credentials): bool;

/**
* Loads a user for the given login.
*
* Since 6.1 login is case-insensitive across all storage engines and database backends, like was the case
* with mysql before in eZ Publish 3.x/4.x/5.x.
* Loads a user for the given email.
*
* @param string $login
* @param string $email
* @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
*
* @return \eZ\Publish\API\Repository\Values\User\User
*
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
*/
public function loadUserByLogin($login, array $prioritizedLanguages = []);
public function loadUserByEmail(string $email, array $prioritizedLanguages = []): User;

/**
* Loads a user for the given email.
* Loads a users for the given email.
*
* Note: This method loads user by $email where $email might be case-insensitive on certain storage engines!
*
Expand All @@ -198,8 +193,10 @@ public function loadUserByLogin($login, array $prioritizedLanguages = []);
* @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
*
* @return \eZ\Publish\API\Repository\Values\User\User[]
*
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
*/
public function loadUsersByEmail($email, array $prioritizedLanguages = []);
public function loadUsersByEmail(string $email, array $prioritizedLanguages = []): array;

/**
* Loads a user with user hash key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public function testCheckAuthentication()
$password = 'foo';
$token = new UsernamePasswordToken($userName, $password, 'bar');

$apiUser = $this->getMockForAbstractClass(APIUser::class);
$apiUser = $this->createMock(APIUser::class);
$user = $this->createMock(User::class);
$user->method('getAPIUser')
->willReturn($apiUser);
Expand Down
Loading