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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions modules/odf_core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-

from . import models
15 changes: 15 additions & 0 deletions modules/odf_core/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
'name': 'Odoo Data Flow Core',
'version': '18.0.1.0.0',
'author': 'Odoo Data Flow',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/odf_connection_views.xml',
'views/odf_flow_project_views.xml',
'views/menus.xml',
'data/scheduled_actions.xml',
],
'installable': True,
'application': True,
}
15 changes: 15 additions & 0 deletions modules/odf_core/data/scheduled_actions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="ir_cron_odf_flow_project_run" model="ir.cron">
<field name="name">ODF: Run Flow Projects</field>
<field name="model_id" ref="model_odf_flow_project"/>
<field name="state">code</field>
<field name="code">model._run_projects()</field>
<field name="user_id" ref="base.user_root"/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The scheduled action is configured to run as the root user (base.user_root), which has maximum privileges. This is a security risk. It's best practice to run cron jobs with a dedicated user that has only the minimum required permissions to perform its tasks.

<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
</record>
</data>
</odoo>
4 changes: 4 additions & 0 deletions modules/odf_core/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-

from . import odf_connection
from . import odf_flow_project
33 changes: 33 additions & 0 deletions modules/odf_core/models/odf_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-

from odoo import models, fields, api
import logging

_logger = logging.getLogger(__name__)

class OdfConnection(models.Model):
_name = 'odf.connection'
_description = 'Odoo Data Flow Connection'

name = fields.Char(required=True)
host = fields.Char(required=True)
port = fields.Integer(required=True, default=5432)
dbname = fields.Char(required=True)
user = fields.Char(required=True)
password = fields.Char(password=True)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Storing passwords in plaintext in the database is a major security vulnerability. The password=True attribute only masks the password in the user interface but does not provide any encryption for storage. Anyone with database access can read these passwords.

You should use a secure method for storing credentials. Consider using Odoo's ir.config_parameter for sensitive data, which can be protected, or integrate with a secrets management tool like HashiCorp Vault. If you must store it in this model, the password should be encrypted before being stored and decrypted when used.


def test_connection(self):
# This is a placeholder for the actual connection test logic.
# For now, it just logs a message.
_logger.info(f"Testing connection for {self.name}")
# In a real implementation, you would use a library like psycopg2
# to attempt a connection and provide feedback to the user.
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Connection Test',
'message': 'Connection test functionality is not yet implemented.',
'sticky': False,
}
}
62 changes: 62 additions & 0 deletions modules/odf_core/models/odf_flow_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-

from odoo import models, fields, api
import logging
import traceback

_logger = logging.getLogger(__name__)

try:
from odoo_data_flow.workflow_runner import run_workflow
except ImportError:
run_workflow = None
_logger.warning("The 'odoo-data-flow' library is not installed. Please install it to use ODF Core features.")


class OdfFlowProject(models.Model):
_name = 'odf.flow.project'
_description = 'Odoo Data Flow Project'

name = fields.Char(required=True)
active = fields.Boolean(default=True)
source_connection_id = fields.Many2one('odf.connection', required=True)
destination_connection_id = fields.Many2one('odf.connection', required=True)
flow_file_path = fields.Char(string="Flow File Path", required=True)
status = fields.Selection([
('new', 'New'),
('running', 'Running'),
('done', 'Done'),
('failed', 'Failed'),
], default='new', copy=False, tracking=True)

@api.model
def _run_projects(self):
"""
This method is called by a scheduled action to run all active data flow projects.
"""
projects = self.search([('active', '=', True), ('status', '!=', 'running')])
for project in projects:
project.write({'status': 'running'})
self.env.cr.commit()

try:
if not run_workflow:
raise ImportError("The 'odoo-data-flow' library is not available.")

if not project.flow_file_path:
raise ValueError(f"Flow file path is not set for project '{project.name}'")

# The odoo-data-flow library expects a path to a YAML config file.
run_workflow(project.flow_file_path)

project.write({'status': 'done'})
self.env.cr.commit()

except Exception:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Catching a broad Exception can hide unexpected errors and make debugging more difficult. It's better practice to catch more specific exceptions that you anticipate, such as ImportError and ValueError which you raise explicitly. If run_workflow can raise other known exceptions, they should also be caught specifically. This makes the error handling more robust and the code's intent clearer.

_logger.error(
"Failed to run data flow project '%s'.\n%s",
project.name,
traceback.format_exc()
)
project.write({'status': 'failed'})
self.env.cr.commit()
Comment on lines +38 to +62
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The use of self.env.cr.commit() inside a loop is highly discouraged in Odoo as it breaks transaction atomicity. This can lead to data inconsistencies that are hard to debug. For instance, if the server crashes after a commit, the project's state could be left as 'running' incorrectly. Odoo cron jobs run in a single transaction, ensuring that all changes are rolled back if any part of the job fails, which maintains data integrity.

If you need to process each project in an isolated transaction, the recommended approach in Odoo is to use a job queue module (like queue_job from the OCA).

I recommend refactoring this method to remove the manual commits and wrap the logic for a single project in a try...except block. This ensures that the entire cron run is atomic.

        for project in projects:
            try:
                project.write({'status': 'running'})

                if not run_workflow:
                    raise ImportError("The 'odoo-data-flow' library is not available.")

                if not project.flow_file_path:
                    raise ValueError(f"Flow file path is not set for project '{project.name}'")

                # The odoo-data-flow library expects a path to a YAML config file.
                run_workflow(project.flow_file_path)

                project.write({'status': 'done'})
            except Exception:
                _logger.error(
                    "Failed to run data flow project '%s'.\n%s",
                    project.name,
                    traceback.format_exc()
                )
                project.write({'status': 'failed'})

3 changes: 3 additions & 0 deletions modules/odf_core/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_odf_connection,odf.connection access,model_odf_connection,base.group_user,1,1,1,1
access_odf_flow_project,odf.flow.project access,model_odf_flow_project,base.group_user,1,1,1,1
Comment on lines +2 to +3
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current access control rules grant all internal users (base.group_user) full create, read, update, and delete (CRUD) permissions on odf.connection and odf.flow.project models. This is likely too permissive. Connection details, which may contain sensitive information, and the ability to run data flows should be restricted to authorized users.

I suggest restricting access to a more privileged group, for example, system administrators (base.group_system). For more granular control, you could create a dedicated security group (e.g., group_odf_manager) and assign access to that group instead.

access_odf_connection,odf.connection access,model_odf_connection,base.group_system,1,1,1,1
access_odf_flow_project,odf.flow.project access,model_odf_flow_project,base.group_system,1,1,1,1

31 changes: 31 additions & 0 deletions modules/odf_core/views/menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Main Menu -->
<menuitem
id="menu_odf_root"
name="Data Flow"
sequence="10"/>

<!-- Projects Menu -->
<menuitem
id="menu_odf_projects"
name="Projects"
parent="menu_odf_root"
action="action_odf_flow_project"
sequence="10"/>

<!-- Configuration Menu -->
<menuitem
id="menu_odf_configuration"
name="Configuration"
parent="menu_odf_root"
sequence="100"/>

<!-- Connections Menu -->
<menuitem
id="menu_odf_connections"
name="Connections"
parent="menu_odf_configuration"
action="action_odf_connection"
sequence="10"/>
</odoo>
46 changes: 46 additions & 0 deletions modules/odf_core/views/odf_connection_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Form View -->
<record id="view_odf_connection_form" model="ir.ui.view">
<field name="name">odf.connection.form</field>
<field name="model">odf.connection</field>
<field name="arch" type="xml">
<form string="Connection">
<header>
<button name="test_connection" type="object" string="Test Connection" class="oe_highlight"/>
</header>
<sheet>
<group>
<field name="name"/>
<field name="host"/>
<field name="port"/>
<field name="dbname"/>
<field name="user"/>
<field name="password"/>
</group>
</sheet>
</form>
</field>
</record>

<!-- Tree View -->
<record id="view_odf_connection_tree" model="ir.ui.view">
<field name="name">odf.connection.tree</field>
<field name="model">odf.connection</field>
<field name="arch" type="xml">
<tree string="Connections">
<field name="name"/>
<field name="host"/>
<field name="dbname"/>
<field name="user"/>
</tree>
</field>
</record>

<!-- Action -->
<record id="action_odf_connection" model="ir.actions.act_window">
<field name="name">Connections</field>
<field name="res_model">odf.connection</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>
41 changes: 41 additions & 0 deletions modules/odf_core/views/odf_flow_project_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Form View -->
<record id="view_odf_flow_project_form" model="ir.ui.view">
<field name="name">odf.flow.project.form</field>
<field name="model">odf.flow.project</field>
<field name="arch" type="xml">
<form string="Flow Project">
<sheet>
<group>
<field name="name"/>
<field name="active"/>
<field name="source_connection_id"/>
<field name="destination_connection_id"/>
<field name="flow_file_path"/>
<field name="status"/>
</group>
</sheet>
</form>
</field>
</record>

<!-- Tree View -->
<record id="view_odf_flow_project_tree" model="ir.ui.view">
<field name="name">odf.flow.project.tree</field>
<field name="model">odf.flow.project</field>
<field name="arch" type="xml">
<tree string="Flow Projects">
<field name="name"/>
<field name="status"/>
</tree>
</field>
</record>

<!-- Action -->
<record id="action_odf_flow_project" model="ir.actions.act_window">
<field name="name">Flow Projects</field>
<field name="res_model">odf.flow.project</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>