From 8e96f9b29ea97697e01d58763cb76750481fdd39 Mon Sep 17 00:00:00 2001 From: William Braeckman Date: Thu, 13 Mar 2025 16:13:18 +0100 Subject: [PATCH] --wip-- [skip ci] --- runbot/controllers/__init__.py | 1 + runbot/controllers/public_api.py | 34 +++++++++++++++++++++++++++++ runbot/models/bundle.py | 2 +- runbot/models/project.py | 5 +++-- runbot/models/public_model_mixin.py | 4 +++- runbot/models/repo.py | 4 ++-- 6 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 runbot/controllers/public_api.py diff --git a/runbot/controllers/__init__.py b/runbot/controllers/__init__.py index 96d149ab..a3adbc61 100644 --- a/runbot/controllers/__init__.py +++ b/runbot/controllers/__init__.py @@ -3,3 +3,4 @@ from . import frontend from . import hook from . import badge +from . import public_api diff --git a/runbot/controllers/public_api.py b/runbot/controllers/public_api.py new file mode 100644 index 00000000..fe0a1cfc --- /dev/null +++ b/runbot/controllers/public_api.py @@ -0,0 +1,34 @@ +from werkzeug.exceptions import Forbidden, UnprocessableEntity, BadRequest + +from odoo.http import Controller, request, route + +from odoo.addons.runbot.models.public_model_mixin import PublicModelMixin + + +class PublicApi(Controller): + + @route('/runbot/api//read', auth='public', methods=['POST'], readonly=True, csrf=False) + def read(self, *, model: str): + REQUIRED_DATA_KEYS = {'schema', 'domain'} + + data = request.get_json_data() + if not isinstance(data, dict) or any(key not in data for key in REQUIRED_DATA_KEYS): + raise BadRequest('Invalid payload') + try: + Model = request.env[model] + except KeyError: + raise BadRequest('Unknown model') + if PublicModelMixin._name not in Model._inherit: + raise BadRequest('Unknown model') + schema = data['schema'] + try: + if not Model._verify_schema(schema) and\ + not request.env.user.has_group('runbot.group_runbot_admin'): + raise BadRequest('Invalid schema or trying to access private data.') + except (ValueError, AssertionError) as e: + raise BadRequest('Invalid schema') from e + try: + records = Model.search(data['domain']) + except ValueError as e: + raise BadRequest('Invalid domain') from e + return request.make_json_response(records._read_schema(schema)) diff --git a/runbot/models/bundle.py b/runbot/models/bundle.py index 9cab8493..e051a56f 100644 --- a/runbot/models/bundle.py +++ b/runbot/models/bundle.py @@ -11,7 +11,7 @@ from ..common import dt2time, s2human_long class Bundle(models.Model): _name = 'runbot.bundle' _description = "Bundle" - _inherit = 'mail.thread' + _inherit = ['mail.thread', 'runbot.public.model.mixin'] name = fields.Char('Bundle name', required=True, help="Name of the base branch") project_id = fields.Many2one('runbot.project', required=True, index=True) diff --git a/runbot/models/project.py b/runbot/models/project.py index 0235dcf9..ddfe2712 100644 --- a/runbot/models/project.py +++ b/runbot/models/project.py @@ -6,11 +6,12 @@ class Project(models.Model): _name = 'runbot.project' _description = 'Project' _order = 'sequence, id' + _inherit = ['runbot.public.model.mixin'] - name = fields.Char('Project name', required=True) + name = fields.Char('Project name', required=True, public=True) group_ids = fields.Many2many('res.groups', string='Required groups') keep_sticky_running = fields.Boolean('Keep last sticky builds running') - trigger_ids = fields.One2many('runbot.trigger', 'project_id', string='Triggers') + trigger_ids = fields.One2many('runbot.trigger', 'project_id', string='Triggers', public=True) dockerfile_id = fields.Many2one('runbot.dockerfile', index=True, help="Project Default Dockerfile") repo_ids = fields.One2many('runbot.repo', 'project_id', string='Repos') sequence = fields.Integer('Sequence') diff --git a/runbot/models/public_model_mixin.py b/runbot/models/public_model_mixin.py index 4e1d2c0f..e0dce41a 100644 --- a/runbot/models/public_model_mixin.py +++ b/runbot/models/public_model_mixin.py @@ -35,7 +35,9 @@ class PublicModelMixin(models.AbstractModel): def _valid_field_parameter(self, field: fields.Field, name: str): if field.type in SUPPORTED_FIELD_TYPES: return name in ( - 'public', # boolean, whether the field is readable through the public api + # boolean, whether the field is readable through the public api, + # public fields on record on which the user does not have access are not exposed. + 'public', ) or super()._valid_field_parameter(field, name) return super()._valid_field_parameter(field, name) diff --git a/runbot/models/repo.py b/runbot/models/repo.py index 2f68d7eb..1fdd2ceb 100644 --- a/runbot/models/repo.py +++ b/runbot/models/repo.py @@ -43,13 +43,13 @@ class Trigger(models.Model): """ _name = 'runbot.trigger' - _inherit = 'mail.thread' + _inherit = ['mail.thread', 'runbot.public.model.mixin'] _description = 'Triggers' _order = 'sequence, id' sequence = fields.Integer('Sequence') - name = fields.Char("Name") + name = fields.Char("Name", public=True) description = fields.Char("Description", help="Informative description") project_id = fields.Many2one('runbot.project', string="Project id", required=True) repo_ids = fields.Many2many('runbot.repo', relation='runbot_trigger_triggers', string="Triggers", domain="[('project_id', '=', project_id)]")