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 !matches!(grant.principal, Principal::Everyone) && grant.principal.matches(principal)
66 {
67 return &grant.permissions;
68 }
69 }
70 for grant in &self.grants {
72 if matches!(grant.principal, Principal::Everyone) {
73 return &grant.permissions;
74 }
75 }
76 &self.default
77 }
78
79 #[must_use]
81 pub fn can(&self, principal: &Principal, operation: Operation) -> bool {
82 let perms = self.permissions_for(principal);
83 match operation {
84 Operation::View => perms.view,
85 Operation::Print => perms.print,
86 Operation::Copy => perms.copy,
87 Operation::Annotate => perms.annotate,
88 Operation::Edit => perms.edit,
89 Operation::Sign => perms.sign,
90 Operation::Decrypt => perms.decrypt,
91 }
92 }
93}
94
95impl Default for AccessControl {
96 fn default() -> Self {
97 Self::read_only()
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108#[allow(clippy::struct_excessive_bools)]
109pub struct Permissions {
110 #[serde(default = "default_true")]
112 pub view: bool,
113
114 #[serde(default)]
116 pub print: bool,
117
118 #[serde(default)]
120 pub copy: bool,
121
122 #[serde(default)]
124 pub annotate: bool,
125
126 #[serde(default)]
128 pub edit: bool,
129
130 #[serde(default)]
132 pub sign: bool,
133
134 #[serde(default)]
136 pub decrypt: bool,
137}
138
139fn default_true() -> bool {
140 true
141}
142
143impl Permissions {
144 #[must_use]
146 pub const fn none() -> Self {
147 Self {
148 view: false,
149 print: false,
150 copy: false,
151 annotate: false,
152 edit: false,
153 sign: false,
154 decrypt: false,
155 }
156 }
157
158 #[must_use]
160 pub const fn all() -> Self {
161 Self {
162 view: true,
163 print: true,
164 copy: true,
165 annotate: true,
166 edit: true,
167 sign: true,
168 decrypt: true,
169 }
170 }
171
172 #[must_use]
174 pub const fn read_only() -> Self {
175 Self {
176 view: true,
177 print: false,
178 copy: false,
179 annotate: false,
180 edit: false,
181 sign: false,
182 decrypt: false,
183 }
184 }
185
186 #[must_use]
188 pub const fn view_and_print() -> Self {
189 Self {
190 view: true,
191 print: true,
192 copy: false,
193 annotate: false,
194 edit: false,
195 sign: false,
196 decrypt: false,
197 }
198 }
199
200 #[must_use]
202 pub const fn reviewer() -> Self {
203 Self {
204 view: true,
205 print: true,
206 copy: true,
207 annotate: true,
208 edit: false,
209 sign: true,
210 decrypt: true,
211 }
212 }
213
214 #[must_use]
216 pub const fn editor() -> Self {
217 Self {
218 view: true,
219 print: true,
220 copy: true,
221 annotate: true,
222 edit: true,
223 sign: false,
224 decrypt: true,
225 }
226 }
227}
228
229impl Default for Permissions {
230 fn default() -> Self {
231 Self::read_only()
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct PermissionGrant {
239 pub principal: Principal,
241
242 pub permissions: Permissions,
244}
245
246impl PermissionGrant {
247 #[must_use]
249 pub fn new(principal: Principal, permissions: Permissions) -> Self {
250 Self {
251 principal,
252 permissions,
253 }
254 }
255
256 #[must_use]
258 pub fn full_access_for_user(email: impl Into<String>) -> Self {
259 Self::new(Principal::User(email.into()), Permissions::all())
260 }
261
262 #[must_use]
264 pub fn reviewer_for_user(email: impl Into<String>) -> Self {
265 Self::new(Principal::User(email.into()), Permissions::reviewer())
266 }
267
268 #[must_use]
270 pub fn editor_for_user(email: impl Into<String>) -> Self {
271 Self::new(Principal::User(email.into()), Permissions::editor())
272 }
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277#[serde(tag = "type", content = "id", rename_all = "lowercase")]
278pub enum Principal {
279 User(String),
281
282 Group(String),
284
285 Role(String),
287
288 Everyone,
290}
291
292impl Principal {
293 #[must_use]
295 pub fn user(id: impl Into<String>) -> Self {
296 Self::User(id.into())
297 }
298
299 #[must_use]
301 pub fn group(id: impl Into<String>) -> Self {
302 Self::Group(id.into())
303 }
304
305 #[must_use]
307 pub fn role(id: impl Into<String>) -> Self {
308 Self::Role(id.into())
309 }
310
311 #[must_use]
315 pub fn matches(&self, other: &Principal) -> bool {
316 match self {
317 Self::Everyone => true,
318 Self::User(id) => matches!(other, Self::User(other_id) if id == other_id),
319 Self::Group(id) => matches!(other, Self::Group(other_id) if id == other_id),
320 Self::Role(id) => matches!(other, Self::Role(other_id) if id == other_id),
321 }
322 }
323}
324
325impl std::fmt::Display for Principal {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 match self {
328 Self::User(id) => write!(f, "user:{id}"),
329 Self::Group(id) => write!(f, "group:{id}"),
330 Self::Role(id) => write!(f, "role:{id}"),
331 Self::Everyone => write!(f, "*"),
332 }
333 }
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display)]
338#[strum(serialize_all = "lowercase")]
339pub enum Operation {
340 View,
342 Print,
344 Copy,
346 Annotate,
348 Edit,
350 Sign,
352 Decrypt,
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn test_permissions_none() {
362 let perms = Permissions::none();
363 assert!(!perms.view);
364 assert!(!perms.print);
365 assert!(!perms.edit);
366 }
367
368 #[test]
369 fn test_permissions_all() {
370 let perms = Permissions::all();
371 assert!(perms.view);
372 assert!(perms.print);
373 assert!(perms.copy);
374 assert!(perms.annotate);
375 assert!(perms.edit);
376 assert!(perms.sign);
377 assert!(perms.decrypt);
378 }
379
380 #[test]
381 fn test_permissions_read_only() {
382 let perms = Permissions::read_only();
383 assert!(perms.view);
384 assert!(!perms.print);
385 assert!(!perms.edit);
386 }
387
388 #[test]
389 fn test_access_control_default() {
390 let ac = AccessControl::read_only();
391 let user = Principal::user("test@example.com");
392
393 assert!(ac.can(&user, Operation::View));
394 assert!(!ac.can(&user, Operation::Print));
395 assert!(!ac.can(&user, Operation::Edit));
396 }
397
398 #[test]
399 fn test_access_control_with_grant() {
400 let ac = AccessControl::deny_all()
401 .with_grant(PermissionGrant::full_access_for_user("admin@example.com"));
402
403 let admin = Principal::user("admin@example.com");
404 let user = Principal::user("user@example.com");
405
406 assert!(ac.can(&admin, Operation::View));
407 assert!(ac.can(&admin, Operation::Edit));
408 assert!(!ac.can(&user, Operation::View));
409 }
410
411 #[test]
412 fn test_principal_matches() {
413 let everyone = Principal::Everyone;
414 let user = Principal::user("test@example.com");
415
416 assert!(everyone.matches(&user));
417 assert!(user.matches(&user));
418 assert!(!user.matches(&Principal::user("other@example.com")));
419 }
420
421 #[test]
422 fn test_principal_display() {
423 assert_eq!(
424 Principal::user("test@example.com").to_string(),
425 "user:test@example.com"
426 );
427 assert_eq!(Principal::group("admins").to_string(), "group:admins");
428 assert_eq!(Principal::role("reviewer").to_string(), "role:reviewer");
429 assert_eq!(Principal::Everyone.to_string(), "*");
430 }
431
432 #[test]
433 fn test_serialization() {
434 let ac = AccessControl::read_only().with_grant(PermissionGrant::new(
435 Principal::user("admin@example.com"),
436 Permissions::all(),
437 ));
438
439 let json = serde_json::to_string_pretty(&ac).unwrap();
440 assert!(json.contains("\"view\": true"));
441 assert!(json.contains("admin@example.com"));
442
443 let deserialized: AccessControl = serde_json::from_str(&json).unwrap();
444 assert_eq!(deserialized.grants.len(), 1);
445 }
446
447 #[test]
448 fn test_reviewer_permissions() {
449 let perms = Permissions::reviewer();
450 assert!(perms.view);
451 assert!(perms.annotate);
452 assert!(perms.sign);
453 assert!(!perms.edit);
454 }
455
456 #[test]
457 fn test_editor_permissions() {
458 let perms = Permissions::editor();
459 assert!(perms.view);
460 assert!(perms.edit);
461 assert!(!perms.sign);
462 }
463
464 #[test]
465 fn test_permissions_for_specificity() {
466 let ac = AccessControl::deny_all()
469 .with_grant(PermissionGrant::new(
470 Principal::Everyone,
471 Permissions::read_only(),
472 ))
473 .with_grant(PermissionGrant::full_access_for_user("admin@example.com"));
474
475 let admin = Principal::user("admin@example.com");
476 let perms = ac.permissions_for(&admin);
477 assert!(
478 perms.edit,
479 "admin should get full_access, not read_only from Everyone"
480 );
481 assert!(perms.sign);
482
483 let user = Principal::user("user@example.com");
485 let perms = ac.permissions_for(&user);
486 assert!(perms.view);
487 assert!(!perms.edit);
488 }
489}