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