Added e-mail confirmation and password reset
This commit is contained in:
parent
121c2f80dc
commit
85a026c37b
23 changed files with 619 additions and 107 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
[basic]
|
||||||
|
serviceName = szuru2
|
||||||
|
serviceBaseUrl = http://localhost/
|
||||||
|
emailAddress = noreply@localhost
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
host = localhost
|
host = localhost
|
||||||
port = 27017
|
port = 27017
|
||||||
|
@ -6,6 +11,7 @@ name = booru-dev
|
||||||
[security]
|
[security]
|
||||||
secret = change
|
secret = change
|
||||||
minPasswordLength = 5
|
minPasswordLength = 5
|
||||||
|
needEmailActivationToRegister = 1
|
||||||
|
|
||||||
[security.privileges]
|
[security.privileges]
|
||||||
register = anonymous
|
register = anonymous
|
||||||
|
|
|
@ -53,7 +53,6 @@
|
||||||
<script type="text/javascript" src="/js/Presenters/TagListPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/TagListPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/HelpPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/HelpPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/HomePresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/HomePresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Presenters/PasswordResetPresenter.js"></script>
|
|
||||||
<script type="text/javascript" src="/js/Presenters/UserActivationPresenter.js"></script>
|
<script type="text/javascript" src="/js/Presenters/UserActivationPresenter.js"></script>
|
||||||
<script type="text/javascript" src="/js/Router.js"></script>
|
<script type="text/javascript" src="/js/Router.js"></script>
|
||||||
<script type="text/javascript" src="/js/Bootstrap.js"></script>
|
<script type="text/javascript" src="/js/Bootstrap.js"></script>
|
||||||
|
|
|
@ -40,12 +40,19 @@ App.Presenters.LoginPresenter = function(
|
||||||
var password = $el.find('[name=password]').val();
|
var password = $el.find('[name=password]').val();
|
||||||
var remember = $el.find('[name=remember]').val();
|
var remember = $el.find('[name=remember]').val();
|
||||||
|
|
||||||
//todo: client side error reporting
|
if (userName.length == 0) {
|
||||||
|
messagePresenter.showError($messages, 'User name cannot be empty.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length == 0) {
|
||||||
|
messagePresenter.showError($messages, 'Password cannot be empty.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auth.loginFromCredentials(userName, password, remember)
|
auth.loginFromCredentials(userName, password, remember)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
router.navigateToMainPage();
|
router.navigateToMainPage();
|
||||||
//todo: "redirect" to main page
|
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
messagePresenter.showError($messages, response.json && response.json.error || response);
|
messagePresenter.showError($messages, response.json && response.json.error || response);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
var App = App || {};
|
|
||||||
App.Presenters = App.Presenters || {};
|
|
||||||
|
|
||||||
App.Presenters.PasswordResetPresenter = function(
|
|
||||||
jQuery,
|
|
||||||
topNavigationPresenter,
|
|
||||||
messagePresenter) {
|
|
||||||
|
|
||||||
var $el = jQuery('#content');
|
|
||||||
var $messages = $el;
|
|
||||||
|
|
||||||
function init(args) {
|
|
||||||
topNavigationPresenter.select('login');
|
|
||||||
if (args.token) {
|
|
||||||
alert('Got token');
|
|
||||||
} else {
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
$el.html('Password reset placeholder');
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
init: init,
|
|
||||||
render: render,
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
App.DI.register('passwordResetPresenter', App.Presenters.PasswordResetPresenter);
|
|
|
@ -50,10 +50,13 @@ App.Presenters.RegistrationPresenter = function(
|
||||||
}
|
}
|
||||||
|
|
||||||
function registrationSuccess(apiResponse) {
|
function registrationSuccess(apiResponse) {
|
||||||
//todo: tell user if it turned out that he needs to confirm his e-mail
|
|
||||||
$el.find('form').slideUp(function() {
|
$el.find('form').slideUp(function() {
|
||||||
var message = 'Registration complete! ';
|
var message = 'Registration complete! ';
|
||||||
|
if (!apiResponse.json.confirmed) {
|
||||||
|
message += '<br/>Check your inbox for activation e-mail.<br/>If e-mail doesn\'t show up, check your spam folder.';
|
||||||
|
} else {
|
||||||
message += '<a href="#/login">Click here</a> to login.';
|
message += '<a href="#/login">Click here</a> to login.';
|
||||||
|
}
|
||||||
messagePresenter.showInfo($messages, message);
|
messagePresenter.showInfo($messages, message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,21 +3,111 @@ App.Presenters = App.Presenters || {};
|
||||||
|
|
||||||
App.Presenters.UserActivationPresenter = function(
|
App.Presenters.UserActivationPresenter = function(
|
||||||
jQuery,
|
jQuery,
|
||||||
topNavigationPresenter) {
|
promise,
|
||||||
|
util,
|
||||||
|
auth,
|
||||||
|
api,
|
||||||
|
router,
|
||||||
|
topNavigationPresenter,
|
||||||
|
messagePresenter) {
|
||||||
|
|
||||||
var $el = jQuery('#content');
|
var $el = jQuery('#content');
|
||||||
|
var $messages = $el;
|
||||||
|
var template;
|
||||||
|
var formHidden = false;
|
||||||
|
var operation;
|
||||||
|
|
||||||
function init(args) {
|
function init(args) {
|
||||||
|
if (auth.isLoggedIn()) {
|
||||||
|
router.navigateToMainPage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
topNavigationPresenter.select('login');
|
topNavigationPresenter.select('login');
|
||||||
|
reinit(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reinit(args) {
|
||||||
|
operation = args.operation;
|
||||||
|
console.log(operation);
|
||||||
|
promise.wait(util.promiseTemplate('user-query-form')).then(function(html) {
|
||||||
|
template = _.template(html);
|
||||||
|
if (args.token) {
|
||||||
|
hideForm();
|
||||||
|
confirmToken(args.token);
|
||||||
|
} else {
|
||||||
|
showForm();
|
||||||
|
}
|
||||||
render();
|
render();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
$el.html('Account activation placeholder');
|
$el.html(template());
|
||||||
};
|
$messages = $el.find('.messages');
|
||||||
|
if (formHidden)
|
||||||
|
$el.find('form').hide();
|
||||||
|
$el.find('form').submit(userQueryFormSubmitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideForm() {
|
||||||
|
formHidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showForm() {
|
||||||
|
formHidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function userQueryFormSubmitted(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
messagePresenter.hideMessages($messages);
|
||||||
|
|
||||||
|
var userNameOrEmail = $el.find('form input[name=user]').val();
|
||||||
|
if (userNameOrEmail.length == 0) {
|
||||||
|
messagePresenter.showError($messages, 'Field cannot be blank.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var url = operation == 'passwordReset'
|
||||||
|
? '/password-reset/' + userNameOrEmail
|
||||||
|
: '/activation/' + userNameOrEmail;
|
||||||
|
|
||||||
|
api.post(url).then(function(response) {
|
||||||
|
var message = operation == 'passwordReset'
|
||||||
|
? 'Password reset request sent.'
|
||||||
|
: 'Activation e-mail resent.';
|
||||||
|
message += ' Check your inbox.<br/>If e-mail doesn\'t show up, check your spam folder.';
|
||||||
|
|
||||||
|
$el.find('#user-query-form').slideUp(function() {
|
||||||
|
messagePresenter.showInfo($messages, message);
|
||||||
|
});
|
||||||
|
}).fail(function(response) {
|
||||||
|
messagePresenter.showError($messages, response.json && response.json.error || response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmToken(token) {
|
||||||
|
messagePresenter.hideMessages($messages);
|
||||||
|
|
||||||
|
var url = operation == 'passwordReset'
|
||||||
|
? '/finish-password-reset/' + token
|
||||||
|
: '/finish-activation/' + token;
|
||||||
|
|
||||||
|
api.post(url).then(function(response) {
|
||||||
|
var message = operation == 'passwordReset'
|
||||||
|
? 'Your new password is <strong>' + response.json.newPassword + '</strong>.'
|
||||||
|
: 'E-mail activation successful.';
|
||||||
|
|
||||||
|
$el.find('#user-query-form').slideUp(function() {
|
||||||
|
messagePresenter.showInfo($messages, message);
|
||||||
|
});
|
||||||
|
}).fail(function(response) {
|
||||||
|
messagePresenter.showError($messages, response.json && response.json.error || response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
|
reinit: reinit,
|
||||||
render: render,
|
render: render,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ App.Router = function(jQuery, util, appState) {
|
||||||
inject('#/logout', 'logoutPresenter');
|
inject('#/logout', 'logoutPresenter');
|
||||||
inject('#/register', 'registrationPresenter');
|
inject('#/register', 'registrationPresenter');
|
||||||
inject('#/upload', 'postUploadPresenter');
|
inject('#/upload', 'postUploadPresenter');
|
||||||
inject('#/password-reset(/:token)', 'passwordResetPresenter');
|
inject('#/password-reset(/:token)', 'userActivationPresenter', {operation: 'passwordReset'});
|
||||||
inject('#/activate(/:token)', 'userActivationPresenter');
|
inject('#/activate(/:token)', 'userActivationPresenter', {operation: 'activation'});
|
||||||
inject('#/users(/:searchArgs)', 'userListPresenter');
|
inject('#/users(/:searchArgs)', 'userListPresenter');
|
||||||
inject('#/user/:userName(/:tab)', 'userPresenter');
|
inject('#/user/:userName(/:tab)', 'userPresenter');
|
||||||
inject('#/posts(/:searchArgs)', 'postListPresenter');
|
inject('#/posts(/:searchArgs)', 'postListPresenter');
|
||||||
|
@ -40,9 +40,9 @@ App.Router = function(jQuery, util, appState) {
|
||||||
Path.root(newRoot);
|
Path.root(newRoot);
|
||||||
};
|
};
|
||||||
|
|
||||||
function inject(path, presenterName) {
|
function inject(path, presenterName, additionalParams) {
|
||||||
Path.map(path).to(function() {
|
Path.map(path).to(function() {
|
||||||
util.initContentPresenter(presenterName, this.params);
|
util.initContentPresenter(presenterName, _.extend(this.params, additionalParams));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
19
public_html/templates/user-query-form.tpl
Normal file
19
public_html/templates/user-query-form.tpl
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<div class="messages"></div>
|
||||||
|
|
||||||
|
<div id="user-query-form">
|
||||||
|
<form class="form-wrapper">
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="form-label" for="user-query-user">User name or e-mail:</label>
|
||||||
|
<div class="form-input">
|
||||||
|
<input autocomplete="off" type="text" name="user" id="user-query-user"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="form-label"></label>
|
||||||
|
<div class="form-input">
|
||||||
|
<button type="submit">Continue</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -24,14 +24,18 @@ final class UserController extends AbstractController
|
||||||
{
|
{
|
||||||
$router->post('/api/users', [$this, 'createUser']);
|
$router->post('/api/users', [$this, 'createUser']);
|
||||||
$router->get('/api/users', [$this, 'getFiltered']);
|
$router->get('/api/users', [$this, 'getFiltered']);
|
||||||
$router->get('/api/users/:userName', [$this, 'getByName']);
|
$router->get('/api/users/:userNameOrEmail', [$this, 'getByNameOrEmail']);
|
||||||
$router->put('/api/users/:userName', [$this, 'updateUser']);
|
$router->put('/api/users/:userNameOrEmail', [$this, 'updateUser']);
|
||||||
$router->delete('/api/users/:userName', [$this, 'deleteUser']);
|
$router->delete('/api/users/:userNameOrEmail', [$this, 'deleteUser']);
|
||||||
|
$router->post('/api/password-reset/:userNameOrEmail', [$this, 'passwordReset']);
|
||||||
|
$router->post('/api/finish-password-reset/:tokenName', [$this, 'finishPasswordReset']);
|
||||||
|
$router->post('/api/activation/:userNameOrEmail', [$this, 'activation']);
|
||||||
|
$router->post('/api/finish-activation/:tokenName', [$this, 'finishActivation']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByName($userName)
|
public function getByNameOrEmail($userNameOrEmail)
|
||||||
{
|
{
|
||||||
$user = $this->userService->getByName($userName);
|
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
|
||||||
return $this->userViewProxy->fromEntity($user);
|
return $this->userViewProxy->fromEntity($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,17 +57,18 @@ final class UserController extends AbstractController
|
||||||
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::REGISTER);
|
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::REGISTER);
|
||||||
$formData = new \Szurubooru\FormData\RegistrationFormData($this->inputReader);
|
$formData = new \Szurubooru\FormData\RegistrationFormData($this->inputReader);
|
||||||
$user = $this->userService->createUser($formData);
|
$user = $this->userService->createUser($formData);
|
||||||
return $this->userViewProxy->fromEntity($user);
|
return array_merge((array) $this->userViewProxy->fromEntity($user), ['confirmed' => $user->emailUnconfirmed == null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateUser($userName)
|
public function updateUser($userNameOrEmail)
|
||||||
{
|
{
|
||||||
|
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
|
||||||
$formData = new \Szurubooru\FormData\UserEditFormData($this->inputReader);
|
$formData = new \Szurubooru\FormData\UserEditFormData($this->inputReader);
|
||||||
|
|
||||||
if ($formData->avatarStyle !== null)
|
if ($formData->avatarStyle !== null)
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(
|
$this->privilegeService->assertPrivilege(
|
||||||
$this->privilegeService->isLoggedIn($userName)
|
$this->privilegeService->isLoggedIn($userNameOrEmail)
|
||||||
? \Szurubooru\Privilege::CHANGE_OWN_AVATAR_STYLE
|
? \Szurubooru\Privilege::CHANGE_OWN_AVATAR_STYLE
|
||||||
: \Szurubooru\Privilege::CHANGE_ALL_AVATAR_STYLES);
|
: \Szurubooru\Privilege::CHANGE_ALL_AVATAR_STYLES);
|
||||||
}
|
}
|
||||||
|
@ -71,7 +76,7 @@ final class UserController extends AbstractController
|
||||||
if ($formData->userName !== null)
|
if ($formData->userName !== null)
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(
|
$this->privilegeService->assertPrivilege(
|
||||||
$this->privilegeService->isLoggedIn($userName)
|
$this->privilegeService->isLoggedIn($userNameOrEmail)
|
||||||
? \Szurubooru\Privilege::CHANGE_OWN_NAME
|
? \Szurubooru\Privilege::CHANGE_OWN_NAME
|
||||||
: \Szurubooru\Privilege::CHANGE_ALL_NAMES);
|
: \Szurubooru\Privilege::CHANGE_ALL_NAMES);
|
||||||
}
|
}
|
||||||
|
@ -79,7 +84,7 @@ final class UserController extends AbstractController
|
||||||
if ($formData->password !== null)
|
if ($formData->password !== null)
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(
|
$this->privilegeService->assertPrivilege(
|
||||||
$this->privilegeService->isLoggedIn($userName)
|
$this->privilegeService->isLoggedIn($userNameOrEmail)
|
||||||
? \Szurubooru\Privilege::CHANGE_OWN_PASSWORD
|
? \Szurubooru\Privilege::CHANGE_OWN_PASSWORD
|
||||||
: \Szurubooru\Privilege::CHANGE_ALL_PASSWORDS);
|
: \Szurubooru\Privilege::CHANGE_ALL_PASSWORDS);
|
||||||
}
|
}
|
||||||
|
@ -87,7 +92,7 @@ final class UserController extends AbstractController
|
||||||
if ($formData->email !== null)
|
if ($formData->email !== null)
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(
|
$this->privilegeService->assertPrivilege(
|
||||||
$this->privilegeService->isLoggedIn($userName)
|
$this->privilegeService->isLoggedIn($userNameOrEmail)
|
||||||
? \Szurubooru\Privilege::CHANGE_OWN_EMAIL_ADDRESS
|
? \Szurubooru\Privilege::CHANGE_OWN_EMAIL_ADDRESS
|
||||||
: \Szurubooru\Privilege::CHANGE_ALL_EMAIL_ADDRESSES);
|
: \Szurubooru\Privilege::CHANGE_ALL_EMAIL_ADDRESSES);
|
||||||
}
|
}
|
||||||
|
@ -99,20 +104,43 @@ final class UserController extends AbstractController
|
||||||
|
|
||||||
if ($formData->browsingSettings)
|
if ($formData->browsingSettings)
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertLoggedIn($userName);
|
$this->privilegeService->assertLoggedIn($userNameOrEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userService->updateUser($userName, $formData);
|
$user = $this->userService->updateUser($user, $formData);
|
||||||
return $this->userViewProxy->fromEntity($user);
|
return $this->userViewProxy->fromEntity($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteUser($userName)
|
public function deleteUser($userNameOrEmail)
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(
|
$this->privilegeService->assertPrivilege(
|
||||||
$this->privilegeService->isLoggedIn($userName)
|
$this->privilegeService->isLoggedIn($userNameOrEmail)
|
||||||
? \Szurubooru\Privilege::DELETE_OWN_ACCOUNT
|
? \Szurubooru\Privilege::DELETE_OWN_ACCOUNT
|
||||||
: \Szurubooru\Privilege::DELETE_ACCOUNTS);
|
: \Szurubooru\Privilege::DELETE_ACCOUNTS);
|
||||||
|
|
||||||
return $this->userService->deleteUserByName($userName);
|
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
|
||||||
|
return $this->userService->deleteUser($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function passwordReset($userNameOrEmail)
|
||||||
|
{
|
||||||
|
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
|
||||||
|
return $this->userService->sendPasswordResetEmail($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activation($userNameOrEmail)
|
||||||
|
{
|
||||||
|
$user = $this->userService->getByNameOrEmail($userNameOrEmail, true);
|
||||||
|
return $this->userService->sendActivationEmail($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function finishPasswordReset($tokenName)
|
||||||
|
{
|
||||||
|
return ['newPassword' => $this->userService->finishPasswordReset($tokenName)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function finishActivation($tokenName)
|
||||||
|
{
|
||||||
|
$this->userService->finishActivation($tokenName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ class UserViewProxy extends AbstractViewProxy
|
||||||
$this->privilegeService->isLoggedIn($user))
|
$this->privilegeService->isLoggedIn($user))
|
||||||
{
|
{
|
||||||
$result->email = $user->email;
|
$result->email = $user->email;
|
||||||
|
$result->emailUnconfirmed = $user->emailUnconfirmed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
|
|
|
@ -15,6 +15,14 @@ class UserDao extends AbstractDao implements ICrudDao
|
||||||
return $this->entityConverter->toEntity($arrayEntity);
|
return $this->entityConverter->toEntity($arrayEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getByEmail($userEmail, $allowUnconfirmed = false)
|
||||||
|
{
|
||||||
|
$arrayEntity = $this->collection->findOne(['email' => $userEmail]);
|
||||||
|
if (!$arrayEntity and $allowUnconfirmed)
|
||||||
|
$arrayEntity = $this->collection->findOne(['emailUnconfirmed' => $userEmail]);
|
||||||
|
return $this->entityConverter->toEntity($arrayEntity);
|
||||||
|
}
|
||||||
|
|
||||||
public function hasAnyUsers()
|
public function hasAnyUsers()
|
||||||
{
|
{
|
||||||
return (bool) $this->collection->findOne();
|
return (bool) $this->collection->findOne();
|
||||||
|
|
|
@ -4,6 +4,8 @@ namespace Szurubooru\Entities;
|
||||||
final class Token extends Entity
|
final class Token extends Entity
|
||||||
{
|
{
|
||||||
const PURPOSE_LOGIN = 'login';
|
const PURPOSE_LOGIN = 'login';
|
||||||
|
const PURPOSE_ACTIVATE = 'activate';
|
||||||
|
const PURPOSE_PASSWORD_RESET = 'passwordReset';
|
||||||
|
|
||||||
public $name;
|
public $name;
|
||||||
public $purpose;
|
public $purpose;
|
||||||
|
|
|
@ -16,6 +16,7 @@ final class User extends Entity
|
||||||
|
|
||||||
public $name;
|
public $name;
|
||||||
public $email;
|
public $email;
|
||||||
|
public $emailUnconfirmed;
|
||||||
public $passwordHash;
|
public $passwordHash;
|
||||||
public $accessRank;
|
public $accessRank;
|
||||||
public $registrationTime;
|
public $registrationTime;
|
||||||
|
|
|
@ -6,20 +6,20 @@ class AuthService
|
||||||
private $loggedInUser = null;
|
private $loggedInUser = null;
|
||||||
private $loginToken = null;
|
private $loginToken = null;
|
||||||
|
|
||||||
private $validator;
|
private $config;
|
||||||
private $passwordService;
|
private $passwordService;
|
||||||
private $timeService;
|
private $timeService;
|
||||||
private $userService;
|
private $userService;
|
||||||
private $tokenService;
|
private $tokenService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
\Szurubooru\Validator $validator,
|
\Szurubooru\Config $config,
|
||||||
\Szurubooru\Services\PasswordService $passwordService,
|
\Szurubooru\Services\PasswordService $passwordService,
|
||||||
\Szurubooru\Services\TimeService $timeService,
|
\Szurubooru\Services\TimeService $timeService,
|
||||||
\Szurubooru\Services\TokenService $tokenService,
|
\Szurubooru\Services\TokenService $tokenService,
|
||||||
\Szurubooru\Services\UserService $userService)
|
\Szurubooru\Services\UserService $userService)
|
||||||
{
|
{
|
||||||
$this->validator = $validator;
|
$this->config = $config;
|
||||||
$this->passwordService = $passwordService;
|
$this->passwordService = $passwordService;
|
||||||
$this->timeService = $timeService;
|
$this->timeService = $timeService;
|
||||||
$this->tokenService = $tokenService;
|
$this->tokenService = $tokenService;
|
||||||
|
@ -43,12 +43,10 @@ class AuthService
|
||||||
return $this->loginToken;
|
return $this->loginToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loginFromCredentials($userName, $password)
|
public function loginFromCredentials($userNameOrEmail, $password)
|
||||||
{
|
{
|
||||||
$this->validator->validateUserName($userName);
|
$user = $this->userService->getByNameOrEmail($userNameOrEmail);
|
||||||
$this->validator->validatePassword($password);
|
$this->validateUser($user);
|
||||||
|
|
||||||
$user = $this->userService->getByName($userName);
|
|
||||||
|
|
||||||
$passwordHash = $this->passwordService->getHash($password);
|
$passwordHash = $this->passwordService->getHash($password);
|
||||||
if ($user->passwordHash != $passwordHash)
|
if ($user->passwordHash != $passwordHash)
|
||||||
|
@ -61,13 +59,12 @@ class AuthService
|
||||||
|
|
||||||
public function loginFromToken($loginTokenName)
|
public function loginFromToken($loginTokenName)
|
||||||
{
|
{
|
||||||
$this->validator->validateToken($loginTokenName);
|
|
||||||
|
|
||||||
$loginToken = $this->tokenService->getByName($loginTokenName);
|
$loginToken = $this->tokenService->getByName($loginTokenName);
|
||||||
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.');
|
||||||
|
|
||||||
$user = $this->userService->getById($loginToken->additionalData);
|
$user = $this->userService->getById($loginToken->additionalData);
|
||||||
|
$this->validateUser($user);
|
||||||
|
|
||||||
$this->loginToken = $loginToken;
|
$this->loginToken = $loginToken;
|
||||||
$this->loggedInUser = $user;
|
$this->loggedInUser = $user;
|
||||||
|
@ -93,12 +90,18 @@ class AuthService
|
||||||
if (!$this->isLoggedIn())
|
if (!$this->isLoggedIn())
|
||||||
throw new \Exception('Not logged in.');
|
throw new \Exception('Not logged in.');
|
||||||
|
|
||||||
$this->tokenService->invalidateByToken($this->loginToken);
|
$this->tokenService->invalidateByName($this->loginToken);
|
||||||
$this->loginToken = null;
|
$this->loginToken = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createAndSaveLoginToken(\Szurubooru\Entities\User $user)
|
private function createAndSaveLoginToken(\Szurubooru\Entities\User $user)
|
||||||
{
|
{
|
||||||
return $this->tokenService->createAndSaveToken($user, \Szurubooru\Entities\Token::PURPOSE_LOGIN);
|
return $this->tokenService->createAndSaveToken($user->id, \Szurubooru\Entities\Token::PURPOSE_LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateUser($user)
|
||||||
|
{
|
||||||
|
if (!$user->email and $this->config->security->needEmailActivationToRegister)
|
||||||
|
throw new \DomainException('User didn\'t confirm mail yet.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,104 @@ namespace Szurubooru\Services;
|
||||||
|
|
||||||
class EmailService
|
class EmailService
|
||||||
{
|
{
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
public function __construct(\Szurubooru\Config $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendPasswordResetEmail(\Szurubooru\Entities\User $user, \Szurubooru\Entities\Token $token)
|
||||||
|
{
|
||||||
|
if (!$user->email)
|
||||||
|
throw new \BadMethodCall('An activated e-mail addreses is needed to reset the password.');
|
||||||
|
|
||||||
|
$recipientEmail = $user->email;
|
||||||
|
$senderName = $this->config->basic->serviceName . ' bot';
|
||||||
|
$subject = $this->config->basic->serviceName . ' password reset';
|
||||||
|
|
||||||
|
$body =
|
||||||
|
'Hello,' .
|
||||||
|
PHP_EOL . PHP_EOL .
|
||||||
|
'Someone (probably you) requested to reset password for an account at ' . $this->config->basic->serviceName . '. ' .
|
||||||
|
'In order to proceed, please click this link or paste it in your browser address bar: ' .
|
||||||
|
PHP_EOL . PHP_EOL .
|
||||||
|
$this->config->basic->serviceBaseUrl . '#/password-reset/' . $token->name .
|
||||||
|
PHP_EOL . PHP_EOL .
|
||||||
|
'Otherwise please ignore this mail.' .
|
||||||
|
$this->getFooter();
|
||||||
|
|
||||||
|
$this->sendEmail($senderName, $recipientEmail, $subject, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendActivationEmail(\Szurubooru\Entities\User $user, \Szurubooru\Entities\Token $token)
|
||||||
|
{
|
||||||
|
if (!$user->emailUnconfirmed)
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException(
|
||||||
|
$user->email
|
||||||
|
? 'E-mail for this account is already confirmed.'
|
||||||
|
: 'An e-mail address is needed to activate the account.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$recipientEmail = $user->emailUnconfirmed;
|
||||||
|
$senderName = $this->config->basic->serviceName . ' bot';
|
||||||
|
$subject = $this->config->basic->serviceName . ' account activation';
|
||||||
|
|
||||||
|
$body =
|
||||||
|
'Hello,' .
|
||||||
|
PHP_EOL . PHP_EOL .
|
||||||
|
'Someone (probably you) registered at ' . $this->config->basic->serviceName . ' an account with this e-mail address. ' .
|
||||||
|
'In order to finish activation, please click this link or paste it in your browser address bar: ' .
|
||||||
|
PHP_EOL . PHP_EOL .
|
||||||
|
$this->config->basic->serviceBaseUrl . '#/activate/' . $token->name .
|
||||||
|
PHP_EOL . PHP_EOL .
|
||||||
|
'Otherwise please ignore this mail.' .
|
||||||
|
$this->getFooter();
|
||||||
|
|
||||||
|
$this->sendEmail($senderName, $recipientEmail, $subject, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendEmail($senderName, $recipientEmail, $subject, $body)
|
||||||
|
{
|
||||||
|
$senderEmail = $this->config->basic->emailAddress;
|
||||||
|
$domain = substr($senderEmail, strpos($senderEmail, '@') + 1);
|
||||||
|
|
||||||
|
$clientIp = isset($_SERVER['SERVER_ADDR'])
|
||||||
|
? $_SERVER['SERVER_ADDR']
|
||||||
|
: '';
|
||||||
|
|
||||||
|
$body = wordwrap($body, 70);
|
||||||
|
if (empty($recipientEmail))
|
||||||
|
throw new \InvalidArgumentException('Destination e-mail address was not found');
|
||||||
|
|
||||||
|
$messageId = sha1(date('r') . uniqid()) . '@' . $domain;
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
$headers []= sprintf('MIME-Version: 1.0');
|
||||||
|
$headers []= sprintf('Content-Transfer-Encoding: 7bit');
|
||||||
|
$headers []= sprintf('Date: %s', date('r'));
|
||||||
|
$headers []= sprintf('Message-ID: <%s>', $messageId);
|
||||||
|
$headers []= sprintf('From: %s <%s>', $senderName, $senderEmail);
|
||||||
|
$headers []= sprintf('Reply-To: %s', $senderEmail);
|
||||||
|
$headers []= sprintf('Return-Path: %s', $senderEmail);
|
||||||
|
$headers []= sprintf('Subject: %s', $subject);
|
||||||
|
$headers []= sprintf('Content-Type: text/plain; charset=utf-8');
|
||||||
|
$headers []= sprintf('X-Mailer: PHP/%s', phpversion());
|
||||||
|
$headers []= sprintf('X-Originating-IP: %s', $clientIp);
|
||||||
|
|
||||||
|
$senderEmail = $this->config->basic->emailAddress;
|
||||||
|
$encodedSubject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
|
||||||
|
|
||||||
|
$arguments = [$recipientEmail, $encodedSubject, $body, implode("\r\n", $headers), '-f' . $senderEmail];
|
||||||
|
//throw new \RuntimeException(htmlentities(print_r($arguments, true)));
|
||||||
|
call_user_func_array('mail', $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFooter()
|
||||||
|
{
|
||||||
|
return PHP_EOL . PHP_EOL .
|
||||||
|
'Thank you and have a nice day,' . PHP_EOL .
|
||||||
|
$this->config->basic->serviceName . ' registration bot';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,35 @@ namespace Szurubooru\Services;
|
||||||
class PasswordService
|
class PasswordService
|
||||||
{
|
{
|
||||||
private $config;
|
private $config;
|
||||||
|
private $alphabet;
|
||||||
|
private $pattern;
|
||||||
|
|
||||||
public function __construct(\Szurubooru\Config $config)
|
public function __construct(\Szurubooru\Config $config)
|
||||||
{
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
$this->alphabet =
|
||||||
|
[
|
||||||
|
'c' => str_split('bcdfghjklmnpqrstvwxyz'),
|
||||||
|
'v' => str_split('aeiou'),
|
||||||
|
'n' => str_split('0123456789'),
|
||||||
|
];
|
||||||
|
$this->pattern = str_split('cvcvnncvcv');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHash($password)
|
public function getHash($password)
|
||||||
{
|
{
|
||||||
return hash('sha256', $this->config->security->secret . '/' . $password);
|
return hash('sha256', $this->config->security->secret . '/' . $password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRandomPassword()
|
||||||
|
{
|
||||||
|
$password = '';
|
||||||
|
foreach ($this->pattern as $token)
|
||||||
|
{
|
||||||
|
$subAlphabet = $this->alphabet[$token];
|
||||||
|
$character = $subAlphabet[mt_rand(0, count($subAlphabet) - 1)];
|
||||||
|
$password .= $character;
|
||||||
|
}
|
||||||
|
return $password;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,10 +57,21 @@ class PrivilegeService
|
||||||
{
|
{
|
||||||
$loggedInUser = $this->authService->getLoggedInUser();
|
$loggedInUser = $this->authService->getLoggedInUser();
|
||||||
if ($userIdentifier instanceof \Szurubooru\Entities\User)
|
if ($userIdentifier instanceof \Szurubooru\Entities\User)
|
||||||
|
{
|
||||||
return $loggedInUser->name == $userIdentifier->name;
|
return $loggedInUser->name == $userIdentifier->name;
|
||||||
|
}
|
||||||
elseif (is_string($userIdentifier))
|
elseif (is_string($userIdentifier))
|
||||||
|
{
|
||||||
|
if ($loggedInUser->email)
|
||||||
|
{
|
||||||
|
if ($loggedInUser->email == $userIdentifier)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return $loggedInUser->name == $userIdentifier;
|
return $loggedInUser->name == $userIdentifier;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
throw new \InvalidArgumentException('Invalid user identifier.');
|
throw new \InvalidArgumentException('Invalid user identifier.');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,23 +18,23 @@ class TokenService
|
||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invalidateByToken($tokenName)
|
public function invalidateByName($tokenName)
|
||||||
{
|
{
|
||||||
return $this->tokenDao->deleteByName($tokenName);
|
return $this->tokenDao->deleteByName($tokenName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invalidateByUser(\Szurubooru\Entities\User $user)
|
public function invalidateByAdditionalData($additionalData)
|
||||||
{
|
{
|
||||||
return $this->tokenDao->deleteByAdditionalData($user->id);
|
return $this->tokenDao->deleteByAdditionalData($additionalData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createAndSaveToken(\Szurubooru\Entities\User $user, $tokenPurpose)
|
public function createAndSaveToken($additionalData, $tokenPurpose)
|
||||||
{
|
{
|
||||||
$token = new \Szurubooru\Entities\Token();
|
$token = new \Szurubooru\Entities\Token();
|
||||||
$token->name = hash('sha256', $user->name . '/' . microtime(true));
|
$token->name = sha1(date('r') . uniqid() . microtime(true));
|
||||||
$token->additionalData = $user->id;
|
$token->additionalData = $additionalData;
|
||||||
$token->purpose = $tokenPurpose;
|
$token->purpose = $tokenPurpose;
|
||||||
$this->invalidateByUser($user);
|
$this->invalidateByAdditionalData($additionalData);
|
||||||
$this->tokenDao->save($token);
|
$this->tokenDao->save($token);
|
||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ class UserService
|
||||||
private $emailService;
|
private $emailService;
|
||||||
private $fileService;
|
private $fileService;
|
||||||
private $timeService;
|
private $timeService;
|
||||||
|
private $tokenService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
\Szurubooru\Config $config,
|
\Szurubooru\Config $config,
|
||||||
|
@ -20,7 +21,8 @@ class UserService
|
||||||
\Szurubooru\Services\PasswordService $passwordService,
|
\Szurubooru\Services\PasswordService $passwordService,
|
||||||
\Szurubooru\Services\EmailService $emailService,
|
\Szurubooru\Services\EmailService $emailService,
|
||||||
\Szurubooru\Services\FileService $fileService,
|
\Szurubooru\Services\FileService $fileService,
|
||||||
\Szurubooru\Services\TimeService $timeService)
|
\Szurubooru\Services\TimeService $timeService,
|
||||||
|
\Szurubooru\Services\TokenService $tokenService)
|
||||||
{
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->validator = $validator;
|
$this->validator = $validator;
|
||||||
|
@ -30,6 +32,20 @@ class UserService
|
||||||
$this->emailService = $emailService;
|
$this->emailService = $emailService;
|
||||||
$this->fileService = $fileService;
|
$this->fileService = $fileService;
|
||||||
$this->timeService = $timeService;
|
$this->timeService = $timeService;
|
||||||
|
$this->tokenService = $tokenService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByNameOrEmail($userNameOrEmail, $allowUnconfirmed = false)
|
||||||
|
{
|
||||||
|
$user = $this->userDao->getByName($userNameOrEmail);
|
||||||
|
if ($user)
|
||||||
|
return $user;
|
||||||
|
|
||||||
|
$user = $this->userDao->getByEmail($userNameOrEmail, $allowUnconfirmed);
|
||||||
|
if ($user)
|
||||||
|
return $user;
|
||||||
|
|
||||||
|
throw new \InvalidArgumentException('User "' . $userNameOrEmail . '" was not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByName($userName)
|
public function getByName($userName)
|
||||||
|
@ -62,12 +78,15 @@ class UserService
|
||||||
$this->validator->validatePassword($formData->password);
|
$this->validator->validatePassword($formData->password);
|
||||||
$this->validator->validateEmail($formData->email);
|
$this->validator->validateEmail($formData->email);
|
||||||
|
|
||||||
|
if ($formData->email and $this->userDao->getByEmail($formData->email))
|
||||||
|
throw new \DomainException('User with this e-mail already exists.');
|
||||||
|
|
||||||
if ($this->userDao->getByName($formData->userName))
|
if ($this->userDao->getByName($formData->userName))
|
||||||
throw new \DomainException('User with this name already exists.');
|
throw new \DomainException('User with this name already exists.');
|
||||||
|
|
||||||
$user = new \Szurubooru\Entities\User();
|
$user = new \Szurubooru\Entities\User();
|
||||||
$user->name = $formData->userName;
|
$user->name = $formData->userName;
|
||||||
$user->email = $formData->email;
|
$user->emailUnconfirmed = $formData->email;
|
||||||
$user->passwordHash = $this->passwordService->getHash($formData->password);
|
$user->passwordHash = $this->passwordService->getHash($formData->password);
|
||||||
$user->accessRank = $this->userDao->hasAnyUsers()
|
$user->accessRank = $this->userDao->hasAnyUsers()
|
||||||
? \Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER
|
? \Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER
|
||||||
|
@ -76,15 +95,13 @@ class UserService
|
||||||
$user->lastLoginTime = null;
|
$user->lastLoginTime = null;
|
||||||
$user->avatarStyle = \Szurubooru\Entities\User::AVATAR_STYLE_GRAVATAR;
|
$user->avatarStyle = \Szurubooru\Entities\User::AVATAR_STYLE_GRAVATAR;
|
||||||
|
|
||||||
$this->sendActivationMailIfNeeded($user);
|
$this->sendActivationEmailIfNeeded($user);
|
||||||
|
|
||||||
return $this->userDao->save($user);
|
return $this->userDao->save($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateUser($userName, \Szurubooru\FormData\UserEditFormData $formData)
|
public function updateUser(\Szurubooru\Entities\User $user, \Szurubooru\FormData\UserEditFormData $formData)
|
||||||
{
|
{
|
||||||
$user = $this->getByName($userName);
|
|
||||||
|
|
||||||
if ($formData->avatarStyle !== null)
|
if ($formData->avatarStyle !== null)
|
||||||
{
|
{
|
||||||
$user->avatarStyle = \Szurubooru\Helpers\EnumHelper::avatarStyleFromString($formData->avatarStyle);
|
$user->avatarStyle = \Szurubooru\Helpers\EnumHelper::avatarStyleFromString($formData->avatarStyle);
|
||||||
|
@ -95,7 +112,8 @@ class UserService
|
||||||
if ($formData->userName !== null and $formData->userName != $user->name)
|
if ($formData->userName !== null and $formData->userName != $user->name)
|
||||||
{
|
{
|
||||||
$this->validator->validateUserName($formData->userName);
|
$this->validator->validateUserName($formData->userName);
|
||||||
if ($this->userDao->getByName($formData->userName))
|
$userWithThisEmail = $this->userDao->getByName($formData->userName);
|
||||||
|
if ($userWithThisEmail and $userWithThisEmail->id != $user->id)
|
||||||
throw new \DomainException('User with this name already exists.');
|
throw new \DomainException('User with this name already exists.');
|
||||||
|
|
||||||
$user->name = $formData->userName;
|
$user->name = $formData->userName;
|
||||||
|
@ -107,10 +125,13 @@ class UserService
|
||||||
$user->passwordHash = $this->passwordService->getHash($formData->password);
|
$user->passwordHash = $this->passwordService->getHash($formData->password);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($formData->email !== null)
|
if ($formData->email !== null and $formData->email != $user->email)
|
||||||
{
|
{
|
||||||
$this->validator->validateEmail($formData->email);
|
$this->validator->validateEmail($formData->email);
|
||||||
$user->email = $formData->email;
|
if ($this->userDao->getByEmail($formData->email))
|
||||||
|
throw new \DomainException('User with this e-mail already exists.');
|
||||||
|
|
||||||
|
$user->emailUnconfirmed = $formData->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($formData->accessRank !== null)
|
if ($formData->accessRank !== null)
|
||||||
|
@ -128,17 +149,15 @@ class UserService
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($formData->email !== null)
|
if ($formData->email !== null)
|
||||||
$this->sendActivationMailIfNeeded($user);
|
$this->sendActivationEmailIfNeeded($user);
|
||||||
|
|
||||||
return $this->userDao->save($user);
|
return $this->userDao->save($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteUserByName($userName)
|
public function deleteUser(\Szurubooru\Entities\User $user)
|
||||||
{
|
{
|
||||||
$user = $this->getByName($userName);
|
$this->userDao->deleteById($user->id);
|
||||||
$this->userDao->deleteByName($userName);
|
|
||||||
$this->fileService->delete($this->getCustomAvatarSourcePath($user));
|
$this->fileService->delete($this->getCustomAvatarSourcePath($user));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCustomAvatarSourcePath(\Szurubooru\Entities\User $user)
|
public function getCustomAvatarSourcePath(\Szurubooru\Entities\User $user)
|
||||||
|
@ -157,8 +176,70 @@ class UserService
|
||||||
$this->userDao->save($user);
|
$this->userDao->save($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sendActivationMailIfNeeded(\Szurubooru\Entities\User &$user)
|
public function sendPasswordResetEmail(\Szurubooru\Entities\User $user)
|
||||||
{
|
{
|
||||||
//todo
|
$token = $this->tokenService->createAndSaveToken($user->name, \Szurubooru\Entities\Token::PURPOSE_PASSWORD_RESET);
|
||||||
|
$this->emailService->sendPasswordResetEmail($user, $token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function finishPasswordReset($tokenName)
|
||||||
|
{
|
||||||
|
$token = $this->tokenService->getByName($tokenName);
|
||||||
|
if ($token->purpose != \Szurubooru\Entities\Token::PURPOSE_PASSWORD_RESET)
|
||||||
|
throw new \Exception('This token is not a password reset token.');
|
||||||
|
|
||||||
|
$user = $this->getByName($token->additionalData);
|
||||||
|
$newPassword = $this->passwordService->getRandomPassword();
|
||||||
|
$user->passwordHash = $this->passwordService->getHash($newPassword);
|
||||||
|
$this->userDao->save($user);
|
||||||
|
$this->tokenService->invalidateByName($token->name);
|
||||||
|
return $newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendActivationEmail(\Szurubooru\Entities\User $user)
|
||||||
|
{
|
||||||
|
$token = $this->tokenService->createAndSaveToken($user->name, \Szurubooru\Entities\Token::PURPOSE_ACTIVATE);
|
||||||
|
$this->emailService->sendActivationEmail($user, $token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function finishActivation($tokenName)
|
||||||
|
{
|
||||||
|
$token = $this->tokenService->getByName($tokenName);
|
||||||
|
if ($token->purpose != \Szurubooru\Entities\Token::PURPOSE_ACTIVATE)
|
||||||
|
throw new \Exception('This token is not an activation token.');
|
||||||
|
|
||||||
|
$user = $this->getByName($token->additionalData);
|
||||||
|
$this->confirmEmail($user);
|
||||||
|
$this->tokenService->invalidateByName($token->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendActivationEmailIfNeeded(\Szurubooru\Entities\User &$user)
|
||||||
|
{
|
||||||
|
if ($user->accessRank == \Szurubooru\Entities\User::ACCESS_RANK_ADMINISTRATOR or !$this->config->security->needEmailActivationToRegister)
|
||||||
|
{
|
||||||
|
$this->confirmEmail($user);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->sendActivationEmail($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function confirmEmail(\Szurubooru\Entities\User &$user)
|
||||||
|
{
|
||||||
|
//security issue:
|
||||||
|
//1. two users set their unconfirmed mail to godzilla@empire.gov
|
||||||
|
//2. activation mail is sent to both of them
|
||||||
|
//3. first user confirms, ok
|
||||||
|
//4. second user confirms, ok
|
||||||
|
//5. two users share the same mail --> problem.
|
||||||
|
//by checking here again for users with such mail, this problem is solved with first-come first-serve approach:
|
||||||
|
//whoever confirms e-mail first, wins.
|
||||||
|
if ($this->userDao->getByEmail($user->emailUnconfirmed))
|
||||||
|
throw new \DomainException('This e-mail was already confirmed by someone else in the meantime.');
|
||||||
|
|
||||||
|
$user->email = $user->emailUnconfirmed;
|
||||||
|
$user->emailUnconfirmed = null;
|
||||||
|
$this->userDao->save($user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace Szurubooru\Tests\Services;
|
||||||
|
|
||||||
class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
{
|
{
|
||||||
private $validatorMock;
|
private $configMock;
|
||||||
private $passwordServiceMock;
|
private $passwordServiceMock;
|
||||||
private $timeServiceMock;
|
private $timeServiceMock;
|
||||||
private $tokenServiceMock;
|
private $tokenServiceMock;
|
||||||
|
@ -11,7 +11,7 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->validatorMock = $this->mock(\Szurubooru\Validator::class);
|
$this->configMock = $this->mockConfig();
|
||||||
$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->tokenServiceMock = $this->mock(\Szurubooru\Services\TokenService::class);
|
$this->tokenServiceMock = $this->mock(\Szurubooru\Services\TokenService::class);
|
||||||
|
@ -20,12 +20,13 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
|
|
||||||
public function testInvalidPassword()
|
public function testInvalidPassword()
|
||||||
{
|
{
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', false);
|
||||||
$this->passwordServiceMock->method('getHash')->willReturn('unmatchingHash');
|
$this->passwordServiceMock->method('getHash')->willReturn('unmatchingHash');
|
||||||
|
|
||||||
$testUser = new \Szurubooru\Entities\User();
|
$testUser = new \Szurubooru\Entities\User();
|
||||||
$testUser->name = 'dummy';
|
$testUser->name = 'dummy';
|
||||||
$testUser->passwordHash = 'hash';
|
$testUser->passwordHash = 'hash';
|
||||||
$this->userServiceMock->expects($this->once())->method('getByName')->willReturn($testUser);
|
$this->userServiceMock->expects($this->once())->method('getByNameOrEmail')->willReturn($testUser);
|
||||||
|
|
||||||
$authService = $this->getAuthService();
|
$authService = $this->getAuthService();
|
||||||
$this->setExpectedException(\Exception::class, 'Specified password is invalid');
|
$this->setExpectedException(\Exception::class, 'Specified password is invalid');
|
||||||
|
@ -34,17 +35,19 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
|
|
||||||
public function testValidCredentials()
|
public function testValidCredentials()
|
||||||
{
|
{
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', false);
|
||||||
$this->passwordServiceMock->method('getHash')->willReturn('hash');
|
$this->passwordServiceMock->method('getHash')->willReturn('hash');
|
||||||
|
|
||||||
$testUser = new \Szurubooru\Entities\User();
|
$testUser = new \Szurubooru\Entities\User();
|
||||||
|
$testUser->id = 'an unusual database identifier';
|
||||||
$testUser->name = 'dummy';
|
$testUser->name = 'dummy';
|
||||||
$testUser->passwordHash = 'hash';
|
$testUser->passwordHash = 'hash';
|
||||||
$this->userServiceMock->expects($this->once())->method('getByName')->willReturn($testUser);
|
$this->userServiceMock->expects($this->once())->method('getByNameOrEmail')->willReturn($testUser);
|
||||||
|
|
||||||
$testToken = new \Szurubooru\Entities\Token();
|
$testToken = new \Szurubooru\Entities\Token();
|
||||||
$testToken->name = 'mummy';
|
$testToken->name = 'mummy';
|
||||||
$this->tokenServiceMock->expects($this->once())->method('createAndSaveToken')->with(
|
$this->tokenServiceMock->expects($this->once())->method('createAndSaveToken')->with(
|
||||||
$testUser,
|
$testUser->id,
|
||||||
\Szurubooru\Entities\Token::PURPOSE_LOGIN)->willReturn($testToken);
|
\Szurubooru\Entities\Token::PURPOSE_LOGIN)->willReturn($testToken);
|
||||||
|
|
||||||
$authService = $this->getAuthService();
|
$authService = $this->getAuthService();
|
||||||
|
@ -56,8 +59,28 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->assertEquals('mummy', $authService->getLoginToken()->name);
|
$this->assertEquals('mummy', $authService->getLoginToken()->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testValidCredentialsUnconfirmedEmail()
|
||||||
|
{
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', true);
|
||||||
|
$this->passwordServiceMock->method('getHash')->willReturn('hash');
|
||||||
|
|
||||||
|
$testUser = new \Szurubooru\Entities\User();
|
||||||
|
$testUser->name = 'dummy';
|
||||||
|
$testUser->passwordHash = 'hash';
|
||||||
|
$this->userServiceMock->expects($this->once())->method('getByNameOrEmail')->willReturn($testUser);
|
||||||
|
|
||||||
|
$this->setExpectedException(\Exception::class, 'User didn\'t confirm mail yet');
|
||||||
|
$authService = $this->getAuthService();
|
||||||
|
$authService->loginFromCredentials('dummy', 'godzilla');
|
||||||
|
|
||||||
|
$this->assertFalse($authService->isLoggedIn());
|
||||||
|
$this->assertNull($testUser, $authService->getLoggedInUser());
|
||||||
|
$this->assertNull($authService->getLoginToken());
|
||||||
|
}
|
||||||
|
|
||||||
public function testInvalidToken()
|
public function testInvalidToken()
|
||||||
{
|
{
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', false);
|
||||||
$this->tokenServiceMock->expects($this->once())->method('getByName')->willReturn(null);
|
$this->tokenServiceMock->expects($this->once())->method('getByName')->willReturn(null);
|
||||||
|
|
||||||
$this->setExpectedException(\Exception::class);
|
$this->setExpectedException(\Exception::class);
|
||||||
|
@ -67,6 +90,7 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
|
|
||||||
public function testValidToken()
|
public function testValidToken()
|
||||||
{
|
{
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', false);
|
||||||
$testUser = new \Szurubooru\Entities\User();
|
$testUser = new \Szurubooru\Entities\User();
|
||||||
$testUser->id = 5;
|
$testUser->id = 5;
|
||||||
$testUser->name = 'dummy';
|
$testUser->name = 'dummy';
|
||||||
|
@ -87,10 +111,51 @@ class AuthServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->assertEquals('dummy_token', $authService->getLoginToken()->name);
|
$this->assertEquals('dummy_token', $authService->getLoginToken()->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testValidTokenInvalidPurpose()
|
||||||
|
{
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', false);
|
||||||
|
$testToken = new \Szurubooru\Entities\Token();
|
||||||
|
$testToken->name = 'dummy_token';
|
||||||
|
$testToken->additionalData = 'whatever';
|
||||||
|
$testToken->purpose = null;
|
||||||
|
$this->tokenServiceMock->expects($this->once())->method('getByName')->willReturn($testToken);
|
||||||
|
|
||||||
|
$this->setExpectedException(\Exception::class, 'This token is not a login token');
|
||||||
|
$authService = $this->getAuthService();
|
||||||
|
$authService->loginFromToken($testToken->name);
|
||||||
|
|
||||||
|
$this->assertFalse($authService->isLoggedIn());
|
||||||
|
$this->assertNull($authService->getLoggedInUser());
|
||||||
|
$this->assertNull($authService->getLoginToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidTokenUnconfirmedEmail()
|
||||||
|
{
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', true);
|
||||||
|
$testUser = new \Szurubooru\Entities\User();
|
||||||
|
$testUser->id = 5;
|
||||||
|
$testUser->name = 'dummy';
|
||||||
|
$this->userServiceMock->expects($this->once())->method('getById')->willReturn($testUser);
|
||||||
|
|
||||||
|
$testToken = new \Szurubooru\Entities\Token();
|
||||||
|
$testToken->name = 'dummy_token';
|
||||||
|
$testToken->additionalData = $testUser->id;
|
||||||
|
$testToken->purpose = \Szurubooru\Entities\Token::PURPOSE_LOGIN;
|
||||||
|
$this->tokenServiceMock->expects($this->once())->method('getByName')->willReturn($testToken);
|
||||||
|
|
||||||
|
$this->setExpectedException(\Exception::class, 'User didn\'t confirm mail yet');
|
||||||
|
$authService = $this->getAuthService();
|
||||||
|
$authService->loginFromToken($testToken->name);
|
||||||
|
|
||||||
|
$this->assertFalse($authService->isLoggedIn());
|
||||||
|
$this->assertNull($testUser, $authService->getLoggedInUser());
|
||||||
|
$this->assertNull($authService->getLoginToken());
|
||||||
|
}
|
||||||
|
|
||||||
private function getAuthService()
|
private function getAuthService()
|
||||||
{
|
{
|
||||||
return new \Szurubooru\Services\AuthService(
|
return new \Szurubooru\Services\AuthService(
|
||||||
$this->validatorMock,
|
$this->configMock,
|
||||||
$this->passwordServiceMock,
|
$this->passwordServiceMock,
|
||||||
$this->timeServiceMock,
|
$this->timeServiceMock,
|
||||||
$this->tokenServiceMock,
|
$this->tokenServiceMock,
|
||||||
|
|
47
tests/Services/PasswordServiceTest.php
Normal file
47
tests/Services/PasswordServiceTest.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Tests\Service;
|
||||||
|
|
||||||
|
class PasswordServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testGeneratingPasswords()
|
||||||
|
{
|
||||||
|
$configMock = $this->mockConfig();
|
||||||
|
$passwordService = new \Szurubooru\Services\PasswordService($configMock);
|
||||||
|
|
||||||
|
$sampleCount = 10000;
|
||||||
|
$distribution = [];
|
||||||
|
for ($i = 0; $i < $sampleCount; $i ++)
|
||||||
|
{
|
||||||
|
$password = $passwordService->getRandomPassword();
|
||||||
|
for ($j = 0; $j < strlen($password); $j ++)
|
||||||
|
{
|
||||||
|
$c = $password{$j};
|
||||||
|
if (!isset($distribution[$j]))
|
||||||
|
$distribution[$j] = [$c => 1];
|
||||||
|
elseif (!isset($distribution[$j][$c]))
|
||||||
|
$distribution[$j][$c] = 1;
|
||||||
|
else
|
||||||
|
$distribution[$j][$c] ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($distribution as $index => $characterDistribution)
|
||||||
|
{
|
||||||
|
$this->assertLessThan(10, $this->getRelativeStandardDeviation($characterDistribution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getStandardDeviation($sample)
|
||||||
|
{
|
||||||
|
$mean = array_sum($sample) / count($sample);
|
||||||
|
foreach ($sample as $key => $num)
|
||||||
|
$devs[$key] = pow($num - $mean, 2);
|
||||||
|
return sqrt(array_sum($devs) / (count($devs)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRelativeStandardDeviation($sample)
|
||||||
|
{
|
||||||
|
$mean = array_sum($sample) / count($sample);
|
||||||
|
return 100 * $this->getStandardDeviation($sample) / $mean;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ class PrivilegeServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->assertTrue($privilegeService->hasPrivilege($privilege));
|
$this->assertTrue($privilegeService->hasPrivilege($privilege));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIsLoggedInByString()
|
public function testIsLoggedInByNameString()
|
||||||
{
|
{
|
||||||
$testUser1 = new \Szurubooru\Entities\User();
|
$testUser1 = new \Szurubooru\Entities\User();
|
||||||
$testUser1->name = 'dummy';
|
$testUser1->name = 'dummy';
|
||||||
|
@ -40,6 +40,21 @@ class PrivilegeServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->assertFalse($privilegeService->isLoggedIn($testUser2->name));
|
$this->assertFalse($privilegeService->isLoggedIn($testUser2->name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIsLoggedInByEmailString()
|
||||||
|
{
|
||||||
|
$testUser1 = new \Szurubooru\Entities\User();
|
||||||
|
$testUser1->name = 'user1';
|
||||||
|
$testUser1->email = 'dummy';
|
||||||
|
$testUser2 = new \Szurubooru\Entities\User();
|
||||||
|
$testUser2->name = 'user2';
|
||||||
|
$testUser2->email = 'godzilla';
|
||||||
|
$this->authServiceMock->method('getLoggedInUser')->willReturn($testUser1);
|
||||||
|
|
||||||
|
$privilegeService = $this->getPrivilegeService();
|
||||||
|
$this->assertTrue($privilegeService->isLoggedIn($testUser1->email));
|
||||||
|
$this->assertFalse($privilegeService->isLoggedIn($testUser2->email));
|
||||||
|
}
|
||||||
|
|
||||||
public function testIsLoggedInByUser()
|
public function testIsLoggedInByUser()
|
||||||
{
|
{
|
||||||
$testUser1 = new \Szurubooru\Entities\User();
|
$testUser1 = new \Szurubooru\Entities\User();
|
||||||
|
|
|
@ -11,6 +11,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
private $emailServiceMock;
|
private $emailServiceMock;
|
||||||
private $fileServiceMock;
|
private $fileServiceMock;
|
||||||
private $timeServiceMock;
|
private $timeServiceMock;
|
||||||
|
private $tokenServiceMock;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
|
@ -22,6 +23,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->emailServiceMock = $this->mock(\Szurubooru\Services\EmailService::class);
|
$this->emailServiceMock = $this->mock(\Szurubooru\Services\EmailService::class);
|
||||||
$this->fileServiceMock = $this->mock(\Szurubooru\Services\FileService::class);
|
$this->fileServiceMock = $this->mock(\Szurubooru\Services\FileService::class);
|
||||||
$this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class);
|
$this->timeServiceMock = $this->mock(\Szurubooru\Services\TimeService::class);
|
||||||
|
$this->tokenServiceMock = $this->mock(\Szurubooru\Services\TokenService::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGettingByName()
|
public function testGettingByName()
|
||||||
|
@ -78,23 +80,56 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->assertEquals($expected, $actual);
|
$this->assertEquals($expected, $actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testValidRegistration()
|
public function testValidRegistrationWithoutMailActivation()
|
||||||
{
|
{
|
||||||
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
||||||
$formData->userName = 'user';
|
$formData->userName = 'user';
|
||||||
$formData->password = 'password';
|
$formData->password = 'password';
|
||||||
$formData->email = 'email';
|
$formData->email = 'human@people.gov';
|
||||||
|
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', false);
|
||||||
$this->passwordServiceMock->method('getHash')->willReturn('hash');
|
$this->passwordServiceMock->method('getHash')->willReturn('hash');
|
||||||
$this->timeServiceMock->method('getCurrentTime')->willReturn('now');
|
$this->timeServiceMock->method('getCurrentTime')->willReturn('now');
|
||||||
$this->userDaoMock->method('hasAnyUsers')->willReturn(true);
|
$this->userDaoMock->method('hasAnyUsers')->willReturn(true);
|
||||||
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
||||||
|
$this->emailServiceMock->expects($this->never())->method('sendActivationEmail');
|
||||||
|
|
||||||
$userService = $this->getUserService();
|
$userService = $this->getUserService();
|
||||||
$savedUser = $userService->createUser($formData);
|
$savedUser = $userService->createUser($formData);
|
||||||
|
|
||||||
$this->assertEquals('user', $savedUser->name);
|
$this->assertEquals('user', $savedUser->name);
|
||||||
$this->assertEquals('email', $savedUser->email);
|
$this->assertEquals('human@people.gov', $savedUser->email);
|
||||||
|
$this->assertNull($savedUser->emailUnconfirmed);
|
||||||
|
$this->assertEquals('hash', $savedUser->passwordHash);
|
||||||
|
$this->assertEquals(\Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER, $savedUser->accessRank);
|
||||||
|
$this->assertEquals('now', $savedUser->registrationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidRegistrationWithMailActivation()
|
||||||
|
{
|
||||||
|
$formData = new \Szurubooru\FormData\RegistrationFormData;
|
||||||
|
$formData->userName = 'user';
|
||||||
|
$formData->password = 'password';
|
||||||
|
$formData->email = 'human@people.gov';
|
||||||
|
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', true);
|
||||||
|
$this->passwordServiceMock->method('getHash')->willReturn('hash');
|
||||||
|
$this->timeServiceMock->method('getCurrentTime')->willReturn('now');
|
||||||
|
$this->userDaoMock->method('hasAnyUsers')->willReturn(true);
|
||||||
|
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
||||||
|
|
||||||
|
$testToken = new \Szurubooru\Entities\Token();
|
||||||
|
$this->tokenServiceMock->expects($this->once())->method('createAndSaveToken')->willReturn($testToken);
|
||||||
|
$this->emailServiceMock->expects($this->once())->method('sendActivationEmail')->with(
|
||||||
|
$this->anything(),
|
||||||
|
$testToken);
|
||||||
|
|
||||||
|
$userService = $this->getUserService();
|
||||||
|
$savedUser = $userService->createUser($formData);
|
||||||
|
|
||||||
|
$this->assertEquals('user', $savedUser->name);
|
||||||
|
$this->assertNull($savedUser->email);
|
||||||
|
$this->assertEquals('human@people.gov', $savedUser->emailUnconfirmed);
|
||||||
$this->assertEquals('hash', $savedUser->passwordHash);
|
$this->assertEquals('hash', $savedUser->passwordHash);
|
||||||
$this->assertEquals(\Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER, $savedUser->accessRank);
|
$this->assertEquals(\Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER, $savedUser->accessRank);
|
||||||
$this->assertEquals('now', $savedUser->registrationTime);
|
$this->assertEquals('now', $savedUser->registrationTime);
|
||||||
|
@ -107,6 +142,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$formData->password = 'password';
|
$formData->password = 'password';
|
||||||
$formData->email = 'email';
|
$formData->email = 'email';
|
||||||
|
|
||||||
|
$this->configMock->set('security/needEmailActivationToRegister', false);
|
||||||
$this->userDaoMock->method('hasAnyUsers')->willReturn(false);
|
$this->userDaoMock->method('hasAnyUsers')->willReturn(false);
|
||||||
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
$this->userDaoMock->method('save')->will($this->returnArgument(0));
|
||||||
|
|
||||||
|
@ -143,6 +179,7 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase
|
||||||
$this->passwordServiceMock,
|
$this->passwordServiceMock,
|
||||||
$this->emailServiceMock,
|
$this->emailServiceMock,
|
||||||
$this->fileServiceMock,
|
$this->fileServiceMock,
|
||||||
$this->timeServiceMock);
|
$this->timeServiceMock,
|
||||||
|
$this->tokenServiceMock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue