1use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::collections::BTreeSet;
5use std::iter::FromIterator;
6use uuid::Uuid;
7
8use crate::InterfaceOperation;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
13#[serde(transparent)]
14pub struct PrincipalId(Uuid);
15
16impl PrincipalId {
17 pub fn from_uuid(value: Uuid) -> Self {
18 Self(value)
19 }
20
21 pub fn as_uuid(&self) -> &Uuid {
22 &self.0
23 }
24}
25
26#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29#[serde(rename_all = "kebab-case")]
30pub enum Capability {
31 #[serde(rename = "session:read")]
32 SessionRead,
33 #[serde(rename = "session:write")]
34 SessionWrite,
35 #[serde(rename = "page:read")]
36 PageRead,
37 #[serde(rename = "page:write")]
38 PageWrite,
39 #[serde(rename = "browser:mutate")]
40 BrowserMutate,
41 #[serde(rename = "file:upload")]
42 FileUpload,
43 #[serde(rename = "file:download")]
44 FileDownload,
45 #[serde(rename = "javascript:evaluate")]
46 JavascriptEvaluate,
47 #[serde(rename = "intent:execute")]
48 IntentExecute,
49 #[serde(rename = "vision:assist")]
50 VisionAssist,
51 #[serde(rename = "artifact:read")]
52 ArtifactRead,
53 #[serde(rename = "artifact:capture")]
54 ArtifactCapture,
55 #[serde(rename = "recovery:read")]
56 RecoveryRead,
57 #[serde(rename = "recovery:write")]
58 RecoveryWrite,
59 #[serde(rename = "job:submit")]
60 JobSubmit,
61 #[serde(rename = "job:read")]
62 JobRead,
63 #[serde(rename = "job:cancel")]
64 JobCancel,
65 #[serde(rename = "authority:admin")]
66 AuthorityAdmin,
67 #[serde(rename = "browser:fingerprint")]
68 BrowserFingerprint,
69 #[serde(rename = "browser:humanize")]
70 BrowserHumanize,
71}
72
73impl Capability {
74 pub const fn as_str(self) -> &'static str {
75 match self {
76 Self::SessionRead => "session:read",
77 Self::SessionWrite => "session:write",
78 Self::PageRead => "page:read",
79 Self::PageWrite => "page:write",
80 Self::BrowserMutate => "browser:mutate",
81 Self::FileUpload => "file:upload",
82 Self::FileDownload => "file:download",
83 Self::JavascriptEvaluate => "javascript:evaluate",
84 Self::IntentExecute => "intent:execute",
85 Self::VisionAssist => "vision:assist",
86 Self::ArtifactRead => "artifact:read",
87 Self::ArtifactCapture => "artifact:capture",
88 Self::RecoveryRead => "recovery:read",
89 Self::RecoveryWrite => "recovery:write",
90 Self::JobSubmit => "job:submit",
91 Self::JobRead => "job:read",
92 Self::JobCancel => "job:cancel",
93 Self::AuthorityAdmin => "authority:admin",
94 Self::BrowserFingerprint => "browser:fingerprint",
95 Self::BrowserHumanize => "browser:humanize",
96 }
97 }
98}
99
100#[derive(Debug, Clone, Default, PartialEq, Eq)]
102#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
103pub struct CapabilitySet(BTreeSet<Capability>);
104
105impl std::str::FromStr for Capability {
106 type Err = UnknownCapability;
107
108 fn from_str(value: &str) -> Result<Self, Self::Err> {
112 Ok(match value {
113 "session:read" => Self::SessionRead,
114 "session:write" => Self::SessionWrite,
115 "page:read" => Self::PageRead,
116 "page:write" => Self::PageWrite,
117 "browser:mutate" => Self::BrowserMutate,
118 "file:upload" => Self::FileUpload,
119 "file:download" => Self::FileDownload,
120 "javascript:evaluate" => Self::JavascriptEvaluate,
121 "intent:execute" => Self::IntentExecute,
122 "vision:assist" => Self::VisionAssist,
123 "artifact:read" => Self::ArtifactRead,
124 "artifact:capture" => Self::ArtifactCapture,
125 "recovery:read" => Self::RecoveryRead,
126 "recovery:write" => Self::RecoveryWrite,
127 "job:submit" => Self::JobSubmit,
128 "job:read" => Self::JobRead,
129 "job:cancel" => Self::JobCancel,
130 "authority:admin" => Self::AuthorityAdmin,
131 "browser:fingerprint" => Self::BrowserFingerprint,
132 "browser:humanize" => Self::BrowserHumanize,
133 _ => return Err(UnknownCapability(value.to_owned())),
134 })
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
140#[error("unknown capability: {0}")]
141pub struct UnknownCapability(pub String);
142
143impl CapabilitySet {
144 pub fn new(capabilities: impl IntoIterator<Item = Capability>) -> Self {
145 capabilities.into_iter().collect()
146 }
147
148 pub fn contains(&self, capability: Capability) -> bool {
149 self.0.contains(&capability)
150 }
151
152 pub fn allows(&self, operation: InterfaceOperation) -> bool {
153 operation
154 .required()
155 .iter()
156 .all(|capability| self.contains(*capability))
157 }
158}
159
160impl FromIterator<Capability> for CapabilitySet {
161 fn from_iter<T: IntoIterator<Item = Capability>>(iter: T) -> Self {
162 Self(iter.into_iter().collect())
163 }
164}
165
166impl Serialize for CapabilitySet {
167 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168 where
169 S: Serializer,
170 {
171 let mut capabilities: Vec<_> = self.0.iter().copied().collect();
172 capabilities.sort_by_key(|capability| capability.as_str());
173 capabilities.serialize(serializer)
174 }
175}
176
177impl<'de> Deserialize<'de> for CapabilitySet {
178 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
179 where
180 D: Deserializer<'de>,
181 {
182 let capabilities = Vec::<Capability>::deserialize(deserializer)?;
183 let mut verified = BTreeSet::new();
184 for capability in capabilities {
185 if !verified.insert(capability) {
186 return Err(serde::de::Error::custom("duplicate capability"));
187 }
188 }
189 Ok(Self(verified))
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn authority_admin_serde_round_trip() {
199 let json = serde_json::to_string(&Capability::AuthorityAdmin).unwrap();
200 assert_eq!(json, "\"authority:admin\"");
201 let parsed: Capability = serde_json::from_str(&json).unwrap();
202 assert_eq!(parsed, Capability::AuthorityAdmin);
203 }
204}