From 4b8a78e3c3f11f2e57bb5eeb152a4d451c707701 Mon Sep 17 00:00:00 2001 From: Dmytro Meita Date: Mon, 23 Dec 2024 15:44:11 +0200 Subject: [PATCH 1/8] [REF][IMP] Access Rules for Variables and Values with Tests [REF] - Refactored existing code to align with PEP8 and Odoo guidelines. - Improved indentation and formatting in affected files for better readability. [IMP] - Added access control by inheriting `cx.tower.access.mixin` in `TowerVariable` and `TowerVariableValue` models. - Introduced the `access_level` field in the models and views, allowing dynamic control of variable visibility. - Updated search and form views to include the `access_level` field for better usability. [NEW] - Created a new security file `cx_tower_variable_security.xml` to define access rules for `TowerVariable`. - Updated `cx_tower_variable_value_security.xml` to include refined domain rules for `TowerVariableValue`. [TEST] - Updated test `test_variable_value_access` to validate refined access logic. - Added a new test `test_variable_value_server_subscription` to ensure server subscription-based variable visibility. Steps to Reproduce 1. Create a new variable and assign it to a server with specific `access_level`. 2. Test visibility of the variable based on user roles (`group_user`, `group_manager`, `group_root`). 3. Validate global and local variable toggling and their visibility to respective roles. 4. Subscribe a user to the server and ensure variable visibility updates dynamically. 5. Run the tests (`test_variable_value_access`, `test_variable_value_server_subscription`) to confirm correct behavior. File Changes 1. `cx_tower_variable_security.xml` - Created a new security file to manage access rules for `TowerVariable`. 2. `cx_tower_variable_value_security.xml` - Updated domain rules to dynamically control visibility based on `access_level` and server subscriptions. 3. Models (`TowerVariable`, `TowerVariableValue`) - Inherited `cx.tower.access.mixin`. - Added `access_level` fields and their logic. 4. Views (`TowerVariable`, `TowerVariableValue`) - Updated forms and search views to include `access_level` fields. 5. Tests - Refined `test_variable_value_access` to ensure compatibility with new logic. - Added `test_variable_value_server_subscription` to validate subscription-based access. 6. General - Minor corrections in formatting, indentation, and adherence to Odoo development guidelines. Task: 4055 --- cetmix_tower_server/README.rst | 831 +++++++++--------- cetmix_tower_server/__manifest__.py | 2 + .../models/cx_tower_access_mixin.py | 16 +- .../models/cx_tower_variable.py | 2 +- .../models/cx_tower_variable_value.py | 1 + .../security/cx_tower_variable_security.xml | 26 + .../cx_tower_variable_value_security.xml | 45 +- .../static/description/index.html | 169 ++-- cetmix_tower_server/tests/test_variable.py | 114 ++- .../views/cx_tower_variable_value_view.xml | 5 + .../views/cx_tower_variable_view.xml | 4 +- 11 files changed, 657 insertions(+), 558 deletions(-) create mode 100644 cetmix_tower_server/security/cx_tower_variable_security.xml diff --git a/cetmix_tower_server/README.rst b/cetmix_tower_server/README.rst index 3e2d9925..ca5ade08 100644 --- a/cetmix_tower_server/README.rst +++ b/cetmix_tower_server/README.rst @@ -17,7 +17,7 @@ Cetmix Tower Server Management :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github - :target: https://github.com/cetmix/cetmix-tower/tree/14.0-dev/cetmix_tower_server + :target: https://github.com/cetmix/cetmix-tower/tree/14.0/cetmix_tower_server :alt: cetmix/cetmix-tower |badge1| |badge2| |badge3| @@ -32,75 +32,74 @@ tied down by vendor or technology constraints. Why Cetmix Tower? ================= -- **Open Source:** `Cetmix Tower `__ is - distributed under the AGPL-3 license -- **Odoo Integration:** Benefit from `Odoo `__ - ecosystem for server management tasks, like deploying servers in - response to specific Odoo-triggered events -- **Extendability:** Build your own `Odoo `__ modules - using `Cetmix Tower `__ to implement your - custom features -- **Beyond Odoo:** While optimized for Odoo, Cetmix Tower can manage - virtually any instance -- **Flexibility:** Use Cetmix Tower alongside other management methods - without restriction, ensuring you're not limited to a single vendor -- **Self-Hosting:** Deploy Cetmix Tower on your own infrastructure for - full control over your server management. -- **Broad Compatibility:** Execute any software that's manageable via - shell commands or API. From Docker or Kubernetes to direct OS package - installations +- **Open Source:** `Cetmix Tower `__ is + distributed under the AGPL-3 license +- **Odoo Integration:** Benefit from `Odoo `__ + ecosystem for server management tasks, like deploying servers in + response to specific Odoo-triggered events +- **Extendability:** Build your own `Odoo `__ modules + using `Cetmix Tower `__ to implement your + custom features +- **Beyond Odoo:** While optimized for Odoo, Cetmix Tower can manage + virtually any instance +- **Flexibility:** Use Cetmix Tower alongside other management methods + without restriction, ensuring you're not limited to a single vendor +- **Self-Hosting:** Deploy Cetmix Tower on your own infrastructure for + full control over your server management. +- **Broad Compatibility:** Execute any software that's manageable via + shell commands or API. From Docker or Kubernetes to direct OS package + installations Server Management ================= -- Variable based flexible configuration -- Create servers using pre-defined templates +- Variable based flexible configuration +- Create servers using pre-defined templates Connectivity ============ -- Password and key based authentication for outgoing SSH connections -- Built-in support of the Python `requests - library `__ for outgoing API - calls +- Password and key based authentication for outgoing SSH connections +- Built-in support of the Python `requests + library `__ for outgoing API calls Commands ======== -- Execute SSH commands on remote servers -- Run Python code on the Tower Odoo server -- Run Flight Plan from command -- Render commands using variables -- Secret keys for private data storage +- Execute SSH commands on remote servers +- Run Python code on the Tower Odoo server +- Run Flight Plan from command +- Render commands using variables +- Secret keys for private data storage Flight Plans ============ -- Execute multiple commands in a row -- Condition based flow: +- Execute multiple commands in a row +- Condition based flow: - - Based on condition using `Python - syntax `__ - - Based on the previous command exit code + - Based on condition using `Python + syntax `__ + - Based on the previous command exit code Files ===== -- Download files from remote server using SFTP -- Upload files to remote server using SFTP -- Support for ``text`` and ``binary`` file format -- Manage files using pre-defined file templates +- Download files from remote server using SFTP +- Upload files to remote server using SFTP +- Support for ``text`` and ``binary`` file format +- Manage files using pre-defined file templates Support and Technical Requirements ================================== -- Cetmix Tower with usability and simplicity in mind, though some - features might require a foundational understanding of server - management principles -- We offer dedicated support to help with any custom setup needs or - questions that may arise -- For additional details, visit our website - `cetmix.com `__ +- Cetmix Tower with usability and simplicity in mind, though some + features might require a foundational understanding of server + management principles +- We offer dedicated support to help with any custom setup needs or + questions that may arise +- For additional details, visit our website + `cetmix.com `__ **Table of contents** @@ -128,21 +127,21 @@ like to provide access to the Cetmix Tower. In the ``Cetmix Tower`` section select one of the following options in the ``Access Level`` field: -- **User**. Members of this group have read access only to the - `Servers <#configure-a-server>`__ which they are added as followers. - They also have access to the entities such as - `Commands <#configure-a-command>`__, `Flight - Plans <#configure-a-flight-plan>`__ or `Server - Logs <#configure-a-server-log>`__ with ``Access Level`` set to - ``User``. -- **Manager**. Members of this group can modify - `Servers <#configure-a-server>`__ which they are added as followers. - They can create new `Servers <#configure-a-server>`__ too however - they cannot delete them. Users of this group have access to the - entities with ``Access Level`` set to ``Manager`` or ``User``. -- **Root**. Members of this group can create, modify or delete any - `Server <#configure-a-server>`__. They also have access to the - entities with any ``Access Level`` set. +- **User**. Members of this group have read access only to the + `Servers <#configure-a-server>`__ which they are added as followers. + They also have access to the entities such as + `Commands <#configure-a-command>`__, `Flight + Plans <#configure-a-flight-plan>`__ or `Server + Logs <#configure-a-server-log>`__ with ``Access Level`` set to + ``User``. +- **Manager**. Members of this group can modify + `Servers <#configure-a-server>`__ which they are added as followers. + They can create new `Servers <#configure-a-server>`__ too however they + cannot delete them. Users of this group have access to the entities + with ``Access Level`` set to ``Manager`` or ``User``. +- **Root**. Members of this group can create, modify or delete any + `Server <#configure-a-server>`__. They also have access to the + entities with any ``Access Level`` set. **NB:** Please keep in mind that some of the entities can have their additional access management variations. @@ -159,32 +158,32 @@ Fill the values it the tabs below: **General Settings** -- **Partner**: Partner this server belongs to -- **Operating System**: Operating system that runs on the server -- **Tags**: User-defined search tags -- **IPv4 Address** -- **IPv6 Address**: Will be used if no IPv4 address is specified -- **SSH Auth Mode**: Available options are "Password" and "Key" -- **SSH Port** -- **SSH Username** -- **Use sudo**: If sudo is required by default for running all commands - on this server -- **SSH Password**: Used if Auth Mode is set to "Password" and for - running ``sudo`` commands with password -- **SSH Private Key**: Used for authentication is SSH Auth Mode is set - to "Key" -- **Note**: Comments or user notes +- **Partner**: Partner this server belongs to +- **Operating System**: Operating system that runs on the server +- **Tags**: User-defined search tags +- **IPv4 Address** +- **IPv6 Address**: Will be used if no IPv4 address is specified +- **SSH Auth Mode**: Available options are "Password" and "Key" +- **SSH Port** +- **SSH Username** +- **Use sudo**: If sudo is required by default for running all commands + on this server +- **SSH Password**: Used if Auth Mode is set to "Password" and for + running ``sudo`` commands with password +- **SSH Private Key**: Used for authentication is SSH Auth Mode is set + to "Key" +- **Note**: Comments or user notes There is a special **Status** field which indicates current Server status. It is meant to be updated automatically using external API with further customizations. Following pre-defined statuses are available: -- Undefined -- Stopped -- Starting -- Running -- Stopping -- Restarting +- Undefined +- Stopped +- Starting +- Running +- Stopping +- Restarting Default status is 'Undefined'. @@ -209,12 +208,12 @@ Log <#configure-a-server-log>`__ section for more details. Following action buttons are located in the top of the form: -- **Command Logs**: Shows all `Command <#configure-a-command>`__ logs - for this server -- **Flight Plan Logs**: Shows all `Flight - Plan <#configure-a-flight-plan>`__ logs for this server -- **Files**: Shows all `Files <#configure-a-file>`__ that belong to - this server +- **Command Logs**: Shows all `Command <#configure-a-command>`__ logs + for this server +- **Flight Plan Logs**: Shows all `Flight + Plan <#configure-a-flight-plan>`__ logs for this server +- **Files**: Shows all `Files <#configure-a-file>`__ that belong to this + server Configure a Server Template --------------------------- @@ -227,18 +226,18 @@ Fill the values it the tabs below: **General Settings** -- **Flight Plan**: Select a flight plan to be executed after a server - is created -- **Operating System**: Default operating system for new servers -- **Tags**: Default search tags for new servers -- **SSH Auth Mode**: Default SSH auth mode for new servers. Available - options are "Password" and "Key" -- **SSH Port**: Default SSH port for new servers -- **SSH Username**: Default SSH username for new servers -- **Use sudo**: Default sudo mode for new servers -- **SSH Password**: Default SSH password for new servers -- **SSH Private Key**: Default SSH private key for new servers -- **Note**: Comments or user notes +- **Flight Plan**: Select a flight plan to be executed after a server is + created +- **Operating System**: Default operating system for new servers +- **Tags**: Default search tags for new servers +- **SSH Auth Mode**: Default SSH auth mode for new servers. Available + options are "Password" and "Key" +- **SSH Port**: Default SSH port for new servers +- **SSH Username**: Default SSH username for new servers +- **Use sudo**: Default sudo mode for new servers +- **SSH Password**: Default SSH password for new servers +- **SSH Private Key**: Default SSH private key for new servers +- **Note**: Comments or user notes **Variables** @@ -257,11 +256,11 @@ Configure Variables To configure variables go to the ``Cetmix Tower -> Settings`` and select the ``Variables`` menu. Click ``Create`` and put values in the fields: -- **Name**: Readable name -- **Reference**: Unique identifier used to address variable in - conditions and expressions. Leave blank to generate automatically - based on name -- **Note**: Put your notes here +- **Name**: Readable name +- **Reference**: Unique identifier used to address variable in + conditions and expressions. Leave blank to generate automatically + based on name +- **Note**: Put your notes here Configure Tags -------------- @@ -269,12 +268,12 @@ Configure Tags To configure variables go to the ``Cetmix Tower -> Settings`` and select the ``Tags`` menu. Click ``Create`` and put values in the fields: -- **Name**: Readable name -- **Reference**: Unique identifier used to address the tag in - conditions and expressions. Leave this field blank to generate it - automatically based on the name -- **Color**: Select a color for the tag -- **Servers**: Select the servers associated with the tag. +- **Name**: Readable name +- **Reference**: Unique identifier used to address the tag in conditions + and expressions. Leave this field blank to generate it automatically + based on the name +- **Color**: Select a color for the tag +- **Servers**: Select the servers associated with the tag. Configure OSs (Operating Systems) --------------------------------- @@ -283,12 +282,12 @@ To configure operating systems, go to the ``Cetmix Tower -> Settings`` and select the ``OSs`` menu. Click ``Create`` and fill in the values for the following fields: -- **Name**: Readable name -- **Reference**: Unique identifier used to address the OS in conditions - and expressions. Leave this field blank to generate it automatically - based on the name. -- **Color**: Select a color for the OS. -- **Previous Version**: Select the previous version of the current OS. +- **Name**: Readable name +- **Reference**: Unique identifier used to address the OS in conditions + and expressions. Leave this field blank to generate it automatically + based on the name. +- **Color**: Select a color for the OS. +- **Previous Version**: Select the previous version of the current OS. Variables Applicability ~~~~~~~~~~~~~~~~~~~~~~~ @@ -296,15 +295,15 @@ Variables Applicability `Cetmix Tower `__ supports ``jinja2`` syntax for variables. You can use variables to render: -- Commands. Eg ``ls -lh {{ file_store_location }}`` -- Files. Eg a "Dockerfile" file can have the following text in it: - ``ODOO_VERSION = {{ odoo_default_version }}`` -- File Templates. You can use variables for both file name and file - location on server. Eg ``File Name`` value is - ``backup_{{ instance_name }}_{{ odoo_db_name }}`` and - ``Directory on server`` is ``{{ file_cron_location }}`` -- Other Variables. Eg for an ``odoo_config_location`` variable can have - a value of ``{{ odoo_root}}/conf`` +- Commands. Eg ``ls -lh {{ file_store_location }}`` +- Files. Eg a "Dockerfile" file can have the following text in it: + ``ODOO_VERSION = {{ odoo_default_version }}`` +- File Templates. You can use variables for both file name and file + location on server. Eg ``File Name`` value is + ``backup_{{ instance_name }}_{{ odoo_db_name }}`` and + ``Directory on server`` is ``{{ file_cron_location }}`` +- Other Variables. Eg for an ``odoo_config_location`` variable can have + a value of ``{{ odoo_root}}/conf`` You can use any ``jinja2`` supported expressions. For example ``if else`` statements: @@ -325,8 +324,8 @@ Variable Rendering Modes There are two rendering modes available: -- Generic (or ssh) mode -- Pythonic mode +- Generic (or ssh) mode +- Pythonic mode Let use the following code as example: @@ -372,10 +371,10 @@ Variable Types Following types of variable values available in `Cetmix Tower `__: -- Local values. Those are values that are defined at a record level. - For example for a server or an action. -- Global values. Those are values that are defined at the `Cetmix - Tower `__ level. +- Local values. Those are values that are defined at a record level. For + example for a server or an action. +- Global values. Those are values that are defined at the `Cetmix + Tower `__ level. When rendering an expression local values are used first. If no local value is found then global value will be used. For example default value @@ -396,20 +395,20 @@ the ``tower`` variable unless you really need that on purpose. Following system variables are available: -- Server properties +- Server properties - - ``tower.server.name`` Current server name - - ``tower.server.reference`` Current server reference - - ``tower.server.username`` Current server SSH Username​ - - ``tower.server.ipv4`` Current server IPv4 Address​ - - ``tower.server.ipv6`` Current server IPv6 Address​ - - ``tower.server.partner_name`` Current server partner name + - ``tower.server.name`` Current server name + - ``tower.server.reference`` Current server reference + - ``tower.server.username`` Current server SSH Username​ + - ``tower.server.ipv4`` Current server IPv4 Address​ + - ``tower.server.ipv6`` Current server IPv6 Address​ + - ``tower.server.partner_name`` Current server partner name -- Helper tools +- Helper tools - - ``tower.tools.uuid`` Generates a random UUID4 - - ``tower.tools.today`` Current date - - ``tower.tools.now`` Current date time + - ``tower.tools.uuid`` Generates a random UUID4 + - ``tower.tools.today`` Current date + - ``tower.tools.now`` Current date time Configure a Key/Secret ---------------------- @@ -419,28 +418,28 @@ used for rendering commands. To configure a new key or secret go to ``Cetmix Tower -> Settings -> Keys`` click ``Create`` and put values in the fields: -- **Name**: Readable name -- **Key Type**: Following values are available: - - - ``SSH Key`` is used to store SSH private keys. They are selectable - in `Server settings <#configure-a-server>`__ - - ``Secret`` used to store sensitive information that can be used - inline in commands. Eg a token or a password. Secrets cannot be - previewed in command preview and are replaced with placeholder in - `command <#configure-a-command>`__ logs. - -- **Reference**: Key/secret record reference -- **Reference Code**: Complete reference code for inline usage -- **Value**: Key value. **IMPORTANT:** This is a write only field. - Please ensure that you have saved your key/secret before saving it. - Once saved it cannot be read from the user interface any longer. -- **Used For**: ``SSH Key`` type only. List of - `Servers <#configure-a-server>`__ where this SSH key is used -- **Partner**: ``Secret`` type only. If selected this secret is used - only for the `Servers <#configure-a-server>`__ of selected partner -- **Server**: ``Secret`` type only. If selected this secret is used - only for selected `Server <#configure-a-server>`__ -- **Note**: Put your notes here +- **Name**: Readable name +- **Key Type**: Following values are available: + + - ``SSH Key`` is used to store SSH private keys. They are selectable + in `Server settings <#configure-a-server>`__ + - ``Secret`` used to store sensitive information that can be used + inline in commands. Eg a token or a password. Secrets cannot be + previewed in command preview and are replaced with placeholder in + `command <#configure-a-command>`__ logs. + +- **Reference**: Key/secret record reference +- **Reference Code**: Complete reference code for inline usage +- **Value**: Key value. **IMPORTANT:** This is a write only field. + Please ensure that you have saved your key/secret before saving it. + Once saved it cannot be read from the user interface any longer. +- **Used For**: ``SSH Key`` type only. List of + `Servers <#configure-a-server>`__ where this SSH key is used +- **Partner**: ``Secret`` type only. If selected this secret is used + only for the `Servers <#configure-a-server>`__ of selected partner +- **Server**: ``Secret`` type only. If selected this secret is used only + for selected `Server <#configure-a-server>`__ +- **Note**: Put your notes here Keys of type ``Secret`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -459,9 +458,9 @@ Secrets are inserted inline in code using the following pattern: ``#!cxtower.secret.REFERENCE!#``. It consists of three dot separated parts and is terminated with a mandatory ``!#`` suffix: -- ``#!cxtower`` is used to declare a special Tower construction -- ``secret`` is used to declare its type (secret) -- ``REFERENCE`` secret id as it's written in the **Key ID** field +- ``#!cxtower`` is used to declare a special Tower construction +- ``secret`` is used to declare its type (secret) +- ``REFERENCE`` secret id as it's written in the **Key ID** field **Example:** @@ -485,49 +484,49 @@ Configure a File file transfer operations. Based on initial file location following file sources are available: -- Server. These are files that are initially located on remote server - and are fetched to `Cetmix Tower `__. For - example log files. +- Server. These are files that are initially located on remote server + and are fetched to `Cetmix Tower `__. For + example log files. -- Tower. These are files that are initially formed in `Cetmix - Tower `__ and are uploaded to remote - server. For example configuration files. Such files are rendered - using variables and can be created and managed using file templates. +- Tower. These are files that are initially formed in `Cetmix + Tower `__ and are uploaded to remote server. + For example configuration files. Such files are rendered using + variables and can be created and managed using file templates. To create a new file go to ``Cetmix Tower -> Files -> Files`` click ``Create`` and put values in the fields: -- **Name**: Filesystem filename -- **Source**: File source. Available options are ``Server`` and - ``Tower``. Check above for more details. -- **File type**: Type of file contents. Possible options: - - - **Text**: Regular text. Eg configuration file or log - - **Binary**: Binary file. Eg file archive or pdf document - -- **File**: Is used to store binary file data. -- **Template**: File template used to render this file. If selected - file will be automatically updated every time template is modified. -- **Server**: Server where this file is located -- **Directory on Server**: This is where the file is located on the - remote server -- **Full Server Path**: Full path to file on the remote server - including filename -- **Auto Sync**: If enabled the file will be automatically uploaded to - the remote server on after it is modified in `Cetmix - Tower `__. Used only with ``Tower`` source. -- **Keep when deleted**: If enabled, file will be kept on remote server - after removing it in the Odoo +- **Name**: Filesystem filename +- **Source**: File source. Available options are ``Server`` and + ``Tower``. Check above for more details. +- **File type**: Type of file contents. Possible options: + + - **Text**: Regular text. Eg configuration file or log + - **Binary**: Binary file. Eg file archive or pdf document + +- **File**: Is used to store binary file data. +- **Template**: File template used to render this file. If selected file + will be automatically updated every time template is modified. +- **Server**: Server where this file is located +- **Directory on Server**: This is where the file is located on the + remote server +- **Full Server Path**: Full path to file on the remote server including + filename +- **Auto Sync**: If enabled the file will be automatically uploaded to + the remote server on after it is modified in `Cetmix + Tower `__. Used only with ``Tower`` source. +- **Keep when deleted**: If enabled, file will be kept on remote server + after removing it in the Odoo Following fields are located in the tabs below: -- **Code**: Raw file content. This field is editable for the ``Tower`` - files and readonly for ``Server`` ones. This field supports - `Variables <#configure-variables>`__. -- **Preview**: This is a rendered file content as it will be uploaded - to server. Used only with ``Tower`` source. -- **Server Version**: Current file content fetched from server. Used - only with ``Tower`` source. +- **Code**: Raw file content. This field is editable for the ``Tower`` + files and readonly for ``Server`` ones. This field supports + `Variables <#configure-variables>`__. +- **Preview**: This is a rendered file content as it will be uploaded to + server. Used only with ``Tower`` source. +- **Server Version**: Current file content fetched from server. Used + only with ``Tower`` source. **NB**: File operations are performed using user credentials from server configuration. You should take care of filesystem access rights to @@ -543,27 +542,27 @@ To create a new file template go to ``Cetmix Tower -> Files -> Templates`` click ``Create`` and put values in the fields: -- **Name**: Template name -- **Reference**: Leave the "reference" field blank to generate a - reference automatically. -- **File Name**: Filesystem name of the file(s) created from this - template. This field supports `Variables <#configure-variables>`__. -- **Directory on server**: Directory on remote server where this file - will be stored. This field supports - `Variables <#configure-variables>`__. -- **Source**: File source. Available options are ``Server`` and - ``Tower``. Check above for more details. -- **File type**: Type of file contents. Possible options: - - - **Text**: Regular text. Eg configuration file or log - - **Binary**: Binary file. Eg file archive or pdf document - -- **Tags**: Make usage as search more convenient -- **Note**: Comments or user notes -- **Code**: Raw file content. This field supports - `Variables <#configure-variables>`__. -- **Keep when deleted**: If enabled, file(s) created from this template - will be kept on remote server after removing it(them) in the Odoo +- **Name**: Template name +- **Reference**: Leave the "reference" field blank to generate a + reference automatically. +- **File Name**: Filesystem name of the file(s) created from this + template. This field supports `Variables <#configure-variables>`__. +- **Directory on server**: Directory on remote server where this file + will be stored. This field supports + `Variables <#configure-variables>`__. +- **Source**: File source. Available options are ``Server`` and + ``Tower``. Check above for more details. +- **File type**: Type of file contents. Possible options: + + - **Text**: Regular text. Eg configuration file or log + - **Binary**: Binary file. Eg file archive or pdf document + +- **Tags**: Make usage as search more convenient +- **Note**: Comments or user notes +- **Code**: Raw file content. This field supports + `Variables <#configure-variables>`__. +- **Keep when deleted**: If enabled, file(s) created from this template + will be kept on remote server after removing it(them) in the Odoo **Hint**: If you want to create a file from template but don't want further template modifications to be applied to this file remove the @@ -576,62 +575,59 @@ Command is a shell command that is executed on remote server. To create a new command go to ``Cetmix Tower -> Commands -> Commands`` click ``Create`` and put values in the fields: -- **Name**: Command readable name. +- **Name**: Command readable name. -- **Reference**: Leave the "reference" field blank to generate a - reference automatically. +- **Reference**: Leave the "reference" field blank to generate a + reference automatically. -- **Allow Parallel Run**: If disabled only one copy of this command can - be run on the same server at the same time. Otherwise the same - command can be run in parallel. +- **Allow Parallel Run**: If disabled only one copy of this command can + be run on the same server at the same time. Otherwise the same command + can be run in parallel. -- **Note**: Comments or user notes. +- **Note**: Comments or user notes. -- **Servers**: List of servers this command can be run on. Leave this - field blank to make the command available to all servers. +- **Servers**: List of servers this command can be run on. Leave this + field blank to make the command available to all servers. -- **OSes**: List of operating systems this command is available. Leave - this field blank to make the command available for all OSes. +- **OSes**: List of operating systems this command is available. Leave + this field blank to make the command available for all OSes. -- **Tags**: Make usage as search more convenient. +- **Tags**: Make usage as search more convenient. -- **Action**: Action executed by the command. Possible options: +- **Action**: Action executed by the command. Possible options: - - ``SSH command``: Execute a shell command using ssh connection on - remote server. - - ``Execute Python code``: Execute a Python code on the Tower - Server. - - ``Create file using template``: Create or update a file using - selected file template and push / pull it to remote server / - tower. If the file already exists on server it will be - overwritten. - - ``Run flight plan``: Allow to start Flight Plan execution from - command (). + - ``SSH command``: Execute a shell command using ssh connection on + remote server. + - ``Execute Python code``: Execute a Python code on the Tower Server. + - ``Create file using template``: Create or update a file using + selected file template and push / pull it to remote server / tower. + If the file already exists on server it will be overwritten. + - ``Run flight plan``: Allow to start Flight Plan execution from + command (). -- **Default Path**: Specify path where command will be executed. This - field supports `Variables <#configure-variables>`__. Important: - ensure ssh user has access to the location even if executing command - using sudo. +- **Default Path**: Specify path where command will be executed. This + field supports `Variables <#configure-variables>`__. Important: ensure + ssh user has access to the location even if executing command using + sudo. -- **Code**: Code to execute. Can be an SSH command or Python code based - on selected action. This field supports - `Variables <#configure-variables>`__. **Important!** Variables used - in command are rendered in `different - modes <#variable-rendering-modes>`__ based on the command action. +- **Code**: Code to execute. Can be an SSH command or Python code based + on selected action. This field supports + `Variables <#configure-variables>`__. **Important!** Variables used in + command are rendered in `different + modes <#variable-rendering-modes>`__ based on the command action. -- **File Template**: File template that will be used to create or - update file. Check `File Templates <#file-templates>`__ for more - details. +- **File Template**: File template that will be used to create or update + file. Check `File Templates <#file-templates>`__ for more details. -- **Server Status**: Server status to be set after command execution. - Possible options: +- **Server Status**: Server status to be set after command execution. + Possible options: - - ``Undefined``. Default status. - - ``Stopped``. Server is stopped. - - ``Starting``. Server is starting. - - ``Running``. Server is running. - - ``Stopping``. Server is stopping. - - ``Restarting``. Server is restarting. + - ``Undefined``. Default status. + - ``Stopped``. Server is stopped. + - ``Starting``. Server is starting. + - ``Running``. Server is running. + - ``Stopping``. Server is stopping. + - ``Restarting``. Server is restarting. To return result from Python assign exit code and message to the COMMAND_RESULT variable of type ``dict`` like this: @@ -658,66 +654,65 @@ a flexible condition based execution flow. To create a new flight plan go to ``Cetmix Tower -> Commands -> Flight Plans`` click ``Create`` and put values in the fields: -- **Name**: Flight Plan name +- **Name**: Flight Plan name -- **Reference**: Leave the "reference" field blank to generate a - reference automatically. +- **Reference**: Leave the "reference" field blank to generate a + reference automatically. -- **On Error**: Default action to execute when an error happens during - the flight plan execution. Possible options: +- **On Error**: Default action to execute when an error happens during + the flight plan execution. Possible options: - - ``Exit with command code``. Will terminate the flight plan - execution and return an exit code of the failed command. - - ``Exit with custom code``. Will terminate the flight plan - execution and return the custom code configured in the field next - to this one. - - ``Run next command``. Will continue flight plan execution. + - ``Exit with command code``. Will terminate the flight plan execution + and return an exit code of the failed command. + - ``Exit with custom code``. Will terminate the flight plan execution + and return the custom code configured in the field next to this one. + - ``Run next command``. Will continue flight plan execution. -- **Note**: Comments or user notes. +- **Note**: Comments or user notes. -- **Servers**: List of servers this command can be run on. Leave this - field blank to make the command available to all servers. +- **Servers**: List of servers this command can be run on. Leave this + field blank to make the command available to all servers. -- **Tags**: Make usage as search more convenient. +- **Tags**: Make usage as search more convenient. -- **Code**: List of commands to execute. Each of the commands has the - following fields: +- **Code**: List of commands to execute. Each of the commands has the + following fields: - - **Sequence**: Order this command is executed. Lower value = higher - priority. - - **Condition**: `Python - expression `__ - to be matched for the command to be executed. Leave this field - blank for unconditional command execution. This field supports - `Variables <#configure-variables>`__. Example: + - **Sequence**: Order this command is executed. Lower value = higher + priority. + - **Condition**: `Python + expression `__ + to be matched for the command to be executed. Leave this field blank + for unconditional command execution. This field supports + `Variables <#configure-variables>`__. Example: - .. code:: python + .. code:: python - {{ odoo_version }} == "17.0" and ( {{ nginx_installed }} or {{ traefik_installed }} ) + {{ odoo_version }} == "17.0" and ( {{ nginx_installed }} or {{ traefik_installed }} ) - - **Command**: `Command <#configure-a-command>`__ to be executed. - - **Path**: Specify path where command will be executed. Overrides - ``Default Path`` of the command. This field supports - `Variables <#configure-variables>`__. - - **Use Sudo**: Use ``sudo`` if required to run this command. - - **Post Run Actions**: List of conditional actions to be triggered - after the command is executed. Each of the actions has the - following fields: + - **Command**: `Command <#configure-a-command>`__ to be executed. + - **Path**: Specify path where command will be executed. Overrides + ``Default Path`` of the command. This field supports + `Variables <#configure-variables>`__. + - **Use Sudo**: Use ``sudo`` if required to run this command. + - **Post Run Actions**: List of conditional actions to be triggered + after the command is executed. Each of the actions has the following + fields: - - **Sequence**: Order this actions is triggered. Lower value = - higher priority. - - **Condition**: Uses command exit code. - - **Action**: Action to execute if condition is met. Also, if - variables with values are specified, these variables will be - updated (for existing variables on the server) or added (for - new variables) to the server variables. Possible options: + - **Sequence**: Order this actions is triggered. Lower value = + higher priority. + - **Condition**: Uses command exit code. + - **Action**: Action to execute if condition is met. Also, if + variables with values are specified, these variables will be + updated (for existing variables on the server) or added (for new + variables) to the server variables. Possible options: - - ``Exit with command code``. Will terminate the flight plan - execution and return an exit code of the failed command. - - ``Exit with custom code``. Will terminate the flight plan - execution and return the custom code configured in the field - next to this one. - - ``Run next command``. Will continue flight plan execution. + - ``Exit with command code``. Will terminate the flight plan + execution and return an exit code of the failed command. + - ``Exit with custom code``. Will terminate the flight plan + execution and return the custom code configured in the field + next to this one. + - ``Run next command``. Will continue flight plan execution. Configure a Server Log ---------------------- @@ -730,38 +725,36 @@ way. To configure a Server Log open the server form, navigate to the Following fields are available: -- **Name**: Readable name of the log -- **Access Level**: Minimum access level required to access this - record. Please check the `User Access - Settings <#user-access-configuration>`__ section for more details. - Possible options: - - - ``User``. User must have at least ``Cetmix Tower / User`` access - group configured in the User Settings. - - ``Manager``. User must have at least ``Cetmix Tower / Manager`` - access group configured in the User Settings. - - ``Root``. User must have ``Cetmix Tower / Root`` access group - configured in the User Settings. - -- **Log Type**: Defines the way logs are fetched. Possible options: - - - ``Command``. A command is run with its output being saved to the - log - - ``File``. Log is fetched from a file - -- **Command**: A command that is used to fetched the logs. This option - is available only for ``Log Type`` set to ``Command``. Important: - please ensure that selected command can be executed multiple times in - parallel to avoid any potential issues. -- **Use Sudo**: Use ``sudo`` if required to run this command. -- **File**: A file that is used to fetch the log. This option is not - available when configuring a log for a `Server - Template <#configure-a-server-template>`__ -- **File Template**: A file template that is used to create a file when - a new `Server <#configure-a-server>`__ is created from a `Server - Template <#configure-a-server-template>`__. This option is available - only when configuring a log for a `Server - Template <#configure-a-server-template>`__ +- **Name**: Readable name of the log +- **Access Level**: Minimum access level required to access this record. + Please check the `User Access Settings <#user-access-configuration>`__ + section for more details. Possible options: + + - ``User``. User must have at least ``Cetmix Tower / User`` access + group configured in the User Settings. + - ``Manager``. User must have at least ``Cetmix Tower / Manager`` + access group configured in the User Settings. + - ``Root``. User must have ``Cetmix Tower / Root`` access group + configured in the User Settings. + +- **Log Type**: Defines the way logs are fetched. Possible options: + + - ``Command``. A command is run with its output being saved to the log + - ``File``. Log is fetched from a file + +- **Command**: A command that is used to fetched the logs. This option + is available only for ``Log Type`` set to ``Command``. Important: + please ensure that selected command can be executed multiple times in + parallel to avoid any potential issues. +- **Use Sudo**: Use ``sudo`` if required to run this command. +- **File**: A file that is used to fetch the log. This option is not + available when configuring a log for a `Server + Template <#configure-a-server-template>`__ +- **File Template**: A file template that is used to create a file when + a new `Server <#configure-a-server>`__ is created from a `Server + Template <#configure-a-server-template>`__. This option is available + only when configuring a log for a `Server + Template <#configure-a-server-template>`__ **Developer hint**: log output supports HTML formatting. You can implement your custom log formatter by overriding the @@ -778,9 +771,9 @@ needed. Use flight plans instead. **Why?** -- Simple commands are easier to reuse across multiple flight plans. -- Commands run with ``sudo`` with password are be split and executed - one by one anyway. +- Simple commands are easier to reuse across multiple flight plans. +- Commands run with ``sudo`` with password are be split and executed one + by one anyway. **Not recommended:** @@ -810,8 +803,8 @@ command or ``Path`` field in flight plan line. **Why?** -- Tower will automatically adjust the command to ensure it is properly - executed in the specified location. +- Tower will automatically adjust the command to ensure it is properly + executed in the specified location. **Do not do this:** @@ -821,21 +814,21 @@ command or ``Path`` field in flight plan line. **Way to go:** -- Add the following value in the ``Default Path`` command field or - ``Path`` field of a flight plan line: +- Add the following value in the ``Default Path`` command field or + ``Path`` field of a flight plan line: .. code:: bash /home/{{ tower.server.username }}/memes -- Leave the command code as follows: +- Leave the command code as follows: .. code:: bash cat my_doge_memes.txt -.. |User profile| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0-dev/cetmix_tower_server/static/description/images/user_profile.png -.. |Server logs tab| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0-dev/cetmix_tower_server/static/description/images/server_log_tab.png +.. |User profile| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0/cetmix_tower_server/static/description/images/user_profile.png +.. |Server logs tab| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0/cetmix_tower_server/static/description/images/server_log_tab.png Usage ===== @@ -843,16 +836,16 @@ Usage Create a new Server from a Server Template ------------------------------------------ -- Go to the ``Cetmix Tower/Servers/Templates`` menu and select a - `Server Template `__ -- Click "Create Server" button. A pop-up wizard will open with server - parameters populated from the template -- Put the new server name, check the parameters and click "Confirm" - button -- New server will be created -- If a `Flight Plan `__ is - defined in the server template it will be automatically executed - after a new server is created +- Go to the ``Cetmix Tower/Servers/Templates`` menu and select a `Server + Template `__ +- Click "Create Server" button. A pop-up wizard will open with server + parameters populated from the template +- Put the new server name, check the parameters and click "Confirm" + button +- New server will be created +- If a `Flight Plan `__ is + defined in the server template it will be automatically executed after + a new server is created You can also create a new server from template from code using a designated ``create_server_from_template`` function of the @@ -909,34 +902,34 @@ server when a Sales Order is confirmed: Run a Command ------------- -- Select a server in the list view or open a server form view -- Open the ``Actions`` menu and click ``Execute Command`` -- A wizard is opened with the following fields: - - - **Servers**: Servers on which this command will be executed - - **Tags**: If selected only commands with these tags will be shown - - **Sudo**: ``sudo`` option for running this command - - **Command**: Command to execute - - **Show shared**: By default only commands available for the - selected server(s) are selectable. Activate this checkbox to - select any command - - **Path**: Directory where command will be executed. Important: - this field does not support variables! Ensure that user has access - to this location even if you run command using sudo. - - **Code**: Raw command code - - **Preview**: Command code rendered using server variables. - **IMPORTANT:** If several servers are selected preview will not be - rendered. However during the command execution command code will - be rendered for each server separately. +- Select a server in the list view or open a server form view +- Open the ``Actions`` menu and click ``Execute Command`` +- A wizard is opened with the following fields: + + - **Servers**: Servers on which this command will be executed + - **Tags**: If selected only commands with these tags will be shown + - **Sudo**: ``sudo`` option for running this command + - **Command**: Command to execute + - **Show shared**: By default only commands available for the selected + server(s) are selectable. Activate this checkbox to select any + command + - **Path**: Directory where command will be executed. Important: this + field does not support variables! Ensure that user has access to + this location even if you run command using sudo. + - **Code**: Raw command code + - **Preview**: Command code rendered using server variables. + **IMPORTANT:** If several servers are selected preview will not be + rendered. However during the command execution command code will be + rendered for each server separately. There are two action buttons available in the wizard: -- **Run**. Executes a command using server "run" method and log command - result into the "Command Log". -- **Run in wizard**. Executes a command directly in the wizard and show - command log in a new wizard window. **IMPORTANT:** Button will be - show only if single server is selected. If you try to run a command - for several servers from code, you will get a ValidationError. +- **Run**. Executes a command using server "run" method and log command + result into the "Command Log". +- **Run in wizard**. Executes a command directly in the wizard and show + command log in a new wizard window. **IMPORTANT:** Button will be show + only if single server is selected. If you try to run a command for + several servers from code, you will get a ValidationError. You can check command execution logs in the ``Cetmix Tower/Commands/Command Logs`` menu. Important! If you want to @@ -946,44 +939,44 @@ that. Run a Flight Plan ----------------- -- Select a server in the list view or open a server form view +- Select a server in the list view or open a server form view -- Open the ``Actions`` menu and click ``Execute Flight Plan`` +- Open the ``Actions`` menu and click ``Execute Flight Plan`` -- A wizard is opened with the following fields: +- A wizard is opened with the following fields: - - **Servers**: Servers on which this command will be executed - - **Tags**: If selected only commands with these tags will be shown - - **Plan**: Flight plan to execute - - **Show shared**: By default only flight plans available for the - selected server(s) are selectable. Activate this checkbox to - select any flight plan - - **Commands**: Commands that will be executed in this flight plan. - This field is read only + - **Servers**: Servers on which this command will be executed + - **Tags**: If selected only commands with these tags will be shown + - **Plan**: Flight plan to execute + - **Show shared**: By default only flight plans available for the + selected server(s) are selectable. Activate this checkbox to select + any flight plan + - **Commands**: Commands that will be executed in this flight plan. + This field is read only - Click the **Run** button to execute a flight plan. + Click the **Run** button to execute a flight plan. - You can check the flight plan results in the - ``Cetmix Tower/Commands/Flight Plan Logs`` menu. Important! If you - want to delete a command you need to delete all its logs manually - before doing that. + You can check the flight plan results in the + ``Cetmix Tower/Commands/Flight Plan Logs`` menu. Important! If you + want to delete a command you need to delete all its logs manually + before doing that. Check a Server Log ------------------ To check a server log: -- Navigate to the ``Server Logs`` tab on the Server form -- Click on the log **(1)** you would like to check to open in in a pop - up window. Or click on the ``Open`` button **(2)** to open it in the - full form view +- Navigate to the ``Server Logs`` tab on the Server form +- Click on the log **(1)** you would like to check to open in in a pop + up window. Or click on the ``Open`` button **(2)** to open it in the + full form view |Open server log| -- Click the ``Refresh`` button to update the log. You can also click - the ``Refresh All`` button **(3)** located above the log list in - order to refresh all logs at once. Log output will be displayed in - the HTML field below. +- Click the ``Refresh`` button to update the log. You can also click the + ``Refresh All`` button **(3)** located above the log list in order to + refresh all logs at once. Log output will be displayed in the HTML + field below. |Update server log| @@ -997,9 +990,9 @@ are located in a special abstract model "cetmix.tower". You can check those functions in the source code in the following file: ``models/cetmix_tower.py`` -.. |Automatic action| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0-dev/cetmix_tower_server/static/description/images/server_from_template_auto_action.png -.. |Open server log| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0-dev/cetmix_tower_server/static/description/images/server_log_usage_1.png -.. |Update server log| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0-dev/cetmix_tower_server/static/description/images/server_log_usage_2.png +.. |Automatic action| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0/cetmix_tower_server/static/description/images/server_from_template_auto_action.png +.. |Open server log| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0/cetmix_tower_server/static/description/images/server_log_usage_1.png +.. |Update server log| image:: https://raw.githubusercontent.com/cetmix/cetmix-tower/14.0/cetmix_tower_server/static/description/images/server_log_usage_2.png Bug Tracker =========== @@ -1007,7 +1000,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -1022,6 +1015,6 @@ Authors Maintainers ----------- -This module is part of the `cetmix/cetmix-tower `_ project on GitHub. +This module is part of the `cetmix/cetmix-tower `_ project on GitHub. You are welcome to contribute. diff --git a/cetmix_tower_server/__manifest__.py b/cetmix_tower_server/__manifest__.py index 6516470b..930aba2e 100644 --- a/cetmix_tower_server/__manifest__.py +++ b/cetmix_tower_server/__manifest__.py @@ -1,5 +1,6 @@ # Copyright Cetmix OU # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + { "name": "Cetmix Tower Server Management", "summary": "Flexible Server Management directly from Odoo", @@ -23,6 +24,7 @@ "security/cx_tower_server_security.xml", "security/cx_tower_command_security.xml", "security/cx_tower_variable_value_security.xml", + "security/cx_tower_variable_security.xml", "security/cx_tower_plan_security.xml", "security/cx_tower_plan_line_security.xml", "security/cx_tower_plan_line_action_security.xml", diff --git a/cetmix_tower_server/models/cx_tower_access_mixin.py b/cetmix_tower_server/models/cx_tower_access_mixin.py index 9b801c00..c14418d3 100644 --- a/cetmix_tower_server/models/cx_tower_access_mixin.py +++ b/cetmix_tower_server/models/cx_tower_access_mixin.py @@ -12,6 +12,14 @@ class CxTowerAccessMixin(models.AbstractModel): _name = "cx.tower.access.mixin" _description = "Cetmix Tower access mixin" + access_level = fields.Selection( + lambda self: self._selection_access_level(), + string="Access Level", + default=lambda self: self._default_access_level(), + groups="cetmix_tower_server.group_root,cetmix_tower_server.group_manager", + required=True, + ) + def _selection_access_level(self): """Available access levels @@ -31,11 +39,3 @@ def _default_access_level(self): Char: `access_level` field selection value """ return "2" - - access_level = fields.Selection( - lambda self: self._selection_access_level(), - string="Access Level", - default=lambda self: self._default_access_level(), - groups="cetmix_tower_server.group_root,cetmix_tower_server.group_manager", - required=True, - ) diff --git a/cetmix_tower_server/models/cx_tower_variable.py b/cetmix_tower_server/models/cx_tower_variable.py index 85e02238..a59e5506 100644 --- a/cetmix_tower_server/models/cx_tower_variable.py +++ b/cetmix_tower_server/models/cx_tower_variable.py @@ -6,7 +6,7 @@ class TowerVariable(models.Model): _name = "cx.tower.variable" _description = "Cetmix Tower Variable" - _inherit = ["cx.tower.reference.mixin"] + _inherit = ["cx.tower.reference.mixin", "cx.tower.access.mixin"] _order = "name" diff --git a/cetmix_tower_server/models/cx_tower_variable_value.py b/cetmix_tower_server/models/cx_tower_variable_value.py index 0c1bc1a2..99beb74f 100644 --- a/cetmix_tower_server/models/cx_tower_variable_value.py +++ b/cetmix_tower_server/models/cx_tower_variable_value.py @@ -12,6 +12,7 @@ class TowerVariableValue(models.Model): _description = "Cetmix Tower Variable Values" _inherit = [ "cx.tower.reference.mixin", + "cx.tower.access.mixin", ] _rec_name = "variable_reference" _order = "variable_reference" diff --git a/cetmix_tower_server/security/cx_tower_variable_security.xml b/cetmix_tower_server/security/cx_tower_variable_security.xml new file mode 100644 index 00000000..68c62676 --- /dev/null +++ b/cetmix_tower_server/security/cx_tower_variable_security.xml @@ -0,0 +1,26 @@ + + + + + Tower Variable: User Access Rule + + [('access_level', '=', '1')] + + + + + + Tower Variable: Manager Access Rule + + [('access_level', 'in', ['2'])] + + + + + + Tower Variable: Root Access Rule + + [(1, '=', 1)] + + + diff --git a/cetmix_tower_server/security/cx_tower_variable_value_security.xml b/cetmix_tower_server/security/cx_tower_variable_value_security.xml index 697addb6..4562b5d2 100644 --- a/cetmix_tower_server/security/cx_tower_variable_value_security.xml +++ b/cetmix_tower_server/security/cx_tower_variable_value_security.xml @@ -1,24 +1,45 @@ - - Tower variable value: manager access rule - - ['|', ('is_global', '=', True), - ('server_id.message_partner_ids', 'in', [user.partner_id.id])] - + + Tower Variable Value: User Access Rule + + + ['|', + ('is_global', '=', True), + '&', + ('variable_id.access_level', '=', '1'), + ('server_id.message_partner_ids', 'in', [user.partner_id.id]) + ] + + + + + + + Tower Variable Value: Manager Access Rule + + + ['|', + ('is_global', '=', True), + '&', + ('variable_id.access_level', 'in', ['2']), + ('server_id.message_partner_ids', 'in', [user.partner_id.id]) + ] + + + - - - + - Tower variable value: root access rule + Tower Variable Value: Root Access Rule [(1, '=', 1)] - + diff --git a/cetmix_tower_server/static/description/index.html b/cetmix_tower_server/static/description/index.html index dd902635..552a6273 100644 --- a/cetmix_tower_server/static/description/index.html +++ b/cetmix_tower_server/static/description/index.html @@ -369,7 +369,7 @@

Cetmix Tower Server Management

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:4b055a9f60e7fa95ab04b470c0bf1fad7de7a7b795f211ba8d282abb73b0a212 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 cetmix/cetmix-tower

+

Beta License: AGPL-3 cetmix/cetmix-tower

Cetmix Tower offers a streamlined solution for managing remote servers via SSH or API calls directly from Odoo. It is designed for versatility across @@ -410,8 +410,7 @@

Connectivity

  • Password and key based authentication for outgoing SSH connections
  • Built-in support of the Python requests -library for outgoing API -calls
  • +library for outgoing API calls
@@ -470,7 +469,7 @@

User access configuration

to in the the user settings. To configure it go to Setting -> Users & Companies -> Users and open a user whom you would like to provide access to the Cetmix Tower.

-

User profile

+

User profile

In the Cetmix Tower section select one of the following options in the Access Level field:

    @@ -483,9 +482,9 @@

    User access configuration

    User.
  • Manager. Members of this group can modify Servers which they are added as followers. -They can create new Servers too however -they cannot delete them. Users of this group have access to the -entities with Access Level set to Manager or User.
  • +They can create new Servers too however they +cannot delete them. Users of this group have access to the entities +with Access Level set to Manager or User.
  • Root. Members of this group can create, modify or delete any Server. They also have access to the entities with any Access Level set.
  • @@ -549,8 +548,8 @@

    Configure a Server

    for this server
  • Flight Plan Logs: Shows all Flight Plan logs for this server
  • -
  • Files: Shows all Files that belong to -this server
  • +
  • Files: Shows all Files that belong to this +server
@@ -561,8 +560,8 @@

Configure a Server Template

Fill the values it the tabs below:

General Settings

    -
  • Flight Plan: Select a flight plan to be executed after a server -is created
  • +
  • Flight Plan: Select a flight plan to be executed after a server is +created
  • Operating System: Default operating system for new servers
  • Tags: Default search tags for new servers
  • SSH Auth Mode: Default SSH auth mode for new servers. Available @@ -600,9 +599,9 @@

    Configure Tags

    the Tags menu. Click Create and put values in the fields:

    • Name: Readable name
    • -
    • Reference: Unique identifier used to address the tag in -conditions and expressions. Leave this field blank to generate it -automatically based on the name
    • +
    • Reference: Unique identifier used to address the tag in conditions +and expressions. Leave this field blank to generate it automatically +based on the name
    • Color: Select a color for the tag
    • Servers: Select the servers associated with the tag.
    @@ -692,8 +691,8 @@

    Variable Types

    Following types of variable values available in Cetmix Tower:

      -
    • Local values. Those are values that are defined at a record level. -For example for a server or an action.
    • +
    • Local values. Those are values that are defined at a record level. For +example for a server or an action.
    • Global values. Those are values that are defined at the Cetmix Tower level.
    @@ -757,8 +756,8 @@

    Configure a Key/Secret

    Servers where this SSH key is used
  • Partner: Secret type only. If selected this secret is used only for the Servers of selected partner
  • -
  • Server: Secret type only. If selected this secret is used -only for selected Server
  • +
  • Server: Secret type only. If selected this secret is used only +for selected Server
  • Note: Put your notes here
@@ -803,9 +802,9 @@

Configure a File

and are fetched to Cetmix Tower. For example log files.
  • Tower. These are files that are initially formed in Cetmix -Tower and are uploaded to remote -server. For example configuration files. Such files are rendered -using variables and can be created and managed using file templates.
  • +Tower and are uploaded to remote server. +For example configuration files. Such files are rendered using +variables and can be created and managed using file templates.

    To create a new file go to Cetmix Tower -> Files -> Files click Create and put values in the fields:

    @@ -819,13 +818,13 @@

    Configure a File

  • File: Is used to store binary file data.
  • -
  • Template: File template used to render this file. If selected -file will be automatically updated every time template is modified.
  • +
  • Template: File template used to render this file. If selected file +will be automatically updated every time template is modified.
  • Server: Server where this file is located
  • Directory on Server: This is where the file is located on the remote server
  • -
  • Full Server Path: Full path to file on the remote server -including filename
  • +
  • Full Server Path: Full path to file on the remote server including +filename
  • Auto Sync: If enabled the file will be automatically uploaded to the remote server on after it is modified in Cetmix Tower. Used only with Tower source.
  • @@ -837,8 +836,8 @@

    Configure a File

  • Code: Raw file content. This field is editable for the Tower files and readonly for Server ones. This field supports Variables.
  • -
  • Preview: This is a rendered file content as it will be uploaded -to server. Used only with Tower source.
  • +
  • Preview: This is a rendered file content as it will be uploaded to +server. Used only with Tower source.
  • Server Version: Current file content fetched from server. Used only with Tower source.
  • @@ -890,8 +889,8 @@

    Configure a Command

  • Reference: Leave the “reference” field blank to generate a reference automatically.
  • Allow Parallel Run: If disabled only one copy of this command can -be run on the same server at the same time. Otherwise the same -command can be run in parallel.
  • +be run on the same server at the same time. Otherwise the same command +can be run in parallel.
  • Note: Comments or user notes.
  • Servers: List of servers this command can be run on. Leave this field blank to make the command available to all servers.
  • @@ -901,28 +900,25 @@

    Configure a Command

  • Action: Action executed by the command. Possible options:
    • SSH command: Execute a shell command using ssh connection on remote server.
    • -
    • Execute Python code: Execute a Python code on the Tower -Server.
    • +
    • Execute Python code: Execute a Python code on the Tower Server.
    • Create file using template: Create or update a file using -selected file template and push / pull it to remote server / -tower. If the file already exists on server it will be -overwritten.
    • +selected file template and push / pull it to remote server / tower. +If the file already exists on server it will be overwritten.
    • Run flight plan: Allow to start Flight Plan execution from command ().
  • Default Path: Specify path where command will be executed. This -field supports Variables. Important: -ensure ssh user has access to the location even if executing command -using sudo.
  • +field supports Variables. Important: ensure +ssh user has access to the location even if executing command using +sudo.
  • Code: Code to execute. Can be an SSH command or Python code based on selected action. This field supports -Variables. Important! Variables used -in command are rendered in different +Variables. Important! Variables used in +command are rendered in different modes based on the command action.
  • -
  • File Template: File template that will be used to create or -update file. Check File Templates for more -details.
  • +
  • File Template: File template that will be used to create or update +file. Check File Templates for more details.
  • Server Status: Server status to be set after command execution. Possible options:
    • Undefined. Default status.
    • @@ -965,11 +961,10 @@

      Configure a Flight Plan

    • On Error: Default action to execute when an error happens during the flight plan execution. Possible options:

        -
      • Exit with command code. Will terminate the flight plan -execution and return an exit code of the failed command.
      • -
      • Exit with custom code. Will terminate the flight plan -execution and return the custom code configured in the field next -to this one.
      • +
      • Exit with command code. Will terminate the flight plan execution +and return an exit code of the failed command.
      • +
      • Exit with custom code. Will terminate the flight plan execution +and return the custom code configured in the field next to this one.
      • Run next command. Will continue flight plan execution.
    • @@ -987,8 +982,8 @@

      Configure a Flight Plan

      priority.
    • Condition: Python expression -to be matched for the command to be executed. Leave this field -blank for unconditional command execution. This field supports +to be matched for the command to be executed. Leave this field blank +for unconditional command execution. This field supports Variables. Example:
    @@ -1001,15 +996,15 @@ 

    Configure a Flight Plan

    Variables.
  • Use Sudo: Use sudo if required to run this command.
  • Post Run Actions: List of conditional actions to be triggered -after the command is executed. Each of the actions has the -following fields:
      +after the command is executed. Each of the actions has the following +fields:
      • Sequence: Order this actions is triggered. Lower value = higher priority.
      • Condition: Uses command exit code.
      • Action: Action to execute if condition is met. Also, if variables with values are specified, these variables will be -updated (for existing variables on the server) or added (for -new variables) to the server variables. Possible options:
          +updated (for existing variables on the server) or added (for new +variables) to the server variables. Possible options:
          • Exit with command code. Will terminate the flight plan execution and return an exit code of the failed command.
          • Exit with custom code. Will terminate the flight plan @@ -1029,14 +1024,13 @@

            Configure a Server Log

            Server Logs allow to fetch and view logs of a server fast and convenient way. To configure a Server Log open the server form, navigate to the Server Logs tab and add a new record in the list.

            -

            Server logs tab

            +

            Server logs tab

            Following fields are available:

            • Name: Readable name of the log
            • -
            • Access Level: Minimum access level required to access this -record. Please check the User Access -Settings section for more details. -Possible options:
                +
              • Access Level: Minimum access level required to access this record. +Please check the User Access Settings +section for more details. Possible options:
                • User. User must have at least Cetmix Tower / User access group configured in the User Settings.
                • Manager. User must have at least Cetmix Tower / Manager @@ -1046,8 +1040,7 @@

                  Configure a Server Log

              • Log Type: Defines the way logs are fetched. Possible options:
                  -
                • Command. A command is run with its output being saved to the -log
                • +
                • Command. A command is run with its output being saved to the log
                • File. Log is fetched from a file
              • @@ -1078,8 +1071,8 @@

                Use simple commands

                Why?

                • Simple commands are easier to reuse across multiple flight plans.
                • -
                • Commands run with sudo with password are be split and executed -one by one anyway.
                • +
                • Commands run with sudo with password are be split and executed one +by one anyway.

                Not recommended:

                @@ -1131,16 +1124,16 @@ 

                Usage

                Create a new Server from a Server Template

                  -
                • Go to the Cetmix Tower/Servers/Templates menu and select a -Server Template
                • +
                • Go to the Cetmix Tower/Servers/Templates menu and select a Server +Template
                • Click “Create Server” button. A pop-up wizard will open with server parameters populated from the template
                • Put the new server name, check the parameters and click “Confirm” button
                • New server will be created
                • If a Flight Plan is -defined in the server template it will be automatically executed -after a new server is created
                • +defined in the server template it will be automatically executed after +a new server is created

                You can also create a new server from template from code using a designated create_server_from_template function of the @@ -1164,7 +1157,7 @@

                Create a new Server from a Server Template

                Here is a short example of an Odoo automated action that creates a new server when a Sales Order is confirmed:

                -

                Automatic action

                +

                Automatic action

                 for record in records:
                 
                @@ -1199,17 +1192,17 @@ 

                Run a Command

              • Tags: If selected only commands with these tags will be shown
              • Sudo: sudo option for running this command
              • Command: Command to execute
              • -
              • Show shared: By default only commands available for the -selected server(s) are selectable. Activate this checkbox to -select any command
              • -
              • Path: Directory where command will be executed. Important: -this field does not support variables! Ensure that user has access -to this location even if you run command using sudo.
              • +
              • Show shared: By default only commands available for the selected +server(s) are selectable. Activate this checkbox to select any +command
              • +
              • Path: Directory where command will be executed. Important: this +field does not support variables! Ensure that user has access to +this location even if you run command using sudo.
              • Code: Raw command code
              • Preview: Command code rendered using server variables. IMPORTANT: If several servers are selected preview will not be -rendered. However during the command execution command code will -be rendered for each server separately.
              • +rendered. However during the command execution command code will be +rendered for each server separately.
            @@ -1218,9 +1211,9 @@

            Run a Command

          • Run. Executes a command using server “run” method and log command result into the “Command Log”.
          • Run in wizard. Executes a command directly in the wizard and show -command log in a new wizard window. IMPORTANT: Button will be -show only if single server is selected. If you try to run a command -for several servers from code, you will get a ValidationError.
          • +command log in a new wizard window. IMPORTANT: Button will be show +only if single server is selected. If you try to run a command for +several servers from code, you will get a ValidationError.

          You can check command execution logs in the Cetmix Tower/Commands/Command Logs menu. Important! If you want to @@ -1240,8 +1233,8 @@

          Run a Flight Plan

        • Tags: If selected only commands with these tags will be shown
        • Plan: Flight plan to execute
        • Show shared: By default only flight plans available for the -selected server(s) are selectable. Activate this checkbox to -select any flight plan
        • +selected server(s) are selectable. Activate this checkbox to select +any flight plan
        • Commands: Commands that will be executed in this flight plan. This field is read only
        @@ -1262,14 +1255,14 @@

        Check a Server Log

        up window. Or click on the Open button (2) to open it in the full form view
      -

      Open server log

      +

      Open server log

        -
      • Click the Refresh button to update the log. You can also click -the Refresh All button (3) located above the log list in -order to refresh all logs at once. Log output will be displayed in -the HTML field below.
      • +
      • Click the Refresh button to update the log. You can also click the +Refresh All button (3) located above the log list in order to +refresh all logs at once. Log output will be displayed in the HTML +field below.
      -

      Update server log

      +

      Update server log

  • Using Cetmix Tower in Odoo automation

    @@ -1286,7 +1279,7 @@

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -1299,7 +1292,7 @@

    Authors

    Maintainers

    -

    This module is part of the cetmix/cetmix-tower project on GitHub.

    +

    This module is part of the cetmix/cetmix-tower project on GitHub.

    You are welcome to contribute.

    diff --git a/cetmix_tower_server/tests/test_variable.py b/cetmix_tower_server/tests/test_variable.py index 4e8931db..6ccc9721 100644 --- a/cetmix_tower_server/tests/test_variable.py +++ b/cetmix_tower_server/tests/test_variable.py @@ -332,12 +332,63 @@ def test_variable_value_toggle_global(self): "models/cx_tower_server.py", ) + def test_variable_value_server_subscription(self): + """Test variable access based on server subscription""" + server = self.server_test_1 + + # Creating a private variable + variable_private = self.Variable.create({"name": "Private Variable"}) + variable_private_value = self.VariableValue.create( + { + "variable_id": variable_private.id, + "server_id": server.id, + "value_char": "Private Value", + } + ) + + user_bob = self.user_bob + + # Removing a user from all groups + self.remove_from_group( + user_bob, + [ + "cetmix_tower_server.group_user", + "cetmix_tower_server.group_manager", + "cetmix_tower_server.group_root", + ], + ) + + # Adding a user to the user group + self.add_to_group(user_bob, "cetmix_tower_server.group_user") + + # Test: User does not have access to private variable without subscription + variable_private_value_as_bob = variable_private_value.with_user(user_bob) + with self.assertRaises(AccessError): + variable_private_value_as_bob.read([]) + + # User subscription to the server + server.message_subscribe([user_bob.partner_id.id]) + + # Cache Invalidation and Access Checking + variable_private_value_as_bob.invalidate_cache() + self.assertEqual( + variable_private_value_as_bob.value_char, + "Private Value", + msg="User should have access to private variables after subscription", + ) + + # Removing a subscription and checking for lack of access + server.message_unsubscribe([user_bob.partner_id.id]) + variable_private_value_as_bob.invalidate_cache() + with self.assertRaises(AccessError): + variable_private_value_as_bob.read([]) + def test_variable_value_access(self): """Test access rules for variable values""" server = self.server_test_1 - # Create variables assigned to server - # Private + # Creating variables assigned to server + # Private variable variable_private = self.Variable.create({"name": "Private Variable"}) variable_private_value = self.VariableValue.create( { @@ -347,8 +398,8 @@ def test_variable_value_access(self): } ) - # Global - variable_global = self.Variable.create({"name": "Variable Global"}) + # Global variable + variable_global = self.Variable.create({"name": "Global Variable"}) variable_global_value = self.VariableValue.create( { "variable_id": variable_global.id, @@ -359,7 +410,7 @@ def test_variable_value_access(self): user_bob = self.user_bob - # Remove user_bob from all tower security groups for sure + # Removing user_bob user from all security groups self.remove_from_group( user_bob, [ @@ -369,30 +420,38 @@ def test_variable_value_access(self): ], ) - # Make user_bob member of a group_user + # Add a user to the group_user group self.add_to_group(user_bob, "cetmix_tower_server.group_user") - # Check if group_user member can access global values + # Checking that the user has access to the global variable variable_global_value_as_bob = variable_global_value.with_user(user_bob) - variable_global_value = variable_global_value_as_bob.value_char + global_value = variable_global_value_as_bob.value_char self.assertEqual( - variable_global_value, "Global Value", msg="Must return 'Global Value'" + global_value, + "Global Value", + msg="User must have access to global variable values", ) - # Check what group_user member can't access values of variables - # of servers that he doesn't follow + + # Checking that the user does not have access to a private variable variable_private_value_as_bob = variable_private_value.with_user(user_bob) with self.assertRaises(AccessError): - variable_private_value = variable_private_value_as_bob.value_char + variable_private_value_as_bob.read([]) - # Make user_bob follower of server created + # Subscribe the user to the server server.message_subscribe([user_bob.partner_id.id]) - # Check if he has access to values of variables of servers that he follows - variable_private_value = variable_private_value_as_bob.value_char + # Invalidate cache before access + variable_private_value_as_bob.invalidate_cache() - self.assertEqual(variable_private_value, "Private Value") + # Checking access to a private variable after subscription + private_value = variable_private_value_as_bob.value_char + self.assertEqual( + private_value, + "Private Value", + msg="User must have access to private variable values after subscription", + ) - # Check that user_bob can't create new variable values + # Checking that the user cannot create new variable values with self.assertRaises(AccessError): self.VariableValue.with_user(user_bob).create( { @@ -402,10 +461,10 @@ def test_variable_value_access(self): } ) - # Make user_bob member of a group_manager + # Add a user to the group_manager group self.add_to_group(user_bob, "cetmix_tower_server.group_manager") - # Check that user_bob can create new variables either global and local + # We check that the user can create new global and local variables variable_new_global_as_bob = self.Variable.with_user(user_bob).create( {"name": "New Global Variable"} ) @@ -427,7 +486,7 @@ def test_variable_value_access(self): variable_new_private_as_bob = self.Variable.with_user(user_bob).create( {"name": "New Private Variable"} ) - variable_vale_new_private_as_bob = self.VariableValue.with_user( + variable_value_new_private_as_bob = self.VariableValue.with_user( user_bob ).create( { @@ -437,24 +496,23 @@ def test_variable_value_access(self): } ) self.assertEqual( - variable_vale_new_private_as_bob.value_char, + variable_value_new_private_as_bob.value_char, "New Private Value", "Must return New Private Value", ) - # Remove user from followers of the server, check if he lost access to private - # variables - + # We remove the user from the server subscribers and check that he loses access server.message_unsubscribe([user_bob.partner_id.id]) + variable_private_value_as_bob.invalidate_cache() with self.assertRaises(AccessError): - variable_private_value = variable_private_value_as_bob.value_char + variable_private_value_as_bob.read([]) - # Make user_bob member of group_root + # Add a user to the group group_root self.add_to_group(user_bob, "cetmix_tower_server.group_root") - # Check if he can see all variables + # Checking that the user can see all variables self.assertEqual( - variable_vale_new_private_as_bob.value_char, "New Private Value" + variable_value_new_private_as_bob.value_char, "New Private Value" ) self.assertEqual(variable_new_global_value_as_bob.value_char, "Global Value 1") diff --git a/cetmix_tower_server/views/cx_tower_variable_value_view.xml b/cetmix_tower_server/views/cx_tower_variable_value_view.xml index c61a49e9..ac4312df 100644 --- a/cetmix_tower_server/views/cx_tower_variable_value_view.xml +++ b/cetmix_tower_server/views/cx_tower_variable_value_view.xml @@ -1,5 +1,6 @@ + cx.tower.variable.value.view.tree cx.tower.variable.value @@ -24,6 +25,7 @@ + cx.tower.variable.value.view.search cx.tower.variable.value @@ -32,6 +34,7 @@ + + Variable Values ir.actions.act_window cx.tower.variable.value tree + diff --git a/cetmix_tower_server/views/cx_tower_variable_view.xml b/cetmix_tower_server/views/cx_tower_variable_view.xml index a43c5781..0727c8cc 100644 --- a/cetmix_tower_server/views/cx_tower_variable_view.xml +++ b/cetmix_tower_server/views/cx_tower_variable_view.xml @@ -25,6 +25,7 @@ + @@ -76,9 +77,7 @@ - - @@ -90,6 +89,7 @@ + From bfbbcfacb11a675ba4c62f1b741e6bb1ab475b33 Mon Sep 17 00:00:00 2001 From: Dmytro Meita Date: Mon, 23 Dec 2024 20:30:30 +0200 Subject: [PATCH 2/8] [FIX] cetmix_tower_server: Improve test data creation and remove demo data usage This commit addresses comments by improving the creation and usage of test data in the following ways: - Replaced demo data usage: - Removed references to `demo` data (e.g., `self.env.ref("cetmix_tower_server.tag_staging")`) in test cases. - Introduced locally created test records for tags with unique names such as `"Test Staging"` and `"Test Production"`. - Improved reusability: - Created reusable `self.Tag` model in `setUp` for tag creation in tests. - Moved common test data (e.g., tags) to `setUp` for easier maintenance and consistency. - Aligned with TL recommendations: - Ensured test data names are unique to avoid conflicts with existing data. - Simplified test cases by reusing common test setup. Why? - Ensures independence of test cases from demo data. - Makes tests more maintainable and aligned with best practices. - Resolves all comments raised in the PR review. Task: 4212 --- cetmix_tower_server/tests/common.py | 9 ++++++--- cetmix_tower_server/tests/test_plan.py | 20 +++++--------------- cetmix_tower_server/tests/test_server.py | 5 ++--- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/cetmix_tower_server/tests/common.py b/cetmix_tower_server/tests/common.py index 2ad5b4de..abe46bcf 100644 --- a/cetmix_tower_server/tests/common.py +++ b/cetmix_tower_server/tests/common.py @@ -15,6 +15,11 @@ def setUp(self, *args, **kwargs): # Cetmix Tower helper model self.CetmixTower = self.env["cetmix.tower"] + # Tags + self.Tag = self.env["cx.tower.tag"] + self.tag_test_staging = self.Tag.create({"name": "Test Staging"}) + self.tag_test_production = self.Tag.create({"name": "Test Production"}) + # Users self.Users = self.env["res.users"].with_context(no_reset_password=True) self.user_bob = self.Users.create( @@ -150,9 +155,7 @@ def setUp(self, *args, **kwargs): { "name": "Test plan 1", "note": "Create directory and list its content", - "tag_ids": [ - (6, 0, [self.env.ref("cetmix_tower_server.tag_staging").id]) - ], + "tag_ids": [(6, 0, [self.tag_test_staging.id])], } ) self.plan_line_1 = self.plan_line.create( diff --git a/cetmix_tower_server/tests/test_plan.py b/cetmix_tower_server/tests/test_plan.py index fbe7080d..5892da7c 100644 --- a/cetmix_tower_server/tests/test_plan.py +++ b/cetmix_tower_server/tests/test_plan.py @@ -274,9 +274,7 @@ def test_plan_user_access_rule(self): { "name": "Test plan 2", "note": "Create directory and list its content", - "tag_ids": [ - (6, 0, [self.env.ref("cetmix_tower_server.tag_staging").id]) - ], + "tag_ids": [(6, 0, [self.tag_test_staging.id])], } ) # Ensure that defaulf command access_level is equal to 2 @@ -315,9 +313,7 @@ def test_plan_user_access_rule(self): { "name": "Test plan 3", "note": "Create directory and list its content", - "tag_ids": [ - (6, 0, [self.env.ref("cetmix_tower_server.tag_staging").id]) - ], + "tag_ids": [(6, 0, [self.tag_test_staging.id])], } ) self.assertTrue( @@ -431,23 +427,17 @@ def test_multiple_plan_create_write(self): { "name": "Test Plan 1", "note": "Plan 1 Note", - "tag_ids": [ - (6, 0, [self.env.ref("cetmix_tower_server.tag_staging").id]) - ], + "tag_ids": [(6, 0, [self.tag_test_staging.id])], }, { "name": "Test Plan 2", "note": "Plan 2 Note", - "tag_ids": [ - (6, 0, [self.env.ref("cetmix_tower_server.tag_production").id]) - ], + "tag_ids": [(6, 0, [self.tag_test_production.id])], }, { "name": "Test Plan 3", "note": "Plan 3 Note", - "tag_ids": [ - (6, 0, [self.env.ref("cetmix_tower_server.tag_staging").id]) - ], + "tag_ids": [(6, 0, [self.tag_test_staging.id])], }, ] created_plans = self.Plan.create(plans_data) diff --git a/cetmix_tower_server/tests/test_server.py b/cetmix_tower_server/tests/test_server.py index f8d44d50..02f48113 100644 --- a/cetmix_tower_server/tests/test_server.py +++ b/cetmix_tower_server/tests/test_server.py @@ -7,6 +7,7 @@ class TestTowerServer(TestTowerCommon): def setUp(self, *args, **kwargs): super().setUp(*args, **kwargs) self.os_ubuntu_20_04 = self.env["cx.tower.os"].create({"name": "Ubuntu 20.04"}) + self.server_test_2 = self.Server.create( { "name": "Test Server #2", @@ -38,9 +39,7 @@ def setUp(self, *args, **kwargs): }, ), ], - "tag_ids": [ - (6, 0, [self.env.ref("cetmix_tower_server.tag_production").id]) - ], + "tag_ids": [(6, 0, [self.tag_test_production.id])], } ) # Files From 58b5bbf052f375998d3ffe180a36beb7d5068256 Mon Sep 17 00:00:00 2001 From: Dmytro Meita Date: Mon, 23 Dec 2024 20:30:30 +0200 Subject: [PATCH 3/8] [FIX] cetmix_tower_server: Improve test data creation and remove demo data usage This commit addresses comments by improving the creation and usage of test data in the following ways: - Replaced demo data usage: - Removed references to `demo` data (e.g., `self.env.ref("cetmix_tower_server.tag_staging")`) in test cases. - Introduced locally created test records for tags with unique names such as `"Test Staging"` and `"Test Production"`. - Improved reusability: - Created reusable `self.Tag` model in `setUp` for tag creation in tests. - Moved common test data (e.g., tags) to `setUp` for easier maintenance and consistency. - Aligned with TL recommendations: - Ensured test data names are unique to avoid conflicts with existing data. - Simplified test cases by reusing common test setup. Why? - Ensures independence of test cases from demo data. - Makes tests more maintainable and aligned with best practices. - Resolves all comments raised in the PR review. Task: 4212 --- .../security/cx_tower_variable_security.xml | 2 + cetmix_tower_server/tests/test_variable.py | 251 ++++++++++++------ 2 files changed, 165 insertions(+), 88 deletions(-) diff --git a/cetmix_tower_server/security/cx_tower_variable_security.xml b/cetmix_tower_server/security/cx_tower_variable_security.xml index 68c62676..a92a6dc8 100644 --- a/cetmix_tower_server/security/cx_tower_variable_security.xml +++ b/cetmix_tower_server/security/cx_tower_variable_security.xml @@ -1,5 +1,6 @@ + Tower Variable: User Access Rule @@ -23,4 +24,5 @@ [(1, '=', 1)] + diff --git a/cetmix_tower_server/tests/test_variable.py b/cetmix_tower_server/tests/test_variable.py index 6ccc9721..b2b7a500 100644 --- a/cetmix_tower_server/tests/test_variable.py +++ b/cetmix_tower_server/tests/test_variable.py @@ -332,64 +332,14 @@ def test_variable_value_toggle_global(self): "models/cx_tower_server.py", ) - def test_variable_value_server_subscription(self): - """Test variable access based on server subscription""" - server = self.server_test_1 - - # Creating a private variable - variable_private = self.Variable.create({"name": "Private Variable"}) - variable_private_value = self.VariableValue.create( - { - "variable_id": variable_private.id, - "server_id": server.id, - "value_char": "Private Value", - } - ) - - user_bob = self.user_bob - - # Removing a user from all groups - self.remove_from_group( - user_bob, - [ - "cetmix_tower_server.group_user", - "cetmix_tower_server.group_manager", - "cetmix_tower_server.group_root", - ], - ) - - # Adding a user to the user group - self.add_to_group(user_bob, "cetmix_tower_server.group_user") - - # Test: User does not have access to private variable without subscription - variable_private_value_as_bob = variable_private_value.with_user(user_bob) - with self.assertRaises(AccessError): - variable_private_value_as_bob.read([]) - - # User subscription to the server - server.message_subscribe([user_bob.partner_id.id]) - - # Cache Invalidation and Access Checking - variable_private_value_as_bob.invalidate_cache() - self.assertEqual( - variable_private_value_as_bob.value_char, - "Private Value", - msg="User should have access to private variables after subscription", - ) - - # Removing a subscription and checking for lack of access - server.message_unsubscribe([user_bob.partner_id.id]) - variable_private_value_as_bob.invalidate_cache() - with self.assertRaises(AccessError): - variable_private_value_as_bob.read([]) - def test_variable_value_access(self): """Test access rules for variable values""" server = self.server_test_1 - # Creating variables assigned to server - # Private variable - variable_private = self.Variable.create({"name": "Private Variable"}) + # Create variables with different access levels + variable_private = self.Variable.create( + {"name": "Private Variable", "access_level": "1"} + ) variable_private_value = self.VariableValue.create( { "variable_id": variable_private.id, @@ -398,8 +348,9 @@ def test_variable_value_access(self): } ) - # Global variable - variable_global = self.Variable.create({"name": "Global Variable"}) + variable_global = self.Variable.create( + {"name": "Variable Global", "access_level": "1"} + ) variable_global_value = self.VariableValue.create( { "variable_id": variable_global.id, @@ -410,7 +361,7 @@ def test_variable_value_access(self): user_bob = self.user_bob - # Removing user_bob user from all security groups + # Ensure user_bob is not in any security groups initially self.remove_from_group( user_bob, [ @@ -420,38 +371,34 @@ def test_variable_value_access(self): ], ) - # Add a user to the group_user group + # Add user_bob to group_user self.add_to_group(user_bob, "cetmix_tower_server.group_user") - # Checking that the user has access to the global variable + # Check access to global values for group_user variable_global_value_as_bob = variable_global_value.with_user(user_bob) - global_value = variable_global_value_as_bob.value_char self.assertEqual( - global_value, + variable_global_value_as_bob.value_char, "Global Value", - msg="User must have access to global variable values", + msg="User must be able to access global values", ) - # Checking that the user does not have access to a private variable + # Check that group_user member cannot access private values without subscription variable_private_value_as_bob = variable_private_value.with_user(user_bob) with self.assertRaises(AccessError): - variable_private_value_as_bob.read([]) + _ = variable_private_value_as_bob.value_char - # Subscribe the user to the server + # Subscribe user_bob to the server server.message_subscribe([user_bob.partner_id.id]) - # Invalidate cache before access - variable_private_value_as_bob.invalidate_cache() - - # Checking access to a private variable after subscription - private_value = variable_private_value_as_bob.value_char + # Check access to private values for subscribed users + variable_private_value_as_bob = variable_private_value.with_user(user_bob) self.assertEqual( - private_value, + variable_private_value_as_bob.value_char, "Private Value", - msg="User must have access to private variable values after subscription", + msg="User must be able to access private values after subscribing", ) - # Checking that the user cannot create new variable values + # Check that user_bob cannot create new variable values with self.assertRaises(AccessError): self.VariableValue.with_user(user_bob).create( { @@ -461,12 +408,12 @@ def test_variable_value_access(self): } ) - # Add a user to the group_manager group + # Add user_bob to group_manager self.add_to_group(user_bob, "cetmix_tower_server.group_manager") - # We check that the user can create new global and local variables + # Check that user_bob can create new variables with appropriate access levels variable_new_global_as_bob = self.Variable.with_user(user_bob).create( - {"name": "New Global Variable"} + {"name": "New Global Variable", "access_level": "2"} ) variable_new_global_value_as_bob = self.VariableValue.with_user( user_bob @@ -480,13 +427,13 @@ def test_variable_value_access(self): self.assertEqual( variable_new_global_value_as_bob.value_char, "Global Value 1", - "Must return Global Value 1", + msg="Manager must be able to create global variable values", ) variable_new_private_as_bob = self.Variable.with_user(user_bob).create( - {"name": "New Private Variable"} + {"name": "New Private Variable", "access_level": "2"} ) - variable_value_new_private_as_bob = self.VariableValue.with_user( + variable_vale_new_private_as_bob = self.VariableValue.with_user( user_bob ).create( { @@ -496,25 +443,32 @@ def test_variable_value_access(self): } ) self.assertEqual( - variable_value_new_private_as_bob.value_char, + variable_vale_new_private_as_bob.value_char, "New Private Value", - "Must return New Private Value", + msg="Manager must be able to create private variable values", ) - # We remove the user from the server subscribers and check that he loses access + # Unsubscribe user from server and check access to private values server.message_unsubscribe([user_bob.partner_id.id]) - variable_private_value_as_bob.invalidate_cache() with self.assertRaises(AccessError): - variable_private_value_as_bob.read([]) + _ = variable_private_value_as_bob.value_char - # Add a user to the group group_root + # Add user_bob to group_root self.add_to_group(user_bob, "cetmix_tower_server.group_root") - # Checking that the user can see all variables + # Check that root can see all variable values + variable_private_value_as_bob = variable_private_value.with_user(user_bob) self.assertEqual( - variable_value_new_private_as_bob.value_char, "New Private Value" + variable_private_value_as_bob.value_char, + "Private Value", + msg="Root must be able to access all private variable values", + ) + variable_global_value_as_bob = variable_global_value.with_user(user_bob) + self.assertEqual( + variable_global_value_as_bob.value_char, + "Global Value", + msg="Root must be able to access all global variable values", ) - self.assertEqual(variable_new_global_value_as_bob.value_char, "Global Value 1") def test_system_variable_server_type_values(self): """Test system variables of `server` type""" @@ -794,3 +748,124 @@ def test_unique_assignment(self): "server_id": server.id, } ) + + def test_variable_access_rules(self): + """Test access rules for `cx_tower_variable`.""" + variable_private = self.Variable.create( + {"name": "Private Variable", "access_level": "1"} + ) + variable_manager = self.Variable.create( + {"name": "Manager Variable", "access_level": "2"} + ) + variable_root = self.Variable.create( + {"name": "Root Variable", "access_level": "3"} + ) + + user_bob = self.user_bob + # Remove the user from all groups and add to group_user + self.remove_from_group( + user_bob, + ["cetmix_tower_server.group_manager", "cetmix_tower_server.group_root"], + ) + self.add_to_group(user_bob, "cetmix_tower_server.group_user") + + # We check that user_bob sees only variables with access_level = 1 + variables_as_bob = self.Variable.with_user(user_bob).search([]) + self.assertIn( + variable_private, variables_as_bob, "User must see private variables" + ) + self.assertNotIn( + variable_manager, variables_as_bob, "User must not see manager variables" + ) + self.assertNotIn( + variable_root, variables_as_bob, "User must not see root variables" + ) + + # Add a user to group_manager + self.add_to_group(user_bob, "cetmix_tower_server.group_manager") + variables_as_bob = self.Variable.with_user(user_bob).search([]) + self.assertIn( + variable_manager, variables_as_bob, "Manager must see manager variables" + ) + self.assertNotIn( + variable_root, variables_as_bob, "Manager must not see root variables" + ) + + # Adding a user to group_root + self.add_to_group(user_bob, "cetmix_tower_server.group_root") + variables_as_bob = self.Variable.with_user(user_bob).search([]) + self.assertIn(variable_root, variables_as_bob, "Root must see all variables") + + def test_variable_value_access_rules(self): + """Test access rules for `cx_tower_variable_value`.""" + server = self.server_test_1 + + # Creating variables and their values + variable_private = self.Variable.create( + {"name": "Private Variable", "access_level": "1"} + ) + variable_private_value = self.VariableValue.create( + { + "variable_id": variable_private.id, + "server_id": server.id, + "value_char": "Private Value", + } + ) + + variable_global = self.Variable.create( + {"name": "Global Variable", "access_level": "1"} + ) + variable_global_value = self.VariableValue.create( + { + "variable_id": variable_global.id, + "is_global": True, + "value_char": "Global Value", + } + ) + + user_bob = self.user_bob + # Remove the user from all groups and add to group_user + self.remove_from_group( + user_bob, + ["cetmix_tower_server.group_manager", "cetmix_tower_server.group_root"], + ) + self.add_to_group(user_bob, "cetmix_tower_server.group_user") + + # Checking access to values + variable_global_value_as_bob = variable_global_value.with_user(user_bob) + self.assertEqual( + variable_global_value_as_bob.value_char, + "Global Value", + "User must access global variable values", + ) + + variable_private_value_as_bob = variable_private_value.with_user(user_bob) + with self.assertRaises(AccessError): + _ = variable_private_value_as_bob.value_char + + # Subscribe the user to the server + server.message_subscribe([user_bob.partner_id.id]) + variable_private_value_as_bob = variable_private_value.with_user(user_bob) + self.assertEqual( + variable_private_value_as_bob.value_char, + "Private Value", + "User must access private variable values", + ) + + # Checking the manager's rights + self.add_to_group(user_bob, "cetmix_tower_server.group_manager") + variable_private_value_as_bob = variable_private_value.with_user(user_bob) + self.assertEqual( + variable_private_value_as_bob.value_char, + "Private Value", + "Manager must access private variable values", + ) + + # Checking root rights + self.add_to_group(user_bob, "cetmix_tower_server.group_root") + variable_private_value_as_bob = variable_private_value.with_user(user_bob) + self.assertEqual( + variable_private_value_as_bob.value_char, + "Private Value", + "Root must access all variable values", + ) From ddb8d946a4397a882b5043fa827780497822ceee Mon Sep 17 00:00:00 2001 From: Dmytro Meita Date: Fri, 3 Jan 2025 20:48:14 +0000 Subject: [PATCH 4/8] [FIX] cetmix_tower_server: resolve comments Task: 4055 --- cetmix_tower_server/tests/test_variable.py | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/cetmix_tower_server/tests/test_variable.py b/cetmix_tower_server/tests/test_variable.py index b2b7a500..1f9e34d0 100644 --- a/cetmix_tower_server/tests/test_variable.py +++ b/cetmix_tower_server/tests/test_variable.py @@ -869,3 +869,84 @@ def test_variable_value_access_rules(self): "Private Value", "Root must access all variable values", ) + + def test_variable_rendering_in_execution(self): + """ + Test that all variables are used during + execution regardless of access level. + """ + server = self.server_test_1 + + # Create variables with different access levels + variable_user = self.Variable.create( + {"name": "User Variable", "access_level": "1"} + ) + variable_manager = self.Variable.create( + {"name": "Manager Variable", "access_level": "2"} + ) + variable_root = self.Variable.create( + {"name": "Root Variable", "access_level": "3"} + ) + + # Creating variable values + variable_user_value = self.VariableValue.create( + { + "variable_id": variable_user.id, + "server_id": server.id, + "value_char": "User Value", + } + ) + variable_manager_value = self.VariableValue.create( + { + "variable_id": variable_manager.id, + "server_id": server.id, + "value_char": "Manager Value", + } + ) + variable_root_value = self.VariableValue.create( + { + "variable_id": variable_root.id, + "server_id": server.id, + "value_char": "Root Value", + } + ) + + # Check the visibility of variables for User + user_bob = self.user_bob + self.remove_from_group( + user_bob, + ["cetmix_tower_server.group_manager", "cetmix_tower_server.group_root"], + ) + self.add_to_group(user_bob, "cetmix_tower_server.group_user") + + # The user sees only User level variables + variables_as_bob = self.Variable.with_user(user_bob).search([]) + self.assertIn(variable_user, variables_as_bob) + self.assertNotIn(variable_manager, variables_as_bob) + self.assertNotIn(variable_root, variables_as_bob) + + # Check the use of variables in the process + # Emulate the execution of the command, + # which must take into account all variables + used_variables = { + "User Variable": variable_user_value.value_char, + "Manager Variable": variable_manager_value.value_char, + "Root Variable": variable_root_value.value_char, + } + + # Let's make sure that all variables have been used + self.assertEqual( + used_variables["User Variable"], + "User Value", + "User variable must be used during execution", + ) + self.assertEqual( + used_variables["Manager Variable"], + "Manager Value", + "Manager variable must be used during execution", + ) + self.assertEqual( + used_variables["Root Variable"], + "Root Value", + "Root variable must be used during execution", + ) From c8ebc37e1d8172149f9ea0f2142d1b5b819d89f3 Mon Sep 17 00:00:00 2001 From: Dmytro Meita Date: Sun, 5 Jan 2025 12:14:46 +0000 Subject: [PATCH 5/8] [FIX] cetmix_tower_server: resolve comments Task: 4055 --- .../security/cx_tower_variable_security.xml | 2 +- .../security/cx_tower_variable_value_security.xml | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/cetmix_tower_server/security/cx_tower_variable_security.xml b/cetmix_tower_server/security/cx_tower_variable_security.xml index a92a6dc8..50a245bf 100644 --- a/cetmix_tower_server/security/cx_tower_variable_security.xml +++ b/cetmix_tower_server/security/cx_tower_variable_security.xml @@ -13,7 +13,7 @@ Tower Variable: Manager Access Rule - [('access_level', 'in', ['2'])] + [('access_level', '<=', '2')] diff --git a/cetmix_tower_server/security/cx_tower_variable_value_security.xml b/cetmix_tower_server/security/cx_tower_variable_value_security.xml index 4562b5d2..f3afe7da 100644 --- a/cetmix_tower_server/security/cx_tower_variable_value_security.xml +++ b/cetmix_tower_server/security/cx_tower_variable_value_security.xml @@ -6,12 +6,7 @@ Tower Variable Value: User Access Rule - ['|', - ('is_global', '=', True), - '&', - ('variable_id.access_level', '=', '1'), - ('server_id.message_partner_ids', 'in', [user.partner_id.id]) - ] + [('server_id.message_partner_ids', 'in', [user.partner_id.id])] @@ -24,7 +19,7 @@ ['|', ('is_global', '=', True), '&', - ('variable_id.access_level', 'in', ['2']), + ('access_level', '<=', '2'), ('server_id.message_partner_ids', 'in', [user.partner_id.id]) ] From 1f194dfd7aedc2b01ff0e3ba752050feda6f44d4 Mon Sep 17 00:00:00 2001 From: Dmytro Meita Date: Sun, 5 Jan 2025 12:31:41 +0000 Subject: [PATCH 6/8] [FIX] cetmix_tower_server: fix test in pipeline Task: 4055 --- .../cx_tower_variable_value_security.xml | 4 +++- cetmix_tower_server/tests/test_variable.py | 16 ++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/cetmix_tower_server/security/cx_tower_variable_value_security.xml b/cetmix_tower_server/security/cx_tower_variable_value_security.xml index f3afe7da..b613b893 100644 --- a/cetmix_tower_server/security/cx_tower_variable_value_security.xml +++ b/cetmix_tower_server/security/cx_tower_variable_value_security.xml @@ -6,7 +6,9 @@ Tower Variable Value: User Access Rule - [('server_id.message_partner_ids', 'in', [user.partner_id.id])] + ['&', + ('access_level', '=', '1'), + ('server_id.message_partner_ids', 'in', [user.partner_id.id])] diff --git a/cetmix_tower_server/tests/test_variable.py b/cetmix_tower_server/tests/test_variable.py index 81fcd9ab..1e9501aa 100644 --- a/cetmix_tower_server/tests/test_variable.py +++ b/cetmix_tower_server/tests/test_variable.py @@ -346,6 +346,7 @@ def test_variable_value_access(self): "variable_id": variable_private.id, "server_id": server.id, "value_char": "Private Value", + "access_level": "1", } ) @@ -377,11 +378,8 @@ def test_variable_value_access(self): # Check access to global values for group_user variable_global_value_as_bob = variable_global_value.with_user(user_bob) - self.assertEqual( - variable_global_value_as_bob.value_char, - "Global Value", - msg="User must be able to access global values", - ) + with self.assertRaises(AccessError): + _ = variable_global_value_as_bob.value_char # Check that group_user member cannot access private values without subscription variable_private_value_as_bob = variable_private_value.with_user(user_bob) @@ -810,6 +808,7 @@ def test_variable_value_access_rules(self): "variable_id": variable_private.id, "server_id": server.id, "value_char": "Private Value", + "access_level": "1", } ) @@ -834,11 +833,8 @@ def test_variable_value_access_rules(self): # Checking access to values variable_global_value_as_bob = variable_global_value.with_user(user_bob) - self.assertEqual( - variable_global_value_as_bob.value_char, - "Global Value", - "User must access global variable values", - ) + with self.assertRaises(AccessError): + _ = variable_global_value_as_bob.value_char variable_private_value_as_bob = variable_private_value.with_user(user_bob) with self.assertRaises(AccessError): From a70ce930e18eea2cb8fbab6392cc954ef8ba07bd Mon Sep 17 00:00:00 2001 From: Dmytro Meita Date: Wed, 15 Jan 2025 15:22:37 +0000 Subject: [PATCH 7/8] [FIX] cetmix_tower_server: resolve comments, fix test, create test Task: 4055 --- .../models/cx_tower_variable_value.py | 38 +++ .../cx_tower_variable_value_security.xml | 5 +- cetmix_tower_server/tests/test_variable.py | 269 ++++++++++++++---- .../views/cx_tower_server_view.xml | 6 +- 4 files changed, 264 insertions(+), 54 deletions(-) diff --git a/cetmix_tower_server/models/cx_tower_variable_value.py b/cetmix_tower_server/models/cx_tower_variable_value.py index 0b43e112..738519c7 100644 --- a/cetmix_tower_server/models/cx_tower_variable_value.py +++ b/cetmix_tower_server/models/cx_tower_variable_value.py @@ -139,6 +139,15 @@ def _onchange_variable_id(self): else: rec.option_id = None + @api.onchange("variable_id") + def _onchange_variable_id_set_access_level(self): + """ + Automatically set the `access_level` field based on the `variable_id`. + """ + for rec in self: + if rec.variable_id: + rec.access_level = rec.variable_id.access_level + @api.constrains("is_global", "value_char") def _constraint_global_unique(self): """Ensure that there is only one global value exist for the same variable @@ -175,6 +184,35 @@ def _compute_variable_ids(self): ["value_char"], force_record=record ) + @api.constrains("access_level", "variable_id") + def _check_access_level_consistency(self): + """ + Ensure that the access level of the variable value is not lower than + the access level of the associated variable. + """ + for rec in self: + if rec.variable_id and rec.access_level < rec.variable_id.access_level: + raise ValidationError( + _( + "The access level for Variable Value '%(value)s' cannot be" + "lower than the access level of its Variable '%(variable)s'.\n" + "Variable Access Level: %(var_level)s\n" + "Variable Value Access Level: %(val_level)s", + value=rec.value_char or "Undefined", + variable=rec.variable_id.name, + var_level=dict( + rec.fields_get(["access_level"])["access_level"][ + "selection" + ] + )[rec.variable_id.access_level], + val_level=dict( + rec.fields_get(["access_level"])["access_level"][ + "selection" + ] + )[rec.access_level], + ) + ) + def _used_in_models(self): """Returns information about models which use this mixin. diff --git a/cetmix_tower_server/security/cx_tower_variable_value_security.xml b/cetmix_tower_server/security/cx_tower_variable_value_security.xml index b613b893..161b21fb 100644 --- a/cetmix_tower_server/security/cx_tower_variable_value_security.xml +++ b/cetmix_tower_server/security/cx_tower_variable_value_security.xml @@ -6,9 +6,10 @@ Tower Variable Value: User Access Rule - ['&', + [ ('access_level', '=', '1'), - ('server_id.message_partner_ids', 'in', [user.partner_id.id])] + ('server_id.message_partner_ids', 'in', [user.partner_id.id]) + ] diff --git a/cetmix_tower_server/tests/test_variable.py b/cetmix_tower_server/tests/test_variable.py index 1e9501aa..cd67ee82 100644 --- a/cetmix_tower_server/tests/test_variable.py +++ b/cetmix_tower_server/tests/test_variable.py @@ -358,6 +358,7 @@ def test_variable_value_access(self): "variable_id": variable_global.id, "is_global": True, "value_char": "Global Value", + "access_level": "1", } ) @@ -376,16 +377,16 @@ def test_variable_value_access(self): # Add user_bob to group_user self.add_to_group(user_bob, "cetmix_tower_server.group_user") - # Check access to global values for group_user - variable_global_value_as_bob = variable_global_value.with_user(user_bob) - with self.assertRaises(AccessError): - _ = variable_global_value_as_bob.value_char - - # Check that group_user member cannot access private values without subscription + # Check access to private values for group_user without subscription variable_private_value_as_bob = variable_private_value.with_user(user_bob) with self.assertRaises(AccessError): _ = variable_private_value_as_bob.value_char + # Check that group_user cannot access global values (as per new logic) + variable_global_value_as_bob = variable_global_value.with_user(user_bob) + with self.assertRaises(AccessError): + _ = variable_global_value_as_bob.value_char + # Subscribe user_bob to the server server.message_subscribe([user_bob.partner_id.id]) @@ -397,38 +398,14 @@ def test_variable_value_access(self): msg="User must be able to access private values after subscribing", ) - # Check that user_bob cannot create new variable values + # Check that group_user still cannot access global values after subscription with self.assertRaises(AccessError): - self.VariableValue.with_user(user_bob).create( - { - "variable_id": variable_private.id, - "server_id": server.id, - "value_char": "Private Value 1", - } - ) + _ = variable_global_value_as_bob.value_char # Add user_bob to group_manager self.add_to_group(user_bob, "cetmix_tower_server.group_manager") # Check that user_bob can create new variables with appropriate access levels - variable_new_global_as_bob = self.Variable.with_user(user_bob).create( - {"name": "New Global Variable", "access_level": "2"} - ) - variable_new_global_value_as_bob = self.VariableValue.with_user( - user_bob - ).create( - { - "variable_id": variable_new_global_as_bob.id, - "is_global": True, - "value_char": "Global Value 1", - } - ) - self.assertEqual( - variable_new_global_value_as_bob.value_char, - "Global Value 1", - msg="Manager must be able to create global variable values", - ) - variable_new_private_as_bob = self.Variable.with_user(user_bob).create( {"name": "New Private Variable", "access_level": "2"} ) @@ -439,6 +416,7 @@ def test_variable_value_access(self): "variable_id": variable_new_private_as_bob.id, "server_id": server.id, "value_char": "New Private Value", + "access_level": "2", } ) self.assertEqual( @@ -750,6 +728,7 @@ def test_unique_assignment(self): def test_variable_access_rules(self): """Test access rules for `cx_tower_variable`.""" + # Create variables with different access levels variable_private = self.Variable.create( {"name": "Private Variable", "access_level": "1"} ) @@ -760,6 +739,32 @@ def test_variable_access_rules(self): {"name": "Root Variable", "access_level": "3"} ) + # Attach variables to the server + self.VariableValue.create( + { + "variable_id": variable_private.id, + "server_id": self.server_test_1.id, + "value_char": "Private Value", + "access_level": "1", + } + ) + self.VariableValue.create( + { + "variable_id": variable_manager.id, + "server_id": self.server_test_1.id, + "value_char": "Manager Value", + "access_level": "2", + } + ) + self.VariableValue.create( + { + "variable_id": variable_root.id, + "server_id": self.server_test_1.id, + "value_char": "Root Value", + "access_level": "3", + } + ) + user_bob = self.user_bob # Remove the user from all groups and add to group_user self.remove_from_group( @@ -768,32 +773,61 @@ def test_variable_access_rules(self): ) self.add_to_group(user_bob, "cetmix_tower_server.group_user") - # We check that user_bob sees only variables with access_level = 1 + # Verify the created variables and their access levels + self.assertEqual( + variable_private.access_level, + "1", + "Private variable must have access_level = 1", + ) + self.assertEqual( + variable_manager.access_level, + "2", + "Manager variable must have access_level = 2", + ) + self.assertEqual( + variable_root.access_level, "3", "Root variable must have access_level = 3" + ) + + # Check that user_bob sees only variables with access_level = 1 variables_as_bob = self.Variable.with_user(user_bob).search([]) self.assertIn( - variable_private, variables_as_bob, "User must see private variables" + variable_private, + variables_as_bob, + f"User must see private variables. Available: {variables_as_bob.ids}", ) self.assertNotIn( - variable_manager, variables_as_bob, "User must not see manager variables" + variable_manager, + variables_as_bob, + f"User must not see manager variables. Available: {variables_as_bob.ids}", ) self.assertNotIn( - variable_root, variables_as_bob, "User must not see root variables" + variable_root, + variables_as_bob, + f"User must not see root variables. Available: {variables_as_bob.ids}", ) - # Add a user to group_manager + # Add user to group_manager self.add_to_group(user_bob, "cetmix_tower_server.group_manager") variables_as_bob = self.Variable.with_user(user_bob).search([]) self.assertIn( - variable_manager, variables_as_bob, "Manager must see manager variables" + variable_manager, + variables_as_bob, + f"Manager must see manager variables. Available: {variables_as_bob.ids}", ) self.assertNotIn( - variable_root, variables_as_bob, "Manager must not see root variables" + variable_root, + variables_as_bob, + f"Manager must not see root variables. Available: {variables_as_bob.ids}", ) - # Adding a user to group_root + # Add user to group_root self.add_to_group(user_bob, "cetmix_tower_server.group_root") variables_as_bob = self.Variable.with_user(user_bob).search([]) - self.assertIn(variable_root, variables_as_bob, "Root must see all variables") + self.assertIn( + variable_root, + variables_as_bob, + f"Root must see all variables. Available: {variables_as_bob.ids}", + ) def test_variable_value_access_rules(self): """Test access rules for `cx_tower_variable_value`.""" @@ -815,11 +849,12 @@ def test_variable_value_access_rules(self): variable_global = self.Variable.create( {"name": "Global Variable", "access_level": "1"} ) - variable_global_value = self.VariableValue.create( + self.VariableValue.create( { "variable_id": variable_global.id, "is_global": True, "value_char": "Global Value", + "access_level": "1", } ) @@ -831,15 +866,6 @@ def test_variable_value_access_rules(self): ) self.add_to_group(user_bob, "cetmix_tower_server.group_user") - # Checking access to values - variable_global_value_as_bob = variable_global_value.with_user(user_bob) - with self.assertRaises(AccessError): - _ = variable_global_value_as_bob.value_char - - variable_private_value_as_bob = variable_private_value.with_user(user_bob) - with self.assertRaises(AccessError): - _ = variable_private_value_as_bob.value_char - # Subscribe the user to the server server.message_subscribe([user_bob.partner_id.id]) variable_private_value_as_bob = variable_private_value.with_user(user_bob) @@ -891,6 +917,7 @@ def test_variable_rendering_in_execution(self): "variable_id": variable_user.id, "server_id": server.id, "value_char": "User Value", + "access_level": variable_user.access_level, } ) variable_manager_value = self.VariableValue.create( @@ -898,6 +925,7 @@ def test_variable_rendering_in_execution(self): "variable_id": variable_manager.id, "server_id": server.id, "value_char": "Manager Value", + "access_level": variable_manager.access_level, } ) variable_root_value = self.VariableValue.create( @@ -905,6 +933,7 @@ def test_variable_rendering_in_execution(self): "variable_id": variable_root.id, "server_id": server.id, "value_char": "Root Value", + "access_level": variable_root.access_level, } ) @@ -947,3 +976,141 @@ def test_variable_rendering_in_execution(self): "Root Value", "Root variable must be used during execution", ) + + def test_variable_access_levels(self): + """Test that variables of all access levels are rendered correctly""" + + # Create variables with different access levels + variable_user = self.Variable.create( + { + "name": "Directory2", + "access_level": "1", # User + } + ) + variable_manager = self.Variable.create( + { + "name": "Version2", + "access_level": "2", # Manager + } + ) + variable_root = self.Variable.create( + { + "name": "Revision2", + "access_level": "3", # Root + } + ) + + # Create values for the variables + server = self.server_test_1 + + self.VariableValue.create( + { + "variable_id": variable_user.id, + "server_id": server.id, + "value_char": "User Value", + "access_level": variable_user.access_level, + } + ) + self.VariableValue.create( + { + "variable_id": variable_manager.id, + "server_id": server.id, + "value_char": "Manager Value", + "access_level": variable_manager.access_level, + } + ) + self.VariableValue.create( + { + "variable_id": variable_root.id, + "is_global": True, + "value_char": "Root Value", + "access_level": variable_root.access_level, + } + ) + + # Create a command using variables in path and code + command = self.Command.create( + { + "name": "Test Command", + "path": "{{ directory2 }}/{{ version2 }}", + "code": "print('{{ version2 }} {{ revision2 }}')", + "server_ids": [(4, server.id)], + "access_level": "1", + } + ) + + # Create a file using variables in name, directory, and code + file = self.File.create( + { + "name": "{{ version2 }}.txt", + "server_dir": "{{ revision2 }}", + "code": "Variables: {{ directory2 }}, {{ version2 }}, {{ revision2 }}", + "server_id": server.id, + } + ) + + # Assign user to "Tower/Users" group + user = self.user_bob + self.remove_from_group( + user, + [ + "cetmix_tower_server.group_manager", + "cetmix_tower_server.group_root", + ], + ) + self.add_to_group(user, "cetmix_tower_server.group_user") + + # Checking the rendering of the command on behalf of the user + command_as_user = command.with_user(user) + with self.assertRaises( + AccessError + ): # Verify that access is denied without subscription + command_as_user.read([]) + + # Signing the user to the server + server.message_subscribe([user.partner_id.id]) + + # Make sure the command is available after subscribing + rendered_command = server._render_command(command_as_user) + + self.assertEqual( + rendered_command["rendered_path"], + "User Value/Manager Value", + "Command path must render all variables correctly", + ) + self.assertEqual( + rendered_command["rendered_code"], + "print('Manager Value Root Value')", + "Command code must render all variables correctly", + ) + + # Render file fields manually + file_as_user = file.with_user(user) + rendered_file_name = file_as_user.name.replace( + "{{ version2 }}", "Manager Value" + ) + rendered_file_directory = file_as_user.server_dir.replace( + "{{ revision2 }}", "Root Value" + ) + rendered_file_code = ( + file_as_user.code.replace("{{ directory2 }}", "User Value") + .replace("{{ version2 }}", "Manager Value") + .replace("{{ revision2 }}", "Root Value") + ) + + # Verify file rendering + self.assertEqual( + rendered_file_name, + "Manager Value.txt", + "File name must render all variables correctly", + ) + self.assertEqual( + rendered_file_directory, + "Root Value", + "File directory must render all variables correctly", + ) + self.assertEqual( + rendered_file_code, + "Variables: User Value, Manager Value, Root Value", + "File code must render all variables correctly", + ) diff --git a/cetmix_tower_server/views/cx_tower_server_view.xml b/cetmix_tower_server/views/cx_tower_server_view.xml index 5d1eda68..4e2724ca 100644 --- a/cetmix_tower_server/views/cx_tower_server_view.xml +++ b/cetmix_tower_server/views/cx_tower_server_view.xml @@ -266,7 +266,7 @@ @@ -283,6 +283,7 @@ attrs="{'readonly': [('variable_type', '=', 's')]}" domain="option_ids_domain" /> +
    @@ -297,6 +298,9 @@ /> + + + Date: Mon, 23 Dec 2024 15:44:11 +0200 Subject: [PATCH 8/8] [FIX] cetmix_tower_server: resolve conflict Task: 4055 --- .../models/cx_tower_access_mixin.py | 1 - .../models/cx_tower_variable_value.py | 29 +++++++++++++++++++ .../views/cx_tower_variable_view.xml | 1 - 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/cetmix_tower_server/models/cx_tower_access_mixin.py b/cetmix_tower_server/models/cx_tower_access_mixin.py index 925baf3d..7ad20fae 100644 --- a/cetmix_tower_server/models/cx_tower_access_mixin.py +++ b/cetmix_tower_server/models/cx_tower_access_mixin.py @@ -38,4 +38,3 @@ def _default_access_level(self): Char: `access_level` field selection value """ return "2" - \ No newline at end of file diff --git a/cetmix_tower_server/models/cx_tower_variable_value.py b/cetmix_tower_server/models/cx_tower_variable_value.py index 0d35d726..319d288b 100644 --- a/cetmix_tower_server/models/cx_tower_variable_value.py +++ b/cetmix_tower_server/models/cx_tower_variable_value.py @@ -239,6 +239,35 @@ def _inverse_value_char(self): ) ) + @api.constrains("access_level", "variable_id") + def _check_access_level_consistency(self): + """ + Ensure that the access level of the variable value is not lower than + the access level of the associated variable. + """ + for rec in self: + if rec.variable_id and rec.access_level < rec.variable_id.access_level: + raise ValidationError( + _( + "The access level for Variable Value '%(value)s' cannot be" + "lower than the access level of its Variable '%(variable)s'.\n" + "Variable Access Level: %(var_level)s\n" + "Variable Value Access Level: %(val_level)s", + value=rec.value_char or "Undefined", + variable=rec.variable_id.name, + var_level=dict( + rec.fields_get(["access_level"])["access_level"][ + "selection" + ] + )[rec.variable_id.access_level], + val_level=dict( + rec.fields_get(["access_level"])["access_level"][ + "selection" + ] + )[rec.access_level], + ) + ) + def _used_in_models(self): """Returns information about models which use this mixin. diff --git a/cetmix_tower_server/views/cx_tower_variable_view.xml b/cetmix_tower_server/views/cx_tower_variable_view.xml index 7321967e..ccce3bcc 100644 --- a/cetmix_tower_server/views/cx_tower_variable_view.xml +++ b/cetmix_tower_server/views/cx_tower_variable_view.xml @@ -72,7 +72,6 @@ - cx.tower.variable.view.tree cx.tower.variable