Odoo security concept
Tags: python, beginners, odoo, security
Published: 2024-02-05 05:27:21

```Rust
/*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.*/
```

Sources:
- [Chapter 5 Security intro](https://www.odoo.com/documentation/16.0/developer/tutorials/getting_started/05_securityintro.html)
- [Security in Odoo](https://www.odoo.com/documentation/16.0/developer/reference/backend/security.html
)
<br/><br/>

## General Info
<br/><br/>

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.
<br/><br/>

### Access Rights
<br/><br/>

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           |
<br/><br/>

### When No Access Rights Are Specified
<br/><br/>

If we run the program without defined access rights for a module, we will see a warning in the terminal, for example, the following:
```bash
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
```

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.
<br/><br/>

### Access rights without groups
<br/><br/>

## Creating access rights
<br/><br/>

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`:
```Python
{
    ...
    'data': [
        'security/ir.model.access.csv'
    ],
    ...
}
```
<br/><br/>

### Installing or updating a module
<br/><br/>

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

```bash
./odoo-bin --addons-path="addons, custom" -d rd-mydb -u estate
```

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

## Csv fields, identifiers and references
<br/><br/>

### Fields of the csv file
<br/><br/>

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`:
```Python
class TestModel(models.Model):
    _name = "estate_property"
```

#### "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`.
<br/><br/>

### Identifiers
<br/><br/>

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.
<br/><br/>

### Reference to a group
<br/><br/>

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.
<br/><br/>

### Access rights for groups
<br/><br/>

### Creating a "group_id"
<br/><br/>

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):

```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>
```

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:
```Python
'data': [
    'security/security.xml',
    'security/ir.model.access.csv',
],
```
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.


<br/><br/>

### Example of demo module
<br/><br/>

File `custom/estate/models/estate_test.py`
```Python
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')],
    )
```
File `custom/estate/__manifest__.py`
```Python
{
    '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': [],
}
```
File `custom/estate/__init__.py`
```Python
from . import models
```
File `custom/estate/models/__init__.py`
```Python
from . import estate_test
```
File `security/security.xml`
```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>
```
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           |