kanidm_proto/scim_v1/
server.rs

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