Files
tryton/trytond/protocols/wrappers.py
T
2017-01-29 01:17:08 +01:00

183 lines
5.9 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import base64
import gzip
import logging
from io import BytesIO
from functools import wraps
from werkzeug.wrappers import Request as _Request, Response
from werkzeug.utils import cached_property
from werkzeug.http import wsgi_to_bytes, bytes_to_wsgi
from werkzeug.datastructures import Authorization
from werkzeug.exceptions import abort, HTTPException
from trytond import security, backend
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.config import config
from trytond.cache import Cache
logger = logging.getLogger(__name__)
class Request(_Request):
view_args = None
@property
def decoded_data(self):
if self.content_encoding == 'gzip':
zipfile = gzip.GzipFile(fileobj=BytesIO(self.data), mode='rb')
return zipfile.read()
else:
return self.data
@property
def parsed_data(self):
return self.data
@property
def rpc_method(self):
return
@property
def rpc_params(self):
return
@cached_property
def authorization(self):
authorization = super(Request, self).authorization
if authorization is None:
header = self.environ.get('HTTP_AUTHORIZATION')
return parse_authorization_header(header)
return authorization
@cached_property
def user_id(self):
database_name = self.view_args['database_name']
auth = self.authorization
if not auth:
abort(401)
if auth.type == 'session':
user_id = security.check(
database_name, auth.get('userid'), auth.get('session'))
if not user_id:
abort(403)
else:
user_id = security.login(
database_name, auth.username, auth, cache=False)
if not user_id:
abort(401)
return user_id
def parse_authorization_header(value):
if not value:
return
value = wsgi_to_bytes(value)
try:
auth_type, auth_info = value.split(None, 1)
auth_type = auth_type.lower()
except ValueError:
return
if auth_type == b'session':
try:
username, userid, session = base64.b64decode(auth_info).split(
b':', 3)
userid = int(userid)
except Exception:
return
return Authorization('session', {
'username': bytes_to_wsgi(username),
'userid': userid,
'session': bytes_to_wsgi(session),
})
def with_pool(func):
@wraps(func)
def wrapper(request, database_name, *args, **kwargs):
database_list = Pool.database_list()
pool = Pool(database_name)
if database_name not in database_list:
with Transaction().start(database_name, 0, readonly=True):
pool.init()
return func(request, pool, *args, **kwargs)
return wrapper
def with_transaction(readonly=None):
def decorator(func):
@wraps(func)
def wrapper(request, pool, *args, **kwargs):
DatabaseOperationalError = backend.get('DatabaseOperationalError')
readonly_ = readonly # can not modify non local
if readonly_ is None:
if request.method in {'POST', 'PUT', 'DELETE', 'PATCH'}:
readonly_ = False
else:
readonly_ = True
for count in range(config.getint('database', 'retry'), -1, -1):
with Transaction().start(
pool.database_name, 0, readonly=readonly_
) as transaction:
try:
Cache.clean(pool.database_name)
result = func(request, pool, *args, **kwargs)
except DatabaseOperationalError:
if count and not readonly_:
transaction.rollback()
continue
logger.error('%s', request, exc_info=True)
raise
except Exception:
logger.error('%s', request, exc_info=True)
raise
# Need to commit to unlock SQLite database
transaction.commit()
Cache.resets(pool.database_name)
return result
return wrapper
return decorator
def user_application(name, json=True):
from .jsonrpc import JSONEncoder, json as json_
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
pool = Pool()
UserApplication = pool.get('res.user.application')
authorization = request.headers['Authorization']
try:
auth_type, auth_info = authorization.split(None, 1)
auth_type = auth_type.lower()
except ValueError:
abort(401)
if auth_type != b'bearer':
abort(403)
application = UserApplication.check(auth_info, name)
if not application:
abort(403)
transaction = Transaction()
# TODO language
with transaction.set_user(application.user.id), \
transaction.set_context(_check_access=True):
try:
response = func(request, *args, **kwargs)
except Exception, e:
if isinstance(e, HTTPException):
raise
logger.error('%s', request, exc_info=True)
abort(500)
if not isinstance(response, Response) and json:
response = Response(json_.dumps(response, cls=JSONEncoder),
content_type='application/json')
return response
return wrapper
return decorator