1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct AccessControl {
14 pub default: Permissions,
16
17 #[serde(default, skip_serializing_if = "Vec::is_empty")]
19 pub grants: Vec<PermissionGrant>,
20}
21
22impl AccessControl {
23 #[must_use]
25 pub fn new(default: Permissions) -> Self {
26 Self {
27 default,
28 grants: Vec::new(),
29 }
30 }
31
32 #[must_use]
34 pub fn deny_all() -> Self {
35 Self::new(Permissions::none())
36 }
37
38 #[must_use]
40 pub fn read_only() -> Self {
41 Self::new(Permissions::read_only())
42 }
43
44 #[must_use]
46 pub fn full_access() -> Self {
47 Self::new(Permissions::all())
48 }
49
50 #[must_use]
52 pub fn with_grant(mut self, grant: PermissionGrant) -> Self {
53 self.grants.push(grant);
54 self
55 }
56
57 #[must_use]
62 pub fn permissions_for(&self, principal: &Principal) -> &Permissions {
63 for grant in &self.grants {
65 if grant.principal.matches(principal) {
66 return &grant.permissions;
67 }
68 }
69 &self.default
70 }
71
72 #[must_use]
74 pub fn can(&self, principal: &Principal, operation: Operation) -> bool {
75 let perms = self.permissions_for(principal);
76 match operation {
77 Operation::View => perms.view,
78 Operation::Print => perms.print,
79 Operation::Copy => perms.copy,
80 Operation::Annotate => perms.annotate,
81 Operation::Edit => perms.edit,
82 Operation::Sign => perms.sign,
83 Operation::Decrypt => perms.decrypt,
84 }
85 }
86}
87
88impl Default for AccessControl {
89 fn default() -> Self {
90 Self::read_only()
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101#[allow(clippy::struct_excessive_bools)]
102pub struct Permissions {
103 #[serde(default = "default_true")]
105 pub view: bool,
106
107 #[serde(default)]
109 pub print: bool,
110
111 #[serde(default)]
113 pub copy: bool,
114
115 #[serde(default)]
117 pub annotate: bool,
118
119 #[serde(default)]
121 pub edit: bool,
122
123 #[serde(default)]
125 pub sign: bool,
126
127 #[serde(default)]
129 pub decrypt: bool,
130}
131
132fn default_true() -> bool {
133 true
134}
135
136impl Permissions {
137 #[must_use]
139 pub const fn none() -> Self {
140 Self {
141 view: false,
142 print: false,
143 copy: false,
144 annotate: false,
145 edit: false,
146 sign: false,
147 decrypt: false,
148 }
149 }
150
151 #[must_use]
153 pub const fn all() -> Self {
154 Self {
155 view: true,
156 print: true,
157 copy: true,
158 annotate: true,
159 edit: true,
160 sign: true,
161 decrypt: true,
162 }
163 }
164
165 #[must_use]
167 pub const fn read_only() -> Self {
168 Self {
169 view: true,
170 print: false,
171 copy: false,
172 annotate: false,
173 edit: false,
174 sign: false,
175 decrypt: false,
176 }
177 }
178
179 #[must_use]
181 pub const fn view_and_print() -> Self {
182 Self {
183 view: true,
184 print: true,
185 copy: false,
186 annotate: false,
187 edit: false,
188 sign: false,
189 decrypt: false,
190 }
191 }
192
193 #[must_use]
195 pub const fn reviewer() -> Self {
196 Self {
197 view: true,
198 print: true,
199 copy: true,
200 annotate: true,
201 edit: false,
202 sign: true,
203 decrypt: true,
204 }
205 }
206
207 #[must_use]
209 pub const fn editor() -> Self {
210 Self {
211 view: true,
212 print: true,
213 copy: true,
214 annotate: true,
215 edit: true,
216 sign: false,
217 decrypt: true,
218 }
219 }
220}
221
222impl Default for Permissions {
223 fn default() -> Self {
224 Self::read_only()
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct PermissionGrant {
232 pub principal: Principal,
234
235 pub permissions: Permissions,
237}
238
239impl PermissionGrant {
240 #[must_use]
242 pub fn new(principal: Principal, permissions: Permissions) -> Self {
243 Self {
244 principal,
245 permissions,
246 }
247 }
248
249 #[must_use]
251 pub fn full_access_for_user(email: impl Into<String>) -> Self {
252 Self::new(Principal::User(email.into()), Permissions::all())
253 }
254
255 #[must_use]
257 pub fn reviewer_for_user(email: impl Into<String>) -> Self {
258 Self::new(Principal::User(email.into()), Permissions::reviewer())
259 }
260
261 #[must_use]
263 pub fn editor_for_user(email: impl Into<String>) -> Self {
264 Self::new(Principal::User(email.into()), Permissions::editor())
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270#[serde(tag = "type", content = "id", rename_all = "lowercase")]
271pub enum Principal {
272 User(String),
274
275 Group(String),
277
278 Role(String),
280
281 Everyone,
283}
284
285impl Principal {
286 #[must_use]
288 pub fn user(id: impl Into<String>) -> Self {
289 Self::User(id.into())
290 }
291
292 #[must_use]
294 pub fn group(id: impl Into<String>) -> Self {
295 Self::Group(id.into())
296 }
297
298 #[must_use]
300 pub fn role(id: impl Into<String>) -> Self {
301 Self::Role(id.into())
302 }
303
304 #[must_use]
308 pub fn matches(&self, other: &Principal) -> bool {
309 match self {
310 Self::Everyone => true,
311 Self::User(id) => matches!(other, Self::User(other_id) if id == other_id),
312 Self::Group(id) => matches!(other, Self::Group(other_id) if id == other_id),
313 Self::Role(id) => matches!(other, Self::Role(other_id) if id == other_id),
314 }
315 }
316}
317
318impl std::fmt::Display for Principal {
319 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320 match self {
321 Self::User(id) => write!(f, "user:{id}"),
322 Self::Group(id) => write!(f, "group:{id}"),
323 Self::Role(id) => write!(f, "role:{id}"),
324 Self::Everyone => write!(f, "*"),
325 }
326 }
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display)]
331#[strum(serialize_all = "lowercase")]
332pub enum Operation {
333 View,
335 Print,
337 Copy,
339 Annotate,
341 Edit,
343 Sign,
345 Decrypt,
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_permissions_none() {
355 let perms = Permissions::none();
356 assert!(!perms.view);
357 assert!(!perms.print);
358 assert!(!perms.edit);
359 }
360
361 #[test]
362 fn test_permissions_all() {
363 let perms = Permissions::all();
364 assert!(perms.view);
365 assert!(perms.print);
366 assert!(perms.copy);
367 assert!(perms.annotate);
368 assert!(perms.edit);
369 assert!(perms.sign);
370 assert!(perms.decrypt);
371 }
372
373 #[test]
374 fn test_permissions_read_only() {
375 let perms = Permissions::read_only();
376 assert!(perms.view);
377 assert!(!perms.print);
378 assert!(!perms.edit);
379 }
380
381 #[test]
382 fn test_access_control_default() {
383 let ac = AccessControl::read_only();
384 let user = Principal::user("test@example.com");
385
386 assert!(ac.can(&user, Operation::View));
387 assert!(!ac.can(&user, Operation::Print));
388 assert!(!ac.can(&user, Operation::Edit));
389 }
390
391 #[test]
392 fn test_access_control_with_grant() {
393 let ac = AccessControl::deny_all()
394 .with_grant(PermissionGrant::full_access_for_user("admin@example.com"));
395
396 let admin = Principal::user("admin@example.com");
397 let user = Principal::user("user@example.com");
398
399 assert!(ac.can(&admin, Operation::View));
400 assert!(ac.can(&admin, Operation::Edit));
401 assert!(!ac.can(&user, Operation::View));
402 }
403
404 #[test]
405 fn test_principal_matches() {
406 let everyone = Principal::Everyone;
407 let user = Principal::user("test@example.com");
408
409 assert!(everyone.matches(&user));
410 assert!(user.matches(&user));
411 assert!(!user.matches(&Principal::user("other@example.com")));
412 }
413
414 #[test]
415 fn test_principal_display() {
416 assert_eq!(
417 Principal::user("test@example.com").to_string(),
418 "user:test@example.com"
419 );
420 assert_eq!(Principal::group("admins").to_string(), "group:admins");
421 assert_eq!(Principal::role("reviewer").to_string(), "role:reviewer");
422 assert_eq!(Principal::Everyone.to_string(), "*");
423 }
424
425 #[test]
426 fn test_serialization() {
427 let ac = AccessControl::read_only().with_grant(PermissionGrant::new(
428 Principal::user("admin@example.com"),
429 Permissions::all(),
430 ));
431
432 let json = serde_json::to_string_pretty(&ac).unwrap();
433 assert!(json.contains("\"view\": true"));
434 assert!(json.contains("admin@example.com"));
435
436 let deserialized: AccessControl = serde_json::from_str(&json).unwrap();
437 assert_eq!(deserialized.grants.len(), 1);
438 }
439
440 #[test]
441 fn test_reviewer_permissions() {
442 let perms = Permissions::reviewer();
443 assert!(perms.view);
444 assert!(perms.annotate);
445 assert!(perms.sign);
446 assert!(!perms.edit);
447 }
448
449 #[test]
450 fn test_editor_permissions() {
451 let perms = Permissions::editor();
452 assert!(perms.view);
453 assert!(perms.edit);
454 assert!(!perms.sign);
455 }
456}