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