From 2257583bb5aeb8d165f8bc6e872a671fbcd6a1a9 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 21 Sep 2017 23:42:45 +0200 Subject: [PATCH] [ADD] ir.attachment: add token field for external access Introduce a new attachment field (access_token) to allow external unauthenticated access. This will be an opaque unique number (typically a UUID) that should be provided via an appropriate controller, for unauthenticated display. The field is intended to be NULL unless unauthenticated access has been allowed, in which case a value will be set for the access_token. This could be used e.g. for allowing access to images within mailings, even when the recipient is not logged in (which is sometimes entirely impossible, when email providers use restricted proxy servers to load images) Note 1: this is still a work-in-progress, but serves to freeze the API. The implementation of the access check and provisioning of the new field will be added later. Note 2: namimg collisions with the file download token prevent the use of a shorter 'token' parameter for download routes. Apologies for the late (and incomplete) addition in saas-18 :-/ --- addons/web/controllers/main.py | 27 ++++++++++++++++++++------- addons/website/models/ir_http.py | 10 ++++++++-- odoo/addons/base/ir/ir_attachment.py | 3 +++ odoo/addons/base/ir/ir_http.py | 6 +++++- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 94e88af0900..84e5b524343 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -417,10 +417,13 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False): res["children"] = kids return res -def binary_content(xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None): +def binary_content(xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, + filename=None, filename_field='datas_fname', download=False, mimetype=None, + default_mimetype='application/octet-stream', access_token=None, env=None): return request.registry['ir.http'].binary_content( - xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, - download=download, mimetype=mimetype, default_mimetype=default_mimetype, env=env) + xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, + filename_field=filename_field, download=download, mimetype=mimetype, + default_mimetype=default_mimetype, access_token=access_token, env=env) #---------------------------------------------------------- # Odoo Web web Controllers @@ -971,8 +974,13 @@ class Binary(http.Controller): '/web/content/-/', '/web/content///', '/web/content////'], type='http', auth="public") - def content_common(self, xmlid=None, model='ir.attachment', id=None, field='datas', filename=None, filename_field='datas_fname', unique=None, mimetype=None, download=None, data=None, token=None): - status, headers, content = binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype) + def content_common(self, xmlid=None, model='ir.attachment', id=None, field='datas', + filename=None, filename_field='datas_fname', unique=None, mimetype=None, + download=None, data=None, token=None, access_token=None): + status, headers, content = binary_content( + xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, + filename_field=filename_field, download=download, mimetype=mimetype, + access_token=access_token) if status == 304: response = werkzeug.wrappers.Response(status=status, headers=headers) elif status == 301: @@ -1004,8 +1012,13 @@ class Binary(http.Controller): '/web/image/-/', '/web/image/-/x', '/web/image/-/x/'], type='http', auth="public") - def content_image(self, xmlid=None, model='ir.attachment', id=None, field='datas', filename_field='datas_fname', unique=None, filename=None, mimetype=None, download=None, width=0, height=0, crop=False): - status, headers, content = binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype='image/png') + def content_image(self, xmlid=None, model='ir.attachment', id=None, field='datas', + filename_field='datas_fname', unique=None, filename=None, mimetype=None, + download=None, width=0, height=0, crop=False, access_token=None): + status, headers, content = binary_content( + xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, + filename_field=filename_field, download=download, mimetype=mimetype, + default_mimetype='image/png', access_token=access_token) if status == 304: return werkzeug.wrappers.Response(status=304, headers=headers) elif status == 301: diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 6853ff29422..ffba3a0bc6c 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -202,7 +202,10 @@ class Http(models.AbstractModel): return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') @classmethod - def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None): + def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas', + unique=False, filename=None, filename_field='datas_fname', download=False, + mimetype=None, default_mimetype='application/octet-stream', + access_token=None, env=None): env = env or request.env obj = None if xmlid: @@ -212,7 +215,10 @@ class Http(models.AbstractModel): if obj and 'website_published' in obj._fields: if env[obj._name].sudo().search([('id', '=', obj.id), ('website_published', '=', True)]): env = env(user=SUPERUSER_ID) - return super(Http, cls).binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype=default_mimetype, env=env) + return super(Http, cls).binary_content( + xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, + filename_field=filename_field, download=download, mimetype=mimetype, + default_mimetype=default_mimetype, access_token=access_token, env=env) class ModelConverter(ModelConverter): diff --git a/odoo/addons/base/ir/ir_attachment.py b/odoo/addons/base/ir/ir_attachment.py index 41b85913ed9..19409a3b78b 100644 --- a/odoo/addons/base/ir/ir_attachment.py +++ b/odoo/addons/base/ir/ir_attachment.py @@ -279,6 +279,9 @@ class IrAttachment(models.Model): url = fields.Char('Url', index=True, size=1024) public = fields.Boolean('Is public document') + # for external access + access_token = fields.Char('Access Token') + # the field 'datas' is computed and may use the other fields below datas = fields.Binary(string='File Content', compute='_compute_datas', inverse='_inverse_datas') db_datas = fields.Binary('Database Data') diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py index 255f034d994..85af389b0fb 100644 --- a/odoo/addons/base/ir/ir_http.py +++ b/odoo/addons/base/ir/ir_http.py @@ -248,7 +248,10 @@ class IrHttp(models.AbstractModel): return content_disposition(filename) @classmethod - def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None): + def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas', + unique=False, filename=None, filename_field='datas_fname', download=False, + mimetype=None, default_mimetype='application/octet-stream', + access_token=None, env=None): """ Get file, attachment or downloadable content If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the @@ -265,6 +268,7 @@ class IrHttp(models.AbstractModel): :param bool download: apply headers to download the file :param str mimetype: mintype of the field (for headers) :param str default_mimetype: default mintype if no mintype found + :param str access_token: optional token for unauthenticated access :param Environment env: by default use request.env :returns: (status, headers, content) """