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> {
116 Ok(match value {
117 "session:read" => Self::SessionRead,
118 "session:write" => Self::SessionWrite,
119 "page:read" => Self::PageRead,
120 "page:write" => Self::PageWrite,
121 "browser:mutate" => Self::BrowserMutate,
122 "file:upload" => Self::FileUpload,
123 "file:download" => Self::FileDownload,
124 "javascript:evaluate" => Self::JavascriptEvaluate,
125 "intent:execute" => Self::IntentExecute,
126 "vision:assist" => Self::VisionAssist,
127 "artifact:read" => Self::ArtifactRead,
128 "artifact:capture" => Self::ArtifactCapture,
129 "recovery:read" => Self::RecoveryRead,
130 "recovery:write" => Self::RecoveryWrite,
131 "job:submit" => Self::JobSubmit,
132 "job:read" => Self::JobRead,
133 "job:cancel" => Self::JobCancel,
134 "authority:admin" => Self::AuthorityAdmin,
135 "browser:fingerprint" => Self::BrowserFingerprint,
136 "browser:humanize" => Self::BrowserHumanize,
137 _ => return Err(UnknownCapability(value.to_owned())),
138 })
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
144#[error("unknown capability: {0}")]
145pub struct UnknownCapability(pub String);
146
147impl CapabilitySet {
148 pub fn new(capabilities: impl IntoIterator<Item = Capability>) -> Self {
149 capabilities.into_iter().collect()
150 }
151
152 pub fn contains(&self, capability: Capability) -> bool {
153 self.0.contains(&capability)
154 }
155
156 pub fn allows(&self, operation: InterfaceOperation) -> bool {
157 operation
158 .required()
159 .iter()
160 .all(|capability| self.contains(*capability))
161 }
162}
163
164impl FromIterator<Capability> for CapabilitySet {
165 fn from_iter<T: IntoIterator<Item = Capability>>(iter: T) -> Self {
166 Self(iter.into_iter().collect())
167 }
168}
169
170impl Serialize for CapabilitySet {
171 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172 where
173 S: Serializer,
174 {
175 let mut capabilities: Vec<_> = self.0.iter().copied().collect();
176 capabilities.sort_by_key(|capability| capability.as_str());
177 capabilities.serialize(serializer)
178 }
179}
180
181impl<'de> Deserialize<'de> for CapabilitySet {
182 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
183 where
184 D: Deserializer<'de>,
185 {
186 let capabilities = Vec::<Capability>::deserialize(deserializer)?;
187 let mut verified = BTreeSet::new();
188 for capability in capabilities {
189 if !verified.insert(capability) {
190 return Err(serde::de::Error::custom("duplicate capability"));
191 }
192 }
193 Ok(Self(verified))
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn authority_admin_serde_round_trip() {
203 let json = serde_json::to_string(&Capability::AuthorityAdmin).unwrap();
204 assert_eq!(json, "\"authority:admin\"");
205 let parsed: Capability = serde_json::from_str(&json).unwrap();
206 assert_eq!(parsed, Capability::AuthorityAdmin);
207 }
208}