documentation/content/developer/tutorials/restrict_data_access.rst
Antoine Vandevenne (anv) 7f623b6ad5 [IMP] developer: improve navigation in top-level pages
Prior to this commit, users had to either know in advance or guess the
location of the content they were looking for. Top-level pages of the
"Developer" section of the documentation, in particular the "Developer"
page itself, were listing their sub-pages without directions for users.

This commit brings the following changes to improve the navigation:
- add directions for users on the "Developer" page and list the three
  main categories of developer documentation ("Tutorials", "How-to
  guides", and "Reference") with explanations of their content and
  target audience;
- add categories for content cards on the "Tutorials" and "How-to
  guides" pages, and fine-tune the toctree of the "Reference" page to
  more easily locate specific topics;
- clarify what are the "Python framework" and the "JavaScript framework"
  by relabelling them to "Server framework" and "Web framework" on
  top-level pages, as some users were confused to find that the JS
  framework was not responsible for the server, and others that the
  documentation for QWeb template is located in the Python documentation;
- extract the "Setup guide" from the "Getting started" tutorial and
  rename the latter to "Server framework 101" to allow reusing the setup
  guide in other tutorials and make clear that the "Server framework 101"
  tutorial is not about the Web framework.

task-3802536

closes odoo/documentation#8597

Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
Co-authored-by: Valeriya (vchu) <vchu@odoo.com>
2024-04-12 09:53:17 +00:00

518 lines
21 KiB
ReStructuredText

=======================
Restrict access to data
=======================
.. important::
This tutorial is an extension of the :doc:`server_framework_101` tutorial. Make sure you have
completed it and use the `estate` module you have built as a base for the exercises in this
tutorial.
So far we have mostly concerned ourselves with implementing useful features.
However in most business scenarios *security* quickly becomes a concern:
currently,
* Any employee (which is what ``group_user`` stands for) can create, read,
update or delete properties, property types, or property tags.
* If ``estate_account`` is installed then only agents allowed to interact
with invoicing can confirm sales as that's necessary to :ref:`create an
invoice <tutorials/server_framework_101/13_other_module/create>`.
However:
* We do not want third parties to be able to access properties directly.
* Not all our employees may be real-estate agents (e.g. administrative
personnel, property managers, ...), we don't want non-agents to see the
available properties.
* Real-estate agents don't need or get to decide what property types or tags are
*available*.
* Real-estate agents can have *exclusive* properties, we do not want one agent
to be able to manage another's exclusives.
* All real-estate agents should be able to confirm the sale of a property they
can manage, but we do not want them to be able to validate or mark as paid
any invoice in the system.
.. note::
We may actually be fine with some or most of these for a small business.
Because it's easier for users to disable unnecessary security rules than it
is to create them from nothing, it's better to err on the side of caution
and limit access: users can relax that access if necessary or convenient.
Groups
======
.. seealso::
The documentation related to this topic can be found in :ref:`the security
reference <reference/security>`.
:doc:`/contributing/development/coding_guidelines` document the format and
location of master data items.
.. admonition:: **Goal**
At the end of this section,
- We can make employees *real-estate agents* or *real-estate managers*.
- The ``admin`` user is a real-estate manager.
- We have a new *real-estate agent* employee with no access to invoicing
or administration.
It would not be practical to attach individual security rules to employees any
time we need a change so *groups* link security rules and users. They correspond
to roles that can be assigned to employees.
For most Odoo applications [#app]_ a good baseline is to have *user* and
*manager* (or administrator) roles: the manager can change the configuration of
the application and oversee the entirety of its use while the user can well,
use the application [#appuser]_.
This baseline seems sufficient for us:
* Real estate managers can configure the system (manage available types and
tags) as well as oversee every property in the pipeline.
* Real estate agents can manage the properties under their care, or properties
which are not specifically under the care of any agent.
In keeping with Odoo's data-driven nature, a group is no more than a record of
the ``res.groups`` model. They are normally part of a module's :doc:`master data
<define_module_data>`, defined in one of the module's data files.
As simple example `can be found here <https://github.com/odoo/odoo/blob/532c083cbbe0ee6e7a940e2bdc9c677bd56b62fa/addons/hr/security/hr_security.xml#L9-L14>`_.
.. exercise::
#. Create the ``security.xml`` file in the appropriate folder and add it to the ``__manifest__.py`` file.
#. If not already, add a ``'category'`` field to your ``__manifest__.py`` with value ``Real Estate/Brokerage``.
#. Add a record creating a group with the id ``estate_group_user``, the name "Agent"
and the category ``base.module_category_real_estate_brokerage``.
#. Below that, add a record creating a group with the id ``estate_group_manager``,
the name "Manager" and the category ``base.module_category_real_estate_brokerage``.
The ``estate_group_manager`` group needs to imply ``estate_group_user``.
.. note::
Where does that **category** comes from ? It's a *module category*.
Here we used the category id ``base.module_category_real_estate_brokerage``
which was automatically generated by Odoo based on the `category` set in the ``__manifest__.py`` of the module.
You can also find here the list of
`default module categories <https://github.com/odoo/odoo/blob/71da80deb044852a2af6b111d695f94aad7803ac/odoo/addons/base/data/ir_module_category_data.xml>`_
provided by Odoo.
.. tip::
Since we modified data files, remember to restart Odoo and update the
module using ``-u estate``.
If you go to :menuselection:`Settings --> Manage Users` and open the
``admin`` user ("Mitchell Admin"), you should see a new section:
.. figure:: restrict_data_access/groups.png
Set the admin user to be a *Real Estate manager*.
.. exercise::
Via the web interface, create a new user with only the "real estate agent"
access. The user should not have any Invoicing or Administration access.
Use a private tab or window to log in with the new user (remember to set
a password), as the real-estate agent you should only see the real estate
application, and possibly the Discuss (chat) application:
.. figure:: restrict_data_access/agent.png
Access Rights
=============
.. seealso:: The documentation related to this topic can be found at
:ref:`reference/security/acl`.
.. admonition:: **Goal**
At the end of this section,
- Employees who are not at least real-estate agents will not see the
real-estate application.
- Real-estate agents will not be able to update the property types or tags.
Access rights were first introduced in :doc:`server_framework_101/04_securityintro`.
Access rights are a way to give users access to models *via* groups: associate
an access right to a group, then all users with that group will have the access.
For instance we don't want real-estate agents to be able to modify what property
types are available, so we would not link that access to the "user" group.
Access rights can only give access, they can't remove it: when access is
checked, the system looks to see if *any* access right associated with the user
(via any group) grants that access.
====== ====== ==== ====== ======
group create read update delete
------ ------ ---- ------ ------
A X X
B X
C X
====== ====== ==== ====== ======
A user with the groups A and C will be able to do anything but delete the object
while one with B and C will be able to read and update it, but not create or delete it.
.. note::
* The group of an access right can be omitted, this means the ACL applies
to *every user*, this is a useful but risky fallback as depending on the
applications installed it can grant even non-users access to the model.
* If no access right applies to a user, they are not granted access
(default-deny).
* If a menu item points to a model to which a user doesn't have access and
has no submenus which the user can see, the menu will not be displayed.
.. exercise:: Update the access rights file to:
* Give full access to all objects to your Real Estate Manager group.
* Give agents (real estate users) only read access to types and tags.
* Give nobody the right to delete properties.
* Check that your agent user is not able to alter types or tags, or to
delete properties, but that they can otherwise create or update
properties.
.. warning::
Remember to give different xids to your ``ir.model.access`` records
otherwise they will overwrite one another.
Since the "demo" user was not made a real-estate agent or manager, they should
not even be able to see the real-estate application. Use a private tab or window
to check for this (the "demo" user has the password "demo").
Record Rules
============
.. seealso:: The documentation related to this topic can be found at
:ref:`reference/security/rules`.
.. admonition:: **Goal**
At the end of this section, agents will not be able to see the properties
exclusive to their colleagues; but managers will still be able to see
everything.
Access rights can grant access to an entire model but often we need to be
more specific: while an agent can interact with properties in general we may not
want them to update or even see properties managed by one of their colleagues.
Record *rules* provide that precision: they can grant or reject access to
individual records:
.. code-block:: xml
<record id="rule_id" model="ir.rule">
<field name="name">A description of the rule's role</field>
<field name="model_id" ref="model_to_manage"/>
<field name="perm_read" eval="False"/>
<field name="groups" eval="[Command.link(ref('base.group_user'))]"/>
<field name="domain_force">[
'|', ('user_id', '=', user.id),
('user_id', '=', False)
]</field>
</record>
The :ref:`reference/orm/domains` is how access is managed: if the record passes
then access is granted, otherwise access is rejected.
.. tip::
Because rules tends to be rather complex and not created in bulk, they're
usually created in XML rather than the CSV used for access rights.
The rule above:
* Only applies to the "create", "update" (write) and "delete" (unlink)
operations: here we want every employee to be able to see other users' records
but only the author / assignee can update a record.
* Is :ref:`non-global <reference/security/rules/global>` so we can provide an
additional rule for e.g. managers.
* Allows the operation if the current user (``user.id``) is set (e.g. created,
or is assigned) on the record, or if the record has no associated user at all.
.. note::
If no rule is defined or applies to a model and operation, then the
operation is allowed (*default-allow*), this can have odd effects
if access rights are not set up correctly (are too permissive).
.. exercise::
Define a rule which limits agents to only being able to see or modify
properties which have no salesperson, or for which they are the salesperson.
You may want to create a second real-estate agent user, or create a few
properties for which the salesperson is a manager or some other user.
Verify that your real estate manager(s) can still see all properties. If
not, why not? Remember:
The ``estate_group_manager`` group needs to imply ``estate_group_user``.
Security Override
=================
Bypassing Security
------------------
.. admonition:: **Goal**
At the end of this section, agents should be able to confirm property sales
without needing invoicing access.
If you try to mark a property as "sold" as the real estate agent, you should get
an access error:
.. figure:: restrict_data_access/error.png
This happens because ``estate_account`` tries to create an invoice during the
process, but creating an invoice requires the right to all invoice management.
We want agents to be able to confirm a sale without them having full invoicing
access, which means we need to *bypass* the normal security checks of Odoo in
order to create an invoice *despite* the current user not having the right to
do so.
There are two main ways to bypass existing security checks in Odoo, either
wilfully or as a side-effect:
* The ``sudo()`` method will create a new recordset in "sudo mode", this ignores
all access rights and record rules (although hard-coded group and user checks
may still apply).
* Performing raw SQL queries will bypass access rights and record rules as a
side-effect of bypassing the ORM itself.
.. exercise::
Update ``estate_account`` to bypass access rights and rules when creating
the invoice.
.. danger::
These features should generally be avoided, and only used with extreme care,
after having checked that the current user and operation should be able to
bypass normal access rights validation.
Operations performed in such modes should also rely on user input as little
as possible, and should validate it to the maximum extent they can.
Programmatically checking security
----------------------------------
.. admonition:: **Goal**
At the end of this section, the creation of the invoice should be resilient
to security issues regardless to changes to ``estate``.
In Odoo, access rights and record rules are only checked *when performing data
access via the ORM* e.g. creating, reading, searching, writing, or unlinking a
record via ORM methods. Other methods do *not* necessarily check against any
sort of access rights.
In the previous section, we bypassed the record rules when creating the invoice
in ``action_sold``. This bypass can be reached by any user without any access
right being checked:
- Add a print to ``action_sold`` in ``estate_account`` before the creation of
the invoice (as creating the invoice accesses the property, therefore triggers
an ACL check) e.g.::
print(" reached ".center(100, '='))
You should see ``reached`` in your Odoo log, followed by an access error.
.. danger:: Just because you're already in Python code does not mean any access
right or rule has or will be checked.
*Currently* the accesses are implicitly checked by accessing data on ``self`` as
well as calling ``super()`` (which does the same and *updates* ``self``),
triggering access errors and cancelling the transaction "uncreating" our
invoice.
*However* if this changes in the future, or we add side-effects to the method
(e.g. reporting the sale to a government agency), or bugs are introduced in
``estate``, ... it would be possible for non-agents to trigger operations they
should not have access to.
Therefore when performing non-CRUD operations, or legitimately bypassing the
ORM or security, or when triggering other side-effects, it is extremely
important to perform *explicit security checks*.
Explicit security checks can be performed by:
* Checking who the current user is (``self.env.user``) and match them against
specific models or records.
* Checking that the current user has specific groups hard-coded to allow or deny
an operation (``self.env.user.has_group``).
* Calling the ``check_access_rights(operation)`` method on a recordset, this
verifies whether the current user has access to the model itself.
* Calling ``check_access_rule(operations)`` on a non-empty recordset, this
verifies that the current user is allowed to perform the operation on *every*
record of the set.
.. warning:: Checking access rights and checking record rules are separate
operations, if you're checking record rules you usually want to
also check access rights beforehand.
.. exercise::
Before creating the invoice, use ``check_access_rights`` and
``check_access_rule`` to ensure that the current user can update properties
in general as well as the specific property the invoice is for.
Re-run the bypass script, check that the error occurs before the print.
.. _tutorials/restrict_data_access/multicompany:
Multi-company security
======================
.. seealso::
:ref:`reference/howtos/company` for an overview of multi-company facilities
in general, and :ref:`multi-company security rules <howto/company/security>`
in particular.
Documentation on rules in general can, again, be found at
:ref:`reference/security/rules`.
.. admonition:: **Goal**
At the end of this section, agents should only have access to properties
of their agency (or agencies).
For one reason or another we might need to manage our real-estate business
as multiple companies e.g. we might have largely autonomous agencies, a
franchise setup, or multiple brands (possibly from having acquired other
real-estate businesses) which remain legally or financially separate from one
another.
Odoo can be used to manage multiple companies inside the same system, however
the actual handling is up to individual modules: Odoo itself provides the tools
to manage the issue of company-dependent fields and *multi-company rules*,
which is what we're going to concern ourselves with.
We want different agencies to be "siloed" from one another, with properties
belonging to a given agency and users (whether agents or managers) only able to
see properties linked to their agency.
As before, because this is based on non-trivial records it's easier for a user
to relax rules than to tighten them so it makes sense to default to a
relatively stronger security model.
Multi-company rules are simply record rules based on the ``company_ids`` or
``company_id`` fields:
* ``company_ids`` is all the companies to which the current user has access
* ``company_id`` is the currently active company (the one the user is currently
working in / for).
Multi-company rules will *usually* use the former i.e. check if the record is
associated with *one* of the companies the user has access to:
.. code-block:: xml
<record model="ir.rule" id="hr_appraisal_plan_comp_rule">
<field name="name">Appraisal Plan multi-company</field>
<field name="model_id" ref="model_hr_appraisal_plan"/>
<field name="domain_force">[
'|', ('company_id', '=', False),
('company_id', 'in', company_ids)
]</field>
</record>
.. danger::
Multi-company rules are usually :ref:`global <reference/security/rules/global>`,
otherwise there is a high risk that additional rules would allow bypassing
the multi-company rules.
.. exercise::
* Add a ``company_id`` field to ``estate.property``, it should be required
(we don't want agency-less properties), and should default to the current
user's current company.
* Create a new company, with a new estate agent in that company.
* The manager should be a member of both companies.
* The old agent should only be a member of the old company.
* Create a few properties in each company (either use the company selector
as the manager or use the agents). Unset the default salesman to avoid
triggering *that* rule.
* All agents can see all companies, which is not desirable, add the record
rule restricting this behaviour.
.. warning:: remember to ``--update`` your module when you change its model or
data
Visibility != security
======================
.. admonition:: **Goal**
At the end of this section, real-estate agents should not see the Settings
menu of the real-estate application, but should still be able to set the
property type or tags.
Specific Odoo models can be associated directly with groups (or companies, or
users). It is important to figure out whether this association is a *security*
or a *visibility* feature before using it:
* *Visibility* features mean a user can still access the model or record
otherwise, either through another part of the interface or by :doc:`performing
operations remotely using RPC <../reference/external_api>`, things might just not be
visible in the web interface in some contexts.
* *Security* features mean a user can not access records, fields or operations.
Here are some examples:
* Groups on *model fields* (in Python) are a security feature, users outside the
group will not be able to retrieve the field, or even know it exists.
Example: in server actions, `only system users can see or update Python code
<https://github.com/odoo/odoo/blob/7058e338a980268df1c502b8b2860bdd8be9f727/odoo/addons/base/models/ir_actions.py#L414-L417>`_.
* Groups on *view elements* (in XML) are a visibility feature, users outside the
group will not be able to see the element or its content in the form but they
will otherwise be able to interact with the object (including that field).
Example: `only managers have an immediate filter to see their teams' leaves
<https://github.com/odoo/odoo/blob/8e19904bcaff8300803a7b596c02ec45fcf36ae6/addons/hr_holidays/report/hr_leave_reports.xml#L16>`_.
* Groups on menus and actions are visibility features, the menu or action will
not be shown in the interface but that doesn't prevent directly interacting
with the underlying object.
Example: `only system administrators can see the elearning settings menu
<https://github.com/odoo/odoo/blob/ff828a3e0c5386dc54e6a46fd71de9272ef3b691/addons/website_slides/views/website_slides_menu_views.xml#L64-L69>`_.
.. exercise::
Real Estate agents can not add property types or tags, but can see their
options from the Property form view when creating it.
The Settings menu just adds noise to their interface, make it only
visible to managers.
Despite not having access to the Property Types and Property Tags menus anymore,
agents can still access the underlying objects since they can still select
tags or a type to set on their properties.
.. [#app] An Odoo Application is a group of related modules covering a business
area or field, usually composed of a base module and a number of
expansions on that base to add optional or specific features, or link
to other business areas.
.. [#appuser] For applications which would be used by most or every employees,
the "application user" role might be done away with and its
abilities granted to all employees directly e.g. generally all
employees can submit expenses or take time off.