authly_common/
document.rs

1//! Authly document type definitions.
2
3use std::collections::BTreeMap;
4
5use serde::Deserialize;
6use toml::Spanned;
7use uuid::Uuid;
8
9use crate::{id::EntityId, property::QualifiedAttributeName};
10
11/// The deserialized representation of an authly document.
12#[derive(Deserialize)]
13#[serde(deny_unknown_fields)]
14#[allow(missing_docs)]
15pub struct Document {
16    /// Metadata about the document
17    #[serde(rename = "authly-document")]
18    pub authly_document: AuthlyDocument,
19
20    /// Collection of settings for the local authly cluster
21    #[serde(default, rename = "local-settings")]
22    pub local_settings: Option<BTreeMap<Spanned<String>, Spanned<String>>>,
23
24    #[serde(default)]
25    pub entity: Vec<Entity>,
26
27    #[serde(default, rename = "service-entity")]
28    pub service_entity: Vec<Entity>,
29
30    #[serde(default)]
31    pub domain: Vec<Domain>,
32
33    #[serde(default, rename = "service-domain")]
34    pub service_domain: Vec<ServiceDomain>,
35
36    #[serde(default)]
37    pub email: Vec<Email>,
38
39    #[serde(default, rename = "password-hash")]
40    pub password_hash: Vec<PasswordHash>,
41
42    #[serde(default)]
43    pub members: Vec<Members>,
44
45    #[serde(default, rename = "entity-attribute-assignment")]
46    pub entity_attribute_assignment: Vec<EntityAttributeAssignment>,
47
48    #[serde(default, rename = "entity-property")]
49    pub entity_property: Vec<EntityProperty>,
50
51    #[serde(default, rename = "resource-property")]
52    pub resource_property: Vec<ResourceProperty>,
53
54    #[serde(default)]
55    pub policy: Vec<Policy>,
56
57    #[serde(default, rename = "policy-binding")]
58    pub policy_binding: Vec<PolicyBinding>,
59}
60
61/// The authly document header
62#[derive(Deserialize)]
63#[serde(deny_unknown_fields)]
64pub struct AuthlyDocument {
65    /// The ID of this document as an Authly authority
66    pub id: Spanned<Uuid>,
67}
68
69/// Represents information that is dynamic and untyped in this document specification.
70pub type DynamicObject = serde_json::Map<String, serde_json::Value>;
71
72/// An entity definition
73#[derive(Deserialize, Debug)]
74#[serde(deny_unknown_fields)]
75pub struct Entity {
76    /// The id of this entity.
77    pub eid: Spanned<EntityId>,
78
79    /// A label for the entity visible in the document namespace.
80    #[serde(default)]
81    pub label: Option<Spanned<String>>,
82
83    /// Metadata about this entity.
84    /// The metadata is not used by authly itself, but can be used by services which have read access to the entity.
85    #[serde(default)]
86    pub metadata: Option<Spanned<DynamicObject>>,
87
88    /// Attributes bound to the entity.
89    #[serde(default)]
90    pub attributes: Vec<Spanned<QualifiedAttributeName>>,
91
92    /// List of usernames.
93    #[serde(default)]
94    pub username: Option<Spanned<String>>,
95
96    /// List of email addresses.
97    #[serde(default)]
98    pub email: Vec<Spanned<String>>,
99
100    /// List of password hashes.
101    #[serde(default, rename = "password-hash")]
102    pub password_hash: Vec<String>,
103
104    /// A list of service hostnames
105    #[serde(default)]
106    pub hosts: Vec<String>,
107
108    /// An optional kubernetes account.
109    #[serde(default, rename = "kubernetes-account")]
110    pub kubernetes_account: Option<KubernetesAccount>,
111}
112
113/// An domain declaration
114#[derive(Deserialize, Debug)]
115#[serde(deny_unknown_fields)]
116pub struct Domain {
117    /// A label for the entity visible in the document namespace.
118    pub label: Spanned<String>,
119
120    /// Metadata about this domain.
121    /// The metadata is not used by authly itself, but can be read and used by services.
122    #[serde(default)]
123    pub metadata: Option<Spanned<DynamicObject>>,
124}
125
126/// An association of a service and a domain the service can use.
127#[derive(Deserialize, Debug)]
128#[serde(deny_unknown_fields)]
129pub struct ServiceDomain {
130    /// A label identifying the impliied service-entity.
131    pub service: Spanned<String>,
132
133    /// A label identifying the domain that will be exposed to the service.
134    pub domain: Spanned<String>,
135}
136
137/// An email address assignment.
138#[derive(Deserialize, Debug)]
139#[serde(deny_unknown_fields)]
140pub struct Email {
141    /// The label of the entity that is assigned this address.
142    pub entity: Spanned<String>,
143
144    /// The address itself.
145    pub value: Spanned<String>,
146}
147
148/// An password hash assignment.
149#[derive(Deserialize, Debug)]
150#[serde(deny_unknown_fields)]
151pub struct PasswordHash {
152    /// The label of the entity that is assigned this password hash.
153    pub entity: Spanned<String>,
154
155    /// The password hash itself.
156    pub hash: String,
157}
158
159/// A members assignment.
160///
161/// In the authly model, any kind of entity may have members.
162#[derive(Deserialize, Debug)]
163#[serde(deny_unknown_fields)]
164pub struct Members {
165    /// The label of the entity that members is assigned to.
166    pub entity: Spanned<String>,
167
168    /// Entity labels of the members.
169    pub members: Vec<Spanned<String>>,
170}
171
172/// A definition of an entity property.
173#[derive(Deserialize)]
174#[serde(deny_unknown_fields)]
175pub struct EntityProperty {
176    /// The label of the namespace this property is defined inside.
177    pub namespace: Spanned<String>,
178
179    /// The property label.
180    pub label: Spanned<String>,
181
182    /// The list of attributes of the property.
183    #[serde(default)]
184    pub attributes: Vec<Spanned<String>>,
185}
186
187/// A kubernetes account definition.
188#[derive(Default, Deserialize, Debug)]
189#[serde(deny_unknown_fields)]
190pub struct KubernetesAccount {
191    /// The kubernetes namespace.
192    ///
193    /// If unspecified, it means the same namespace that Authly itself is deployed within.
194    #[serde(default)]
195    pub namespace: Option<String>,
196
197    /// The account name.
198    pub name: String,
199}
200
201/// A definition of a resource property.
202#[derive(Deserialize)]
203#[serde(deny_unknown_fields)]
204pub struct ResourceProperty {
205    /// The label of the namespace this property is defined inside.
206    pub namespace: Spanned<String>,
207
208    /// The property label.
209    pub label: Spanned<String>,
210
211    /// The list of attributes of the property.
212    #[serde(default)]
213    pub attributes: Vec<Spanned<String>>,
214}
215
216/// A policy definition.
217///
218/// A policy must contain either an `allow` or `deny` expression.
219#[derive(Deserialize)]
220#[serde(deny_unknown_fields)]
221pub struct Policy {
222    /// The policy label.
223    pub label: Spanned<String>,
224
225    /// An allow expression.
226    #[serde(default)]
227    pub allow: Option<Spanned<String>>,
228
229    /// An deny expression.
230    #[serde(default)]
231    pub deny: Option<Spanned<String>>,
232}
233
234/// A policy binding.
235#[derive(Deserialize)]
236pub struct PolicyBinding {
237    /// The attribute set which will trigger the policy set.
238    pub attributes: Vec<Spanned<QualifiedAttributeName>>,
239
240    /// A set of policies triggered.
241    pub policies: Vec<Spanned<String>>,
242}
243
244/// An entity attribute binding, which assigns attributes to entities.
245#[derive(Deserialize)]
246pub struct EntityAttributeAssignment {
247    /// An Entity ID or label identifying the entity to assign to.
248    pub entity: Spanned<String>,
249
250    /// The attributes assigned to the entity.
251    pub attributes: Vec<Spanned<QualifiedAttributeName>>,
252}
253
254impl Document {
255    /// Deserialize document from `toml` format.
256    pub fn from_toml(toml: &str) -> anyhow::Result<Self> {
257        Ok(preprocess(toml::from_str(toml)?))
258    }
259}
260
261fn preprocess(mut doc: Document) -> Document {
262    for user in &mut doc.entity {
263        let label = user
264            .label
265            .get_or_insert_with(|| Spanned::new(0..0, Uuid::new_v4().to_string()));
266
267        for email in std::mem::take(&mut user.email) {
268            doc.email.push(Email {
269                entity: label.clone(),
270                value: email,
271            });
272        }
273
274        for pw_hash in std::mem::take(&mut user.password_hash) {
275            doc.password_hash.push(PasswordHash {
276                entity: label.clone(),
277                hash: pw_hash,
278            });
279        }
280    }
281
282    doc
283}