Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions purchase_security/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Purchase Order security
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:9e1bbc1a8aa6093aa6f5cffb798552acf8886822317f4ed66d7b5da5684ea62d
!! source digest: sha256:e34914e47157d89425e93f9ef3639e32765ef386fc0c435d9d35a6ce6b7a56dd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
Expand All @@ -28,15 +28,15 @@ Purchase Order security

|badge1| |badge2| |badge3| |badge4| |badge5|

This addon creates new groups in Purchase.
This add-on creates two new groups: "User (own orders)" and "User (own or unrestricted orders)"
in Purchases.

Visibility of purchase orders is restricted for users in these groups.
You can only see the purchase order:
Users in the "User (own orders)" group can only see Purchase Orders where they are
the representative, or all of them if they are managers.

- User (own orders): If you are a follower of the partner or there is no user
or you are the partner's user.
- User (team orders): If you are a follower of the partner or there is no user
or you are a user of your purchasing team.
Users in the "User (own or unrestricted orders)" group can only see Purchase Orders where
they are the representative, as well as those where is_restricted is false. If they are managers,
they can see all orders.

**Table of contents**

Expand All @@ -53,10 +53,13 @@ To use this module, you need to:
(in the **Other Information** tab), if you are a Purchase User or Manager.
If you are a Purchass User (own orders), i'll be automatically assigned,
and you won't be able to change it
#. Enable the `is_restricted` option in the purchase order if you want
to prevent other users from viewing your PO.
#. Confirm the Purchase Order
#. Go back to the **Purchase Orders** view.
#. If you are a Purchase User or a Purchase Manager, you should be
able to see all orders. If not, you'll only see your own orders.
#. If you are a Purchase User or a Purchase Manager, you can see all orders. If you are a Purchase User (own orders),
you'll only see your own orders. If you are a Purchase User (own or unrestricted orders), you'll see
both your own orders and those that are unrestricted.

Bug Tracker
===========
Expand Down Expand Up @@ -88,6 +91,10 @@ Contributors
* `Solvos <https://www.solvos.es>`_:

* David Alonso <david.alonso@solvos.es>
* `Quartile <https://www.quartile.co>`__:

* Rinaldi Firdaus
* Aung Ko Ko Lin

Maintainers
~~~~~~~~~~~
Expand Down
19 changes: 18 additions & 1 deletion purchase_security/models/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# Copyright 2024 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError


class PurchaseOrder(models.Model):
Expand All @@ -22,6 +23,22 @@ class PurchaseOrder(models.Model):
store=True,
readonly=False,
)
is_restricted = fields.Boolean(
"Restrict Access",
tracking=True,
help="If selected, this purchase order can only be accessed by the assigned "
"buyer or purchase managers.",
)

@api.constrains("is_restricted")
def _check_is_restricted_access(self):
for order in self:
if order.user_id != self.env.user and not self.user_has_groups(
"purchase.group_purchase_manager"
):
raise ValidationError(
_("You do not have the right to change Restrict Access setting.")
)

def _compute_is_user_id_editable(self):
is_user_id_editable = self.env.user.has_group(
Expand Down
2 changes: 1 addition & 1 deletion purchase_security/readme/CONFIGURATION.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
To use this module you need to add the users whose access you want to restrict
to the "Purchases / User (own orders)" group
to the "Purchases / User (own orders)" group or "Purchases / User (own or unrestricted orders)"

You can also assign a Purchase Manager by adding him to the
"Purchases / Administrator" group
4 changes: 4 additions & 0 deletions purchase_security/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
* `Solvos <https://www.solvos.es>`_:

* David Alonso <david.alonso@solvos.es>
* `Quartile <https://www.quartile.co>`__:

* Rinaldi Firdaus
* Aung Ko Ko Lin
14 changes: 7 additions & 7 deletions purchase_security/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
This addon creates new groups in Purchase.
This add-on creates two new groups: "User (own orders)" and "User (own or unrestricted orders)"
in Purchases.

Visibility of purchase orders is restricted for users in these groups.
You can only see the purchase order:
Users in the "User (own orders)" group can only see Purchase Orders where they are
the representative, or all of them if they are managers.

- User (own orders): If you are a follower of the partner or there is no user
or you are the partner's user.
- User (team orders): If you are a follower of the partner or there is no user
or you are a user of your purchasing team.
Users in the "User (own or unrestricted orders)" group can only see Purchase Orders where
they are the representative, as well as those where is_restricted is false. If they are managers,
they can see all orders.
7 changes: 5 additions & 2 deletions purchase_security/readme/USAGE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ To use this module, you need to:
(in the **Other Information** tab), if you are a Purchase User or Manager.
If you are a Purchass User (own orders), i'll be automatically assigned,
and you won't be able to change it
#. Enable the `is_restricted` option in the purchase order if you want
to prevent other users from viewing your PO.
#. Confirm the Purchase Order
#. Go back to the **Purchase Orders** view.
#. If you are a Purchase User or a Purchase Manager, you should be
able to see all orders. If not, you'll only see your own orders.
#. If you are a Purchase User or a Purchase Manager, you can see all orders. If you are a Purchase User (own orders),
you'll only see your own orders. If you are a Purchase User (own or unrestricted orders), you'll see
both your own orders and those that are unrestricted.
19 changes: 17 additions & 2 deletions purchase_security/security/security.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,18 @@
eval="[(4, ref('purchase_security.group_purchase_own_orders'))]"
/>
</record>
<record id="group_purchase_restricted_orders" model="res.groups">
<field name="name">User (own or unrestricted orders)</field>
<field name="category_id" ref="base.module_category_inventory_purchase" />
<field
name="implied_ids"
eval="[(4, ref('purchase.group_purchase_user'))]"
/>
</record>
<record id="purchase.group_purchase_manager" model="res.groups">
<field
name="implied_ids"
eval="[(3, ref('purchase.group_purchase_user')),
(4, ref('group_purchase_own_orders')),(4, ref('group_purchase_group_orders'))]"
eval="[(3, ref('purchase.group_purchase_user')), (4, ref('group_purchase_own_orders')), (4, ref('group_purchase_restricted_orders'))]"
/>
</record>
</data>
Expand All @@ -31,6 +38,14 @@
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('purchase.group_purchase_manager'))]" />
</record>
<record model="ir.rule" id="purchase_order_restriction_users">
<field name="name">View purchase orders (own or unrestricted)</field>
<field name="model_id" ref="model_purchase_order" />
<field
name="domain_force"
>['|','|',('is_restricted','=',False),('user_id','=',user.id),('user_id','=',False)]</field>
<field name="groups" eval="[(4,ref('group_purchase_restricted_orders'))]" />
</record>
<record model="ir.rule" id="purchase_order_group_purchase_order_own_orders">
<field name="name">View purchase orders (own responsible)</field>
<field name="model_id" ref="purchase.model_purchase_order" />
Expand Down
41 changes: 22 additions & 19 deletions purchase_security/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@

/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.

See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
Expand Down Expand Up @@ -275,7 +274,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: gray; } /* line numbers */
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
Expand All @@ -301,7 +300,7 @@
span.pre {
white-space: pre }

span.problematic, pre.problematic {
span.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -367,18 +366,16 @@ <h1 class="title">Purchase Order security</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:9e1bbc1a8aa6093aa6f5cffb798552acf8886822317f4ed66d7b5da5684ea62d
!! source digest: sha256:e34914e47157d89425e93f9ef3639e32765ef386fc0c435d9d35a6ce6b7a56dd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/purchase-workflow/tree/16.0/purchase_security"><img alt="OCA/purchase-workflow" src="https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/purchase-workflow-16-0/purchase-workflow-16-0-purchase_security"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/purchase-workflow&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This addon creates new groups in Purchase.</p>
<p>Visibility of purchase orders is restricted for users in these groups.
You can only see the purchase order:</p>
<ul class="simple">
<li>User (own orders): If you are a follower of the partner or there is no user
or you are the partner’s user.</li>
<li>User (team orders): If you are a follower of the partner or there is no user
or you are a user of your purchasing team.</li>
</ul>
<p>This add-on creates two new groups: “User (own orders)” and “User (own or unrestricted orders)”
in Purchases.</p>
<p>Users in the “User (own orders)” group can only see Purchase Orders where they are
the representative, or all of them if they are managers.</p>
<p>Users in the “User (own or unrestricted orders)” group can only see Purchase Orders where
they are the representative, as well as those where is_restricted is false. If they are managers,
they can see all orders.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
Expand All @@ -401,10 +398,13 @@ <h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
(in the <strong>Other Information</strong> tab), if you are a Purchase User or Manager.
If you are a Purchass User (own orders), i’ll be automatically assigned,
and you won’t be able to change it</li>
<li>Enable the <cite>is_restricted</cite> option in the purchase order if you want
to prevent other users from viewing your PO.</li>
<li>Confirm the Purchase Order</li>
<li>Go back to the <strong>Purchase Orders</strong> view.</li>
<li>If you are a Purchase User or a Purchase Manager, you should be
able to see all orders. If not, you’ll only see your own orders.</li>
<li>If you are a Purchase User or a Purchase Manager, you can see all orders. If you are a Purchase User (own orders),
you’ll only see your own orders. If you are a Purchase User (own or unrestricted orders), you’ll see
both your own orders and those that are unrestricted.</li>
</ol>
</div>
<div class="section" id="bug-tracker">
Expand Down Expand Up @@ -437,14 +437,17 @@ <h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<li>David Alonso &lt;<a class="reference external" href="mailto:david.alonso&#64;solvos.es">david.alonso&#64;solvos.es</a>&gt;</li>
</ul>
</li>
<li><a class="reference external" href="https://www.quartile.co">Quartile</a>:<ul>
<li>Rinaldi Firdaus</li>
<li>Aung Ko Ko Lin</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
Expand Down
60 changes: 55 additions & 5 deletions purchase_security/tests/test_access_rights.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def setUpClass(cls):
cls.team1 = cls.env["purchase.team"].create({"name": "Team1"})
cls.team2 = cls.env["purchase.team"].create({"name": "Team2"})
# Users
users = cls.env["res.users"].with_context(no_reset_password=True)
purchase_restricted_group = "group_purchase_restricted_orders"
# User in group_purchase_own_orders
cls.user_group_purchase_own_orders = new_test_user(
cls.env,
Expand Down Expand Up @@ -55,6 +57,25 @@ def setUpClass(cls):
login="group_purchase_team_3_orders",
groups="purchase_security.group_purchase_group_orders",
)
# User in group_purchase_restricted_orders
cls.user_group_purchase_restricted_orders = users.create(
{
"name": "group_purchase_restricted_orders",
"login": "group_purchase_restricted_orders",
"email": "group_purchase_restricted_orders@example.com",
"groups_id": [
(
6,
0,
[
cls.env.ref(
"purchase_security.%s" % purchase_restricted_group
).id
],
)
],
}
)
# Purchase order user
cls.user_po_user = new_test_user(
cls.env, login="po_user", groups="purchase.group_purchase_user"
Expand Down Expand Up @@ -93,6 +114,17 @@ def setUpClass(cls):
"partner_id": cls.partner_po.id,
"team_id": cls.team2.id,
},
{
"name": "po_security_5",
"user_id": cls.user_group_purchase_own_orders.id,
"partner_id": cls.partner_po.id,
"is_restricted": True,
},
{
"name": "po_security_6",
"user_id": cls.user_group_purchase_restricted_orders.id,
"partner_id": cls.partner_po.id,
},
)
)

Expand Down Expand Up @@ -153,7 +185,7 @@ def test_access_user_user_group_purchase_own_orders(self):
.with_user(self.user_group_purchase_own_orders)
.search([])
),
2,
3,
)
self.assertFalse(
self.orders.filtered(
Expand All @@ -163,6 +195,24 @@ def test_access_user_user_group_purchase_own_orders(self):
.is_user_id_editable
)

def test_access_user_group_purchase_restricted_orders(self):
# User in group should have access to it's own PO
# and unrestricted orders
self.assertEqual(
len(
self.env["purchase.order"]
.with_user(self.user_group_purchase_restricted_orders)
.search([("name", "like", "po_security")])
.ids
),
5,
)
self.assertTrue(
self.orders.with_user(self.user_group_purchase_restricted_orders)[
0
].is_user_id_editable
)

def test_access_user_po_user(self):
# Normal PO user should have access to all of them
# because he is not in group
Expand All @@ -172,7 +222,7 @@ def test_access_user_po_user(self):
.with_user(self.user_po_user)
.search([("name", "like", "po_security")])
),
4,
6,
)
self.assertTrue(self.orders.with_user(self.user_po_user)[0].is_user_id_editable)

Expand All @@ -184,7 +234,7 @@ def test_access_user_po_manager(self):
.with_user(self.user_po_manager)
.search([("name", "like", "po_security")])
),
4,
6,
)
self.assertTrue(
self.orders.with_user(self.user_po_manager)[1].is_user_id_editable
Expand All @@ -207,7 +257,7 @@ def test_access_user_user_group_purchase_group_orders_1(self):
.with_user(self.user_group_team_1)
.search([("name", "like", "po_security")])
),
4,
6,
)

def test_access_user_user_group_purchase_group_orders_2(self):
Expand All @@ -220,7 +270,7 @@ def test_access_user_user_group_purchase_group_orders_2(self):
.with_user(self.user_group_team_2)
.search([("name", "like", "po_security")])
),
3,
5,
)

def test_access_user_user_group_purchase_group_orders_3(self):
Expand Down
Loading