diff --git a/trp_external_user/README.rst b/trp_external_user/README.rst new file mode 100644 index 00000000..a8e04211 --- /dev/null +++ b/trp_external_user/README.rst @@ -0,0 +1,17 @@ +External users +============== + +Define a group 'External Users' that only have access to selected partners, +usually their own organizations. These partners are indicated on the user's user +form. By default, this module only grants read access to these partners. + +On a permission level, it seems necessary that the partners also need read access +to the database company partner, which this module also allows. Keep this in mind +when building functionality on top of this module that allows interaction with +the partner model. By means of precaution, this module does define separate +records for reading and writing so as to prevent modifications of the company +partner (even if global write access on partners are not granted to the external +users group in this module). + +This module also disables the default assignment of the 'user' and 'partner manager' +groups to new users. diff --git a/trp_external_user/__init__.py b/trp_external_user/__init__.py new file mode 100644 index 00000000..c32fd62b --- /dev/null +++ b/trp_external_user/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/trp_external_user/__manifest__.py b/trp_external_user/__manifest__.py new file mode 100644 index 00000000..635f49e4 --- /dev/null +++ b/trp_external_user/__manifest__.py @@ -0,0 +1,19 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "External users", + "summary": "Allow external users on your system", + "version": "13.0.1.0.0", + "author": "Therp BV, Sunflower IT", + "category": "External users", + "website": "https://therp.nl", + "depends": ["base", "portal"], + "demo": ["demo/res_users.xml"], + "data": [ + "data/ir_module_category.xml", + "security/res_groups.xml", + "security/ir.model.access.csv", + "security/ir_rule.xml", + "views/res_users.xml", + ], + "license": "AGPL-3", +} diff --git a/trp_external_user/data/ir_module_category.xml b/trp_external_user/data/ir_module_category.xml new file mode 100644 index 00000000..2bc6741d --- /dev/null +++ b/trp_external_user/data/ir_module_category.xml @@ -0,0 +1,8 @@ + + + + External users + External users + 60 + + diff --git a/trp_external_user/demo/res_users.xml b/trp_external_user/demo/res_users.xml new file mode 100644 index 00000000..8f49166f --- /dev/null +++ b/trp_external_user/demo/res_users.xml @@ -0,0 +1,9 @@ + + + + external + External user + + + + diff --git a/trp_external_user/models/__init__.py b/trp_external_user/models/__init__.py new file mode 100644 index 00000000..26984bbe --- /dev/null +++ b/trp_external_user/models/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import res_users diff --git a/trp_external_user/models/res_users.py b/trp_external_user/models/res_users.py new file mode 100644 index 00000000..0623aca3 --- /dev/null +++ b/trp_external_user/models/res_users.py @@ -0,0 +1,39 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + @api.model + def _default_groups(self): + """Disable default assignment of group 'users' and 'partner manager'""" + filter_groups = self.env.ref("base.group_user") + self.env.ref( + "base.group_partner_manager" + ) + return ( + super(ResUsers, self) + ._default_groups() + .filtered(lambda x: not x & filter_groups) + ) + + @api.depends("groups_id") + def _compute_is_external_user(self): + for this in self: + this.is_external_user = ( + self.env["ir.model.access"] + .with_user(this) + .check_groups("trp_external_user.group_external_user") + ) + + external_user_partner_ids = fields.Many2many( + "res.partner", + "trp_external_user_partner_id_rel", + "user_id", + "partner_id", + "External access to related partners", + ) + is_external_user = fields.Boolean( + "Is external user", compute="_compute_is_external_user" + ) + groups_id = fields.Many2many(default=lambda self: self._default_groups()) diff --git a/trp_external_user/security/ir.model.access.csv b/trp_external_user/security/ir.model.access.csv new file mode 100644 index 00000000..52a69fe8 --- /dev/null +++ b/trp_external_user/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" +"external_access_partner","Read access on partners for external users","base.model_res_partner","trp_external_user.group_external_user",1,0,0,0 +"external_access_mail_message","Read/create access on mail messages for external users","mail.model_mail_message","trp_external_user.group_external_user",1,0,1,0 diff --git a/trp_external_user/security/ir_rule.xml b/trp_external_user/security/ir_rule.xml new file mode 100644 index 00000000..d9654ff2 --- /dev/null +++ b/trp_external_user/security/ir_rule.xml @@ -0,0 +1,35 @@ + + + + Read access to own partners plus company partner + + + + + + ['|', '|', + ('id', '=', user.company_id.partner_id.id), + ('id', '=', user.partner_id.id), + ('id', 'in', user.external_user_partner_ids.ids), + ] + + + + Access to own partners + + + ['|', + ('id', '=', user.partner_id.id), + ('id', 'in', user.external_user_partner_ids.ids), + ] + + + + Access to own user + + + [ + ('id', '=', user.id), + ] + + diff --git a/trp_external_user/security/res_groups.xml b/trp_external_user/security/res_groups.xml new file mode 100644 index 00000000..13f6b6d3 --- /dev/null +++ b/trp_external_user/security/res_groups.xml @@ -0,0 +1,11 @@ + + + + External users + + + + diff --git a/trp_external_user/static/description/icon.png b/trp_external_user/static/description/icon.png new file mode 100644 index 00000000..4c7ab302 Binary files /dev/null and b/trp_external_user/static/description/icon.png differ diff --git a/trp_external_user/tests/__init__.py b/trp_external_user/tests/__init__.py new file mode 100644 index 00000000..b3ab0c98 --- /dev/null +++ b/trp_external_user/tests/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import test_trp_external_user diff --git a/trp_external_user/tests/test_trp_external_user.py b/trp_external_user/tests/test_trp_external_user.py new file mode 100644 index 00000000..03f0befc --- /dev/null +++ b/trp_external_user/tests/test_trp_external_user.py @@ -0,0 +1,33 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo.exceptions import AccessError +from odoo.tests.common import TransactionCase + + +class TestTrpExternalUser(TransactionCase): + def test_trp_external_user(self): + external_user = self.env.ref("trp_external_user.user_external") + # check some permissions + with self.assertRaises(AccessError): + self.env.ref("base.user_root").with_user(external_user).name + self.assertEqual( + self.env.ref("base.main_partner").name, + self.env.ref("base.main_partner").with_user(external_user).name, + ) + self.assertEqual( + self.env.ref("base.res_partner_2").name, + self.env.ref("base.res_partner_2").with_user(external_user).name, + ) + self.assertEqual( + external_user.name, external_user.with_user(external_user).name + ) + # check flag + self.assertTrue(external_user.is_external_user) + self.assertFalse(self.env.ref("base.user_root").is_external_user) + # check user creation + test_user = self.env["res.users"].create( + {"login": "some login", "name": "Some name"} + ) + self.assertNotIn(self.env.ref("base.group_user"), test_user.groups_id) + self.assertNotIn( + self.env.ref("base.group_partner_manager"), test_user.groups_id + ) diff --git a/trp_external_user/views/res_users.xml b/trp_external_user/views/res_users.xml new file mode 100644 index 00000000..831afe60 --- /dev/null +++ b/trp_external_user/views/res_users.xml @@ -0,0 +1,18 @@ + + + + + res.users + + + + + + + + + +