-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[12.0][add] New decorator @api.allowed_groups #2026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
e834c4c
e319f45
07b4599
ece8455
5d97cc1
3a201ec
5460029
c78369f
03fc87d
314ab14
6332db6
4228761
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ========================== | ||
| Decorator - Allowed Groups | ||
| ========================== | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from . import models |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Copyright (C) 2021-Today: GRAP (<http://www.grap.coop/>) | ||
| # @author: Sylvain LE GAL (https://twitter.com/legalsylvain) | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
|
||
| { | ||
| 'name': "Decorator - Allowed Groups", | ||
| 'summary': "Decorator that checks if user belong to given groups", | ||
| 'author': 'GRAP, Odoo Community Association (OCA)', | ||
| 'website': "https://github.com/OCA/server-tools/", | ||
| 'category': 'Technical', | ||
| 'version': '12.0.1.0.0', | ||
| 'license': 'AGPL-3', | ||
| 'depends': [ | ||
| 'base', | ||
| ], | ||
| 'installable': True | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Translation of Odoo Server. | ||
| # This file contains the translation of the following modules: | ||
| # * decorator_allowed_groups | ||
| # | ||
| msgid "" | ||
| msgstr "" | ||
| "Project-Id-Version: Odoo Server 12.0\n" | ||
| "Report-Msgid-Bugs-To: \n" | ||
| "POT-Creation-Date: 2021-02-19 07:52+0000\n" | ||
| "PO-Revision-Date: 2021-02-19 07:52+0000\n" | ||
| "Last-Translator: <>\n" | ||
| "Language-Team: \n" | ||
| "MIME-Version: 1.0\n" | ||
| "Content-Type: text/plain; charset=UTF-8\n" | ||
| "Content-Transfer-Encoding: \n" | ||
| "Plural-Forms: \n" | ||
|
|
||
| #. module: decorator_allowed_groups | ||
| #: code:addons/decorator_allowed_groups/models/decorator.py:45 | ||
| #, python-format | ||
| msgid "To execute the function {}, you should be member of one of the following groups:\n" | ||
| " {}" | ||
| msgstr "Pour executer cette fonction {}, vous devez être membre de l'un des groupes suivants:\n" | ||
| " {}" | ||
|
|
||
| #. module: decorator_allowed_groups | ||
| #: model:ir.model,name:decorator_allowed_groups.model_ir_ui_view | ||
| msgid "View" | ||
| msgstr "Vue" | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| from . import decorator | ||
| from . import ir_ui_view |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Copyright (C) 2021-Today: GRAP (<http://www.grap.coop/>) | ||
| # @author: Sylvain LE GAL (https://twitter.com/legalsylvain) | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
|
||
| from odoo import _, api | ||
| from odoo.exceptions import AccessError | ||
|
|
||
|
|
||
| def allowed_groups(*group_xml_ids): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The decorator is not a model. IMO it should be moved into |
||
| """ Return a decorator that specifies group(s) | ||
| to which the user must belong in order to perform | ||
| this function. | ||
| - if the user does not belong to any group, | ||
| an AccessError will be raised if the function is called. | ||
| - Also in the generated views, the according button | ||
| will be hidden if the user doesn't belong to the group(s). | ||
|
|
||
| @api.allowed_groups( | ||
legalsylvain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "purchase.group_purchase_manager", | ||
| "sale.group_sale_manager" | ||
| ) | ||
| def my_secure_action(self): | ||
| pass | ||
| """ | ||
|
|
||
| def decorator(method): | ||
| def secure_method(*args, **kwargs): | ||
| _self = args[0] | ||
| # Check if current user is member of correct groups | ||
| if any([ | ||
| _self.env.user.has_group(group_xml_id) | ||
| for group_xml_id in group_xml_ids | ||
| ]): | ||
legalsylvain marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| # If it's OK, return the original method | ||
| return method(*args, **kwargs) | ||
| else: | ||
| # If it's KO, raise an error. | ||
| # We raise a technical message (with function name | ||
| # and xml_ids of the groups, because this message | ||
| # will be raised only in XML-RPC call. | ||
| raise AccessError(_( | ||
| "To execute the function {}, you should be member" | ||
| " of one of the following groups:\n {}".format( | ||
| method, | ||
| ', '.join(group_xml_ids)) | ||
|
||
| )) | ||
| setattr(secure_method, "_allowed_groups", group_xml_ids) | ||
| return secure_method | ||
|
|
||
| return decorator | ||
|
|
||
|
|
||
| api.allowed_groups = allowed_groups | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Copyright (C) 2021-Today: GRAP (<http://www.grap.coop/>) | ||
| # @author: Sylvain LE GAL (https://twitter.com/legalsylvain) | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
|
||
| from odoo import api, models | ||
|
|
||
|
|
||
| class IrUiView(models.Model): | ||
| _inherit = "ir.ui.view" | ||
|
|
||
| @api.model | ||
| def postprocess(self, model, node, view_id, in_tree_view, model_fields): | ||
| if node.tag == "button" and node.get("name", False): | ||
| func = getattr(self.env[model], node.get("name"), False) | ||
| if func: | ||
| group_xml_ids = getattr(func, "_allowed_groups", False) | ||
| if group_xml_ids: | ||
| node.set("groups", ",".join(group_xml_ids)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe emit a warning if groups is already set and not equivalent?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about that point, and I'm not sure. Consider you have an Odoo / OCA module with a In a custom module, you want to change the group, so you simply write If you do so, it will raise a false warning, forcing developpers to overload the view in the custom module, to remove the group. I so just added in the
What do you think ?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My preference would be to emit the warning. It seems to me that the value of this module is mainly for methods introduced in modules that use this decorator in the first place, rather than overriding existing methods, and in that case, aligning the views is the proper thing to do to avoid confusion. But it's just my preference.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about combining the groups in the button and in the decorator, so you can only limit the authorities by either method, but never grant authorities?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say that is not the way I would expect and want such a mechanism to work. I guess it depends on whether you take a security perspective or a usability perspective.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is what I would expect. But I think I expressed myself poorly. Combining should be interpreted as: when groups are defined on the button and in the decorator, only the groups that are valid in both should be shown. When this leaves no valid groups, at least a warning should be shown, or maybe even an exception thrown.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You're right. 👍
do you mean :
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ah indeed, I misunderstood. This would improve the usability as well as the security. 👍 |
||
| return super().postprocess( | ||
| model, node, view_id, in_tree_view, model_fields) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| * Sylvain LE GAL (https://twitter.com/legalsylvain) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| This module is a technical module for developpers. | ||
|
|
||
| It adds a new decorator, named ``@api.allowed_groups``. | ||
|
|
||
| - When the function is executed, it checks if the user belong to one of the given groups. | ||
|
|
||
| - It also adds automatically group(s) in the according form views to hide | ||
| buttons if the user doesn't belong to one of the given groups. | ||
|
|
||
| Interests | ||
| --------- | ||
|
|
||
| It makes the application more secure and more concise, the developer only has to write the accreditation level in one place, instead of writing it twice (once in the XML view, and the other time in the python code) | ||
|
|
||
| In Odoo, there are many places where an action is hidden in the Form view, but the function can be called in particular by XML-RPC calls, that makes a lot a security issue, included | ||
| in recent version. | ||
|
|
||
| Ref : https://github.com/odoo/odoo/pull/66505 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| * Write tests. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| Once installed, the following code: | ||
|
|
||
| .. code-block:: xml | ||
|
|
||
| <button type="object" name="my_action" groups="purchase.group_purchase_manager"/> | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| def my_action(self): | ||
| if not self.env.user.has_group("purchase.group_purchase_manager"): | ||
| raise AccessError(_("Some Error")) | ||
| pass | ||
|
|
||
| can be replaced by: | ||
|
|
||
| .. code-block:: xml | ||
|
|
||
| <button type="object" name="my_action"/> | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| @api.allowed_groups("purchase.group_purchase_manager") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @legalsylvain IMO it's not a good practice to import the decorator from |
||
| def my_action(self): | ||
| pass | ||
|
|
||
|
|
||
| Note | ||
| ---- | ||
|
|
||
| - it is possible to list many groups. In that case, the action will be allowed | ||
| if the user belong to at least one group. | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| @api.allowed_groups("purchase.group_purchase_manager", "sale.group_sale_manager") | ||
| def my_action(self): | ||
| pass | ||
|
|
||
| - it is possible to change accreditation level in custom module. | ||
|
|
||
| For exemple, if a module define a function like this: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| @api.allowed_groups("purchase.group_purchase_manager") | ||
| def my_action(self): | ||
| pass | ||
|
|
||
| Another module that depends on the first module can redefine the accreditation | ||
| level by writing. | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| @api.allowed_groups("purchase.group_purchase_user") | ||
| def my_action(self): | ||
| return super().my_action() | ||
Uh oh!
There was an error while loading. Please reload this page.