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    /// Called once a renderer's `hello` names it, after `authorize_connect`
19    /// already approved the connection — a chance to record durable identity
20    /// for tracking or failover purposes. Best-effort: Core doesn't drop the
21    /// connection if this does nothing.
22    async fn on_renderer_connected(&self, name: &str, query: &str);
23
24    /// Filters `renderers` down to what `api_key` may see — `GET /renderers`.
25    async fn filter_visible(
26        &self,
27        api_key: &str,
28        renderers: Vec<RendererInfo>,
29    ) -> Vec<RendererInfo>;
30
31    /// Filters `graphics` down to what `api_key` may see — `GET /graphics`.
32    /// Default implementation returns all graphics (no filtering), maintaining
33    /// backward compatibility and the original "unscoped by design" behavior.
34    async fn filter_graphics(&self, _api_key: &str, graphics: Vec<Graphic>) -> Vec<Graphic> {
35        graphics
36    }
37
38    /// Whether `api_key` may target the renderer named `renderer_name` —
39    /// checked before every renderer-scoped call (get/target/load/play/
40    /// stop/update/customAction/clear). Keyed on the stable *name*, not the
41    /// per-session `id`.
42    async fn can_target(&self, api_key: &str, renderer_name: &str) -> bool;
43
44    /// Whether `api_key` may load `graphic_id` onto `renderer_name` — checked
45    /// before load() sends a LoadMessage. Default implementation allows all
46    /// loads (maintaining backward compatibility), but implementations can
47    /// enforce zone/renderer-specific graphic restrictions.
48    async fn can_load_graphic(
49        &self,
50        _api_key: &str,
51        _renderer_name: &str,
52        _graphic_id: &str,
53    ) -> bool {
54        true
55    }
56}
57
58/// No restriction at all — every renderer visible, every key can target
59/// anything, every connection accepted. The default for a consumer that
60/// doesn't need access control (or hasn't wired anything up yet).
61pub struct AllowAllAccessControl;
62
63#[async_trait]
64impl AccessControl for AllowAllAccessControl {
65    async fn authorize_connect(&self, _query: &str) -> bool {
66        true
67    }
68
69    async fn on_renderer_connected(&self, _name: &str, _query: &str) {}
70
71    async fn filter_visible(
72        &self,
73        _api_key: &str,
74        renderers: Vec<RendererInfo>,
75    ) -> Vec<RendererInfo> {
76        renderers
77    }
78
79    async fn can_target(&self, _api_key: &str, _renderer_name: &str) -> bool {
80        true
81    }
82}