1pub const SCOPE_MEETING_ATTEND: &str = "meeting:attend";
7pub const SCOPE_MEETING_SPEAK: &str = "meeting:speak";
8pub const SCOPE_MEETING_VIDEO: &str = "meeting:video";
9pub const SCOPE_MEETING_CHAT: &str = "meeting:chat";
10pub const SCOPE_MEETING_SHARE_SCREEN: &str = "meeting:share_screen";
11pub const SCOPE_MEETING_RECORD: &str = "meeting:record"; pub const SCOPE_COMMS_MESSAGE_READ: &str = "comms:message:read";
15pub const SCOPE_COMMS_MESSAGE_SEND: &str = "comms:message:send";
16pub const SCOPE_COMMS_MESSAGE_DELETE: &str = "comms:message:delete"; pub const SCOPE_COMMS_EMAIL_READ: &str = "comms:email:read";
18pub const SCOPE_COMMS_EMAIL_SEND: &str = "comms:email:send";
19pub const SCOPE_COMMS_EMAIL_DELETE: &str = "comms:email:delete"; pub const SCOPE_COMMS_CALENDAR_READ: &str = "comms:calendar:read";
21pub const SCOPE_COMMS_CALENDAR_WRITE: &str = "comms:calendar:write";
22
23pub const SCOPE_FILES_READ: &str = "files:read";
25pub const SCOPE_FILES_WRITE: &str = "files:write"; pub const SCOPE_IDENTITY_PROVE: &str = "identity:prove";
29pub const SCOPE_IDENTITY_DELEGATE: &str = "identity:delegate"; pub const SCOPE_TRANSACT_PURCHASE: &str = "transact:purchase";
33pub const SCOPE_TRANSACT_SELL: &str = "transact:sell";
34pub const SCOPE_PAYMENTS_SEND: &str = "payments:send";
35pub const SCOPE_PAYMENTS_RECEIVE: &str = "payments:receive";
36pub const SCOPE_PAYMENTS_AUTHORIZE: &str = "payments:authorize"; pub const SCOPE_CONTRACT_READ: &str = "contract:read";
40pub const SCOPE_CONTRACT_SIGN: &str = "contract:sign"; pub const SCOPE_DATA_READ: &str = "data:read";
44pub const SCOPE_DATA_WRITE: &str = "data:write"; pub const SCOPE_DATA_DELETE: &str = "data:delete"; pub const SCOPE_DATA_EXPORT: &str = "data:export"; pub const SCOPE_DATA_SHARE: &str = "data:share";
48
49pub const SCOPE_EXECUTE_TOOL: &str = "execute:tool";
51pub const SCOPE_EXECUTE_CODE: &str = "execute:code"; pub const SCOPE_GENERATE_CONTENT: &str = "generate:content";
55pub const SCOPE_GENERATE_DEEPFAKE: &str = "generate:deepfake"; pub const SCOPE_PHYSICAL_ENTER: &str = "physical:enter";
66pub const SCOPE_PHYSICAL_EXIT: &str = "physical:exit";
67pub const SCOPE_PHYSICAL_ACTUATE: &str = "physical:actuate"; pub const SCOPE_PHYSICAL_MANIPULATE: &str = "physical:manipulate"; pub const SCOPE_ROBOT_OPERATE: &str = "robot:operate";
71pub const SCOPE_ROBOT_MOVE: &str = "robot:move";
72pub const SCOPE_ROBOT_INTERACT: &str = "robot:interact";
73
74pub const SCOPE_DRONE_FLY: &str = "drone:fly"; pub const SCOPE_DRONE_DELIVER: &str = "drone:deliver";
76pub const SCOPE_DRONE_CAPTURE: &str = "drone:capture";
77
78pub const SCOPE_VEHICLE_OPERATE: &str = "vehicle:operate"; pub const SCOPE_VEHICLE_TRANSPORT: &str = "vehicle:transport";
80pub const SCOPE_VEHICLE_CHARGE: &str = "vehicle:charge";
81
82pub const SCOPE_INFRASTRUCTURE_MONITOR: &str = "infrastructure:monitor";
83pub const SCOPE_INFRASTRUCTURE_CONTROL: &str = "infrastructure:control"; pub const SCOPE_INFRASTRUCTURE_ACCESS: &str = "infrastructure:access"; pub const SCOPE_ACTUATE_VALVE: &str = "actuate:valve"; pub const SCOPE_ACTUATE_MOTOR: &str = "actuate:motor"; pub const SCOPE_ACTUATE_SWITCH: &str = "actuate:switch"; pub const CUSTOM_SCOPE_PREFIX: &str = "custom:";
97
98fn sensitive_scopes() -> &'static [&'static str] {
99 &[
100 SCOPE_MEETING_RECORD,
101 SCOPE_COMMS_MESSAGE_DELETE,
102 SCOPE_COMMS_EMAIL_DELETE,
103 SCOPE_FILES_WRITE,
104 SCOPE_IDENTITY_DELEGATE,
105 SCOPE_PAYMENTS_AUTHORIZE,
106 SCOPE_CONTRACT_SIGN,
107 SCOPE_DATA_WRITE,
108 SCOPE_DATA_DELETE,
109 SCOPE_DATA_EXPORT,
110 SCOPE_EXECUTE_CODE,
111 SCOPE_GENERATE_DEEPFAKE,
112 SCOPE_PHYSICAL_ACTUATE,
113 SCOPE_PHYSICAL_MANIPULATE,
114 SCOPE_DRONE_FLY,
115 SCOPE_VEHICLE_OPERATE,
116 SCOPE_INFRASTRUCTURE_CONTROL,
117 SCOPE_INFRASTRUCTURE_ACCESS,
118 SCOPE_ACTUATE_VALVE,
119 SCOPE_ACTUATE_MOTOR,
120 SCOPE_ACTUATE_SWITCH,
121 ]
122}
123
124fn valid_scopes() -> &'static [&'static str] {
125 &[
126 SCOPE_MEETING_ATTEND,
127 SCOPE_MEETING_SPEAK,
128 SCOPE_MEETING_VIDEO,
129 SCOPE_MEETING_CHAT,
130 SCOPE_MEETING_SHARE_SCREEN,
131 SCOPE_MEETING_RECORD,
132 SCOPE_COMMS_MESSAGE_READ,
133 SCOPE_COMMS_MESSAGE_SEND,
134 SCOPE_COMMS_MESSAGE_DELETE,
135 SCOPE_COMMS_EMAIL_READ,
136 SCOPE_COMMS_EMAIL_SEND,
137 SCOPE_COMMS_EMAIL_DELETE,
138 SCOPE_COMMS_CALENDAR_READ,
139 SCOPE_COMMS_CALENDAR_WRITE,
140 SCOPE_FILES_READ,
141 SCOPE_FILES_WRITE,
142 SCOPE_IDENTITY_PROVE,
143 SCOPE_IDENTITY_DELEGATE,
144 SCOPE_TRANSACT_PURCHASE,
145 SCOPE_TRANSACT_SELL,
146 SCOPE_PAYMENTS_SEND,
147 SCOPE_PAYMENTS_RECEIVE,
148 SCOPE_PAYMENTS_AUTHORIZE,
149 SCOPE_CONTRACT_READ,
150 SCOPE_CONTRACT_SIGN,
151 SCOPE_DATA_READ,
152 SCOPE_DATA_WRITE,
153 SCOPE_DATA_DELETE,
154 SCOPE_DATA_EXPORT,
155 SCOPE_DATA_SHARE,
156 SCOPE_EXECUTE_TOOL,
157 SCOPE_EXECUTE_CODE,
158 SCOPE_GENERATE_CONTENT,
159 SCOPE_GENERATE_DEEPFAKE,
160 SCOPE_PHYSICAL_ENTER,
161 SCOPE_PHYSICAL_EXIT,
162 SCOPE_PHYSICAL_ACTUATE,
163 SCOPE_PHYSICAL_MANIPULATE,
164 SCOPE_ROBOT_OPERATE,
165 SCOPE_ROBOT_MOVE,
166 SCOPE_ROBOT_INTERACT,
167 SCOPE_DRONE_FLY,
168 SCOPE_DRONE_DELIVER,
169 SCOPE_DRONE_CAPTURE,
170 SCOPE_VEHICLE_OPERATE,
171 SCOPE_VEHICLE_TRANSPORT,
172 SCOPE_VEHICLE_CHARGE,
173 SCOPE_INFRASTRUCTURE_MONITOR,
174 SCOPE_INFRASTRUCTURE_CONTROL,
175 SCOPE_INFRASTRUCTURE_ACCESS,
176 SCOPE_ACTUATE_VALVE,
177 SCOPE_ACTUATE_MOTOR,
178 SCOPE_ACTUATE_SWITCH,
179 ]
180}
181
182fn wildcard_expansion(w: &str) -> Option<&'static [&'static str]> {
183 match w {
184 "meeting:*" => Some(&[
185 SCOPE_MEETING_ATTEND,
186 SCOPE_MEETING_SPEAK,
187 SCOPE_MEETING_VIDEO,
188 SCOPE_MEETING_CHAT,
189 SCOPE_MEETING_SHARE_SCREEN,
190 ]),
191 "comms:message:*" => Some(&[SCOPE_COMMS_MESSAGE_READ, SCOPE_COMMS_MESSAGE_SEND]),
192 "comms:email:*" => Some(&[SCOPE_COMMS_EMAIL_READ, SCOPE_COMMS_EMAIL_SEND]),
193 "comms:*" => Some(&[
194 SCOPE_COMMS_MESSAGE_READ,
195 SCOPE_COMMS_MESSAGE_SEND,
196 SCOPE_COMMS_EMAIL_READ,
197 SCOPE_COMMS_EMAIL_SEND,
198 SCOPE_COMMS_CALENDAR_READ,
199 SCOPE_COMMS_CALENDAR_WRITE,
200 ]),
201 "transact:*" => Some(&[SCOPE_TRANSACT_PURCHASE, SCOPE_TRANSACT_SELL]),
202 "payments:*" => Some(&[SCOPE_PAYMENTS_SEND, SCOPE_PAYMENTS_RECEIVE]),
203 "data:*" => Some(&[SCOPE_DATA_READ, SCOPE_DATA_SHARE]),
204 "execute:*" => Some(&[SCOPE_EXECUTE_TOOL]),
205 "generate:*" => Some(&[SCOPE_GENERATE_CONTENT]),
206 "physical:*" => Some(&[SCOPE_PHYSICAL_ENTER, SCOPE_PHYSICAL_EXIT]),
207 "robot:*" => Some(&[SCOPE_ROBOT_OPERATE, SCOPE_ROBOT_MOVE, SCOPE_ROBOT_INTERACT]),
208 "drone:*" => Some(&[SCOPE_DRONE_DELIVER, SCOPE_DRONE_CAPTURE]),
209 "vehicle:*" => Some(&[SCOPE_VEHICLE_TRANSPORT, SCOPE_VEHICLE_CHARGE]),
210 "infrastructure:*" => Some(&[SCOPE_INFRASTRUCTURE_MONITOR]),
211 _ => None,
213 }
214}
215
216fn is_custom_scope(s: &str) -> bool {
217 s.starts_with(CUSTOM_SCOPE_PREFIX) && s.len() > CUSTOM_SCOPE_PREFIX.len()
218}
219
220pub fn validate_scopes(scopes: &[String]) -> Option<String> {
223 for s in scopes {
224 if valid_scopes().contains(&s.as_str()) {
225 continue;
226 }
227 if wildcard_expansion(s).is_some() {
228 continue;
229 }
230 if is_custom_scope(s) {
231 continue;
232 }
233 return Some(format!(
234 "unknown scope \"{}\": not in canonical vocabulary and not a custom: extension",
235 s
236 ));
237 }
238 None
239}
240
241pub fn is_sensitive(scope: &str) -> bool {
244 sensitive_scopes().contains(&scope)
245}
246
247pub fn expand_scopes(scopes: &[String]) -> Vec<String> {
250 let mut seen = std::collections::BTreeSet::new();
251 for s in scopes {
252 if let Some(children) = wildcard_expansion(s) {
253 for c in children {
254 seen.insert((*c).to_string());
255 }
256 } else {
257 seen.insert(s.clone());
258 }
259 }
260 seen.into_iter().collect()
261}
262
263pub fn has_scope(granted: &[String], required: &str) -> bool {
264 expand_scopes(granted).iter().any(|s| s == required)
265}
266
267pub fn intersect_scopes(lists: &[&[String]]) -> Vec<String> {
269 if lists.is_empty() {
270 return Vec::new();
271 }
272 let mut effective: std::collections::BTreeSet<String> =
273 expand_scopes(lists[0]).into_iter().collect();
274 for list in &lists[1..] {
275 let expanded: std::collections::BTreeSet<String> =
276 expand_scopes(list).into_iter().collect();
277 effective = effective.intersection(&expanded).cloned().collect();
278 }
279 effective.into_iter().collect()
280}