authly_common/
document.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
use serde::Deserialize;
use toml::Spanned;
use uuid::Uuid;

use crate::{id::Eid, property::QualifiedAttributeName};

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Document {
    #[serde(rename = "authly-document")]
    pub authly_document: AuthlyDocument,

    #[serde(default)]
    pub entity: Vec<Entity>,

    #[serde(default, rename = "service-entity")]
    pub service_entity: Vec<Entity>,

    #[serde(default)]
    pub email: Vec<Email>,

    #[serde(default, rename = "password-hash")]
    pub password_hash: Vec<PasswordHash>,

    #[serde(default)]
    pub members: Vec<Members>,

    #[serde(default, rename = "entity-property")]
    pub entity_property: Vec<EntityProperty>,

    #[serde(default, rename = "resource-property")]
    pub resource_property: Vec<ResourceProperty>,

    #[serde(default)]
    pub policy: Vec<Policy>,

    #[serde(default, rename = "policy-binding")]
    pub policy_binding: Vec<PolicyBinding>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AuthlyDocument {
    /// The ID of this document as an Authly authority
    pub id: Spanned<Uuid>,
}

#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Entity {
    pub eid: Spanned<Eid>,
    #[serde(default)]
    pub label: Option<Spanned<String>>,

    #[serde(default)]
    pub attributes: Vec<Spanned<QualifiedAttributeName>>,

    #[serde(default)]
    pub username: Option<Spanned<String>>,

    #[serde(default)]
    pub email: Vec<Spanned<String>>,

    #[serde(default, rename = "password-hash")]
    pub password_hash: Vec<String>,

    #[serde(default, rename = "kubernetes-account")]
    pub kubernetes_account: Option<KubernetesAccount>,
}

#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Email {
    pub entity: Spanned<String>,
    pub value: Spanned<String>,
}

#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct PasswordHash {
    pub entity: Spanned<String>,
    pub hash: String,
}

#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Members {
    pub entity: Spanned<String>,

    pub members: Vec<Spanned<String>>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EntityProperty {
    #[serde(default)]
    pub service: Option<Spanned<String>>,

    pub label: Spanned<String>,

    #[serde(default)]
    pub attributes: Vec<Spanned<String>>,
}

#[derive(Default, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ServiceK8sExt {
    #[serde(default, rename = "service-account")]
    pub service_account: Vec<KubernetesAccount>,
}

#[derive(Default, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct KubernetesAccount {
    pub namespace: String,
    pub name: String,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ResourceProperty {
    pub service: Spanned<String>,

    pub label: Spanned<String>,

    #[serde(default)]
    pub attributes: Vec<Spanned<String>>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Policy {
    pub service: Spanned<String>,
    pub label: Spanned<String>,

    #[serde(default)]
    pub allow: Option<Spanned<String>>,

    #[serde(default)]
    pub deny: Option<Spanned<String>>,
}

#[derive(Deserialize)]
pub struct PolicyBinding {
    pub service: Spanned<String>,
    pub attributes: Vec<Spanned<QualifiedAttributeName>>,
    pub policies: Vec<Spanned<String>>,
}

impl Document {
    pub fn from_toml(toml: &str) -> anyhow::Result<Self> {
        Ok(preprocess(toml::from_str(toml)?))
    }
}

fn preprocess(mut doc: Document) -> Document {
    for user in &mut doc.entity {
        let label = user
            .label
            .get_or_insert_with(|| Spanned::new(0..0, Uuid::new_v4().to_string()));

        for email in std::mem::take(&mut user.email) {
            doc.email.push(Email {
                entity: label.clone(),
                value: email,
            });
        }

        for pw_hash in std::mem::take(&mut user.password_hash) {
            doc.password_hash.push(PasswordHash {
                entity: label.clone(),
                hash: pw_hash,
            });
        }
    }

    doc
}

#[cfg(test)]
mod tests {
    #[test]
    fn testusers_example() {
        let toml = include_str!("../../../examples/0_testusers.toml");
        let document = super::Document::from_toml(toml).unwrap();

        assert_eq!(document.authly_document.id.span(), 23..61);
        // BUG: The span is off:
        assert_eq!(&toml[26..61], "83648f-e6ac-4492-87f7-43d5e5805d60\"");

        assert_eq!(document.entity[0].eid.span(), 80..88);
        assert_eq!(&toml[80..88], "\"111111\"");

        assert_eq!(document.entity.len(), 3);
    }

    #[test]
    fn testservice_example() {
        let toml = include_str!("../../../examples/1_testservice.toml");
        super::Document::from_toml(toml).unwrap();
    }
}