Maintainers
+Maintainers
This module is maintained by the OCA.
@@ -465,6 +471,5 @@ diff --git a/impersonate_login/README.rst b/impersonate_login/README.rst index 658ce207ec..f37c3b433b 100644 --- a/impersonate_login/README.rst +++ b/impersonate_login/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ================= Impersonate Login ================= @@ -17,7 +13,7 @@ Impersonate Login .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github @@ -64,12 +60,23 @@ If you want to prevent impersonation of users with the *Administration: Settings* rights, enable the *Restrict Impersonation of "Administration: Settings" Users* option in the settings. +Each user also has an *Impersonation* setting, on the *Access Rights* +tab of the user form (administrators only): + +- *Default*: follow the general setting above; +- *Allowed*: the user can always be impersonated, even with the + *Administration: Settings* rights; +- *Forbidden*: the user can never be impersonated. + Usage ===== 1. In the menu that is displayed when clicking on the user avatar on the top right corner, or in the res.users list, click "Switch Login" to - impersonate another user. + impersonate another user. Users that cannot be impersonated (set to + *Forbidden*, or set to *Default* with the *Administration: Settings* + rights when the general setting restricts them) are not listed, and + the button is not shown on their row. 2. On the top-right corner, the button "Back to Original User" is displayed in case the current user is being impersonated. diff --git a/impersonate_login/models/res_config_settings.py b/impersonate_login/models/res_config_settings.py index 04fbf9571f..9c76378dd4 100644 --- a/impersonate_login/models/res_config_settings.py +++ b/impersonate_login/models/res_config_settings.py @@ -13,3 +13,9 @@ class ResConfigSettings(models.TransientModel): ), default=False, ) + + def set_values(self): + res = super().set_values() + # Whether a user can be impersonated depends on this setting. + self.env["res.users"].invalidate_model(["impersonate_allowed"]) + return res diff --git a/impersonate_login/models/res_users.py b/impersonate_login/models/res_users.py index 0703234d87..7b1f548293 100644 --- a/impersonate_login/models/res_users.py +++ b/impersonate_login/models/res_users.py @@ -15,6 +15,93 @@ class Users(models.Model): _inherit = "res.users" + impersonate_mode = fields.Selection( + selection=[ + ("default", "Default"), + ("allowed", "Allowed"), + ("forbidden", "Forbidden"), + ], + string="Impersonation", + default="default", + required=True, + groups="base.group_system", + help="Default: follow the general setting (when 'Restrict Impersonation of " + "Administration: Settings Users' is enabled, users with that access right " + "cannot be impersonated).\n" + "Allowed: this user can always be impersonated, even with that access right.\n" + "Forbidden: this user can never be impersonated.", + ) + + impersonate_allowed = fields.Boolean( + compute="_compute_impersonate_allowed", + search="_search_impersonate_allowed", + string="Can Be Impersonated", + help="Whether the current user may switch to this user: never oneself, " + "a user set to Forbidden, nor (when the general setting restricts " + "them) a user with the 'Administration: Settings' access right.", + ) + + @api.depends("impersonate_mode", "groups_id") + @api.depends_context("uid") + def _compute_impersonate_allowed(self): + for user in self: + user.impersonate_allowed = ( + user.id != self.env.uid and not user._get_impersonation_refusal() + ) + + def _search_impersonate_allowed(self, operator, value): + """Same rule as _get_impersonation_refusal(), as a SQL domain.""" + if operator not in ("=", "!=") or not isinstance(value, bool): + raise UserError(self.env._("Operation not supported")) + domain = [ + ("id", "!=", self.env.uid), + ("impersonate_mode", "!=", "forbidden"), + ] + config_restrict = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("impersonate_login.restrict_impersonate_admin_settings") + ) + if config_restrict: + domain += [ + "|", + ("impersonate_mode", "=", "allowed"), + ("groups_id", "not in", self.env.ref("base.group_system").ids), + ] + # impersonate_mode is readable by administrators only: the subquery is + # built as superuser, its result only says who may be switched to. + query = self.sudo().with_context(active_test=False)._search(domain) + allowed = (operator == "=") == value + return [("id", "in" if allowed else "not in", query)] + + def _get_impersonation_refusal(self): + """Why nobody may impersonate this user (a message), or False.""" + self.ensure_one() + mode = self.sudo().impersonate_mode + if mode == "forbidden": + return self.env._("This user cannot be impersonated.") + if mode == "allowed": + return False + config_restrict = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("impersonate_login.restrict_impersonate_admin_settings") + ) + if config_restrict and self.env.ref("base.group_system") in ( + self.sudo().groups_id + ): + return self.env._( + "You cannot impersonate users with" + " 'Administration: Settings' access rights." + ) + return False + + def _check_can_be_impersonated(self): + """Raise if nobody may impersonate this user.""" + refusal = self._get_impersonation_refusal() + if refusal: + raise UserError(refusal) + def _get_partner_name(self, user_id): return self.env["res.users"].browse(user_id).partner_id.name @@ -24,20 +111,8 @@ def _is_impersonate_user(self): def impersonate_login(self): if request: - config_restrict = ( - self.env["ir.config_parameter"] - .sudo() - .get_param("impersonate_login.restrict_impersonate_admin_settings") - ) - if config_restrict: - admin_settings_group = self.env.ref("base.group_system") - if admin_settings_group in self.groups_id: - raise UserError( - self.env._( - "You cannot impersonate users with" - " 'Administration: Settings' access rights." - ) - ) + if self.id != request.session.impersonate_from_uid: + self._check_can_be_impersonated() if request.session.impersonate_from_uid: if self.id == request.session.impersonate_from_uid: return self.back_to_origin_login() @@ -93,9 +168,12 @@ def action_impersonate_login(self): "base.action_res_users" ) action["views"] = [[self.env.ref("base.view_users_tree").id, "list"]] + # Only the users that can be switched to: the others would only + # show a button that ends in an error. action["domain"] = [ ("id", "!=", self.env.user.id), ("share", "=", False), + ("impersonate_allowed", "=", True), ] action["target"] = "new" return action diff --git a/impersonate_login/readme/CONFIGURE.md b/impersonate_login/readme/CONFIGURE.md index 649fb31c76..2d9adfa302 100644 --- a/impersonate_login/readme/CONFIGURE.md +++ b/impersonate_login/readme/CONFIGURE.md @@ -3,3 +3,11 @@ The impersonating user must belong to group "Impersonate Users". If you want to prevent impersonation of users with the *Administration: Settings* rights, enable the *Restrict Impersonation of "Administration: Settings" Users* option in the settings. + +Each user also has an *Impersonation* setting, on the *Access Rights* tab of the +user form (administrators only): + +- *Default*: follow the general setting above; +- *Allowed*: the user can always be impersonated, even with the + *Administration: Settings* rights; +- *Forbidden*: the user can never be impersonated. diff --git a/impersonate_login/readme/USAGE.md b/impersonate_login/readme/USAGE.md index 348d570009..437a2cdd23 100644 --- a/impersonate_login/readme/USAGE.md +++ b/impersonate_login/readme/USAGE.md @@ -1,4 +1,7 @@ 1. In the menu that is displayed when clicking on the user avatar on the top right corner, or in the res.users list, click "Switch Login" to impersonate another user. + Users that cannot be impersonated (set to *Forbidden*, or set to *Default* with + the *Administration: Settings* rights when the general setting restricts them) + are not listed, and the button is not shown on their row. 2. On the top-right corner, the button "Back to Original User" is displayed in case the current user is being impersonated. diff --git a/impersonate_login/static/description/index.html b/impersonate_login/static/description/index.html index 8a91815f16..998059c539 100644 --- a/impersonate_login/static/description/index.html +++ b/impersonate_login/static/description/index.html @@ -3,7 +3,7 @@
-This module allows one user (for example, a member of the support team) to log in as another user. The impersonation session can be exited by clicking on the button “Back to Original User”.
@@ -407,24 +402,35 @@The impersonating user must belong to group “Impersonate Users”.
If you want to prevent impersonation of users with the Administration: Settings rights, enable the Restrict Impersonation of “Administration: Settings” Users option in the settings.
+Each user also has an Impersonation setting, on the Access Rights +tab of the user form (administrators only):
+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 to smash it by providing a detailed and welcomed @@ -432,15 +438,15 @@
Do not contact contributors directly about support or help with technical issues.