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}