Skip to main content

simple_zanzibar/
model.rs

1//! Core data structures for the Zanzibar authorization system.
2
3use std::hash::Hash;
4
5use crate::{
6    domain::{
7        DomainError, ObjectRef, ObjectType, RelationName, Relationship, SubjectRef, SubjectType,
8    },
9    revision::Consistency,
10};
11
12/// Represents a namespaced digital object.
13/// e.g., `doc:readme`, `folder:A`
14#[cfg_attr(
15    feature = "serde",
16    derive(serde::Serialize),
17    serde(rename_all = "camelCase", deny_unknown_fields)
18)]
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub struct Object {
21    /// Object namespace/type.
22    pub namespace: String,
23    /// Object identifier within the namespace.
24    pub id: String,
25}
26
27impl Object {
28    /// Creates a namespaced object.
29    #[must_use]
30    pub fn new(namespace: impl Into<String>, id: impl Into<String>) -> Self {
31        Self {
32            namespace: namespace.into(),
33            id: id.into(),
34        }
35    }
36
37    /// Validates this object's namespace and identifier.
38    ///
39    /// # Errors
40    ///
41    /// Returns [`DomainError`] when the namespace or object id violates the public identifier
42    /// grammar or byte limits.
43    pub fn validate(&self) -> Result<(), DomainError> {
44        ObjectRef::try_from(self).map(drop)
45    }
46}
47
48#[cfg(feature = "serde")]
49impl<'de> serde::Deserialize<'de> for Object {
50    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51    where
52        D: serde::Deserializer<'de>,
53    {
54        #[derive(serde::Deserialize)]
55        #[serde(rename_all = "camelCase", deny_unknown_fields)]
56        struct ObjectSerde {
57            namespace: String,
58            id: String,
59        }
60
61        let value = ObjectSerde::deserialize(deserializer)?;
62        let object = Self {
63            namespace: value.namespace,
64            id: value.id,
65        };
66        object.validate().map_err(serde::de::Error::custom)?;
67        Ok(object)
68    }
69}
70
71/// Represents a relation or permission type on an object.
72/// e.g., `owner`, `editor`, `viewer`
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74#[derive(Debug, Clone, PartialEq, Eq, Hash)]
75pub struct Relation(pub String);
76
77impl Relation {
78    /// Creates a relation or permission name.
79    #[must_use]
80    pub fn new(name: impl Into<String>) -> Self {
81        Self(name.into())
82    }
83
84    /// Validates this relation or permission name.
85    ///
86    /// # Errors
87    ///
88    /// Returns [`DomainError`] when the relation violates the public relation-name grammar or byte
89    /// limit.
90    pub fn validate(&self) -> Result<(), DomainError> {
91        RelationName::try_from(self).map(drop)
92    }
93}
94
95#[cfg(feature = "serde")]
96impl<'de> serde::Deserialize<'de> for Relation {
97    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98    where
99        D: serde::Deserializer<'de>,
100    {
101        let relation = Self(<String as serde::Deserialize>::deserialize(deserializer)?);
102        relation.validate().map_err(serde::de::Error::custom)?;
103        Ok(relation)
104    }
105}
106
107/// Represents either a specific user ID or a reference to a userset (e.g., a group).
108#[cfg_attr(
109    feature = "serde",
110    derive(serde::Serialize),
111    serde(rename_all = "camelCase", tag = "type", content = "value")
112)]
113#[derive(Debug, Clone, PartialEq, Eq, Hash)]
114pub enum User {
115    /// A specific user, identified by a unique string.
116    /// e.g., `"10"`, `"alice"`.
117    UserId(String),
118    /// A set of users, identified by an object-relation pair.
119    /// e.g., `group:eng#member`
120    Userset(Object, Relation),
121}
122
123impl User {
124    /// Creates a direct user subject.
125    #[must_use]
126    pub fn user_id(id: impl Into<String>) -> Self {
127        Self::UserId(id.into())
128    }
129
130    /// Creates a userset subject.
131    #[must_use]
132    pub fn userset(object: Object, relation: Relation) -> Self {
133        Self::Userset(object, relation)
134    }
135
136    /// Validates this subject reference.
137    ///
138    /// # Errors
139    ///
140    /// Returns [`DomainError`] when a direct user id or userset object/relation violates public
141    /// identifier grammar or byte limits.
142    pub fn validate(&self) -> Result<(), DomainError> {
143        SubjectRef::try_from(self).map(drop)
144    }
145}
146
147#[cfg(feature = "serde")]
148impl<'de> serde::Deserialize<'de> for User {
149    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150    where
151        D: serde::Deserializer<'de>,
152    {
153        #[derive(serde::Deserialize)]
154        #[serde(rename_all = "camelCase", tag = "type", content = "value")]
155        enum UserSerde {
156            UserId(String),
157            Userset(Object, Relation),
158        }
159
160        let user = match UserSerde::deserialize(deserializer)? {
161            UserSerde::UserId(id) => Self::UserId(id),
162            UserSerde::Userset(object, relation) => Self::Userset(object, relation),
163        };
164        user.validate().map_err(serde::de::Error::custom)?;
165        Ok(user)
166    }
167}
168
169/// The core relation tuple, representing a single permission assertion.
170/// This is the atomic unit of authorization data.
171/// e.g., `(doc:readme#owner@user:alice)`
172#[cfg_attr(
173    feature = "serde",
174    derive(serde::Serialize),
175    serde(rename_all = "camelCase", deny_unknown_fields)
176)]
177#[derive(Debug, Clone, PartialEq, Eq, Hash)]
178pub struct RelationTuple {
179    /// Relationship resource object.
180    pub object: Object,
181    /// Relationship relation.
182    pub relation: Relation,
183    /// Relationship subject.
184    pub user: User,
185}
186
187impl RelationTuple {
188    /// Creates a relationship tuple.
189    #[must_use]
190    pub fn new(object: Object, relation: Relation, user: User) -> Self {
191        Self {
192            object,
193            relation,
194            user,
195        }
196    }
197
198    /// Validates this relationship tuple.
199    ///
200    /// # Errors
201    ///
202    /// Returns [`DomainError`] when any tuple component violates public identifier grammar or byte
203    /// limits.
204    pub fn validate(&self) -> Result<(), DomainError> {
205        Relationship::try_from(self).map(drop)
206    }
207}
208
209#[cfg(feature = "serde")]
210impl<'de> serde::Deserialize<'de> for RelationTuple {
211    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
212    where
213        D: serde::Deserializer<'de>,
214    {
215        #[derive(serde::Deserialize)]
216        #[serde(rename_all = "camelCase", deny_unknown_fields)]
217        struct RelationTupleSerde {
218            object: Object,
219            relation: Relation,
220            user: User,
221        }
222
223        let value = RelationTupleSerde::deserialize(deserializer)?;
224        let tuple = Self {
225            object: value.object,
226            relation: value.relation,
227            user: value.user,
228        };
229        tuple.validate().map_err(serde::de::Error::custom)?;
230        Ok(tuple)
231    }
232}
233
234/// Request for a check at a specified consistency level.
235#[cfg_attr(
236    feature = "serde",
237    derive(serde::Serialize, serde::Deserialize),
238    serde(rename_all = "camelCase", deny_unknown_fields)
239)]
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct CheckRequest {
242    /// Protected object to evaluate.
243    pub object: Object,
244    /// Relation or permission to evaluate.
245    pub relation: Relation,
246    /// Subject whose membership is checked.
247    pub user: User,
248    /// Consistency selector for the read.
249    pub consistency: Consistency,
250}
251
252impl CheckRequest {
253    /// Creates a check request.
254    #[must_use]
255    pub fn new(object: Object, relation: Relation, user: User, consistency: Consistency) -> Self {
256        Self {
257            object,
258            relation,
259            user,
260            consistency,
261        }
262    }
263
264    /// Validates domain fields in this check request.
265    ///
266    /// # Errors
267    ///
268    /// Returns [`DomainError`] when the object, relation, or subject is invalid.
269    pub fn validate(&self) -> Result<(), DomainError> {
270        self.object.validate()?;
271        self.relation.validate()?;
272        self.user.validate()
273    }
274}
275
276/// Response for a check request.
277#[cfg_attr(
278    feature = "serde",
279    derive(serde::Serialize, serde::Deserialize),
280    serde(rename_all = "camelCase", deny_unknown_fields)
281)]
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub struct CheckResponse {
284    /// Whether the subject has the requested relation or permission.
285    pub allowed: bool,
286}
287
288/// Request for expanding an object relation or permission at a specified consistency level.
289#[cfg_attr(
290    feature = "serde",
291    derive(serde::Serialize, serde::Deserialize),
292    serde(rename_all = "camelCase", deny_unknown_fields)
293)]
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct ExpandRequest {
296    /// Protected object to expand.
297    pub object: Object,
298    /// Relation or permission to expand.
299    pub relation: Relation,
300    /// Consistency selector for the read.
301    pub consistency: Consistency,
302}
303
304impl ExpandRequest {
305    /// Creates an expand request.
306    #[must_use]
307    pub fn new(object: Object, relation: Relation, consistency: Consistency) -> Self {
308        Self {
309            object,
310            relation,
311            consistency,
312        }
313    }
314
315    /// Validates domain fields in this expand request.
316    ///
317    /// # Errors
318    ///
319    /// Returns [`DomainError`] when the object or relation is invalid.
320    pub fn validate(&self) -> Result<(), DomainError> {
321        self.object.validate()?;
322        self.relation.validate()
323    }
324}
325
326/// Response for an expand request.
327#[cfg_attr(
328    feature = "serde",
329    derive(serde::Serialize, serde::Deserialize),
330    serde(rename_all = "camelCase", deny_unknown_fields)
331)]
332#[derive(Debug, Clone, PartialEq, Eq)]
333pub struct ExpandResponse {
334    /// Expanded userset tree.
335    pub expanded: ExpandedUserset,
336}
337
338/// Request for resources of one type that a subject can access through a permission.
339#[cfg_attr(
340    feature = "serde",
341    derive(serde::Serialize),
342    serde(rename_all = "camelCase", deny_unknown_fields)
343)]
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct LookupResourcesRequest {
346    /// Subject whose accessible resources are requested.
347    pub subject: User,
348    /// Permission or relation to check on each candidate resource.
349    pub permission: Relation,
350    /// Resource namespace/type to return.
351    pub resource_type: String,
352}
353
354impl LookupResourcesRequest {
355    /// Creates a request to list resources of one type that a subject can access.
356    #[must_use]
357    pub fn new(subject: User, permission: Relation, resource_type: impl Into<String>) -> Self {
358        Self {
359            subject,
360            permission,
361            resource_type: resource_type.into(),
362        }
363    }
364
365    /// Validates domain fields in this lookup request.
366    ///
367    /// # Errors
368    ///
369    /// Returns [`DomainError`] when the subject, permission, or resource type is invalid.
370    pub fn validate(&self) -> Result<(), DomainError> {
371        self.subject.validate()?;
372        self.permission.validate()?;
373        ObjectType::try_from(self.resource_type.as_str()).map(drop)
374    }
375}
376
377#[cfg(feature = "serde")]
378impl<'de> serde::Deserialize<'de> for LookupResourcesRequest {
379    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
380    where
381        D: serde::Deserializer<'de>,
382    {
383        #[derive(serde::Deserialize)]
384        #[serde(rename_all = "camelCase", deny_unknown_fields)]
385        struct LookupResourcesRequestSerde {
386            subject: User,
387            permission: Relation,
388            resource_type: String,
389        }
390
391        let value = LookupResourcesRequestSerde::deserialize(deserializer)?;
392        let request = Self {
393            subject: value.subject,
394            permission: value.permission,
395            resource_type: value.resource_type,
396        };
397        request.validate().map_err(serde::de::Error::custom)?;
398        Ok(request)
399    }
400}
401
402/// Resources returned by a lookup request.
403#[cfg_attr(
404    feature = "serde",
405    derive(serde::Serialize, serde::Deserialize),
406    serde(rename_all = "camelCase", deny_unknown_fields)
407)]
408#[derive(Debug, Clone, PartialEq, Eq)]
409pub struct LookupResources {
410    /// De-duplicated resources that passed the shared check evaluator.
411    pub resources: Vec<Object>,
412}
413
414/// Request for subjects of one type that can access a resource through a permission.
415#[cfg_attr(
416    feature = "serde",
417    derive(serde::Serialize),
418    serde(rename_all = "camelCase", deny_unknown_fields)
419)]
420#[derive(Debug, Clone, PartialEq, Eq)]
421pub struct LookupSubjectsRequest {
422    /// Protected resource to check.
423    pub resource: Object,
424    /// Permission or relation to evaluate on the resource.
425    pub permission: Relation,
426    /// Subject namespace/type to return.
427    pub subject_type: String,
428}
429
430impl LookupSubjectsRequest {
431    /// Creates a request to list subjects of one type that can access a resource.
432    #[must_use]
433    pub fn new(resource: Object, permission: Relation, subject_type: impl Into<String>) -> Self {
434        Self {
435            resource,
436            permission,
437            subject_type: subject_type.into(),
438        }
439    }
440
441    /// Validates domain fields in this lookup request.
442    ///
443    /// # Errors
444    ///
445    /// Returns [`DomainError`] when the resource, permission, or subject type is invalid.
446    pub fn validate(&self) -> Result<(), DomainError> {
447        self.resource.validate()?;
448        self.permission.validate()?;
449        SubjectType::try_from(self.subject_type.as_str()).map(drop)
450    }
451}
452
453#[cfg(feature = "serde")]
454impl<'de> serde::Deserialize<'de> for LookupSubjectsRequest {
455    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
456    where
457        D: serde::Deserializer<'de>,
458    {
459        #[derive(serde::Deserialize)]
460        #[serde(rename_all = "camelCase", deny_unknown_fields)]
461        struct LookupSubjectsRequestSerde {
462            resource: Object,
463            permission: Relation,
464            subject_type: String,
465        }
466
467        let value = LookupSubjectsRequestSerde::deserialize(deserializer)?;
468        let request = Self {
469            resource: value.resource,
470            permission: value.permission,
471            subject_type: value.subject_type,
472        };
473        request.validate().map_err(serde::de::Error::custom)?;
474        Ok(request)
475    }
476}
477
478/// Subjects returned by a lookup request.
479#[cfg_attr(
480    feature = "serde",
481    derive(serde::Serialize, serde::Deserialize),
482    serde(rename_all = "camelCase", deny_unknown_fields)
483)]
484#[derive(Debug, Clone, PartialEq, Eq)]
485pub struct LookupSubjects {
486    /// De-duplicated subjects that passed the shared check evaluator.
487    pub subjects: Vec<User>,
488}
489
490/// Request for all permissions a subject has on one resource.
491#[cfg_attr(
492    feature = "serde",
493    derive(serde::Serialize, serde::Deserialize),
494    serde(rename_all = "camelCase", deny_unknown_fields)
495)]
496#[derive(Debug, Clone, PartialEq, Eq)]
497pub struct LookupPermissionsRequest {
498    /// Subject whose permissions are requested.
499    pub subject: User,
500    /// Resource object to evaluate.
501    pub resource: Object,
502    /// Consistency selector for the read.
503    pub consistency: Consistency,
504}
505
506impl LookupPermissionsRequest {
507    /// Creates a request to list all relations or permissions one subject has on one resource.
508    #[must_use]
509    pub fn new(subject: User, resource: Object, consistency: Consistency) -> Self {
510        Self {
511            subject,
512            resource,
513            consistency,
514        }
515    }
516
517    /// Validates domain fields in this lookup request.
518    ///
519    /// # Errors
520    ///
521    /// Returns [`DomainError`] when the subject or resource is invalid.
522    pub fn validate(&self) -> Result<(), DomainError> {
523        self.subject.validate()?;
524        self.resource.validate()
525    }
526}
527
528/// Permissions returned by a subject/resource lookup request.
529#[cfg_attr(
530    feature = "serde",
531    derive(serde::Serialize, serde::Deserialize),
532    serde(rename_all = "camelCase", deny_unknown_fields)
533)]
534#[derive(Debug, Clone, PartialEq, Eq)]
535pub struct LookupPermissions {
536    /// Sorted relations or permissions that evaluated to allowed.
537    pub permissions: Vec<Relation>,
538}
539
540/// Request for subjects grouped by every permission they have on one resource.
541#[cfg_attr(
542    feature = "serde",
543    derive(serde::Serialize),
544    serde(rename_all = "camelCase", deny_unknown_fields)
545)]
546#[derive(Debug, Clone, PartialEq, Eq)]
547pub struct LookupObjectPermissionsRequest {
548    /// Resource object to evaluate.
549    pub resource: Object,
550    /// Subject namespace/type to return.
551    pub subject_type: String,
552    /// Consistency selector for the read.
553    pub consistency: Consistency,
554}
555
556impl LookupObjectPermissionsRequest {
557    /// Creates a request to list subjects grouped by each relation or permission on a resource.
558    #[must_use]
559    pub fn new(
560        resource: Object,
561        subject_type: impl Into<String>,
562        consistency: Consistency,
563    ) -> Self {
564        Self {
565            resource,
566            subject_type: subject_type.into(),
567            consistency,
568        }
569    }
570
571    /// Validates domain fields in this lookup request.
572    ///
573    /// # Errors
574    ///
575    /// Returns [`DomainError`] when the resource or subject type is invalid.
576    pub fn validate(&self) -> Result<(), DomainError> {
577        self.resource.validate()?;
578        SubjectType::try_from(self.subject_type.as_str()).map(drop)
579    }
580}
581
582#[cfg(feature = "serde")]
583impl<'de> serde::Deserialize<'de> for LookupObjectPermissionsRequest {
584    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
585    where
586        D: serde::Deserializer<'de>,
587    {
588        #[derive(serde::Deserialize)]
589        #[serde(rename_all = "camelCase", deny_unknown_fields)]
590        struct LookupObjectPermissionsRequestSerde {
591            resource: Object,
592            subject_type: String,
593            consistency: Consistency,
594        }
595
596        let value = LookupObjectPermissionsRequestSerde::deserialize(deserializer)?;
597        let request = Self {
598            resource: value.resource,
599            subject_type: value.subject_type,
600            consistency: value.consistency,
601        };
602        request.validate().map_err(serde::de::Error::custom)?;
603        Ok(request)
604    }
605}
606
607/// Subjects grouped by permission for one resource.
608#[cfg_attr(
609    feature = "serde",
610    derive(serde::Serialize, serde::Deserialize),
611    serde(rename_all = "camelCase", deny_unknown_fields)
612)]
613#[derive(Debug, Clone, PartialEq, Eq)]
614pub struct LookupObjectPermissions {
615    /// Permission groups with non-empty subjects.
616    pub permissions: Vec<PermissionSubjects>,
617}
618
619/// Subjects that have one permission on a resource.
620#[cfg_attr(
621    feature = "serde",
622    derive(serde::Serialize, serde::Deserialize),
623    serde(rename_all = "camelCase", deny_unknown_fields)
624)]
625#[derive(Debug, Clone, PartialEq, Eq)]
626pub struct PermissionSubjects {
627    /// Relation or permission name.
628    pub permission: Relation,
629    /// Subjects that passed the shared check evaluator.
630    pub subjects: Vec<User>,
631}
632
633/// Defines the schema and policy rules for a particular namespace.
634#[cfg_attr(
635    feature = "serde",
636    derive(serde::Serialize, serde::Deserialize),
637    serde(rename_all = "camelCase", deny_unknown_fields)
638)]
639#[derive(Debug, Clone, Default)]
640pub struct NamespaceConfig {
641    /// Namespace/type name.
642    pub name: String,
643    /// Relation definitions keyed by relation name.
644    pub relations: std::collections::HashMap<Relation, RelationConfig>,
645}
646
647/// Defines a specific relation within a namespace, including its rewrite rules.
648#[cfg_attr(
649    feature = "serde",
650    derive(serde::Serialize, serde::Deserialize),
651    serde(rename_all = "camelCase", deny_unknown_fields)
652)]
653#[derive(Debug, Clone)]
654pub struct RelationConfig {
655    /// Relation name.
656    pub name: Relation,
657    /// Optional userset rewrite for computed permissions.
658    pub userset_rewrite: Option<UsersetExpression>,
659}
660
661/// Represents a tree of userset computations, forming the core of the policy language.
662#[cfg_attr(
663    feature = "serde",
664    derive(serde::Serialize, serde::Deserialize),
665    serde(rename_all = "camelCase")
666)]
667#[derive(Debug, Clone)]
668pub enum UsersetExpression {
669    /// `this` - The set of users directly granted this relation.
670    This,
671    /// A set computed from another relation on the *same* object.
672    /// e.g., an `editor` is also a `viewer`.
673    ComputedUserset {
674        /// Relation on the same object to compute.
675        relation: Relation,
676    },
677    /// A set computed by first finding a related object via a `tupleset` relation,
678    /// and then computing a userset from that related object.
679    /// e.g., for `doc:readme`, find its `parent` folder, then take `viewers` of that folder.
680    TupleToUserset {
681        /// Relation that points from the source object to intermediate objects.
682        tupleset_relation: Relation,
683        /// Relation to evaluate on each intermediate object.
684        computed_userset_relation: Relation,
685    },
686    /// The union of multiple sub-expressions.
687    Union(Vec<UsersetExpression>),
688    /// The intersection of multiple sub-expressions.
689    Intersection(Vec<UsersetExpression>),
690    /// The exclusion (or difference) of one set from another.
691    Exclusion {
692        /// Base userset expression.
693        base: Box<UsersetExpression>,
694        /// Userset expression to subtract from the base.
695        exclude: Box<UsersetExpression>,
696    },
697}
698
699/// Represents the result of an `expand` operation, detailing the effective userset.
700#[cfg_attr(
701    feature = "serde",
702    derive(serde::Serialize, serde::Deserialize),
703    serde(rename_all = "camelCase")
704)]
705#[derive(Debug, Clone, PartialEq, Eq)]
706pub enum ExpandedUserset {
707    /// A specific user who has the permission.
708    User(String),
709    /// A reference to another userset that contributes to the permission.
710    Userset(Object, Relation),
711    /// The union of multiple expanded usersets.
712    Union(Vec<ExpandedUserset>),
713    /// The intersection of multiple expanded usersets.
714    Intersection(Vec<ExpandedUserset>),
715    /// The exclusion of one expanded userset from another.
716    Exclusion {
717        /// Base expanded userset.
718        base: Box<ExpandedUserset>,
719        /// Expanded userset to subtract from the base.
720        exclude: Box<ExpandedUserset>,
721    },
722}