From bd4868a1de140ec16c95f43926565e591d28972a Mon Sep 17 00:00:00 2001 From: Ted Salmon Date: Mon, 22 May 2017 13:00:18 -0700 Subject: [PATCH] [10.0] [ADD] user_immutable: Add Module (#830) * [ADD] user_immutable: Add Module * Add module to support users that are immutable * [IMP] user_immutable: Fixes per PR * Add missing test * Fix README * Add logging * [IMP] user_immutable: Fixes per PR * Lower code complexity * Fix README * Fix License * [IMP] user_immutable: Fixes per PR Review * Call `_check_immutable` on singleton * Simplify check for immutable group --- user_immutable/README.rst | 68 +++++++++++++++++++++ user_immutable/__init__.py | 5 ++ user_immutable/__manifest__.py | 20 ++++++ user_immutable/data/user_immutable_data.xml | 13 ++++ user_immutable/demo/user_immutable_demo.xml | 11 ++++ user_immutable/models/__init__.py | 6 ++ user_immutable/models/res_groups.py | 28 +++++++++ user_immutable/models/res_users.py | 48 +++++++++++++++ user_immutable/tests/__init__.py | 6 ++ user_immutable/tests/test_res_groups.py | 28 +++++++++ user_immutable/tests/test_res_users.py | 66 ++++++++++++++++++++ 11 files changed, 299 insertions(+) create mode 100644 user_immutable/README.rst create mode 100644 user_immutable/__init__.py create mode 100644 user_immutable/__manifest__.py create mode 100644 user_immutable/data/user_immutable_data.xml create mode 100644 user_immutable/demo/user_immutable_demo.xml create mode 100644 user_immutable/models/__init__.py create mode 100644 user_immutable/models/res_groups.py create mode 100644 user_immutable/models/res_users.py create mode 100644 user_immutable/tests/__init__.py create mode 100644 user_immutable/tests/test_res_groups.py create mode 100644 user_immutable/tests/test_res_users.py diff --git a/user_immutable/README.rst b/user_immutable/README.rst new file mode 100644 index 000000000..31f6da060 --- /dev/null +++ b/user_immutable/README.rst @@ -0,0 +1,68 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +============== +User Immutable +============== + +This module adds a group named `Immutable` which cannot be altered by users +outside of that group. By default, the `Administrator` user is the only user +given access to this group on install. This module also adds protections +against non-members granting/revoking membership to this group. + + + +Installation +============ + +* Install module as normal + +Usage +===== + +Simply add any user to the `Immutable` group, provided that your login user +is a member of that group already. + + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/149/10.0 + +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 smash it by providing detailed and welcomed feedback. + + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Ted Salmon + + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/user_immutable/__init__.py b/user_immutable/__init__.py new file mode 100644 index 000000000..fd02263ce --- /dev/null +++ b/user_immutable/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import models diff --git a/user_immutable/__manifest__.py b/user_immutable/__manifest__.py new file mode 100644 index 000000000..ee0780849 --- /dev/null +++ b/user_immutable/__manifest__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +{ + "name": "Immutable Users", + "summary": "Add Immutable User Support", + "version": "10.0.1.0.0", + "category": "Authentication", + "website": "https://www.laslabs.com", + "author": "LasLabs, Odoo Community Association (OCA)", + "license": "LGPL-3", + "application": False, + 'installable': True, + "data": [ + 'data/user_immutable_data.xml', + ], + "demo": [ + 'demo/user_immutable_demo.xml', + ] +} diff --git a/user_immutable/data/user_immutable_data.xml b/user_immutable/data/user_immutable_data.xml new file mode 100644 index 000000000..e04138533 --- /dev/null +++ b/user_immutable/data/user_immutable_data.xml @@ -0,0 +1,13 @@ + + + + + + + Immutable + + + + + diff --git a/user_immutable/demo/user_immutable_demo.xml b/user_immutable/demo/user_immutable_demo.xml new file mode 100644 index 000000000..a5ae30389 --- /dev/null +++ b/user_immutable/demo/user_immutable_demo.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/user_immutable/models/__init__.py b/user_immutable/models/__init__.py new file mode 100644 index 000000000..de54f9c4b --- /dev/null +++ b/user_immutable/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import res_groups +from . import res_users diff --git a/user_immutable/models/res_groups.py b/user_immutable/models/res_groups.py new file mode 100644 index 000000000..608ffa3b0 --- /dev/null +++ b/user_immutable/models/res_groups.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import _, api, models +from odoo.exceptions import AccessError + +IMMUTABLE = 'user_immutable.group_immutable' + + +class ResGroups(models.Model): + + _inherit = 'res.groups' + + @api.multi + def write(self, vals): + """ Override write to verify that access to the `Immutable` group is + not given or removed by users without access + """ + if not vals.get('users') or self.env.user.has_group(IMMUTABLE): + return super(ResGroups, self).write(vals) + immutable = self.env.ref(IMMUTABLE, raise_if_not_found=False) + if immutable and immutable in self: + raise AccessError(_( + 'You must be a member of the `Immutable` group to grant' + ' access to it' + )) + return super(ResGroups, self).write(vals) diff --git a/user_immutable/models/res_users.py b/user_immutable/models/res_users.py new file mode 100644 index 000000000..dd0944bfd --- /dev/null +++ b/user_immutable/models/res_users.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import _, api, models +from odoo.exceptions import AccessError + +from .res_groups import IMMUTABLE + + +class ResUsers(models.Model): + + _inherit = 'res.users' + + def _check_immutable(self): + """ Check to see if the user being edited is Immutable and if so, + make sure that the user performing the action has access + """ + if self.has_group(IMMUTABLE): + if not self.env.user.has_group(IMMUTABLE): + raise AccessError( + _('You do not have permission to alter an Immutable User') + ) + + @api.multi + def write(self, vals): + """ Override write to verify that there are no alterations to users + whom are members of the `Immutable` group + """ + for rec in self: + rec._check_immutable() + immutable = self.env.ref(IMMUTABLE) + has_group = self.env.user.has_group(IMMUTABLE) + if vals.get('in_group_%s' % immutable.id) and not has_group: + raise AccessError( + _('You must be a member of the `Immutable` group to grant ' + 'access to it') + ) + return super(ResUsers, self).write(vals) + + @api.multi + def unlink(self): + """ Override unlink to verify that there are no deletions of users + whom are members of the `Immutable` group + """ + for rec in self: + rec._check_immutable() + return super(ResUsers, self).unlink() diff --git a/user_immutable/tests/__init__.py b/user_immutable/tests/__init__.py new file mode 100644 index 000000000..caba981be --- /dev/null +++ b/user_immutable/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import test_res_groups +from . import test_res_users diff --git a/user_immutable/tests/test_res_groups.py b/user_immutable/tests/test_res_groups.py new file mode 100644 index 000000000..a4841e3a4 --- /dev/null +++ b/user_immutable/tests/test_res_groups.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html + +from odoo.exceptions import AccessError +from odoo.tests import TransactionCase + + +class TestResGroups(TransactionCase): + + def setUp(self): + super(TestResGroups, self).setUp() + self.immutable = self.env.ref('user_immutable.group_immutable') + self.user = self.env.ref('base.user_demo') + self.user.write({'in_group_%s' % self.immutable.id: False}) + + def test_can_add_immutable(self): + """ It should make sure that `Administrator` can add users to the + immutable group by default """ + self.immutable.write({'users': [(4, [self.user.id])]}) + self.assertTrue(self.user.has_group('user_immutable.group_immutable')) + + def test_non_immutable_cannot_add_immutable(self): + """ It should make sure that other users cannot add to the immutable + group """ + immutable = self.env.ref('user_immutable.group_immutable') + with self.assertRaises(AccessError): + immutable.sudo(self.user.id).write({'users': [self.user.id]}) diff --git a/user_immutable/tests/test_res_users.py b/user_immutable/tests/test_res_users.py new file mode 100644 index 000000000..1f105887a --- /dev/null +++ b/user_immutable/tests/test_res_users.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html + +from odoo.exceptions import AccessError +from odoo.tests import TransactionCase + + +class TestResUsers(TransactionCase): + + def setUp(self): + super(TestResUsers, self).setUp() + self.immutable = self.env.ref('user_immutable.group_immutable') + self.user = self.env.ref('base.user_demo') + self.user.write({'in_group_%s' % self.immutable.id: False}) + + def test_can_add_immutable(self): + """ It should verify that `Administrator` can add users to the + immutable group by default + """ + self.user.write({'in_group_%s' % self.immutable.id: True}) + self.assertTrue(self.user.has_group('user_immutable.group_immutable')) + + def test_non_immutable_cannot_add_immutable(self): + """ It should verify that other users cannot add to the immutable + group + """ + with self.assertRaises(AccessError): + self.user.sudo(self.user.id).write({ + 'in_group_%s' % self.immutable.id: True + }) + + def test_immutable_can_alter_immutable(self): + """ It should verify that immutable users can alter users in the + immutable group + """ + self.user.write({'in_group_%s' % self.immutable.id: True}) + exp = 'Princess Peach' + self.user.write({'name': exp}) + self.assertEquals(self.user.name, exp) + + def test_immutable_cannot_be_unlinked(self): + """ It should make sure non `Immutable` members cannot unlink other + `Immutable` Members + """ + with self.assertRaises(AccessError): + self.env.ref('base.user_root').sudo( + self.user.id + ).unlink() + + def test_immutable_can_be_unlinked_by_immutable(self): + """ It should make sure `Immutable` members can unlink other + `Immutable` Members + """ + user = self.user.copy() + user.write({'in_group_%s' % self.immutable.id: True}) + self.assertTrue(user.unlink()) + + def test_check_immutable(self): + """ It should raise `AccessError` when trying called by a user + outside the `Immutable` group on an `Immutable` user + """ + with self.assertRaises(AccessError): + self.env.ref('base.user_root').sudo( + self.user.id + )._check_immutable()