Skip to content

Commit

Permalink
Merged branch '1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
kaff committed Sep 29, 2020
2 parents a0ba6fb + e2a99b2 commit ad01314
Show file tree
Hide file tree
Showing 18 changed files with 332 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace EzSystems\PlatformInstallerBundle\Command;

use eZ\Publish\Core\FieldType\User\UserStorage;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class ValidatePasswordHashesCommand extends Command
{
/** @var \eZ\Publish\Core\FieldType\User\UserStorage */
private $userStorage;

public function __construct(
UserStorage $userStorage
) {
$this->userStorage = $userStorage;
parent::__construct();
}

protected function configure()
{
$this->setName('ezplatform:user:validate-password-hashes');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$unsupportedHashesCounter = $this->userStorage->countUsersWithUnsupportedHashType();

if ($unsupportedHashesCounter > 0) {
$output->writeln(sprintf('<error>Found %s users with unsupported password hash types</error>', $unsupportedHashesCounter));
$output->writeln('<info>For more details check documentation:</info> <href=https://doc.ezplatform.com/en/latest/releases/ez_platform_v3.0_deprecations/#password-hashes>https://doc.ezplatform.com/en/latest/releases/ez_platform_v3.0_deprecations/#password-hashes</>');
} else {
$output->writeln('OK - <info>All users have supported password hash types</info>');
}

return Command::SUCCESS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@ services:
$repositoryConfigurationProvider: '@ezpublish.api.repository_configuration_provider'
tags:
- { name: console.command, command: ezplatform:install }

EzSystems\PlatformInstallerBundle\Command\ValidatePasswordHashesCommand:
arguments:
$userStorage: '@ezpublish.fieldType.ezuser.externalStorage'

tags:
- { name: console.command, command: ezplatform:user:validate-password-hashes }
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace eZ\Publish\API\Repository\Exceptions;

use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Throwable;

class PasswordInUnsupportedFormatException extends AuthenticationException
{
public function __construct(Throwable $previous = null)
{
parent::__construct("User's password is in a format which is not supported any more.", 0, $previous);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public function getValidUpdateFieldData()
'login' => 'changeLogin',
'email' => 'changeEmail@ez.no',
'passwordHash' => '*2',
'passwordHashType' => 1,
'passwordHashType' => User::DEFAULT_PASSWORD_HASH,
'enabled' => false,
]
);
Expand All @@ -284,7 +284,7 @@ public function assertUpdatedFieldDataLoadedCorrect(Field $field)
'hasStoredLogin' => true,
'login' => 'changeLogin',
'email' => 'changeEmail@ez.no',
'passwordHashType' => 1,
'passwordHashType' => User::DEFAULT_PASSWORD_HASH,
'enabled' => false,
];

Expand Down
10 changes: 4 additions & 6 deletions eZ/Publish/API/Repository/Tests/UserServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use DateTime;
use DateTimeImmutable;
use eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException;
use eZ\Publish\API\Repository\Exceptions\InvalidArgumentException;
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
use eZ\Publish\API\Repository\Values\Content\Language;
Expand Down Expand Up @@ -2707,7 +2706,7 @@ private function createMultiLanguageUser($userGroupId = 13)
*
* @see \eZ\Publish\API\Repository\UserService::createUser()
*/
public function testCreateUserInvalidPasswordHashTypeThrowsException()
public function testCreateUserWithDefaultPasswordHashTypeWhenHashTypeIsUnsupported(): void
{
$repository = $this->getRepository();
$eventUserService = $repository->getUserService();
Expand Down Expand Up @@ -2743,12 +2742,11 @@ public function testCreateUserInvalidPasswordHashTypeThrowsException()
// Set not supported hash type.
$userValue->passwordHashType = 42424242;

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage("Argument 'hashType' is invalid: Password hash type '42424242' is not recognized");

// Create a new user instance.
// 13 is ID of the "Editors" user group in an eZ Publish demo installation.
$eventUserService->createUser($createStruct, [$eventUserService->loadUserGroup(13)]);
$createdUser = $eventUserService->createUser($createStruct, [$eventUserService->loadUserGroup(13)]);

self::assertEquals(User::DEFAULT_PASSWORD_HASH, $createdUser->hashAlgorithm);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions eZ/Publish/API/Repository/Values/User/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
*/
abstract class User extends Content implements UserReference
{
public const SUPPORTED_PASSWORD_HASHES = [
self::PASSWORD_HASH_BCRYPT,
self::PASSWORD_HASH_PHP_DEFAULT,
];

/** @var int Passwords in bcrypt */
const PASSWORD_HASH_BCRYPT = 6;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Integration\User\UserStorage;

use eZ\Publish\Core\FieldType\Tests\Integration\User\UserStorage\UserStorageGatewayTest;
use eZ\Publish\Core\FieldType\User\UserStorage\Gateway as UserStorageGateway;
use eZ\Publish\Core\FieldType\User\UserStorage\Gateway\DoctrineStorage;

final class UserDoctrineStorageGatewayTest extends UserStorageGatewayTest
{
protected function getGateway(): UserStorageGateway
{
return new DoctrineStorage($this->getDatabaseConnection());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,19 @@
namespace eZ\Publish\Core\FieldType\Tests\Integration\User\UserStorage;

use eZ\Publish\Core\FieldType\Tests\Integration\BaseCoreFieldTypeIntegrationTest;
use eZ\Publish\Core\FieldType\User\UserStorage\Gateway;
use eZ\Publish\Core\Repository\Values\User\User;
use eZ\Publish\SPI\Tests\Persistence\FixtureImporter;
use eZ\Publish\SPI\Tests\Persistence\YamlFixture;

/**
* User Field Type external storage gateway tests.
*/
abstract class UserStorageGatewayTest extends BaseCoreFieldTypeIntegrationTest
{
/**
* @return \eZ\Publish\Core\FieldType\User\UserStorage\Gateway
*/
abstract protected function getGateway();
abstract protected function getGateway(): Gateway;

/**
* @return array
*/
public function providerForGetFieldData()
public function providerForGetFieldData(): array
{
$expectedUserData = [
10 => [
Expand Down Expand Up @@ -59,14 +56,39 @@ public function providerForGetFieldData()

/**
* @dataProvider providerForGetFieldData
*
* @param int|null $fieldId
* @param int $userId
* @param array $expectedUserData
*/
public function testGetFieldData($fieldId, $userId, array $expectedUserData)
public function testGetFieldData(?int $fieldId, ?int $userId, array $expectedUserData): void
{
$data = $this->getGateway()->getFieldData($fieldId, $userId);
$this->assertEquals($expectedUserData, $data);
self::assertEquals($expectedUserData, $data);
}

/**
* @dataProvider getDataForTestCountUsersWithUnsupportedHashType
*/
public function testCountUsersWithUnsupportedHashType(
int $expectedCount,
?string $fixtureFilePath
): void {
if (null !== $fixtureFilePath) {
$importer = new FixtureImporter($this->getDatabaseConnection());
$importer->import(new YamlFixture($fixtureFilePath));
}

$actualCount = $this->getGateway()->countUsersWithUnsupportedHashType();
self::assertEquals($expectedCount, $actualCount);
}

public function getDataForTestCountUsersWithUnsupportedHashType(): iterable
{
yield 'no unsupported hashes' => [
0,
null,
];

yield 'with unsupported hash' => [
1,
__DIR__ . '/_fixtures/unsupported_hash.yaml',
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ezuser:
- { contentobject_id: 10, email: nospam@ez.no, login: anonymous, password_hash: $2y$10$35gOSQs6JK4u4whyERaeUuVeQBi2TUBIZIfP7HEj7sfz.MxvTuOeC, password_hash_type: 7 }
- { contentobject_id: 16, email: test@link.invalid, login: test, password_hash: $2y$10$35gOSQs6JK4u4whyERaeUuVeQBi2TUBIZIfP7HEj7sfz.MxvTuOeC, password_hash_type: 5 }
- { contentobject_id: 14, email: spam@ez.no, login: admin, password_hash: $2y$10$FDn9NPwzhq85cLLxfD5Wu.L3SL3Z/LNCvhkltJUV0wcJj7ciJg2oy, password_hash_type: 7 }
73 changes: 73 additions & 0 deletions eZ/Publish/Core/FieldType/Tests/UserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
use eZ\Publish\Core\FieldType\User\Type;
use eZ\Publish\Core\FieldType\User\Type as UserType;
use eZ\Publish\Core\FieldType\User\Value as UserValue;
use eZ\Publish\Core\Repository\Values\User\User as RepositoryUser;
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
use eZ\Publish\Core\FieldType\ValidationError;
use eZ\Publish\Core\Persistence\Cache\UserHandler;
use eZ\Publish\Core\Repository\User\PasswordHashServiceInterface;
use eZ\Publish\Core\Repository\User\PasswordValidatorInterface;
use eZ\Publish\Core\Repository\Values\ContentType\FieldDefinition as CoreFieldDefinition;
use eZ\Publish\SPI\Persistence\Content\FieldValue;
use eZ\Publish\SPI\Persistence\User;
use PHPUnit\Framework\MockObject\Builder\InvocationMocker;

Expand Down Expand Up @@ -579,6 +581,77 @@ public function testEmailAlreadyTaken(): void
], $validationErrors);
}

/**
* @covers \eZ\Publish\Core\FieldType\User\Type::toPersistenceValue
*
* @dataProvider providerForTestCreatePersistenceValue
*/
public function testCreatePersistenceValue(array $userValueDate, array $expectedFieldValueExternalData): void
{
$passwordHashServiceMock = $this->createMock(PasswordHashServiceInterface::class);
$passwordHashServiceMock->method('getDefaultHashType')->willReturn(RepositoryUser::DEFAULT_PASSWORD_HASH);
$userType = new UserType(
$this->createMock(UserHandler::class),
$passwordHashServiceMock,
$this->createMock(PasswordValidatorInterface::class)
);

$value = new UserValue($userValueDate);
$fieldValue = $userType->toPersistenceValue($value);

$expected = new FieldValue(
[
'data' => null,
'externalData' => $expectedFieldValueExternalData,
'sortKey' => null,
]);
self::assertEquals($expected, $fieldValue);
}

public function providerForTestCreatePersistenceValue(): iterable
{
$passwordUpdatedAt = new DateTimeImmutable();
$userData = [
'hasStoredLogin' => false,
'contentId' => 46,
'login' => 'validate_user',
'email' => 'test@test.ez',
'passwordHash' => '1234567890abcdef',
'enabled' => true,
'maxLogin' => 1000,
'plainPassword' => '',
'passwordUpdatedAt' => $passwordUpdatedAt,
];

yield 'when password hash type is given' => [
$userValueData = [
'passwordHashType' => RepositoryUser::PASSWORD_HASH_PHP_DEFAULT,
] + $userData,
$expectedFieldValueExternalData = [
'passwordHashType' => RepositoryUser::PASSWORD_HASH_PHP_DEFAULT,
'passwordUpdatedAt' => $passwordUpdatedAt->getTimestamp(),
] + $userData,
];
yield 'when password hash type is null' => [
$userValueData = [
'passwordHashType' => null,
] + $userData,
$expectedFieldValueExternalData = [
'passwordHashType' => RepositoryUser::DEFAULT_PASSWORD_HASH,
'passwordUpdatedAt' => $passwordUpdatedAt->getTimestamp(),
] + $userData,
];
yield 'when password hash type is unsupported' => [
$userValueData = [
'passwordHashType' => '__UNSUPPORTED_HASH_TYPE__',
] + $userData,
$expectedFieldValueExternalData = [
'passwordHashType' => RepositoryUser::DEFAULT_PASSWORD_HASH,
'passwordUpdatedAt' => $passwordUpdatedAt->getTimestamp(),
] + $userData,
];
}

public function testEmailFreeToUse(): void
{
$validateUserValue = new UserValue([
Expand Down
16 changes: 15 additions & 1 deletion eZ/Publish/Core/FieldType/User/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use DateTimeInterface;
use eZ\Publish\Core\FieldType\FieldType;
use eZ\Publish\Core\FieldType\ValidationError;
use eZ\Publish\Core\Repository\Values\User\User;
use eZ\Publish\SPI\Persistence\User\Handler as SPIUserHandler;
use eZ\Publish\Core\Repository\User\PasswordHashServiceInterface;
use eZ\Publish\Core\Repository\User\PasswordValidatorInterface;
Expand Down Expand Up @@ -251,7 +252,7 @@ public function toHash(SPIValue $value)
*/
public function toPersistenceValue(SPIValue $value)
{
$value->passwordHashType = $value->passwordHashType ?? $this->passwordHashService->getDefaultHashType();
$value->passwordHashType = $this->getPasswordHashTypeForPersistenceValue($value);
if ($value->plainPassword) {
$value->passwordHash = $this->passwordHashService->createPasswordHash(
$value->plainPassword,
Expand All @@ -269,6 +270,19 @@ public function toPersistenceValue(SPIValue $value)
);
}

private function getPasswordHashTypeForPersistenceValue(SPIValue $value): int
{
if (null === $value->passwordHashType) {
return $this->passwordHashService->getDefaultHashType();
}

if (!in_array($value->passwordHashType, User::SUPPORTED_PASSWORD_HASHES, true)) {
return $this->passwordHashService->getDefaultHashType();
}

return $value->passwordHashType;
}

/**
* Converts a persistence $fieldValue to a Value.
*
Expand Down
5 changes: 5 additions & 0 deletions eZ/Publish/Core/FieldType/User/UserStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,9 @@ public function hasFieldData()
public function getIndexData(VersionInfo $versionInfo, Field $field, array $context)
{
}

public function countUsersWithUnsupportedHashType(): int
{
return $this->gateway->countUsersWithUnsupportedHashType();
}
}
2 changes: 2 additions & 0 deletions eZ/Publish/Core/FieldType/User/UserStorage/Gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ abstract public function storeFieldData(VersionInfo $versionInfo, Field $field):
* @throws \Doctrine\DBAL\DBALException
*/
abstract public function deleteFieldData(VersionInfo $versionInfo, array $fieldIds): bool;

abstract public function countUsersWithUnsupportedHashType(): int;
}
Loading

0 comments on commit ad01314

Please sign in to comment.