Refactored AuthService and UserService

This commit is contained in:
Marcin Kurczewski 2014-09-08 08:20:31 +02:00
parent aa4c401df9
commit 121c2f80dc
7 changed files with 111 additions and 86 deletions

View file

@ -28,8 +28,6 @@ final class UserAvatarController extends AbstractController
public function getAvatarByName($userName, $size) public function getAvatarByName($userName, $size)
{ {
$user = $this->userService->getByName($userName); $user = $this->userService->getByName($userName);
if (!$user)
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
switch ($user->avatarStyle) switch ($user->avatarStyle)
{ {

View file

@ -32,8 +32,6 @@ final class UserController extends AbstractController
public function getByName($userName) public function getByName($userName)
{ {
$user = $this->userService->getByName($userName); $user = $this->userService->getByName($userName);
if (!$user)
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
return $this->userViewProxy->fromEntity($user); return $this->userViewProxy->fromEntity($user);
} }

View file

@ -9,23 +9,23 @@ class AuthService
private $validator; private $validator;
private $passwordService; private $passwordService;
private $timeService; private $timeService;
private $userDao; private $userService;
private $tokenDao; private $tokenService;
public function __construct( public function __construct(
\Szurubooru\Validator $validator, \Szurubooru\Validator $validator,
\Szurubooru\Services\PasswordService $passwordService, \Szurubooru\Services\PasswordService $passwordService,
\Szurubooru\Services\TimeService $timeService, \Szurubooru\Services\TimeService $timeService,
\Szurubooru\Dao\TokenDao $tokenDao, \Szurubooru\Services\TokenService $tokenService,
\Szurubooru\Dao\UserDao $userDao) \Szurubooru\Services\UserService $userService)
{ {
$this->loggedInUser = $this->getAnonymousUser();
$this->validator = $validator; $this->validator = $validator;
$this->passwordService = $passwordService; $this->passwordService = $passwordService;
$this->timeService = $timeService; $this->timeService = $timeService;
$this->tokenDao = $tokenDao; $this->tokenService = $tokenService;
$this->userDao = $userDao; $this->userService = $userService;
$this->loggedInUser = $this->getAnonymousUser();
} }
public function isLoggedIn() public function isLoggedIn()
@ -48,9 +48,7 @@ class AuthService
$this->validator->validateUserName($userName); $this->validator->validateUserName($userName);
$this->validator->validatePassword($password); $this->validator->validatePassword($password);
$user = $this->userDao->getByName($userName); $user = $this->userService->getByName($userName);
if (!$user)
throw new \InvalidArgumentException('User not found.');
$passwordHash = $this->passwordService->getHash($password); $passwordHash = $this->passwordService->getHash($password);
if ($user->passwordHash != $passwordHash) if ($user->passwordHash != $passwordHash)
@ -58,31 +56,22 @@ class AuthService
$this->loginToken = $this->createAndSaveLoginToken($user); $this->loginToken = $this->createAndSaveLoginToken($user);
$this->loggedInUser = $user; $this->loggedInUser = $user;
$this->updateLoginTime($user); $this->userService->updateUserLastLoginTime($user);
} }
public function loginFromToken($loginTokenName) public function loginFromToken($loginTokenName)
{ {
$this->validator->validateToken($loginTokenName); $this->validator->validateToken($loginTokenName);
$loginToken = $this->tokenDao->getByName($loginTokenName); $loginToken = $this->tokenService->getByName($loginTokenName);
if (!$loginToken)
throw new \Exception('Invalid login token.');
if ($loginToken->purpose != \Szurubooru\Entities\Token::PURPOSE_LOGIN) if ($loginToken->purpose != \Szurubooru\Entities\Token::PURPOSE_LOGIN)
throw new \Exception('This token is not a login token.'); throw new \Exception('This token is not a login token.');
$this->loginToken = $loginToken; $user = $this->userService->getById($loginToken->additionalData);
$this->loggedInUser = $this->userDao->getById($loginToken->additionalData);
if (!$this->loggedInUser)
throw new \Exception('User was deleted.');
$this->updateLoginTime($this->loggedInUser);
if (!$this->loggedInUser) $this->loginToken = $loginToken;
{ $this->loggedInUser = $user;
$this->logout(); $this->userService->updateUserLastLoginTime($this->loggedInUser);
throw new \RuntimeException('Token is correct, but user is not. Have you deleted your account?');
}
} }
public function getAnonymousUser() public function getAnonymousUser()
@ -104,24 +93,12 @@ class AuthService
if (!$this->isLoggedIn()) if (!$this->isLoggedIn())
throw new \Exception('Not logged in.'); throw new \Exception('Not logged in.');
$this->tokenDao->deleteByName($this->loginToken); $this->tokenService->invalidateByToken($this->loginToken);
$this->loginToken = null; $this->loginToken = null;
} }
private function createAndSaveLoginToken(\Szurubooru\Entities\User $user) private function createAndSaveLoginToken(\Szurubooru\Entities\User $user)
{ {
$loginToken = new \Szurubooru\Entities\Token(); return $this->tokenService->createAndSaveToken($user, \Szurubooru\Entities\Token::PURPOSE_LOGIN);
$loginToken->name = hash('sha256', $user->name . '/' . microtime(true));
$loginToken->additionalData = $user->id;
$loginToken->purpose = \Szurubooru\Entities\Token::PURPOSE_LOGIN;
$this->tokenDao->deleteByAdditionalData($loginToken->additionalData);
$this->tokenDao->save($loginToken);
return $loginToken;
}
private function updateLoginTime($user)
{
$user->lastLoginTime = $this->timeService->getCurrentTime();
$this->userDao->save($user);
} }
} }

View file

@ -10,23 +10,32 @@ class TokenService
$this->tokenDao = $tokenDao; $this->tokenDao = $tokenDao;
} }
public function getById($tokenId)
{
return $this->tokenDao->getById($tokenId);
}
public function getByName($tokenName) public function getByName($tokenName)
{ {
return $this->tokenDao->getByName($tokenName); $token = $this->tokenDao->getByName($tokenName);
if (!$token)
throw new \InvalidArgumentException('Token with identifier "' . $tokenName . '" not found.');
return $token;
} }
public function deleteByName($tokenName) public function invalidateByToken($tokenName)
{ {
return $this->tokenDao->deleteByName($tokenName); return $this->tokenDao->deleteByName($tokenName);
} }
public function save($token) public function invalidateByUser(\Szurubooru\Entities\User $user)
{ {
return $this->tokenDao->save($token); return $this->tokenDao->deleteByAdditionalData($user->id);
}
public function createAndSaveToken(\Szurubooru\Entities\User $user, $tokenPurpose)
{
$token = new \Szurubooru\Entities\Token();
$token->name = hash('sha256', $user->name . '/' . microtime(true));
$token->additionalData = $user->id;
$token->purpose = $tokenPurpose;
$this->invalidateByUser($user);
$this->tokenDao->save($token);
return $token;
} }
} }

View file

@ -32,9 +32,20 @@ class UserService
$this->timeService = $timeService; $this->timeService = $timeService;
} }
public function getByName($name) public function getByName($userName)
{ {
return $this->userDao->getByName($name); $user = $this->userDao->getByName($userName);
if (!$user)
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
return $user;
}
public function getById($userId)
{
$user = $this->userDao->getById($userId);
if (!$user)
throw new \InvalidArgumentException('User with id "' . $userId . '" was not found.');
return $user;
} }
public function getFiltered(\Szurubooru\FormData\SearchFormData $formData) public function getFiltered(\Szurubooru\FormData\SearchFormData $formData)
@ -73,8 +84,6 @@ class UserService
public function updateUser($userName, \Szurubooru\FormData\UserEditFormData $formData) public function updateUser($userName, \Szurubooru\FormData\UserEditFormData $formData)
{ {
$user = $this->getByName($userName); $user = $this->getByName($userName);
if (!$user)
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
if ($formData->avatarStyle !== null) if ($formData->avatarStyle !== null)
{ {
@ -127,15 +136,12 @@ class UserService
public function deleteUserByName($userName) public function deleteUserByName($userName)
{ {
$user = $this->getByName($userName); $user = $this->getByName($userName);
if (!$user)
throw new \InvalidArgumentException('User with name "' . $userName . '" was not found.');
$this->userDao->deleteByName($userName); $this->userDao->deleteByName($userName);
$this->fileService->delete($this->getCustomAvatarSourcePath($user)); $this->fileService->delete($this->getCustomAvatarSourcePath($user));
return true; return true;
} }
public function getCustomAvatarSourcePath($user) public function getCustomAvatarSourcePath(\Szurubooru\Entities\User $user)
{ {
return 'avatars' . DIRECTORY_SEPARATOR . $user->id; return 'avatars' . DIRECTORY_SEPARATOR . $user->id;
} }
@ -145,6 +151,12 @@ class UserService
return 'avatars' . DIRECTORY_SEPARATOR . 'blank.png'; return 'avatars' . DIRECTORY_SEPARATOR . 'blank.png';
} }
public function updateUserLastLoginTime(\Szurubooru\Entities\User $user)
{
$user->lastLoginTime = $this->timeService->getCurrentTime();
$this->userDao->save($user);
}
private function sendActivationMailIfNeeded(\Szurubooru\Entities\User &$user) private function sendActivationMailIfNeeded(\Szurubooru\Entities\User &$user)
{ {
//todo //todo

View file

@ -6,24 +6,16 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
private $validatorMock; private $validatorMock;
private $passwordServiceMock; private $passwordServiceMock;
private $timeServiceMock; private $timeServiceMock;
private $tokenDaoMock; private $tokenServiceMock;
private $userDaoMock; private $userServiceMock;
public function setUp() public function setUp()
{ {
$this->validatorMock = $this->mock(\Szurubooru\Validator::class); $this->validatorMock = $this->mock(\Szurubooru\Validator::class);
$this->passwordServiceMock = $this->mock(\Szurubooru\Services\PasswordService::class); $this->passwordServiceMock = $this->mock(\Szurubooru\Services\PasswordService::class);
$this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class); $this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class);
$this->tokenDaoMock = $this->mock(\Szurubooru\Dao\TokenDao::class); $this->tokenServiceMock = $this->mock(\Szurubooru\Services\TokenService::class);
$this->userDaoMock = $this->mock(\Szurubooru\Dao\UserDao::class); $this->userServiceMock = $this->mock(\Szurubooru\Services\UserService::class);
}
public function testInvalidUser()
{
$this->setExpectedException(\InvalidArgumentException::class, 'User not found');
$authService = $this->getAuthService();
$authService->loginFromCredentials('dummy', 'godzilla');
} }
public function testInvalidPassword() public function testInvalidPassword()
@ -33,23 +25,27 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
$testUser = new \Szurubooru\Entities\User(); $testUser = new \Szurubooru\Entities\User();
$testUser->name = 'dummy'; $testUser->name = 'dummy';
$testUser->passwordHash = 'hash'; $testUser->passwordHash = 'hash';
$this->userDaoMock->expects($this->once())->method('getByName')->willReturn($testUser); $this->userServiceMock->expects($this->once())->method('getByName')->willReturn($testUser);
$authService = $this->getAuthService(); $authService = $this->getAuthService();
$this->setExpectedException(\InvalidArgumentException::class, 'Specified password is invalid'); $this->setExpectedException(\Exception::class, 'Specified password is invalid');
$authService->loginFromCredentials('dummy', 'godzilla'); $authService->loginFromCredentials('dummy', 'godzilla');
} }
public function testValidCredentials() public function testValidCredentials()
{ {
$this->tokenDaoMock->expects($this->once())->method('save');
$this->passwordServiceMock->method('getHash')->willReturn('hash'); $this->passwordServiceMock->method('getHash')->willReturn('hash');
$testUser = new \Szurubooru\Entities\User(); $testUser = new \Szurubooru\Entities\User();
$testUser->name = 'dummy'; $testUser->name = 'dummy';
$testUser->passwordHash = 'hash'; $testUser->passwordHash = 'hash';
$this->userDaoMock->expects($this->once())->method('getByName')->willReturn($testUser); $this->userServiceMock->expects($this->once())->method('getByName')->willReturn($testUser);
$this->tokenDaoMock->expects($this->once())->method('deleteByAdditionalData')->with($testUser->id);
$testToken = new \Szurubooru\Entities\Token();
$testToken->name = 'mummy';
$this->tokenServiceMock->expects($this->once())->method('createAndSaveToken')->with(
$testUser,
\Szurubooru\Entities\Token::PURPOSE_LOGIN)->willReturn($testToken);
$authService = $this->getAuthService(); $authService = $this->getAuthService();
$authService->loginFromCredentials('dummy', 'godzilla'); $authService->loginFromCredentials('dummy', 'godzilla');
@ -57,12 +53,12 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
$this->assertTrue($authService->isLoggedIn()); $this->assertTrue($authService->isLoggedIn());
$this->assertEquals($testUser, $authService->getLoggedInUser()); $this->assertEquals($testUser, $authService->getLoggedInUser());
$this->assertNotNull($authService->getLoginToken()); $this->assertNotNull($authService->getLoginToken());
$this->assertNotNull($authService->getLoginToken()->name); $this->assertEquals('mummy', $authService->getLoginToken()->name);
} }
public function testInvalidToken() public function testInvalidToken()
{ {
$this->tokenDaoMock->expects($this->once())->method('getByName')->willReturn(null); $this->tokenServiceMock->expects($this->once())->method('getByName')->willReturn(null);
$this->setExpectedException(\Exception::class); $this->setExpectedException(\Exception::class);
$authService = $this->getAuthService(); $authService = $this->getAuthService();
@ -74,14 +70,13 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
$testUser = new \Szurubooru\Entities\User(); $testUser = new \Szurubooru\Entities\User();
$testUser->id = 5; $testUser->id = 5;
$testUser->name = 'dummy'; $testUser->name = 'dummy';
$testUser->passwordHash = 'hash'; $this->userServiceMock->expects($this->once())->method('getById')->willReturn($testUser);
$this->userDaoMock->expects($this->once())->method('getById')->willReturn($testUser);
$testToken = new \Szurubooru\Entities\Token(); $testToken = new \Szurubooru\Entities\Token();
$testToken->name = 'dummy_token'; $testToken->name = 'dummy_token';
$testToken->additionalData = $testUser->id; $testToken->additionalData = $testUser->id;
$testToken->purpose = \Szurubooru\Entities\Token::PURPOSE_LOGIN; $testToken->purpose = \Szurubooru\Entities\Token::PURPOSE_LOGIN;
$this->tokenDaoMock->expects($this->once())->method('getByName')->willReturn($testToken); $this->tokenServiceMock->expects($this->once())->method('getByName')->willReturn($testToken);
$authService = $this->getAuthService(); $authService = $this->getAuthService();
$authService->loginFromToken($testToken->name); $authService->loginFromToken($testToken->name);
@ -89,7 +84,7 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
$this->assertTrue($authService->isLoggedIn()); $this->assertTrue($authService->isLoggedIn());
$this->assertEquals($testUser, $authService->getLoggedInUser()); $this->assertEquals($testUser, $authService->getLoggedInUser());
$this->assertNotNull($authService->getLoginToken()); $this->assertNotNull($authService->getLoginToken());
$this->assertNotNull($authService->getLoginToken()->name); $this->assertEquals('dummy_token', $authService->getLoginToken()->name);
} }
private function getAuthService() private function getAuthService()
@ -98,7 +93,7 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
$this->validatorMock, $this->validatorMock,
$this->passwordServiceMock, $this->passwordServiceMock,
$this->timeServiceMock, $this->timeServiceMock,
$this->tokenDaoMock, $this->tokenServiceMock,
$this->userDaoMock); $this->userServiceMock);
} }
} }

View file

@ -24,6 +24,42 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
$this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class); $this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class);
} }
public function testGettingByName()
{
$testUser = new \Szurubooru\Entities\User();
$testUser->name = 'godzilla';
$this->userDaoMock->expects($this->once())->method('getByName')->willReturn($testUser);
$userService = $this->getUserService();
$expected = $testUser;
$actual = $userService->getByName('godzilla');
$this->assertEquals($expected, $actual);
}
public function testGettingByNameNonExistentUsers()
{
$this->setExpectedException(\Exception::class, 'User with name "godzilla" was not found.');
$userService = $this->getUserService();
$userService->getByName('godzilla');
}
public function testGettingById()
{
$testUser = new \Szurubooru\Entities\User();
$testUser->name = 'godzilla';
$this->userDaoMock->expects($this->once())->method('getById')->willReturn($testUser);
$userService = $this->getUserService();
$expected = $testUser;
$actual = $userService->getById('godzilla');
$this->assertEquals($expected, $actual);
}
public function testGettingByIdNonExistentUsers()
{
$this->setExpectedException(\Exception::class, 'User with id "godzilla" was not found.');
$userService = $this->getUserService();
$userService->getById('godzilla');
}
public function testGettingFilteredUsers() public function testGettingFilteredUsers()
{ {
$mockUser = new \Szurubooru\Entities\User(); $mockUser = new \Szurubooru\Entities\User();