Skip to main content

ograf_core/
access.rs

1//! Dependency Inversion seam: Core defines what access control it needs,
2//! never how it's decided. Consumers can provide their own implementation;
3//! [`AllowAllAccessControl`] is a trivial one for anyone who wants no
4//! restriction at all.
5
6use async_trait::async_trait;
7
8use crate::models::{Graphic, RendererInfo};
9
10#[async_trait]
11pub trait AccessControl: Send + Sync {
12    /// Authorizes a renderer's WebSocket connection *before* it's upgraded.
13    /// `query` is the connect URL's raw query string, unparsed and
14    /// unvalidated by Core — an implementation decides what it means (a
15    /// zone name + token for `ZoneAccessControl`, ignored entirely here).
16    async fn authorize_connect(&self, query: &str) -> bool;
17
18    /// Authorizes the renderer name from `hello`, before the session is registered.
19    /// Called after `authorize_connect` and after name validation, but before
20    /// attempting to register the session. Returning `false` closes the connection
21    /// and never registers it.
22    ///
23    /// Default implementation allows all names (backward compatible). Implementations
24    /// can enforce policies like "name must match query string" or "name must be
25    /// authorized for this zone".
26    ///
27    /// Added in 0.4.0 to prevent name squatting (connecting with valid query but
28    /// claiming another zone's renderer name in `hello`).
29    async fn authorize_name(&self, _name: &str, _query: &str) -> bool {
30        true
31    }
32
33    /// Called once a renderer's `hello` names it, after `authorize_connect`
34    /// already approved the connection — a chance to record durable identity
35    /// for tracking or failover purposes. Best-effort: Core doesn't drop the
36    /// connection if this does nothing.
37    async fn on_renderer_connected(&self, name: &str, query: &str);
38
39    /// Filters `renderers` down to what `api_key` may see — `GET /renderers`.
40    async fn filter_visible(
41        &self,
42        api_key: &str,
43        renderers: Vec<RendererInfo>,
44    ) -> Vec<RendererInfo>;
45
46    /// Filters `graphics` down to what `api_key` may see — `GET /graphics`.
47    /// Default implementation returns all graphics (no filtering), maintaining
48    /// backward compatibility and the original "unscoped by design" behavior.
49    async fn filter_graphics(&self, _api_key: &str, graphics: Vec<Graphic>) -> Vec<Graphic> {
50        graphics
51    }
52
53    /// Whether `api_key` may target the renderer named `renderer_name` —
54    /// checked before every renderer-scoped call (get/target/load/play/
55    /// stop/update/customAction/clear). Keyed on the stable *name*, not the
56    /// per-session `id`.
57    async fn can_target(&self, api_key: &str, renderer_name: &str) -> bool;
58
59    /// Whether `api_key` may load `graphic_id` onto `renderer_name` — checked
60    /// before load() sends a LoadMessage. Default implementation allows all
61    /// loads (maintaining backward compatibility), but implementations can
62    /// enforce zone/renderer-specific graphic restrictions.
63    async fn can_load_graphic(
64        &self,
65        _api_key: &str,
66        _renderer_name: &str,
67        _graphic_id: &str,
68    ) -> bool {
69        true
70    }
71
72    /// Whether `api_key` may delete `graphic_id` — `DELETE /graphics/{id}`.
73    /// Defaults to allowing it, like every other call without access control:
74    /// a deployment that runs Core on its own already lets anyone on its
75    /// network clear what's on air. A delete without `force` only unlists the
76    /// graphic, so it's recoverable until the retention period ends.
77    async fn can_delete_graphic(&self, _api_key: &str, _graphic_id: &str) -> bool {
78        true
79    }
80}
81
82/// No restriction at all — every renderer visible, every key can target
83/// anything, every connection accepted. The default for a consumer that
84/// doesn't need access control (or hasn't wired anything up yet).
85pub struct AllowAllAccessControl;
86
87#[async_trait]
88impl AccessControl for AllowAllAccessControl {
89    async fn authorize_connect(&self, _query: &str) -> bool {
90        true
91    }
92
93    async fn on_renderer_connected(&self, _name: &str, _query: &str) {}
94
95    async fn filter_visible(
96        &self,
97        _api_key: &str,
98        renderers: Vec<RendererInfo>,
99    ) -> Vec<RendererInfo> {
100        renderers
101    }
102
103    async fn can_target(&self, _api_key: &str, _renderer_name: &str) -> bool {
104        true
105    }
106}