From b13553d2b65ae2a5ae9ecf5ee7a56f0b0ab73931 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 3 Jul 2015 12:24:51 +0200 Subject: [PATCH] [FIX] web, base: POST requests handling POST requests on routes with auth="user" are now correctly handled when the user is not connected. Request data (form and files) are stored in the session. Once the user is logged, redirect her to a specific route that will reprocess the original POST request. --- addons/web/controllers/main.py | 15 ++++++++++ openerp/http.py | 51 ++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 5edb152b871..bd5df34e6aa 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -670,6 +670,21 @@ class Proxy(http.Controller): base_url = request.httprequest.base_url return Client(request.httprequest.app, BaseResponse).get(path, base_url=base_url).data + @http.route('/web/proxy/post/', type='http', auth='user', methods=['GET']) + def post(self, path): + """Effectively execute a POST request that was hooked through user login""" + with request.session.load_request_data() as data: + if not data: + raise werkzeug.exceptions.BadRequest() + from werkzeug.test import Client + from werkzeug.wrappers import BaseResponse + base_url = request.httprequest.base_url + query_string = request.httprequest.query_string + client = Client(request.httprequest.app, BaseResponse) + headers = {'X-Openerp-Session-Id': request.session.sid} + return client.post('/' + path, base_url=base_url, query_string=query_string, + headers=headers, data=data) + class Database(http.Controller): def _render_template(self, **d): diff --git a/openerp/http.py b/openerp/http.py index 6daf9f01a24..5d019711c17 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -674,9 +674,16 @@ class HttpRequest(WebRequest): try: return super(HttpRequest, self)._handle_exception(exception) except SessionExpiredException: - if not request.params.get('noredirect'): + redirect = None + req = request.httprequest + if req.method == 'POST': + request.session.save_request_data() + redirect = '/web/proxy/post{r.path}?{r.query_string}'.format(r=req) + elif not request.params.get('noredirect'): + redirect = req.url + if redirect: query = werkzeug.urls.url_encode({ - 'redirect': request.httprequest.url, + 'redirect': redirect, }) return werkzeug.utils.redirect('/web/login?%s' % query) except werkzeug.exceptions.HTTPException, e: @@ -1170,6 +1177,46 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): saved_actions = self.get('saved_actions', {}) return saved_actions.get("actions", {}).get(key) + def save_request_data(self): + import uuid + req = request.httprequest + files = werkzeug.datastructures.MultiDict() + # NOTE we do not store files in the session itself to avoid loading them in memory. + # By storing them in the session store, we ensure every worker (even ones on other + # servers) can access them. It also allow stale files to be deleted by `session_gc`. + for f in req.files.values(): + storename = 'file-{0}-{1}'.format(self.sid, uuid.uuid4().hex) + path = os.path.join(root.session_store.path, storename) + with open(path, 'w') as fp: + f.save(fp) + files.add(f.name, (storename, f.filename, f.content_type)) + self['serialized_request_data'] = { + 'form': req.form, + 'files': files, + } + + @contextlib.contextmanager + def load_request_data(self): + data = self.pop('serialized_request_data', None) + files = werkzeug.datastructures.MultiDict() + try: + if data: + # regenerate files filenames with the current session store + for name, (storename, filename, content_type) in data['files'].iteritems(): + path = os.path.join(root.session_store.path, storename) + files.add(name, (path, filename, content_type)) + yield werkzeug.datastructures.CombinedMultiDict([data['form'], files]) + else: + yield None + finally: + # cleanup files + for f, _, _ in files.values(): + try: + os.unlink(f) + except IOError: + pass + + def session_gc(session_store): if random.random() < 0.001: # we keep session one week