cts_common/auth/claims/
scope.rs

1use derive_more::Display;
2use std::{
3    any::type_name,
4    fmt::{self, Debug},
5    ops::{BitAnd, BitOr},
6};
7
8use super::Role;
9use crate::claims::common::ScalarOrArray;
10use serde::{Deserialize, Serialize};
11
12/// A `Scope` is set of [`Permission`]s with an allocation-free representation and is cheaply copyable.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
14#[display("{_0}")]
15pub struct Scope(PermissionFlags);
16
17impl Default for Scope {
18    /// Very explicitly, a default [`Scope`] has no permissions.
19    /// This is important to avoid accidentally granting permissions that were not intended.
20    fn default() -> Self {
21        Self::with_no_permissions()
22    }
23}
24
25/// A compact representation of a set of [`Permission`] values.
26///
27/// `PermissionFlags` implements [`IntoIterator<Item = Permission>`].
28#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct PermissionFlags(u64);
30
31/// A `Permission` represents authorisation to perform a specific operation.
32///
33/// Each enum variant represents a permission category and each category has its own specific permissions.
34#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
35pub enum Permission {
36    #[display("access_key:{_0}")]
37    AccessKey(AccessKeyPermission),
38
39    #[display("dataset:{_0}")]
40    Keyset(KeysetPermission),
41
42    #[display("client:{_0}")]
43    Client(ClientPermission),
44
45    #[display("data_key:{_0}")]
46    DataKey(DataKeyPermission),
47
48    #[display("cs_support:{_0}")]
49    Support(SupportPermission),
50
51    #[display("logging:{_0}")]
52    Logging(LoggingPermission),
53}
54
55/// Represents a permission reserved for a CipherStash employee in a customer support role.
56///
57/// **NOTE** this is here for backwards compatibility and should be removed - it seems more like a role.
58#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
59#[repr(u8)]
60pub enum SupportPermission {
61    #[display("admin")]
62    Admin = 0b0000_0001,
63}
64
65/// Represents a permission reserved for a audit logging.
66///
67/// **NOTE** this is here for backwards compatibility and should be removed - it seems more like a role.
68#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
69#[repr(u8)]
70pub enum LoggingPermission {
71    #[display("append")]
72    Append = 0b0000_0001,
73}
74
75/// An `AccessKey` permission.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
77#[repr(u8)]
78pub enum AccessKeyPermission {
79    /// Permission to create Access Keys
80    #[display("create")]
81    Create = 0b0000_0001,
82
83    /// Permission to list Access Keys
84    #[display("list")]
85    List = 0b0000_0010,
86
87    /// Permission to delete Access Keys
88    #[display("delete")]
89    Delete = 0b0000_0100,
90}
91
92/// A `Keyset` permission.
93#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
94#[repr(u8)]
95pub enum KeysetPermission {
96    /// Permission to create Keysets
97    #[display("create")]
98    Create = 0b0000_0001,
99
100    /// Permission to list Keysets
101    #[display("list")]
102    List = 0b0000_0010,
103
104    /// Permission to disable Keysets
105    #[display("disable")]
106    Disable = 0b0000_0100,
107
108    /// Permission to enable Keysets
109    #[display("enable")]
110    Enable = 0b0000_1000,
111
112    /// Permission to grant Keysets
113    #[display("grant")]
114    Grant = 0b0001_0000,
115
116    /// Permission to modify Keysets
117    #[display("modify")]
118    Modify = 0b0010_0000,
119
120    /// Permission to revoke Keysets
121    #[display("revoke")]
122    Revoke = 0b0100_0000,
123}
124
125/// A `Keyset` permission.
126#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
127#[repr(u8)]
128pub enum ClientPermission {
129    /// Permission to create Clients
130    #[display("create")]
131    Create = 0b0000_0001,
132
133    /// Permission to list Clients
134    #[display("list")]
135    List = 0b0000_0010,
136
137    /// Permission to delete Clients
138    #[display("delete")]
139    Delete = 0b0000_0100,
140}
141
142/// A `DataKey` permission.
143#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)]
144#[repr(u8)]
145pub enum DataKeyPermission {
146    /// Permission to generate DataKeys for encrypting data
147    #[display("generate")]
148    Generate = 0b0000_0001,
149
150    /// Permission to retrieve DataKeys for decrypting data
151    #[display("retrieve")]
152    Retrieve = 0b0000_0010,
153}
154
155impl Permission {
156    /// Parse a `Permission` from `input`.
157    ///
158    /// If the input does not match the known syntax for any permission the `None` is returned.
159    fn parse(input: &str) -> Option<Self> {
160        match input {
161            s if s.starts_with("keyset:") => Some(Self::Keyset(KeysetPermission::parse(
162                &s["keyset:".len()..],
163            )?)),
164            s if s.starts_with("access_key:") => Some(Self::AccessKey(AccessKeyPermission::parse(
165                &s["access_key:".len()..],
166            )?)),
167            // Backwards compatibilty: deal with any "dataset:*" permissions in JWTs
168            s if s.starts_with("dataset:") => Some(Self::Keyset(KeysetPermission::parse(
169                &s["dataset:".len()..],
170            )?)),
171            s if s.starts_with("data_key:") => Some(Self::DataKey(DataKeyPermission::parse(
172                &s["data_key:".len()..],
173            )?)),
174            s if s.starts_with("client:") => Some(Self::Client(ClientPermission::parse(
175                &s["client:".len()..],
176            )?)),
177            s if s.starts_with("cs_support:") => Some(Self::Support(SupportPermission::parse(
178                &s["cs_support:".len()..],
179            )?)),
180            s if s.starts_with("logging:") => Some(Self::Logging(LoggingPermission::parse(
181                &s["logging:".len()..],
182            )?)),
183            _ => None,
184        }
185    }
186
187    /// Converts `self` to a `PermissionFlags` with a single bit set that represents `self`.
188    const fn to_flags(self) -> PermissionFlags {
189        // These static assertions will fail if a permission type is not #[repr(u8)], which is critical for knowing that
190        // the flags will merge correctly into a u64.  If the static assertions fail then the bit shifts and/or the size
191        // of PermissionFlags must change.
192        const _: () = assert!(size_of::<KeysetPermission>() == 1);
193        const _: () = assert!(size_of::<ClientPermission>() == 1);
194        const _: () = assert!(size_of::<DataKeyPermission>() == 1);
195        const _: () = assert!(size_of::<SupportPermission>() == 1);
196        const _: () = assert!(size_of::<LoggingPermission>() == 1);
197        const _: () = assert!(size_of::<AccessKeyPermission>() == 1);
198
199        match self {
200            Permission::Keyset(permission) => PermissionFlags(permission as u64),
201            Permission::Client(permission) => PermissionFlags((permission as u64) << 8),
202            Permission::DataKey(permission) => PermissionFlags((permission as u64) << 16),
203            Permission::Support(permission) => PermissionFlags((permission as u64) << 24),
204            Permission::Logging(permission) => PermissionFlags((permission as u64) << 32),
205            Permission::AccessKey(permission) => PermissionFlags((permission as u64) << 40),
206        }
207    }
208
209    /// All permissions.
210    const ALL: &[Permission] = &[
211        // Keyset permissions
212        Self::Keyset(KeysetPermission::Create),
213        Self::Keyset(KeysetPermission::List),
214        Self::Keyset(KeysetPermission::Enable),
215        Self::Keyset(KeysetPermission::Disable),
216        Self::Keyset(KeysetPermission::Grant),
217        Self::Keyset(KeysetPermission::Modify),
218        Self::Keyset(KeysetPermission::Revoke),
219        // DataKey permissions
220        Self::DataKey(DataKeyPermission::Generate),
221        Self::DataKey(DataKeyPermission::Retrieve),
222        // Client permissions
223        Self::Client(ClientPermission::Create),
224        Self::Client(ClientPermission::List),
225        Self::Client(ClientPermission::Delete),
226        // AccessKey permissions
227        Self::AccessKey(AccessKeyPermission::Create),
228        Self::AccessKey(AccessKeyPermission::List),
229        Self::AccessKey(AccessKeyPermission::Delete),
230        Self::Support(SupportPermission::Admin),
231        Self::Logging(LoggingPermission::Append),
232    ];
233}
234
235macro_rules! impl_from {
236    ($variant:ident, $ty:ty) => {
237        impl From<$ty> for Permission {
238            fn from(value: $ty) -> Self {
239                Self::$variant(value)
240            }
241        }
242    };
243}
244
245impl_from!(AccessKey, AccessKeyPermission);
246impl_from!(Keyset, KeysetPermission);
247impl_from!(DataKey, DataKeyPermission);
248impl_from!(Client, ClientPermission);
249impl_from!(Support, SupportPermission);
250impl_from!(Logging, LoggingPermission);
251
252impl PermissionFlags {
253    /// Create a new empty set of permissions.
254    pub const fn empty() -> Self {
255        Self(0)
256    }
257
258    /// Return permissions with a single bit set for `flag`.
259    pub const fn with_flag(flag: Permission) -> Self {
260        Self::empty().add(flag)
261    }
262
263    /// Add `permission` to `self` and return a new `PermissionFlags`.
264    pub const fn add(self, permission: Permission) -> Self {
265        Self(self.0 | permission.to_flags().0)
266    }
267
268    /// Merge `self` with `other` returning a new `PermissionFlags`.
269    pub const fn merge(self, other: Self) -> Self {
270        Self(self.0 | other.0)
271    }
272
273    /// Count how many permission flags are set in `self`.
274    fn count_permissions(&self) -> u32 {
275        self.0.count_ones()
276    }
277}
278
279impl IntoIterator for PermissionFlags {
280    type IntoIter = PermissionsIter;
281    type Item = Permission;
282
283    fn into_iter(self) -> Self::IntoIter {
284        PermissionsIter(self, Permission::ALL)
285    }
286}
287
288impl FromIterator<Permission> for PermissionFlags {
289    fn from_iter<T: IntoIterator<Item = Permission>>(iter: T) -> Self {
290        let iter = iter.into_iter();
291        let mut flags = Self(0);
292        for permission in iter {
293            flags = flags.add(permission);
294        }
295        flags
296    }
297}
298
299/// Iterator that iterates over all flags in `self` producing [`Permission`] values.
300pub struct PermissionsIter(PermissionFlags, &'static [Permission]);
301
302impl Iterator for PermissionsIter {
303    type Item = Permission;
304
305    fn next(&mut self) -> Option<Self::Item> {
306        // Iteration works by looping through all of the possible permissions and checking if a flag for each permission
307        // exists in the PermissionFlags.
308        loop {
309            if let Some((first, rest)) = self.1.split_first() {
310                // Effectively pops the front of the slice from self.1
311                self.1 = rest;
312                if self.0 & first.to_flags() == first.to_flags() {
313                    return Some(*first);
314                }
315            } else {
316                // There are no more permissions left to check.
317                return None;
318            }
319        }
320    }
321}
322
323impl BitAnd for PermissionFlags {
324    type Output = Self;
325
326    fn bitand(self, rhs: Self) -> Self::Output {
327        Self(self.0 & rhs.0)
328    }
329}
330
331impl BitOr for PermissionFlags {
332    type Output = Self;
333
334    fn bitor(self, rhs: Self) -> Self::Output {
335        Self(self.0 | rhs.0)
336    }
337}
338
339impl fmt::Display for PermissionFlags {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341        let count = self.count_permissions() as usize;
342
343        // NOTE: Iterator::intersperse is unstable
344        for (idx, scope) in self.into_iter().enumerate() {
345            fmt::Display::fmt(&scope, f)?;
346            if idx < count - 1 {
347                fmt::Display::fmt(", ", f)?;
348            }
349        }
350
351        Ok(())
352    }
353}
354
355impl Debug for PermissionFlags {
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357        let count = self.count_permissions() as usize;
358
359        f.write_fmt(format_args!("{}(", type_name::<Self>()))?;
360
361        for (idx, scope) in self.into_iter().enumerate() {
362            fmt::Display::fmt(&scope, f)?;
363            if idx < count - 1 {
364                fmt::Display::fmt("|", f)?;
365            }
366        }
367
368        f.write_str(")")?;
369
370        Ok(())
371    }
372}
373
374impl KeysetPermission {
375    fn parse(input: &str) -> Option<Self> {
376        match input {
377            "create" => Some(Self::Create),
378            "list" => Some(Self::List),
379            "disable" => Some(Self::Disable),
380            "enable" => Some(Self::Enable),
381            "grant" => Some(Self::Grant),
382            "modify" => Some(Self::Modify),
383            "revoke" => Some(Self::Revoke),
384            _ => None,
385        }
386    }
387}
388
389impl ClientPermission {
390    fn parse(input: &str) -> Option<Self> {
391        match input {
392            "create" => Some(Self::Create),
393            "list" => Some(Self::List),
394            "delete" => Some(Self::Delete),
395            _ => None,
396        }
397    }
398}
399
400impl DataKeyPermission {
401    fn parse(input: &str) -> Option<Self> {
402        match input {
403            "generate" => Some(Self::Generate),
404            "retrieve" => Some(Self::Retrieve),
405            _ => None,
406        }
407    }
408}
409
410impl SupportPermission {
411    fn parse(input: &str) -> Option<Self> {
412        match input {
413            "admin" => Some(Self::Admin),
414            _ => None,
415        }
416    }
417}
418
419impl LoggingPermission {
420    fn parse(input: &str) -> Option<Self> {
421        match input {
422            "append" => Some(Self::Append),
423            _ => None,
424        }
425    }
426}
427
428impl AccessKeyPermission {
429    fn parse(input: &str) -> Option<Self> {
430        match input {
431            "create" => Some(Self::Create),
432            "list" => Some(Self::List),
433            "delete" => Some(Self::Delete),
434            _ => None,
435        }
436    }
437}
438
439impl Scope {
440    /// Makes a `Scope` containing all [`Permission`]s in `permissions`.
441    pub const fn with_permissions(permissions: PermissionFlags) -> Self {
442        Self(permissions)
443    }
444
445    /// Makes a `Scope` containing `permission`.
446    pub const fn with_permission(permission: Permission) -> Self {
447        Self(PermissionFlags::empty().add(permission))
448    }
449
450    /// Makes a `Scope` without permissions.
451    pub fn with_no_permissions() -> Self {
452        Self(PermissionFlags::empty())
453    }
454
455    /// Merges `self` with `other` returning a new `Scope` containing the union of the permissions of both scopes.
456    pub fn merge(self, other: Self) -> Self {
457        Self(self.0 | other.0)
458    }
459
460    /// Parses a `Scope` from `input`. Always succeeds, even when the string does not contain a syntactically valid
461    /// scope but an empty `Scope` is returned when this occurs.
462    pub fn parse(input: &str) -> Self {
463        let permissions: PermissionFlags =
464            input
465                .split_whitespace()
466                .fold(PermissionFlags::empty(), |acc, s| {
467                    if let Some(permission) = Permission::parse(s) {
468                        acc.add(permission)
469                    } else {
470                        acc
471                    }
472                });
473
474        Self(permissions)
475    }
476
477    /// Checks if `self` contains `permission`.
478    pub fn has_permission(&self, permission: Permission) -> bool {
479        self.0 & permission.to_flags() == permission.to_flags()
480    }
481
482    /// Checks if `self` has at least all of the permissions of `other`.
483    pub fn has_all_permissions(&self, other: Self) -> bool {
484        self.0 & other.0 == other.0
485    }
486
487    /// Counts the number of permissions in `self`.
488    pub fn count_permissions(&self) -> u32 {
489        self.0.count_permissions()
490    }
491
492    /// Returns an [`Iterator`] over the [`Permission`]s in `self`.
493    pub fn permissions(&self) -> impl Iterator<Item = Permission> {
494        self.0.into_iter()
495    }
496}
497
498/// The set of [`Scope`]s assigned to an authenticated ZeroKMS admin role.
499/// See also [`Role`].
500pub const WORKSPACE_ADMIN_SCOPE: Scope = Scope(
501    PermissionFlags::empty()
502        .add(Permission::AccessKey(AccessKeyPermission::Create))
503        .add(Permission::AccessKey(AccessKeyPermission::List))
504        .add(Permission::AccessKey(AccessKeyPermission::Delete))
505        .add(Permission::Keyset(KeysetPermission::Create))
506        .add(Permission::Keyset(KeysetPermission::List))
507        .add(Permission::Keyset(KeysetPermission::Disable))
508        .add(Permission::Keyset(KeysetPermission::Enable))
509        .add(Permission::Keyset(KeysetPermission::Grant))
510        .add(Permission::Keyset(KeysetPermission::Modify))
511        .add(Permission::Keyset(KeysetPermission::Revoke))
512        .add(Permission::Client(ClientPermission::Create))
513        .add(Permission::Client(ClientPermission::List))
514        .add(Permission::Client(ClientPermission::Delete))
515        .add(Permission::DataKey(DataKeyPermission::Generate))
516        .add(Permission::DataKey(DataKeyPermission::Retrieve)),
517);
518
519/// The set of [`Scope`]s assigned to an authenticated ZeroKMS control role.
520/// See also [`Role`].
521/// This role is used by services that need to manage keysets for multi-tenant setups.
522pub const WORKSPACE_CONTROL_SCOPE: Scope = Scope(
523    PermissionFlags::empty()
524        .add(Permission::Keyset(KeysetPermission::Create))
525        .add(Permission::Keyset(KeysetPermission::List))
526        .add(Permission::Keyset(KeysetPermission::Disable))
527        .add(Permission::Keyset(KeysetPermission::Enable))
528        .add(Permission::Keyset(KeysetPermission::Grant))
529        .add(Permission::Keyset(KeysetPermission::Modify))
530        .add(Permission::Keyset(KeysetPermission::Revoke))
531        .add(Permission::Client(ClientPermission::List)),
532);
533
534/// The set of [`Scope`]s assigned to an authenticated ZeroKMS member role.
535/// See also [`Role`].
536pub const WORKSPACE_MEMBER_SCOPE: Scope = Scope(
537    PermissionFlags::empty()
538        .add(Permission::Keyset(KeysetPermission::List))
539        .add(Permission::DataKey(DataKeyPermission::Generate))
540        .add(Permission::DataKey(DataKeyPermission::Retrieve)),
541);
542
543pub const LOGGING_SCOPE: Scope =
544    Scope(PermissionFlags::empty().add(Permission::Logging(LoggingPermission::Append)));
545
546impl From<Option<Role>> for Scope {
547    fn from(role: Option<Role>) -> Self {
548        match role {
549            Some(role) => role.scope(),
550            None => Default::default(),
551        }
552    }
553}
554
555impl From<Vec<String>> for Scope {
556    fn from(scope_names: Vec<String>) -> Self {
557        Scope(
558            scope_names
559                .into_iter()
560                .fold(PermissionFlags::empty(), |acc, s| {
561                    if let Some(permission) = Permission::parse(&s) {
562                        acc.merge(permission.to_flags())
563                    } else {
564                        acc
565                    }
566                }),
567        )
568    }
569}
570
571impl Serialize for Permission {
572    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
573    where
574        S: serde::Serializer,
575    {
576        self.to_string().serialize(serializer)
577    }
578}
579
580impl Serialize for Scope {
581    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
582    where
583        S: serde::Serializer,
584    {
585        let flags: Vec<Permission> = self.permissions().collect();
586
587        // TODO: serialize directly from an Iterator to avoid allocating a Vec.
588        match &flags[..] {
589            &[] => serializer.serialize_none(),
590            &[permission] => Some(ScalarOrArray::Scalar(permission)).serialize(serializer),
591            flags => Some(ScalarOrArray::Array(flags.iter().collect())).serialize(serializer),
592        }
593    }
594}
595
596// Custom deserialization for `Scope` to handle both single string and array of strings.
597impl<'de> Deserialize<'de> for Scope {
598    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
599    where
600        D: serde::Deserializer<'de>,
601    {
602        let items: Option<ScalarOrArray<String>> = Deserialize::deserialize(deserializer)?;
603        match items {
604            Some(ScalarOrArray::Array(items)) => Ok(Scope::from(items)),
605            Some(ScalarOrArray::Scalar(item)) => Ok(Scope(
606                Permission::parse(&item)
607                    .map(|p| p.to_flags())
608                    .unwrap_or(PermissionFlags::empty()),
609            )),
610            None => Ok(Scope::with_no_permissions()),
611        }
612    }
613}
614
615impl From<String> for Scope {
616    fn from(value: String) -> Self {
617        Scope::parse(&value)
618    }
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624    use serde_json::{json, Value};
625
626    mod parse {
627        use super::*;
628
629        #[test]
630        fn empty_string_parses_to_empty_scope() {
631            let scope = Scope::parse("");
632            assert_eq!(scope, Scope::with_no_permissions());
633        }
634
635        #[test]
636        fn single_scope_parses_correctly() {
637            let scope = Scope::parse("dataset:create");
638            assert!(scope.has_permission(Permission::Keyset(KeysetPermission::Create)));
639            assert_eq!(scope.count_permissions(), 1);
640        }
641
642        #[test]
643        fn multiple_scopes_parse_correctly() {
644            let scope = Scope::parse("dataset:create client:list");
645            assert!(scope.has_permission(Permission::Keyset(KeysetPermission::Create)));
646            assert!(scope.has_permission(Permission::Client(ClientPermission::List)));
647            assert_eq!(scope.count_permissions(), 2);
648        }
649    }
650
651    mod serialize {
652        use super::*;
653
654        #[test]
655        fn empty_scope_serializes_as_null() {
656            let scope = Scope::default();
657            let serialized = serde_json::to_value(scope).unwrap();
658            assert_eq!(serialized, Value::Null);
659        }
660
661        #[test]
662        fn single_scope_serializes_as_string() {
663            let scope = Scope::parse("dataset:create");
664            let serialized = serde_json::to_value(scope).unwrap();
665            assert_eq!(serialized, json!("dataset:create"));
666        }
667
668        #[test]
669        fn multiple_scopes_serializes_as_array() {
670            let scope = Scope::parse("dataset:create client:list");
671            let serialized = serde_json::to_value(scope).unwrap();
672
673            assert!(
674                serialized == json!(["dataset:create", "client:list"])
675                    || serialized == json!(["client:list", "dataset:create"]),
676                "Expected serialized scope to be an array of scopes, got: {serialized:?}"
677            );
678        }
679    }
680
681    mod deserialize {
682        use super::*;
683
684        #[test]
685        fn empty_scope_deserializes_from_null() {
686            let json = Value::Null;
687            let scope: Scope = serde_json::from_value(json).unwrap();
688            assert_eq!(scope, Scope::default());
689        }
690
691        #[test]
692        fn empty_string_deserializes_to_empty_scope() {
693            let json = json!("");
694            let scope: Scope = serde_json::from_value(json).unwrap();
695            assert_eq!(scope, Scope::default());
696        }
697
698        #[test]
699        fn single_scope_deserializes_from_string() {
700            let json = json!("dataset:create");
701            let scope: Scope = serde_json::from_value(json).unwrap();
702            assert_eq!(scope, Scope::parse("dataset:create"));
703        }
704
705        #[test]
706        fn multiple_scopes_deserialize_from_array() {
707            let json = json!(["dataset:create", "client:list"]);
708            let scope: Scope = serde_json::from_value(json).unwrap();
709            assert!(scope.has_permission(Permission::Keyset(KeysetPermission::Create)));
710            assert!(scope.has_permission(Permission::Client(ClientPermission::List)));
711            assert_eq!(
712                scope.count_permissions(),
713                2,
714                "Expected scope to contain 2 items"
715            );
716        }
717
718        #[test]
719        fn invalid_scope_deserializes_from_invalid_value() {
720            let json = json!(12345);
721            let result: Result<Scope, _> = serde_json::from_value(json);
722            assert!(
723                result.is_err(),
724                "Expected deserialization to fail for invalid scope"
725            );
726        }
727    }
728
729    mod merge {
730        use super::*;
731
732        #[test]
733        fn non_overlapping() {
734            let scope1 = Scope::parse("dataset:create");
735            let scope2 = Scope::parse("client:list");
736            let merged = scope1.merge(scope2);
737
738            assert!(merged.has_permission(Permission::Keyset(KeysetPermission::Create)));
739            assert!(merged.has_permission(Permission::Client(ClientPermission::List)));
740            assert_eq!(merged.count_permissions(), 2);
741        }
742
743        #[test]
744        fn overlapping() {
745            let scope1 = Scope::parse("dataset:create client:list");
746            let scope2 = Scope::parse("client:list data_key:generate");
747            let merged = scope1.merge(scope2);
748            assert!(merged.has_permission(Permission::Keyset(KeysetPermission::Create)));
749            assert!(merged.has_permission(Permission::Client(ClientPermission::List)));
750            assert!(merged.has_permission(Permission::DataKey(DataKeyPermission::Generate)));
751            assert_eq!(merged.count_permissions(), 3);
752        }
753    }
754
755    mod ensure_bitflag_representation_has_no_collisions {
756        use super::*;
757
758        mod scope_with_all_permissions {
759            use super::*;
760
761            #[test]
762            fn for_each_permission_has_permission_returns_true() {
763                let mut scope = Scope::with_no_permissions();
764                for permission in Permission::ALL {
765                    scope = scope.merge(Scope::with_permission(*permission));
766                }
767
768                for permission in Permission::ALL {
769                    assert!(scope.has_permission(*permission))
770                }
771            }
772        }
773
774        mod scope_with_all_but_one_permssion {
775            use super::*;
776
777            #[test]
778            fn has_permission_returns_true_for_all_except_one_permission() {
779                for without_permission in Permission::ALL {
780                    let permissions: Vec<_> = Permission::ALL
781                        .iter()
782                        .filter(|p| *p != without_permission)
783                        .copied()
784                        .collect();
785                    let scope = Scope::with_permissions(PermissionFlags::from_iter(
786                        permissions.into_iter(),
787                    ));
788
789                    for permission in Permission::ALL {
790                        if permission != without_permission {
791                            assert!(scope.has_permission(*permission))
792                        } else {
793                            assert!(!scope.has_permission(*permission))
794                        }
795                    }
796                }
797            }
798        }
799    }
800}