server/users: allow rank+avatar when creating user
This commit is contained in:
parent
08271caf32
commit
e42cede27c
48
API.md
48
API.md
|
@ -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**
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue