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