client+server: finish password reminders
This commit is contained in:
parent
1fb2f53914
commit
92dd958866
14 changed files with 246 additions and 56 deletions
|
@ -20,7 +20,7 @@
|
||||||
<fieldset class='messages'></fieldset>
|
<fieldset class='messages'></fieldset>
|
||||||
<fieldset class='buttons'>
|
<fieldset class='buttons'>
|
||||||
<input type='submit' value='Log in'/>
|
<input type='submit' value='Log in'/>
|
||||||
<a>Forgot the password?</a>
|
<a href='/password-reset'>Forgot the password?</a>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
20
client/html/password_reset.hbs
Normal file
20
client/html/password_reset.hbs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<div class='content-wrapper' id='password-reset'>
|
||||||
|
<h1>Password reset</h1>
|
||||||
|
<form autocomplete='off'>
|
||||||
|
<fieldset class='input'>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label for='user-name'>User name or e-mail address</label>
|
||||||
|
<input id='user-name' name='user-name' type='text' autocomplete='off' required/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</fieldset>
|
||||||
|
<p><small>Proceeding will send an e-mail that contains a password reset
|
||||||
|
link. Clicking it is going to generate a new password for your account.
|
||||||
|
It is recommended to change that password to something else.</small></p>
|
||||||
|
<fieldset class='messages'></fieldset>
|
||||||
|
<fieldset class='buttons'>
|
||||||
|
<input type='submit' value='Proceed'/>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -5,10 +5,12 @@ const page = require('page');
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const topNavController = require('../controllers/top_nav_controller.js');
|
const topNavController = require('../controllers/top_nav_controller.js');
|
||||||
const LoginView = require('../views/login_view.js');
|
const LoginView = require('../views/login_view.js');
|
||||||
|
const PasswordResetView = require('../views/password_reset_view.js');
|
||||||
|
|
||||||
class AuthController {
|
class AuthController {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.loginView = new LoginView();
|
this.loginView = new LoginView();
|
||||||
|
this.passwordResetView = new PasswordResetView();
|
||||||
|
|
||||||
const auth = cookies.getJSON('auth');
|
const auth = cookies.getJSON('auth');
|
||||||
if (auth && auth.user && auth.password) {
|
if (auth && auth.user && auth.password) {
|
||||||
|
@ -51,6 +53,43 @@ class AuthController {
|
||||||
page('/');
|
page('/');
|
||||||
this.loginView.notifySuccess('Logged out');
|
this.loginView.notifySuccess('Logged out');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
passwordResetRoute() {
|
||||||
|
topNavController.activate('login');
|
||||||
|
this.passwordResetView.render({
|
||||||
|
proceed: nameOrEmail => {
|
||||||
|
api.logout();
|
||||||
|
cookies.remove('auth');
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
api.get('/password-reset/' + nameOrEmail)
|
||||||
|
.then(() => { resolve(); })
|
||||||
|
.catch(errorMessage => { reject(errorMessage); });
|
||||||
|
});
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordResetFinishRoute(name, token) {
|
||||||
|
api.logout();
|
||||||
|
cookies.remove('auth');
|
||||||
|
api.post('/password-reset/' + name, {token: token})
|
||||||
|
.then(response => {
|
||||||
|
const password = response.password;
|
||||||
|
api.login(name, password)
|
||||||
|
.then(() => {
|
||||||
|
cookies.set(
|
||||||
|
'auth', {'user': name, 'password': password}, {});
|
||||||
|
page('/');
|
||||||
|
this.passwordResetView.notifySuccess(
|
||||||
|
'New password: ' + password);
|
||||||
|
}).catch(errorMessage => {
|
||||||
|
page('/');
|
||||||
|
this.passwordResetView.notifyError(errorMessage);
|
||||||
|
});
|
||||||
|
}).catch(response => {
|
||||||
|
page('/');
|
||||||
|
this.passwordResetView.notifyError(response.description);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new AuthController();
|
module.exports = new AuthController();
|
||||||
|
|
|
@ -44,6 +44,11 @@ page(
|
||||||
page('/history', () => { historyController.showHistoryRoute(); });
|
page('/history', () => { historyController.showHistoryRoute(); });
|
||||||
page('/tags', () => { tagsController.listTagsRoute(); });
|
page('/tags', () => { tagsController.listTagsRoute(); });
|
||||||
page('/comments', () => { commentsController.listCommentsRoute(); });
|
page('/comments', () => { commentsController.listCommentsRoute(); });
|
||||||
|
page(/\/password-reset\/([^:]+):([^:]+)$/,
|
||||||
|
(ctx, next) => {
|
||||||
|
authController.passwordResetFinishRoute(ctx.params[0], ctx.params[1]);
|
||||||
|
});
|
||||||
|
page('/password-reset', () => { authController.passwordResetRoute(); });
|
||||||
page('/login', () => { authController.loginRoute(); });
|
page('/login', () => { authController.loginRoute(); });
|
||||||
page('/logout', () => { authController.logoutRoute(); });
|
page('/logout', () => { authController.logoutRoute(); });
|
||||||
|
|
||||||
|
|
37
client/js/views/password_reset_view.js
Normal file
37
client/js/views/password_reset_view.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const BaseView = require('./base_view.js');
|
||||||
|
|
||||||
|
class PasswordResetView extends BaseView {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.template = this.getTemplate('password-reset-template');
|
||||||
|
}
|
||||||
|
|
||||||
|
render(options) {
|
||||||
|
this.showView(this.template());
|
||||||
|
const form = this.contentHolder.querySelector('form');
|
||||||
|
this.decorateValidator(form);
|
||||||
|
|
||||||
|
const userNameOrEmailField = document.getElementById('user-name');
|
||||||
|
|
||||||
|
form.addEventListener('submit', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.clearMessages();
|
||||||
|
this.disableForm(form);
|
||||||
|
options
|
||||||
|
.proceed(userNameOrEmailField.value)
|
||||||
|
.then(() => {
|
||||||
|
this.notifySuccess(
|
||||||
|
'E-mail has been sent. To finish the procedure, ' +
|
||||||
|
'please click the link it contains.');
|
||||||
|
})
|
||||||
|
.catch(errorMessage => {
|
||||||
|
this.enableForm(form);
|
||||||
|
this.notifyError(errorMessage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PasswordResetView;
|
|
@ -1,4 +1,4 @@
|
||||||
''' Falcon-compatible API facades. '''
|
''' Falcon-compatible API facades. '''
|
||||||
|
|
||||||
from szurubooru.api.password_reminder_api import PasswordReminderApi
|
from szurubooru.api.password_reset_api import PasswordResetApi
|
||||||
from szurubooru.api.user_api import UserListApi, UserDetailApi
|
from szurubooru.api.user_api import UserListApi, UserDetailApi
|
||||||
|
|
|
@ -8,18 +8,18 @@ MAIL_BODY = \
|
||||||
'If you wish to proceed, click this link: {url}\n' \
|
'If you wish to proceed, click this link: {url}\n' \
|
||||||
'Otherwise, please ignore this email.'
|
'Otherwise, please ignore this email.'
|
||||||
|
|
||||||
class PasswordReminderApi(BaseApi):
|
class PasswordResetApi(BaseApi):
|
||||||
def get(self, context, user_name):
|
def get(self, context, user_name):
|
||||||
''' Send a mail with secure token to the correlated user. '''
|
''' Send a mail with secure token to the correlated user. '''
|
||||||
user = users.get_by_name(context.session, user_name)
|
user = users.get_by_name_or_email(context.session, user_name)
|
||||||
if not user:
|
if not user:
|
||||||
raise errors.NotFoundError('User %r not found.' % user_name)
|
raise errors.NotFoundError('User %r not found.' % user_name)
|
||||||
if not user.email:
|
if not user.email:
|
||||||
raise errors.ValidationError(
|
raise errors.ValidationError(
|
||||||
'User %r hasn\'t supplied email. Cannot reset password.' % user_name)
|
'User %r hasn\'t supplied email. Cannot reset password.' % user_name)
|
||||||
token = auth.generate_authentication_token(user)
|
token = auth.generate_authentication_token(user)
|
||||||
url = '%s/password-reset/%s' % (
|
url = '%s/password-reset/%s:%s' % (
|
||||||
config.config['basic']['base_url'].rstrip('/'), token)
|
config.config['basic']['base_url'].rstrip('/'), user.name, token)
|
||||||
mailer.send_mail(
|
mailer.send_mail(
|
||||||
'noreply@%s' % config.config['basic']['name'],
|
'noreply@%s' % config.config['basic']['name'],
|
||||||
user.email,
|
user.email,
|
||||||
|
@ -29,7 +29,7 @@ class PasswordReminderApi(BaseApi):
|
||||||
|
|
||||||
def post(self, context, user_name):
|
def post(self, context, user_name):
|
||||||
''' Verify token from mail, generate a new password and return it. '''
|
''' Verify token from mail, generate a new password and return it. '''
|
||||||
user = users.get_by_name(context.session, user_name)
|
user = users.get_by_name_or_email(context.session, user_name)
|
||||||
if not user:
|
if not user:
|
||||||
raise errors.NotFoundError('User %r not found.' % user_name)
|
raise errors.NotFoundError('User %r not found.' % user_name)
|
||||||
good_token = auth.generate_authentication_token(user)
|
good_token = auth.generate_authentication_token(user)
|
|
@ -1,4 +1,3 @@
|
||||||
import sqlalchemy
|
|
||||||
from szurubooru import errors, search
|
from szurubooru import errors, search
|
||||||
from szurubooru.util import auth, users
|
from szurubooru.util import auth, users
|
||||||
from szurubooru.api.base_api import BaseApi
|
from szurubooru.api.base_api import BaseApi
|
||||||
|
|
|
@ -64,7 +64,7 @@ def create_app():
|
||||||
|
|
||||||
user_list_api = api.UserListApi()
|
user_list_api = api.UserListApi()
|
||||||
user_detail_api = api.UserDetailApi()
|
user_detail_api = api.UserDetailApi()
|
||||||
password_reminder_api = api.PasswordReminderApi()
|
password_reset_api = api.PasswordResetApi()
|
||||||
|
|
||||||
app.add_error_handler(errors.AuthError, _on_auth_error)
|
app.add_error_handler(errors.AuthError, _on_auth_error)
|
||||||
app.add_error_handler(errors.IntegrityError, _on_integrity_error)
|
app.add_error_handler(errors.IntegrityError, _on_integrity_error)
|
||||||
|
@ -74,6 +74,6 @@ def create_app():
|
||||||
|
|
||||||
app.add_route('/users/', user_list_api)
|
app.add_route('/users/', user_list_api)
|
||||||
app.add_route('/user/{user_name}', user_detail_api)
|
app.add_route('/user/{user_name}', user_detail_api)
|
||||||
app.add_route('/password_reminder/{user_name}', password_reminder_api)
|
app.add_route('/password-reset/{user_name}', password_reset_api)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
77
server/szurubooru/tests/api/test_password_reset_api.py
Normal file
77
server/szurubooru/tests/api/test_password_reset_api.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from unittest import mock
|
||||||
|
from szurubooru import api, db, errors, config
|
||||||
|
from szurubooru.util import auth, misc, mailer
|
||||||
|
from szurubooru.tests.database_test_case import DatabaseTestCase
|
||||||
|
from szurubooru.tests.api import util
|
||||||
|
|
||||||
|
class TestPasswordReset(DatabaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
config_mock = {
|
||||||
|
'basic': {
|
||||||
|
'secret': 'x',
|
||||||
|
'base_url': 'http://example.com/',
|
||||||
|
'name': 'Test instance',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.old_config = config.config
|
||||||
|
config.config = config_mock
|
||||||
|
self.context = misc.dotdict()
|
||||||
|
self.context.session = self.session
|
||||||
|
self.context.request = {}
|
||||||
|
self.context.user = db.User()
|
||||||
|
self.api = api.PasswordResetApi()
|
||||||
|
|
||||||
|
def test_reset_non_existing(self):
|
||||||
|
self.assertRaises(errors.NotFoundError, self.api.get, self.context, 'u1')
|
||||||
|
|
||||||
|
def test_reset_without_email(self):
|
||||||
|
user = util.mock_user('u1', 'regular_user')
|
||||||
|
user.email = None
|
||||||
|
self.session.add(user)
|
||||||
|
self.assertRaises(errors.ValidationError, self.api.get, self.context, 'u1')
|
||||||
|
|
||||||
|
def test_reset_sending_email(self):
|
||||||
|
user = util.mock_user('u1', 'regular_user')
|
||||||
|
user.email = 'user@example.com'
|
||||||
|
self.session.add(user)
|
||||||
|
for getter in ['u1', 'user@example.com']:
|
||||||
|
with mock.MagicMock() as mock_method:
|
||||||
|
mailer.send_mail = mock_method
|
||||||
|
self.assertEqual({}, self.api.get(self.context, getter))
|
||||||
|
mock_method.assert_called_once_with(
|
||||||
|
'noreply@Test instance',
|
||||||
|
'user@example.com',
|
||||||
|
'Password reset for Test instance',
|
||||||
|
'You (or someone else) requested to reset your password ' +
|
||||||
|
'on Test instance.\nIf you wish to proceed, click this l' +
|
||||||
|
'ink: http://example.com/password-reset/u1:4ac0be176fb36' +
|
||||||
|
'4f13ee6b634c43220e2\nOtherwise, please ignore this email.')
|
||||||
|
|
||||||
|
def test_confirmation_non_existing(self):
|
||||||
|
self.assertRaises(errors.NotFoundError, self.api.post, self.context, 'u1')
|
||||||
|
|
||||||
|
def test_confirmation_no_token(self):
|
||||||
|
user = util.mock_user('u1', 'regular_user')
|
||||||
|
user.email = 'user@example.com'
|
||||||
|
self.session.add(user)
|
||||||
|
self.context.request = {}
|
||||||
|
self.assertRaises(errors.ValidationError, self.api.post, self.context, 'u1')
|
||||||
|
|
||||||
|
def test_confirmation_bad_token(self):
|
||||||
|
user = util.mock_user('u1', 'regular_user')
|
||||||
|
user.email = 'user@example.com'
|
||||||
|
self.session.add(user)
|
||||||
|
self.context.request = {'token': 'bad'}
|
||||||
|
self.assertRaises(errors.ValidationError, self.api.post, self.context, 'u1')
|
||||||
|
|
||||||
|
def test_confirmation_good_token(self):
|
||||||
|
user = util.mock_user('u1', 'regular_user')
|
||||||
|
user.email = 'user@example.com'
|
||||||
|
old_hash = user.password_hash
|
||||||
|
self.session.add(user)
|
||||||
|
self.context.request = {'token': '4ac0be176fb364f13ee6b634c43220e2'}
|
||||||
|
result = self.api.post(self.context, 'u1')
|
||||||
|
self.assertNotEqual(user.password_hash, old_hash)
|
||||||
|
self.assertTrue(auth.is_valid_password(user, result['password']))
|
|
@ -2,30 +2,7 @@ from datetime import datetime
|
||||||
from szurubooru import api, db, errors, config
|
from szurubooru import api, db, errors, config
|
||||||
from szurubooru.util import auth, misc
|
from szurubooru.util import auth, misc
|
||||||
from szurubooru.tests.database_test_case import DatabaseTestCase
|
from szurubooru.tests.database_test_case import DatabaseTestCase
|
||||||
|
from szurubooru.tests.api import util
|
||||||
def _create_user(name, rank='admin'):
|
|
||||||
user = db.User()
|
|
||||||
user.name = name
|
|
||||||
user.password = 'dummy'
|
|
||||||
user.password_salt = 'dummy'
|
|
||||||
user.password_hash = 'dummy'
|
|
||||||
user.email = 'dummy'
|
|
||||||
user.access_rank = rank
|
|
||||||
user.creation_time = datetime(1997, 1, 1)
|
|
||||||
user.avatar_style = db.User.AVATAR_GRAVATAR
|
|
||||||
return user
|
|
||||||
|
|
||||||
def _mock_params(context, params):
|
|
||||||
def get_param_as_string(key, default=None):
|
|
||||||
if key not in params:
|
|
||||||
return default
|
|
||||||
return params[key]
|
|
||||||
def get_param_as_int(key, default=None):
|
|
||||||
if key not in params:
|
|
||||||
return default
|
|
||||||
return int(params[key])
|
|
||||||
context.get_param_as_string = get_param_as_string
|
|
||||||
context.get_param_as_int = get_param_as_int
|
|
||||||
|
|
||||||
class TestRetrievingUsers(DatabaseTestCase):
|
class TestRetrievingUsers(DatabaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -48,10 +25,10 @@ class TestRetrievingUsers(DatabaseTestCase):
|
||||||
self.context.user = db.User()
|
self.context.user = db.User()
|
||||||
|
|
||||||
def test_retrieving_multiple(self):
|
def test_retrieving_multiple(self):
|
||||||
user1 = _create_user('u1', 'mod')
|
user1 = util.mock_user('u1', 'mod')
|
||||||
user2 = _create_user('u2', 'mod')
|
user2 = util.mock_user('u2', 'mod')
|
||||||
self.session.add_all([user1, user2])
|
self.session.add_all([user1, user2])
|
||||||
_mock_params(self.context, {'query': '', 'page': 1})
|
util.mock_params(self.context, {'query': '', 'page': 1})
|
||||||
self.context.user.access_rank = 'regular_user'
|
self.context.user.access_rank = 'regular_user'
|
||||||
api_ = api.UserListApi()
|
api_ = api.UserListApi()
|
||||||
result = api_.get(self.context)
|
result = api_.get(self.context)
|
||||||
|
@ -63,15 +40,15 @@ class TestRetrievingUsers(DatabaseTestCase):
|
||||||
|
|
||||||
def test_retrieving_multiple_without_privileges(self):
|
def test_retrieving_multiple_without_privileges(self):
|
||||||
self.context.user.access_rank = 'anonymous'
|
self.context.user.access_rank = 'anonymous'
|
||||||
_mock_params(self.context, {'query': '', 'page': 1})
|
util.mock_params(self.context, {'query': '', 'page': 1})
|
||||||
api_ = api.UserListApi()
|
api_ = api.UserListApi()
|
||||||
self.assertRaises(errors.AuthError, api_.get, self.context)
|
self.assertRaises(errors.AuthError, api_.get, self.context)
|
||||||
|
|
||||||
def test_retrieving_single(self):
|
def test_retrieving_single(self):
|
||||||
user = _create_user('u1', 'regular_user')
|
user = util.mock_user('u1', 'regular_user')
|
||||||
self.session.add(user)
|
self.session.add(user)
|
||||||
self.context.user.access_rank = 'regular_user'
|
self.context.user.access_rank = 'regular_user'
|
||||||
_mock_params(self.context, {'query': '', 'page': 1})
|
util.mock_params(self.context, {'query': '', 'page': 1})
|
||||||
api_ = api.UserDetailApi()
|
api_ = api.UserDetailApi()
|
||||||
result = api_.get(self.context, 'u1')
|
result = api_.get(self.context, 'u1')
|
||||||
self.assertEqual(result['user']['id'], user.user_id)
|
self.assertEqual(result['user']['id'], user.user_id)
|
||||||
|
@ -83,13 +60,13 @@ class TestRetrievingUsers(DatabaseTestCase):
|
||||||
|
|
||||||
def test_retrieving_non_existing(self):
|
def test_retrieving_non_existing(self):
|
||||||
self.context.user.access_rank = 'regular_user'
|
self.context.user.access_rank = 'regular_user'
|
||||||
_mock_params(self.context, {'query': '', 'page': 1})
|
util.mock_params(self.context, {'query': '', 'page': 1})
|
||||||
api_ = api.UserDetailApi()
|
api_ = api.UserDetailApi()
|
||||||
self.assertRaises(errors.NotFoundError, api_.get, self.context, '-')
|
self.assertRaises(errors.NotFoundError, api_.get, self.context, '-')
|
||||||
|
|
||||||
def test_retrieving_single_without_privileges(self):
|
def test_retrieving_single_without_privileges(self):
|
||||||
self.context.user.access_rank = 'anonymous'
|
self.context.user.access_rank = 'anonymous'
|
||||||
_mock_params(self.context, {'query': '', 'page': 1})
|
util.mock_params(self.context, {'query': '', 'page': 1})
|
||||||
api_ = api.UserDetailApi()
|
api_ = api.UserDetailApi()
|
||||||
self.assertRaises(errors.AuthError, api_.get, self.context, '-')
|
self.assertRaises(errors.AuthError, api_.get, self.context, '-')
|
||||||
|
|
||||||
|
@ -200,7 +177,7 @@ class TestUpdatingUser(DatabaseTestCase):
|
||||||
config.config = self.old_config
|
config.config = self.old_config
|
||||||
|
|
||||||
def test_update_changing_nothing(self):
|
def test_update_changing_nothing(self):
|
||||||
admin_user = _create_user('u1', 'admin')
|
admin_user = util.mock_user('u1', 'admin')
|
||||||
self.session.add(admin_user)
|
self.session.add(admin_user)
|
||||||
self.context.user = admin_user
|
self.context.user = admin_user
|
||||||
self.api.put(self.context, 'u1')
|
self.api.put(self.context, 'u1')
|
||||||
|
@ -210,13 +187,13 @@ class TestUpdatingUser(DatabaseTestCase):
|
||||||
self.assertEqual(admin_user.access_rank, 'admin')
|
self.assertEqual(admin_user.access_rank, 'admin')
|
||||||
|
|
||||||
def test_updating_non_existing_user(self):
|
def test_updating_non_existing_user(self):
|
||||||
admin_user = _create_user('u1', 'admin')
|
admin_user = util.mock_user('u1', 'admin')
|
||||||
self.session.add(admin_user)
|
self.session.add(admin_user)
|
||||||
self.context.user = admin_user
|
self.context.user = admin_user
|
||||||
self.assertRaises(errors.NotFoundError, self.api.put, self.context, 'u2')
|
self.assertRaises(errors.NotFoundError, self.api.put, self.context, 'u2')
|
||||||
|
|
||||||
def test_admin_updating_everything_for_themselves(self):
|
def test_admin_updating_everything_for_themselves(self):
|
||||||
admin_user = _create_user('u1', 'admin')
|
admin_user = util.mock_user('u1', 'admin')
|
||||||
self.session.add(admin_user)
|
self.session.add(admin_user)
|
||||||
self.context.user = admin_user
|
self.context.user = admin_user
|
||||||
self.context.request = {
|
self.context.request = {
|
||||||
|
@ -234,7 +211,7 @@ class TestUpdatingUser(DatabaseTestCase):
|
||||||
self.assertFalse(auth.is_valid_password(admin_user, 'invalid'))
|
self.assertFalse(auth.is_valid_password(admin_user, 'invalid'))
|
||||||
|
|
||||||
def test_removing_email(self):
|
def test_removing_email(self):
|
||||||
admin_user = _create_user('u1', 'admin')
|
admin_user = util.mock_user('u1', 'admin')
|
||||||
self.session.add(admin_user)
|
self.session.add(admin_user)
|
||||||
self.context.user = admin_user
|
self.context.user = admin_user
|
||||||
self.context.request = {'email': ''}
|
self.context.request = {'email': ''}
|
||||||
|
@ -243,7 +220,7 @@ class TestUpdatingUser(DatabaseTestCase):
|
||||||
self.assertEqual(admin_user.email, None)
|
self.assertEqual(admin_user.email, None)
|
||||||
|
|
||||||
def test_invalid_inputs(self):
|
def test_invalid_inputs(self):
|
||||||
admin_user = _create_user('u1', 'admin')
|
admin_user = util.mock_user('u1', 'admin')
|
||||||
self.session.add(admin_user)
|
self.session.add(admin_user)
|
||||||
self.context.user = admin_user
|
self.context.user = admin_user
|
||||||
self.context.request = {'name': '.'}
|
self.context.request = {'name': '.'}
|
||||||
|
@ -260,8 +237,8 @@ class TestUpdatingUser(DatabaseTestCase):
|
||||||
errors.ValidationError, self.api.put, self.context, 'u1')
|
errors.ValidationError, self.api.put, self.context, 'u1')
|
||||||
|
|
||||||
def test_user_trying_to_update_someone_else(self):
|
def test_user_trying_to_update_someone_else(self):
|
||||||
user1 = _create_user('u1', 'regular_user')
|
user1 = util.mock_user('u1', 'regular_user')
|
||||||
user2 = _create_user('u2', 'regular_user')
|
user2 = util.mock_user('u2', 'regular_user')
|
||||||
self.session.add_all([user1, user2])
|
self.session.add_all([user1, user2])
|
||||||
self.context.user = user1
|
self.context.user = user1
|
||||||
for request in [
|
for request in [
|
||||||
|
@ -274,26 +251,28 @@ class TestUpdatingUser(DatabaseTestCase):
|
||||||
errors.AuthError, self.api.put, self.context, user2.name)
|
errors.AuthError, self.api.put, self.context, user2.name)
|
||||||
|
|
||||||
def test_user_trying_to_become_someone_else(self):
|
def test_user_trying_to_become_someone_else(self):
|
||||||
user1 = _create_user('me', 'regular_user')
|
user1 = util.mock_user('me', 'regular_user')
|
||||||
user2 = _create_user('her', 'regular_user')
|
user2 = util.mock_user('her', 'regular_user')
|
||||||
self.session.add_all([user1, user2])
|
self.session.add_all([user1, user2])
|
||||||
self.context.user = user1
|
self.context.user = user1
|
||||||
self.context.request = {'name': 'her'}
|
self.context.request = {'name': 'her'}
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
errors.IntegrityError, self.api.put, self.context, 'me')
|
errors.IntegrityError, self.api.put, self.context, 'me')
|
||||||
|
self.session.rollback()
|
||||||
|
|
||||||
def test_user_trying_to_become_someone_else_insensitive(self):
|
def test_user_trying_to_become_someone_else_insensitive(self):
|
||||||
user1 = _create_user('me', 'regular_user')
|
user1 = util.mock_user('me', 'regular_user')
|
||||||
user2 = _create_user('her', 'regular_user')
|
user2 = util.mock_user('her', 'regular_user')
|
||||||
self.session.add_all([user1, user2])
|
self.session.add_all([user1, user2])
|
||||||
self.context.user = user1
|
self.context.user = user1
|
||||||
self.context.request = {'name': 'HER'}
|
self.context.request = {'name': 'HER'}
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
errors.IntegrityError, self.api.put, self.context, 'me')
|
errors.IntegrityError, self.api.put, self.context, 'me')
|
||||||
|
self.session.rollback()
|
||||||
|
|
||||||
def test_mods_trying_to_become_admin(self):
|
def test_mods_trying_to_become_admin(self):
|
||||||
user1 = _create_user('u1', 'mod')
|
user1 = util.mock_user('u1', 'mod')
|
||||||
user2 = _create_user('u2', 'mod')
|
user2 = util.mock_user('u2', 'mod')
|
||||||
self.session.add_all([user1, user2])
|
self.session.add_all([user1, user2])
|
||||||
self.context.user = user1
|
self.context.user = user1
|
||||||
self.context.request = {'accessRank': 'admin'}
|
self.context.request = {'accessRank': 'admin'}
|
||||||
|
|
26
server/szurubooru/tests/api/util.py
Normal file
26
server/szurubooru/tests/api/util.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from szurubooru import db
|
||||||
|
|
||||||
|
def mock_user(name, rank='admin'):
|
||||||
|
user = db.User()
|
||||||
|
user.name = name
|
||||||
|
user.password = 'dummy'
|
||||||
|
user.password_salt = 'dummy'
|
||||||
|
user.password_hash = 'dummy'
|
||||||
|
user.email = 'dummy'
|
||||||
|
user.access_rank = rank
|
||||||
|
user.creation_time = datetime(1997, 1, 1)
|
||||||
|
user.avatar_style = db.User.AVATAR_GRAVATAR
|
||||||
|
return user
|
||||||
|
|
||||||
|
def mock_params(context, params):
|
||||||
|
def get_param_as_string(key, default=None):
|
||||||
|
if key not in params:
|
||||||
|
return default
|
||||||
|
return params[key]
|
||||||
|
def get_param_as_int(key, default=None):
|
||||||
|
if key not in params:
|
||||||
|
return default
|
||||||
|
return int(params[key])
|
||||||
|
context.get_param_as_string = get_param_as_string
|
||||||
|
context.get_param_as_int = get_param_as_int
|
|
@ -53,7 +53,7 @@ def verify_privilege(user, privilege_name):
|
||||||
|
|
||||||
def generate_authentication_token(user):
|
def generate_authentication_token(user):
|
||||||
''' Generate nonguessable challenge (e.g. links in password reminder). '''
|
''' Generate nonguessable challenge (e.g. links in password reminder). '''
|
||||||
digest = hashlib.sha256()
|
digest = hashlib.md5()
|
||||||
digest.update(config.config['basic']['secret'].encode('utf8'))
|
digest.update(config.config['basic']['secret'].encode('utf8'))
|
||||||
digest.update(user.password_salt.encode('utf8'))
|
digest.update(user.password_salt.encode('utf8'))
|
||||||
return digest.hexdigest()
|
return digest.hexdigest()
|
||||||
|
|
|
@ -68,3 +68,11 @@ def get_by_name(session, name):
|
||||||
return session.query(db.User) \
|
return session.query(db.User) \
|
||||||
.filter(func.lower(db.User.name) == func.lower(name)) \
|
.filter(func.lower(db.User.name) == func.lower(name)) \
|
||||||
.first()
|
.first()
|
||||||
|
|
||||||
|
def get_by_name_or_email(session, name_or_email):
|
||||||
|
''' Retrieve an user by its name or email. '''
|
||||||
|
return session.query(db.User) \
|
||||||
|
.filter(
|
||||||
|
(func.lower(db.User.name) == func.lower(name_or_email))
|
||||||
|
| (func.lower(db.User.email) == func.lower(name_or_email))) \
|
||||||
|
.first()
|
||||||
|
|
Loading…
Reference in a new issue