cdx-core 0.7.1

Core library for reading, writing, and validating Codex Document Format (.cdx) files
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
//! Access control and permissions for Codex documents.
//!
//! This module provides a permission system for controlling access to document
//! operations like viewing, printing, copying, editing, and annotating.

use serde::{Deserialize, Serialize};

/// Access control settings for a document.
///
/// Defines default permissions and per-principal permission grants.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccessControl {
    /// Default permissions for unauthenticated/unknown principals.
    pub default: Permissions,

    /// Per-principal permission grants.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub grants: Vec<PermissionGrant>,
}

impl AccessControl {
    /// Create access control with default permissions.
    #[must_use]
    pub fn new(default: Permissions) -> Self {
        Self {
            default,
            grants: Vec::new(),
        }
    }

    /// Create access control with all permissions denied by default.
    #[must_use]
    pub fn deny_all() -> Self {
        Self::new(Permissions::none())
    }

    /// Create access control with read-only permissions by default.
    #[must_use]
    pub fn read_only() -> Self {
        Self::new(Permissions::read_only())
    }

    /// Create access control with all permissions granted by default.
    #[must_use]
    pub fn full_access() -> Self {
        Self::new(Permissions::all())
    }

    /// Add a permission grant for a principal.
    #[must_use]
    pub fn with_grant(mut self, grant: PermissionGrant) -> Self {
        self.grants.push(grant);
        self
    }

    /// Get effective permissions for a principal.
    ///
    /// Checks specific grants (User/Group/Role) first, then falls back to
    /// `Everyone` grants, then the default permissions.
    #[must_use]
    pub fn permissions_for(&self, principal: &Principal) -> &Permissions {
        // Specific matches first (skip Everyone)
        for grant in &self.grants {
            if !matches!(grant.principal, Principal::Everyone) && grant.principal.matches(principal)
            {
                return &grant.permissions;
            }
        }
        // Then wildcard
        for grant in &self.grants {
            if matches!(grant.principal, Principal::Everyone) {
                return &grant.permissions;
            }
        }
        &self.default
    }

    /// Check if a principal can perform an operation.
    #[must_use]
    pub fn can(&self, principal: &Principal, operation: Operation) -> bool {
        let perms = self.permissions_for(principal);
        match operation {
            Operation::View => perms.view,
            Operation::Print => perms.print,
            Operation::Copy => perms.copy,
            Operation::Annotate => perms.annotate,
            Operation::Edit => perms.edit,
            Operation::Sign => perms.sign,
            Operation::Decrypt => perms.decrypt,
        }
    }
}

impl Default for AccessControl {
    fn default() -> Self {
        Self::read_only()
    }
}

/// Permission settings.
///
/// This struct uses boolean flags for each permission type as specified by the
/// Codex Document Format specification. Each flag represents an independent
/// permission that can be granted or denied.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::struct_excessive_bools)]
pub struct Permissions {
    /// Permission to view/read the document.
    #[serde(default = "default_true")]
    pub view: bool,

    /// Permission to print the document.
    #[serde(default)]
    pub print: bool,

    /// Permission to copy content from the document.
    #[serde(default)]
    pub copy: bool,

    /// Permission to add annotations/comments.
    #[serde(default)]
    pub annotate: bool,

    /// Permission to edit the document.
    #[serde(default)]
    pub edit: bool,

    /// Permission to sign the document.
    #[serde(default)]
    pub sign: bool,

    /// Permission to decrypt (if encrypted).
    #[serde(default)]
    pub decrypt: bool,
}

fn default_true() -> bool {
    true
}

impl Permissions {
    /// Create permissions with all operations denied.
    #[must_use]
    pub const fn none() -> Self {
        Self {
            view: false,
            print: false,
            copy: false,
            annotate: false,
            edit: false,
            sign: false,
            decrypt: false,
        }
    }

    /// Create permissions with all operations allowed.
    #[must_use]
    pub const fn all() -> Self {
        Self {
            view: true,
            print: true,
            copy: true,
            annotate: true,
            edit: true,
            sign: true,
            decrypt: true,
        }
    }

    /// Create read-only permissions (view only).
    #[must_use]
    pub const fn read_only() -> Self {
        Self {
            view: true,
            print: false,
            copy: false,
            annotate: false,
            edit: false,
            sign: false,
            decrypt: false,
        }
    }

    /// Create view and print permissions.
    #[must_use]
    pub const fn view_and_print() -> Self {
        Self {
            view: true,
            print: true,
            copy: false,
            annotate: false,
            edit: false,
            sign: false,
            decrypt: false,
        }
    }

    /// Create reviewer permissions (view, annotate, sign).
    #[must_use]
    pub const fn reviewer() -> Self {
        Self {
            view: true,
            print: true,
            copy: true,
            annotate: true,
            edit: false,
            sign: true,
            decrypt: true,
        }
    }

    /// Create editor permissions (all except sign).
    #[must_use]
    pub const fn editor() -> Self {
        Self {
            view: true,
            print: true,
            copy: true,
            annotate: true,
            edit: true,
            sign: false,
            decrypt: true,
        }
    }
}

impl Default for Permissions {
    fn default() -> Self {
        Self::read_only()
    }
}

/// A permission grant for a specific principal.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PermissionGrant {
    /// The principal receiving the permissions.
    pub principal: Principal,

    /// The granted permissions.
    pub permissions: Permissions,
}

impl PermissionGrant {
    /// Create a new permission grant.
    #[must_use]
    pub fn new(principal: Principal, permissions: Permissions) -> Self {
        Self {
            principal,
            permissions,
        }
    }

    /// Grant full access to a user.
    #[must_use]
    pub fn full_access_for_user(email: impl Into<String>) -> Self {
        Self::new(Principal::User(email.into()), Permissions::all())
    }

    /// Grant reviewer access to a user.
    #[must_use]
    pub fn reviewer_for_user(email: impl Into<String>) -> Self {
        Self::new(Principal::User(email.into()), Permissions::reviewer())
    }

    /// Grant editor access to a user.
    #[must_use]
    pub fn editor_for_user(email: impl Into<String>) -> Self {
        Self::new(Principal::User(email.into()), Permissions::editor())
    }
}

/// A security principal (user, group, or role).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", content = "id", rename_all = "lowercase")]
pub enum Principal {
    /// A specific user identified by email or ID.
    User(String),

    /// A group of users.
    Group(String),

    /// A role (e.g., "admin", "reviewer").
    Role(String),

    /// Everyone (wildcard).
    Everyone,
}

impl Principal {
    /// Create a user principal.
    #[must_use]
    pub fn user(id: impl Into<String>) -> Self {
        Self::User(id.into())
    }

    /// Create a group principal.
    #[must_use]
    pub fn group(id: impl Into<String>) -> Self {
        Self::Group(id.into())
    }

    /// Create a role principal.
    #[must_use]
    pub fn role(id: impl Into<String>) -> Self {
        Self::Role(id.into())
    }

    /// Check if this principal matches another.
    ///
    /// `Everyone` matches all principals. Otherwise, exact match is required.
    #[must_use]
    pub fn matches(&self, other: &Principal) -> bool {
        match self {
            Self::Everyone => true,
            Self::User(id) => matches!(other, Self::User(other_id) if id == other_id),
            Self::Group(id) => matches!(other, Self::Group(other_id) if id == other_id),
            Self::Role(id) => matches!(other, Self::Role(other_id) if id == other_id),
        }
    }
}

impl std::fmt::Display for Principal {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::User(id) => write!(f, "user:{id}"),
            Self::Group(id) => write!(f, "group:{id}"),
            Self::Role(id) => write!(f, "role:{id}"),
            Self::Everyone => write!(f, "*"),
        }
    }
}

/// Operations that can be controlled by permissions.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display)]
#[strum(serialize_all = "lowercase")]
pub enum Operation {
    /// View/read the document.
    View,
    /// Print the document.
    Print,
    /// Copy content from the document.
    Copy,
    /// Add annotations or comments.
    Annotate,
    /// Edit the document content.
    Edit,
    /// Add a signature.
    Sign,
    /// Decrypt the document (if encrypted).
    Decrypt,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_permissions_none() {
        let perms = Permissions::none();
        assert!(!perms.view);
        assert!(!perms.print);
        assert!(!perms.edit);
    }

    #[test]
    fn test_permissions_all() {
        let perms = Permissions::all();
        assert!(perms.view);
        assert!(perms.print);
        assert!(perms.copy);
        assert!(perms.annotate);
        assert!(perms.edit);
        assert!(perms.sign);
        assert!(perms.decrypt);
    }

    #[test]
    fn test_permissions_read_only() {
        let perms = Permissions::read_only();
        assert!(perms.view);
        assert!(!perms.print);
        assert!(!perms.edit);
    }

    #[test]
    fn test_access_control_default() {
        let ac = AccessControl::read_only();
        let user = Principal::user("test@example.com");

        assert!(ac.can(&user, Operation::View));
        assert!(!ac.can(&user, Operation::Print));
        assert!(!ac.can(&user, Operation::Edit));
    }

    #[test]
    fn test_access_control_with_grant() {
        let ac = AccessControl::deny_all()
            .with_grant(PermissionGrant::full_access_for_user("admin@example.com"));

        let admin = Principal::user("admin@example.com");
        let user = Principal::user("user@example.com");

        assert!(ac.can(&admin, Operation::View));
        assert!(ac.can(&admin, Operation::Edit));
        assert!(!ac.can(&user, Operation::View));
    }

    #[test]
    fn test_principal_matches() {
        let everyone = Principal::Everyone;
        let user = Principal::user("test@example.com");

        assert!(everyone.matches(&user));
        assert!(user.matches(&user));
        assert!(!user.matches(&Principal::user("other@example.com")));
    }

    #[test]
    fn test_principal_display() {
        assert_eq!(
            Principal::user("test@example.com").to_string(),
            "user:test@example.com"
        );
        assert_eq!(Principal::group("admins").to_string(), "group:admins");
        assert_eq!(Principal::role("reviewer").to_string(), "role:reviewer");
        assert_eq!(Principal::Everyone.to_string(), "*");
    }

    #[test]
    fn test_serialization() {
        let ac = AccessControl::read_only().with_grant(PermissionGrant::new(
            Principal::user("admin@example.com"),
            Permissions::all(),
        ));

        let json = serde_json::to_string_pretty(&ac).unwrap();
        assert!(json.contains("\"view\": true"));
        assert!(json.contains("admin@example.com"));

        let deserialized: AccessControl = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.grants.len(), 1);
    }

    #[test]
    fn test_reviewer_permissions() {
        let perms = Permissions::reviewer();
        assert!(perms.view);
        assert!(perms.annotate);
        assert!(perms.sign);
        assert!(!perms.edit);
    }

    #[test]
    fn test_editor_permissions() {
        let perms = Permissions::editor();
        assert!(perms.view);
        assert!(perms.edit);
        assert!(!perms.sign);
    }

    #[test]
    fn test_permissions_for_specificity() {
        // Everyone → read_only, but admin user → full_access
        // Admin should get full_access even though Everyone appears first
        let ac = AccessControl::deny_all()
            .with_grant(PermissionGrant::new(
                Principal::Everyone,
                Permissions::read_only(),
            ))
            .with_grant(PermissionGrant::full_access_for_user("admin@example.com"));

        let admin = Principal::user("admin@example.com");
        let perms = ac.permissions_for(&admin);
        assert!(
            perms.edit,
            "admin should get full_access, not read_only from Everyone"
        );
        assert!(perms.sign);

        // Regular user should still get Everyone's read_only
        let user = Principal::user("user@example.com");
        let perms = ac.permissions_for(&user);
        assert!(perms.view);
        assert!(!perms.edit);
    }
}