server/api: improve input sanitization, fix docs
This commit is contained in:
parent
7263849fac
commit
83784c5e76
11
API.md
11
API.md
|
@ -14,7 +14,7 @@
|
|||
2. [API reference](#api-reference)
|
||||
|
||||
- Tag categories
|
||||
- [Listing tag categories](#listing-tags-category)
|
||||
- [Listing tag categories](#listing-tags-categories)
|
||||
- [Creating tag category](#creating-tag-category)
|
||||
- [Updating tag category](#updating-tag-category)
|
||||
- [Getting tag category](#getting-tag-category)
|
||||
|
@ -245,7 +245,8 @@ Not implemented yet.
|
|||
are optional. If specified implied tags or suggested tags do not exist yet,
|
||||
they will be automatically created. Tags created automatically have no
|
||||
implications, no suggestions, one name and their category is set to the
|
||||
first item of `tag_categories` from server's configuration.
|
||||
first tag category found. If there are no tag categories established yet,
|
||||
an error will be thrown.
|
||||
|
||||
|
||||
## Updating tag
|
||||
|
@ -275,10 +276,10 @@ Not implemented yet.
|
|||
|
||||
- **Errors**
|
||||
|
||||
- the tag does not exist
|
||||
- any name is used by an existing tag (names are case insensitive)
|
||||
- any name, implication or suggestion has invalid name
|
||||
- any name, implication or suggestion name is invalid
|
||||
- category is invalid
|
||||
- no name was specified
|
||||
- implications or suggestions contain any item from names (e.g. there's a
|
||||
shallow cyclic dependency)
|
||||
- privileges are too low
|
||||
|
@ -338,7 +339,7 @@ Not implemented yet.
|
|||
|
||||
- **Description**
|
||||
|
||||
Deletes existing tag.
|
||||
Deletes existing tag. The tag to be deleted must have no usages.
|
||||
|
||||
|
||||
## Listing users
|
||||
|
|
|
@ -70,6 +70,34 @@ def test_creating_simple_tags(test_ctx, fake_datetime):
|
|||
assert_relations(tag.implications, [])
|
||||
assert os.path.exists(os.path.join(config.config['data_dir'], 'tags.json'))
|
||||
|
||||
@pytest.mark.parametrize('input,expected_exception', [
|
||||
({'names': None}, tags.InvalidTagNameError),
|
||||
({'names': []}, tags.InvalidTagNameError),
|
||||
({'names': [None]}, tags.InvalidTagNameError),
|
||||
({'names': ['']}, tags.InvalidTagNameError),
|
||||
({'names': ['!bad']}, tags.InvalidTagNameError),
|
||||
({'names': ['x' * 65]}, tags.InvalidTagNameError),
|
||||
({'category': None}, tags.InvalidTagCategoryError),
|
||||
({'category': ''}, tags.InvalidTagCategoryError),
|
||||
({'category': 'invalid'}, tags.InvalidTagCategoryError),
|
||||
({'suggestions': ['good', '!bad']}, tags.InvalidTagNameError),
|
||||
({'implications': ['good', '!bad']}, tags.InvalidTagNameError),
|
||||
])
|
||||
def test_invalid_inputs(test_ctx, input, expected_exception):
|
||||
real_input={
|
||||
'names': ['tag1', 'tag2'],
|
||||
'category': 'meta',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
}
|
||||
for key, value in input.items():
|
||||
real_input[key] = value
|
||||
with pytest.raises(expected_exception):
|
||||
test_ctx.api.post(
|
||||
test_ctx.context_factory(
|
||||
input=real_input,
|
||||
user=test_ctx.user_factory()))
|
||||
|
||||
@pytest.mark.parametrize('field', ['names', 'category'])
|
||||
def test_missing_mandatory_field(test_ctx, field):
|
||||
input = {
|
||||
|
@ -94,10 +122,11 @@ def test_missing_optional_field(test_ctx, tmpdir, field):
|
|||
'implications': [],
|
||||
}
|
||||
del input[field]
|
||||
test_ctx.api.post(
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(
|
||||
input=input,
|
||||
user=test_ctx.user_factory(rank='regular_user')))
|
||||
assert result is not None
|
||||
|
||||
def test_duplicating_names(test_ctx):
|
||||
result = test_ctx.api.post(
|
||||
|
@ -114,33 +143,6 @@ def test_duplicating_names(test_ctx):
|
|||
tag = get_tag(test_ctx.session, 'tag1')
|
||||
assert [tag_name.name for tag_name in tag.names] == ['tag1']
|
||||
|
||||
def test_trying_to_create_tag_without_names(test_ctx):
|
||||
with pytest.raises(tags.InvalidTagNameError):
|
||||
test_ctx.api.post(
|
||||
test_ctx.context_factory(
|
||||
input={
|
||||
'names': [],
|
||||
'category': 'meta',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
},
|
||||
user=test_ctx.user_factory(rank='regular_user')))
|
||||
|
||||
@pytest.mark.parametrize('names', [['!'], ['x' * 65]])
|
||||
def test_trying_to_create_tag_with_invalid_name(test_ctx, names):
|
||||
with pytest.raises(tags.InvalidTagNameError):
|
||||
test_ctx.api.post(
|
||||
test_ctx.context_factory(
|
||||
input={
|
||||
'names': names,
|
||||
'category': 'meta',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
},
|
||||
user=test_ctx.user_factory(rank='regular_user')))
|
||||
assert get_tag(test_ctx.session, 'ok') is None
|
||||
assert get_tag(test_ctx.session, '!') is None
|
||||
|
||||
def test_trying_to_use_existing_name(test_ctx):
|
||||
test_ctx.session.add_all([
|
||||
test_ctx.tag_factory(names=['used1'], category_name='meta'),
|
||||
|
@ -169,19 +171,6 @@ def test_trying_to_use_existing_name(test_ctx):
|
|||
user=test_ctx.user_factory(rank='regular_user')))
|
||||
assert get_tag(test_ctx.session, 'unused') is None
|
||||
|
||||
def test_trying_to_create_tag_with_invalid_category(test_ctx):
|
||||
with pytest.raises(tags.InvalidTagCategoryError):
|
||||
test_ctx.api.post(
|
||||
test_ctx.context_factory(
|
||||
input={
|
||||
'names': ['ok'],
|
||||
'category': 'invalid',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
},
|
||||
user=test_ctx.user_factory(rank='regular_user')))
|
||||
assert get_tag(test_ctx.session, 'ok') is None
|
||||
|
||||
@pytest.mark.parametrize('input,expected_suggestions,expected_implications', [
|
||||
# new relations
|
||||
({
|
||||
|
@ -285,7 +274,7 @@ def test_trying_to_create_tag_with_invalid_relation(test_ctx, input):
|
|||
}
|
||||
])
|
||||
def test_tag_trying_to_relate_to_itself(test_ctx, input):
|
||||
with pytest.raises(tags.RelationError):
|
||||
with pytest.raises(tags.InvalidTagRelationError):
|
||||
test_ctx.api.post(
|
||||
test_ctx.context_factory(
|
||||
input=input,
|
||||
|
|
|
@ -27,7 +27,7 @@ def test_ctx(
|
|||
ret.api = api.TagDetailApi()
|
||||
return ret
|
||||
|
||||
def test_deleting_tags(test_ctx):
|
||||
def test_deleting(test_ctx):
|
||||
test_ctx.session.add(test_ctx.tag_factory(names=['tag']))
|
||||
test_ctx.session.commit()
|
||||
result = test_ctx.api.delete(
|
||||
|
@ -38,7 +38,7 @@ def test_deleting_tags(test_ctx):
|
|||
assert test_ctx.session.query(db.Tag).count() == 0
|
||||
assert os.path.exists(os.path.join(config.config['data_dir'], 'tags.json'))
|
||||
|
||||
def test_deleting_tags_without_privileges(test_ctx):
|
||||
def test_deleting_without_privileges(test_ctx):
|
||||
test_ctx.session.add(test_ctx.tag_factory(names=['tag']))
|
||||
test_ctx.session.commit()
|
||||
with pytest.raises(errors.AuthError):
|
||||
|
@ -48,7 +48,7 @@ def test_deleting_tags_without_privileges(test_ctx):
|
|||
'tag')
|
||||
assert test_ctx.session.query(db.Tag).count() == 1
|
||||
|
||||
def test_deleting_tags_with_usages(test_ctx, post_factory):
|
||||
def test_deleting_with_usages(test_ctx, post_factory):
|
||||
tag = test_ctx.tag_factory(names=['tag'])
|
||||
post = post_factory()
|
||||
post.tags.append(tag)
|
||||
|
|
|
@ -20,9 +20,31 @@ def test_ctx(
|
|||
ret.context_factory = context_factory
|
||||
ret.user_factory = user_factory
|
||||
ret.tag_factory = tag_factory
|
||||
ret.list_api = api.TagListApi()
|
||||
ret.detail_api = api.TagDetailApi()
|
||||
return ret
|
||||
|
||||
def test_retrieving_multiple(test_ctx):
|
||||
tag1 = test_ctx.tag_factory(names=['t1'])
|
||||
tag2 = test_ctx.tag_factory(names=['t2'])
|
||||
test_ctx.session.add_all([tag1, tag2])
|
||||
result = test_ctx.list_api.get(
|
||||
test_ctx.context_factory(
|
||||
input={'query': '', 'page': 1},
|
||||
user=test_ctx.user_factory(rank='regular_user')))
|
||||
assert result['query'] == ''
|
||||
assert result['page'] == 1
|
||||
assert result['pageSize'] == 100
|
||||
assert result['total'] == 2
|
||||
assert [t['names'] for t in result['tags']] == [['t1'], ['t2']]
|
||||
|
||||
def test_retrieving_multiple_without_privileges(test_ctx):
|
||||
with pytest.raises(errors.AuthError):
|
||||
test_ctx.list_api.get(
|
||||
test_ctx.context_factory(
|
||||
input={'query': '', 'page': 1},
|
||||
user=test_ctx.user_factory(rank='anonymous')))
|
||||
|
||||
def test_retrieving_single(test_ctx):
|
||||
test_ctx.session.add(test_ctx.tag_factory(names=['tag']))
|
||||
result = test_ctx.detail_api.get(
|
||||
|
|
|
@ -78,6 +78,50 @@ def test_simple_updating(test_ctx, fake_datetime):
|
|||
assert tag.implications == []
|
||||
assert os.path.exists(os.path.join(config.config['data_dir'], 'tags.json'))
|
||||
|
||||
@pytest.mark.parametrize('input,expected_exception', [
|
||||
({'names': None}, tags.InvalidTagNameError),
|
||||
({'names': []}, tags.InvalidTagNameError),
|
||||
({'names': [None]}, tags.InvalidTagNameError),
|
||||
({'names': ['']}, tags.InvalidTagNameError),
|
||||
({'names': ['!bad']}, tags.InvalidTagNameError),
|
||||
({'names': ['x' * 65]}, tags.InvalidTagNameError),
|
||||
({'category': None}, tags.InvalidTagCategoryError),
|
||||
({'category': ''}, tags.InvalidTagCategoryError),
|
||||
({'category': 'invalid'}, tags.InvalidTagCategoryError),
|
||||
({'suggestions': ['good', '!bad']}, tags.InvalidTagNameError),
|
||||
({'implications': ['good', '!bad']}, tags.InvalidTagNameError),
|
||||
])
|
||||
def test_invalid_inputs(test_ctx, input, expected_exception):
|
||||
test_ctx.session.add(
|
||||
test_ctx.tag_factory(names=['tag1'], category_name='meta'))
|
||||
test_ctx.session.commit()
|
||||
with pytest.raises(expected_exception):
|
||||
test_ctx.api.put(
|
||||
test_ctx.context_factory(
|
||||
input=input,
|
||||
user=test_ctx.user_factory(rank='regular_user')),
|
||||
'tag1')
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'field', ['names', 'category', 'implications', 'suggestions'])
|
||||
def test_missing_optional_field(test_ctx, tmpdir, field):
|
||||
test_ctx.session.add(
|
||||
test_ctx.tag_factory(names=['tag'], category_name='meta'))
|
||||
test_ctx.session.commit()
|
||||
input = {
|
||||
'names': ['tag1', 'tag2'],
|
||||
'category': 'meta',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
}
|
||||
del input[field]
|
||||
result = test_ctx.api.put(
|
||||
test_ctx.context_factory(
|
||||
input=input,
|
||||
user=test_ctx.user_factory(rank='regular_user')),
|
||||
'tag')
|
||||
assert result is not None
|
||||
|
||||
def test_trying_to_update_non_existing_tag(test_ctx):
|
||||
with pytest.raises(tags.TagNotFoundError):
|
||||
test_ctx.api.put(
|
||||
|
@ -118,22 +162,6 @@ def test_duplicating_names(test_ctx):
|
|||
assert tag is not None
|
||||
assert [tag_name.name for tag_name in tag.names] == ['tag3']
|
||||
|
||||
@pytest.mark.parametrize('input', [
|
||||
{'names': []},
|
||||
{'names': ['!']},
|
||||
{'names': ['x' * 65]},
|
||||
])
|
||||
def test_trying_to_set_invalid_name(test_ctx, input):
|
||||
test_ctx.session.add(
|
||||
test_ctx.tag_factory(names=['tag1'], category_name='meta'))
|
||||
test_ctx.session.commit()
|
||||
with pytest.raises(tags.InvalidTagNameError):
|
||||
test_ctx.api.put(
|
||||
test_ctx.context_factory(
|
||||
input=input,
|
||||
user=test_ctx.user_factory(rank='regular_user')),
|
||||
'tag1')
|
||||
|
||||
@pytest.mark.parametrize('dup_name', ['tag1', 'TAG1', 'tag2', 'TAG2'])
|
||||
def test_trying_to_use_existing_name(test_ctx, dup_name):
|
||||
test_ctx.session.add_all([
|
||||
|
@ -147,22 +175,6 @@ def test_trying_to_use_existing_name(test_ctx, dup_name):
|
|||
user=test_ctx.user_factory(rank='regular_user')),
|
||||
'tag3')
|
||||
|
||||
def test_trying_to_update_tag_with_invalid_category(test_ctx):
|
||||
test_ctx.session.add(
|
||||
test_ctx.tag_factory(names=['tag1'], category_name='meta'))
|
||||
test_ctx.session.commit()
|
||||
with pytest.raises(tags.InvalidTagCategoryError):
|
||||
test_ctx.api.put(
|
||||
test_ctx.context_factory(
|
||||
input={
|
||||
'names': ['ok'],
|
||||
'category': 'invalid',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
},
|
||||
user=test_ctx.user_factory(rank='regular_user')),
|
||||
'tag1')
|
||||
|
||||
@pytest.mark.parametrize('input,expected_suggestions,expected_implications', [
|
||||
# new relations
|
||||
({
|
||||
|
@ -226,25 +238,6 @@ def test_reusing_suggestions_and_implications(test_ctx):
|
|||
assert_relations(tag.suggestions, ['tag1'])
|
||||
assert_relations(tag.implications, ['tag1'])
|
||||
|
||||
@pytest.mark.parametrize('input', [
|
||||
{'names': ['ok'], 'suggestions': ['ok2', '!']},
|
||||
{'names': ['ok'], 'implications': ['ok2', '!']},
|
||||
])
|
||||
def test_trying_to_update_tag_with_invalid_relation(test_ctx, input):
|
||||
test_ctx.session.add(
|
||||
test_ctx.tag_factory(names=['tag'], category_name='meta'))
|
||||
test_ctx.session.commit()
|
||||
with pytest.raises(tags.InvalidTagNameError):
|
||||
test_ctx.api.put(
|
||||
test_ctx.context_factory(
|
||||
input=input, user=test_ctx.user_factory(rank='regular_user')),
|
||||
'tag')
|
||||
test_ctx.session.rollback()
|
||||
assert get_tag(test_ctx.session, 'tag') is not None
|
||||
assert get_tag(test_ctx.session, '!') is None
|
||||
assert get_tag(test_ctx.session, 'ok') is None
|
||||
assert get_tag(test_ctx.session, 'ok2') is None
|
||||
|
||||
@pytest.mark.parametrize('input', [
|
||||
{
|
||||
'names': ['tag1'],
|
||||
|
@ -263,7 +256,7 @@ def test_tag_trying_to_relate_to_itself(test_ctx, input):
|
|||
test_ctx.session.add(
|
||||
test_ctx.tag_factory(names=['tag1'], category_name='meta'))
|
||||
test_ctx.session.commit()
|
||||
with pytest.raises(tags.RelationError):
|
||||
with pytest.raises(tags.InvalidTagRelationError):
|
||||
test_ctx.api.put(
|
||||
test_ctx.context_factory(
|
||||
input=input, user=test_ctx.user_factory(rank='regular_user')),
|
||||
|
|
|
@ -16,8 +16,8 @@ def test_ctx(
|
|||
session, config_injector, context_factory, user_factory):
|
||||
config_injector({
|
||||
'secret': '',
|
||||
'user_name_regex': '.{3,}',
|
||||
'password_regex': '.{3,}',
|
||||
'user_name_regex': '[^!]{3,}',
|
||||
'password_regex': '[^!]{3,}',
|
||||
'default_rank': 'regular_user',
|
||||
'thumbnails': {'avatar_width': 200, 'avatar_height': 200},
|
||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||
|
@ -150,34 +150,44 @@ def test_missing_optional_field(test_ctx, tmpdir, field):
|
|||
'avatarStyle': 'manual',
|
||||
}
|
||||
del input[field]
|
||||
test_ctx.api.post(
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(
|
||||
input=input,
|
||||
files={'avatar': EMPTY_PIXEL},
|
||||
user=test_ctx.user_factory(rank='mod')))
|
||||
assert result is not None
|
||||
|
||||
@pytest.mark.parametrize('input', [
|
||||
{'name': '.'},
|
||||
{'name': 'x' * 51},
|
||||
{'password': '.'},
|
||||
{'rank': '.'},
|
||||
{'email': '.'},
|
||||
{'email': 'x' * 65},
|
||||
{'avatarStyle': 'manual'},
|
||||
@pytest.mark.parametrize('input,expected_exception', [
|
||||
({'name': None}, users.InvalidUserNameError),
|
||||
({'name': ''}, users.InvalidUserNameError),
|
||||
({'name': '!bad'}, users.InvalidUserNameError),
|
||||
({'name': 'x' * 51}, users.InvalidUserNameError),
|
||||
({'password': None}, users.InvalidPasswordError),
|
||||
({'password': ''}, users.InvalidPasswordError),
|
||||
({'password': '!bad'}, users.InvalidPasswordError),
|
||||
({'rank': None}, users.InvalidRankError),
|
||||
({'rank': ''}, users.InvalidRankError),
|
||||
({'rank': 'bad'}, users.InvalidRankError),
|
||||
({'email': 'bad'}, users.InvalidEmailError),
|
||||
({'email': 'x@' * 65 + '.com'}, users.InvalidEmailError),
|
||||
({'avatarStyle': None}, users.InvalidAvatarError),
|
||||
({'avatarStyle': ''}, users.InvalidAvatarError),
|
||||
({'avatarStyle': 'invalid'}, users.InvalidAvatarError),
|
||||
({'avatarStyle': 'manual'}, users.InvalidAvatarError), # missing file
|
||||
])
|
||||
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
|
||||
def test_invalid_inputs(test_ctx, input, expected_exception):
|
||||
real_input={
|
||||
'name': 'chewie',
|
||||
'email': 'asd@asd.asd',
|
||||
'password': 'oks',
|
||||
}
|
||||
for key, value in input.items():
|
||||
real_input[key] = value
|
||||
with pytest.raises(expected_exception):
|
||||
test_ctx.api.post(
|
||||
test_ctx.context_factory(input=real_input, user=user))
|
||||
test_ctx.context_factory(
|
||||
input=real_input,
|
||||
user=test_ctx.user_factory(name='u1', rank='admin')))
|
||||
|
||||
def test_mods_trying_to_become_admin(test_ctx):
|
||||
user1 = test_ctx.user_factory(name='u1', rank='mod')
|
||||
|
|
|
@ -3,6 +3,11 @@ import pytest
|
|||
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()
|
||||
|
||||
|
@ -11,8 +16,8 @@ def test_ctx(
|
|||
session, config_injector, context_factory, user_factory):
|
||||
config_injector({
|
||||
'secret': '',
|
||||
'user_name_regex': '.{3,}',
|
||||
'password_regex': '.{3,}',
|
||||
'user_name_regex': '^[^!]{3,}$',
|
||||
'password_regex': '^[^!]{3,}$',
|
||||
'thumbnails': {'avatar_width': 200, 'avatar_height': 200},
|
||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||
'rank_names': {},
|
||||
|
@ -71,6 +76,29 @@ def test_updating_user(test_ctx):
|
|||
assert auth.is_valid_password(user, 'oks') is True
|
||||
assert auth.is_valid_password(user, 'invalid') is False
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'field', ['name', 'email', 'password', 'rank', '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/'
|
||||
user = test_ctx.user_factory(name='u1', rank='admin')
|
||||
test_ctx.session.add(user)
|
||||
input = {
|
||||
'name': 'chewie',
|
||||
'email': 'asd@asd.asd',
|
||||
'password': 'oks',
|
||||
'rank': 'mod',
|
||||
'avatarStyle': 'gravatar',
|
||||
}
|
||||
del input[field]
|
||||
result = test_ctx.api.put(
|
||||
test_ctx.context_factory(
|
||||
input=input,
|
||||
files={'avatar': EMPTY_PIXEL},
|
||||
user=user),
|
||||
'u1')
|
||||
assert result is not None
|
||||
|
||||
def test_update_changing_nothing(test_ctx):
|
||||
user = test_ctx.user_factory(name='u1', rank='admin')
|
||||
test_ctx.session.add(user)
|
||||
|
@ -93,19 +121,28 @@ def test_removing_email(test_ctx):
|
|||
test_ctx.context_factory(input={'email': ''}, user=user), 'u1')
|
||||
assert get_user(test_ctx.session, 'u1').email is None
|
||||
|
||||
@pytest.mark.parametrize('input', [
|
||||
{'name': '.'},
|
||||
{'name': 'x' * 51},
|
||||
{'password': '.'},
|
||||
{'rank': '.'},
|
||||
{'email': '.'},
|
||||
{'email': 'x' * 65},
|
||||
{'avatarStyle': 'manual'},
|
||||
@pytest.mark.parametrize('input,expected_exception', [
|
||||
({'name': None}, users.InvalidUserNameError),
|
||||
({'name': ''}, users.InvalidUserNameError),
|
||||
({'name': '!bad'}, users.InvalidUserNameError),
|
||||
({'name': 'x' * 51}, users.InvalidUserNameError),
|
||||
({'password': None}, users.InvalidPasswordError),
|
||||
({'password': ''}, users.InvalidPasswordError),
|
||||
({'password': '!bad'}, users.InvalidPasswordError),
|
||||
({'rank': None}, users.InvalidRankError),
|
||||
({'rank': ''}, users.InvalidRankError),
|
||||
({'rank': 'bad'}, users.InvalidRankError),
|
||||
({'email': 'bad'}, users.InvalidEmailError),
|
||||
({'email': 'x@' * 65 + '.com'}, users.InvalidEmailError),
|
||||
({'avatarStyle': None}, users.InvalidAvatarError),
|
||||
({'avatarStyle': ''}, users.InvalidAvatarError),
|
||||
({'avatarStyle': 'invalid'}, users.InvalidAvatarError),
|
||||
({'avatarStyle': 'manual'}, users.InvalidAvatarError), # missing file
|
||||
])
|
||||
def test_invalid_inputs(test_ctx, input):
|
||||
def test_invalid_inputs(test_ctx, input, expected_exception):
|
||||
user = test_ctx.user_factory(name='u1', rank='admin')
|
||||
test_ctx.session.add(user)
|
||||
with pytest.raises(errors.ValidationError):
|
||||
with pytest.raises(expected_exception):
|
||||
test_ctx.api.put(
|
||||
test_ctx.context_factory(input=input, user=user), 'u1')
|
||||
|
||||
|
@ -151,14 +188,10 @@ def test_uploading_avatar(test_ctx, tmpdir):
|
|||
config.config['data_url'] = 'http://example.com/data/'
|
||||
user = test_ctx.user_factory(name='u1', rank='mod')
|
||||
test_ctx.session.add(user)
|
||||
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'
|
||||
response = test_ctx.api.put(
|
||||
test_ctx.context_factory(
|
||||
input={'avatarStyle': 'manual'},
|
||||
files={'avatar': empty_pixel},
|
||||
files={'avatar': EMPTY_PIXEL},
|
||||
user=user),
|
||||
'u1')
|
||||
user = get_user(test_ctx.session, 'u1')
|
||||
|
|
|
@ -11,7 +11,7 @@ class TagAlreadyExistsError(errors.ValidationError): pass
|
|||
class TagIsInUseError(errors.ValidationError): pass
|
||||
class InvalidTagNameError(errors.ValidationError): pass
|
||||
class InvalidTagCategoryError(errors.ValidationError): pass
|
||||
class RelationError(errors.ValidationError): pass
|
||||
class InvalidTagRelationError(errors.ValidationError): pass
|
||||
|
||||
def _verify_name_validity(name):
|
||||
name_regex = config.config['tag_name_regex']
|
||||
|
@ -111,7 +111,7 @@ def update_category_name(tag, category_name):
|
|||
tag.category = category
|
||||
|
||||
def update_names(tag, names):
|
||||
names = misc.icase_unique(names)
|
||||
names = misc.icase_unique([name for name in names if name])
|
||||
if not len(names):
|
||||
raise InvalidTagNameError('At least one name must be specified.')
|
||||
for name in names:
|
||||
|
@ -139,14 +139,14 @@ def update_names(tag, names):
|
|||
|
||||
def update_implications(tag, relations):
|
||||
if _check_name_intersection(_get_plain_names(tag), relations):
|
||||
raise RelationError('Tag cannot imply itself.')
|
||||
raise InvalidTagRelationError('Tag cannot imply itself.')
|
||||
related_tags, new_tags = get_or_create_tags_by_names(relations)
|
||||
db.session().flush()
|
||||
tag.implications = related_tags + new_tags
|
||||
|
||||
def update_suggestions(tag, relations):
|
||||
if _check_name_intersection(_get_plain_names(tag), relations):
|
||||
raise RelationError('Tag cannot suggest itself.')
|
||||
raise InvalidTagRelationError('Tag cannot suggest itself.')
|
||||
related_tags, new_tags = get_or_create_tags_by_names(relations)
|
||||
db.session().flush()
|
||||
tag.suggestions = related_tags + new_tags
|
||||
|
|
|
@ -6,7 +6,7 @@ from szurubooru.util import auth, misc, files, images
|
|||
|
||||
class UserNotFoundError(errors.NotFoundError): pass
|
||||
class UserAlreadyExistsError(errors.ValidationError): pass
|
||||
class InvalidNameError(errors.ValidationError): pass
|
||||
class InvalidUserNameError(errors.ValidationError): pass
|
||||
class InvalidEmailError(errors.ValidationError): pass
|
||||
class InvalidPasswordError(errors.ValidationError): pass
|
||||
class InvalidRankError(errors.ValidationError): pass
|
||||
|
@ -41,19 +41,23 @@ def create_user(name, password, email, auth_user):
|
|||
return user
|
||||
|
||||
def update_name(user, name, auth_user):
|
||||
if not name:
|
||||
raise InvalidUserNameError('Name cannot be empty.')
|
||||
if misc.value_exceeds_column_size(name, db.User.name):
|
||||
raise InvalidNameError('User name is too long.')
|
||||
raise InvalidUserNameError('User name is too long.')
|
||||
other_user = get_user_by_name(name)
|
||||
if other_user and other_user.user_id != auth_user.user_id:
|
||||
raise UserAlreadyExistsError('User %r already exists.' % name)
|
||||
name = name.strip()
|
||||
name_regex = config.config['user_name_regex']
|
||||
if not re.match(name_regex, name):
|
||||
raise InvalidNameError(
|
||||
raise InvalidUserNameError(
|
||||
'User name %r must satisfy regex %r.' % (name, name_regex))
|
||||
user.name = name
|
||||
|
||||
def update_password(user, password):
|
||||
if not password:
|
||||
raise InvalidPasswordError('Password cannot be empty.')
|
||||
password_regex = config.config['password_regex']
|
||||
if not re.match(password_regex, password):
|
||||
raise InvalidPasswordError(
|
||||
|
@ -62,7 +66,10 @@ def update_password(user, password):
|
|||
user.password_hash = auth.get_password_hash(user.password_salt, password)
|
||||
|
||||
def update_email(user, email):
|
||||
email = email.strip() or None
|
||||
if email:
|
||||
email = email.strip()
|
||||
if not email:
|
||||
email = None
|
||||
if email and misc.value_exceeds_column_size(email, db.User.email):
|
||||
raise InvalidEmailError('Email is too long.')
|
||||
if not misc.is_valid_email(email):
|
||||
|
@ -70,6 +77,8 @@ def update_email(user, email):
|
|||
user.email = email
|
||||
|
||||
def update_rank(user, rank, authenticated_user):
|
||||
if not rank:
|
||||
raise InvalidRankError('Rank cannot be empty.')
|
||||
rank = rank.strip()
|
||||
available_ranks = config.config['ranks']
|
||||
if not rank in available_ranks:
|
||||
|
|
Loading…
Reference in New Issue