authly_common/
id.rs

1//! Authly identifier types
2use std::{
3    fmt::{Debug, Display},
4    hash::Hash,
5    marker::PhantomData,
6    str::FromStr,
7};
8
9use anyhow::{anyhow, Context};
10use kind::{IdKind, Kind};
11use rand::Rng;
12use serde::{Deserialize, Serialize};
13use subset::{IdKindSubset, IdKindSupersetOf};
14
15use crate::FromStrVisitor;
16
17/// Authly generic 128-bit identifier
18pub struct Id128<K>([u8; 16], PhantomData<K>);
19
20impl<K> Id128<K> {
21    /// Construct a new identifier from a 128-bit unsigned int.
22    pub const fn from_uint(val: u128) -> Self {
23        Self(val.to_be_bytes(), PhantomData)
24    }
25
26    /// Construct a new identifier from a reference to a byte array, without type information
27    pub const fn from_raw_array(array: &[u8; 16]) -> Self {
28        Self(*array, PhantomData)
29    }
30
31    /// Get the byte-wise representation of the ID, without type information
32    pub const fn to_raw_array(self) -> [u8; 16] {
33        self.0
34    }
35
36    /// Try to deserialize from a raw byte representation, without type information
37    pub fn from_raw_bytes(bytes: &[u8]) -> Option<Self> {
38        Some(Self(bytes.try_into().ok()?, PhantomData))
39    }
40
41    /// Create a new random identifier.
42    pub fn random() -> Self {
43        loop {
44            let id: u128 = rand::thread_rng().gen();
45            // low IDs are reserved for builtin/fixed
46            if id > u16::MAX as u128 {
47                return Self(id.to_be_bytes(), PhantomData);
48            }
49        }
50    }
51
52    /// Convert to an unsigned integer
53    pub fn to_uint(&self) -> u128 {
54        u128::from_be_bytes(self.0)
55    }
56}
57
58impl<K: IdKind> Id128<K> {
59    /// Infallibly convert this into a [DynamicId]
60    pub fn upcast<KS: IdKindSubset + IdKindSupersetOf<K>>(self) -> DynamicId<KS> {
61        DynamicId {
62            id: self.0,
63            kind: K::kind(),
64            _subset: PhantomData,
65        }
66    }
67}
68
69impl<K> Clone for Id128<K> {
70    fn clone(&self) -> Self {
71        *self
72    }
73}
74
75impl<K> Copy for Id128<K> {}
76
77impl<K> PartialEq for Id128<K> {
78    fn eq(&self, other: &Self) -> bool {
79        self.0 == other.0
80    }
81}
82
83impl<K> Eq for Id128<K> {}
84
85impl<K> PartialOrd<Id128<K>> for Id128<K> {
86    fn partial_cmp(&self, other: &Id128<K>) -> Option<std::cmp::Ordering> {
87        Some(self.0.cmp(&other.0))
88    }
89}
90
91impl<K> Ord for Id128<K> {
92    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
93        self.0.cmp(&other.0)
94    }
95}
96
97impl<K> Hash for Id128<K> {
98    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
99        self.0.hash(state);
100    }
101}
102
103impl<K> From<[u8; 16]> for Id128<K> {
104    fn from(value: [u8; 16]) -> Self {
105        Self(value, PhantomData)
106    }
107}
108
109impl<K> From<&[u8; 16]> for Id128<K> {
110    fn from(value: &[u8; 16]) -> Self {
111        Self(*value, PhantomData)
112    }
113}
114
115impl<K> Debug for Id128<K> {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        write!(f, "{}", hexhex::hex(&self.0))
118    }
119}
120
121impl<K: IdKind> Display for Id128<K> {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        write!(f, "{}.{}", K::kind().str_prefix(), hexhex::hex(&self.0))
124    }
125}
126
127/// Conversion to and from byte arrays with Kind information.
128pub trait Id128DynamicArrayConv: Sized {
129    /// Convert a byte array into this type.
130    fn try_from_array_dynamic(array: &[u8; 17]) -> Option<Self>;
131
132    /// Convert a byte slice into this type.
133    fn try_from_bytes_dynamic(bytes: &[u8]) -> Option<Self> {
134        Self::try_from_array_dynamic(bytes.try_into().ok()?)
135    }
136
137    /// Convert this type into a byte array.
138    fn to_array_dynamic(&self) -> [u8; 17];
139}
140
141/// Types of Kinds of typed Ids.
142pub mod kind {
143    use int_enum::IntEnum;
144    use serde::{Deserialize, Serialize};
145
146    /// A dynamic kind of ID.
147    ///
148    /// It acts as a "namespace" for identifiers.
149    ///
150    /// NB: This enum is used in persisted postcard serializations, new variants should be added at the end!
151    #[derive(
152        Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntEnum, Serialize, Deserialize, Debug,
153    )]
154    #[repr(u8)]
155    pub enum Kind {
156        /// Persona Entity kind.
157        Persona = 0,
158        /// Group Entity kind.
159        Group = 1,
160        /// Service Entity kind.
161        Service = 2,
162        /// Domain kind.
163        Domain = 3,
164        /// Policy kind.
165        Policy = 4,
166        /// Policy binding kind.
167        PolicyBinding = 5,
168        /// Property kind.
169        Property = 6,
170        /// Attribute kind.
171        Attribute = 7,
172        /// Directory kind.
173        Directory = 8,
174    }
175
176    impl Kind {
177        #[inline]
178        pub(super) const fn str_prefix(&self) -> &'static str {
179            match self {
180                Self::Persona => "p",
181                Self::Group => "g",
182                Self::Service => "s",
183                Self::Domain => "d",
184                Self::Property => "prp",
185                Self::Attribute => "atr",
186                Self::Directory => "dir",
187                Self::Policy => "pol",
188                Self::PolicyBinding => "plb",
189            }
190        }
191
192        pub(super) const fn name(&self) -> &'static str {
193            match self {
194                Kind::Persona => "persona ID",
195                Kind::Group => "group ID",
196                Kind::Service => "service ID",
197                Kind::Domain => "domain ID",
198                Kind::Policy => "policy ID",
199                Kind::PolicyBinding => "policy binding ID",
200                Kind::Property => "property ID",
201                Kind::Attribute => "attribute ID",
202                Kind::Directory => "directory ID",
203            }
204        }
205
206        pub(super) const fn entries() -> &'static [Self] {
207            &[
208                Self::Persona,
209                Self::Group,
210                Self::Service,
211                Self::Domain,
212                Self::Policy,
213                Self::PolicyBinding,
214                Self::Property,
215                Self::Attribute,
216                Self::Directory,
217            ]
218        }
219    }
220
221    /// Trait for static kinds of Ids.
222    pub trait IdKind {
223        /// The runtime kind of static ID kind.
224        fn kind() -> Kind;
225    }
226
227    /// Persona ID kind.
228    pub struct Persona;
229
230    /// Group ID kind.
231    pub struct Group;
232
233    /// Service ID kind.
234    pub struct Service;
235
236    /// Domain ID kind.
237    pub struct Domain;
238
239    /// Policy ID kind.
240    pub struct Policy;
241
242    /// Policy binding ID kind.
243    pub struct PolicyBinding;
244
245    /// Attribute ID kind.
246    pub struct Property;
247
248    /// Attribute ID kind.
249    pub struct Attrbute;
250
251    /// Directory ID kind.
252    pub struct Directory;
253
254    impl IdKind for Persona {
255        fn kind() -> Kind {
256            Kind::Persona
257        }
258    }
259
260    impl IdKind for Group {
261        fn kind() -> Kind {
262            Kind::Group
263        }
264    }
265
266    impl IdKind for Service {
267        fn kind() -> Kind {
268            Kind::Service
269        }
270    }
271
272    impl IdKind for Domain {
273        fn kind() -> Kind {
274            Kind::Domain
275        }
276    }
277
278    impl IdKind for Policy {
279        fn kind() -> Kind {
280            Kind::Policy
281        }
282    }
283
284    impl IdKind for PolicyBinding {
285        fn kind() -> Kind {
286            Kind::PolicyBinding
287        }
288    }
289
290    impl IdKind for Property {
291        fn kind() -> Kind {
292            Kind::Property
293        }
294    }
295
296    impl IdKind for Attrbute {
297        fn kind() -> Kind {
298            Kind::Attribute
299        }
300    }
301
302    impl IdKind for Directory {
303        fn kind() -> Kind {
304            Kind::Directory
305        }
306    }
307}
308
309/// Id kind subsets.
310pub mod subset {
311    use super::kind::{Group, IdKind, Kind, Persona, Service};
312
313    /// Describes a specific subset of Authly ID kinds.
314    pub trait IdKindSubset {
315        /// Whether the subset contains the given [Kind].
316        fn contains(kind: Kind) -> bool;
317
318        /// The name of this subset.
319        fn name() -> &'static str;
320    }
321
322    /// Describes a specific _superset_ of K.
323    pub trait IdKindSupersetOf<K> {}
324
325    /// The Entity subset.
326    pub struct Entity;
327
328    /// The Any subset, which contains all kinds of Authly IDs.
329    pub struct Any;
330
331    impl IdKindSubset for Entity {
332        fn contains(kind: Kind) -> bool {
333            matches!(kind, Kind::Persona | Kind::Group | Kind::Service)
334        }
335
336        fn name() -> &'static str {
337            "Entity ID"
338        }
339    }
340
341    impl IdKindSupersetOf<Persona> for Entity {}
342    impl IdKindSupersetOf<Group> for Entity {}
343    impl IdKindSupersetOf<Service> for Entity {}
344
345    impl IdKindSubset for Any {
346        fn contains(_kind: Kind) -> bool {
347            true
348        }
349
350        fn name() -> &'static str {
351            "Any ID"
352        }
353    }
354
355    impl<K: IdKind> IdKindSupersetOf<K> for Any {}
356    impl IdKindSupersetOf<Entity> for Any {}
357}
358
359/// Authly Persona ID
360pub type PersonaId = Id128<kind::Persona>;
361
362/// Authly Group ID
363pub type GroupId = Id128<kind::Group>;
364
365/// Authly Service ID
366pub type ServiceId = Id128<kind::Service>;
367
368/// Authly Property ID
369pub type PropId = Id128<kind::Property>;
370
371/// Authly Attribute ID
372pub type AttrId = Id128<kind::Attrbute>;
373
374/// Authly Policy ID
375pub type PolicyId = Id128<kind::Policy>;
376
377/// Authly Policy Binding ID
378pub type PolicyBindingId = Id128<kind::PolicyBinding>;
379
380/// Authly Domain ID
381pub type DomainId = Id128<kind::Domain>;
382
383/// Authly Directory ID
384pub type DirectoryId = Id128<kind::Directory>;
385
386/// Dynamically typed ID, can represent any kind "object" Id
387pub struct DynamicId<KS: IdKindSubset> {
388    pub(crate) id: [u8; 16],
389    kind: kind::Kind,
390    _subset: PhantomData<KS>,
391}
392
393impl<KS: IdKindSubset> DynamicId<KS> {
394    /// Construct a new dynamicId.
395    ///
396    /// Panics if [Kind] is not member of the KS subset.
397    pub fn new(kind: Kind, id: [u8; 16]) -> Self {
398        if !KS::contains(kind) {
399            panic!("Not in subset");
400        }
401        Self {
402            kind,
403            id,
404            _subset: PhantomData,
405        }
406    }
407
408    /// The dynamic kind of this dynamic id.
409    pub fn kind(&self) -> Kind {
410        self.kind
411    }
412
413    /// Infallibly upcast this into a superset [DynamicId].
414    pub fn upcast<KS2: IdKindSubset + IdKindSupersetOf<KS>>(&self) -> DynamicId<KS2> {
415        DynamicId {
416            id: self.id,
417            kind: self.kind,
418            _subset: PhantomData,
419        }
420    }
421
422    /// Get the byte-wise representation of the ID, without type information.
423    /// NB! This erases the dynamic tag!
424    pub const fn to_raw_array(self) -> [u8; 16] {
425        self.id
426    }
427}
428
429impl<KS: IdKindSubset> Clone for DynamicId<KS> {
430    fn clone(&self) -> Self {
431        *self
432    }
433}
434
435impl<KS: IdKindSubset> Copy for DynamicId<KS> {}
436
437impl<KS: IdKindSubset> Debug for DynamicId<KS> {
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        write!(f, "{}.{}", self.kind.str_prefix(), hexhex::hex(&self.id))
440    }
441}
442
443impl<KS: IdKindSubset> Display for DynamicId<KS> {
444    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445        write!(f, "{}.{}", self.kind.str_prefix(), hexhex::hex(&self.id))
446    }
447}
448
449impl<KS: IdKindSubset> PartialEq for DynamicId<KS> {
450    fn eq(&self, other: &Self) -> bool {
451        self.kind == other.kind && self.id == other.id
452    }
453}
454
455impl<KS: IdKindSubset> Eq for DynamicId<KS> {}
456
457impl<KS: IdKindSubset> Hash for DynamicId<KS> {
458    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
459        self.id.hash(state);
460        self.kind.hash(state);
461    }
462}
463
464impl<KS: IdKindSubset> PartialOrd<DynamicId<KS>> for DynamicId<KS> {
465    fn partial_cmp(&self, other: &DynamicId<KS>) -> Option<std::cmp::Ordering> {
466        Some(self.cmp(other))
467    }
468}
469
470impl<KS: IdKindSubset> Ord for DynamicId<KS> {
471    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
472        self.kind
473            .cmp(&other.kind)
474            .then_with(|| self.id.cmp(&other.id))
475    }
476}
477
478/// An Authly Entity ID.
479pub type EntityId = DynamicId<subset::Entity>;
480
481/// An Authly Any Id - the Id of any object, entities or other objects.
482pub type AnyId = DynamicId<subset::Any>;
483
484impl<K: IdKind> FromStr for Id128<K> {
485    type Err = anyhow::Error;
486
487    fn from_str(s: &str) -> Result<Self, Self::Err> {
488        let prefix = K::kind().str_prefix();
489        let Some(s) = s.strip_prefix(prefix) else {
490            return Err(anyhow!("unrecognized prefix, expected `{prefix}`"));
491        };
492        let s = s.strip_prefix('.').context("missing `.`")?;
493
494        let hex = hexhex::decode(s).context("invalid format")?;
495        let array: [u8; 16] = hex.try_into().map_err(|_| anyhow!("invalid length"))?;
496
497        let min = 32768_u128.to_be_bytes();
498
499        if array != [0; 16] && array < min {
500            return Err(anyhow!("invalid value, too small"));
501        }
502
503        Ok(Id128(array, PhantomData))
504    }
505}
506
507impl<S: IdKindSubset> FromStr for DynamicId<S> {
508    type Err = anyhow::Error;
509
510    fn from_str(s: &str) -> Result<Self, Self::Err> {
511        let mut segments = s.splitn(2, ".");
512        let prefix = segments.next().context("no prefix")?;
513        let s = segments.next().context("no hex code")?;
514
515        if segments.next().is_some() {
516            return Err(anyhow!("too many dots"));
517        }
518
519        let kind = Kind::entries()
520            .iter()
521            .copied()
522            .find(|kind| kind.str_prefix() == prefix)
523            .context("unrecognized prefix")?;
524
525        if !S::contains(kind) {
526            return Err(anyhow!("invalid subset"));
527        }
528
529        let hex = hexhex::decode(s).context("invalid format")?;
530        let array: [u8; 16] = hex.try_into().map_err(|_| anyhow!("invalid length"))?;
531
532        let min = 32768_u128.to_be_bytes();
533
534        if array != [0; 16] && array < min {
535            return Err(anyhow!("invalid value, too small"));
536        }
537
538        Ok(DynamicId {
539            id: array,
540            kind,
541            _subset: PhantomData,
542        })
543    }
544}
545
546impl<'de, K: IdKind> Deserialize<'de> for Id128<K> {
547    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
548    where
549        D: serde::Deserializer<'de>,
550    {
551        deserializer.deserialize_str(FromStrVisitor::new(K::kind().name()))
552    }
553}
554
555impl<K: IdKind> Serialize for Id128<K> {
556    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
557    where
558        S: serde::Serializer,
559    {
560        serializer.serialize_str(&format!(
561            "{}.{}",
562            K::kind().str_prefix(),
563            hexhex::hex(&self.0)
564        ))
565    }
566}
567
568impl<KS: IdKindSubset> Serialize for DynamicId<KS> {
569    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
570    where
571        S: serde::Serializer,
572    {
573        serializer.serialize_str(&format!(
574            "{}.{}",
575            self.kind.str_prefix(),
576            hexhex::hex(&self.id)
577        ))
578    }
579}
580
581impl<'de, KS: IdKindSubset> Deserialize<'de> for DynamicId<KS> {
582    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
583    where
584        D: serde::Deserializer<'de>,
585    {
586        deserializer.deserialize_str(FromStrVisitor::new(KS::name()))
587    }
588}
589
590impl<K: IdKind> Id128DynamicArrayConv for Id128<K> {
591    fn try_from_array_dynamic(array: &[u8; 17]) -> Option<Self> {
592        let kind_byte: u8 = K::kind().into();
593        if array[0] == kind_byte {
594            Self::from_raw_bytes(&array[1..])
595        } else {
596            None
597        }
598    }
599
600    fn to_array_dynamic(&self) -> [u8; 17] {
601        let mut output = [0u8; 17];
602        output[0] = K::kind().into();
603        output[1..].clone_from_slice(&self.to_raw_array());
604        output
605    }
606}
607
608impl<KS: IdKindSubset> Id128DynamicArrayConv for DynamicId<KS> {
609    fn try_from_array_dynamic(array: &[u8; 17]) -> Option<Self> {
610        let kind = Kind::try_from(array[0]).ok()?;
611        if !KS::contains(kind) {
612            return None;
613        }
614
615        let id = array[1..].try_into().ok()?;
616
617        Some(Self {
618            kind,
619            id,
620            _subset: PhantomData,
621        })
622    }
623
624    fn to_array_dynamic(&self) -> [u8; 17] {
625        let mut output = [0u8; 17];
626        output[0] = self.kind.into();
627        output[1..].clone_from_slice(&self.id);
628        output
629    }
630}
631
632impl<K: IdKind, KS: IdKindSubset> TryFrom<Id128<K>> for DynamicId<KS> {
633    type Error = ();
634
635    fn try_from(value: Id128<K>) -> Result<Self, Self::Error> {
636        if KS::contains(K::kind()) {
637            Ok(Self {
638                kind: K::kind(),
639                id: value.0,
640                _subset: PhantomData,
641            })
642        } else {
643            Err(())
644        }
645    }
646}
647
648impl<K: IdKind, KS: IdKindSubset> TryFrom<DynamicId<KS>> for Id128<K> {
649    type Error = ();
650
651    fn try_from(value: DynamicId<KS>) -> Result<Self, Self::Error> {
652        if value.kind != K::kind() {
653            return Err(());
654        }
655
656        Ok(Self(value.id, PhantomData))
657    }
658}
659
660#[test]
661fn from_hex_literal() {
662    let _ = PersonaId::from(hexhex::hex_literal!("1234abcd1234abcd1234abcd1234abcd"));
663}
664
665#[test]
666fn from_str() {
667    PersonaId::from_str("p.1234abcd1234abcd1234abcd1234abcd").unwrap();
668    ServiceId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
669    PersonaId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap_err();
670    DomainId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap();
671
672    AnyId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
673    AnyId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap();
674    EntityId::from_str("s.1234abcd1234abcd1234abcd1234abcd").unwrap();
675    EntityId::from_str("g.1234abcd1234abcd1234abcd1234abcd").unwrap();
676    EntityId::from_str("d.1234abcd1234abcd1234abcd1234abcd").unwrap_err();
677}
678
679#[test]
680fn serde() {
681    let before = PersonaId::from_str("p.1234abcd1234abcd1234abcd1234abcd").unwrap();
682    let json = serde_json::to_string(&before).unwrap();
683
684    assert_eq!("\"p.1234abcd1234abcd1234abcd1234abcd\"", json);
685
686    let after: PersonaId = serde_json::from_str(&json).unwrap();
687
688    assert_eq!(before, after);
689}