server/users: allow rank+avatar when creating user

This commit is contained in:
rr- 2016-04-17 19:02:39 +02:00
parent 08271caf32
commit e42cede27c
4 changed files with 137 additions and 30 deletions

48
API.md
View File

@ -222,10 +222,10 @@ data.
```json5 ```json5
{ {
"names": [<name1>, <name2>, ...], "names": [<name1>, <name2>, ...], // optional
"category": <category>, "category": <category>, // optional
"implications": [<name1>, <name2>, ...], "implications": [<name1>, <name2>, ...], // optional
"suggestions": [<name1>, <name2>, ...] "suggestions": [<name1>, <name2>, ...] // optional
} }
``` ```
@ -384,10 +384,16 @@ data.
{ {
"name": <user-name>, "name": <user-name>,
"password": <user-password>, "password": <user-password>,
"email": <email> "email": <email>, // optional
"rank": <rank>, // optional
"avatarStyle": <avatar-style> // optional
} }
``` ```
- **Files**
- `avatar` - the content of the new avatar (optional).
- **Output** - **Output**
```json5 ```json5
@ -399,17 +405,23 @@ data.
- **Errors** - **Errors**
- such user already exists (names are case insensitive) - a user with such name already exists (names are case insensitive)
- either user name, password or email are invalid - either user name, password, email or rank are invalid
- the user is trying to update their or someone else's rank to higher than
their own
- avatar is missing for manual avatar style
- privileges are too low - privileges are too low
- **Description** - **Description**
Creates a new user using specified parameters. Names and passwords must Creates a new user using specified parameters. Names and passwords must
match `user_name_regex` and `password_regex` from server's configuration, match `user_name_regex` and `password_regex` from server's configuration,
respectively. Email address is optional. If the user happens to be the respectively. Email address, rank and avatar fields are optional. Avatar
first user ever created, they're granted highest available rank, becoming style can be either `gravatar` or `manual`. `manual` avatar style requires
an administrator. Subsequent users will be given the rank indicated by client to pass also `avatar` file - see [file uploads](#file-uploads) for
details. If the rank is empty and the user happens to be the first user
ever created, they're granted highest available rank, becoming an
administrator, whereas subsequent users will be given the rank indicated by
`default_rank` in the server's configuration. `default_rank` in the server's configuration.
@ -422,17 +434,17 @@ data.
```json5 ```json5
{ {
"name": <user-name>, "name": <user-name>, // optional
"password": <user-password>, "password": <user-password>, // optional
"email": <email>, "email": <email>, // optional
"rank": <rank>, "rank": <rank>, // optional
"avatarStyle": <avatar-style> "avatarStyle": <avatar-style> // optional
} }
``` ```
- **Files** - **Files**
- `avatar` - the content of the new avatar. - `avatar` - the content of the new avatar (optional).
- **Output** - **Output**
@ -446,12 +458,12 @@ data.
- **Errors** - **Errors**
- the user does not exist - the user does not exist
- the user with new name already exists (names are case insensitive) - a user with new name already exists (names are case insensitive)
- either user name, password, email or rank are invalid - either user name, password, email or rank are invalid
- the user is trying to update their or someone else's rank to higher than - the user is trying to update their or someone else's rank to higher than
their own their own
- privileges are too low
- avatar is missing for manual avatar style - avatar is missing for manual avatar style
- privileges are too low
- **Description** - **Description**

View File

@ -54,9 +54,20 @@ class UserListApi(BaseApi):
name = ctx.get_param_as_string('name', required=True) name = ctx.get_param_as_string('name', required=True)
password = ctx.get_param_as_string('password', required=True) password = ctx.get_param_as_string('password', required=True)
email = ctx.get_param_as_string('email', required=True) email = ctx.get_param_as_string('email', required=False, default='')
user = users.create_user(ctx.session, name, password, email, ctx.user) user = users.create_user(ctx.session, name, password, email, ctx.user)
if ctx.has_param('rank'):
users.update_rank(
ctx.session, user, ctx.get_param_as_string('rank'), ctx.user)
if ctx.has_param('avatarStyle'):
users.update_avatar(
user,
ctx.get_param_as_string('avatarStyle'),
ctx.get_file('avatar'))
ctx.session.add(user) ctx.session.add(user)
ctx.session.commit() ctx.session.commit()
return {'user': _serialize_user(ctx.user, user)} return {'user': _serialize_user(ctx.user, user)}
@ -94,7 +105,8 @@ class UserDetailApi(BaseApi):
if ctx.has_param('rank'): if ctx.has_param('rank'):
auth.verify_privilege(ctx.user, 'users:edit:%s:rank' % infix) auth.verify_privilege(ctx.user, 'users:edit:%s:rank' % infix)
users.update_rank(user, ctx.get_param_as_string('rank'), ctx.user) users.update_rank(
ctx.session, user, ctx.get_param_as_string('rank'), ctx.user)
if ctx.has_param('avatarStyle'): if ctx.has_param('avatarStyle'):
auth.verify_privilege(ctx.user, 'users:edit:%s:avatar' % infix) auth.verify_privilege(ctx.user, 'users:edit:%s:avatar' % infix)

View File

@ -1,8 +1,13 @@
import datetime import datetime
import pytest import pytest
from szurubooru import api, db, errors from szurubooru import api, config, db, errors
from szurubooru.util import auth, misc, users from szurubooru.util import auth, misc, users
EMPTY_PIXEL = \
b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00' \
b'\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x01\x00\x2c\x00\x00\x00\x00' \
b'\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b'
def get_user(session, name): def get_user(session, name):
return session.query(db.User).filter_by(name=name).first() return session.query(db.User).filter_by(name=name).first()
@ -14,7 +19,7 @@ def test_ctx(
'user_name_regex': '.{3,}', 'user_name_regex': '.{3,}',
'password_regex': '.{3,}', 'password_regex': '.{3,}',
'default_rank': 'regular_user', 'default_rank': 'regular_user',
'thumbnails': {'avatar_width': 200}, 'thumbnails': {'avatar_width': 200, 'avatar_height': 200},
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'], 'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
'rank_names': {}, 'rank_names': {},
'privileges': {'users:create': 'anonymous'}, 'privileges': {'users:create': 'anonymous'},
@ -63,7 +68,7 @@ def test_first_user_becomes_admin_others_not(test_ctx):
'email': 'asd@asd.asd', 'email': 'asd@asd.asd',
'password': 'oks', 'password': 'oks',
}, },
user=test_ctx.user_factory(rank='regular_user'))) user=test_ctx.user_factory(rank='anonymous')))
result2 = test_ctx.api.post( result2 = test_ctx.api.post(
test_ctx.context_factory( test_ctx.context_factory(
input={ input={
@ -71,7 +76,7 @@ def test_first_user_becomes_admin_others_not(test_ctx):
'email': 'asd@asd.asd', 'email': 'asd@asd.asd',
'password': 'sok', 'password': 'sok',
}, },
user=test_ctx.user_factory(rank='regular_user'))) user=test_ctx.user_factory(rank='anonymous')))
assert result1['user']['rank'] == 'admin' assert result1['user']['rank'] == 'admin'
assert result2['user']['rank'] == 'regular_user' assert result2['user']['rank'] == 'regular_user'
first_user = get_user(test_ctx.session, 'chewie1') first_user = get_user(test_ctx.session, 'chewie1')
@ -79,6 +84,18 @@ def test_first_user_becomes_admin_others_not(test_ctx):
assert first_user.rank == 'admin' assert first_user.rank == 'admin'
assert other_user.rank == 'regular_user' assert other_user.rank == 'regular_user'
def test_first_user_does_not_become_admin_if_they_dont_wish_so(test_ctx):
result = test_ctx.api.post(
test_ctx.context_factory(
input={
'name': 'chewie1',
'email': 'asd@asd.asd',
'password': 'oks',
'rank': 'regular_user',
},
user=test_ctx.user_factory(rank='anonymous')))
assert result['user']['rank'] == 'regular_user'
def test_creating_user_that_already_exists(test_ctx): def test_creating_user_that_already_exists(test_ctx):
test_ctx.api.post( test_ctx.api.post(
test_ctx.context_factory( test_ctx.context_factory(
@ -107,8 +124,8 @@ def test_creating_user_that_already_exists(test_ctx):
}, },
user=test_ctx.user_factory(rank='regular_user'))) user=test_ctx.user_factory(rank='regular_user')))
@pytest.mark.parametrize('field', ['name', 'email', 'password']) @pytest.mark.parametrize('field', ['name', 'password'])
def test_missing_field(test_ctx, field): def test_missing_mandatory_field(test_ctx, field):
input = { input = {
'name': 'chewie', 'name': 'chewie',
'email': 'asd@asd.asd', 'email': 'asd@asd.asd',
@ -121,6 +138,24 @@ def test_missing_field(test_ctx, field):
input=input, input=input,
user=test_ctx.user_factory(rank='regular_user'))) user=test_ctx.user_factory(rank='regular_user')))
@pytest.mark.parametrize('field', ['rank', 'email', 'avatarStyle'])
def test_missing_optional_field(test_ctx, tmpdir, field):
config.config['data_dir'] = str(tmpdir.mkdir('data'))
config.config['data_url'] = 'http://example.com/data/'
input = {
'name': 'chewie',
'email': 'asd@asd.asd',
'password': 'oks',
'rank': 'mod',
'avatarStyle': 'manual',
}
del input[field]
test_ctx.api.post(
test_ctx.context_factory(
input=input,
files={'avatar': EMPTY_PIXEL},
user=test_ctx.user_factory(rank='mod')))
@pytest.mark.parametrize('input', [ @pytest.mark.parametrize('input', [
{'name': '.'}, {'name': '.'},
{'name': 'x' * 51}, {'name': 'x' * 51},
@ -134,7 +169,55 @@ def test_invalid_inputs(test_ctx, input):
user = test_ctx.user_factory(name='u1', rank='admin') user = test_ctx.user_factory(name='u1', rank='admin')
test_ctx.session.add(user) test_ctx.session.add(user)
with pytest.raises(errors.ValidationError): with pytest.raises(errors.ValidationError):
real_input={
'name': 'chewie',
'email': 'asd@asd.asd',
'password': 'oks',
}
for key, value in input.items():
real_input[key] = value
test_ctx.api.post( test_ctx.api.post(
test_ctx.context_factory(input=input, user=user)) test_ctx.context_factory(input=real_input, user=user))
# TODO: support avatar and avatarStyle def test_mods_trying_to_become_admin(test_ctx):
user1 = test_ctx.user_factory(name='u1', rank='mod')
user2 = test_ctx.user_factory(name='u2', rank='mod')
test_ctx.session.add_all([user1, user2])
context = test_ctx.context_factory(input={
'name': 'chewie',
'email': 'asd@asd.asd',
'password': 'oks',
'rank': 'admin',
}, user=user1)
with pytest.raises(errors.AuthError):
test_ctx.api.post(context)
def test_admin_creating_mod_account(test_ctx):
user = test_ctx.user_factory(rank='admin')
test_ctx.session.add(user)
context = test_ctx.context_factory(input={
'name': 'chewie',
'email': 'asd@asd.asd',
'password': 'oks',
'rank': 'mod',
}, user=user)
result = test_ctx.api.post(context)
assert result['user']['rank'] == 'mod'
def test_uploading_avatar(test_ctx, tmpdir):
config.config['data_dir'] = str(tmpdir.mkdir('data'))
config.config['data_url'] = 'http://example.com/data/'
response = test_ctx.api.post(
test_ctx.context_factory(
input={
'name': 'chewie',
'email': 'asd@asd.asd',
'password': 'oks',
'avatarStyle': 'manual',
},
files={'avatar': EMPTY_PIXEL},
user=test_ctx.user_factory(rank='mod')))
user = get_user(test_ctx.session, 'chewie')
assert user.avatar_style == user.AVATAR_MANUAL
assert response['user']['avatarUrl'] == \
'http://example.com/data/avatars/chewie.jpg'

View File

@ -66,14 +66,14 @@ def update_email(user, email):
raise InvalidEmailError('E-mail is invalid.') raise InvalidEmailError('E-mail is invalid.')
user.email = email user.email = email
def update_rank(user, rank, authenticated_user): def update_rank(session, user, rank, authenticated_user):
rank = rank.strip() rank = rank.strip()
available_ranks = config.config['ranks'] available_ranks = config.config['ranks']
if not rank in available_ranks: if not rank in available_ranks:
raise InvalidRankError( raise InvalidRankError(
'Rank %r is invalid. Valid ranks: %r' % (rank, available_ranks)) 'Rank %r is invalid. Valid ranks: %r' % (rank, available_ranks))
if available_ranks.index(authenticated_user.rank) \ if available_ranks.index(authenticated_user.rank) \
< available_ranks.index(rank): < available_ranks.index(rank) and session.query(db.User).count() > 0:
raise errors.AuthError('Trying to set higher rank than your own.') raise errors.AuthError('Trying to set higher rank than your own.')
user.rank = rank user.rank = rank