From 5a1bc137a3ec7d98e45a046706031964bebd1c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 3 Sep 2017 11:32:17 +0200 Subject: [PATCH] Add a maximum size for request issue6619 review38441002 --- CHANGELOG | 1 + doc/topics/configuration.rst | 18 ++++++++++++++++++ doc/topics/user_application.rst | 3 +++ trytond/config.py | 3 +++ trytond/protocols/wrappers.py | 17 +++++++++++------ trytond/wsgi.py | 25 ++++++++++++++++++++++++- 6 files changed, 60 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f1006bb317..c24a8ade2d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add a maximum size for request * Add support for single record report * Add support for Flat OpenDocument * Add get_email in trytond.report diff --git a/doc/topics/configuration.rst b/doc/topics/configuration.rst index fe2ccf6b6b..0b2698be80 100644 --- a/doc/topics/configuration.rst +++ b/doc/topics/configuration.rst @@ -119,6 +119,24 @@ table for translations. Default: `en` +request +------- + +max_size +~~~~~~~~ + +The maximum size in bytes of unauthenticated request (zero means no limit). + +Default: 2MB + +max_size_authenticated +~~~~~~~~~~~~~~~~~~~~~~ + +The maximum size in bytes of an authenticated request (zero means no limit). + +Default: 2GB + + cache ----- diff --git a/doc/topics/user_application.rst b/doc/topics/user_application.rst index de43e0b92a..38123cfe9d 100644 --- a/doc/topics/user_application.rst +++ b/doc/topics/user_application.rst @@ -25,6 +25,9 @@ Example:: Tryton also provides some wrappers in `trytond.protocols.wrappers` to ease the creation of such route. + - `set_max_request_size(size)`: change the default limit of the request to + the size in bytes. + - `with_pool`: which takes the first parameter as database name and replace it by the corresponding instance of the :ref:`Pool `. diff --git a/trytond/config.py b/trytond/config.py index bfdc2e4807..1206a5839b 100644 --- a/trytond/config.py +++ b/trytond/config.py @@ -52,6 +52,9 @@ class TrytonConfigParser(ConfigParser.RawConfigParser): self.set('database', 'list', 'True') self.set('database', 'retry', 5) self.set('database', 'language', 'en') + self.add_section('request') + self.set('request', 'max_size', 2 * 1024 * 1024) + self.set('request', 'max_size_authenticated', 2 * 1024 * 1024 * 1024) self.add_section('cache') self.set('cache', 'model', 200) self.set('cache', 'record', 2000) diff --git a/trytond/protocols/wrappers.py b/trytond/protocols/wrappers.py index e493a12d1a..9dfccfec27 100644 --- a/trytond/protocols/wrappers.py +++ b/trytond/protocols/wrappers.py @@ -54,20 +54,18 @@ class Request(_Request): @cached_property def user_id(self): - database_name = self.view_args['database_name'] + database_name = self.view_args.get('database_name') + if not database_name: + return None auth = self.authorization if not auth: - abort(401) + return None 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 @@ -94,6 +92,13 @@ def parse_authorization_header(value): }) +def set_max_request_size(size): + def decorator(func): + func.max_request_size = size + return func + return decorator + + def with_pool(func): @wraps(func) def wrapper(request, database_name, *args, **kwargs): diff --git a/trytond/wsgi.py b/trytond/wsgi.py index 6d4dbb0701..f6ef004863 100644 --- a/trytond/wsgi.py +++ b/trytond/wsgi.py @@ -1,5 +1,6 @@ # 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 httplib import logging import sys import traceback @@ -10,6 +11,7 @@ from werkzeug.exceptions import abort, HTTPException, InternalServerError import wrapt +from trytond.config import config from trytond.protocols.wrappers import Request from trytond.protocols.jsonrpc import JSONProtocol from trytond.protocols.xmlrpc import XMLProtocol @@ -38,12 +40,33 @@ class TrytondWSGI(object): if request.user_id: return wrapped(*args, **kwargs) else: - abort(303) + abort(httplib.UNAUTHORIZED) + + def check_request_size(self, request, size=None): + if request.method not in {'POST', 'PUT', 'PATCH'}: + return + if size is None: + if request.user_id: + max_size = config.getint( + 'request', 'max_size_authenticated') + else: + max_size = config.getint( + 'request', 'max_size') + else: + max_size = size + if max_size: + content_length = request.content_length + if content_length is None: + abort(httplib.LENGTH_REQUIRED) + elif content_length > max_size: + abort(httplib.REQUEST_ENTITY_TOO_LARGE) def dispatch_request(self, request): adapter = self.url_map.bind_to_environ(request.environ) try: endpoint, request.view_args = adapter.match() + max_request_size = getattr(endpoint, 'max_request_size', None) + self.check_request_size(request, max_request_size) return endpoint(request, **request.view_args) except HTTPException, e: return e