[IMP] base_import_module: Add layer of security (#19175)
Previous to this commit, anyone could import studio customizations into a community db, this was troublesome since some people would just install studio, do their customizations and then move them into a community db, not paying anything at all. This commit fixes this by checking whether the exported views were created with studio or not and if studio is installed
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import ast
|
||||
import base64
|
||||
import logging
|
||||
import lxml
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
@@ -32,8 +34,17 @@ class IrModule(models.Model):
|
||||
values = self.get_values_from_terp(terp)
|
||||
|
||||
unmet_dependencies = set(terp['depends']).difference(installed_mods)
|
||||
|
||||
if unmet_dependencies:
|
||||
raise UserError(_("Unmet module dependencies: %s") % ', '.join(unmet_dependencies))
|
||||
if _is_studio_custom(path):
|
||||
err = _("Studio customizations require Studio")
|
||||
else:
|
||||
err = _("Unmet module dependencies: %s") % ', '.join(
|
||||
unmet_dependencies,
|
||||
)
|
||||
raise UserError(err)
|
||||
elif 'web_studio' not in installed_mods and _is_studio_custom(path):
|
||||
raise UserError(_("Studio customizations require Studio"))
|
||||
|
||||
mod = known_mods_names.get(module)
|
||||
if mod:
|
||||
@@ -52,7 +63,7 @@ class IrModule(models.Model):
|
||||
continue
|
||||
_logger.info("module %s: loading %s", module, filename)
|
||||
noupdate = False
|
||||
if filename.endswith('.csv') and kind in ('init', 'init_xml'):
|
||||
if ext == '.csv' and kind in ('init', 'init_xml'):
|
||||
noupdate = True
|
||||
pathname = opj(path, filename)
|
||||
idref = {}
|
||||
@@ -101,9 +112,9 @@ class IrModule(models.Model):
|
||||
raise UserError(_("File '%s' exceed maximum allowed file size") % zf.filename)
|
||||
|
||||
with tempdir() as module_dir:
|
||||
import odoo.modules as addons
|
||||
import odoo.modules.module as module
|
||||
try:
|
||||
addons.module.ad_paths.append(module_dir)
|
||||
module.ad_paths.append(module_dir)
|
||||
z.extractall(module_dir)
|
||||
dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))]
|
||||
for mod_name in dirs:
|
||||
@@ -117,8 +128,38 @@ class IrModule(models.Model):
|
||||
_logger.exception('Error while importing module')
|
||||
errors[mod_name] = exception_to_unicode(e)
|
||||
finally:
|
||||
addons.module.ad_paths.remove(module_dir)
|
||||
module.ad_paths.remove(module_dir)
|
||||
r = ["Successfully imported module '%s'" % mod for mod in success]
|
||||
for mod, error in errors.items():
|
||||
r.append("Error while importing module '%s': %r" % (mod, error))
|
||||
return '\n'.join(r), module_names
|
||||
|
||||
|
||||
def _is_studio_custom(path):
|
||||
"""
|
||||
Checks the to-be-imported records to see if there are any references to
|
||||
studio, which would mean that the module was created using studio
|
||||
|
||||
Returns True if any of the records contains a context with the key
|
||||
studio in it, False if none of the records do
|
||||
"""
|
||||
path = os.path.join(path, 'data')
|
||||
filenames = next(iter(os.walk(path)))[2]
|
||||
filenames = [f for f in filenames if f.lower().endswith('.xml')]
|
||||
|
||||
for filename in filenames:
|
||||
root = lxml.etree.parse(os.path.join(path, filename)).getroot()
|
||||
|
||||
for record in root:
|
||||
# there might not be a context if it's a non-studio module
|
||||
try:
|
||||
# ast.literal_eval is like eval(), but safer
|
||||
# context is a string representing a python dict
|
||||
ctx = ast.literal_eval(record.get('context'))
|
||||
# there are no cases in which studio is false
|
||||
# so just checking for its existence is enough
|
||||
if ctx and ctx.get('studio'):
|
||||
return True
|
||||
except Exception:
|
||||
continue
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user