summaryrefslogtreecommitdiff
path: root/odoo/addons
diff options
context:
space:
mode:
authorPedro M. Baeza <pedro.baeza@gmail.com>2018-08-27 23:36:33 +0200
committerOlivier Dony <odo@odoo.com>2018-10-03 17:48:01 +0200
commit1be50fdeafcd2b94f7b2f47d6e5bc4b6ebaceadd (patch)
tree4489771b5b5dcf02212a4dfdce4e8c2c80e3a6b5 /odoo/addons
parent376361c15c8d2be15a2579ffd0432f31d745deb8 (diff)
[ADD] *: support SVG images
Introduce official support for SVG files in the framework, including the following parts: 1. When client-side SVG images are uploaded, the content is displayed until you save using data URI scheme according RFC 2397 [1]. This scheme requires to specify content format. Using hardcoded "image/png" works for all images types except SVG. Type-sniffing is done using "magic byte" detection via the first base64 encode byte, so that the proper data URI scheme can be used. This should not cause SVG-related security problems as the file is displayed through `<img>` tag, which does not allow SVG scripting [2]. 2. Make /web/image controller compatible with SVG 3. Add support for SVG files for company logo, which uses a dedicated controller. 4. Resizing of SVG files is a no-op, as it makes little sense for a vector-based format. We also want to avoid micro-alterations to the SVG document (in "natural" viewport parameters) as we would store multiple copies of the files in the filestore. 5. Because SVG files are inherently dangerous, upload of SVG files is restricted to administrators, either by blocking it directly before saving it in the database (binary fields with attachment=False), or by neutering them to text/plain mimetype (for binary fields with attachment=True) 6. Add tests for the SVG upload cases and for the non-admin uploads. [1] https://tools.ietf.org/html/rfc2397 [2] https://www.w3.org/wiki/SVG_Security Closes #26635
Diffstat (limited to 'odoo/addons')
-rw-r--r--odoo/addons/base/models/ir_attachment.py3
-rw-r--r--odoo/addons/base/models/res_users.py2
-rw-r--r--odoo/addons/base/tests/test_mimetypes.py14
-rw-r--r--odoo/addons/test_new_api/ir.model.access.csv1
-rw-r--r--odoo/addons/test_new_api/models.py9
-rw-r--r--odoo/addons/test_new_api/tests/test_new_fields.py56
6 files changed, 83 insertions, 2 deletions
diff --git a/odoo/addons/base/models/ir_attachment.py b/odoo/addons/base/models/ir_attachment.py
index 94b88536932..3cca0bb85a7 100644
--- a/odoo/addons/base/models/ir_attachment.py
+++ b/odoo/addons/base/models/ir_attachment.py
@@ -254,7 +254,8 @@ class IrAttachment(models.Model):
def _check_contents(self, values):
mimetype = values['mimetype'] = self._compute_mimetype(values)
xml_like = 'ht' in mimetype or 'xml' in mimetype # hta, html, xhtml, etc.
- force_text = (xml_like and (not self.env.user._is_admin() or
+ user = self.env.context.get('binary_field_real_user', self.env.user)
+ force_text = (xml_like and (not user._is_system() or
self.env.context.get('attachments_mime_plainxml')))
if force_text:
values['mimetype'] = 'text/plain'
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index 247fb367334..850c21d6b3f 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -449,7 +449,7 @@ class Users(models.Model):
if values['company_id'] not in self.env.user.company_ids.ids:
del values['company_id']
# safe fields only, so we write as super-user to bypass access rights
- self = self.sudo()
+ self = self.sudo().with_context(binary_field_real_user=self.env.user)
res = super(Users, self).write(values)
if 'company_id' in values:
diff --git a/odoo/addons/base/tests/test_mimetypes.py b/odoo/addons/base/tests/test_mimetypes.py
index 2f2f1433c3e..3ca506cb744 100644
--- a/odoo/addons/base/tests/test_mimetypes.py
+++ b/odoo/addons/base/tests/test_mimetypes.py
@@ -16,6 +16,11 @@ AAAAAAAAAAAAA/9oACAEBAAEFAn//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/AX//xAAUEQE
AA/9oACAECAQE/AX//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAY/An//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBA
AE/IX//2gAMAwEAAgADAAAAEB//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEAAAAAAAAAAAAAAAAAAAAA
/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/EH//2Q=="""
+SVG = b"""PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiL
+S8vVzNDLy9EVEQgU1ZHIDIwMDAxMTAyLy9FTiIKICJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAwL0NSLVNWRy0yMDAwMTEwMi9E
+VEQvc3ZnLTIwMDAxMTAyLmR0ZCI+Cgo8c3ZnIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPgogIDxnIHRyYW5zZm9ybT0idHJ
+hbnNsYXRlKDUwLDUwKSI+CiAgICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjUwIiBzdHlsZT0iZmlsbD
+pyZWQ7IiAvPgogIDwvZz4KCjwvc3ZnPgo="""
@tagged('standard', 'at_install')
@@ -57,6 +62,15 @@ class test_guess_mimetype(unittest.TestCase):
mimetype = guess_mimetype(content, default='test')
self.assertEqual(mimetype, 'image/gif')
+ def test_mimetype_svg(self):
+ content = base64.b64decode(SVG)
+ mimetype = guess_mimetype(content, default='test')
+ self.assertTrue(mimetype.startswith('image/svg'))
+ # Tests that whitespace padded SVG are not detected as SVG
+ mimetype = guess_mimetype(b" " + content, default='test')
+ self.assertNotIn("svg", mimetype)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/odoo/addons/test_new_api/ir.model.access.csv b/odoo/addons/test_new_api/ir.model.access.csv
index 46a658c818d..c825fb51c59 100644
--- a/odoo/addons/test_new_api/ir.model.access.csv
+++ b/odoo/addons/test_new_api/ir.model.access.csv
@@ -21,3 +21,4 @@ access_test_new_api_compute_protected,access_test_new_api_compute_protected,mode
access_test_new_api_multi_compute_inverse,access_test_new_api_multi_compute_inverse,model_test_new_api_multi_compute_inverse,,1,1,1,1
access_test_new_api_recursive,access_test_new_api_recursive,model_test_new_api_recursive,,1,1,1,1
access_test_new_api_cascade,access_test_new_api_cascade,model_test_new_api_cascade,,1,1,1,1
+access_test_new_api_binary_svg,access_test_new_api_binary_svg,model_test_new_api_binary_svg,,1,1,1,1
diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py
index bc0a3e45876..043ee8a0b7d 100644
--- a/odoo/addons/test_new_api/models.py
+++ b/odoo/addons/test_new_api/models.py
@@ -459,3 +459,12 @@ class ComputeCascade(models.Model):
def _compute_baz(self):
for record in self:
record.baz = "<%s>" % (record.bar or "")
+
+
+class BinarySvg(models.Model):
+ _name = 'test_new_api.binary_svg'
+ _description = 'Test SVG upload'
+
+ name = fields.Char(required=True)
+ image_attachment = fields.Binary(attachment=True)
+ image_wo_attachment = fields.Binary(attachment=False)
diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py
index 8284f4652c6..caa4eedf4bf 100644
--- a/odoo/addons/test_new_api/tests/test_new_fields.py
+++ b/odoo/addons/test_new_api/tests/test_new_fields.py
@@ -998,6 +998,62 @@ class TestFields(common.TransactionCase):
self.assertEqual(count(message), 1)
self.assertEqual(count(message1), 0)
+ def test_90_binary_svg(self):
+ from odoo.addons.base.tests.test_mimetypes import SVG
+ # This should work without problems
+ self.env['test_new_api.binary_svg'].create({
+ 'name': 'Test without attachment',
+ 'image_wo_attachment': SVG,
+ })
+ # And this gives error
+ with self.assertRaises(UserError):
+ self.env['test_new_api.binary_svg'].sudo(
+ self.env.ref('base.user_demo'),
+ ).create({
+ 'name': 'Test without attachment',
+ 'image_wo_attachment': SVG,
+ })
+
+ def test_91_binary_svg_attachment(self):
+ from odoo.addons.base.tests.test_mimetypes import SVG
+ # This doesn't neuter SVG with admin
+ record = self.env['test_new_api.binary_svg'].create({
+ 'name': 'Test without attachment',
+ 'image_attachment': SVG,
+ })
+ attachment = self.env['ir.attachment'].search([
+ ('res_model', '=', record._name),
+ ('res_field', '=', 'image_attachment'),
+ ('res_id', '=', record.id),
+ ])
+ self.assertEqual(attachment.mimetype, 'image/svg+xml')
+ # ...but this should be neutered with demo user
+ record = self.env['test_new_api.binary_svg'].sudo(
+ self.env.ref('base.user_demo'),
+ ).create({
+ 'name': 'Test without attachment',
+ 'image_attachment': SVG,
+ })
+ attachment = self.env['ir.attachment'].search([
+ ('res_model', '=', record._name),
+ ('res_field', '=', 'image_attachment'),
+ ('res_id', '=', record.id),
+ ])
+ self.assertEqual(attachment.mimetype, 'text/plain')
+
+ def test_92_binary_self_avatar_svg(self):
+ from odoo.addons.base.tests.test_mimetypes import SVG
+ demo_user = self.env.ref('base.user_demo')
+ # User demo changes his own avatar
+ demo_user.sudo(demo_user).image = SVG
+ # The SVG file should have been neutered
+ attachment = self.env['ir.attachment'].search([
+ ('res_model', '=', demo_user.partner_id._name),
+ ('res_field', '=', 'image'),
+ ('res_id', '=', demo_user.partner_id.id),
+ ])
+ self.assertEqual(attachment.mimetype, 'text/plain')
+
class TestX2many(common.TransactionCase):
def test_search_many2many(self):