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
33#[serde_as]
34#[skip_serializing_none]
35#[derive(Serialize, Clone, Debug, Default)]
36#[serde(deny_unknown_fields, rename_all = "camelCase")]
37pub struct ScimListResponse {
38    pub schemas: Vec<String>,
39    pub total_results: u64,
40    pub items_per_page: Option<NonZeroU64>,
41    pub start_index: Option<NonZeroU64>,
42    pub resources: Vec<ScimEntryKanidm>,
43}
44
45#[derive(Serialize, Debug, Clone, ToSchema)]
46pub enum ScimAttributeEffectiveAccess {
47    /// All attributes on the entry have this permission granted
48    Grant,
49    /// All attributes on the entry have this permission denied
50    Deny,
51    /// The following attributes on the entry have this permission granted
52    Allow(BTreeSet<Attribute>),
53}
54
55impl ScimAttributeEffectiveAccess {
56    /// Check if the effective access allows or denies this attribute
57    pub fn check(&self, attr: &Attribute) -> bool {
58        match self {
59            Self::Grant => true,
60            Self::Deny => false,
61            Self::Allow(set) => set.contains(attr),
62        }
63    }
64
65    /// Check if the effective access allows ANY of the attributes
66    pub fn check_any(&self, attrs: &BTreeSet<Attribute>) -> bool {
67        match self {
68            Self::Grant => true,
69            Self::Deny => false,
70            Self::Allow(set) => attrs.intersection(set).next().is_some(),
71        }
72    }
73}
74
75#[derive(Serialize, Debug, Clone, ToSchema)]
76#[serde(rename_all = "camelCase")]
77pub struct ScimEffectiveAccess {
78    /// The identity that inherits the effective permission
79    pub ident: Uuid,
80    /// If the ident may delete the target entry
81    pub delete: bool,
82    /// The set of effective access over search events
83    pub search: ScimAttributeEffectiveAccess,
84    /// The set of effective access over modify present events
85    pub modify_present: ScimAttributeEffectiveAccess,
86    /// The set of effective access over modify remove events
87    pub modify_remove: ScimAttributeEffectiveAccess,
88}
89
90#[derive(Serialize, Debug, Clone, ToSchema)]
91#[serde(rename_all = "camelCase")]
92pub struct ScimAddress {
93    pub formatted: String,
94    pub street_address: String,
95    pub locality: String,
96    pub region: String,
97    pub postal_code: String,
98    pub country: String,
99}
100
101#[derive(Serialize, Debug, Clone, ToSchema)]
102#[serde(rename_all = "camelCase")]
103pub struct ScimApplicationPassword {
104    pub uuid: Uuid,
105    pub application_uuid: Uuid,
106    pub label: String,
107}
108
109#[serde_as]
110#[derive(Serialize, Debug, Clone, ToSchema)]
111#[serde(rename_all = "camelCase")]
112pub struct ScimBinary {
113    pub label: String,
114    #[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
115    pub value: Vec<u8>,
116}
117
118#[serde_as]
119#[derive(Serialize, Debug, Clone, ToSchema)]
120#[serde(rename_all = "camelCase")]
121pub struct ScimCertificate {
122    #[serde_as(as = "Hex")]
123    pub s256: Vec<u8>,
124    #[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
125    pub der: Vec<u8>,
126}
127
128#[serde_as]
129#[derive(Serialize, Debug, Clone, ToSchema)]
130#[serde(rename_all = "camelCase")]
131pub struct ScimAuditString {
132    #[serde_as(as = "Rfc3339")]
133    pub date_time: OffsetDateTime,
134    pub value: String,
135}
136
137#[derive(Serialize, Debug, Clone, ToSchema)]
138#[serde(rename_all = "camelCase")]
139pub enum ScimIntentTokenState {
140    Valid,
141    InProgress,
142    Consumed,
143}
144
145#[serde_as]
146#[derive(Serialize, Debug, Clone, ToSchema)]
147#[serde(rename_all = "camelCase")]
148pub struct ScimIntentToken {
149    pub token_id: String,
150    pub state: ScimIntentTokenState,
151    #[serde_as(as = "Rfc3339")]
152    pub expires: OffsetDateTime,
153}
154
155#[serde_as]
156#[derive(Serialize, Debug, Clone, ToSchema)]
157#[serde(rename_all = "camelCase")]
158pub struct ScimKeyInternal {
159    pub key_id: String,
160    pub status: String,
161    pub usage: String,
162    #[serde_as(as = "Rfc3339")]
163    pub valid_from: OffsetDateTime,
164}
165
166#[serde_as]
167#[skip_serializing_none]
168#[derive(Serialize, Debug, Clone, ToSchema)]
169#[serde(rename_all = "camelCase")]
170pub struct ScimAuthSession {
171    pub id: Uuid,
172    #[serde_as(as = "Option<Rfc3339>")]
173    pub expires: Option<OffsetDateTime>,
174    #[serde_as(as = "Option<Rfc3339>")]
175    pub revoked: Option<OffsetDateTime>,
176    #[serde_as(as = "Rfc3339")]
177    pub issued_at: OffsetDateTime,
178    pub issued_by: Uuid,
179    pub credential_id: Uuid,
180    pub auth_type: String,
181    pub session_scope: String,
182}
183
184#[serde_as]
185#[skip_serializing_none]
186#[derive(Serialize, Debug, Clone, ToSchema)]
187#[serde(rename_all = "camelCase")]
188pub struct ScimOAuth2Session {
189    pub id: Uuid,
190    pub parent_id: Option<Uuid>,
191    pub client_id: Uuid,
192    #[serde_as(as = "Rfc3339")]
193    pub issued_at: OffsetDateTime,
194    #[serde_as(as = "Option<Rfc3339>")]
195    pub expires: Option<OffsetDateTime>,
196    #[serde_as(as = "Option<Rfc3339>")]
197    pub revoked: Option<OffsetDateTime>,
198}
199
200#[serde_as]
201#[skip_serializing_none]
202#[derive(Serialize, Debug, Clone, ToSchema)]
203#[serde(rename_all = "camelCase")]
204pub struct ScimApiToken {
205    pub id: Uuid,
206    pub label: String,
207    #[serde_as(as = "Option<Rfc3339>")]
208    pub expires: Option<OffsetDateTime>,
209    #[serde_as(as = "Rfc3339")]
210    pub issued_at: OffsetDateTime,
211    pub issued_by: Uuid,
212    pub scope: String,
213}
214
215#[serde_as]
216#[derive(Serialize, Debug, Clone, ToSchema)]
217#[serde(rename_all = "camelCase")]
218pub struct ScimOAuth2ScopeMap {
219    pub group: String,
220    pub group_uuid: Uuid,
221    pub scopes: BTreeSet<String>,
222}
223
224#[serde_as]
225#[derive(Serialize, Debug, Clone, ToSchema)]
226#[serde(rename_all = "camelCase")]
227pub struct ScimOAuth2ClaimMap {
228    pub group: String,
229    pub group_uuid: Uuid,
230    pub claim: String,
231    pub join_char: ScimOauth2ClaimMapJoinChar,
232    pub values: BTreeSet<String>,
233}
234
235#[derive(Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
236#[serde(rename_all = "camelCase")]
237pub struct ScimReference {
238    pub uuid: Uuid,
239    pub value: String,
240}
241
242/// This is a strongly typed ScimValue for Kanidm. It is for serialisation only
243/// since on a deserialisation path we can not know the intent of the sender
244/// to how we deserialise strings. Additionally during deserialisation we need
245/// to accept optional or partial types too.
246#[serde_as]
247#[derive(Serialize, Debug, Clone, ToSchema)]
248#[serde(untagged)]
249pub enum ScimValueKanidm {
250    Bool(bool),
251    Uint32(u32),
252    Integer(i64),
253    Decimal(f64),
254    String(String),
255
256    // Other strong outbound types.
257    DateTime(#[serde_as(as = "Rfc3339")] OffsetDateTime),
258    Reference(Url),
259    Uuid(Uuid),
260    EntryReference(ScimReference),
261    EntryReferences(Vec<ScimReference>),
262
263    ArrayString(Vec<String>),
264    ArrayDateTime(#[serde_as(as = "Vec<Rfc3339>")] Vec<OffsetDateTime>),
265    ArrayUuid(Vec<Uuid>),
266    ArrayBinary(Vec<ScimBinary>),
267    ArrayCertificate(Vec<ScimCertificate>),
268
269    Address(Vec<ScimAddress>),
270    Mail(Vec<ScimMail>),
271    ApplicationPassword(Vec<ScimApplicationPassword>),
272    AuditString(Vec<ScimAuditString>),
273    SshPublicKey(Vec<ScimSshPublicKey>),
274    AuthSession(Vec<ScimAuthSession>),
275    OAuth2Session(Vec<ScimOAuth2Session>),
276    ApiToken(Vec<ScimApiToken>),
277    IntentToken(Vec<ScimIntentToken>),
278    OAuth2ScopeMap(Vec<ScimOAuth2ScopeMap>),
279    OAuth2ClaimMap(Vec<ScimOAuth2ClaimMap>),
280    KeyInternal(Vec<ScimKeyInternal>),
281    UiHints(Vec<UiHint>),
282}
283
284#[serde_as]
285#[derive(Serialize, Debug, Clone, ToSchema)]
286pub struct ScimPerson {
287    pub uuid: Uuid,
288    pub name: String,
289    pub displayname: String,
290    pub spn: String,
291    pub description: Option<String>,
292    pub mails: Vec<ScimMail>,
293    pub managed_by: Option<ScimReference>,
294    pub groups: Vec<ScimReference>,
295}
296
297impl TryFrom<ScimEntryKanidm> for ScimPerson {
298    type Error = ();
299
300    fn try_from(scim_entry: ScimEntryKanidm) -> Result<Self, Self::Error> {
301        let uuid = scim_entry.header.id;
302        let name = scim_entry
303            .attrs
304            .get(&Attribute::Name)
305            .and_then(|v| match v {
306                ScimValueKanidm::String(s) => Some(s.clone()),
307                _ => None,
308            })
309            .ok_or(())?;
310
311        let displayname = scim_entry
312            .attrs
313            .get(&Attribute::DisplayName)
314            .and_then(|v| match v {
315                ScimValueKanidm::String(s) => Some(s.clone()),
316                _ => None,
317            })
318            .ok_or(())?;
319
320        let spn = scim_entry
321            .attrs
322            .get(&Attribute::Spn)
323            .and_then(|v| match v {
324                ScimValueKanidm::String(s) => Some(s.clone()),
325                _ => None,
326            })
327            .ok_or(())?;
328
329        let description = scim_entry
330            .attrs
331            .get(&Attribute::Description)
332            .and_then(|v| match v {
333                ScimValueKanidm::String(s) => Some(s.clone()),
334                _ => None,
335            });
336
337        let mails = scim_entry
338            .attrs
339            .get(&Attribute::Mail)
340            .and_then(|v| match v {
341                ScimValueKanidm::Mail(m) => Some(m.clone()),
342                _ => None,
343            })
344            .unwrap_or_default();
345
346        let groups = scim_entry
347            .attrs
348            .get(&Attribute::DirectMemberOf)
349            .and_then(|v| match v {
350                ScimValueKanidm::EntryReferences(v) => Some(v.clone()),
351                _ => None,
352            })
353            .unwrap_or_default();
354
355        let managed_by = scim_entry
356            .attrs
357            .get(&Attribute::EntryManagedBy)
358            .and_then(|v| match v {
359                ScimValueKanidm::EntryReference(v) => Some(v.clone()),
360                _ => None,
361            });
362
363        Ok(ScimPerson {
364            uuid,
365            name,
366            displayname,
367            spn,
368            description,
369            mails,
370            managed_by,
371            groups,
372        })
373    }
374}
375
376#[serde_as]
377#[derive(Serialize, Debug, Clone, ToSchema)]
378pub struct ScimGroup {
379    pub uuid: Uuid,
380    pub name: String,
381}
382
383impl TryFrom<ScimEntryKanidm> for ScimGroup {
384    type Error = ();
385
386    fn try_from(scim_entry: ScimEntryKanidm) -> Result<Self, Self::Error> {
387        let uuid = scim_entry.header.id;
388        let name = scim_entry
389            .attrs
390            .get(&Attribute::Name)
391            .and_then(|v| match v {
392                ScimValueKanidm::String(s) => Some(s.clone()),
393                _ => None,
394            })
395            .ok_or(())?;
396
397        Ok(ScimGroup { uuid, name })
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<ScimApplicationPassword>> for ScimValueKanidm {
474    fn from(set: Vec<ScimApplicationPassword>) -> 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}