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

View File

@ -54,9 +54,20 @@ class UserListApi(BaseApi):
name = ctx.get_param_as_string('name', 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)
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.commit()
return {'user': _serialize_user(ctx.user, user)}
@ -94,7 +105,8 @@ class UserDetailApi(BaseApi):
if ctx.has_param('rank'):
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'):
auth.verify_privilege(ctx.user, 'users:edit:%s:avatar' % infix)

View File

@ -1,8 +1,13 @@
import datetime
import pytest
from szurubooru import api, db, errors
from szurubooru import api, config, db, errors
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):
return session.query(db.User).filter_by(name=name).first()
@ -14,7 +19,7 @@ def test_ctx(
'user_name_regex': '.{3,}',
'password_regex': '.{3,}',
'default_rank': 'regular_user',
'thumbnails': {'avatar_width': 200},
'thumbnails': {'avatar_width': 200, 'avatar_height': 200},
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
'rank_names': {},
'privileges': {'users:create': 'anonymous'},
@ -63,7 +68,7 @@ def test_first_user_becomes_admin_others_not(test_ctx):
'email': 'asd@asd.asd',
'password': 'oks',
},
user=test_ctx.user_factory(rank='regular_user')))
user=test_ctx.user_factory(rank='anonymous')))
result2 = test_ctx.api.post(
test_ctx.context_factory(
input={
@ -71,7 +76,7 @@ def test_first_user_becomes_admin_others_not(test_ctx):
'email': 'asd@asd.asd',
'password': 'sok',
},
user=test_ctx.user_factory(rank='regular_user')))
user=test_ctx.user_factory(rank='anonymous')))
assert result1['user']['rank'] == 'admin'
assert result2['user']['rank'] == 'regular_user'
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 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):
test_ctx.api.post(
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')))
@pytest.mark.parametrize('field', ['name', 'email', 'password'])
def test_missing_field(test_ctx, field):
@pytest.mark.parametrize('field', ['name', 'password'])
def test_missing_mandatory_field(test_ctx, field):
input = {
'name': 'chewie',
'email': 'asd@asd.asd',
@ -121,6 +138,24 @@ def test_missing_field(test_ctx, field):
input=input,
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', [
{'name': '.'},
{'name': 'x' * 51},
@ -134,7 +169,55 @@ def test_invalid_inputs(test_ctx, input):
user = test_ctx.user_factory(name='u1', rank='admin')
test_ctx.session.add(user)
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.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.')
user.email = email
def update_rank(user, rank, authenticated_user):
def update_rank(session, user, rank, authenticated_user):
rank = rank.strip()
available_ranks = config.config['ranks']
if not rank in available_ranks:
raise InvalidRankError(
'Rank %r is invalid. Valid ranks: %r' % (rank, available_ranks))
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.')
user.rank = rank