Skip to main content

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