Skip to content

Commit

Permalink
EZP-28917: As a Developer I want API to manipulate User Tokens (ezsys…
Browse files Browse the repository at this point in the history
…tems#2270)

* EZP-28917: As a Developer I want API to manipulate User Tokens

* EZP-28917: Implemented integration tests for User Token APIs

* EZP-28917: Implemented checking for user / password policy

It allows Users w/o content / edit policy to update their password

BC: If User has content / edit policy, but no user / password policy, updating password works as well
  • Loading branch information
mikadamczyk authored and alongosz committed Mar 12, 2018
1 parent 59965ca commit 4339a84
Show file tree
Hide file tree
Showing 15 changed files with 788 additions and 1 deletion.
100 changes: 100 additions & 0 deletions eZ/Publish/API/Repository/Tests/UserServiceTest.php
Expand Up @@ -8,11 +8,13 @@
*/
namespace eZ\Publish\API\Repository\Tests;

use DateTime;
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\VersionInfo as APIVersionInfo;
use eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct;
use eZ\Publish\API\Repository\Values\User\UserTokenUpdateStruct;
use eZ\Publish\API\Repository\Values\User\UserUpdateStruct;
use eZ\Publish\API\Repository\Values\User\User;
use eZ\Publish\Core\Repository\Values\Content\Content;
Expand Down Expand Up @@ -2531,4 +2533,102 @@ public function testCreateUserInvalidPasswordHashTypeThrowsException()
// Reset to default settings, so we don't break other tests
$settingsProperty->setValue($userService, $defaultUserServiceSettings);
}

/**
* Test loading User by Token.
*
* @covers \eZ\Publish\API\Repository\UserService::loadUserByToken
*/
public function testLoadUserByToken()
{
$repository = $this->getRepository();
$userService = $repository->getUserService();

$user = $this->createUserVersion1();

$userTokenUpdateStruct = new UserTokenUpdateStruct();
$userTokenUpdateStruct->hashKey = md5('hash');
$userTokenUpdateStruct->time = new DateTime();

$userService->updateUserToken($user, $userTokenUpdateStruct);

$loadedUser = $userService->loadUserByToken($userTokenUpdateStruct->hashKey);
self::assertEquals($user, $loadedUser);

return $userTokenUpdateStruct->hashKey;
}

/**
* Test trying to load User by invalid Token.
*
* @covers \eZ\Publish\API\Repository\UserService::loadUserByToken
*/
public function testLoadUserByTokenThrowsNotFoundException()
{
$this->expectException(NotFoundException::class);

$repository = $this->getRepository();
$userService = $repository->getUserService();

$user = $this->createUserVersion1();

$userTokenUpdateStruct = new UserTokenUpdateStruct();
$userTokenUpdateStruct->hashKey = md5('hash');
$userTokenUpdateStruct->time = new DateTime();

$userService->updateUserToken($user, $userTokenUpdateStruct);

$userService->loadUserByToken('not_existing_token');
}

/**
* Test updating User Token.
*
* @covers \eZ\Publish\API\Repository\UserService::updateUserToken()
*
* @depends testLoadUserByToken
*
* @param string $originalUserToken
*/
public function testUpdateUserToken($originalUserToken)
{
$repository = $this->getRepository(false);
$userService = $repository->getUserService();

$user = $userService->loadUserByToken($originalUserToken);

$userTokenUpdateStruct = new UserTokenUpdateStruct();
$userTokenUpdateStruct->hashKey = md5('my_updated_hash');
$userTokenUpdateStruct->time = new DateTime();

$userService->updateUserToken($user, $userTokenUpdateStruct);

$loadedUser = $userService->loadUserByToken($userTokenUpdateStruct->hashKey);
self::assertEquals($user, $loadedUser);
}

/**
* Test invalidating (expiring) User Token.
*
* @covers \eZ\Publish\API\Repository\UserService::expireUserToken()
*
* @depends testLoadUserByToken
*
* @param string $userToken
*/
public function testExpireUserToken($userToken)
{
$this->expectException(NotFoundException::class);

$repository = $this->getRepository(false);
$userService = $repository->getUserService();

// sanity check
$userService->loadUserByToken($userToken);

$userService->expireUserToken($userToken);

// should throw NotFoundException now
$userService->loadUserByToken($userToken);
}
}
28 changes: 28 additions & 0 deletions eZ/Publish/API/Repository/UserService.php
Expand Up @@ -8,6 +8,7 @@
*/
namespace eZ\Publish\API\Repository;

use eZ\Publish\API\Repository\Values\User\UserTokenUpdateStruct;
use eZ\Publish\API\Repository\Values\User\UserCreateStruct;
use eZ\Publish\API\Repository\Values\User\UserUpdateStruct;
use eZ\Publish\API\Repository\Values\User\User;
Expand Down Expand Up @@ -195,6 +196,16 @@ public function loadUserByLogin($login, array $prioritizedLanguages = []);
*/
public function loadUsersByEmail($email, array $prioritizedLanguages = []);

/**
* Loads a user with user hash key.
*
* @param string $hash
* @param array $prioritizedLanguages
*
* @return \eZ\Publish\API\Repository\Values\User\User
*/
public function loadUserByToken($hash, array $prioritizedLanguages = []);

/**
* This method deletes a user.
*
Expand Down Expand Up @@ -224,6 +235,23 @@ public function deleteUser(User $user);
*/
public function updateUser(User $user, UserUpdateStruct $userUpdateStruct);

/**
* Update the user token information specified by the user token struct.
*
* @param \eZ\Publish\API\Repository\Values\User\User $user
* @param \eZ\Publish\API\Repository\Values\User\UserTokenUpdateStruct $userTokenUpdateStruct
*
* @return \eZ\Publish\API\Repository\Values\User\User
*/
public function updateUserToken(User $user, UserTokenUpdateStruct $userTokenUpdateStruct);

/**
* Expires user token with user hash.
*
* @param string $hash
*/
public function expireUserToken($hash);

/**
* Assigns a new user group to the user.
*
Expand Down
29 changes: 29 additions & 0 deletions eZ/Publish/API/Repository/Values/User/UserTokenUpdateStruct.php
@@ -0,0 +1,29 @@
<?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 eZ\Publish\API\Repository\Values\User;

use eZ\Publish\API\Repository\Values\ValueObject;

/**
* This class is used to update a user token in the repository.
*/
class UserTokenUpdateStruct extends ValueObject
{
/**
* Hash key date for user account.
*
* @var string
*/
public $hashKey;

/**
* Time to which the token is valid.
*
* @var \DateTime|null
*/
public $time;
}
4 changes: 4 additions & 0 deletions eZ/Publish/Core/Persistence/Cache/Tests/UserHandlerTest.php
Expand Up @@ -36,11 +36,14 @@ public function providerForUnCachedMethods(): array
{
$user = new User(['id' => 14]);
$policy = new Policy(['id' => 13, 'roleId' => 9]);
$userToken = new User\UserTokenUpdateStruct(['userId' => 14]);

// string $method, array $arguments, array? $tags, string? $key
return [
['create', [$user], ['content-fields-14']],
['update', [$user], ['content-fields-14', 'user-14']],
['updateUserToken', [$userToken], ['content-fields-14', 'user-14']],
['expireUserToken', [14]],
['delete', [14], ['content-fields-14', 'user-14']],
['createRole', [new RoleCreateStruct()]],
['createRoleDraft', [new RoleCreateStruct()]],
Expand Down Expand Up @@ -74,6 +77,7 @@ public function providerForCachedLoadMethods(): array
['load', [14], 'ez-user-14', $user],
['loadByLogin', ['admin'], 'ez-user-admin-by-login', $user],
['loadByEmail', ['nospam@ez.no'], 'ez-user-nospam§ez.no-by-email', [$user]],
['loadUserByToken', ['hash'], 'ez-user-hash-by-account-key', $user],
['loadRole', [9], 'ez-role-9', $role],
['loadRoleByIdentifier', ['member'], 'ez-role-member-by-identifier', $role],
['loadRoleAssignment', [11], 'ez-role-assignment-11', $roleAssignment],
Expand Down
46 changes: 46 additions & 0 deletions eZ/Publish/Core/Persistence/Cache/UserHandler.php
Expand Up @@ -8,6 +8,7 @@
*/
namespace eZ\Publish\Core\Persistence\Cache;

use eZ\Publish\SPI\Persistence\User\UserTokenUpdateStruct;
use eZ\Publish\SPI\Persistence\User\Handler as UserHandlerInterface;
use eZ\Publish\SPI\Persistence\User;
use eZ\Publish\SPI\Persistence\User\Role;
Expand Down Expand Up @@ -100,6 +101,26 @@ public function loadByEmail($email)
return $users;
}

/**
* {@inheritdoc}
*/
public function loadUserByToken($hash)
{
$cacheItem = $this->cache->getItem('ez-user-' . $hash . '-by-account-key');
if ($cacheItem->isHit()) {
return $cacheItem->get();
}

$this->logger->logCall(__METHOD__, array('hash' => $hash));
$user = $this->persistenceHandler->userHandler()->loadUserByToken($hash);

$cacheItem->set($user);
$cacheItem->tag(['content-' . $user->id, 'user-' . $user->id]);
$this->cache->save($cacheItem);

return $user;
}

/**
* {@inheritdoc}
*/
Expand All @@ -114,6 +135,31 @@ public function update(User $user)
return $return;
}

/**
* {@inheritdoc}
*/
public function updateUserToken(UserTokenUpdateStruct $userTokenUpdateStruct)
{
$this->logger->logCall(__METHOD__, array('struct' => $userTokenUpdateStruct));
$return = $this->persistenceHandler->userHandler()->updateUserToken($userTokenUpdateStruct);

// Clear corresponding content cache as update of the User changes it's external data
$this->cache->invalidateTags(['content-fields-' . $userTokenUpdateStruct->userId, 'user-' . $userTokenUpdateStruct->userId]);

return $return;
}

/**
* {@inheritdoc}
*/
public function expireUserToken($hash)
{
$this->logger->logCall(__METHOD__, array('hash' => $hash));
$return = $this->persistenceHandler->userHandler()->expireUserToken($hash);

return $return;
}

/**
* {@inheritdoc}
*/
Expand Down
77 changes: 77 additions & 0 deletions eZ/Publish/Core/Persistence/Legacy/Tests/User/UserHandlerTest.php
Expand Up @@ -46,6 +46,16 @@ protected function getValidUser()
return $user;
}

protected function getValidUserToken($time = null)
{
$userToken = new Persistence\User\UserTokenUpdateStruct();
$userToken->userId = 42;
$userToken->hashKey = md5('hash');
$userToken->time = $time ?? (new \DateTime())->add(new \DateInterval('P1D'))->getTimestamp();

return $userToken;
}

public function testCreateUser()
{
$handler = $this->getUserHandler();
Expand Down Expand Up @@ -144,6 +154,73 @@ public function testLoadUserByEmail()
);
}

/**
* @expectedException \eZ\Publish\API\Repository\Exceptions\NotFoundException
*/
public function testLoadUserByTokenNotFound()
{
$handler = $this->getUserHandler();
$handler->create($user = $this->getValidUser());
$handler->updateUserToken($this->getValidUserToken());

$handler->loadUserByToken('asd');
}

public function testLoadUserByToken()
{
$handler = $this->getUserHandler();
$handler->create($user = $this->getValidUser());
$handler->updateUserToken($userToken = $this->getValidUserToken());

$loadedUser = $handler->loadUserByToken($userToken->hashKey);
$this->assertEquals(
$user,
$loadedUser
);
}

public function testUpdateUserToken()
{
$handler = $this->getUserHandler();

$handler->updateUserToken($userToken = $this->getValidUserToken(1234567890));

$this->assertQueryResult(
[['0800fc577294c34e0b28ad2839435945', 1, 1234567890, 42]],
$this->handler->createSelectQuery()->select('*')->from('ezuser_accountkey'),
'Expected user data to be updated.'
);

$handler->updateUserToken($userToken = $this->getValidUserToken(2234567890));

$this->assertQueryResult(
[['0800fc577294c34e0b28ad2839435945', 1, 2234567890, 42]],
$this->handler->createSelectQuery()->select('*')->from('ezuser_accountkey'),
'Expected user token data to be updated.'
);
}

public function testExpireUserToken()
{
$handler = $this->getUserHandler();

$handler->updateUserToken($userToken = $this->getValidUserToken(1234567890));

$this->assertQueryResult(
[['0800fc577294c34e0b28ad2839435945', 1, 1234567890, 42]],
$this->handler->createSelectQuery()->select('*')->from('ezuser_accountkey'),
'Expected user data to be updated.'
);

$handler->expireUserToken($userToken->hashKey);

$this->assertQueryResult(
[['0800fc577294c34e0b28ad2839435945', 1, 0, 42]],
$this->handler->createSelectQuery()->select('*')->from('ezuser_accountkey'),
'Expected user token to be expired.'
);
}

public function testCreateAndDeleteUser()
{
$handler = $this->getUserHandler();
Expand Down

0 comments on commit 4339a84

Please sign in to comment.