From fe00f4c31928e22875f36f2f9998df49c2c7fa43 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Mon, 11 Apr 2022 11:36:15 +0200 Subject: [PATCH 01/30] [ADD] pre-review version --- auditlog/:w | 13 ++ auditlog/models/rule.py | 2 + auditlog/views/auditlog_view.xml | 2 +- auditlog_security/README.rst | 112 +++++++++++++++++ auditlog_security/__init__.py | 3 + auditlog_security/__manifest__.py | 21 ++++ auditlog_security/demo/auditlog_rule.xml | 10 ++ auditlog_security/models/__init__.py | 5 + .../models/auditlog_autovacuum.py | 14 +++ auditlog_security/models/auditlog_rule.py | 71 +++++++++++ auditlog_security/models/ir_rule.py | 52 ++++++++ auditlog_security/readme/CONTRIBUTORS.rst | 1 + auditlog_security/readme/CREDITS.rst | 0 auditlog_security/readme/DESCRIPTION.rst | 3 + auditlog_security/readme/ROADMAP.rst | 1 + auditlog_security/readme/USAGE.rst | 10 ++ .../security/ir.model.access.csv | 3 + auditlog_security/views/auditlog_view.xml | 114 ++++++++++++++++++ 18 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 auditlog/:w create mode 100644 auditlog_security/README.rst create mode 100644 auditlog_security/__init__.py create mode 100644 auditlog_security/__manifest__.py create mode 100644 auditlog_security/demo/auditlog_rule.xml create mode 100644 auditlog_security/models/__init__.py create mode 100644 auditlog_security/models/auditlog_autovacuum.py create mode 100644 auditlog_security/models/auditlog_rule.py create mode 100644 auditlog_security/models/ir_rule.py create mode 100644 auditlog_security/readme/CONTRIBUTORS.rst create mode 100644 auditlog_security/readme/CREDITS.rst create mode 100644 auditlog_security/readme/DESCRIPTION.rst create mode 100644 auditlog_security/readme/ROADMAP.rst create mode 100644 auditlog_security/readme/USAGE.rst create mode 100644 auditlog_security/security/ir.model.access.csv create mode 100644 auditlog_security/views/auditlog_view.xml diff --git a/auditlog/:w b/auditlog/:w new file mode 100644 index 00000000000..8b489702eb1 --- /dev/null +++ b/auditlog/:w @@ -0,0 +1,13 @@ +# Copyright 2021 Therp B.V. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models, api + + +class AuditlogAutovacuum(models.TransientModel): + _inherit = 'auditlog.autovacuum' + + @api.model + def autovaccum(self, days): + return super(AuditlogAutovacuum, self.with_context( + auditlog_write=True)).autovacuum(days=days) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index 6cdc7233ccd..e7a3c8f6492 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -449,6 +449,8 @@ def _create_log_line_on_write( self, log, fields_list, old_values, new_values): """Log field updated on a 'write' operation.""" log_line_model = self.env['auditlog.log.line'] + import pudb + pudb.set_trace() for field_name in fields_list: if field_name in FIELDS_BLACKLIST: continue diff --git a/auditlog/views/auditlog_view.xml b/auditlog/views/auditlog_view.xml index 2ec7b480896..8eaefc4e36a 100644 --- a/auditlog/views/auditlog_view.xml +++ b/auditlog/views/auditlog_view.xml @@ -193,7 +193,7 @@ Logs auditlog.log form - + `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ABF OSIELL + +Contributors +~~~~~~~~~~~~ + +* Sebastien Alix +* Holger Brunn +* Holden Rehg + +Other credits +~~~~~~~~~~~~~ + +* Icon: built with different icons from the `Oxygen theme `_ (LGPL) + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/auditlog_security/__init__.py b/auditlog_security/__init__.py new file mode 100644 index 00000000000..31660d6a965 --- /dev/null +++ b/auditlog_security/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/auditlog_security/__manifest__.py b/auditlog_security/__manifest__.py new file mode 100644 index 00000000000..5e196c31fa3 --- /dev/null +++ b/auditlog_security/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2021 Therp B.V. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Audit Log Permissions", + "version": "11.0.1.0.1", + "author": "Therp B.V.,Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/server-tools/", + "category": "Tools", + "depends": [ + "auditlog", + "contacts", + ], + "data": [ + "demo/auditlog_rule.xml", + "views/auditlog_view.xml", + ], + "application": True, + "installable": True, +} diff --git a/auditlog_security/demo/auditlog_rule.xml b/auditlog_security/demo/auditlog_rule.xml new file mode 100644 index 00000000000..eb7639adc6e --- /dev/null +++ b/auditlog_security/demo/auditlog_rule.xml @@ -0,0 +1,10 @@ + + + + + main auditlog partner rule + + subscribed + + + diff --git a/auditlog_security/models/__init__.py b/auditlog_security/models/__init__.py new file mode 100644 index 00000000000..aa60319d2a3 --- /dev/null +++ b/auditlog_security/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import auditlog_rule +from . import ir_rule +from . import auditlog_autovacuum diff --git a/auditlog_security/models/auditlog_autovacuum.py b/auditlog_security/models/auditlog_autovacuum.py new file mode 100644 index 00000000000..c37db710aaf --- /dev/null +++ b/auditlog_security/models/auditlog_autovacuum.py @@ -0,0 +1,14 @@ +# Copyright 2021 Therp B.V. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models, api + + +class AuditlogAutovacuum(models.TransientModel): + _inherit = "auditlog.autovacuum" + + @api.model + def autovaccum(self, days): + return super( + AuditlogAutovacuum, self.with_context(auditlog_write=True) + ).autovacuum(days=days) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py new file mode 100644 index 00000000000..fd45558ca70 --- /dev/null +++ b/auditlog_security/models/auditlog_rule.py @@ -0,0 +1,71 @@ +# Copyright 2021 Therp B.V. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models, fields, api, modules, _ +from odoo.addons.auditlog.models.rule import FIELDS_BLACKLIST + + +class AuditlogRule(models.Model): + _inherit = "auditlog.rule" + + field_ids = fields.Many2many( + "ir.model.fields", + required=True, + ) + group_ids = fields.Many2many( + "res.groups", + string="Groups", + help="""Groups that will be allowed to see the logged fields, if left empty + all groups will be allowed (global rule creation)""", + ) + + """ note this solution will work only with a hardcoded design of models, + because on initialization , self.model_id.id still is not defined. + for now, to keep generality we put the filtering in the view.""" + + def get_field_ids_domain(self): + return [ + ("model_id", "=", self.env.ref("base.model_res_partner").id), + ("name", "not in", FIELDS_BLACKLIST), + ] + + def unlink(self): + # if we delete auditlog rule, corresponding ir.rules are removed + # TODO PROPOSAL: a warning here with detailed information? + res = super(AuditlogRule, self).unlink() + return ( + res + and self.env["ir.rule"] + .with_context(auditlog_write=True) + .search([("auditlog_id", "in", self.ids)]) + .unlink() + ) + + @api.multi + def write(self, vals): + res = super(AuditlogRule, self).write(vals) + for this in self: + if any([x in vals for x in ("group_ids", "field_ids", "model_id")]): + this.generate_rules() + return res + + def generate_rules(self): + old_rule = self.env["ir.rule"].search([("auditlog_id", "=", self.id)], limit=1) + domain_force = ( + "[ " + + " ('log_id.model_id' , '=', %s)," % (self.model_id.id) + + "('field_id', 'in', %s)" % (self.field_ids.ids) + + "]" + ) + values = { + "name": "auditlog_extended_%s" % self.id, + "model_id": self.env.ref("auditlog.model_auditlog_log_line").id, + "groups": [(6, 0, self.group_ids.ids)], + "perm_read": True, + "domain_force": domain_force, + "auditlog_id": self.id, + } + if old_rule: + old_rule.with_context(auditlog_write=True).write(values) + else: + self.with_context(auditlog_write=True).env["ir.rule"].create(values) diff --git a/auditlog_security/models/ir_rule.py b/auditlog_security/models/ir_rule.py new file mode 100644 index 00000000000..44176e14afe --- /dev/null +++ b/auditlog_security/models/ir_rule.py @@ -0,0 +1,52 @@ +# Copyright 2021 Therp B.V. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import exceptions, models, fields, api, _ + + +class IrRule(models.Model): + _inherit = "ir.rule" + + auditlog_id = fields.Many2one( + "auditlog.rule", + required=False, + help="Auditlog Rule that generated this ir.rule", + ) + + def prevent_rule_mod(self, vals=None): + auditlog_write = self.env.context.get("auditlog_write") + if "auditlog_id" in vals and not auditlog_write: + raise exceptions.validationerror(_("""Cannot change auditlog_id""")) + for this in self: + if this.auditlog_id and not auditlog_write: + raise exceptions.validationerror( + _( + """ + auditlog line rules are automatically generated from the auditlog + interface, please use that to edit/delete/""" + ) + ) + + @api.model + def create(self, values): + if values.get("model_id") == self.env.ref( + "auditlog.model_auditlog_log_line" + ).id and not self.env.context.get("auditlog_write"): + raise exceptions.ValidationError( + _( + """ + Auditlog line rules are automatically generated from the auditlog + interface, please use that to create""" + ) + ) + return super(IrRule, self).create(values) + + @api.multi + def write(self, vals): + self.prevent_rule_mod(vals) + return super(IrRule, self).write(vals) + + @api.multi + def unlink(self): + self.prevent_rule_mod() + return super(IrRule, self).unlink() diff --git a/auditlog_security/readme/CONTRIBUTORS.rst b/auditlog_security/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..addcc3f4a2b --- /dev/null +++ b/auditlog_security/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Giovanni Francesco Capalbo diff --git a/auditlog_security/readme/CREDITS.rst b/auditlog_security/readme/CREDITS.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/auditlog_security/readme/DESCRIPTION.rst b/auditlog_security/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..2d056774e16 --- /dev/null +++ b/auditlog_security/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module allows extends auditlog, allowing specific log lines to be viewed only +by users belonging to specific views, while all other lines are allowed only to +administrator. diff --git a/auditlog_security/readme/ROADMAP.rst b/auditlog_security/readme/ROADMAP.rst new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/auditlog_security/readme/ROADMAP.rst @@ -0,0 +1 @@ + diff --git a/auditlog_security/readme/USAGE.rst b/auditlog_security/readme/USAGE.rst new file mode 100644 index 00000000000..8fa4cd27178 --- /dev/null +++ b/auditlog_security/readme/USAGE.rst @@ -0,0 +1,10 @@ +Go to `Settings / Technical / Audit / Rules` to subscribe rules. A rule defines +which operations to log for a given data model. +The rule is now extended with a new field permission_ids, that tells us wich groups will +be allowed to read the lines produced by this rule. +If permission_ids is left empty, the default will be: +"auditlog lines visible only by administrator" + + +Then, check logs in the `Settings / Technical / Audit / Logs` menu. You can +group them by user sessions, date, data model , HTTP requests. diff --git a/auditlog_security/security/ir.model.access.csv b/auditlog_security/security/ir.model.access.csv new file mode 100644 index 00000000000..005a168ba9d --- /dev/null +++ b/auditlog_security/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_auditlog_log_line_user,auditlog_log_line_user,model_auditlog_log_line,base.group_user,1,0,0,0 +access_auditlog_log_user,auditlog_log_user,model_auditlog_log,base.group_user,1,0,0,0 diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml new file mode 100644 index 00000000000..46df0263ce9 --- /dev/null +++ b/auditlog_security/views/auditlog_view.xml @@ -0,0 +1,114 @@ + + + + + auditlog.log.line.form + auditlog.log.line + +
+ + + + + + + + + +
+
+
+ + + + auditlog.log.line.tree + auditlog.log.line + + + + + + + + + + + + + + + + + auditlog rule form extension + auditlog.rule + + + + + + + + + + + + + + + + + + auditlog rule tree extension + auditlog.rule + + + + + + + + + + + auditlog.log.search + auditlog.log.line + + + + + + + + + + + + Logs + auditlog.log.line + form + + [('log_id.res_id', '=', active_id)] + + + + + + +
From aded99e3732c01b91f31f75ecdc37398140aa0d8 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 13 Apr 2022 11:45:14 +0200 Subject: [PATCH 02/30] [ADD] review fixes --- auditlog/:w | 13 - auditlog/models/rule.py | 2 - auditlog/views/auditlog_view.xml | 2 +- auditlog_security/README.rst | 55 +-- auditlog_security/__manifest__.py | 8 +- auditlog_security/demo/auditlog_rule.xml | 10 - auditlog_security/models/__init__.py | 1 + .../models/auditlog_autovacuum.py | 2 +- .../models/auditlog_line_access_rule.py | 115 +++++ auditlog_security/models/auditlog_rule.py | 85 ++-- auditlog_security/models/ir_rule.py | 42 +- auditlog_security/readme/USAGE.rst | 3 +- .../security/ir.model.access.csv | 1 + .../static/description/index.html | 438 ++++++++++++++++++ auditlog_security/views/auditlog_view.xml | 106 +++-- maintainer-tools | 1 + 16 files changed, 691 insertions(+), 193 deletions(-) delete mode 100644 auditlog/:w delete mode 100644 auditlog_security/demo/auditlog_rule.xml create mode 100644 auditlog_security/models/auditlog_line_access_rule.py create mode 100644 auditlog_security/static/description/index.html create mode 160000 maintainer-tools diff --git a/auditlog/:w b/auditlog/:w deleted file mode 100644 index 8b489702eb1..00000000000 --- a/auditlog/:w +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2021 Therp B.V. -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from odoo import models, api - - -class AuditlogAutovacuum(models.TransientModel): - _inherit = 'auditlog.autovacuum' - - @api.model - def autovaccum(self, days): - return super(AuditlogAutovacuum, self.with_context( - auditlog_write=True)).autovacuum(days=days) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index e7a3c8f6492..6cdc7233ccd 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -449,8 +449,6 @@ def _create_log_line_on_write( self, log, fields_list, old_values, new_values): """Log field updated on a 'write' operation.""" log_line_model = self.env['auditlog.log.line'] - import pudb - pudb.set_trace() for field_name in fields_list: if field_name in FIELDS_BLACKLIST: continue diff --git a/auditlog/views/auditlog_view.xml b/auditlog/views/auditlog_view.xml index 8eaefc4e36a..2ec7b480896 100644 --- a/auditlog/views/auditlog_view.xml +++ b/auditlog/views/auditlog_view.xml @@ -193,7 +193,7 @@ Logs auditlog.log form - + `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -80,19 +70,12 @@ Credits Authors ~~~~~~~ -* ABF OSIELL +* Therp B.V. Contributors ~~~~~~~~~~~~ -* Sebastien Alix -* Holger Brunn -* Holden Rehg - -Other credits -~~~~~~~~~~~~~ - -* Icon: built with different icons from the `Oxygen theme `_ (LGPL) +* Giovanni Francesco Capalbo Maintainers ~~~~~~~~~~~ @@ -107,6 +90,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/server-tools `_ project on GitHub. +This module is part of the `OCA/server-tools `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/auditlog_security/__manifest__.py b/auditlog_security/__manifest__.py index 5e196c31fa3..782da1100cc 100644 --- a/auditlog_security/__manifest__.py +++ b/auditlog_security/__manifest__.py @@ -2,19 +2,21 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { - "name": "Audit Log Permissions", - "version": "11.0.1.0.1", + "name": "Audit Log User Permissions", + "version": "11.0.1.0.0", "author": "Therp B.V.,Odoo Community Association (OCA)", "license": "AGPL-3", "website": "https://github.com/OCA/server-tools/", "category": "Tools", + "description": """Allow regular users to view Audit log lines + via the form view of the relevant model""", "depends": [ "auditlog", "contacts", ], "data": [ - "demo/auditlog_rule.xml", "views/auditlog_view.xml", + "security/ir.model.access.csv", ], "application": True, "installable": True, diff --git a/auditlog_security/demo/auditlog_rule.xml b/auditlog_security/demo/auditlog_rule.xml deleted file mode 100644 index eb7639adc6e..00000000000 --- a/auditlog_security/demo/auditlog_rule.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - main auditlog partner rule - - subscribed - - - diff --git a/auditlog_security/models/__init__.py b/auditlog_security/models/__init__.py index aa60319d2a3..4c1c462d621 100644 --- a/auditlog_security/models/__init__.py +++ b/auditlog_security/models/__init__.py @@ -1,5 +1,6 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import auditlog_rule +from . import auditlog_line_access_rule from . import ir_rule from . import auditlog_autovacuum diff --git a/auditlog_security/models/auditlog_autovacuum.py b/auditlog_security/models/auditlog_autovacuum.py index c37db710aaf..7b4b0bc3a19 100644 --- a/auditlog_security/models/auditlog_autovacuum.py +++ b/auditlog_security/models/auditlog_autovacuum.py @@ -8,7 +8,7 @@ class AuditlogAutovacuum(models.TransientModel): _inherit = "auditlog.autovacuum" @api.model - def autovaccum(self, days): + def autovacuum(self, days): return super( AuditlogAutovacuum, self.with_context(auditlog_write=True) ).autovacuum(days=days) diff --git a/auditlog_security/models/auditlog_line_access_rule.py b/auditlog_security/models/auditlog_line_access_rule.py new file mode 100644 index 00000000000..aedb39009f0 --- /dev/null +++ b/auditlog_security/models/auditlog_line_access_rule.py @@ -0,0 +1,115 @@ +# Copyright 2021 Therp B.V. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import exceptions, models, fields, api, modules, _ +from odoo.addons.auditlog.models.rule import FIELDS_BLACKLIST + + +class AuditlogLineAccessRule(models.Model): + _name = "auditlog.line.access.rule" + + name = fields.Char() + + field_ids = fields.Many2many("ir.model.fields") + group_ids = fields.Many2many( + "res.groups", + help="""Groups that will be allowed to see the logged fields, if left empty + default will be all users with a login""", + ) + model_id = fields.Many2one( + "ir.model", related="auditlog_rule_id.model_id", readonly=True + ) + auditlog_rule_id = fields.Many2one( + "auditlog.rule", "auditlog_access_rule_ids", readonly=True, ondelete="cascade" + ) + state = fields.Selection(related="auditlog_rule_id.state", readonly=True) + + + def needs_rule(self): + self.ensure_one() + return bool(self.group_ids) + + def get_linked_rules(self): + # return with context key so that deletion will not be forbidden + return self.env["ir.rule"].search( + [("auditlog_line_access_rule_id", "in", self.ids)] + ) + + def get_field_ids_domain(self): + """note this solution will work only with a hardcoded design of models, + because on initialization , self.model_id.id still is not defined. + for now, to keep generality we put the filtering in the view.""" + return [ + ("model_id", "=", self.env.ref("base.model_res_partner").id), + ("name", "not in", FIELDS_BLACKLIST), + ] + + def unlink(self): + to_delete = self.get_linked_rules() + res = super(AuditlogLineAccessRule, self).unlink() + if res: + res = res and to_delete.with_context(auditlog_write=True).unlink() + return res + + def add_default_group_if_needed(self): + self.ensure_one() + res = False + if not self.group_ids and self.field_ids: + res = self.with_context(no_iter=True).write( + {"group_ids": [(6, 0, [self.env.ref("base.group_user").id])]} + ) + return res + + @api.model + def create(self, vals): + res = super(AuditlogLineAccessRule, self).create(vals) + res.add_default_group_if_needed() + if res.needs_rule(): + res.generate_rules() + return res + + @api.multi + def write(self, vals): + res = super(AuditlogLineAccessRule, self).write(vals) + for this in self: + added = this.add_default_group_if_needed() + if ( + any( + [ + x in vals + for x in ("group_ids", "field_ids", "model_id", "all_fields") + ] + ) + or added + ): + if this.needs_rule(): + this.generate_rules() + else: + this.get_linked_rules().with_context(auditlog_write=True).unlink() + return res + + def generate_rules(self): + old_rule = self.env["ir.rule"].search( + [("auditlog_line_access_rule_id", "=", self.id)], limit=1 + ) + values = self._prepare_rule_values() + if old_rule: + old_rule.with_context(auditlog_write=True).write(values) + else: + self.with_context(auditlog_write=True).env["ir.rule"].create(values) + + def _prepare_rule_values(self): + domain_force = "[" + " ('aulditlog_rule_id.log_id.model_id' , '=', %s)," % ( + self.model_id.id + ) + if self.field_ids: + domain_force += "('field_id', 'in', %s)" % (self.field_ids.ids) + domain_force += "]" + return { + "name": "auditlog_extended_%s" % self.id, + "model_id": self.env.ref("auditlog.model_auditlog_log_line").id, + "groups": [(6, 0, self.group_ids.ids)], + "perm_read": True, + "domain_force": domain_force, + "auditlog_line_access_rule_id": self.id, + } diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index fd45558ca70..4ca5a924395 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -1,71 +1,46 @@ # Copyright 2021 Therp B.V. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import models, fields, api, modules, _ +from odoo import exceptions, models, fields, api, modules, _ from odoo.addons.auditlog.models.rule import FIELDS_BLACKLIST class AuditlogRule(models.Model): _inherit = "auditlog.rule" - field_ids = fields.Many2many( - "ir.model.fields", - required=True, + auditlog_line_access_rule_ids = fields.One2many( + "auditlog.line.access.rule", "auditlog_rule_id", ondelete="cascade" ) - group_ids = fields.Many2many( - "res.groups", - string="Groups", - help="""Groups that will be allowed to see the logged fields, if left empty - all groups will be allowed (global rule creation)""", - ) - - """ note this solution will work only with a hardcoded design of models, - because on initialization , self.model_id.id still is not defined. - for now, to keep generality we put the filtering in the view.""" - def get_field_ids_domain(self): - return [ - ("model_id", "=", self.env.ref("base.model_res_partner").id), - ("name", "not in", FIELDS_BLACKLIST), - ] + @api.onchange("model_id") + def onchange_model_id(self): + # if model changes we must wipe out all field ids + self.auditlog_line_access_rule_ids.unlink() + @api.multi def unlink(self): - # if we delete auditlog rule, corresponding ir.rules are removed - # TODO PROPOSAL: a warning here with detailed information? + lines = self.mapped("auditlog_line_access_rule_ids") res = super(AuditlogRule, self).unlink() - return ( - res - and self.env["ir.rule"] - .with_context(auditlog_write=True) - .search([("auditlog_id", "in", self.ids)]) - .unlink() - ) - - @api.multi - def write(self, vals): - res = super(AuditlogRule, self).write(vals) - for this in self: - if any([x in vals for x in ("group_ids", "field_ids", "model_id")]): - this.generate_rules() + if res: + lines.unlink() return res - def generate_rules(self): - old_rule = self.env["ir.rule"].search([("auditlog_id", "=", self.id)], limit=1) - domain_force = ( - "[ " - + " ('log_id.model_id' , '=', %s)," % (self.model_id.id) - + "('field_id', 'in', %s)" % (self.field_ids.ids) - + "]" - ) - values = { - "name": "auditlog_extended_%s" % self.id, - "model_id": self.env.ref("auditlog.model_auditlog_log_line").id, - "groups": [(6, 0, self.group_ids.ids)], - "perm_read": True, - "domain_force": domain_force, - "auditlog_id": self.id, - } - if old_rule: - old_rule.with_context(auditlog_write=True).write(values) - else: - self.with_context(auditlog_write=True).env["ir.rule"].create(values) + @api.multi + def subscribe(self): + super(AuditlogRule, self).subscribe() + act_window_model = self.env["ir.actions.act_window"] + for rule in self: + domain = ( + "[('log_id.model_id', '=', %s), ('log_id.res_id', '=', active_id)]" + % (rule.model_id.id) + ) + vals = { + "name": _("View log lines"), + "res_model": "auditlog.log.line", + "src_model": rule.model_id.model, + "binding_model_id": rule.model_id.id, + "domain": domain, + } + act_window = act_window_model.sudo().create(vals) + rule.write({"state": "subscribed", "action_id": act_window.id}) + return True diff --git a/auditlog_security/models/ir_rule.py b/auditlog_security/models/ir_rule.py index 44176e14afe..dfb1c822fae 100644 --- a/auditlog_security/models/ir_rule.py +++ b/auditlog_security/models/ir_rule.py @@ -7,26 +7,14 @@ class IrRule(models.Model): _inherit = "ir.rule" - auditlog_id = fields.Many2one( - "auditlog.rule", + auditlog_line_access_rule_id = fields.Many2one( + "auditlog.line.access.rule", required=False, - help="Auditlog Rule that generated this ir.rule", + index=True, + ondelete='cascade', + help="Auditlog line access Rule that generated this ir.rule", ) - def prevent_rule_mod(self, vals=None): - auditlog_write = self.env.context.get("auditlog_write") - if "auditlog_id" in vals and not auditlog_write: - raise exceptions.validationerror(_("""Cannot change auditlog_id""")) - for this in self: - if this.auditlog_id and not auditlog_write: - raise exceptions.validationerror( - _( - """ - auditlog line rules are automatically generated from the auditlog - interface, please use that to edit/delete/""" - ) - ) - @api.model def create(self, values): if values.get("model_id") == self.env.ref( @@ -35,18 +23,30 @@ def create(self, values): raise exceptions.ValidationError( _( """ - Auditlog line rules are automatically generated from the auditlog - interface, please use that to create""" + Auditlog line rules are automatically generated from the + auditlog interface, please use that to create""" ) ) return super(IrRule, self).create(values) @api.multi def write(self, vals): - self.prevent_rule_mod(vals) + if "auditlog_id" in vals and not self.env.context.get("auditlog_write"): + raise exceptions.ValidationError( + _("""Cannot change auditlog_line_access_rule""") + ) return super(IrRule, self).write(vals) @api.multi def unlink(self): - self.prevent_rule_mod() + auditlog_write = self.env.context.get("auditlog_write") + for this in self: + if this.auditlog_line_access_rule_id and not auditlog_write: + raise exceptions.ValidationError( + _( + """ + Auditlog line rules are automatically generated from the + auditlog interface, please use that to delete""" + ) + ) return super(IrRule, self).unlink() diff --git a/auditlog_security/readme/USAGE.rst b/auditlog_security/readme/USAGE.rst index 8fa4cd27178..f4cfaeca2cf 100644 --- a/auditlog_security/readme/USAGE.rst +++ b/auditlog_security/readme/USAGE.rst @@ -3,7 +3,8 @@ which operations to log for a given data model. The rule is now extended with a new field permission_ids, that tells us wich groups will be allowed to read the lines produced by this rule. If permission_ids is left empty, the default will be: -"auditlog lines visible only by administrator" +"auditlog lines visible only by user in Settings group, which is the default +for the auditlog module" Then, check logs in the `Settings / Technical / Audit / Logs` menu. You can diff --git a/auditlog_security/security/ir.model.access.csv b/auditlog_security/security/ir.model.access.csv index 005a168ba9d..f5314c702a3 100644 --- a/auditlog_security/security/ir.model.access.csv +++ b/auditlog_security/security/ir.model.access.csv @@ -1,3 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_auditlog_log_line_user,auditlog_log_line_user,model_auditlog_log_line,base.group_user,1,0,0,0 access_auditlog_log_user,auditlog_log_user,model_auditlog_log,base.group_user,1,0,0,0 +access_auditlog_line_access_rule_admin,auditlog_line_access_rule_admin,model_auditlog_line_access_rule,base.group_system,1,1,1,1 diff --git a/auditlog_security/static/description/index.html b/auditlog_security/static/description/index.html new file mode 100644 index 00000000000..03f14d1e6be --- /dev/null +++ b/auditlog_security/static/description/index.html @@ -0,0 +1,438 @@ + + + + + + +Audit Log User Permissions + + + +
+

Audit Log User Permissions

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runbot

+

This module allows extends auditlog, allowing specific log lines to be viewed only +by users belonging to specific views, while all other lines are allowed only to +administrator.

+

Table of contents

+ +
+

Usage

+

Go to Settings / Technical / Audit / Rules to subscribe rules. A rule defines +which operations to log for a given data model. +The rule is now extended with a new field permission_ids, that tells us wich groups will +be allowed to read the lines produced by this rule. +If permission_ids is left empty, the default will be: +“auditlog lines visible only by user in Settings group, which is the default +for the auditlog module”

+

Then, check logs in the Settings / Technical / Audit / Logs menu. You can +group them by user sessions, date, data model , HTTP requests.

+
+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Therp B.V.
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index 46df0263ce9..6d34b52e66d 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -44,21 +44,59 @@ auditlog.rule - + - - - - - - +

+ Add fields here to make any changes to them (audit log lines) + visible to members of the selected groups. +

+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+ +
@@ -68,47 +106,15 @@ - - + + + + + + + + - - - auditlog.log.search - auditlog.log.line - - - - - - - - - - - - Logs - auditlog.log.line - form - - [('log_id.res_id', '=', active_id)] - - - - - - diff --git a/maintainer-tools b/maintainer-tools new file mode 160000 index 00000000000..7d8a9f9ad73 --- /dev/null +++ b/maintainer-tools @@ -0,0 +1 @@ +Subproject commit 7d8a9f9ad73db0976fb03cbee43d953bc29b89e9 From 386127ba14256e2c252d97a5bcdb0d2a2e620482 Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Thu, 2 Jun 2022 14:10:08 +0200 Subject: [PATCH 03/30] fixup! [ADD] review fixes --- auditlog_security/models/auditlog_line_access_rule.py | 2 +- auditlog_security/security/ir.model.access.csv | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/auditlog_security/models/auditlog_line_access_rule.py b/auditlog_security/models/auditlog_line_access_rule.py index aedb39009f0..ae9f5ec1b7a 100644 --- a/auditlog_security/models/auditlog_line_access_rule.py +++ b/auditlog_security/models/auditlog_line_access_rule.py @@ -99,7 +99,7 @@ def generate_rules(self): self.with_context(auditlog_write=True).env["ir.rule"].create(values) def _prepare_rule_values(self): - domain_force = "[" + " ('aulditlog_rule_id.log_id.model_id' , '=', %s)," % ( + domain_force = "[" + " ('log_id.model_id' , '=', %s)," % ( self.model_id.id ) if self.field_ids: diff --git a/auditlog_security/security/ir.model.access.csv b/auditlog_security/security/ir.model.access.csv index f5314c702a3..1d82edf2108 100644 --- a/auditlog_security/security/ir.model.access.csv +++ b/auditlog_security/security/ir.model.access.csv @@ -1,4 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_auditlog_log_line_user,auditlog_log_line_user,model_auditlog_log_line,base.group_user,1,0,0,0 -access_auditlog_log_user,auditlog_log_user,model_auditlog_log,base.group_user,1,0,0,0 -access_auditlog_line_access_rule_admin,auditlog_line_access_rule_admin,model_auditlog_line_access_rule,base.group_system,1,1,1,1 +access_auditlog_log_line_user,auditlog_log_line_user,auditlog.model_auditlog_log_line,base.group_user,1,0,0,0 +access_auditlog_line_access_rule_admin,auditlog_line_access_rule_admin,model_auditlog_line_access_rule,base.group_erp_manager,1,1,1,1 From 98f670ae04eb2389b79f4b7e05309837e08751c9 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Tue, 7 Jun 2022 11:59:42 +0200 Subject: [PATCH 04/30] [ADD] Lint XML --- auditlog_security/views/auditlog_view.xml | 62 +++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index 6d34b52e66d..2b5005206b0 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -19,7 +19,7 @@ - + auditlog.log.line.tree auditlog.log.line @@ -37,20 +37,20 @@ - - - auditlog rule form extension - auditlog.rule - - - + + + auditlog rule form extension + auditlog.rule + + + -

- Add fields here to make any changes to them (audit log lines) + Add fields here to make any changes to them (audit log lines) visible to members of the selected groups.

@@ -58,22 +58,22 @@ - - -
+ - + > @@ -83,7 +83,7 @@ + > @@ -96,25 +96,25 @@ -
-
-
+
+
+
- - auditlog rule tree extension - auditlog.rule - - - + + auditlog rule tree extension + auditlog.rule + + + - - + + - - - + + + From b3323840ba9a84b0c84a8e96b144ab909edc3072 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Tue, 7 Jun 2022 15:15:30 +0200 Subject: [PATCH 05/30] [ADD] show nicer group_ids and field_ids in treeview --- auditlog_security/views/auditlog_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index 2b5005206b0..d9ba1f559de 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -58,10 +58,10 @@ - - From de40a70cb5924f642ad040919c7758fb42ce93c3 Mon Sep 17 00:00:00 2001 From: KKamaa Date: Tue, 5 Jul 2022 16:13:56 +0300 Subject: [PATCH 06/30] [FIX] open issues and errors --- auditlog_security/__manifest__.py | 3 +- auditlog_security/models/__init__.py | 2 + .../models/auditlog_line_access_rule.py | 16 ++-- auditlog_security/models/auditlog_log_line.py | 15 ++++ auditlog_security/models/auditlog_rule.py | 75 ++++++++++++++++++- .../security/ir.model.access.csv | 2 +- auditlog_security/security/res_groups.xml | 6 ++ auditlog_security/views/auditlog_view.xml | 57 +++++++++----- 8 files changed, 145 insertions(+), 31 deletions(-) create mode 100644 auditlog_security/models/auditlog_log_line.py create mode 100644 auditlog_security/security/res_groups.xml diff --git a/auditlog_security/__manifest__.py b/auditlog_security/__manifest__.py index 782da1100cc..358c8ad6c11 100644 --- a/auditlog_security/__manifest__.py +++ b/auditlog_security/__manifest__.py @@ -8,13 +8,14 @@ "license": "AGPL-3", "website": "https://github.com/OCA/server-tools/", "category": "Tools", - "description": """Allow regular users to view Audit log lines + "description": """Allow regular users to view Audit log lines via the form view of the relevant model""", "depends": [ "auditlog", "contacts", ], "data": [ + "security/res_groups.xml", "views/auditlog_view.xml", "security/ir.model.access.csv", ], diff --git a/auditlog_security/models/__init__.py b/auditlog_security/models/__init__.py index 4c1c462d621..da32e810bcf 100644 --- a/auditlog_security/models/__init__.py +++ b/auditlog_security/models/__init__.py @@ -4,3 +4,5 @@ from . import auditlog_line_access_rule from . import ir_rule from . import auditlog_autovacuum +from . import auditlog_log_line + diff --git a/auditlog_security/models/auditlog_line_access_rule.py b/auditlog_security/models/auditlog_line_access_rule.py index ae9f5ec1b7a..597d6be7a8d 100644 --- a/auditlog_security/models/auditlog_line_access_rule.py +++ b/auditlog_security/models/auditlog_line_access_rule.py @@ -13,7 +13,7 @@ class AuditlogLineAccessRule(models.Model): field_ids = fields.Many2many("ir.model.fields") group_ids = fields.Many2many( "res.groups", - help="""Groups that will be allowed to see the logged fields, if left empty + help="""Groups that will be allowed to see the logged fields, if left empty default will be all users with a login""", ) model_id = fields.Many2one( @@ -23,7 +23,7 @@ class AuditlogLineAccessRule(models.Model): "auditlog.rule", "auditlog_access_rule_ids", readonly=True, ondelete="cascade" ) state = fields.Selection(related="auditlog_rule_id.state", readonly=True) - + def needs_rule(self): self.ensure_one() @@ -44,12 +44,12 @@ def get_field_ids_domain(self): ("name", "not in", FIELDS_BLACKLIST), ] - def unlink(self): - to_delete = self.get_linked_rules() - res = super(AuditlogLineAccessRule, self).unlink() - if res: - res = res and to_delete.with_context(auditlog_write=True).unlink() - return res + # def unlink(self): + # to_delete = self.get_linked_rules() + # res = super(AuditlogLineAccessRule, self).unlink() + # if res: + # res = res and to_delete.with_context(auditlog_write=True).unlink() + # return res def add_default_group_if_needed(self): self.ensure_one() diff --git a/auditlog_security/models/auditlog_log_line.py b/auditlog_security/models/auditlog_log_line.py new file mode 100644 index 00000000000..cb6f41ff9f8 --- /dev/null +++ b/auditlog_security/models/auditlog_log_line.py @@ -0,0 +1,15 @@ +# Copyright 2022 Therp B.V. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import exceptions, models, fields + + +class AuditlogLogLine(models.Model): + _inherit = 'auditlog.log.line' + _order = "create_date desc" + + user_id = fields.Many2one( + 'res.users', + string="User", + default=lambda self: self.env.user + ) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 4ca5a924395..3bcd80a99e3 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -31,16 +31,85 @@ def subscribe(self): act_window_model = self.env["ir.actions.act_window"] for rule in self: domain = ( - "[('log_id.model_id', '=', %s), ('log_id.res_id', '=', active_id)]" - % (rule.model_id.id) + "[('log_id.model_id', '=', %s), ('log_id.res_id', '=', " + "active_id),('field_id', 'in', %s)]" + % (rule.model_id.id, + rule.auditlog_line_access_rule_ids.mapped('field_ids').ids) ) vals = { "name": _("View log lines"), "res_model": "auditlog.log.line", "src_model": rule.model_id.model, "binding_model_id": rule.model_id.id, - "domain": domain, + "domain": domain } + audit_grp_sec = self.env.ref( + 'auditlog_security.group_can_view_audit_logs') act_window = act_window_model.sudo().create(vals) + act_window.groups_id = audit_grp_sec + domain = "[('model_id', '=', %s), ('res_id', '=', active_id)]" % ( + rule.model_id.id) + pvals = { + 'name': _("View logs"), + 'res_model': 'auditlog.log', + 'src_model': rule.model_id.model, + 'binding_model_id': rule.model_id.id, + 'domain': domain, + } + params_view = [ + ('name', '=', pvals['name']), + ('res_model', '=', pvals['res_model']), + ('src_model', '=', pvals['src_model']), + ('binding_model_id', '=', pvals['binding_model_id']), + ('domain', '=', pvals['domain']) + ] + act_window_view = act_window_model.search(params_view) + for action in act_window_view: + action.groups_id = audit_grp_sec rule.write({"state": "subscribed", "action_id": act_window.id}) return True + + @api.multi + def unsubscribe(self): + act_window_model = self.env["ir.actions.act_window"] + for rule in self: + domain = "[('model_id', '=', %s), ('res_id', '=', active_id)]" % ( + rule.model_id.id) + vals = { + 'name': _("View logs"), + 'res_model': 'auditlog.log', + 'src_model': rule.model_id.model, + 'binding_model_id': rule.model_id.id, + 'domain': domain, + } + params_view = [ + ('name', '=', vals['name']), + ('res_model', '=', vals['res_model']), + ('src_model', '=', vals['src_model']), + ('binding_model_id', '=', vals['binding_model_id']), + ('domain', '=', vals['domain']) + ] + act_window_view = act_window_model.search(params_view) + for action in act_window_view: + action.unlink() + return super(AuditlogRule, self).unsubscribe() + + def _prepare_log_line_vals_on_read(self, log, field, read_values): + res = super(AuditlogRule, self)._prepare_log_line_vals_on_read( + log, field, read_values) + res.update({'user_id': self.env.user.id}) + return res + + def _prepare_log_line_vals_on_write( + self, log, field, old_values, new_values): + res = super(AuditlogRule, self)._prepare_log_line_vals_on_write( + log, field, old_values, new_values) + res.update({'user_id': self.env.user.id}) + return res + + def _prepare_log_line_vals_on_create(self, log, field, new_values): + res = super(AuditlogRule, self)._prepare_log_line_vals_on_create( + log, field, new_values) + res.update({'user_id': self.env.user.id}) + return res + diff --git a/auditlog_security/security/ir.model.access.csv b/auditlog_security/security/ir.model.access.csv index 1d82edf2108..c0429ab1ed7 100644 --- a/auditlog_security/security/ir.model.access.csv +++ b/auditlog_security/security/ir.model.access.csv @@ -1,3 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_auditlog_log_line_user,auditlog_log_line_user,auditlog.model_auditlog_log_line,base.group_user,1,0,0,0 -access_auditlog_line_access_rule_admin,auditlog_line_access_rule_admin,model_auditlog_line_access_rule,base.group_erp_manager,1,1,1,1 +access_auditlog_line_access_rule_admin,auditlog_line_access_rule_admin,model_auditlog_line_access_rule,base.group_erp_manager,1,1,1,0 \ No newline at end of file diff --git a/auditlog_security/security/res_groups.xml b/auditlog_security/security/res_groups.xml new file mode 100644 index 00000000000..2b4fd7171c9 --- /dev/null +++ b/auditlog_security/security/res_groups.xml @@ -0,0 +1,6 @@ + + + + View Audit Logs + + diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index d9ba1f559de..710a0af246f 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -1,15 +1,47 @@ + + auditlog.log.form + auditlog.log + + + + false + + + + + + auditlog.log.form + auditlog.log + + + + false + + + + + + + + + + auditlog.log.line.form auditlog.log.line -
+ + + @@ -24,7 +56,9 @@ auditlog.log.line.tree auditlog.log.line - + + + @@ -55,7 +89,7 @@

- + - - - - - - + /> - - - - - - - + />
From 1896427d978897995061bf715e7b65db0817b0c5 Mon Sep 17 00:00:00 2001 From: KKamaa Date: Tue, 5 Jul 2022 16:49:56 +0300 Subject: [PATCH 07/30] fixup! [FIX] open issues and errors --- auditlog_security/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auditlog_security/security/ir.model.access.csv b/auditlog_security/security/ir.model.access.csv index c0429ab1ed7..eba2422b41f 100644 --- a/auditlog_security/security/ir.model.access.csv +++ b/auditlog_security/security/ir.model.access.csv @@ -1,3 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_auditlog_log_line_user,auditlog_log_line_user,auditlog.model_auditlog_log_line,base.group_user,1,0,0,0 -access_auditlog_line_access_rule_admin,auditlog_line_access_rule_admin,model_auditlog_line_access_rule,base.group_erp_manager,1,1,1,0 \ No newline at end of file +access_auditlog_line_access_rule_admin,auditlog_line_access_rule_admin,model_auditlog_line_access_rule,base.group_erp_manager,1,1,1,1 \ No newline at end of file From 42a3b7efddb74ad659776f33c3aa95219a83e925 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 20 Jul 2022 15:48:09 +0200 Subject: [PATCH 08/30] [FIX] fixes points 1,3,4,7 --- auditlog_security/models/auditlog_log_line.py | 4 +- auditlog_security/models/auditlog_rule.py | 96 +++++++++---------- auditlog_security/views/auditlog_view.xml | 3 +- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/auditlog_security/models/auditlog_log_line.py b/auditlog_security/models/auditlog_log_line.py index cb6f41ff9f8..b79f6b25ba0 100644 --- a/auditlog_security/models/auditlog_log_line.py +++ b/auditlog_security/models/auditlog_log_line.py @@ -11,5 +11,7 @@ class AuditlogLogLine(models.Model): user_id = fields.Many2one( 'res.users', string="User", - default=lambda self: self.env.user ) + method = fields.Char("Method", related='log_id.method') + + diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 3bcd80a99e3..22eca101c96 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -3,6 +3,7 @@ from odoo import exceptions, models, fields, api, modules, _ from odoo.addons.auditlog.models.rule import FIELDS_BLACKLIST +from odoo import SUPERUSER_ID class AuditlogRule(models.Model): @@ -12,6 +13,12 @@ class AuditlogRule(models.Model): "auditlog.line.access.rule", "auditlog_rule_id", ondelete="cascade" ) + + def sudo(self, user=SUPERUSER_ID): + return super( + AuditlogRule, self.with_context(real_user=self.env.context.get('uid')) + ).sudo(user=user) + @api.onchange("model_id") def onchange_model_id(self): # if model changes we must wipe out all field ids @@ -25,91 +32,80 @@ def unlink(self): lines.unlink() return res + def _get_view_log_lines_action(rule): + rule.ensure_one() + domain = ( + "[('log_id.model_id', '=', %s), ('log_id.res_id', '=', " + "active_id),('field_id', 'in', %s)]" + % (rule.model_id.id, + rule.auditlog_line_access_rule_ids.mapped('field_ids').ids) + ) + return { + "name": _("View log lines"), + "res_model": "auditlog.log.line", + "src_model": rule.model_id.model, + "binding_model_id": rule.model_id.id, + "domain": domain + } + + def _get_view_log_action(rule): + #small helper , not used but may be useful. + rule.ensure_one() + domain = "[('model_id', '=', %s), ('res_id', '=', active_id)]" % ( + rule.model_id.id) + return { + 'name': _("View logs"), + 'res_model': 'auditlog.log', + 'src_model': rule.model_id.model, + 'binding_model_id': rule.model_id.id, + 'domain': domain, + } + @api.multi def subscribe(self): - super(AuditlogRule, self).subscribe() act_window_model = self.env["ir.actions.act_window"] for rule in self: - domain = ( - "[('log_id.model_id', '=', %s), ('log_id.res_id', '=', " - "active_id),('field_id', 'in', %s)]" - % (rule.model_id.id, - rule.auditlog_line_access_rule_ids.mapped('field_ids').ids) - ) - vals = { - "name": _("View log lines"), - "res_model": "auditlog.log.line", - "src_model": rule.model_id.model, - "binding_model_id": rule.model_id.id, - "domain": domain - } + vals = rule._get_view_log_lines_action() audit_grp_sec = self.env.ref( 'auditlog_security.group_can_view_audit_logs') act_window = act_window_model.sudo().create(vals) act_window.groups_id = audit_grp_sec - domain = "[('model_id', '=', %s), ('res_id', '=', active_id)]" % ( - rule.model_id.id) - pvals = { - 'name': _("View logs"), - 'res_model': 'auditlog.log', - 'src_model': rule.model_id.model, - 'binding_model_id': rule.model_id.id, - 'domain': domain, - } - params_view = [ - ('name', '=', pvals['name']), - ('res_model', '=', pvals['res_model']), - ('src_model', '=', pvals['src_model']), - ('binding_model_id', '=', pvals['binding_model_id']), - ('domain', '=', pvals['domain']) - ] - act_window_view = act_window_model.search(params_view) - for action in act_window_view: - action.groups_id = audit_grp_sec - rule.write({"state": "subscribed", "action_id": act_window.id}) - return True + return super(AuditlogRule, self).subscribe() @api.multi def unsubscribe(self): act_window_model = self.env["ir.actions.act_window"] for rule in self: - domain = "[('model_id', '=', %s), ('res_id', '=', active_id)]" % ( - rule.model_id.id) - vals = { - 'name': _("View logs"), - 'res_model': 'auditlog.log', - 'src_model': rule.model_id.model, - 'binding_model_id': rule.model_id.id, - 'domain': domain, - } - params_view = [ + vals = rule._get_view_log_lines_action() + params_view_line = [ ('name', '=', vals['name']), ('res_model', '=', vals['res_model']), ('src_model', '=', vals['src_model']), ('binding_model_id', '=', vals['binding_model_id']), ('domain', '=', vals['domain']) ] - act_window_view = act_window_model.search(params_view) - for action in act_window_view: + act_window_view_line = act_window_model.search(params_view_line) + for action in act_window_view_line: action.unlink() return super(AuditlogRule, self).unsubscribe() + def _prepare_log_line_vals_on_read(self, log, field, read_values): res = super(AuditlogRule, self)._prepare_log_line_vals_on_read( log, field, read_values) - res.update({'user_id': self.env.user.id}) + res.update({'user_id': self.env.context.get('real_user')}) return res def _prepare_log_line_vals_on_write( self, log, field, old_values, new_values): res = super(AuditlogRule, self)._prepare_log_line_vals_on_write( log, field, old_values, new_values) - res.update({'user_id': self.env.user.id}) + res.update({'user_id': self.env.context.get('real_user')}) return res def _prepare_log_line_vals_on_create(self, log, field, new_values): res = super(AuditlogRule, self)._prepare_log_line_vals_on_create( log, field, new_values) - res.update({'user_id': self.env.user.id}) + res.update({'user_id': self.env.context.get('real_user')}) return res diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index 710a0af246f..9878d485e06 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -59,7 +59,7 @@ - + @@ -90,7 +90,6 @@ - Date: Thu, 21 Jul 2022 19:54:59 +0300 Subject: [PATCH 09/30] [FIX] point 2: server actions instead of window action --- auditlog_security/models/auditlog_rule.py | 49 +++++++++++++---------- auditlog_security/views/auditlog_view.xml | 21 +++++++--- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 22eca101c96..ca3e0d93eda 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -12,6 +12,7 @@ class AuditlogRule(models.Model): auditlog_line_access_rule_ids = fields.One2many( "auditlog.line.access.rule", "auditlog_rule_id", ondelete="cascade" ) + server_action_id = fields.Many2one('ir.actions.server', "Server Action") def sudo(self, user=SUPERUSER_ID): @@ -41,15 +42,37 @@ def _get_view_log_lines_action(rule): rule.auditlog_line_access_rule_ids.mapped('field_ids').ids) ) return { - "name": _("View log lines"), + "name": _("View Log Lines"), "res_model": "auditlog.log.line", "src_model": rule.model_id.model, "binding_model_id": rule.model_id.id, "domain": domain } + def _create_server_action(self, rule): + + code = """ + if env.user.has_group("auditlog_security.group_can_view_audit_logs"): + rule = env['auditlog.rule'].browse(%s) + fields_ids = rule.auditlog_line_access_rule_ids.mapped('field_ids').ids + logs = env['auditlog.log'].sudo().search([('model_id', '=', rule.model_id.id), ('res_id', 'in', env.context.get('active_ids'))]) + domain = [('log_id', 'in', logs.ids), ('field_id', 'in', fields_ids)] + action_values = env.ref('auditlog_security.audit_log_line_action').read()[0] + action = action_values + """ % rule.id + server_action = self.env['ir.actions.server'].sudo().create({ + 'name': "View Log Lines", + 'model_id': rule.model_id.id, + 'state': "code", + 'code': code.strip() + }) + rule.write({ + 'server_action_id': server_action.id + }) + return server_action + def _get_view_log_action(rule): - #small helper , not used but may be useful. + #small helper , not used but may be useful. rule.ensure_one() domain = "[('model_id', '=', %s), ('res_id', '=', active_id)]" % ( rule.model_id.id) @@ -63,33 +86,17 @@ def _get_view_log_action(rule): @api.multi def subscribe(self): - act_window_model = self.env["ir.actions.act_window"] for rule in self: - vals = rule._get_view_log_lines_action() - audit_grp_sec = self.env.ref( - 'auditlog_security.group_can_view_audit_logs') - act_window = act_window_model.sudo().create(vals) - act_window.groups_id = audit_grp_sec + server_action = self._create_server_action(rule) + server_action.create_action() return super(AuditlogRule, self).subscribe() @api.multi def unsubscribe(self): - act_window_model = self.env["ir.actions.act_window"] for rule in self: - vals = rule._get_view_log_lines_action() - params_view_line = [ - ('name', '=', vals['name']), - ('res_model', '=', vals['res_model']), - ('src_model', '=', vals['src_model']), - ('binding_model_id', '=', vals['binding_model_id']), - ('domain', '=', vals['domain']) - ] - act_window_view_line = act_window_model.search(params_view_line) - for action in act_window_view_line: - action.unlink() + rule.server_action_id.unlink() return super(AuditlogRule, self).unsubscribe() - def _prepare_log_line_vals_on_read(self, log, field, read_values): res = super(AuditlogRule, self)._prepare_log_line_vals_on_read( log, field, read_values) diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index 9878d485e06..34fe09ec042 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -51,7 +51,6 @@
- auditlog.log.line.tree auditlog.log.line @@ -69,6 +68,13 @@
+ + View Log Lines + ir.actions.act_window + auditlog.log.line + tree,form + + @@ -79,15 +85,18 @@ + +
+

+ Add fields here to make any changes to them (audit log lines) + visible to members of the selected groups. +

+
+
-

- Add fields here to make any changes to them (audit log lines) - visible to members of the selected groups. -

- From db95a565132bf272110dcb91e9ccf60c9d9b9e93 Mon Sep 17 00:00:00 2001 From: Renzo Brown Date: Thu, 21 Jul 2022 19:09:20 +0000 Subject: [PATCH 10/30] fixup! [FIX] point 2: server actions instead of window action --- .../models/auditlog_line_access_rule.py | 21 +++--- auditlog_security/models/auditlog_rule.py | 70 ++++++++----------- 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/auditlog_security/models/auditlog_line_access_rule.py b/auditlog_security/models/auditlog_line_access_rule.py index 597d6be7a8d..fc210e851c7 100644 --- a/auditlog_security/models/auditlog_line_access_rule.py +++ b/auditlog_security/models/auditlog_line_access_rule.py @@ -44,12 +44,12 @@ def get_field_ids_domain(self): ("name", "not in", FIELDS_BLACKLIST), ] - # def unlink(self): - # to_delete = self.get_linked_rules() - # res = super(AuditlogLineAccessRule, self).unlink() - # if res: - # res = res and to_delete.with_context(auditlog_write=True).unlink() - # return res + def unlink(self): + to_delete = self.get_linked_rules() + res = super(AuditlogLineAccessRule, self).unlink() + if res: + res = res and to_delete.with_context(auditlog_write=True).unlink() + return res def add_default_group_if_needed(self): self.ensure_one() @@ -103,11 +103,14 @@ def _prepare_rule_values(self): self.model_id.id ) if self.field_ids: - domain_force += "('field_id', 'in', %s)" % (self.field_ids.ids) - domain_force += "]" + domain_force = "[('field_id', 'in', %s)]" % (self.field_ids.ids) + model = self.env.ref("auditlog.model_auditlog_log_line") + else: + domain_force = "[('model_id', '=', %s)]" % (self.model_id.id) + model = self.env.ref("auditlog.model_auditlog_log") return { "name": "auditlog_extended_%s" % self.id, - "model_id": self.env.ref("auditlog.model_auditlog_log_line").id, + "model_id": model.id, "groups": [(6, 0, self.group_ids.ids)], "perm_read": True, "domain_force": domain_force, diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index ca3e0d93eda..04ab70a03eb 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -33,61 +33,51 @@ def unlink(self): lines.unlink() return res - def _get_view_log_lines_action(rule): - rule.ensure_one() - domain = ( - "[('log_id.model_id', '=', %s), ('log_id.res_id', '=', " - "active_id),('field_id', 'in', %s)]" - % (rule.model_id.id, - rule.auditlog_line_access_rule_ids.mapped('field_ids').ids) - ) - return { + @api.multi + def _get_view_log_lines_action(self): + self.ensure_one() + fields_ids = self.auditlog_line_access_rule_ids.mapped('field_ids').ids + logs = self.env['auditlog.log'].sudo().search([ + ('model_id', '=', self.model_id.id), + ('res_id', 'in', self.env.context.get('active_ids')) + ]) + print('XXX', logs.ids) + lines = self.env['auditlog.log.line'].sudo().search([ + ('log_id', 'in', logs.ids), ('field_id', 'in', fields_ids) + ]) + print('YYY', lines.ids) + return { "name": _("View Log Lines"), "res_model": "auditlog.log.line", - "src_model": rule.model_id.model, - "binding_model_id": rule.model_id.id, - "domain": domain + #"src_model": self.model_id.model, + #"binding_model_id": self.model_id.id, + "view_mode": "tree,form", + "view_id": False, + "domain": [('id', 'in', lines.ids)], + "type": "ir.actions.act_window", } - def _create_server_action(self, rule): - - code = """ - if env.user.has_group("auditlog_security.group_can_view_audit_logs"): - rule = env['auditlog.rule'].browse(%s) - fields_ids = rule.auditlog_line_access_rule_ids.mapped('field_ids').ids - logs = env['auditlog.log'].sudo().search([('model_id', '=', rule.model_id.id), ('res_id', 'in', env.context.get('active_ids'))]) - domain = [('log_id', 'in', logs.ids), ('field_id', 'in', fields_ids)] - action_values = env.ref('auditlog_security.audit_log_line_action').read()[0] - action = action_values - """ % rule.id + @api.multi + def _create_server_action(self): + self.ensure_one() + code = \ + "rule = env['auditlog.rule'].browse(%s)\n" \ + "action = rule._get_view_log_lines_action()" % (self.id,) server_action = self.env['ir.actions.server'].sudo().create({ 'name': "View Log Lines", - 'model_id': rule.model_id.id, + 'model_id': self.model_id.id, 'state': "code", - 'code': code.strip() + 'code': code }) - rule.write({ + self.write({ 'server_action_id': server_action.id }) return server_action - def _get_view_log_action(rule): - #small helper , not used but may be useful. - rule.ensure_one() - domain = "[('model_id', '=', %s), ('res_id', '=', active_id)]" % ( - rule.model_id.id) - return { - 'name': _("View logs"), - 'res_model': 'auditlog.log', - 'src_model': rule.model_id.model, - 'binding_model_id': rule.model_id.id, - 'domain': domain, - } - @api.multi def subscribe(self): for rule in self: - server_action = self._create_server_action(rule) + server_action = rule._create_server_action() server_action.create_action() return super(AuditlogRule, self).subscribe() From 7eff32c5250b9df0e2edf1e10b3ae2313c711873 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Tue, 26 Jul 2022 11:14:11 +0200 Subject: [PATCH 11/30] [FIX] user_id fix, use related fields , allows for massive cleanup too. --- auditlog_security/models/auditlog_log_line.py | 1 + auditlog_security/models/auditlog_rule.py | 26 ------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/auditlog_security/models/auditlog_log_line.py b/auditlog_security/models/auditlog_log_line.py index b79f6b25ba0..076217bbc6f 100644 --- a/auditlog_security/models/auditlog_log_line.py +++ b/auditlog_security/models/auditlog_log_line.py @@ -10,6 +10,7 @@ class AuditlogLogLine(models.Model): user_id = fields.Many2one( 'res.users', + related="log_id.user_id", string="User", ) method = fields.Char("Method", related='log_id.method') diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 04ab70a03eb..59544922ad8 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -3,7 +3,6 @@ from odoo import exceptions, models, fields, api, modules, _ from odoo.addons.auditlog.models.rule import FIELDS_BLACKLIST -from odoo import SUPERUSER_ID class AuditlogRule(models.Model): @@ -14,12 +13,6 @@ class AuditlogRule(models.Model): ) server_action_id = fields.Many2one('ir.actions.server', "Server Action") - - def sudo(self, user=SUPERUSER_ID): - return super( - AuditlogRule, self.with_context(real_user=self.env.context.get('uid')) - ).sudo(user=user) - @api.onchange("model_id") def onchange_model_id(self): # if model changes we must wipe out all field ids @@ -87,22 +80,3 @@ def unsubscribe(self): rule.server_action_id.unlink() return super(AuditlogRule, self).unsubscribe() - def _prepare_log_line_vals_on_read(self, log, field, read_values): - res = super(AuditlogRule, self)._prepare_log_line_vals_on_read( - log, field, read_values) - res.update({'user_id': self.env.context.get('real_user')}) - return res - - def _prepare_log_line_vals_on_write( - self, log, field, old_values, new_values): - res = super(AuditlogRule, self)._prepare_log_line_vals_on_write( - log, field, old_values, new_values) - res.update({'user_id': self.env.context.get('real_user')}) - return res - - def _prepare_log_line_vals_on_create(self, log, field, new_values): - res = super(AuditlogRule, self)._prepare_log_line_vals_on_create( - log, field, new_values) - res.update({'user_id': self.env.context.get('real_user')}) - return res - From 9bcce334844107f17e0358b02ee797431769a332 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Tue, 26 Jul 2022 11:58:41 +0200 Subject: [PATCH 12/30] fixup! [FIX] user_id fix, use related fields , allows for massive cleanup too. --- auditlog_security/models/auditlog_rule.py | 20 +++++--------------- auditlog_security/views/auditlog_view.xml | 5 ++++- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 59544922ad8..3876d65c34c 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -18,25 +18,15 @@ def onchange_model_id(self): # if model changes we must wipe out all field ids self.auditlog_line_access_rule_ids.unlink() - @api.multi - def unlink(self): - lines = self.mapped("auditlog_line_access_rule_ids") - res = super(AuditlogRule, self).unlink() - if res: - lines.unlink() - return res - - @api.multi - def _get_view_log_lines_action(self): - self.ensure_one() - fields_ids = self.auditlog_line_access_rule_ids.mapped('field_ids').ids + @api.model + def _get_view_log_lines_action(self, model): logs = self.env['auditlog.log'].sudo().search([ - ('model_id', '=', self.model_id.id), + ('model_id', '=', model.id), ('res_id', 'in', self.env.context.get('active_ids')) ]) print('XXX', logs.ids) lines = self.env['auditlog.log.line'].sudo().search([ - ('log_id', 'in', logs.ids), ('field_id', 'in', fields_ids) + ('log_id', 'in', logs.ids) ]) print('YYY', lines.ids) return { @@ -55,7 +45,7 @@ def _create_server_action(self): self.ensure_one() code = \ "rule = env['auditlog.rule'].browse(%s)\n" \ - "action = rule._get_view_log_lines_action()" % (self.id,) + "action = rule._get_view_log_lines_action()" % (self.model_id,) server_action = self.env['ir.actions.server'].sudo().create({ 'name': "View Log Lines", 'model_id': self.model_id.id, diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index 34fe09ec042..433d19d9d26 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -99,12 +99,15 @@ --> +
From df143e68c45bfe545ff0b9d9e92786e4cee29532 Mon Sep 17 00:00:00 2001 From: Pieter Paulussen Date: Wed, 24 Mar 2021 11:14:42 +0100 Subject: [PATCH 13/30] [IMP] auditlog: prevent cascading delete of logs when models or fields are unlinked When a field or a model is unlinked, keep the related audit logs. Denormalize the field and model info on the logs and log lines so that the information is still available after the deletion of the related data model. Also, to improve the performance of the deletion of fields and models, add indexes on the log's model_id and log line's field_id. Co-Authored-By: Stefan Rijnhart --- auditlog/__manifest__.py | 2 +- .../migrations/14.0.1.1.0/pre-migration.py | 73 +++++++++++ auditlog/models/log.py | 56 ++++++++- auditlog/models/rule.py | 44 +++++-- auditlog/readme/CONTRIBUTORS.rst | 4 + auditlog/tests/test_auditlog.py | 118 +++++++++++++++++- auditlog/views/auditlog_view.xml | 24 +++- 7 files changed, 300 insertions(+), 21 deletions(-) create mode 100644 auditlog/migrations/14.0.1.1.0/pre-migration.py diff --git a/auditlog/__manifest__.py b/auditlog/__manifest__.py index 0bf459cbf71..4626a18a678 100644 --- a/auditlog/__manifest__.py +++ b/auditlog/__manifest__.py @@ -3,7 +3,7 @@ { 'name': "Audit Log", - 'version': "11.0.1.0.1", + 'version': "11.0.1.1.1", 'author': "ABF OSIELL,Odoo Community Association (OCA)", 'license': "AGPL-3", 'website': "https://github.com/OCA/server-tools/", diff --git a/auditlog/migrations/14.0.1.1.0/pre-migration.py b/auditlog/migrations/14.0.1.1.0/pre-migration.py new file mode 100644 index 00000000000..53a120e98bc --- /dev/null +++ b/auditlog/migrations/14.0.1.1.0/pre-migration.py @@ -0,0 +1,73 @@ +# © 2018 Pieter Paulussen +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + + +def migrate(cr, version): + if not version: + return + logger = logging.getLogger(__name__) + logger.info( + "Creating columns: auditlog_log (model_name, model_model) " + "and auditlog_log_line (field_name, field_description)." + ) + cr.execute( + """ + ALTER TABLE auditlog_log + ADD COLUMN IF NOT EXISTS model_name VARCHAR, + ADD COLUMN IF NOT EXISTS model_model VARCHAR; + ALTER TABLE auditlog_log_line + ADD COLUMN IF NOT EXISTS field_name VARCHAR, + ADD COLUMN IF NOT EXISTS field_description VARCHAR; + ALTER TABLE auditlog_rule + ADD COLUMN IF NOT EXISTS model_name VARCHAR, + ADD COLUMN IF NOT EXISTS model_model VARCHAR; + """ + ) + logger.info( + "Creating indexes on auditlog_log column 'model_id' and " + "auditlog_log_line column 'field_id'." + ) + cr.execute( + """ + CREATE INDEX IF NOT EXISTS + auditlog_log_model_id_index ON auditlog_log (model_id); + CREATE INDEX IF NOT EXISTS + auditlog_log_line_field_id_index ON auditlog_log_line (field_id); + """ + ) + logger.info( + "Preemptive fill auditlog_log columns: 'model_name' and " "'model_model'." + ) + cr.execute( + """ + UPDATE auditlog_log al + SET model_name = im.name, model_model = im.model + FROM ir_model im + WHERE im.id = al.model_id AND model_name IS NULL + """ + ) + logger.info( + "Preemtive fill of auditlog_log_line columns: 'field_name' and" + " 'field_description'." + ) + cr.execute( + """ + UPDATE auditlog_log_line al + SET field_name = imf.name, field_description = imf.field_description + FROM ir_model_fields imf + WHERE imf.id = al.field_id AND field_name IS NULL + """ + ) + logger.info( + "Preemptive fill auditlog_rule columns: 'model_name' and " "'model_model'." + ) + cr.execute( + """ + UPDATE auditlog_rule al + SET model_name = im.name, model_model = im.model + FROM ir_model im + WHERE im.id = al.model_id AND model_name IS NULL + """ + ) + logger.info("Successfully updated auditlog tables") diff --git a/auditlog/models/log.py b/auditlog/models/log.py index bbab1fc9211..c4edfaebaef 100644 --- a/auditlog/models/log.py +++ b/auditlog/models/log.py @@ -1,6 +1,7 @@ # Copyright 2015 ABF OSIELL # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import models, fields +from odoo import _, api, fields, models +from odoo.exceptions import UserError class AuditlogLog(models.Model): @@ -10,7 +11,10 @@ class AuditlogLog(models.Model): name = fields.Char("Resource Name", size=64) model_id = fields.Many2one( - 'ir.model', string="Model") + "ir.model", string="Model", index=True, ondelete="set null" + ) + model_name = fields.Char(readonly=True) + model_model = fields.Char(string="Technical Model Name", readonly=True) res_id = fields.Integer("Resource ID") user_id = fields.Many2one( 'res.users', string="User") @@ -27,13 +31,34 @@ class AuditlogLog(models.Model): ], string="Type") + @api.model_create_multi + def create(self, vals_list): + """ Insert model_name and model_model field values upon creation. """ + for vals in vals_list: + if not vals.get("model_id"): + raise UserError(_("No model defined to create log.")) + model = self.env["ir.model"].browse(vals["model_id"]) + vals.update({"model_name": model.name, "model_model": model.model}) + return super().create(vals_list) + + def write(self, vals): + """Update model_name and model_model field values to reflect model_id + changes.""" + if "model_id" in vals: + if not vals["model_id"]: + raise UserError(_("The field 'model_id' cannot be empty.")) + model = self.env["ir.model"].browse(vals["model_id"]) + vals.update({"model_name": model.name, "model_model": model.model}) + return super().write(vals) + class AuditlogLogLine(models.Model): _name = 'auditlog.log.line' _description = "Auditlog - Log details (fields updated)" field_id = fields.Many2one( - 'ir.model.fields', ondelete='cascade', string="Field", required=True) + "ir.model.fields", ondelete="set null", string="Field", index=True + ) log_id = fields.Many2one( 'auditlog.log', string="Log", ondelete='cascade', index=True) old_value = fields.Text("Old Value") @@ -43,3 +68,28 @@ class AuditlogLogLine(models.Model): field_name = fields.Char("Technical name", related='field_id.name') field_description = fields.Char( "Description", related='field_id.field_description') + + @api.model_create_multi + def create(self, vals_list): + """Ensure field_id is not empty on creation and store field_name and + field_description.""" + for vals in vals_list: + if not vals.get("field_id"): + raise UserError(_("No field defined to create line.")) + field = self.env["ir.model.fields"].browse(vals["field_id"]) + vals.update( + {"field_name": field.name, "field_description": field.field_description} + ) + return super().create(vals_list) + + def write(self, vals): + """Ensure field_id is set during write and update field_name and + field_description values.""" + if "field_id" in vals: + if not vals["field_id"]: + raise UserError(_("The field 'field_id' cannot be empty.")) + field = self.env["ir.model.fields"].browse(vals["field_id"]) + vals.update( + {"field_name": field.name, "field_description": field.field_description} + ) + return super().write(vals) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index 6cdc7233ccd..0ba9784fcbe 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -1,7 +1,10 @@ # Copyright 2015 ABF OSIELL # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import models, fields, api, modules, _ +import copy + +from odoo import _, api, fields, models, modules +from odoo.exceptions import UserError FIELDS_BLACKLIST = [ 'id', 'create_uid', 'create_date', 'write_uid', 'write_date', @@ -46,8 +49,15 @@ class AuditlogRule(models.Model): name = fields.Char("Name", size=32, required=True) model_id = fields.Many2one( - 'ir.model', "Model", required=True, - help="Select model for which you want to generate log.") + "ir.model", + "Model", + help="Select model for which you want to generate log.", + states={"subscribed": [("readonly", True)]}, + ondelete="set null", + index=True, + ) + model_name = fields.Char(readonly=True) + model_model = fields.Char(string="Technical Model Name", readonly=True) user_ids = fields.Many2many( 'res.users', 'audittail_rules_users', @@ -119,11 +129,11 @@ def _patch_methods(self): for rule in self: if rule.state != 'subscribed': continue - if not self.pool.get(rule.model_id.model): + if not self.pool.get(rule.model_id.model or rule.model_model): # ignore rules for models not loadable currently continue model_cache[rule.model_id.model] = rule.model_id.id - model_model = self.env[rule.model_id.model] + model_model = self.env[rule.model_id.model or rule.model_model] # CRUD # -> create check_attr = 'auditlog_ruled_create' @@ -160,10 +170,11 @@ def _revert_methods(self): """Restore original ORM methods of models defined in rules.""" updated = False for rule in self: - model_model = self.env[rule.model_id.model] - for method in ['create', 'read', 'write', 'unlink']: - if getattr(rule, 'log_%s' % method) and hasattr( - getattr(model_model, method), 'origin'): + model_model = self.env[rule.model_id.model or rule.model_model] + for method in ["create", "read", "write", "unlink"]: + if getattr(rule, "log_%s" % method) and hasattr( + getattr(model_model, method), "origin" + ): model_model._revert_method(method) delattr(type(model_model), 'auditlog_ruled_%s' % method) updated = True @@ -173,7 +184,11 @@ def _revert_methods(self): @api.model def create(self, vals): """Update the registry when a new rule is created.""" - new_record = super(AuditlogRule, self).create(vals) + if "model_id" not in vals or not vals["model_id"]: + raise UserError(_("No model defined to create line.")) + model = self.env["ir.model"].browse(vals["model_id"]) + vals.update({"model_name": model.name, "model_model": model.model}) + new_record = super().create(vals) if new_record._register_hook(): modules.registry.Registry(self.env.cr.dbname).signal_changes() return new_record @@ -181,10 +196,15 @@ def create(self, vals): @api.multi def write(self, vals): """Update the registry when existing rules are updated.""" - super(AuditlogRule, self).write(vals) + if "model_id" in vals: + if not vals["model_id"]: + raise UserError(_("Field 'model_id' cannot be empty.")) + model = self.env["ir.model"].browse(vals["model_id"]) + vals.update({"model_name": model.name, "model_model": model.model}) + res = super().write(vals) if self._register_hook(): modules.registry.Registry(self.env.cr.dbname).signal_changes() - return True + return res @api.multi def unlink(self): diff --git a/auditlog/readme/CONTRIBUTORS.rst b/auditlog/readme/CONTRIBUTORS.rst index 1567f0b2cdc..5b88f88de33 100644 --- a/auditlog/readme/CONTRIBUTORS.rst +++ b/auditlog/readme/CONTRIBUTORS.rst @@ -1,3 +1,7 @@ * Sebastien Alix * Holger Brunn * Holden Rehg +* Eric Lembregts +* Pieter Paulussen +* Alan Ramos +* Stefan Rijnhart diff --git a/auditlog/tests/test_auditlog.py b/auditlog/tests/test_auditlog.py index 996cf5fa12a..195a91e95cc 100644 --- a/auditlog/tests/test_auditlog.py +++ b/auditlog/tests/test_auditlog.py @@ -1,6 +1,11 @@ # Copyright 2015 Therp BV +# © 2018 Pieter Paulussen +# © 2021 Stefan Rijnhart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo.tests.common import TransactionCase +from odoo.modules.migration import load_script +from odoo.tests.common import SavepointCase, TransactionCase + +from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG class AuditlogCommon(object): @@ -119,3 +124,114 @@ def setUp(self): def tearDown(self): self.groups_rule.unlink() super(TestAuditlogFast, self).tearDown() + + +class TestFieldRemoval(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + # Clear all existing logging lines + existing_audit_logs = cls.env["auditlog.log"].search([]) + existing_audit_logs.unlink() + + # Create a test model to remove + cls.test_model = cls.env["ir.model"].create( + {"name": "x_test_model", "model": "x_test.model", "state": "manual"} + ) + + # Create a test model field to remove + cls.test_field = cls.env["ir.model.fields"].create( + { + "name": "x_test_field", + "field_description": "x_Test Field", + "model_id": cls.test_model.id, + "ttype": "char", + "state": "manual", + } + ) + + # Setup auditlog rule + cls.auditlog_rule = cls.env["auditlog.rule"].create( + { + "name": "test.model", + "model_id": cls.test_model.id, + "log_type": "fast", + "log_read": False, + "log_create": True, + "log_write": True, + "log_unlink": False, + } + ) + + cls.auditlog_rule.subscribe() + # Trigger log creation + rec = cls.env["x_test.model"].create({"x_test_field": "test value"}) + rec.write({"x_test_field": "test value 2"}) + + cls.logs = cls.env["auditlog.log"].search( + [("res_id", "=", rec.id), ("model_id", "=", cls.test_model.id)] + ) + + def assert_values(self): + """Assert that the denormalized field and model info is present + on the auditlog records""" + self.logs.refresh() + self.assertEqual(self.logs[0].model_name, "x_test_model") + self.assertEqual(self.logs[0].model_model, "x_test.model") + + log_lines = self.logs.mapped("line_ids") + self.assertEqual(len(log_lines), 2) + self.assertEqual(log_lines[0].field_name, "x_test_field") + self.assertEqual(log_lines[0].field_description, "x_Test Field") + + self.auditlog_rule.refresh() + self.assertEqual(self.auditlog_rule.model_name, "x_test_model") + self.assertEqual(self.auditlog_rule.model_model, "x_test.model") + + def test_01_field_and_model_removal(self): + """ Test field and model removal to check auditlog line persistence """ + self.assert_values() + + # Remove the field + self.test_field.with_context({MODULE_UNINSTALL_FLAG: True}).unlink() + self.assert_values() + # The field should not be linked + self.assertFalse(self.logs.mapped("line_ids.field_id")) + + # Remove the model + self.test_model.with_context({MODULE_UNINSTALL_FLAG: True}).unlink() + self.assert_values() + + # The model should not be linked + self.assertFalse(self.logs.mapped("model_id")) + # Assert rule values + self.assertFalse(self.auditlog_rule.model_id) + + def test_02_migration(self): + """Test the migration of the data model related to this feature""" + # Drop the data model + self.env.cr.execute( + """ALTER TABLE auditlog_log + DROP COLUMN model_name, DROP COLUMN model_model""" + ) + self.env.cr.execute( + """ALTER TABLE auditlog_rule + DROP COLUMN model_name, DROP COLUMN model_model""" + ) + self.env.cr.execute( + """ALTER TABLE auditlog_log_line + DROP COLUMN field_name, DROP COLUMN field_description""" + ) + + # Recreate the data model + mod = load_script( + "auditlog/migrations/14.0.1.1.0/pre-migration.py", "pre-migration" + ) + mod.migrate(self.env.cr, "14.0.1.0.2") + + # Values are restored + self.assert_values() + + # The migration script is tolerant if the data model is already in place + mod.migrate(self.env.cr, "14.0.1.0.2") diff --git a/auditlog/views/auditlog_view.xml b/auditlog/views/auditlog_view.xml index 2ec7b480896..897d8ded358 100644 --- a/auditlog/views/auditlog_view.xml +++ b/auditlog/views/auditlog_view.xml @@ -104,9 +104,20 @@ - - - + + + /> + + + @@ -117,7 +128,12 @@ - + + From 568c7544af1e3f8d1f0d3a50aed2b8e58f50ca08 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Tue, 26 Jul 2022 13:44:50 +0200 Subject: [PATCH 14/30] fixup! [IMP] auditlog: prevent cascading delete of logs when models or fields are unlinked --- .../pre-migration.py | 0 auditlog/models/log.py | 42 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) rename auditlog/migrations/{14.0.1.1.0 => 11.0.1.1.1}/pre-migration.py (100%) diff --git a/auditlog/migrations/14.0.1.1.0/pre-migration.py b/auditlog/migrations/11.0.1.1.1/pre-migration.py similarity index 100% rename from auditlog/migrations/14.0.1.1.0/pre-migration.py rename to auditlog/migrations/11.0.1.1.1/pre-migration.py diff --git a/auditlog/models/log.py b/auditlog/models/log.py index c4edfaebaef..55cd20fedba 100644 --- a/auditlog/models/log.py +++ b/auditlog/models/log.py @@ -31,16 +31,16 @@ class AuditlogLog(models.Model): ], string="Type") - @api.model_create_multi - def create(self, vals_list): + @api.model + def create(self, values): """ Insert model_name and model_model field values upon creation. """ - for vals in vals_list: - if not vals.get("model_id"): - raise UserError(_("No model defined to create log.")) - model = self.env["ir.model"].browse(vals["model_id"]) - vals.update({"model_name": model.name, "model_model": model.model}) - return super().create(vals_list) + if not values.get("model_id"): + raise UserError(_("No model defined to create log.")) + model = self.env["ir.model"].browse(values["model_id"]) + values.update({"model_name": model.name, "model_model": model.model}) + return super(AuditlogLog, self).create(values) + @api.multi def write(self, vals): """Update model_name and model_model field values to reflect model_id changes.""" @@ -49,7 +49,7 @@ def write(self, vals): raise UserError(_("The field 'model_id' cannot be empty.")) model = self.env["ir.model"].browse(vals["model_id"]) vals.update({"model_name": model.name, "model_model": model.model}) - return super().write(vals) + return super(AuditlogLog, self).write(vals) class AuditlogLogLine(models.Model): @@ -69,19 +69,19 @@ class AuditlogLogLine(models.Model): field_description = fields.Char( "Description", related='field_id.field_description') - @api.model_create_multi - def create(self, vals_list): + @api.model + def create(self, values): """Ensure field_id is not empty on creation and store field_name and field_description.""" - for vals in vals_list: - if not vals.get("field_id"): - raise UserError(_("No field defined to create line.")) - field = self.env["ir.model.fields"].browse(vals["field_id"]) - vals.update( - {"field_name": field.name, "field_description": field.field_description} - ) - return super().create(vals_list) - + if not values.get("field_id"): + raise UserError(_("No field defined to create line.")) + field = self.env["ir.model.fields"].browse(values["field_id"]) + values.update( + {"field_name": field.name, "field_description": field.field_description} + ) + return super(AuditlogLogLine, self).create(values) + + @api.multi def write(self, vals): """Ensure field_id is set during write and update field_name and field_description values.""" @@ -92,4 +92,4 @@ def write(self, vals): vals.update( {"field_name": field.name, "field_description": field.field_description} ) - return super().write(vals) + return super(AuditlogLogLine, self).write(vals) From c3fb342665e0acbd68b8c9c8fabe12f2a6f57beb Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Tue, 26 Jul 2022 14:45:53 +0200 Subject: [PATCH 15/30] [IMP] review improvements --- auditlog/models/log.py | 57 +++---------------- auditlog_security/__manifest__.py | 2 +- .../migrations/11.0.1.1.2/pre-migration.py | 51 +++++++++++++++++ .../models/auditlog_line_access_rule.py | 39 +++++++------ auditlog_security/models/auditlog_log_line.py | 18 +++++- auditlog_security/models/auditlog_rule.py | 36 ++++++------ 6 files changed, 115 insertions(+), 88 deletions(-) create mode 100644 auditlog_security/migrations/11.0.1.1.2/pre-migration.py diff --git a/auditlog/models/log.py b/auditlog/models/log.py index 55cd20fedba..fcc0bbc0f72 100644 --- a/auditlog/models/log.py +++ b/auditlog/models/log.py @@ -11,10 +11,13 @@ class AuditlogLog(models.Model): name = fields.Char("Resource Name", size=64) model_id = fields.Many2one( - "ir.model", string="Model", index=True, ondelete="set null" + "ir.model", string="Model", index=True, + required=True + ) + model_name = fields.Char(readonly=True, related='model_id.name' ) + model_model = fields.Char(string="Technical Model Name", readonly=True, + related="model_id.model" ) - model_name = fields.Char(readonly=True) - model_model = fields.Char(string="Technical Model Name", readonly=True) res_id = fields.Integer("Resource ID") user_id = fields.Many2one( 'res.users', string="User") @@ -31,33 +34,14 @@ class AuditlogLog(models.Model): ], string="Type") - @api.model - def create(self, values): - """ Insert model_name and model_model field values upon creation. """ - if not values.get("model_id"): - raise UserError(_("No model defined to create log.")) - model = self.env["ir.model"].browse(values["model_id"]) - values.update({"model_name": model.name, "model_model": model.model}) - return super(AuditlogLog, self).create(values) - - @api.multi - def write(self, vals): - """Update model_name and model_model field values to reflect model_id - changes.""" - if "model_id" in vals: - if not vals["model_id"]: - raise UserError(_("The field 'model_id' cannot be empty.")) - model = self.env["ir.model"].browse(vals["model_id"]) - vals.update({"model_name": model.name, "model_model": model.model}) - return super(AuditlogLog, self).write(vals) - class AuditlogLogLine(models.Model): _name = 'auditlog.log.line' _description = "Auditlog - Log details (fields updated)" field_id = fields.Many2one( - "ir.model.fields", ondelete="set null", string="Field", index=True + "ir.model.fields", ondelete="set null", string="Field", index=True, + required=True ) log_id = fields.Many2one( 'auditlog.log', string="Log", ondelete='cascade', index=True) @@ -68,28 +52,3 @@ class AuditlogLogLine(models.Model): field_name = fields.Char("Technical name", related='field_id.name') field_description = fields.Char( "Description", related='field_id.field_description') - - @api.model - def create(self, values): - """Ensure field_id is not empty on creation and store field_name and - field_description.""" - if not values.get("field_id"): - raise UserError(_("No field defined to create line.")) - field = self.env["ir.model.fields"].browse(values["field_id"]) - values.update( - {"field_name": field.name, "field_description": field.field_description} - ) - return super(AuditlogLogLine, self).create(values) - - @api.multi - def write(self, vals): - """Ensure field_id is set during write and update field_name and - field_description values.""" - if "field_id" in vals: - if not vals["field_id"]: - raise UserError(_("The field 'field_id' cannot be empty.")) - field = self.env["ir.model.fields"].browse(vals["field_id"]) - vals.update( - {"field_name": field.name, "field_description": field.field_description} - ) - return super(AuditlogLogLine, self).write(vals) diff --git a/auditlog_security/__manifest__.py b/auditlog_security/__manifest__.py index 358c8ad6c11..91c212af1db 100644 --- a/auditlog_security/__manifest__.py +++ b/auditlog_security/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Audit Log User Permissions", - "version": "11.0.1.0.0", + "version": "11.0.1.1.2", "author": "Therp B.V.,Odoo Community Association (OCA)", "license": "AGPL-3", "website": "https://github.com/OCA/server-tools/", diff --git a/auditlog_security/migrations/11.0.1.1.2/pre-migration.py b/auditlog_security/migrations/11.0.1.1.2/pre-migration.py new file mode 100644 index 00000000000..5080ad73307 --- /dev/null +++ b/auditlog_security/migrations/11.0.1.1.2/pre-migration.py @@ -0,0 +1,51 @@ +# © 2018 Pieter Paulussen +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + + +def migrate(cr, version): + if not version: + return + logger = logging.getLogger(__name__) + logger.info( + "Creating columns: auditlog_log_line (method. user_id) " + ) + cr.execute( + """ + ALTER TABLE auditlog_log_line + ADD COLUMN IF NOT EXISTS method VARCHAR, + ADD COLUMN IF NOT EXISTS user_id INTEGER; + + """ + ) + cr.execute( + """ + ALTER TABLE auditlog_log_line DROP CONSTRAINT IF EXISTS auditlog_log_line_user_id_fkey; + ALTER TABLE auditlog_log_line ADD constraint + auditlog_log_line_user_id_fkey + FOREIGN KEY (user_id) REFERENCES res_users(id) ON DELETE SET NULL; + """ + ) + logger.info( + "Creating indexes on auditlog_log_line column 'method'" + ) + cr.execute( + """ + CREATE INDEX IF NOT EXISTS + auditlog_log_line_method_index ON auditlog_log_line (method); + CREATE INDEX IF NOT EXISTS + auditlog_log_line_user_id_index ON auditlog_log_line (user_id); + """ + ) + logger.info( + "Preemtive fill of auditlog_log_line columns: 'method', user_id" + ) + cr.execute( + """ + UPDATE auditlog_log_line aline + SET method = al.method, user_id = al.user_id + FROM auditlog_log al + WHERE al.id = aline.log_id; + """ + ) + logger.info("Successfully updated auditlog tables") diff --git a/auditlog_security/models/auditlog_line_access_rule.py b/auditlog_security/models/auditlog_line_access_rule.py index fc210e851c7..7b70b924daa 100644 --- a/auditlog_security/models/auditlog_line_access_rule.py +++ b/auditlog_security/models/auditlog_line_access_rule.py @@ -30,20 +30,10 @@ def needs_rule(self): return bool(self.group_ids) def get_linked_rules(self): - # return with context key so that deletion will not be forbidden return self.env["ir.rule"].search( [("auditlog_line_access_rule_id", "in", self.ids)] ) - def get_field_ids_domain(self): - """note this solution will work only with a hardcoded design of models, - because on initialization , self.model_id.id still is not defined. - for now, to keep generality we put the filtering in the view.""" - return [ - ("model_id", "=", self.env.ref("base.model_res_partner").id), - ("name", "not in", FIELDS_BLACKLIST), - ] - def unlink(self): to_delete = self.get_linked_rules() res = super(AuditlogLineAccessRule, self).unlink() @@ -86,17 +76,19 @@ def write(self, vals): this.generate_rules() else: this.get_linked_rules().with_context(auditlog_write=True).unlink() + return res def generate_rules(self): old_rule = self.env["ir.rule"].search( [("auditlog_line_access_rule_id", "=", self.id)], limit=1 ) - values = self._prepare_rule_values() - if old_rule: - old_rule.with_context(auditlog_write=True).write(values) - else: - self.with_context(auditlog_write=True).env["ir.rule"].create(values) + dict_values = self._prepare_rule_values() + for values in dict_values: + if old_rule: + old_rule.with_context(auditlog_write=True).write(values) + else: + self.with_context(auditlog_write=True).env["ir.rule"].create(values) def _prepare_rule_values(self): domain_force = "[" + " ('log_id.model_id' , '=', %s)," % ( @@ -108,11 +100,24 @@ def _prepare_rule_values(self): else: domain_force = "[('model_id', '=', %s)]" % (self.model_id.id) model = self.env.ref("auditlog.model_auditlog_log") - return { + auditlog_security_group = self.env.ref( + 'auditlog_security.group_can_view_audit_logs') + return [ + { "name": "auditlog_extended_%s" % self.id, "model_id": model.id, "groups": [(6, 0, self.group_ids.ids)], "perm_read": True, "domain_force": domain_force, "auditlog_line_access_rule_id": self.id, - } + }, + { + "name": "auditlog_extended_%s" % self.id, + "model_id": model.id, + "groups": [(6, 0, [auditlog_security_group.id])], + "perm_read": True, + "domain_force": [(1, '=', 1)], + "auditlog_line_access_rule_id": self.id, + }] + + diff --git a/auditlog_security/models/auditlog_log_line.py b/auditlog_security/models/auditlog_log_line.py index 076217bbc6f..dbe32b51079 100644 --- a/auditlog_security/models/auditlog_log_line.py +++ b/auditlog_security/models/auditlog_log_line.py @@ -1,7 +1,7 @@ # Copyright 2022 Therp B.V. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import exceptions, models, fields +from odoo import api, exceptions, models, fields class AuditlogLogLine(models.Model): @@ -10,9 +10,21 @@ class AuditlogLogLine(models.Model): user_id = fields.Many2one( 'res.users', - related="log_id.user_id", + compute="compute_user_id", + store=True, + index=True, string="User", ) - method = fields.Char("Method", related='log_id.method') + method = fields.Char("Method", compute='compute_method', store=True, index=True) + + @api.depends('log_id.method') + def compute_method(self): + for this in self: + this.method=this.log_id.method + + @api.depends('log_id.user_id') + def compute_user_id(self): + for this in self: + this.user_id=this.log_id.user_id diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 3876d65c34c..f3ec1da5c55 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -11,7 +11,9 @@ class AuditlogRule(models.Model): auditlog_line_access_rule_ids = fields.One2many( "auditlog.line.access.rule", "auditlog_rule_id", ondelete="cascade" ) - server_action_id = fields.Many2one('ir.actions.server', "Server Action") + server_action_id = fields.Many2one('ir.actions.server', "Server Action", + ondelete="cascade" + ) @api.onchange("model_id") def onchange_model_id(self): @@ -19,33 +21,31 @@ def onchange_model_id(self): self.auditlog_line_access_rule_ids.unlink() @api.model - def _get_view_log_lines_action(self, model): + def _get_view_log_lines_action(self, model_id): logs = self.env['auditlog.log'].sudo().search([ - ('model_id', '=', model.id), + ('model_id', '=', model_id), ('res_id', 'in', self.env.context.get('active_ids')) - ]) - print('XXX', logs.ids) + ]) lines = self.env['auditlog.log.line'].sudo().search([ ('log_id', 'in', logs.ids) - ]) - print('YYY', lines.ids) + ]) return { - "name": _("View Log Lines"), - "res_model": "auditlog.log.line", - #"src_model": self.model_id.model, - #"binding_model_id": self.model_id.id, - "view_mode": "tree,form", - "view_id": False, - "domain": [('id', 'in', lines.ids)], - "type": "ir.actions.act_window", - } + "name": _("View Log Lines"), + "res_model": "auditlog.log.line", + "view_mode": "tree,form", + #"src_model": self.model_id.model, + #"binding_model_id": self.model_id.id, + "view_id": False, + "domain": [('id', 'in', lines.ids)], + "type": "ir.actions.act_window", + } @api.multi def _create_server_action(self): self.ensure_one() code = \ - "rule = env['auditlog.rule'].browse(%s)\n" \ - "action = rule._get_view_log_lines_action()" % (self.model_id,) + "action = env['auditlog.rule']._get_view_log_lines_action(%s)" % ( + self.model_id.id,) server_action = self.env['ir.actions.server'].sudo().create({ 'name': "View Log Lines", 'model_id': self.model_id.id, From eb494a8676b8bc98c94dede7004fc0c5fadd7def Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 27 Jul 2022 09:41:22 +0200 Subject: [PATCH 16/30] fixup! [IMP] review improvements --- auditlog_security/models/auditlog_rule.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index f3ec1da5c55..85e9f9975f9 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -43,9 +43,8 @@ def _get_view_log_lines_action(self, model_id): @api.multi def _create_server_action(self): self.ensure_one() - code = \ - "action = env['auditlog.rule']._get_view_log_lines_action(%s)" % ( - self.model_id.id,) + code = "action = env['auditlog.rule']._get_view_log_lines_action(%s)" % ( + self.model_id.id,) server_action = self.env['ir.actions.server'].sudo().create({ 'name': "View Log Lines", 'model_id': self.model_id.id, From 6dbb36451a53c034fa8bb8abcc8d0039db3514c3 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 27 Jul 2022 10:44:04 +0200 Subject: [PATCH 17/30] [FIX] second review --- auditlog_security/__manifest__.py | 2 +- .../{11.0.1.1.2 => 11.0.1.1.3}/pre-migration.py | 9 ++++++++- auditlog_security/models/auditlog_log_line.py | 10 ++++++++++ auditlog_security/models/auditlog_rule.py | 7 ++----- 4 files changed, 21 insertions(+), 7 deletions(-) rename auditlog_security/migrations/{11.0.1.1.2 => 11.0.1.1.3}/pre-migration.py (74%) diff --git a/auditlog_security/__manifest__.py b/auditlog_security/__manifest__.py index 91c212af1db..af123988d0b 100644 --- a/auditlog_security/__manifest__.py +++ b/auditlog_security/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Audit Log User Permissions", - "version": "11.0.1.1.2", + "version": "11.0.1.1.3", "author": "Therp B.V.,Odoo Community Association (OCA)", "license": "AGPL-3", "website": "https://github.com/OCA/server-tools/", diff --git a/auditlog_security/migrations/11.0.1.1.2/pre-migration.py b/auditlog_security/migrations/11.0.1.1.3/pre-migration.py similarity index 74% rename from auditlog_security/migrations/11.0.1.1.2/pre-migration.py rename to auditlog_security/migrations/11.0.1.1.3/pre-migration.py index 5080ad73307..8904db08d5e 100644 --- a/auditlog_security/migrations/11.0.1.1.2/pre-migration.py +++ b/auditlog_security/migrations/11.0.1.1.3/pre-migration.py @@ -15,6 +15,7 @@ def migrate(cr, version): ALTER TABLE auditlog_log_line ADD COLUMN IF NOT EXISTS method VARCHAR, ADD COLUMN IF NOT EXISTS user_id INTEGER; + ADD COLUMN IF NOT EXISTS model_id INTEGER; """ ) @@ -24,6 +25,10 @@ def migrate(cr, version): ALTER TABLE auditlog_log_line ADD constraint auditlog_log_line_user_id_fkey FOREIGN KEY (user_id) REFERENCES res_users(id) ON DELETE SET NULL; + ALTER TABLE auditlog_log_line DROP CONSTRAINT IF EXISTS auditlog_log_line_model_id_fkey; + ALTER TABLE auditlog_log_line ADD constraint + auditlog_log_line_model_id_fkey + FOREIGN KEY (model_id) REFERENCES ir_model(id) ON DELETE SET NULL; """ ) logger.info( @@ -35,6 +40,8 @@ def migrate(cr, version): auditlog_log_line_method_index ON auditlog_log_line (method); CREATE INDEX IF NOT EXISTS auditlog_log_line_user_id_index ON auditlog_log_line (user_id); + CREATE INDEX IF NOT EXISTS + auditlog_log_line_model_id_index ON auditlog_log_line (model_id); """ ) logger.info( @@ -43,7 +50,7 @@ def migrate(cr, version): cr.execute( """ UPDATE auditlog_log_line aline - SET method = al.method, user_id = al.user_id + SET method = al.method, user_id = al.user_id , model_id = al.model_id FROM auditlog_log al WHERE al.id = aline.log_id; """ diff --git a/auditlog_security/models/auditlog_log_line.py b/auditlog_security/models/auditlog_log_line.py index dbe32b51079..92c7f5cd624 100644 --- a/auditlog_security/models/auditlog_log_line.py +++ b/auditlog_security/models/auditlog_log_line.py @@ -16,6 +16,12 @@ class AuditlogLogLine(models.Model): string="User", ) method = fields.Char("Method", compute='compute_method', store=True, index=True) + model_id = fields.Many2one( + "ir.model", + compute='compute_model_id', + store=True, + index=True) + @api.depends('log_id.method') def compute_method(self): @@ -27,4 +33,8 @@ def compute_user_id(self): for this in self: this.user_id=this.log_id.user_id + @api.depends('log_id.model_id') + def compute_model_id(self): + for this in self: + this.model_id=this.log_id.model_id diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 85e9f9975f9..6adaecd8d10 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -22,12 +22,9 @@ def onchange_model_id(self): @api.model def _get_view_log_lines_action(self, model_id): - logs = self.env['auditlog.log'].sudo().search([ + lines = self.env['auditlog.log.line'].search([ ('model_id', '=', model_id), - ('res_id', 'in', self.env.context.get('active_ids')) - ]) - lines = self.env['auditlog.log.line'].sudo().search([ - ('log_id', 'in', logs.ids) + ('log_id.res_id', 'in', self.env.context.get('active_ids')) ]) return { "name": _("View Log Lines"), From f8bd300c20c95f51b10ba2b5c92425bccb078344 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 27 Jul 2022 10:48:47 +0200 Subject: [PATCH 18/30] fixup! [FIX] second review --- auditlog_security/migrations/11.0.1.1.3/pre-migration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auditlog_security/migrations/11.0.1.1.3/pre-migration.py b/auditlog_security/migrations/11.0.1.1.3/pre-migration.py index 8904db08d5e..0a48c5ef2d6 100644 --- a/auditlog_security/migrations/11.0.1.1.3/pre-migration.py +++ b/auditlog_security/migrations/11.0.1.1.3/pre-migration.py @@ -14,7 +14,7 @@ def migrate(cr, version): """ ALTER TABLE auditlog_log_line ADD COLUMN IF NOT EXISTS method VARCHAR, - ADD COLUMN IF NOT EXISTS user_id INTEGER; + ADD COLUMN IF NOT EXISTS user_id INTEGER, ADD COLUMN IF NOT EXISTS model_id INTEGER; """ From 385407e6e85a6eda63985d5be050c4f5e32cb983 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 27 Jul 2022 11:03:35 +0200 Subject: [PATCH 19/30] fixup! [FIX] second review --- auditlog_security/__manifest__.py | 2 +- .../{11.0.1.1.3 => 11.0.1.1.4}/pre-migration.py | 5 ++++- auditlog_security/models/auditlog_log_line.py | 8 ++++++++ auditlog_security/models/auditlog_rule.py | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) rename auditlog_security/migrations/{11.0.1.1.3 => 11.0.1.1.4}/pre-migration.py (91%) diff --git a/auditlog_security/__manifest__.py b/auditlog_security/__manifest__.py index af123988d0b..a05868ead2b 100644 --- a/auditlog_security/__manifest__.py +++ b/auditlog_security/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Audit Log User Permissions", - "version": "11.0.1.1.3", + "version": "11.0.1.1.4", "author": "Therp B.V.,Odoo Community Association (OCA)", "license": "AGPL-3", "website": "https://github.com/OCA/server-tools/", diff --git a/auditlog_security/migrations/11.0.1.1.3/pre-migration.py b/auditlog_security/migrations/11.0.1.1.4/pre-migration.py similarity index 91% rename from auditlog_security/migrations/11.0.1.1.3/pre-migration.py rename to auditlog_security/migrations/11.0.1.1.4/pre-migration.py index 0a48c5ef2d6..994bd71dd4d 100644 --- a/auditlog_security/migrations/11.0.1.1.3/pre-migration.py +++ b/auditlog_security/migrations/11.0.1.1.4/pre-migration.py @@ -16,6 +16,7 @@ def migrate(cr, version): ADD COLUMN IF NOT EXISTS method VARCHAR, ADD COLUMN IF NOT EXISTS user_id INTEGER, ADD COLUMN IF NOT EXISTS model_id INTEGER; + ADD COLUMN IF NOT EXISTS res_id INTEGER; """ ) @@ -42,6 +43,8 @@ def migrate(cr, version): auditlog_log_line_user_id_index ON auditlog_log_line (user_id); CREATE INDEX IF NOT EXISTS auditlog_log_line_model_id_index ON auditlog_log_line (model_id); + CREATE INDEX IF NOT EXISTS + auditlog_log_line_res_id_index ON auditlog_log_line (res_id); """ ) logger.info( @@ -50,7 +53,7 @@ def migrate(cr, version): cr.execute( """ UPDATE auditlog_log_line aline - SET method = al.method, user_id = al.user_id , model_id = al.model_id + SET method = al.method, user_id = al.user_id , model_id = al.model_id, res_id = al.res_id FROM auditlog_log al WHERE al.id = aline.log_id; """ diff --git a/auditlog_security/models/auditlog_log_line.py b/auditlog_security/models/auditlog_log_line.py index 92c7f5cd624..d33a2dd2013 100644 --- a/auditlog_security/models/auditlog_log_line.py +++ b/auditlog_security/models/auditlog_log_line.py @@ -21,6 +21,10 @@ class AuditlogLogLine(models.Model): compute='compute_model_id', store=True, index=True) + res_id = fields.Integer( + compute='compute_res_id', + store=True, + index=True) @api.depends('log_id.method') @@ -38,3 +42,7 @@ def compute_model_id(self): for this in self: this.model_id=this.log_id.model_id + @api.depends('log_id.res_id') + def compute_res_id(self): + for this in self: + this.res_id=this.log_id.res_id diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 6adaecd8d10..e74bc89084e 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -24,7 +24,7 @@ def onchange_model_id(self): def _get_view_log_lines_action(self, model_id): lines = self.env['auditlog.log.line'].search([ ('model_id', '=', model_id), - ('log_id.res_id', 'in', self.env.context.get('active_ids')) + ('res_id', 'in', self.env.context.get('active_ids')) ]) return { "name": _("View Log Lines"), From b55f32d59c8c6509c0a65525060921bc31ead278 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 27 Jul 2022 11:04:41 +0200 Subject: [PATCH 20/30] fixup! [FIX] second review --- auditlog_security/migrations/11.0.1.1.4/pre-migration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auditlog_security/migrations/11.0.1.1.4/pre-migration.py b/auditlog_security/migrations/11.0.1.1.4/pre-migration.py index 994bd71dd4d..3ac2b6da142 100644 --- a/auditlog_security/migrations/11.0.1.1.4/pre-migration.py +++ b/auditlog_security/migrations/11.0.1.1.4/pre-migration.py @@ -15,7 +15,7 @@ def migrate(cr, version): ALTER TABLE auditlog_log_line ADD COLUMN IF NOT EXISTS method VARCHAR, ADD COLUMN IF NOT EXISTS user_id INTEGER, - ADD COLUMN IF NOT EXISTS model_id INTEGER; + ADD COLUMN IF NOT EXISTS model_id INTEGER, ADD COLUMN IF NOT EXISTS res_id INTEGER; """ From 80426f00cc5ed9d95f63be073d82e39d032e998c Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 27 Jul 2022 11:26:54 +0200 Subject: [PATCH 21/30] fixup! [FIX] second review --- auditlog_security/models/auditlog_log_line.py | 1 - auditlog_security/models/auditlog_rule.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/auditlog_security/models/auditlog_log_line.py b/auditlog_security/models/auditlog_log_line.py index d33a2dd2013..e7698e2c2cf 100644 --- a/auditlog_security/models/auditlog_log_line.py +++ b/auditlog_security/models/auditlog_log_line.py @@ -26,7 +26,6 @@ class AuditlogLogLine(models.Model): store=True, index=True) - @api.depends('log_id.method') def compute_method(self): for this in self: diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index e74bc89084e..61d944440db 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -11,9 +11,7 @@ class AuditlogRule(models.Model): auditlog_line_access_rule_ids = fields.One2many( "auditlog.line.access.rule", "auditlog_rule_id", ondelete="cascade" ) - server_action_id = fields.Many2one('ir.actions.server', "Server Action", - ondelete="cascade" - ) + server_action_id = fields.Many2one('ir.actions.server', "Server Action",) @api.onchange("model_id") def onchange_model_id(self): From 4c04c1bb881bfa38b1a16fc5738064ec671fe7b6 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 27 Jul 2022 11:43:24 +0200 Subject: [PATCH 22/30] [ADD] standard log view now restricted, only loglines for normal users --- auditlog_security/models/auditlog_rule.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 61d944440db..15ae23af866 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -56,7 +56,13 @@ def subscribe(self): for rule in self: server_action = rule._create_server_action() server_action.create_action() - return super(AuditlogRule, self).subscribe() + res = super(AuditlogRule, self).subscribe() + # rule now will have "View Log" Action, make that visible only for admin + if res: + self.action_id.write({ + 'groups_id': [(6, 0, [self.env.ref('base.group_system').id])] + }) + return res @api.multi def unsubscribe(self): From 999aff163994538827bf32a0c26c3e7feb8c61b8 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Wed, 27 Jul 2022 14:20:59 +0200 Subject: [PATCH 23/30] fixup! fixup! [FIX] second review --- auditlog_security/migrations/11.0.1.1.4/pre-migration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auditlog_security/migrations/11.0.1.1.4/pre-migration.py b/auditlog_security/migrations/11.0.1.1.4/pre-migration.py index 3ac2b6da142..9083cb4b6fc 100644 --- a/auditlog_security/migrations/11.0.1.1.4/pre-migration.py +++ b/auditlog_security/migrations/11.0.1.1.4/pre-migration.py @@ -48,7 +48,7 @@ def migrate(cr, version): """ ) logger.info( - "Preemtive fill of auditlog_log_line columns: 'method', user_id" + "Preemtive fill of auditlog_log_line columns: 'method', user_id, res_id, model_id" ) cr.execute( """ From ff6b23f4b085e6cedb699926b4577f51c12eed95 Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Thu, 28 Jul 2022 14:30:11 +0200 Subject: [PATCH 24/30] [IMP] Add filtering and correct problems with display --- auditlog_security/models/auditlog_rule.py | 37 +++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 15ae23af866..5fc6773e174 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -1,7 +1,7 @@ # Copyright 2021 Therp B.V. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import exceptions, models, fields, api, modules, _ +from odoo import exceptions, models, fields, api, modules, _, tools from odoo.addons.auditlog.models.rule import FIELDS_BLACKLIST @@ -13,17 +13,35 @@ class AuditlogRule(models.Model): ) server_action_id = fields.Many2one('ir.actions.server', "Server Action",) + @api.multi + def write(self, values): + if "auditlog_line_access_rule_ids" in values.keys(): + #clear cache for all ormcache methods. + self.clear_caches() + return super(AuditlogRule, self).write(values) + @api.onchange("model_id") def onchange_model_id(self): # if model changes we must wipe out all field ids self.auditlog_line_access_rule_ids.unlink() - @api.model - def _get_view_log_lines_action(self, model_id): - lines = self.env['auditlog.log.line'].search([ - ('model_id', '=', model_id), - ('res_id', 'in', self.env.context.get('active_ids')) - ]) + @tools.ormcache('rule') + def _get_fields_of_rule(rule): + if rule.auditlog_line_access_rule_ids: + return rule.mapped( + 'auditlog_line_access_rule_ids.field_ids').ids + return [] + + @api.multi + def _get_view_log_lines_action(self): + domain = [ + ('model_id', '=', self.model_id.id), + ('res_id', 'in', self.env.context.get('active_ids')), + ] + field_ids = self._get_fields_of_rule() + if field_ids: + domain.append(('field_id', 'in', field_ids)) + lines = self.env['auditlog.log.line'].search(domain) return { "name": _("View Log Lines"), "res_model": "auditlog.log.line", @@ -38,8 +56,9 @@ def _get_view_log_lines_action(self, model_id): @api.multi def _create_server_action(self): self.ensure_one() - code = "action = env['auditlog.rule']._get_view_log_lines_action(%s)" % ( - self.model_id.id,) + code = \ + "rule = env['auditlog.rule'].browse(%s)\n" \ + "action = rule._get_view_log_lines_action()" % (self.id) server_action = self.env['ir.actions.server'].sudo().create({ 'name': "View Log Lines", 'model_id': self.model_id.id, From b09f5b8d8684e0fc953343dcdcc32746de1ee1cf Mon Sep 17 00:00:00 2001 From: Giovanni Francesco Capalbo Date: Thu, 28 Jul 2022 16:02:28 +0200 Subject: [PATCH 25/30] [IMP] no more access errors, regressions, and methods to reduce number of logged fields --- auditlog_security/models/auditlog_rule.py | 46 ++++++++++++++++++----- auditlog_security/views/auditlog_view.xml | 1 + 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 5fc6773e174..b669ea1475f 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -3,7 +3,7 @@ from odoo import exceptions, models, fields, api, modules, _, tools from odoo.addons.auditlog.models.rule import FIELDS_BLACKLIST - +from odoo.exceptions import ValidationError, UserError class AuditlogRule(models.Model): _inherit = "auditlog.rule" @@ -12,11 +12,33 @@ class AuditlogRule(models.Model): "auditlog.line.access.rule", "auditlog_rule_id", ondelete="cascade" ) server_action_id = fields.Many2one('ir.actions.server', "Server Action",) + log_selected_fields_only = fields.Boolean( + default=True, + help="Log only the selected fields, to save space avoid large DB data.") + + @api.constrains('model_id') + def unique_model(self): + if self.search_count([('model_id', '=', self.model_id.id)]) > 1: + raise ValidationError("A rule for this model already exists") + + @api.model + def get_auditlog_fields(self, model): + res = super(AuditlogRule, self).get_auditlog_fields(model) + unique_rule = self._get_rule_for_model(model) + if unique_rule.log_selected_fields_only: + traced_fields = unique_rule._get_fields_of_rule() + # we re-use the checks on non-stored fields from super. + return [x for x in traced_fields if x in res] + return res @api.multi def write(self, values): - if "auditlog_line_access_rule_ids" in values.keys(): - #clear cache for all ormcache methods. + cache_invalidating_fields = [ + "auditlog_line_access_rule_ids" , + "log_selected_fields_only", + ] + if any([field in values.keys() for field in cache_invalidating_fields]): + # clear cache for all ormcache methods. self.clear_caches() return super(AuditlogRule, self).write(values) @@ -25,6 +47,12 @@ def onchange_model_id(self): # if model changes we must wipe out all field ids self.auditlog_line_access_rule_ids.unlink() + @tools.ormcache('model') + def _get_rule_for_model(self, model): + unique_rule = self.env['auditlog.rule'].sudo().search( + [('model_id.model', '=', model._name)]) + return unique_rule.sudo() + @tools.ormcache('rule') def _get_fields_of_rule(rule): if rule.auditlog_line_access_rule_ids: @@ -32,13 +60,14 @@ def _get_fields_of_rule(rule): 'auditlog_line_access_rule_ids.field_ids').ids return [] - @api.multi - def _get_view_log_lines_action(self): + @api.model + def _get_view_log_lines_action(self, rule_id=None): + rule = self.sudo().browse(rule_id) domain = [ - ('model_id', '=', self.model_id.id), + ('model_id', '=', rule.model_id.id), ('res_id', 'in', self.env.context.get('active_ids')), ] - field_ids = self._get_fields_of_rule() + field_ids = rule._get_fields_of_rule() if field_ids: domain.append(('field_id', 'in', field_ids)) lines = self.env['auditlog.log.line'].search(domain) @@ -57,8 +86,7 @@ def _get_view_log_lines_action(self): def _create_server_action(self): self.ensure_one() code = \ - "rule = env['auditlog.rule'].browse(%s)\n" \ - "action = rule._get_view_log_lines_action()" % (self.id) + "action =env['auditlog.rule']._get_view_log_lines_action(rule_id=%s)" % (self.id) server_action = self.env['ir.actions.server'].sudo().create({ 'name': "View Log Lines", 'model_id': self.model_id.id, diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index 433d19d9d26..89d37590a1d 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -94,6 +94,7 @@ + From 5cac37fa5f499468602ccb3f1f9d52d96f987ea1 Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Fri, 19 Aug 2022 13:54:21 +0200 Subject: [PATCH 26/30] fixup! [IMP] no more access errors, regressions, and methods to reduce number of logged fields --- auditlog_security/models/auditlog_rule.py | 71 ++++++++++++----------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index b669ea1475f..c40a655f0d8 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -21,14 +21,32 @@ def unique_model(self): if self.search_count([('model_id', '=', self.model_id.id)]) > 1: raise ValidationError("A rule for this model already exists") + @api.model + @tools.ormcache('model_name') + def _get_field_names_of_rule(self, model_name): + """ Memory-cached list of fields per rule """ + rule = self.env['auditlog.rule'].sudo().search( + [('model_id.model', '=', model_name)], limit=1) + if rule.auditlog_line_access_rule_ids: + return rule.mapped( + 'auditlog_line_access_rule_ids.field_ids.name') + return [] + + @api.model + @tools.ormcache('model_name') + def _get_log_selected_fields_only(self, model_name): + """ Memory-cached translation of model to rule """ + rule = self.env['auditlog.rule'].sudo().search( + [('model_id.model', '=', model_name)], limit=1) + return rule.log_selected_fields_only + @api.model def get_auditlog_fields(self, model): res = super(AuditlogRule, self).get_auditlog_fields(model) - unique_rule = self._get_rule_for_model(model) - if unique_rule.log_selected_fields_only: - traced_fields = unique_rule._get_fields_of_rule() + if self._get_log_selected_fields_only(model._name): + selected_field_names = self._get_field_names_of_rule(model._name) # we re-use the checks on non-stored fields from super. - return [x for x in traced_fields if x in res] + res = [x for x in selected_field_names if x in res] return res @api.multi @@ -47,46 +65,31 @@ def onchange_model_id(self): # if model changes we must wipe out all field ids self.auditlog_line_access_rule_ids.unlink() - @tools.ormcache('model') - def _get_rule_for_model(self, model): - unique_rule = self.env['auditlog.rule'].sudo().search( - [('model_id.model', '=', model._name)]) - return unique_rule.sudo() - - @tools.ormcache('rule') - def _get_fields_of_rule(rule): - if rule.auditlog_line_access_rule_ids: - return rule.mapped( - 'auditlog_line_access_rule_ids.field_ids').ids - return [] - @api.model - def _get_view_log_lines_action(self, rule_id=None): - rule = self.sudo().browse(rule_id) + def _get_view_log_lines_action(self): + assert(self.env.context.get('active_model')) + assert(self.env.context.get('active_ids')) + model = self.env['ir.model'].sudo().search([ + ('model', '=', self.env.context.get('active_model')) + ]) domain = [ - ('model_id', '=', rule.model_id.id), + ('model_id', '=', model.id), ('res_id', 'in', self.env.context.get('active_ids')), ] - field_ids = rule._get_fields_of_rule() - if field_ids: - domain.append(('field_id', 'in', field_ids)) - lines = self.env['auditlog.log.line'].search(domain) return { - "name": _("View Log Lines"), - "res_model": "auditlog.log.line", - "view_mode": "tree,form", - #"src_model": self.model_id.model, - #"binding_model_id": self.model_id.id, - "view_id": False, - "domain": [('id', 'in', lines.ids)], - "type": "ir.actions.act_window", - } + "name": _("View Log Lines"), + "res_model": "auditlog.log.line", + "view_mode": "tree,form", + "view_id": False, + "domain": domain, + "type": "ir.actions.act_window", + } @api.multi def _create_server_action(self): self.ensure_one() code = \ - "action =env['auditlog.rule']._get_view_log_lines_action(rule_id=%s)" % (self.id) + "action = env['auditlog.rule']._get_view_log_lines_action()" server_action = self.env['ir.actions.server'].sudo().create({ 'name': "View Log Lines", 'model_id': self.model_id.id, From d9c99f2c96e15e9b98f2b2f6a9f555809c218f4e Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Fri, 19 Aug 2022 14:55:30 +0200 Subject: [PATCH 27/30] fixup! fixup! [IMP] no more access errors, regressions, and methods to reduce number of logged fields --- auditlog_security/models/auditlog_rule.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index c40a655f0d8..989751bf7e1 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -52,8 +52,9 @@ def get_auditlog_fields(self, model): @api.multi def write(self, values): cache_invalidating_fields = [ - "auditlog_line_access_rule_ids" , - "log_selected_fields_only", + "state", + "auditlog_line_access_rule_ids", + "log_selected_fields_only", ] if any([field in values.keys() for field in cache_invalidating_fields]): # clear cache for all ormcache methods. From f08ee94b466ef85ffc53528ff3144eebd65f2a3e Mon Sep 17 00:00:00 2001 From: Nightly Odoo user Date: Tue, 13 Sep 2022 09:32:57 +0000 Subject: [PATCH 28/30] fixup! fixup! fixup! [IMP] no more access errors, regressions, and methods to reduce number of logged fields --- auditlog_security/__manifest__.py | 1 + auditlog_security/models/__init__.py | 1 - .../models/auditlog_autovacuum.py | 14 -------- .../models/auditlog_line_access_rule.py | 34 +++++++++--------- auditlog_security/models/auditlog_rule.py | 4 +++ auditlog_security/models/ir_rule.py | 36 ------------------- auditlog_security/security/ir_rule.xml | 15 ++++++++ 7 files changed, 36 insertions(+), 69 deletions(-) delete mode 100644 auditlog_security/models/auditlog_autovacuum.py create mode 100644 auditlog_security/security/ir_rule.xml diff --git a/auditlog_security/__manifest__.py b/auditlog_security/__manifest__.py index a05868ead2b..22218b90eec 100644 --- a/auditlog_security/__manifest__.py +++ b/auditlog_security/__manifest__.py @@ -18,6 +18,7 @@ "security/res_groups.xml", "views/auditlog_view.xml", "security/ir.model.access.csv", + "security/ir_rule.xml", ], "application": True, "installable": True, diff --git a/auditlog_security/models/__init__.py b/auditlog_security/models/__init__.py index da32e810bcf..5b1ed36f36a 100644 --- a/auditlog_security/models/__init__.py +++ b/auditlog_security/models/__init__.py @@ -3,6 +3,5 @@ from . import auditlog_rule from . import auditlog_line_access_rule from . import ir_rule -from . import auditlog_autovacuum from . import auditlog_log_line diff --git a/auditlog_security/models/auditlog_autovacuum.py b/auditlog_security/models/auditlog_autovacuum.py deleted file mode 100644 index 7b4b0bc3a19..00000000000 --- a/auditlog_security/models/auditlog_autovacuum.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 Therp B.V. -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from odoo import models, api - - -class AuditlogAutovacuum(models.TransientModel): - _inherit = "auditlog.autovacuum" - - @api.model - def autovacuum(self, days): - return super( - AuditlogAutovacuum, self.with_context(auditlog_write=True) - ).autovacuum(days=days) diff --git a/auditlog_security/models/auditlog_line_access_rule.py b/auditlog_security/models/auditlog_line_access_rule.py index 7b70b924daa..573ac55b2b1 100644 --- a/auditlog_security/models/auditlog_line_access_rule.py +++ b/auditlog_security/models/auditlog_line_access_rule.py @@ -24,7 +24,6 @@ class AuditlogLineAccessRule(models.Model): ) state = fields.Selection(related="auditlog_rule_id.state", readonly=True) - def needs_rule(self): self.ensure_one() return bool(self.group_ids) @@ -38,7 +37,7 @@ def unlink(self): to_delete = self.get_linked_rules() res = super(AuditlogLineAccessRule, self).unlink() if res: - res = res and to_delete.with_context(auditlog_write=True).unlink() + res = res and to_delete.unlink() return res def add_default_group_if_needed(self): @@ -54,8 +53,7 @@ def add_default_group_if_needed(self): def create(self, vals): res = super(AuditlogLineAccessRule, self).create(vals) res.add_default_group_if_needed() - if res.needs_rule(): - res.generate_rules() + res.regenerate_rules() return res @api.multi @@ -72,25 +70,25 @@ def write(self, vals): ) or added ): - if this.needs_rule(): - this.generate_rules() - else: - this.get_linked_rules().with_context(auditlog_write=True).unlink() + this.regenerate_rules() return res - def generate_rules(self): - old_rule = self.env["ir.rule"].search( - [("auditlog_line_access_rule_id", "=", self.id)], limit=1 - ) - dict_values = self._prepare_rule_values() - for values in dict_values: - if old_rule: - old_rule.with_context(auditlog_write=True).write(values) - else: - self.with_context(auditlog_write=True).env["ir.rule"].create(values) + def remove_rules(self): + for this in self: + this.get_linked_rules().unlink() + + def regenerate_rules(self): + for this in self: + this.remove_rules() + dict_values = this._prepare_rule_values() + for values in dict_values: + self.env["ir.rule"].create(values) def _prepare_rule_values(self): + self.ensure_one() + if not self.needs_rule(): + return [] domain_force = "[" + " ('log_id.model_id' , '=', %s)," % ( self.model_id.id ) diff --git a/auditlog_security/models/auditlog_rule.py b/auditlog_security/models/auditlog_rule.py index 989751bf7e1..76ac9003648 100644 --- a/auditlog_security/models/auditlog_rule.py +++ b/auditlog_security/models/auditlog_rule.py @@ -108,6 +108,8 @@ def subscribe(self): server_action = rule._create_server_action() server_action.create_action() res = super(AuditlogRule, self).subscribe() + for rule in self: + rule.auditlog_line_access_rule_ids.regenerate_rules() # rule now will have "View Log" Action, make that visible only for admin if res: self.action_id.write({ @@ -117,6 +119,8 @@ def subscribe(self): @api.multi def unsubscribe(self): + for rule in self: + rule.auditlog_line_access_rule_ids.remove_rules() for rule in self: rule.server_action_id.unlink() return super(AuditlogRule, self).unsubscribe() diff --git a/auditlog_security/models/ir_rule.py b/auditlog_security/models/ir_rule.py index dfb1c822fae..665b58f0507 100644 --- a/auditlog_security/models/ir_rule.py +++ b/auditlog_security/models/ir_rule.py @@ -14,39 +14,3 @@ class IrRule(models.Model): ondelete='cascade', help="Auditlog line access Rule that generated this ir.rule", ) - - @api.model - def create(self, values): - if values.get("model_id") == self.env.ref( - "auditlog.model_auditlog_log_line" - ).id and not self.env.context.get("auditlog_write"): - raise exceptions.ValidationError( - _( - """ - Auditlog line rules are automatically generated from the - auditlog interface, please use that to create""" - ) - ) - return super(IrRule, self).create(values) - - @api.multi - def write(self, vals): - if "auditlog_id" in vals and not self.env.context.get("auditlog_write"): - raise exceptions.ValidationError( - _("""Cannot change auditlog_line_access_rule""") - ) - return super(IrRule, self).write(vals) - - @api.multi - def unlink(self): - auditlog_write = self.env.context.get("auditlog_write") - for this in self: - if this.auditlog_line_access_rule_id and not auditlog_write: - raise exceptions.ValidationError( - _( - """ - Auditlog line rules are automatically generated from the - auditlog interface, please use that to delete""" - ) - ) - return super(IrRule, self).unlink() diff --git a/auditlog_security/security/ir_rule.xml b/auditlog_security/security/ir_rule.xml new file mode 100644 index 00000000000..d783eb6f6d2 --- /dev/null +++ b/auditlog_security/security/ir_rule.xml @@ -0,0 +1,15 @@ + + + + + Nobody can read by default + + [(0, '=', 1)] + + + + + + + + From e1c4f7cb3392b6dec4db85dab0fe7320a1aba0d8 Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Wed, 14 Sep 2022 15:49:01 +0200 Subject: [PATCH 29/30] fixup! fixup! fixup! fixup! [IMP] no more access errors, regressions, and methods to reduce number of logged fields --- auditlog/views/auditlog_view.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/auditlog/views/auditlog_view.xml b/auditlog/views/auditlog_view.xml index 897d8ded358..f9d1e6a7020 100644 --- a/auditlog/views/auditlog_view.xml +++ b/auditlog/views/auditlog_view.xml @@ -110,7 +110,6 @@ attrs="{'invisible': [('model_id', '!=', False)]}" readonly="1" /> - /> Date: Wed, 28 Sep 2022 17:36:58 +0200 Subject: [PATCH 30/30] fixup! fixup! fixup! fixup! fixup! [IMP] no more access errors, regressions, and methods to reduce number of logged fields --- auditlog_security/views/auditlog_view.xml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/auditlog_security/views/auditlog_view.xml b/auditlog_security/views/auditlog_view.xml index 89d37590a1d..3050c1d7517 100644 --- a/auditlog_security/views/auditlog_view.xml +++ b/auditlog_security/views/auditlog_view.xml @@ -55,16 +55,14 @@ auditlog.log.line.tree auditlog.log.line - - - - - - - - - - + + + + + + + +