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