kanidm_proto/scim_v1/
server.rs

1use super::ScimMail;
2use super::ScimOauth2ClaimMapJoinChar;
3use super::ScimSshPublicKey;
4use crate::attribute::Attribute;
5use crate::internal::UiHint;
6use scim_proto::ScimEntryHeader;
7use serde::Serialize;
8use serde_with::{base64, formats, hex::Hex, serde_as, skip_serializing_none};
9use std::collections::{BTreeMap, BTreeSet};
10use time::format_description::well_known::Rfc3339;
11use time::OffsetDateTime;
12use url::Url;
13use utoipa::ToSchema;
14use uuid::Uuid;
15
16/// A strongly typed ScimEntry that is for transmission to clients. This uses
17/// Kanidm internal strong types for values allowing direct serialisation and
18/// transmission.
19#[serde_as]
20#[skip_serializing_none]
21#[derive(Serialize, Debug, Clone, ToSchema)]
22pub struct ScimEntryKanidm {
23    #[serde(flatten)]
24    pub header: ScimEntryHeader,
25
26    pub ext_access_check: Option<ScimEffectiveAccess>,
27    #[serde(flatten)]
28    pub attrs: BTreeMap<Attribute, ScimValueKanidm>,
29}
30
31#[derive(Serialize, Debug, Clone, ToSchema)]
32pub enum ScimAttributeEffectiveAccess {
33    /// All attributes on the entry have this permission granted
34    Grant,
35    /// All attributes on the entry have this permission denied
36    Deny,
37    /// The following attributes on the entry have this permission granted
38    Allow(BTreeSet<Attribute>),
39}
40
41impl ScimAttributeEffectiveAccess {
42    /// Check if the effective access allows or denies this attribute
43    pub fn check(&self, attr: &Attribute) -> bool {
44        match self {
45            Self::Grant => true,
46            Self::Deny => false,
47            Self::Allow(set) => set.contains(attr),
48        }
49    }
50}
51
52#[derive(Serialize, Debug, Clone, ToSchema)]
53#[serde(rename_all = "camelCase")]
54pub struct ScimEffectiveAccess {
55    /// The identity that inherits the effective permission
56    pub ident: Uuid,
57    /// If the ident may delete the target entry
58    pub delete: bool,
59    /// The set of effective access over search events
60    pub search: ScimAttributeEffectiveAccess,
61    /// The set of effective access over modify present events
62    pub modify_present: ScimAttributeEffectiveAccess,
63    /// The set of effective access over modify remove events
64    pub modify_remove: ScimAttributeEffectiveAccess,
65}
66
67#[derive(Serialize, Debug, Clone, ToSchema)]
68#[serde(rename_all = "camelCase")]
69pub struct ScimAddress {
70    pub formatted: String,
71    pub street_address: String,
72    pub locality: String,
73    pub region: String,
74    pub postal_code: String,
75    pub country: String,
76}
77
78#[derive(Serialize, Debug, Clone, ToSchema)]
79#[serde(rename_all = "camelCase")]
80pub struct ScimApplicationPassword {
81    pub uuid: Uuid,
82    pub application_uuid: Uuid,
83    pub label: String,
84}
85
86#[serde_as]
87#[derive(Serialize, Debug, Clone, ToSchema)]
88#[serde(rename_all = "camelCase")]
89pub struct ScimBinary {
90    pub label: String,
91    #[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
92    pub value: Vec<u8>,
93}
94
95#[serde_as]
96#[derive(Serialize, Debug, Clone, ToSchema)]
97#[serde(rename_all = "camelCase")]
98pub struct ScimCertificate {
99    #[serde_as(as = "Hex")]
100    pub s256: Vec<u8>,
101    #[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
102    pub der: Vec<u8>,
103}
104
105#[serde_as]
106#[derive(Serialize, Debug, Clone, ToSchema)]
107#[serde(rename_all = "camelCase")]
108pub struct ScimAuditString {
109    #[serde_as(as = "Rfc3339")]
110    pub date_time: OffsetDateTime,
111    pub value: String,
112}
113
114#[derive(Serialize, Debug, Clone, ToSchema)]
115#[serde(rename_all = "camelCase")]
116pub enum ScimIntentTokenState {
117    Valid,
118    InProgress,
119    Consumed,
120}
121
122#[serde_as]
123#[derive(Serialize, Debug, Clone, ToSchema)]
124#[serde(rename_all = "camelCase")]
125pub struct ScimIntentToken {
126    pub token_id: String,
127    pub state: ScimIntentTokenState,
128    #[serde_as(as = "Rfc3339")]
129    pub expires: OffsetDateTime,
130}
131
132#[serde_as]
133#[derive(Serialize, Debug, Clone, ToSchema)]
134#[serde(rename_all = "camelCase")]
135pub struct ScimKeyInternal {
136    pub key_id: String,
137    pub status: String,
138    pub usage: String,
139    #[serde_as(as = "Rfc3339")]
140    pub valid_from: OffsetDateTime,
141}
142
143#[serde_as]
144#[skip_serializing_none]
145#[derive(Serialize, Debug, Clone, ToSchema)]
146#[serde(rename_all = "camelCase")]
147pub struct ScimAuthSession {
148    pub id: Uuid,
149    #[serde_as(as = "Option<Rfc3339>")]
150    pub expires: Option<OffsetDateTime>,
151    #[serde_as(as = "Option<Rfc3339>")]
152    pub revoked: Option<OffsetDateTime>,
153    #[serde_as(as = "Rfc3339")]
154    pub issued_at: OffsetDateTime,
155    pub issued_by: Uuid,
156    pub credential_id: Uuid,
157    pub auth_type: String,
158    pub session_scope: String,
159}
160
161#[serde_as]
162#[skip_serializing_none]
163#[derive(Serialize, Debug, Clone, ToSchema)]
164#[serde(rename_all = "camelCase")]
165pub struct ScimOAuth2Session {
166    pub id: Uuid,
167    pub parent_id: Option<Uuid>,
168    pub client_id: Uuid,
169    #[serde_as(as = "Rfc3339")]
170    pub issued_at: OffsetDateTime,
171    #[serde_as(as = "Option<Rfc3339>")]
172    pub expires: Option<OffsetDateTime>,
173    #[serde_as(as = "Option<Rfc3339>")]
174    pub revoked: Option<OffsetDateTime>,
175}
176
177#[serde_as]
178#[skip_serializing_none]
179#[derive(Serialize, Debug, Clone, ToSchema)]
180#[serde(rename_all = "camelCase")]
181pub struct ScimApiToken {
182    pub id: Uuid,
183    pub label: String,
184    #[serde_as(as = "Option<Rfc3339>")]
185    pub expires: Option<OffsetDateTime>,
186    #[serde_as(as = "Rfc3339")]
187    pub issued_at: OffsetDateTime,
188    pub issued_by: Uuid,
189    pub scope: String,
190}
191
192#[serde_as]
193#[derive(Serialize, Debug, Clone, ToSchema)]
194#[serde(rename_all = "camelCase")]
195pub struct ScimOAuth2ScopeMap {
196    pub group: String,
197    pub group_uuid: Uuid,
198    pub scopes: BTreeSet<String>,
199}
200
201#[serde_as]
202#[derive(Serialize, Debug, Clone, ToSchema)]
203#[serde(rename_all = "camelCase")]
204pub struct ScimOAuth2ClaimMap {
205    pub group: String,
206    pub group_uuid: Uuid,
207    pub claim: String,
208    pub join_char: ScimOauth2ClaimMapJoinChar,
209    pub values: BTreeSet<String>,
210}
211
212#[derive(Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
213#[serde(rename_all = "camelCase")]
214pub struct ScimReference {
215    pub uuid: Uuid,
216    pub value: String,
217}
218
219/// This is a strongly typed ScimValue for Kanidm. It is for serialisation only
220/// since on a deserialisation path we can not know the intent of the sender
221/// to how we deserialise strings. Additionally during deserialisation we need
222/// to accept optional or partial types too.
223#[serde_as]
224#[derive(Serialize, Debug, Clone, ToSchema)]
225#[serde(untagged)]
226pub enum ScimValueKanidm {
227    Bool(bool),
228    Uint32(u32),
229    Integer(i64),
230    Decimal(f64),
231    String(String),
232    DateTime(#[serde_as(as = "Rfc3339")] OffsetDateTime),
233    Reference(Url),
234    Uuid(Uuid),
235    EntryReference(ScimReference),
236    EntryReferences(Vec<ScimReference>),
237
238    // Other strong outbound types.
239    ArrayString(Vec<String>),
240    ArrayDateTime(#[serde_as(as = "Vec<Rfc3339>")] Vec<OffsetDateTime>),
241    ArrayUuid(Vec<Uuid>),
242    ArrayBinary(Vec<ScimBinary>),
243    ArrayCertificate(Vec<ScimCertificate>),
244
245    Address(Vec<ScimAddress>),
246    Mail(Vec<ScimMail>),
247    ApplicationPassword(Vec<ScimApplicationPassword>),
248    AuditString(Vec<ScimAuditString>),
249    SshPublicKey(Vec<ScimSshPublicKey>),
250    AuthSession(Vec<ScimAuthSession>),
251    OAuth2Session(Vec<ScimOAuth2Session>),
252    ApiToken(Vec<ScimApiToken>),
253    IntentToken(Vec<ScimIntentToken>),
254    OAuth2ScopeMap(Vec<ScimOAuth2ScopeMap>),
255    OAuth2ClaimMap(Vec<ScimOAuth2ClaimMap>),
256    KeyInternal(Vec<ScimKeyInternal>),
257    UiHints(Vec<UiHint>),
258}
259
260#[serde_as]
261#[derive(Serialize, Debug, Clone, ToSchema)]
262pub struct ScimPerson {
263    pub uuid: Uuid,
264    pub name: String,
265    pub displayname: String,
266    pub spn: String,
267    pub description: Option<String>,
268    pub mails: Vec<ScimMail>,
269    pub managed_by: Option<ScimReference>,
270    pub groups: Vec<ScimReference>,
271}
272
273impl TryFrom<ScimEntryKanidm> for ScimPerson {
274    type Error = ();
275
276    fn try_from(scim_entry: ScimEntryKanidm) -> Result<Self, Self::Error> {
277        let uuid = scim_entry.header.id;
278        let name = scim_entry
279            .attrs
280            .get(&Attribute::Name)
281            .and_then(|v| match v {
282                ScimValueKanidm::String(s) => Some(s.clone()),
283                _ => None,
284            })
285            .ok_or(())?;
286
287        let displayname = scim_entry
288            .attrs
289            .get(&Attribute::DisplayName)
290            .and_then(|v| match v {
291                ScimValueKanidm::String(s) => Some(s.clone()),
292                _ => None,
293            })
294            .ok_or(())?;
295
296        let spn = scim_entry
297            .attrs
298            .get(&Attribute::Spn)
299            .and_then(|v| match v {
300                ScimValueKanidm::String(s) => Some(s.clone()),
301                _ => None,
302            })
303            .ok_or(())?;
304
305        let description = scim_entry
306            .attrs
307            .get(&Attribute::Description)
308            .and_then(|v| match v {
309                ScimValueKanidm::String(s) => Some(s.clone()),
310                _ => None,
311            });
312
313        let mails = scim_entry
314            .attrs
315            .get(&Attribute::Mail)
316            .and_then(|v| match v {
317                ScimValueKanidm::Mail(m) => Some(m.clone()),
318                _ => None,
319            })
320            .unwrap_or_default();
321
322        let groups = scim_entry
323            .attrs
324            .get(&Attribute::DirectMemberOf)
325            .and_then(|v| match v {
326                ScimValueKanidm::EntryReferences(v) => Some(v.clone()),
327                _ => None,
328            })
329            .unwrap_or_default();
330
331        let managed_by = scim_entry
332            .attrs
333            .get(&Attribute::EntryManagedBy)
334            .and_then(|v| match v {
335                ScimValueKanidm::EntryReference(v) => Some(v.clone()),
336                _ => None,
337            });
338
339        Ok(ScimPerson {
340            uuid,
341            name,
342            displayname,
343            spn,
344            description,
345            mails,
346            managed_by,
347            groups,
348        })
349    }
350}
351
352impl From<bool> for ScimValueKanidm {
353    fn from(b: bool) -> Self {
354        Self::Bool(b)
355    }
356}
357
358impl From<OffsetDateTime> for ScimValueKanidm {
359    fn from(odt: OffsetDateTime) -> Self {
360        Self::DateTime(odt)
361    }
362}
363
364impl From<Vec<UiHint>> for ScimValueKanidm {
365    fn from(set: Vec<UiHint>) -> Self {
366        Self::UiHints(set)
367    }
368}
369
370impl From<Vec<OffsetDateTime>> for ScimValueKanidm {
371    fn from(set: Vec<OffsetDateTime>) -> Self {
372        Self::ArrayDateTime(set)
373    }
374}
375
376impl From<String> for ScimValueKanidm {
377    fn from(s: String) -> Self {
378        Self::String(s)
379    }
380}
381
382impl From<&str> for ScimValueKanidm {
383    fn from(s: &str) -> Self {
384        Self::String(s.to_string())
385    }
386}
387
388impl From<Vec<String>> for ScimValueKanidm {
389    fn from(set: Vec<String>) -> Self {
390        Self::ArrayString(set)
391    }
392}
393
394impl From<Uuid> for ScimValueKanidm {
395    fn from(u: Uuid) -> Self {
396        Self::Uuid(u)
397    }
398}
399
400impl From<Vec<Uuid>> for ScimValueKanidm {
401    fn from(set: Vec<Uuid>) -> Self {
402        Self::ArrayUuid(set)
403    }
404}
405
406impl From<u32> for ScimValueKanidm {
407    fn from(u: u32) -> Self {
408        Self::Uint32(u)
409    }
410}
411
412impl From<Vec<ScimAddress>> for ScimValueKanidm {
413    fn from(set: Vec<ScimAddress>) -> Self {
414        Self::Address(set)
415    }
416}
417
418impl From<Vec<ScimMail>> for ScimValueKanidm {
419    fn from(set: Vec<ScimMail>) -> Self {
420        Self::Mail(set)
421    }
422}
423
424impl From<Vec<ScimApplicationPassword>> for ScimValueKanidm {
425    fn from(set: Vec<ScimApplicationPassword>) -> Self {
426        Self::ApplicationPassword(set)
427    }
428}
429
430impl From<Vec<ScimAuditString>> for ScimValueKanidm {
431    fn from(set: Vec<ScimAuditString>) -> Self {
432        Self::AuditString(set)
433    }
434}
435
436impl From<Vec<ScimBinary>> for ScimValueKanidm {
437    fn from(set: Vec<ScimBinary>) -> Self {
438        Self::ArrayBinary(set)
439    }
440}
441
442impl From<Vec<ScimCertificate>> for ScimValueKanidm {
443    fn from(set: Vec<ScimCertificate>) -> Self {
444        Self::ArrayCertificate(set)
445    }
446}
447
448impl From<Vec<ScimSshPublicKey>> for ScimValueKanidm {
449    fn from(set: Vec<ScimSshPublicKey>) -> Self {
450        Self::SshPublicKey(set)
451    }
452}
453
454impl From<Vec<ScimAuthSession>> for ScimValueKanidm {
455    fn from(set: Vec<ScimAuthSession>) -> Self {
456        Self::AuthSession(set)
457    }
458}
459
460impl From<Vec<ScimOAuth2Session>> for ScimValueKanidm {
461    fn from(set: Vec<ScimOAuth2Session>) -> Self {
462        Self::OAuth2Session(set)
463    }
464}
465
466impl From<Vec<ScimApiToken>> for ScimValueKanidm {
467    fn from(set: Vec<ScimApiToken>) -> Self {
468        Self::ApiToken(set)
469    }
470}
471
472impl From<Vec<ScimIntentToken>> for ScimValueKanidm {
473    fn from(set: Vec<ScimIntentToken>) -> Self {
474        Self::IntentToken(set)
475    }
476}
477
478impl From<Vec<ScimOAuth2ScopeMap>> for ScimValueKanidm {
479    fn from(set: Vec<ScimOAuth2ScopeMap>) -> Self {
480        Self::OAuth2ScopeMap(set)
481    }
482}
483
484impl From<Vec<ScimOAuth2ClaimMap>> for ScimValueKanidm {
485    fn from(set: Vec<ScimOAuth2ClaimMap>) -> Self {
486        Self::OAuth2ClaimMap(set)
487    }
488}
489
490impl From<Vec<ScimKeyInternal>> for ScimValueKanidm {
491    fn from(set: Vec<ScimKeyInternal>) -> Self {
492        Self::KeyInternal(set)
493    }
494}