server/api: move validation to service layer

This commit is contained in:
rr- 2016-04-02 10:01:27 +02:00
parent d3d2540b88
commit 06cf8c79d4
5 changed files with 40 additions and 35 deletions

View File

@ -1,8 +1,7 @@
''' Users public API. ''' ''' Users public API. '''
import re import sqlalchemy
import falcon from szurubooru.services.errors import IntegrityError, ValidationError
from szurubooru.services.errors import IntegrityError
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):
@ -20,9 +19,8 @@ def _serialize_user(authenticated_user, user):
class UserListApi(BaseApi): class UserListApi(BaseApi):
''' API for lists of users. ''' ''' API for lists of users. '''
def __init__(self, config, auth_service, user_service): def __init__(self, auth_service, user_service):
super().__init__() super().__init__()
self._config = config
self._auth_service = auth_service self._auth_service = auth_service
self._user_service = user_service self._user_service = user_service
@ -34,42 +32,26 @@ class UserListApi(BaseApi):
def post(self, context): def post(self, context):
''' Creates a new user. ''' ''' Creates a new user. '''
self._auth_service.verify_privilege(context.user, 'users:create') self._auth_service.verify_privilege(context.user, 'users:create')
name_regex = self._config['service']['user_name_regex']
password_regex = self._config['service']['password_regex']
try: try:
name = context.request['name'] name = context.request['name']
password = context.request['password'] password = context.request['password']
email = context.request['email'].strip() email = context.request['email'].strip()
if not email:
email = None
except KeyError as ex: except KeyError as ex:
raise falcon.HTTPBadRequest( raise ValidationError('Field %r not found.' % ex.args[0])
'Malformed data', 'Field %r not found' % ex.args[0])
if not re.match(name_regex, name):
raise falcon.HTTPBadRequest(
'Malformed data',
'Name must validate %r expression' % name_regex)
if not re.match(password_regex, password):
raise falcon.HTTPBadRequest(
'Malformed data',
'Password must validate %r expression' % password_regex)
user = self._user_service.create_user(
context.session, name, password, email)
try: try:
user = self._user_service.create_user(
context.session, name, password, email)
context.session.commit() context.session.commit()
except: except sqlalchemy.exc.IntegrityError:
raise IntegrityError('User %r already exists.' % name) raise IntegrityError('User %r already exists.' % name)
return {'user': _serialize_user(context.user, user)} return {'user': _serialize_user(context.user, user)}
class UserDetailApi(BaseApi): class UserDetailApi(BaseApi):
''' API for individual users. ''' ''' API for individual users. '''
def __init__(self, config, auth_service, user_service): def __init__(self, auth_service, user_service):
super().__init__() super().__init__()
self._config = config
self._auth_service = auth_service self._auth_service = auth_service
self._user_service = user_service self._user_service = user_service

View File

@ -13,10 +13,13 @@ import szurubooru.util
class _CustomRequest(falcon.Request): class _CustomRequest(falcon.Request):
context_type = szurubooru.util.dotdict context_type = szurubooru.util.dotdict
def _on_auth_error(ex, req, resp, params): def _on_auth_error(ex, request, response, params):
raise falcon.HTTPForbidden('Authentication error', str(ex)) raise falcon.HTTPForbidden('Authentication error', str(ex))
def _on_integrity_error(ex, req, resp, params): def _on_validation_error(ex, request, response, params):
raise falcon.HTTPBadRequest('Validation error', str(ex))
def _on_integrity_error(ex, request, response, params):
raise falcon.HTTPConflict('Integrity violation', ex.args[0]) raise falcon.HTTPConflict('Integrity violation', ex.args[0])
def create_app(): def create_app():
@ -41,8 +44,8 @@ def create_app():
auth_service = szurubooru.services.AuthService(config, password_service) auth_service = szurubooru.services.AuthService(config, password_service)
user_service = szurubooru.services.UserService(config, password_service) user_service = szurubooru.services.UserService(config, password_service)
user_list = szurubooru.api.UserListApi(config, auth_service, user_service) user_list = szurubooru.api.UserListApi(auth_service, user_service)
user = szurubooru.api.UserDetailApi(config, auth_service, user_service) user = szurubooru.api.UserDetailApi(auth_service, user_service)
app = falcon.API( app = falcon.API(
request_type=_CustomRequest, request_type=_CustomRequest,
@ -55,6 +58,7 @@ def create_app():
app.add_error_handler(szurubooru.services.AuthError, _on_auth_error) app.add_error_handler(szurubooru.services.AuthError, _on_auth_error)
app.add_error_handler(szurubooru.services.IntegrityError, _on_integrity_error) app.add_error_handler(szurubooru.services.IntegrityError, _on_integrity_error)
app.add_error_handler(szurubooru.services.ValidationError, _on_validation_error)
app.add_route('/users/', user_list) app.add_route('/users/', user_list)
app.add_route('/user/{user_name}', user) app.add_route('/user/{user_name}', user)

View File

@ -3,7 +3,8 @@ Middle layer between REST API and database.
All the business logic goes here. All the business logic goes here.
''' '''
from szurubooru.services.auth_service import AuthService, AuthError from szurubooru.services.auth_service import AuthService
from szurubooru.services.user_service import UserService from szurubooru.services.user_service import UserService
from szurubooru.services.password_service import PasswordService from szurubooru.services.password_service import PasswordService
from szurubooru.services.errors import AuthError, IntegrityError from szurubooru.services.errors import (
AuthError, IntegrityError, ValidationError)

View File

@ -2,8 +2,9 @@
class AuthError(RuntimeError): class AuthError(RuntimeError):
''' Generic authentication error ''' ''' Generic authentication error '''
pass
class IntegrityError(RuntimeError): class IntegrityError(RuntimeError):
''' Database integrity error ''' ''' Database integrity error (e.g. trying to edit nonexisting resource) '''
pass
class ValidationError(RuntimeError):
''' Validation error (e.g. trying to create user with invalid name) '''

View File

@ -1,6 +1,8 @@
''' Exports UserService. ''' ''' Exports UserService. '''
import re
from datetime import datetime from datetime import datetime
from szurubooru.services.errors import ValidationError
from szurubooru.model.user import User from szurubooru.model.user import User
class UserService(object): class UserService(object):
@ -9,9 +11,24 @@ class UserService(object):
def __init__(self, config, password_service): def __init__(self, config, password_service):
self._config = config self._config = config
self._password_service = password_service self._password_service = password_service
self._name_regex = self._config['service']['user_name_regex']
self._password_regex = self._config['service']['password_regex']
def create_user(self, session, name, password, email): def create_user(self, session, name, password, email):
''' Creates an user with given parameters and returns it. ''' ''' Creates an user with given parameters and returns it. '''
if not re.match(self._name_regex, name):
raise ValidationError(
'Name must satisfy regex %r.' % self._name_regex)
if not re.match(self._password_regex, password):
raise ValidationError(
'Password must satisfy regex %r.' % self._password_regex)
# prefer nulls to empty strings in the DB
if not email:
email = None
user = User() user = User()
user.name = name user.name = name
user.password = password user.password = password