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