server/api: refactor + remove ID from user JSON
This commit is contained in:
parent
adecdd4cd9
commit
e4239a199c
23 changed files with 482 additions and 560 deletions
1
API.md
1
API.md
|
@ -459,7 +459,6 @@ Not yet implemented.
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"id": 2,
|
|
||||||
"name": "rr-",
|
"name": "rr-",
|
||||||
"email": "rr-@sakuya.pl", // available only if the request is authenticated by the same user
|
"email": "rr-@sakuya.pl", // available only if the request is authenticated by the same user
|
||||||
"rank": "admin", // controlled by server's configuration
|
"rank": "admin", // controlled by server's configuration
|
||||||
|
|
|
@ -140,8 +140,13 @@ class Api {
|
||||||
cookies.remove('auth');
|
cookies.remove('auth');
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoggedIn() {
|
isLoggedIn(user) {
|
||||||
return this.userName !== null;
|
if (user) {
|
||||||
|
return this.userName !== null &&
|
||||||
|
this.userName.toLowerCase() === user.name.toLowerCase();
|
||||||
|
} else {
|
||||||
|
return this.userName !== null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFullUrl(url) {
|
getFullUrl(url) {
|
||||||
|
|
|
@ -155,7 +155,7 @@ class UsersController {
|
||||||
files.avatar = data.avatarContent;
|
files.avatar = data.avatarContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoggedIn = api.isLoggedIn() && api.user.id == user.id;
|
const isLoggedIn = api.isLoggedIn(user);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
api.put('/user/' + user.name, data, files)
|
api.put('/user/' + user.name, data, files)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
@ -182,7 +182,7 @@ class UsersController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_delete(user) {
|
_delete(user) {
|
||||||
const isLoggedIn = api.isLoggedIn() && api.user.id == user.id;
|
const isLoggedIn = api.isLoggedIn(user);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
api.delete('/user/' + user.name)
|
api.delete('/user/' + user.name)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
@ -205,7 +205,7 @@ class UsersController {
|
||||||
}
|
}
|
||||||
|
|
||||||
_show(user, section) {
|
_show(user, section) {
|
||||||
const isLoggedIn = api.isLoggedIn() && api.user.id == user.id;
|
const isLoggedIn = api.isLoggedIn(user);
|
||||||
const infix = isLoggedIn ? 'self' : 'any';
|
const infix = isLoggedIn ? 'self' : 'any';
|
||||||
|
|
||||||
const myRankIdx = api.user ? config.ranks.indexOf(api.user.rank) : 0;
|
const myRankIdx = api.user ? config.ranks.indexOf(api.user.rank) : 0;
|
||||||
|
|
|
@ -8,7 +8,7 @@ dummy-variables-rgx=_|dummy
|
||||||
max-line-length=90
|
max-line-length=90
|
||||||
|
|
||||||
[messages control]
|
[messages control]
|
||||||
disable=missing-docstring,no-self-use,too-few-public-methods
|
disable=missing-docstring,no-self-use,too-few-public-methods,multiple-statements
|
||||||
|
|
||||||
[typecheck]
|
[typecheck]
|
||||||
generated-members=add|add_all
|
generated-members=add|add_all
|
||||||
|
|
|
@ -35,6 +35,7 @@ class Context(object):
|
||||||
return default
|
return default
|
||||||
raise errors.ValidationError('Required paramter %r is missing.' % name)
|
raise errors.ValidationError('Required paramter %r is missing.' % name)
|
||||||
|
|
||||||
|
# pylint: disable=redefined-builtin,too-many-arguments
|
||||||
def get_param_as_int(
|
def get_param_as_int(
|
||||||
self, name, required=False, min=None, max=None, default=None):
|
self, name, required=False, min=None, max=None, default=None):
|
||||||
if name in self.input:
|
if name in self.input:
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import datetime
|
import datetime
|
||||||
from szurubooru import errors
|
|
||||||
from szurubooru.util import auth, tags
|
from szurubooru.util import auth, tags
|
||||||
from szurubooru.api.base_api import BaseApi
|
from szurubooru.api.base_api import BaseApi
|
||||||
|
|
||||||
|
@ -49,8 +48,7 @@ class TagDetailApi(BaseApi):
|
||||||
|
|
||||||
if ctx.has_param('category'):
|
if ctx.has_param('category'):
|
||||||
auth.verify_privilege(ctx.user, 'tags:edit:category')
|
auth.verify_privilege(ctx.user, 'tags:edit:category')
|
||||||
tags.update_category(
|
tags.update_category(tag, ctx.get_param_as_string('category'))
|
||||||
ctx.session, tag, ctx.get_param_as_string('category'))
|
|
||||||
|
|
||||||
if ctx.has_param('suggestions'):
|
if ctx.has_param('suggestions'):
|
||||||
auth.verify_privilege(ctx.user, 'tags:edit:suggestions')
|
auth.verify_privilege(ctx.user, 'tags:edit:suggestions')
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from szurubooru import config, errors, search
|
from szurubooru import config, 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
|
||||||
|
|
||||||
def _serialize_user(authenticated_user, user):
|
def _serialize_user(authenticated_user, user):
|
||||||
ret = {
|
ret = {
|
||||||
'id': user.user_id,
|
|
||||||
'name': user.name,
|
'name': user.name,
|
||||||
'rank': user.rank,
|
'rank': user.rank,
|
||||||
'rankName': config.config['rank_names'].get(user.rank, 'Unknown'),
|
'rankName': config.config['rank_names'].get(user.rank, 'Unknown'),
|
||||||
|
@ -57,9 +56,7 @@ class UserListApi(BaseApi):
|
||||||
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=True)
|
||||||
|
|
||||||
if users.get_by_name(ctx.session, name):
|
user = users.create_user(ctx.session, name, password, email, ctx.user)
|
||||||
raise errors.IntegrityError('User %r already exists.' % name)
|
|
||||||
user = users.create_user(ctx.session, name, password, email)
|
|
||||||
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)}
|
||||||
|
@ -69,13 +66,13 @@ class UserDetailApi(BaseApi):
|
||||||
auth.verify_privilege(ctx.user, 'users:view')
|
auth.verify_privilege(ctx.user, 'users:view')
|
||||||
user = users.get_by_name(ctx.session, user_name)
|
user = users.get_by_name(ctx.session, user_name)
|
||||||
if not user:
|
if not user:
|
||||||
raise errors.NotFoundError('User %r not found.' % user_name)
|
raise users.UserNotFoundError('User %r not found.' % user_name)
|
||||||
return {'user': _serialize_user(ctx.user, user)}
|
return {'user': _serialize_user(ctx.user, user)}
|
||||||
|
|
||||||
def put(self, ctx, user_name):
|
def put(self, ctx, user_name):
|
||||||
user = users.get_by_name(ctx.session, user_name)
|
user = users.get_by_name(ctx.session, user_name)
|
||||||
if not user:
|
if not user:
|
||||||
raise errors.NotFoundError('User %r not found.' % user_name)
|
raise users.UserNotFoundError('User %r not found.' % user_name)
|
||||||
|
|
||||||
if ctx.user.user_id == user.user_id:
|
if ctx.user.user_id == user.user_id:
|
||||||
infix = 'self'
|
infix = 'self'
|
||||||
|
@ -84,10 +81,8 @@ class UserDetailApi(BaseApi):
|
||||||
|
|
||||||
if ctx.has_param('name'):
|
if ctx.has_param('name'):
|
||||||
auth.verify_privilege(ctx.user, 'users:edit:%s:name' % infix)
|
auth.verify_privilege(ctx.user, 'users:edit:%s:name' % infix)
|
||||||
other_user = users.get_by_name(ctx.session, ctx.get_param_as_string('name'))
|
users.update_name(
|
||||||
if other_user and other_user.user_id != user.user_id:
|
ctx.session, user, ctx.get_param_as_string('name'), ctx.user)
|
||||||
raise errors.IntegrityError('User %r already exists.' % user.name)
|
|
||||||
users.update_name(user, ctx.get_param_as_string('name'))
|
|
||||||
|
|
||||||
if ctx.has_param('password'):
|
if ctx.has_param('password'):
|
||||||
auth.verify_privilege(ctx.user, 'users:edit:%s:pass' % infix)
|
auth.verify_privilege(ctx.user, 'users:edit:%s:pass' % infix)
|
||||||
|
@ -114,7 +109,7 @@ class UserDetailApi(BaseApi):
|
||||||
def delete(self, ctx, user_name):
|
def delete(self, ctx, user_name):
|
||||||
user = users.get_by_name(ctx.session, user_name)
|
user = users.get_by_name(ctx.session, user_name)
|
||||||
if not user:
|
if not user:
|
||||||
raise errors.NotFoundError('User %r not found.' % user_name)
|
raise users.UserNotFoundError('User %r not found.' % user_name)
|
||||||
|
|
||||||
if ctx.user.user_id == user.user_id:
|
if ctx.user.user_id == user.user_id:
|
||||||
infix = 'self'
|
infix = 'self'
|
||||||
|
|
|
@ -13,52 +13,48 @@ def merge(left, right):
|
||||||
left[key] = right[key]
|
left[key] = right[key]
|
||||||
return left
|
return left
|
||||||
|
|
||||||
class Config(object):
|
def read_config():
|
||||||
''' Config parser and container. '''
|
with open('../config.yaml.dist') as handle:
|
||||||
def __init__(self):
|
ret = yaml.load(handle.read())
|
||||||
with open('../config.yaml.dist') as handle:
|
|
||||||
self.config = yaml.load(handle.read())
|
|
||||||
if os.path.exists('../config.yaml'):
|
if os.path.exists('../config.yaml'):
|
||||||
with open('../config.yaml') as handle:
|
with open('../config.yaml') as handle:
|
||||||
self.config = merge(self.config, yaml.load(handle.read()))
|
ret = merge(ret, yaml.load(handle.read()))
|
||||||
self._validate()
|
return ret
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def validate_config(src):
|
||||||
return self.config[key]
|
'''
|
||||||
|
Check whether config doesn't contain errors that might prove
|
||||||
def _validate(self):
|
lethal at runtime.
|
||||||
'''
|
'''
|
||||||
Check whether config doesn't contain errors that might prove
|
all_ranks = src['ranks']
|
||||||
lethal at runtime.
|
for privilege, rank in src['privileges'].items():
|
||||||
'''
|
if rank not in all_ranks:
|
||||||
all_ranks = self['ranks']
|
|
||||||
for privilege, rank in self['privileges'].items():
|
|
||||||
if rank not in all_ranks:
|
|
||||||
raise errors.ConfigError(
|
|
||||||
'Rank %r for privilege %r is missing' % (rank, privilege))
|
|
||||||
for rank in ['anonymous', 'admin', 'nobody']:
|
|
||||||
if rank not in all_ranks:
|
|
||||||
raise errors.ConfigError('Protected rank %r is missing' % rank)
|
|
||||||
if self['default_rank'] not in all_ranks:
|
|
||||||
raise errors.ConfigError(
|
raise errors.ConfigError(
|
||||||
'Default rank %r is not on the list of known ranks' % (
|
'Rank %r for privilege %r is missing' % (rank, privilege))
|
||||||
self['default_rank']))
|
for rank in ['anonymous', 'admin', 'nobody']:
|
||||||
|
if rank not in all_ranks:
|
||||||
|
raise errors.ConfigError('Protected rank %r is missing' % rank)
|
||||||
|
if src['default_rank'] not in all_ranks:
|
||||||
|
raise errors.ConfigError(
|
||||||
|
'Default rank %r is not on the list of known ranks' % (
|
||||||
|
src['default_rank']))
|
||||||
|
|
||||||
for key in ['base_url', 'api_url', 'data_url', 'data_dir']:
|
for key in ['base_url', 'api_url', 'data_url', 'data_dir']:
|
||||||
if not self[key]:
|
if not src[key]:
|
||||||
raise errors.ConfigError(
|
|
||||||
'Service is not configured: %r is missing' % key)
|
|
||||||
|
|
||||||
if not os.path.isabs(self['data_dir']):
|
|
||||||
raise errors.ConfigError(
|
raise errors.ConfigError(
|
||||||
'data_dir must be an absolute path')
|
'Service is not configured: %r is missing' % key)
|
||||||
|
|
||||||
for key in ['schema', 'host', 'port', 'user', 'pass', 'name']:
|
if not os.path.isabs(src['data_dir']):
|
||||||
if not self['database'][key]:
|
raise errors.ConfigError(
|
||||||
raise errors.ConfigError(
|
'data_dir must be an absolute path')
|
||||||
'Database is not configured: %r is missing' % key)
|
|
||||||
|
|
||||||
if not len(self['tag_categories']):
|
for key in ['schema', 'host', 'port', 'user', 'pass', 'name']:
|
||||||
raise errors.ConfigError('Must have at least one tag category')
|
if not src['database'][key]:
|
||||||
|
raise errors.ConfigError(
|
||||||
|
'Database is not configured: %r is missing' % key)
|
||||||
|
|
||||||
config = Config() # pylint: disable=invalid-name
|
if not len(src['tag_categories']):
|
||||||
|
raise errors.ConfigError('Must have at least one tag category')
|
||||||
|
|
||||||
|
config = read_config() # pylint: disable=invalid-name
|
||||||
|
validate_config(config)
|
||||||
|
|
|
@ -53,7 +53,7 @@ class TagName(Base):
|
||||||
__tablename__ = 'tag_name'
|
__tablename__ = 'tag_name'
|
||||||
tag_name_id = Column('tag_name_id', Integer, primary_key=True)
|
tag_name_id = Column('tag_name_id', Integer, primary_key=True)
|
||||||
tag_id = Column('tag_id', Integer, ForeignKey('tag.id'))
|
tag_id = Column('tag_id', Integer, ForeignKey('tag.id'))
|
||||||
name = Column('name', String(50), nullable=False, unique=True)
|
name = Column('name', String(64), nullable=False, unique=True)
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
|
@ -11,7 +11,7 @@ class User(Base):
|
||||||
name = Column('name', String(50), nullable=False, unique=True)
|
name = Column('name', String(50), nullable=False, unique=True)
|
||||||
password_hash = Column('password_hash', String(64), nullable=False)
|
password_hash = Column('password_hash', String(64), nullable=False)
|
||||||
password_salt = Column('password_salt', String(32))
|
password_salt = Column('password_salt', String(32))
|
||||||
email = Column('email', String(200), nullable=True)
|
email = Column('email', String(64), nullable=True)
|
||||||
rank = Column('rank', String(32), nullable=False)
|
rank = Column('rank', String(32), nullable=False)
|
||||||
creation_time = Column('creation_time', DateTime, nullable=False)
|
creation_time = Column('creation_time', DateTime, nullable=False)
|
||||||
last_login_time = Column('last_login_time', DateTime)
|
last_login_time = Column('last_login_time', DateTime)
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
class ConfigError(RuntimeError):
|
class ConfigError(RuntimeError): pass
|
||||||
''' A problem with configuration file. '''
|
class AuthError(RuntimeError): pass
|
||||||
|
class IntegrityError(RuntimeError): pass
|
||||||
class AuthError(RuntimeError):
|
class ValidationError(RuntimeError): pass
|
||||||
''' Generic authentication error '''
|
class SearchError(RuntimeError): pass
|
||||||
|
class NotFoundError(RuntimeError): pass
|
||||||
class IntegrityError(RuntimeError):
|
class ProcessingError(RuntimeError): pass
|
||||||
''' Database integrity error (e.g. trying to edit nonexisting resource) '''
|
|
||||||
|
|
||||||
class ValidationError(RuntimeError):
|
|
||||||
''' Validation error (e.g. trying to create user with invalid name) '''
|
|
||||||
|
|
||||||
class SearchError(RuntimeError):
|
|
||||||
''' Search error (e.g. trying to use special: where it doesn't make sense) '''
|
|
||||||
|
|
||||||
class NotFoundError(RuntimeError):
|
|
||||||
''' Error thrown when a resource (usually DB) couldn't be found. '''
|
|
||||||
|
|
||||||
class ProcessingError(RuntimeError):
|
|
||||||
''' Error thrown by things such as thumbnail generator. '''
|
|
||||||
|
|
|
@ -21,13 +21,14 @@ class ContextAdapter(object):
|
||||||
def process_request(self, request, _response):
|
def process_request(self, request, _response):
|
||||||
request.context.files = {}
|
request.context.files = {}
|
||||||
request.context.input = {}
|
request.context.input = {}
|
||||||
|
# pylint: disable=protected-access
|
||||||
for key, value in request._params.items():
|
for key, value in request._params.items():
|
||||||
request.context.input[key] = value
|
request.context.input[key] = value
|
||||||
|
|
||||||
if request.content_length in (None, 0):
|
if request.content_length in (None, 0):
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'multipart/form-data' in (request.content_type or ''):
|
if request.content_type and 'multipart/form-data' in request.content_type:
|
||||||
# obscure, claims to "avoid a bug in cgi.FieldStorage"
|
# obscure, claims to "avoid a bug in cgi.FieldStorage"
|
||||||
request.env.setdefault('QUERY_STRING', '')
|
request.env.setdefault('QUERY_STRING', '')
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ def upgrade():
|
||||||
'tag_name',
|
'tag_name',
|
||||||
sa.Column('tag_name_id', sa.Integer(), nullable=False),
|
sa.Column('tag_name_id', sa.Integer(), nullable=False),
|
||||||
sa.Column('tag_id', sa.Integer(), nullable=True),
|
sa.Column('tag_id', sa.Integer(), nullable=True),
|
||||||
sa.Column('name', sa.String(length=50), nullable=False),
|
sa.Column('name', sa.String(length=64), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['tag_id'], ['tag.id']),
|
sa.ForeignKeyConstraint(['tag_id'], ['tag.id']),
|
||||||
sa.PrimaryKeyConstraint('tag_name_id'),
|
sa.PrimaryKeyConstraint('tag_name_id'),
|
||||||
sa.UniqueConstraint('name'))
|
sa.UniqueConstraint('name'))
|
||||||
|
|
|
@ -20,7 +20,7 @@ def upgrade():
|
||||||
sa.Column('name', sa.String(length=50), nullable=False),
|
sa.Column('name', sa.String(length=50), nullable=False),
|
||||||
sa.Column('password_hash', sa.String(length=64), nullable=False),
|
sa.Column('password_hash', sa.String(length=64), nullable=False),
|
||||||
sa.Column('password_salt', sa.String(length=32), nullable=True),
|
sa.Column('password_salt', sa.String(length=32), nullable=True),
|
||||||
sa.Column('email', sa.String(length=200), nullable=True),
|
sa.Column('email', sa.String(length=64), nullable=True),
|
||||||
sa.Column('rank', sa.String(length=32), nullable=False),
|
sa.Column('rank', sa.String(length=32), nullable=False),
|
||||||
sa.Column('creation_time', sa.DateTime(), nullable=False),
|
sa.Column('creation_time', sa.DateTime(), nullable=False),
|
||||||
sa.Column('last_login_time', sa.DateTime()),
|
sa.Column('last_login_time', sa.DateTime()),
|
||||||
|
|
|
@ -19,7 +19,7 @@ def test_ctx(
|
||||||
config_injector({
|
config_injector({
|
||||||
'tag_categories': ['meta', 'character', 'copyright'],
|
'tag_categories': ['meta', 'character', 'copyright'],
|
||||||
'tag_name_regex': '^[^!]*$',
|
'tag_name_regex': '^[^!]*$',
|
||||||
'ranks': ['regular_user'],
|
'ranks': ['anonymous', 'regular_user'],
|
||||||
'privileges': {'tags:create': 'regular_user'},
|
'privileges': {'tags:create': 'regular_user'},
|
||||||
})
|
})
|
||||||
ret = misc.dotdict()
|
ret = misc.dotdict()
|
||||||
|
@ -86,12 +86,13 @@ def test_trying_to_create_tag_without_names(test_ctx):
|
||||||
},
|
},
|
||||||
user=test_ctx.user_factory(rank='regular_user')))
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
|
|
||||||
def test_trying_to_create_tag_with_invalid_name(test_ctx):
|
@pytest.mark.parametrize('names', [['!'], ['x' * 65]])
|
||||||
|
def test_trying_to_create_tag_with_invalid_name(test_ctx, names):
|
||||||
with pytest.raises(tags.InvalidNameError):
|
with pytest.raises(tags.InvalidNameError):
|
||||||
test_ctx.api.post(
|
test_ctx.api.post(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
'names': ['!'],
|
'names': names,
|
||||||
'category': 'meta',
|
'category': 'meta',
|
||||||
'suggestions': [],
|
'suggestions': [],
|
||||||
'implications': [],
|
'implications': [],
|
||||||
|
@ -244,7 +245,6 @@ def test_trying_to_create_tag_with_invalid_relation(test_ctx, input):
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
def test_tag_trying_to_relate_to_itself(test_ctx, input):
|
def test_tag_trying_to_relate_to_itself(test_ctx, input):
|
||||||
assert get_tag(test_ctx.session, 'tag') is None
|
|
||||||
with pytest.raises(tags.RelationError):
|
with pytest.raises(tags.RelationError):
|
||||||
test_ctx.api.post(
|
test_ctx.api.post(
|
||||||
test_ctx.context_factory(
|
test_ctx.context_factory(
|
||||||
|
@ -252,5 +252,14 @@ def test_tag_trying_to_relate_to_itself(test_ctx, input):
|
||||||
user=test_ctx.user_factory(rank='regular_user')))
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
assert get_tag(test_ctx.session, 'tag') is None
|
assert get_tag(test_ctx.session, 'tag') is None
|
||||||
|
|
||||||
# TODO: test bad privileges
|
def test_trying_to_create_tag_without_privileges(test_ctx):
|
||||||
# TODO: test max length
|
with pytest.raises(errors.AuthError):
|
||||||
|
test_ctx.api.post(
|
||||||
|
test_ctx.context_factory(
|
||||||
|
input={
|
||||||
|
'names': ['tag'],
|
||||||
|
'category': 'meta',
|
||||||
|
'suggestions': ['tag'],
|
||||||
|
'implications': [],
|
||||||
|
},
|
||||||
|
user=test_ctx.user_factory(rank='anonymous')))
|
||||||
|
|
|
@ -19,7 +19,7 @@ def test_ctx(
|
||||||
config_injector({
|
config_injector({
|
||||||
'tag_categories': ['meta', 'character', 'copyright'],
|
'tag_categories': ['meta', 'character', 'copyright'],
|
||||||
'tag_name_regex': '^[^!]*$',
|
'tag_name_regex': '^[^!]*$',
|
||||||
'ranks': ['regular_user'],
|
'ranks': ['anonymous', 'regular_user'],
|
||||||
'privileges': {
|
'privileges': {
|
||||||
'tags:edit:names': 'regular_user',
|
'tags:edit:names': 'regular_user',
|
||||||
'tags:edit:category': 'regular_user',
|
'tags:edit:category': 'regular_user',
|
||||||
|
@ -110,6 +110,7 @@ def test_duplicating_names(test_ctx):
|
||||||
@pytest.mark.parametrize('input', [
|
@pytest.mark.parametrize('input', [
|
||||||
{'names': []},
|
{'names': []},
|
||||||
{'names': ['!']},
|
{'names': ['!']},
|
||||||
|
{'names': ['x' * 65]},
|
||||||
])
|
])
|
||||||
def test_trying_to_set_invalid_name(test_ctx, input):
|
def test_trying_to_set_invalid_name(test_ctx, input):
|
||||||
test_ctx.session.add(test_ctx.tag_factory(names=['tag1'], category='meta'))
|
test_ctx.session.add(test_ctx.tag_factory(names=['tag1'], category='meta'))
|
||||||
|
@ -252,5 +253,18 @@ def test_tag_trying_to_relate_to_itself(test_ctx, input):
|
||||||
input=input, user=test_ctx.user_factory(rank='regular_user')),
|
input=input, user=test_ctx.user_factory(rank='regular_user')),
|
||||||
'tag1')
|
'tag1')
|
||||||
|
|
||||||
# TODO: test bad privileges
|
@pytest.mark.parametrize('input', [
|
||||||
# TODO: test max length
|
{'names': 'whatever'},
|
||||||
|
{'category': 'whatever'},
|
||||||
|
{'suggestions': ['whatever']},
|
||||||
|
{'implications': ['whatever']},
|
||||||
|
])
|
||||||
|
def test_trying_to_update_tag_without_privileges(test_ctx, input):
|
||||||
|
test_ctx.session.add(test_ctx.tag_factory(names=['tag'], category='meta'))
|
||||||
|
test_ctx.session.commit()
|
||||||
|
with pytest.raises(errors.AuthError):
|
||||||
|
test_ctx.api.put(
|
||||||
|
test_ctx.context_factory(
|
||||||
|
input=input,
|
||||||
|
user=test_ctx.user_factory(rank='anonymous')),
|
||||||
|
'tag')
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
|
import datetime
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime
|
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
from szurubooru.util import auth
|
from szurubooru.util import auth, misc, users
|
||||||
|
|
||||||
|
def get_user(session, name):
|
||||||
|
return session.query(db.User).filter_by(name=name).first()
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user_list_api():
|
def test_ctx(
|
||||||
return api.UserListApi()
|
session, config_injector, context_factory, user_factory):
|
||||||
|
|
||||||
def test_creating_users(
|
|
||||||
session,
|
|
||||||
config_injector,
|
|
||||||
context_factory,
|
|
||||||
user_factory,
|
|
||||||
user_list_api):
|
|
||||||
config_injector({
|
config_injector({
|
||||||
'secret': '',
|
'secret': '',
|
||||||
'user_name_regex': '.{3,}',
|
'user_name_regex': '.{3,}',
|
||||||
|
@ -23,94 +19,122 @@ def test_creating_users(
|
||||||
'rank_names': {},
|
'rank_names': {},
|
||||||
'privileges': {'users:create': 'anonymous'},
|
'privileges': {'users:create': 'anonymous'},
|
||||||
})
|
})
|
||||||
|
ret = misc.dotdict()
|
||||||
|
ret.session = session
|
||||||
|
ret.context_factory = context_factory
|
||||||
|
ret.user_factory = user_factory
|
||||||
|
ret.api = api.UserListApi()
|
||||||
|
return ret
|
||||||
|
|
||||||
user_list_api.post(
|
def test_creating_user(test_ctx, fake_datetime):
|
||||||
context_factory(
|
fake_datetime(datetime.datetime(1969, 2, 12))
|
||||||
|
result = test_ctx.api.post(
|
||||||
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
'name': 'chewie1',
|
'name': 'chewie1',
|
||||||
'email': 'asd@asd.asd',
|
'email': 'asd@asd.asd',
|
||||||
'password': 'oks',
|
'password': 'oks',
|
||||||
},
|
},
|
||||||
user=user_factory(rank='regular_user')))
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
user_list_api.post(
|
assert result == {
|
||||||
context_factory(
|
'user': {
|
||||||
|
'avatarStyle': 'gravatar',
|
||||||
|
'avatarUrl': 'http://gravatar.com/avatar/' +
|
||||||
|
'6f370c8c7109534c3d5c394123a477d7?d=retro&s=200',
|
||||||
|
'creationTime': datetime.datetime(1969, 2, 12),
|
||||||
|
'lastLoginTime': None,
|
||||||
|
'name': 'chewie1',
|
||||||
|
'rank': 'admin',
|
||||||
|
'rankName': 'Unknown',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user = get_user(test_ctx.session, 'chewie1')
|
||||||
|
assert user.name == 'chewie1'
|
||||||
|
assert user.email == 'asd@asd.asd'
|
||||||
|
assert user.rank == 'admin'
|
||||||
|
assert auth.is_valid_password(user, 'oks') is True
|
||||||
|
assert auth.is_valid_password(user, 'invalid') is False
|
||||||
|
|
||||||
|
def test_first_user_becomes_admin_others_not(test_ctx):
|
||||||
|
result1 = test_ctx.api.post(
|
||||||
|
test_ctx.context_factory(
|
||||||
|
input={
|
||||||
|
'name': 'chewie1',
|
||||||
|
'email': 'asd@asd.asd',
|
||||||
|
'password': 'oks',
|
||||||
|
},
|
||||||
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
|
result2 = test_ctx.api.post(
|
||||||
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
'name': 'chewie2',
|
'name': 'chewie2',
|
||||||
'email': 'asd@asd.asd',
|
'email': 'asd@asd.asd',
|
||||||
'password': 'sok',
|
'password': 'sok',
|
||||||
},
|
},
|
||||||
user=user_factory(rank='regular_user')))
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
|
assert result1['user']['rank'] == 'admin'
|
||||||
first_user = session.query(db.User).filter_by(name='chewie1').one()
|
assert result2['user']['rank'] == 'regular_user'
|
||||||
other_user = session.query(db.User).filter_by(name='chewie2').one()
|
first_user = get_user(test_ctx.session, 'chewie1')
|
||||||
assert first_user.name == 'chewie1'
|
other_user = get_user(test_ctx.session, 'chewie2')
|
||||||
assert first_user.email == 'asd@asd.asd'
|
|
||||||
assert first_user.rank == 'admin'
|
assert first_user.rank == 'admin'
|
||||||
assert auth.is_valid_password(first_user, 'oks') is True
|
|
||||||
assert auth.is_valid_password(first_user, 'invalid') is False
|
|
||||||
assert other_user.name == 'chewie2'
|
|
||||||
assert other_user.email == 'asd@asd.asd'
|
|
||||||
assert other_user.rank == 'regular_user'
|
assert other_user.rank == 'regular_user'
|
||||||
assert auth.is_valid_password(other_user, 'sok') is True
|
|
||||||
assert auth.is_valid_password(other_user, 'invalid') is False
|
|
||||||
|
|
||||||
def test_creating_user_that_already_exists(
|
def test_creating_user_that_already_exists(test_ctx):
|
||||||
config_injector, context_factory, user_factory, user_list_api):
|
test_ctx.api.post(
|
||||||
config_injector({
|
test_ctx.context_factory(
|
||||||
'secret': '',
|
|
||||||
'user_name_regex': '.{3,}',
|
|
||||||
'password_regex': '.{3,}',
|
|
||||||
'default_rank': 'regular_user',
|
|
||||||
'thumbnails': {'avatar_width': 200},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'rank_names': {},
|
|
||||||
'privileges': {'users:create': 'anonymous'},
|
|
||||||
})
|
|
||||||
user_list_api.post(
|
|
||||||
context_factory(
|
|
||||||
input={
|
input={
|
||||||
'name': 'chewie',
|
'name': 'chewie',
|
||||||
'email': 'asd@asd.asd',
|
'email': 'asd@asd.asd',
|
||||||
'password': 'oks',
|
'password': 'oks',
|
||||||
},
|
},
|
||||||
user=user_factory(rank='regular_user')))
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
with pytest.raises(errors.IntegrityError):
|
with pytest.raises(users.UserAlreadyExistsError):
|
||||||
user_list_api.post(
|
test_ctx.api.post(
|
||||||
context_factory(
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
'name': 'chewie',
|
'name': 'chewie',
|
||||||
'email': 'asd@asd.asd',
|
'email': 'asd@asd.asd',
|
||||||
'password': 'oks',
|
'password': 'oks',
|
||||||
},
|
},
|
||||||
user=user_factory(rank='regular_user')))
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
with pytest.raises(errors.IntegrityError):
|
with pytest.raises(users.UserAlreadyExistsError):
|
||||||
user_list_api.post(
|
test_ctx.api.post(
|
||||||
context_factory(
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
'name': 'CHEWIE',
|
'name': 'CHEWIE',
|
||||||
'email': 'asd@asd.asd',
|
'email': 'asd@asd.asd',
|
||||||
'password': 'oks',
|
'password': 'oks',
|
||||||
},
|
},
|
||||||
user=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', 'email', 'password'])
|
||||||
def test_missing_field(
|
def test_missing_field(test_ctx, field):
|
||||||
config_injector, context_factory, user_factory, user_list_api, field):
|
input = {
|
||||||
config_injector({
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'privileges': {'users:create': 'anonymous'},
|
|
||||||
})
|
|
||||||
request = {
|
|
||||||
'name': 'chewie',
|
'name': 'chewie',
|
||||||
'email': 'asd@asd.asd',
|
'email': 'asd@asd.asd',
|
||||||
'password': 'oks',
|
'password': 'oks',
|
||||||
}
|
}
|
||||||
del request[field]
|
del input[field]
|
||||||
with pytest.raises(errors.ValidationError):
|
with pytest.raises(errors.ValidationError):
|
||||||
user_list_api.post(
|
test_ctx.api.post(
|
||||||
context_factory(
|
test_ctx.context_factory(
|
||||||
input=request, user=user_factory(rank='regular_user')))
|
input=input,
|
||||||
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('input', [
|
||||||
|
{'name': '.'},
|
||||||
|
{'name': 'x' * 51},
|
||||||
|
{'password': '.'},
|
||||||
|
{'rank': '.'},
|
||||||
|
{'email': '.'},
|
||||||
|
{'email': 'x' * 65},
|
||||||
|
{'avatarStyle': 'manual'},
|
||||||
|
])
|
||||||
|
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):
|
||||||
|
test_ctx.api.post(
|
||||||
|
test_ctx.context_factory(input=input, user=user))
|
||||||
|
|
||||||
# TODO: test too long name
|
|
||||||
# TODO: test bad password, email or name
|
|
||||||
# TODO: support avatar and avatarStyle
|
# TODO: support avatar and avatarStyle
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
|
from szurubooru.util import misc, users
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user_detail_api():
|
def test_ctx(session, config_injector, context_factory, user_factory):
|
||||||
return api.UserDetailApi()
|
|
||||||
|
|
||||||
def test_removing_oneself(
|
|
||||||
session,
|
|
||||||
config_injector,
|
|
||||||
context_factory,
|
|
||||||
user_factory,
|
|
||||||
user_detail_api):
|
|
||||||
config_injector({
|
config_injector({
|
||||||
'privileges': {
|
'privileges': {
|
||||||
'users:delete:self': 'regular_user',
|
'users:delete:self': 'regular_user',
|
||||||
|
@ -19,47 +12,37 @@ def test_removing_oneself(
|
||||||
},
|
},
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||||
})
|
})
|
||||||
user1 = user_factory(name='u1', rank='regular_user')
|
ret = misc.dotdict()
|
||||||
user2 = user_factory(name='u2', rank='regular_user')
|
ret.session = session
|
||||||
session.add_all([user1, user2])
|
ret.context_factory = context_factory
|
||||||
session.commit()
|
ret.user_factory = user_factory
|
||||||
|
ret.api = api.UserDetailApi()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def test_removing_oneself(test_ctx):
|
||||||
|
user1 = test_ctx.user_factory(name='u1', rank='regular_user')
|
||||||
|
user2 = test_ctx.user_factory(name='u2', rank='regular_user')
|
||||||
|
test_ctx.session.add_all([user1, user2])
|
||||||
|
test_ctx.session.commit()
|
||||||
with pytest.raises(errors.AuthError):
|
with pytest.raises(errors.AuthError):
|
||||||
user_detail_api.delete(context_factory(user=user1), 'u2')
|
test_ctx.api.delete(test_ctx.context_factory(user=user1), 'u2')
|
||||||
user_detail_api.delete(context_factory(user=user1), 'u1')
|
result = test_ctx.api.delete(test_ctx.context_factory(user=user1), 'u1')
|
||||||
assert [u.name for u in session.query(db.User).all()] == ['u2']
|
assert result == {}
|
||||||
|
assert [u.name for u in test_ctx.session.query(db.User).all()] == ['u2']
|
||||||
|
|
||||||
def test_removing_someone_else(
|
def test_removing_someone_else(test_ctx):
|
||||||
session,
|
user1 = test_ctx.user_factory(name='u1', rank='regular_user')
|
||||||
config_injector,
|
user2 = test_ctx.user_factory(name='u2', rank='regular_user')
|
||||||
context_factory,
|
mod_user = test_ctx.user_factory(rank='mod')
|
||||||
user_factory,
|
test_ctx.session.add_all([user1, user2])
|
||||||
user_detail_api):
|
test_ctx.session.commit()
|
||||||
config_injector({
|
test_ctx.api.delete(test_ctx.context_factory(user=mod_user), 'u1')
|
||||||
'privileges': {
|
test_ctx.api.delete(test_ctx.context_factory(user=mod_user), 'u2')
|
||||||
'users:delete:self': 'regular_user',
|
assert test_ctx.session.query(db.User).all() == []
|
||||||
'users:delete:any': 'mod',
|
|
||||||
},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
})
|
|
||||||
user1 = user_factory(name='u1', rank='regular_user')
|
|
||||||
user2 = user_factory(name='u2', rank='regular_user')
|
|
||||||
mod_user = user_factory(rank='mod')
|
|
||||||
session.add_all([user1, user2])
|
|
||||||
session.commit()
|
|
||||||
user_detail_api.delete(context_factory(user=mod_user), 'u1')
|
|
||||||
user_detail_api.delete(context_factory(user=mod_user), 'u2')
|
|
||||||
assert session.query(db.User).all() == []
|
|
||||||
|
|
||||||
def test_removing_non_existing(
|
def test_removing_non_existing(test_ctx):
|
||||||
context_factory, config_injector, user_factory, user_detail_api):
|
with pytest.raises(users.UserNotFoundError):
|
||||||
config_injector({
|
test_ctx.api.delete(
|
||||||
'privileges': {
|
test_ctx.context_factory(
|
||||||
'users:delete:self': 'regular_user',
|
user=test_ctx.user_factory(rank='regular_user')), 'bad')
|
||||||
'users:delete:any': 'mod',
|
|
||||||
},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
})
|
|
||||||
with pytest.raises(errors.NotFoundError):
|
|
||||||
user_detail_api.delete(
|
|
||||||
context_factory(user=user_factory(rank='regular_user')), 'bad')
|
|
||||||
|
|
||||||
|
|
|
@ -1,116 +1,91 @@
|
||||||
|
import datetime
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime
|
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, db, errors
|
||||||
|
from szurubooru.util import misc, users
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user_list_api():
|
def test_ctx(session, context_factory, config_injector, user_factory):
|
||||||
return api.UserListApi()
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def user_detail_api():
|
|
||||||
return api.UserDetailApi()
|
|
||||||
|
|
||||||
def test_retrieving_multiple(
|
|
||||||
session,
|
|
||||||
config_injector,
|
|
||||||
context_factory,
|
|
||||||
user_factory,
|
|
||||||
user_list_api):
|
|
||||||
config_injector({
|
config_injector({
|
||||||
'privileges': {'users:list': 'regular_user'},
|
'privileges': {
|
||||||
|
'users:list': 'regular_user',
|
||||||
|
'users:view': 'regular_user',
|
||||||
|
},
|
||||||
'thumbnails': {'avatar_width': 200},
|
'thumbnails': {'avatar_width': 200},
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||||
'rank_names': {},
|
'rank_names': {'regular_user': 'Peasant'},
|
||||||
})
|
})
|
||||||
user1 = user_factory(name='u1', rank='mod')
|
ret = misc.dotdict()
|
||||||
user2 = user_factory(name='u2', rank='mod')
|
ret.session = session
|
||||||
session.add_all([user1, user2])
|
ret.context_factory = context_factory
|
||||||
result = user_list_api.get(
|
ret.user_factory = user_factory
|
||||||
context_factory(
|
ret.list_api = api.UserListApi()
|
||||||
|
ret.detail_api = api.UserDetailApi()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def test_retrieving_multiple(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])
|
||||||
|
result = test_ctx.list_api.get(
|
||||||
|
test_ctx.context_factory(
|
||||||
input={'query': '', 'page': 1},
|
input={'query': '', 'page': 1},
|
||||||
user=user_factory(rank='regular_user')))
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
assert result['query'] == ''
|
assert result['query'] == ''
|
||||||
assert result['page'] == 1
|
assert result['page'] == 1
|
||||||
assert result['pageSize'] == 100
|
assert result['pageSize'] == 100
|
||||||
assert result['total'] == 2
|
assert result['total'] == 2
|
||||||
assert [u['name'] for u in result['users']] == ['u1', 'u2']
|
assert [u['name'] for u in result['users']] == ['u1', 'u2']
|
||||||
|
|
||||||
def test_retrieving_multiple_without_privileges(
|
def test_retrieving_multiple_without_privileges(test_ctx):
|
||||||
context_factory, config_injector, user_factory, user_list_api):
|
|
||||||
config_injector({
|
|
||||||
'privileges': {'users:list': 'regular_user'},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
})
|
|
||||||
with pytest.raises(errors.AuthError):
|
with pytest.raises(errors.AuthError):
|
||||||
user_list_api.get(
|
test_ctx.list_api.get(
|
||||||
context_factory(
|
test_ctx.context_factory(
|
||||||
input={'query': '', 'page': 1},
|
input={'query': '', 'page': 1},
|
||||||
user=user_factory(rank='anonymous')))
|
user=test_ctx.user_factory(rank='anonymous')))
|
||||||
|
|
||||||
def test_retrieving_multiple_with_privileges(
|
def test_retrieving_multiple_with_privileges(test_ctx):
|
||||||
context_factory, config_injector, user_factory, user_list_api):
|
result = test_ctx.list_api.get(
|
||||||
config_injector({
|
test_ctx.context_factory(
|
||||||
'privileges': {'users:list': 'regular_user'},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
})
|
|
||||||
result = user_list_api.get(
|
|
||||||
context_factory(
|
|
||||||
input={'query': 'asd', 'page': 1},
|
input={'query': 'asd', 'page': 1},
|
||||||
user=user_factory(rank='regular_user')))
|
user=test_ctx.user_factory(rank='regular_user')))
|
||||||
assert result['query'] == 'asd'
|
assert result['query'] == 'asd'
|
||||||
assert result['page'] == 1
|
assert result['page'] == 1
|
||||||
assert result['pageSize'] == 100
|
assert result['pageSize'] == 100
|
||||||
assert result['total'] == 0
|
assert result['total'] == 0
|
||||||
assert [u['name'] for u in result['users']] == []
|
assert [u['name'] for u in result['users']] == []
|
||||||
|
|
||||||
def test_retrieving_single(
|
def test_retrieving_single(test_ctx):
|
||||||
session,
|
test_ctx.session.add(test_ctx.user_factory(name='u1', rank='regular_user'))
|
||||||
config_injector,
|
result = test_ctx.detail_api.get(
|
||||||
context_factory,
|
test_ctx.context_factory(
|
||||||
user_factory,
|
|
||||||
user_detail_api):
|
|
||||||
config_injector({
|
|
||||||
'privileges': {'users:view': 'regular_user'},
|
|
||||||
'thumbnails': {'avatar_width': 200},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'rank_names': {},
|
|
||||||
})
|
|
||||||
user = user_factory(name='u1', rank='regular_user')
|
|
||||||
session.add(user)
|
|
||||||
result = user_detail_api.get(
|
|
||||||
context_factory(
|
|
||||||
input={'query': '', 'page': 1},
|
input={'query': '', 'page': 1},
|
||||||
user=user_factory(rank='regular_user')),
|
user=test_ctx.user_factory(rank='regular_user')),
|
||||||
'u1')
|
'u1')
|
||||||
assert result['user']['id'] == user.user_id
|
assert result == {
|
||||||
assert result['user']['name'] == 'u1'
|
'user': {
|
||||||
assert result['user']['rank'] == 'regular_user'
|
'name': 'u1',
|
||||||
assert result['user']['creationTime'] == datetime(1997, 1, 1)
|
'rank': 'regular_user',
|
||||||
assert result['user']['lastLoginTime'] == None
|
'rankName': 'Peasant',
|
||||||
assert result['user']['avatarStyle'] == 'gravatar'
|
'creationTime': datetime.datetime(1997, 1, 1),
|
||||||
|
'lastLoginTime': None,
|
||||||
|
'avatarStyle': 'gravatar',
|
||||||
|
'avatarUrl': 'http://gravatar.com/avatar/' +
|
||||||
|
'275876e34cf609db118f3d84b799a790?d=retro&s=200',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def test_retrieving_non_existing(
|
def test_retrieving_non_existing(test_ctx):
|
||||||
context_factory, config_injector, user_factory, user_detail_api):
|
with pytest.raises(users.UserNotFoundError):
|
||||||
config_injector({
|
test_ctx.detail_api.get(
|
||||||
'privileges': {'users:view': 'regular_user'},
|
test_ctx.context_factory(
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
})
|
|
||||||
with pytest.raises(errors.NotFoundError):
|
|
||||||
user_detail_api.get(
|
|
||||||
context_factory(
|
|
||||||
input={'query': '', 'page': 1},
|
input={'query': '', 'page': 1},
|
||||||
user=user_factory(rank='regular_user')),
|
user=test_ctx.user_factory(rank='regular_user')),
|
||||||
'-')
|
'-')
|
||||||
|
|
||||||
def test_retrieving_single_without_privileges(
|
def test_retrieving_single_without_privileges(test_ctx):
|
||||||
context_factory, config_injector, user_factory, user_detail_api):
|
|
||||||
config_injector({
|
|
||||||
'privileges': {'users:view': 'regular_user'},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
})
|
|
||||||
with pytest.raises(errors.AuthError):
|
with pytest.raises(errors.AuthError):
|
||||||
user_detail_api.get(
|
test_ctx.detail_api.get(
|
||||||
context_factory(
|
test_ctx.context_factory(
|
||||||
input={'query': '', 'page': 1},
|
input={'query': '', 'page': 1},
|
||||||
user=user_factory(rank='anonymous')),
|
user=test_ctx.user_factory(rank='anonymous')),
|
||||||
'-')
|
'-')
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
|
import datetime
|
||||||
import pytest
|
import pytest
|
||||||
from szurubooru import api, db, errors
|
from szurubooru import api, config, db, errors
|
||||||
from szurubooru.util import auth
|
from szurubooru.util import auth, misc, users
|
||||||
|
|
||||||
|
def get_user(session, name):
|
||||||
|
return session.query(db.User).filter_by(name=name).first()
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user_detail_api():
|
def test_ctx(
|
||||||
return api.UserDetailApi()
|
session, config_injector, context_factory, user_factory):
|
||||||
|
|
||||||
def test_updating_user(
|
|
||||||
session,
|
|
||||||
config_injector,
|
|
||||||
context_factory,
|
|
||||||
user_factory,
|
|
||||||
user_detail_api):
|
|
||||||
config_injector({
|
config_injector({
|
||||||
'secret': '',
|
'secret': '',
|
||||||
'user_name_regex': '.{3,}',
|
'user_name_regex': '.{3,}',
|
||||||
'password_regex': '.{3,}',
|
'password_regex': '.{3,}',
|
||||||
'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': {
|
'privileges': {
|
||||||
|
@ -25,12 +22,25 @@ def test_updating_user(
|
||||||
'users:edit:self:email': 'regular_user',
|
'users:edit:self:email': 'regular_user',
|
||||||
'users:edit:self:rank': 'mod',
|
'users:edit:self:rank': 'mod',
|
||||||
'users:edit:self:avatar': 'mod',
|
'users:edit:self:avatar': 'mod',
|
||||||
|
'users:edit:any:name': 'mod',
|
||||||
|
'users:edit:any:pass': 'mod',
|
||||||
|
'users:edit:any:email': 'mod',
|
||||||
|
'users:edit:any:rank': 'admin',
|
||||||
|
'users:edit:any:avatar': 'admin',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
user = user_factory(name='u1', rank='admin')
|
ret = misc.dotdict()
|
||||||
session.add(user)
|
ret.session = session
|
||||||
user_detail_api.put(
|
ret.context_factory = context_factory
|
||||||
context_factory(
|
ret.user_factory = user_factory
|
||||||
|
ret.api = api.UserDetailApi()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def test_updating_user(test_ctx):
|
||||||
|
user = test_ctx.user_factory(name='u1', rank='admin')
|
||||||
|
test_ctx.session.add(user)
|
||||||
|
result = test_ctx.api.put(
|
||||||
|
test_ctx.context_factory(
|
||||||
input={
|
input={
|
||||||
'name': 'chewie',
|
'name': 'chewie',
|
||||||
'email': 'asd@asd.asd',
|
'email': 'asd@asd.asd',
|
||||||
|
@ -40,7 +50,20 @@ def test_updating_user(
|
||||||
},
|
},
|
||||||
user=user),
|
user=user),
|
||||||
'u1')
|
'u1')
|
||||||
user = session.query(db.User).filter_by(name='chewie').one()
|
assert result == {
|
||||||
|
'user': {
|
||||||
|
'avatarStyle': 'gravatar',
|
||||||
|
'avatarUrl': 'http://gravatar.com/avatar/' +
|
||||||
|
'6f370c8c7109534c3d5c394123a477d7?d=retro&s=200',
|
||||||
|
'creationTime': datetime.datetime(1997, 1, 1),
|
||||||
|
'lastLoginTime': None,
|
||||||
|
'email': 'asd@asd.asd',
|
||||||
|
'name': 'chewie',
|
||||||
|
'rank': 'mod',
|
||||||
|
'rankName': 'Unknown',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user = get_user(test_ctx.session, 'chewie')
|
||||||
assert user.name == 'chewie'
|
assert user.name == 'chewie'
|
||||||
assert user.email == 'asd@asd.asd'
|
assert user.email == 'asd@asd.asd'
|
||||||
assert user.rank == 'mod'
|
assert user.rank == 'mod'
|
||||||
|
@ -48,192 +71,98 @@ def test_updating_user(
|
||||||
assert auth.is_valid_password(user, 'oks') is True
|
assert auth.is_valid_password(user, 'oks') is True
|
||||||
assert auth.is_valid_password(user, 'invalid') is False
|
assert auth.is_valid_password(user, 'invalid') is False
|
||||||
|
|
||||||
def test_update_changing_nothing(
|
def test_update_changing_nothing(test_ctx):
|
||||||
session,
|
user = test_ctx.user_factory(name='u1', rank='admin')
|
||||||
config_injector,
|
test_ctx.session.add(user)
|
||||||
context_factory,
|
test_ctx.api.put(test_ctx.context_factory(user=user), 'u1')
|
||||||
user_factory,
|
user = get_user(test_ctx.session, 'u1')
|
||||||
user_detail_api):
|
|
||||||
config_injector({
|
|
||||||
'thumbnails': {'avatar_width': 200},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'rank_names': {},
|
|
||||||
})
|
|
||||||
user = user_factory(name='u1', rank='admin')
|
|
||||||
session.add(user)
|
|
||||||
user_detail_api.put(context_factory(user=user), 'u1')
|
|
||||||
user = session.query(db.User).filter_by(name='u1').one()
|
|
||||||
assert user.name == 'u1'
|
assert user.name == 'u1'
|
||||||
assert user.email == 'dummy'
|
assert user.email == 'dummy'
|
||||||
assert user.rank == 'admin'
|
assert user.rank == 'admin'
|
||||||
|
|
||||||
def test_updating_non_existing_user(
|
def test_updating_non_existing_user(test_ctx):
|
||||||
session,
|
user = test_ctx.user_factory(name='u1', rank='admin')
|
||||||
config_injector,
|
test_ctx.session.add(user)
|
||||||
context_factory,
|
with pytest.raises(users.UserNotFoundError):
|
||||||
user_factory,
|
test_ctx.api.put(test_ctx.context_factory(user=user), 'u2')
|
||||||
user_detail_api):
|
|
||||||
config_injector({
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
})
|
|
||||||
user = user_factory(name='u1', rank='admin')
|
|
||||||
session.add(user)
|
|
||||||
with pytest.raises(errors.NotFoundError):
|
|
||||||
user_detail_api.put(context_factory(user=user), 'u2')
|
|
||||||
|
|
||||||
def test_removing_email(
|
def test_removing_email(test_ctx):
|
||||||
session,
|
user = test_ctx.user_factory(name='u1', rank='admin')
|
||||||
config_injector,
|
test_ctx.session.add(user)
|
||||||
context_factory,
|
test_ctx.api.put(
|
||||||
user_factory,
|
test_ctx.context_factory(input={'email': ''}, user=user), 'u1')
|
||||||
user_detail_api):
|
assert get_user(test_ctx.session, 'u1').email is None
|
||||||
config_injector({
|
|
||||||
'thumbnails': {'avatar_width': 200},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'rank_names': {},
|
|
||||||
'privileges': {'users:edit:self:email': 'regular_user'},
|
|
||||||
})
|
|
||||||
user = user_factory(name='u1', rank='admin')
|
|
||||||
session.add(user)
|
|
||||||
user_detail_api.put(
|
|
||||||
context_factory(input={'email': ''}, user=user), 'u1')
|
|
||||||
assert session.query(db.User).filter_by(name='u1').one().email is None
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('request', [
|
@pytest.mark.parametrize('input', [
|
||||||
{'name': '.'},
|
{'name': '.'},
|
||||||
|
{'name': 'x' * 51},
|
||||||
{'password': '.'},
|
{'password': '.'},
|
||||||
{'rank': '.'},
|
{'rank': '.'},
|
||||||
{'email': '.'},
|
{'email': '.'},
|
||||||
|
{'email': 'x' * 65},
|
||||||
{'avatarStyle': 'manual'},
|
{'avatarStyle': 'manual'},
|
||||||
])
|
])
|
||||||
def test_invalid_inputs(
|
def test_invalid_inputs(test_ctx, input):
|
||||||
session,
|
user = test_ctx.user_factory(name='u1', rank='admin')
|
||||||
config_injector,
|
test_ctx.session.add(user)
|
||||||
context_factory,
|
|
||||||
user_factory,
|
|
||||||
user_detail_api,
|
|
||||||
request):
|
|
||||||
config_injector({
|
|
||||||
'user_name_regex': '.{3,}',
|
|
||||||
'password_regex': '.{3,}',
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'privileges': {
|
|
||||||
'users:edit:self:name': 'regular_user',
|
|
||||||
'users:edit:self:pass': 'regular_user',
|
|
||||||
'users:edit:self:email': 'regular_user',
|
|
||||||
'users:edit:self:rank': 'mod',
|
|
||||||
'users:edit:self:avatar': 'mod',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
user = user_factory(name='u1', rank='admin')
|
|
||||||
session.add(user)
|
|
||||||
with pytest.raises(errors.ValidationError):
|
with pytest.raises(errors.ValidationError):
|
||||||
user_detail_api.put(context_factory(input=request, user=user), 'u1')
|
test_ctx.api.put(
|
||||||
|
test_ctx.context_factory(input=input, user=user), 'u1')
|
||||||
|
|
||||||
@pytest.mark.parametrize('request', [
|
@pytest.mark.parametrize('input', [
|
||||||
{'name': 'whatever'},
|
{'name': 'whatever'},
|
||||||
{'email': 'whatever'},
|
{'email': 'whatever'},
|
||||||
{'rank': 'whatever'},
|
{'rank': 'whatever'},
|
||||||
{'password': 'whatever'},
|
{'password': 'whatever'},
|
||||||
{'avatarStyle': 'whatever'},
|
{'avatarStyle': 'whatever'},
|
||||||
])
|
])
|
||||||
def test_user_trying_to_update_someone_else(
|
def test_user_trying_to_update_someone_else(test_ctx, input):
|
||||||
session,
|
user1 = test_ctx.user_factory(name='u1', rank='regular_user')
|
||||||
config_injector,
|
user2 = test_ctx.user_factory(name='u2', rank='regular_user')
|
||||||
context_factory,
|
test_ctx.session.add_all([user1, user2])
|
||||||
user_factory,
|
|
||||||
user_detail_api,
|
|
||||||
request):
|
|
||||||
config_injector({
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'privileges': {
|
|
||||||
'users:edit:any:name': 'mod',
|
|
||||||
'users:edit:any:pass': 'mod',
|
|
||||||
'users:edit:any:email': 'mod',
|
|
||||||
'users:edit:any:rank': 'admin',
|
|
||||||
'users:edit:any:avatar': 'admin',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
user1 = user_factory(name='u1', rank='regular_user')
|
|
||||||
user2 = user_factory(name='u2', rank='regular_user')
|
|
||||||
session.add_all([user1, user2])
|
|
||||||
with pytest.raises(errors.AuthError):
|
with pytest.raises(errors.AuthError):
|
||||||
user_detail_api.put(
|
test_ctx.api.put(
|
||||||
context_factory(input=request, user=user1), user2.name)
|
test_ctx.context_factory(input=input, user=user1), user2.name)
|
||||||
|
|
||||||
def test_user_trying_to_become_someone_else(
|
def test_user_trying_to_become_someone_else(test_ctx):
|
||||||
session,
|
user1 = test_ctx.user_factory(name='me', rank='regular_user')
|
||||||
config_injector,
|
user2 = test_ctx.user_factory(name='her', rank='regular_user')
|
||||||
context_factory,
|
test_ctx.session.add_all([user1, user2])
|
||||||
user_factory,
|
with pytest.raises(users.UserAlreadyExistsError):
|
||||||
user_detail_api):
|
test_ctx.api.put(
|
||||||
config_injector({
|
test_ctx.context_factory(input={'name': 'her'}, user=user1),
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'privileges': {'users:edit:self:name': 'regular_user'},
|
|
||||||
})
|
|
||||||
user1 = user_factory(name='me', rank='regular_user')
|
|
||||||
user2 = user_factory(name='her', rank='regular_user')
|
|
||||||
session.add_all([user1, user2])
|
|
||||||
with pytest.raises(errors.IntegrityError):
|
|
||||||
user_detail_api.put(
|
|
||||||
context_factory(input={'name': 'her'}, user=user1),
|
|
||||||
'me')
|
'me')
|
||||||
with pytest.raises(errors.IntegrityError):
|
with pytest.raises(users.UserAlreadyExistsError):
|
||||||
user_detail_api.put(
|
test_ctx.api.put(
|
||||||
context_factory(input={'name': 'HER'}, user=user1), 'me')
|
test_ctx.context_factory(input={'name': 'HER'}, user=user1), 'me')
|
||||||
|
|
||||||
def test_mods_trying_to_become_admin(
|
def test_mods_trying_to_become_admin(test_ctx):
|
||||||
session,
|
user1 = test_ctx.user_factory(name='u1', rank='mod')
|
||||||
config_injector,
|
user2 = test_ctx.user_factory(name='u2', rank='mod')
|
||||||
context_factory,
|
test_ctx.session.add_all([user1, user2])
|
||||||
user_factory,
|
context = test_ctx.context_factory(input={'rank': 'admin'}, user=user1)
|
||||||
user_detail_api):
|
|
||||||
config_injector({
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'privileges': {
|
|
||||||
'users:edit:self:rank': 'mod',
|
|
||||||
'users:edit:any:rank': 'admin',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
user1 = user_factory(name='u1', rank='mod')
|
|
||||||
user2 = user_factory(name='u2', rank='mod')
|
|
||||||
session.add_all([user1, user2])
|
|
||||||
context = context_factory(input={'rank': 'admin'}, user=user1)
|
|
||||||
with pytest.raises(errors.AuthError):
|
with pytest.raises(errors.AuthError):
|
||||||
user_detail_api.put(context, user1.name)
|
test_ctx.api.put(context, user1.name)
|
||||||
with pytest.raises(errors.AuthError):
|
with pytest.raises(errors.AuthError):
|
||||||
user_detail_api.put(context, user2.name)
|
test_ctx.api.put(context, user2.name)
|
||||||
|
|
||||||
def test_uploading_avatar(
|
def test_uploading_avatar(test_ctx, tmpdir):
|
||||||
tmpdir,
|
config.config['data_dir'] = str(tmpdir.mkdir('data'))
|
||||||
session,
|
config.config['data_url'] = 'http://example.com/data/'
|
||||||
config_injector,
|
|
||||||
context_factory,
|
user = test_ctx.user_factory(name='u1', rank='mod')
|
||||||
user_factory,
|
test_ctx.session.add(user)
|
||||||
user_detail_api):
|
|
||||||
config_injector({
|
|
||||||
'data_dir': str(tmpdir.mkdir('data')),
|
|
||||||
'data_url': 'http://example.com/data/',
|
|
||||||
'thumbnails': {'avatar_width': 200, 'avatar_height': 200},
|
|
||||||
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'rank_names': {},
|
|
||||||
'privileges': {'users:edit:self:avatar': 'mod'},
|
|
||||||
})
|
|
||||||
user = user_factory(name='u1', rank='mod')
|
|
||||||
session.add(user)
|
|
||||||
empty_pixel = \
|
empty_pixel = \
|
||||||
b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00' \
|
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'\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'
|
b'\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b'
|
||||||
response = user_detail_api.put(
|
response = test_ctx.api.put(
|
||||||
context_factory(
|
test_ctx.context_factory(
|
||||||
input={'avatarStyle': 'manual'},
|
input={'avatarStyle': 'manual'},
|
||||||
files={'avatar': empty_pixel},
|
files={'avatar': empty_pixel},
|
||||||
user=user),
|
user=user),
|
||||||
'u1')
|
'u1')
|
||||||
user = session.query(db.User).filter_by(name='u1').one()
|
user = get_user(test_ctx.session, 'u1')
|
||||||
assert user.avatar_style == user.AVATAR_MANUAL
|
assert user.avatar_style == user.AVATAR_MANUAL
|
||||||
assert response['user']['avatarUrl'] == \
|
assert response['user']['avatarUrl'] == \
|
||||||
'http://example.com/data/avatars/u1.jpg'
|
'http://example.com/data/avatars/u1.jpg'
|
||||||
|
|
||||||
# TODO: test too long name
|
|
||||||
|
|
|
@ -70,3 +70,6 @@ def icase_unique(source):
|
||||||
target.append(source_item)
|
target.append(source_item)
|
||||||
target_low.append(source_item.lower())
|
target_low.append(source_item.lower())
|
||||||
return target
|
return target
|
||||||
|
|
||||||
|
def value_exceeds_column_size(value, column):
|
||||||
|
return len(value) > column.property.columns[0].type.length
|
||||||
|
|
|
@ -4,27 +4,11 @@ import sqlalchemy
|
||||||
from szurubooru import config, db, errors
|
from szurubooru import config, db, errors
|
||||||
from szurubooru.util import misc
|
from szurubooru.util import misc
|
||||||
|
|
||||||
class TagNotFoundError(errors.NotFoundError):
|
class TagNotFoundError(errors.NotFoundError): pass
|
||||||
def __init__(self, tag):
|
class TagAlreadyExistsError(errors.ValidationError): pass
|
||||||
super().__init__('Tag %r not found')
|
class InvalidNameError(errors.ValidationError): pass
|
||||||
|
class InvalidCategoryError(errors.ValidationError): pass
|
||||||
class TagAlreadyExistsError(errors.ValidationError):
|
class RelationError(errors.ValidationError): pass
|
||||||
def __init__(self):
|
|
||||||
super().__init__('One of names is already used by another tag.')
|
|
||||||
|
|
||||||
class InvalidNameError(errors.ValidationError):
|
|
||||||
def __init__(self, message):
|
|
||||||
super().__init__(message)
|
|
||||||
|
|
||||||
class InvalidCategoryError(errors.ValidationError):
|
|
||||||
def __init__(self, category, valid_categories):
|
|
||||||
super().__init__(
|
|
||||||
'Category %r is invalid. Valid categories: %r.' % (
|
|
||||||
category, valid_categories))
|
|
||||||
|
|
||||||
class RelationError(errors.ValidationError):
|
|
||||||
def __init__(self, message):
|
|
||||||
super().__init__(message)
|
|
||||||
|
|
||||||
def _verify_name_validity(name):
|
def _verify_name_validity(name):
|
||||||
name_regex = config.config['tag_name_regex']
|
name_regex = config.config['tag_name_regex']
|
||||||
|
@ -82,14 +66,16 @@ def create_tag(session, names, category, suggestions, implications):
|
||||||
tag = db.Tag()
|
tag = db.Tag()
|
||||||
tag.creation_time = datetime.datetime.now()
|
tag.creation_time = datetime.datetime.now()
|
||||||
update_names(session, tag, names)
|
update_names(session, tag, names)
|
||||||
update_category(session, tag, category)
|
update_category(tag, category)
|
||||||
update_suggestions(session, tag, suggestions)
|
update_suggestions(session, tag, suggestions)
|
||||||
update_implications(session, tag, implications)
|
update_implications(session, tag, implications)
|
||||||
return tag
|
return tag
|
||||||
|
|
||||||
def update_category(session, tag, category):
|
def update_category(tag, category):
|
||||||
if not category in config.config['tag_categories']:
|
if not category in config.config['tag_categories']:
|
||||||
raise InvalidCategoryError(category, config.config['tag_categories'])
|
raise InvalidCategoryError(
|
||||||
|
'Category %r is invalid. Valid categories: %r.' % (
|
||||||
|
category, config.config['tag_categories']))
|
||||||
tag.category = category
|
tag.category = category
|
||||||
|
|
||||||
def update_names(session, tag, names):
|
def update_names(session, tag, names):
|
||||||
|
@ -100,23 +86,24 @@ def update_names(session, tag, names):
|
||||||
_verify_name_validity(name)
|
_verify_name_validity(name)
|
||||||
expr = sqlalchemy.sql.false()
|
expr = sqlalchemy.sql.false()
|
||||||
for name in names:
|
for name in names:
|
||||||
|
if misc.value_exceeds_column_size(name, db.TagName.name):
|
||||||
|
raise InvalidNameError('Name is too long.')
|
||||||
expr = expr | db.TagName.name.ilike(name)
|
expr = expr | db.TagName.name.ilike(name)
|
||||||
if tag.tag_id:
|
if tag.tag_id:
|
||||||
expr = expr & (db.TagName.tag_id != tag.tag_id)
|
expr = expr & (db.TagName.tag_id != tag.tag_id)
|
||||||
existing_tags = session.query(db.TagName).filter(expr).all()
|
existing_tags = session.query(db.TagName).filter(expr).all()
|
||||||
if len(existing_tags):
|
if len(existing_tags):
|
||||||
raise TagAlreadyExistsError()
|
raise TagAlreadyExistsError(
|
||||||
|
'One of names is already used by another tag.')
|
||||||
tag_names_to_remove = []
|
tag_names_to_remove = []
|
||||||
for tag_name in tag.names:
|
for tag_name in tag.names:
|
||||||
if tag_name.name.lower() not in [name.lower() for name in names]:
|
if not _check_name_intersection([tag_name.name], names):
|
||||||
tag_names_to_remove.append(tag_name)
|
tag_names_to_remove.append(tag_name)
|
||||||
for tag_name in tag_names_to_remove:
|
for tag_name in tag_names_to_remove:
|
||||||
tag.names.remove(tag_name)
|
tag.names.remove(tag_name)
|
||||||
for name in names:
|
for name in names:
|
||||||
if name.lower() not in [tag_name.name.lower() for tag_name in tag.names]:
|
if not _check_name_intersection(_get_plain_names(tag), [name]):
|
||||||
tag_name = db.TagName(name)
|
tag.names.append(db.TagName(name))
|
||||||
session.add(tag_name)
|
|
||||||
tag.names.append(tag_name)
|
|
||||||
|
|
||||||
def update_implications(session, tag, relations):
|
def update_implications(session, tag, relations):
|
||||||
if _check_name_intersection(_get_plain_names(tag), relations):
|
if _check_name_intersection(_get_plain_names(tag), relations):
|
||||||
|
|
|
@ -1,79 +1,16 @@
|
||||||
|
import datetime
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from szurubooru import config, db, errors
|
from szurubooru import config, db, errors
|
||||||
from szurubooru.util import auth, misc, files, images
|
from szurubooru.util import auth, misc, files, images
|
||||||
|
|
||||||
def create_user(session, name, password, email):
|
class UserNotFoundError(errors.NotFoundError): pass
|
||||||
user = db.User()
|
class UserAlreadyExistsError(errors.ValidationError): pass
|
||||||
update_name(user, name)
|
class InvalidNameError(errors.ValidationError): pass
|
||||||
update_password(user, password)
|
class InvalidEmailError(errors.ValidationError): pass
|
||||||
update_email(user, email)
|
class InvalidPasswordError(errors.ValidationError): pass
|
||||||
if not session.query(db.User).count():
|
class InvalidRankError(errors.ValidationError): pass
|
||||||
user.rank = 'admin'
|
class InvalidAvatarError(errors.ValidationError): pass
|
||||||
else:
|
|
||||||
user.rank = config.config['default_rank']
|
|
||||||
user.creation_time = datetime.now()
|
|
||||||
user.avatar_style = db.User.AVATAR_GRAVATAR
|
|
||||||
return user
|
|
||||||
|
|
||||||
def update_name(user, name):
|
|
||||||
name = name.strip()
|
|
||||||
name_regex = config.config['user_name_regex']
|
|
||||||
if not re.match(name_regex, name):
|
|
||||||
raise errors.ValidationError(
|
|
||||||
'Name must satisfy regex %r.' % name_regex)
|
|
||||||
user.name = name
|
|
||||||
|
|
||||||
def update_password(user, password):
|
|
||||||
password_regex = config.config['password_regex']
|
|
||||||
if not re.match(password_regex, password):
|
|
||||||
raise errors.ValidationError(
|
|
||||||
'Password must satisfy regex %r.' % password_regex)
|
|
||||||
user.password_salt = auth.create_password()
|
|
||||||
user.password_hash = auth.get_password_hash(user.password_salt, password)
|
|
||||||
|
|
||||||
def update_email(user, email):
|
|
||||||
email = email.strip() or None
|
|
||||||
if not misc.is_valid_email(email):
|
|
||||||
raise errors.ValidationError(
|
|
||||||
'%r is not a vaild email address.' % email)
|
|
||||||
user.email = email
|
|
||||||
|
|
||||||
def update_rank(user, rank, authenticated_user):
|
|
||||||
rank = rank.strip()
|
|
||||||
available_ranks = config.config['ranks']
|
|
||||||
if not rank in available_ranks:
|
|
||||||
raise errors.ValidationError(
|
|
||||||
'Bad rank %r. Valid ranks: %r' % (rank, available_ranks))
|
|
||||||
if available_ranks.index(authenticated_user.rank) \
|
|
||||||
< available_ranks.index(rank):
|
|
||||||
raise errors.AuthError('Trying to set higher rank than your own.')
|
|
||||||
user.rank = rank
|
|
||||||
|
|
||||||
def update_avatar(user, avatar_style, avatar_content):
|
|
||||||
if avatar_style == 'gravatar':
|
|
||||||
user.avatar_style = user.AVATAR_GRAVATAR
|
|
||||||
elif avatar_style == 'manual':
|
|
||||||
user.avatar_style = user.AVATAR_MANUAL
|
|
||||||
if not avatar_content:
|
|
||||||
raise errors.ValidationError('Avatar content missing.')
|
|
||||||
image = images.Image(avatar_content)
|
|
||||||
image.resize_fill(
|
|
||||||
int(config.config['thumbnails']['avatar_width']),
|
|
||||||
int(config.config['thumbnails']['avatar_height']))
|
|
||||||
files.save('avatars/' + user.name.lower() + '.jpg', image.to_jpeg())
|
|
||||||
else:
|
|
||||||
raise errors.ValidationError('Unknown avatar style: %r' % avatar_style)
|
|
||||||
|
|
||||||
def bump_login_time(user):
|
|
||||||
user.last_login_time = datetime.now()
|
|
||||||
|
|
||||||
def reset_password(user):
|
|
||||||
password = auth.create_password()
|
|
||||||
user.password_salt = auth.create_password()
|
|
||||||
user.password_hash = auth.get_password_hash(user.password_salt, password)
|
|
||||||
return password
|
|
||||||
|
|
||||||
def get_by_name(session, name):
|
def get_by_name(session, name):
|
||||||
return session.query(db.User) \
|
return session.query(db.User) \
|
||||||
|
@ -86,3 +23,82 @@ def get_by_name_or_email(session, name_or_email):
|
||||||
(func.lower(db.User.name) == func.lower(name_or_email))
|
(func.lower(db.User.name) == func.lower(name_or_email))
|
||||||
| (func.lower(db.User.email) == func.lower(name_or_email))) \
|
| (func.lower(db.User.email) == func.lower(name_or_email))) \
|
||||||
.first()
|
.first()
|
||||||
|
|
||||||
|
def create_user(session, name, password, email, auth_user):
|
||||||
|
user = db.User()
|
||||||
|
update_name(session, user, name, auth_user)
|
||||||
|
update_password(user, password)
|
||||||
|
update_email(user, email)
|
||||||
|
if not session.query(db.User).count():
|
||||||
|
user.rank = 'admin'
|
||||||
|
else:
|
||||||
|
user.rank = config.config['default_rank']
|
||||||
|
user.creation_time = datetime.datetime.now()
|
||||||
|
user.avatar_style = db.User.AVATAR_GRAVATAR
|
||||||
|
return user
|
||||||
|
|
||||||
|
def update_name(session, user, name, auth_user):
|
||||||
|
if misc.value_exceeds_column_size(name, db.User.name):
|
||||||
|
raise InvalidNameError('User name is too long.')
|
||||||
|
other_user = get_by_name(session, 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(
|
||||||
|
'User name %r must satisfy regex %r.' % (name, name_regex))
|
||||||
|
user.name = name
|
||||||
|
|
||||||
|
def update_password(user, password):
|
||||||
|
password_regex = config.config['password_regex']
|
||||||
|
if not re.match(password_regex, password):
|
||||||
|
raise InvalidPasswordError(
|
||||||
|
'Password must satisfy regex %r.' % password_regex)
|
||||||
|
user.password_salt = auth.create_password()
|
||||||
|
user.password_hash = auth.get_password_hash(user.password_salt, password)
|
||||||
|
|
||||||
|
def update_email(user, email):
|
||||||
|
email = email.strip() or 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):
|
||||||
|
raise InvalidEmailError('E-mail is invalid.')
|
||||||
|
user.email = email
|
||||||
|
|
||||||
|
def update_rank(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):
|
||||||
|
raise errors.AuthError('Trying to set higher rank than your own.')
|
||||||
|
user.rank = rank
|
||||||
|
|
||||||
|
def update_avatar(user, avatar_style, avatar_content):
|
||||||
|
if avatar_style == 'gravatar':
|
||||||
|
user.avatar_style = user.AVATAR_GRAVATAR
|
||||||
|
elif avatar_style == 'manual':
|
||||||
|
user.avatar_style = user.AVATAR_MANUAL
|
||||||
|
if not avatar_content:
|
||||||
|
raise InvalidAvatarError('Avatar content missing.')
|
||||||
|
image = images.Image(avatar_content)
|
||||||
|
image.resize_fill(
|
||||||
|
int(config.config['thumbnails']['avatar_width']),
|
||||||
|
int(config.config['thumbnails']['avatar_height']))
|
||||||
|
files.save('avatars/' + user.name.lower() + '.jpg', image.to_jpeg())
|
||||||
|
else:
|
||||||
|
raise InvalidAvatarError(
|
||||||
|
'Avatar style %r is invalid. Valid avatar styles: %r.' % (
|
||||||
|
avatar_style, ['gravatar', 'manual']))
|
||||||
|
|
||||||
|
def bump_login_time(user):
|
||||||
|
user.last_login_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
def reset_password(user):
|
||||||
|
password = auth.create_password()
|
||||||
|
user.password_salt = auth.create_password()
|
||||||
|
user.password_hash = auth.get_password_hash(user.password_salt, password)
|
||||||
|
return password
|
||||||
|
|
Loading…
Reference in a new issue