Odoo security concept

Antonov Mike - Feb 5 - - Dev Community
/*Disclaimer

I haven't written tutorials for a few years,  and 
recently a fellow friend advised me to learn Odoo 
and look for a job in this field, so I decided to 
practice and rewrite Odoo  security concept in  a 
different style. I'm sure  I've lost the skill to 
do this  kind of  work and might have missed some 
details.  In any case,  reasoned  criticism in an 
acceptable form is welcome.*/
Enter fullscreen mode Exit fullscreen mode

Sources:

General Info

The security concept in Odoo is based on the mechanism of user groups and access rights. Access rights define which users can view, create, modify or delete records in the database. User groups unite users with common access rights. A user can belong to multiple groups, and each group can have its own access rights.


Access Rights

These are access rights that define what operations (create, read, update, delete) a user can perform on data models. Access Rights are applied at the model level, i.e. to all records in the model. Access Rights are defined in the file ir.model.access.access.csv, which must be located in the security folder of the module. The file ir.model.access.csv contains the following fields:

  • id – unique identifier of access right
  • name – access right description
  • model_id – model to which the right of access applies
  • group_id – user group to which the right of access is granted
  • perm_read – read permission (1 or 0)
  • perm_write – update permission (1 or 0)
  • perm_create – create permission (1 or 0)
  • perm_unlink – delete permission (1 or 0) For example, to give the "Library / User" user group the right to read and create books, but not to update and delete them, you can add the following line to the file ir.model.access.csv:
id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_library_book_user library.book.user model_library_book library.group_library_user 1 0 1 0

When No Access Rights Are Specified

If we run the program without defined access rights for a module, we will see a warning in the terminal, for example, the following:

2024-02-02 11:48:53,572 2533261 WARNING rd-mydb odoo.modules.loading: 
The models ['estate_property'] have no access rules in module estate, 
consider adding some, like:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,0,0,0
Enter fullscreen mode Exit fullscreen mode

To add access rights for a module, you must create a file named ir.model.access.csv in the security folder of the current module, for example estate/security/ir.model.access.csv. In this file, you can define different access rights for different user groups. For example, you can define that the user group "managers" has the right to read, write and delete records, while the user group "users" has only the right to read.


Access rights without groups

Creating access rights

You can define access rights in the file security/ir.model.access.csv using the following format:

id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_estate_property_user access_estate_property_user model_estate_property 1 0 0 0
access_estate_property_manager access_estate_property_manager model_estate_property 1 1 1 1

If there is an entry in the group_id field, for example group_estate_manager or estate.group_estate_manager, then the rules for it are defined in the security/security.xml file. But for now we'll leave the group_id field empty, so that the rule will apply to all users.
In this example we define two types of access: one for regular users (access_estate_property_user), which is read-only (perm_read=1), and one for managers (access_estate_property_manager), which has all permissions.
Once this file is created, you need to make sure it is included in the manifest of the current module. Add the file path to the 'data' section of the manifest.
File estate/__manifest__.py:

{
    ...
    'data': [
        'security/ir.model.access.csv'
    ],
    ...
}
Enter fullscreen mode Exit fullscreen mode

Installing or updating a module

Odoo will automatically apply these access rights to the appropriate data models. For example, you can use the command to update -u estate:

./odoo-bin --addons-path="addons, custom" -d rd-mydb -u estate
Enter fullscreen mode Exit fullscreen mode

If you have correctly created access rights for your module, the warning will no longer appear in the terminal.


Csv fields, identifiers and references

Fields of the csv file

The "id" and "name" fields in the ir.model.access.csv file are required and they must be unique. They are used to identify the access rule.

"id"

This is a unique identifier for the access rule. You can choose any value, but it must be unique in the context of the entire ir.model.access.csv file. In our case, access_estate_property_user and access_estate_property_manager are unique identifiers for two different access rules.

"name"

This is the name for the access rule. It is usually the same as "id", but can be different if you want to use a name that describes the rule in more detail. In our case, access_estate_property_user and access_estate_property_manager are also names for two different access rules.

"model_id:id"

Refers to the data model to which the access rule applies. In our case, model_estate_property is a reference to the data model that was defined in the "TestModel" class using the _name attribute.
File estate/models/estate_test.py:

class TestModel(models.Model):
    _name = "estate_property"
Enter fullscreen mode Exit fullscreen mode

"group_id:id"

Refers to the user group to which the access rule applies. If this field is left empty, the rule applies to all users. This may be convenient for testing, but in real situations it is necessary to restrict access to the model for certain groups of users. For this purpose you can use existing groups from the base module, for example, base.group_user or base.group_system, or you can create your own groups in the file security.xml.


Identifiers

In Odoo, model_id:id and group_id:id are used to refer to external model and group identifiers. The : is used to separate the name of the external identifier (model_id or group_id) from the identifier itself.
The usual way of naming a model in Odoo is model_<model_name>, where <model_name> is the _name of the model, where . is replaced by _. For example, sale.order becomes model_sale_order.
Similarly, group_id:id refers to the group that will apply the access right. The value of id here is the group's external identifier, which was created automatically when the group was created. This approach allows you to reference a model or group without knowing their internal identifiers in the database.


Reference to a group

The reference can be either to a group within the "estate" module (group_estate_manager), or to the same group but from another module (estate.group_estate_manager). The format group_estate_manager without the module prefix is usually used within a module where it refers to a group defined in the current module. In the latter case, estate is the name of the module and group_estate_manager is the group identifier within that module.


Access rights for groups

Creating a "group_id"

Instead of using existing groups from the base module, such as base.group_user or base.group_system, you can create your own groups:

  • In estate/security/security.xml create two user groups using the <record> tag and specify their IDs, names, categories, and parent groups (if any):
<odoo>
  <record id="group_estate_manager" model="res.groups">
    <field name="name">Estate Manager</field>
    <field name="category_id" ref="base.module_category_hidden"/>
    <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
  </record>

  <record id="group_estate_user" model="res.groups">
    <field name="name">Estate User</field>
    <field name="category_id" ref="base.module_category_hidden"/>
    <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
  </record>
</odoo>
Enter fullscreen mode Exit fullscreen mode

The parent groups for each of the created groups group_estate_manager and group_estate_user are specified here. This is done using the implied_ids field. For each group, it is specified that they include the base.group_user group, which is the default user group in Odoo. So any user who is in the group_estate_manager or group_estate_user group will automatically also be in the base.group_user group.

  • In the file estate/security/ir.model.access.csv set external identifiers for user groups estate.group_estate_manager и estate.group_estate_user:
id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_estate_property_user access_estate_property_user model_estate_property group_estate_user 1 0 0 0
access_estate_property_manager access_estate_property_manager model_estate_property group_estate_manager 1 1 1 1
  • In the estate/manifest.py file, add the paths to the files estate/security/security.xml and estate/security/ir.model.access.csv to the 'data' list so that they will be loaded when the module is installed:
'data': [
    'security/security.xml',
    'security/ir.model.access.csv',
],
Enter fullscreen mode Exit fullscreen mode

Then restart Odoo with a command like
./odoo-bin --addons-path="addons, custom" -d rd-mydb -u estate
The module is now running with updated access rights.

Example of demo module

File custom/estate/models/estate_test.py

from odoo import fields, models

class TestModel(models.Model):
    _name = "estate_property"
    _description = "Estate test module"

    name = fields.Char(required=True)
    description = fields.Text()
    postcode = fields.Char()
    date_availability = fields.Date()
    expected_price = fields.Float(
        required=True, 
        help="Some helpful information")
    selling_price = fields.Float()
    bedrooms = fields.Integer()
    living_area = fields.Integer()
    facades = fields.Integer()
    garage = fields.Boolean()
    garden = fields.Boolean()
    garden_area = fields.Integer()
    garden_orientation = fields.Selection(
        string="Type",
        required=False,
        selection=[('south', 'South'),
                   ('north', 'North'),
                   ('east', 'East'),
                   ('west', 'West')],
    )
Enter fullscreen mode Exit fullscreen mode

File custom/estate/__manifest__.py

{
    'name': "estate",
    'version': '1.0',
    'license': 'LGPL-3',
    'depends': ['base'],
    'author': "Author Name",
    'category': 'Estate',
    'description': """
    Description Text
    """,
    # data files always loaded at installation
    'data': [
        'security/security.xml',
        'security/ir.model.access.csv',
        'views/estate_view.xml',
        'views/estate_menu.xml',
    ],
    # data files containing optionally loaded demonstration data
    'demo': [],
}
Enter fullscreen mode Exit fullscreen mode

File custom/estate/__init__.py

from . import models
Enter fullscreen mode Exit fullscreen mode

File custom/estate/models/__init__.py

from . import estate_test
Enter fullscreen mode Exit fullscreen mode

File security/security.xml

<odoo>
  <record id="group_estate_manager" model="res.groups">
    <field name="name">Estate Manager</field>
    <field name="category_id" ref="base.module_category_hidden"/>
    <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
  </record>

  <record id="group_estate_user" model="res.groups">
    <field name="name">Estate User</field>
    <field name="category_id" ref="base.module_category_hidden"/>
    <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
  </record>
</odoo>
Enter fullscreen mode Exit fullscreen mode

File security/ir.model.access.csv

id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_estate_property_user access_estate_property_user model_estate_property group_estate_user 1 0 0 0
access_estate_property_manager access_estate_property_manager model_estate_property group_estate_manager 1 1 1 1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .