Files
one-os-dev-wayland-html5-co…/models/compositor_audit_log.py
T
1b-agent 93a5a0a221 Initial commit: one_os_compositor Odoo 20 addon — core OWL library + backend models + JWT auth
Includes:
- compositor.app, compositor.session, compositor.session.app, compositor.audit.log models
- /web/compositor/{token,apps,launch,audit} HTTP controllers with JWT (HS256)
- CompositorService OWL service (reusable via useService('compositor'))
- CompositorCanvas OWL component (reusable canvas wrapper)
- Security: compositor_group_user, compositor_group_manager groups + ir.access.csv rules
- 5 demo apps pre-loaded (gnome-calculator, xterm, gtk4-demo, glxgears, featherpad)
- Audit log capturing JWT issuance + app launch events
- Stub mode: launching apps returns a stub SurfaceHandle when proxy isn't connected
- Tested against Odoo 20 + PostgreSQL 17

Author: 1bspace.com
Parent company: 1B Global
License: Proprietary (1B Space internal use)
2026-10-08 08:48:46 +00:00

116 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
"""``compositor.audit.log`` — Append-only audit log.
Records every compositor event (app launch, terminate, crash, session close)
for compliance, debugging, and security forensics.
Records CANNOT be modified or deleted via the UI. Only superuser can
directly delete via SQL (e.g. for archival).
"""
from odoo import api, fields, models
from odoo.exceptions import UserError
class CompositorAuditLog(models.Model):
_name = "compositor.audit.log"
_description = "1B Space Compositor Audit Log (append-only)"
_order = "create_date desc"
_rec_name = "create_date"
# ─── Identity ────────────────────────────────────────────────────────────
user_id = fields.Many2one(
"res.users",
string="User",
required=True,
index=True,
)
session_id = fields.Many2one(
"compositor.session",
string="Session",
index=True,
)
app_id = fields.Many2one(
"compositor.app",
string="Application",
index=True,
)
app_instance_id = fields.Many2one(
"compositor.session.app",
string="App Instance",
index=True,
)
# ─── Event ──────────────────────────────────────────────────────────────
event_type = fields.Selection(
[
("session_start", "Session Started"),
("session_close", "Session Closed"),
("session_reconnect", "Session Reconnected"),
("session_timeout", "Session Timed Out (SIGHUP)"),
("app_launch", "App Launched"),
("app_terminate", "App Terminated"),
("app_crash", "App Crashed"),
("app_force_kill", "App Force-Killed"),
("jwt_issued", "JWT Issued"),
("jwt_validation_failed", "JWT Validation Failed"),
("proxy_started", "Proxy Started"),
("proxy_crashed", "Proxy Crashed"),
],
string="Event Type",
required=True,
index=True,
)
# ─── Detail ─────────────────────────────────────────────────────────────
pid = fields.Integer(string="PID")
exit_code = fields.Integer(string="Exit Code")
signal_received = fields.Char(string="Signal Received")
detail = fields.Text(string="Detail")
client_ip = fields.Char(string="Client IP")
user_agent = fields.Char(string="User Agent")
# ─── Lifecycle ──────────────────────────────────────────────────────────
create_date = fields.Datetime(
string="Timestamp",
default=fields.Datetime.now,
readonly=True,
index=True,
)
# ─── Append-only enforcement ────────────────────────────────────────────
def write(self, vals):
if not self.env.context.get("compositor_allow_audit_write"):
raise UserError(
"Audit logs are append-only and cannot be modified.\n\n"
"To fix a mistake, create a new corrective log entry with "
"context={'compositor_allow_audit_write': True} (admin only)."
)
return super().write(vals)
def unlink(self):
if not self.env.user.has_group("base.group_system"):
raise UserError(
"Audit logs can only be deleted by system administrators."
)
return super().unlink()
# ─── Public methods ────────────────────────────────────────────────────
@api.model
def log_event(self, event_type, **kwargs):
"""Convenience method to add a log entry.
:param event_type: One of the event_type selection values.
:param kwargs: Field values for the new record.
:return: The created record.
"""
defaults = {
"user_id": self.env.user.id,
"create_date": fields.Datetime.now(),
}
defaults.update(kwargs)
# Bypass append-only check since this is the create path
return self.sudo().with_context(compositor_allow_audit_write=True).create({
"event_type": event_type,
**defaults,
})