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    Int64(i64),
272    Uint64(u64),
273    Integer(i64),
274    Decimal(f64),
275    String(String),
276
277    // Other strong outbound types.
278    DateTime(#[serde_as(as = "Rfc3339")] OffsetDateTime),
279    Reference(Url),
280    Uuid(Uuid),
281    EntryReference(ScimReference),
282    EntryReferences(Vec<ScimReference>),
283
284    ArrayString(Vec<String>),
285    ArrayDateTime(#[serde_as(as = "Vec<Rfc3339>")] Vec<OffsetDateTime>),
286    ArrayUuid(Vec<Uuid>),
287    ArrayBinary(Vec<ScimBinary>),
288    ArrayCertificate(Vec<ScimCertificate>),
289
290    Address(Vec<ScimAddress>),
291    Mail(Vec<ScimMail>),
292    ApplicationPassword(Vec<ScimApplicationPasswordReference>),
293    AuditString(Vec<ScimAuditString>),
294    SshPublicKey(Vec<ScimSshPublicKey>),
295    AuthSession(Vec<ScimAuthSession>),
296    OAuth2Session(Vec<ScimOAuth2Session>),
297    ApiToken(Vec<ScimApiToken>),
298    IntentToken(Vec<ScimIntentToken>),
299    OAuth2ScopeMap(Vec<ScimOAuth2ScopeMap>),
300    OAuth2ClaimMap(Vec<ScimOAuth2ClaimMap>),
301    KeyInternal(Vec<ScimKeyInternal>),
302    UiHints(Vec<UiHint>),
303
304    Message(OutboundMessage),
305
306    #[schema(value_type = Vec<String>)]
307    Sha256(#[serde_as(as = "Vec<Hex>")] Vec<Sha256Output>),
308}
309
310#[serde_as]
311#[derive(Serialize, Debug, Clone, ToSchema)]
312pub struct ScimPerson {
313    pub uuid: Uuid,
314    pub name: String,
315    pub displayname: String,
316    pub spn: String,
317    pub description: Option<String>,
318    pub mails: Vec<ScimMail>,
319    pub managed_by: Option<ScimReference>,
320    pub groups: Vec<ScimReference>,
321}
322
323impl TryFrom<ScimEntryKanidm> for ScimPerson {
324    type Error = ();
325
326    fn try_from(scim_entry: ScimEntryKanidm) -> Result<Self, Self::Error> {
327        let uuid = scim_entry.header.id;
328        let name = scim_entry
329            .get_string_attr(&Attribute::Name)
330            .cloned()
331            .ok_or(())?;
332        let displayname = scim_entry
333            .get_string_attr(&Attribute::DisplayName)
334            .cloned()
335            .ok_or(())?;
336        let spn = scim_entry
337            .get_string_attr(&Attribute::Spn)
338            .cloned()
339            .ok_or(())?;
340        let description = scim_entry.get_string_attr(&Attribute::Description).cloned();
341
342        let mails = scim_entry
343            .attrs
344            .get(&Attribute::Mail)
345            .and_then(|v| match v {
346                ScimValueKanidm::Mail(m) => Some(m.clone()),
347                _ => None,
348            })
349            .unwrap_or_default();
350
351        let groups = scim_entry
352            .get_scim_refs_attr(&Attribute::DirectMemberOf)
353            .cloned()
354            .unwrap_or_default();
355
356        let managed_by = scim_entry
357            .attrs
358            .get(&Attribute::EntryManagedBy)
359            .and_then(|v| match v {
360                ScimValueKanidm::EntryReference(v) => Some(v.clone()),
361                _ => None,
362            });
363
364        Ok(ScimPerson {
365            uuid,
366            name,
367            displayname,
368            spn,
369            description,
370            mails,
371            managed_by,
372            groups,
373        })
374    }
375}
376
377#[serde_as]
378#[derive(Serialize, Debug, Clone, ToSchema)]
379pub struct ScimGroup {
380    pub uuid: Uuid,
381    pub name: String,
382    pub description: Option<String>,
383    pub members: Vec<ScimReference>,
384}
385
386impl TryFrom<ScimEntryKanidm> for ScimGroup {
387    type Error = ();
388
389    fn try_from(scim_entry: ScimEntryKanidm) -> Result<Self, Self::Error> {
390        let uuid = scim_entry.header.id;
391        let name = scim_entry
392            .get_string_attr(&Attribute::Name)
393            .cloned()
394            .ok_or(())?;
395        let description = scim_entry.get_string_attr(&Attribute::Description).cloned();
396        let members = scim_entry
397            .get_scim_refs_attr(&Attribute::Member)
398            .cloned()
399            .unwrap_or_default();
400
401        Ok(ScimGroup {
402            uuid,
403            name,
404            description,
405            members,
406        })
407    }
408}
409
410impl From<bool> for ScimValueKanidm {
411    fn from(b: bool) -> Self {
412        Self::Bool(b)
413    }
414}
415
416impl From<OffsetDateTime> for ScimValueKanidm {
417    fn from(odt: OffsetDateTime) -> Self {
418        Self::DateTime(odt)
419    }
420}
421
422impl From<Vec<UiHint>> for ScimValueKanidm {
423    fn from(set: Vec<UiHint>) -> Self {
424        Self::UiHints(set)
425    }
426}
427
428impl From<Vec<OffsetDateTime>> for ScimValueKanidm {
429    fn from(set: Vec<OffsetDateTime>) -> Self {
430        Self::ArrayDateTime(set)
431    }
432}
433
434impl From<String> for ScimValueKanidm {
435    fn from(s: String) -> Self {
436        Self::String(s)
437    }
438}
439
440impl From<&str> for ScimValueKanidm {
441    fn from(s: &str) -> Self {
442        Self::String(s.to_string())
443    }
444}
445
446impl From<Vec<String>> for ScimValueKanidm {
447    fn from(set: Vec<String>) -> Self {
448        Self::ArrayString(set)
449    }
450}
451
452impl From<Uuid> for ScimValueKanidm {
453    fn from(u: Uuid) -> Self {
454        Self::Uuid(u)
455    }
456}
457
458impl From<Vec<Uuid>> for ScimValueKanidm {
459    fn from(set: Vec<Uuid>) -> Self {
460        Self::ArrayUuid(set)
461    }
462}
463
464impl From<u32> for ScimValueKanidm {
465    fn from(u: u32) -> Self {
466        Self::Uint32(u)
467    }
468}
469
470impl From<i64> for ScimValueKanidm {
471    fn from(u: i64) -> Self {
472        Self::Int64(u)
473    }
474}
475
476impl From<u64> for ScimValueKanidm {
477    fn from(u: u64) -> Self {
478        Self::Uint64(u)
479    }
480}
481
482impl From<Vec<ScimAddress>> for ScimValueKanidm {
483    fn from(set: Vec<ScimAddress>) -> Self {
484        Self::Address(set)
485    }
486}
487
488impl From<Vec<ScimMail>> for ScimValueKanidm {
489    fn from(set: Vec<ScimMail>) -> Self {
490        Self::Mail(set)
491    }
492}
493
494impl From<Vec<ScimApplicationPasswordReference>> for ScimValueKanidm {
495    fn from(set: Vec<ScimApplicationPasswordReference>) -> Self {
496        Self::ApplicationPassword(set)
497    }
498}
499
500impl From<Vec<ScimAuditString>> for ScimValueKanidm {
501    fn from(set: Vec<ScimAuditString>) -> Self {
502        Self::AuditString(set)
503    }
504}
505
506impl From<Vec<ScimBinary>> for ScimValueKanidm {
507    fn from(set: Vec<ScimBinary>) -> Self {
508        Self::ArrayBinary(set)
509    }
510}
511
512impl From<Vec<ScimCertificate>> for ScimValueKanidm {
513    fn from(set: Vec<ScimCertificate>) -> Self {
514        Self::ArrayCertificate(set)
515    }
516}
517
518impl From<Vec<ScimSshPublicKey>> for ScimValueKanidm {
519    fn from(set: Vec<ScimSshPublicKey>) -> Self {
520        Self::SshPublicKey(set)
521    }
522}
523
524impl From<Vec<ScimAuthSession>> for ScimValueKanidm {
525    fn from(set: Vec<ScimAuthSession>) -> Self {
526        Self::AuthSession(set)
527    }
528}
529
530impl From<Vec<ScimOAuth2Session>> for ScimValueKanidm {
531    fn from(set: Vec<ScimOAuth2Session>) -> Self {
532        Self::OAuth2Session(set)
533    }
534}
535
536impl From<Vec<ScimApiToken>> for ScimValueKanidm {
537    fn from(set: Vec<ScimApiToken>) -> Self {
538        Self::ApiToken(set)
539    }
540}
541
542impl From<Vec<ScimIntentToken>> for ScimValueKanidm {
543    fn from(set: Vec<ScimIntentToken>) -> Self {
544        Self::IntentToken(set)
545    }
546}
547
548impl From<Vec<ScimOAuth2ScopeMap>> for ScimValueKanidm {
549    fn from(set: Vec<ScimOAuth2ScopeMap>) -> Self {
550        Self::OAuth2ScopeMap(set)
551    }
552}
553
554impl From<Vec<ScimOAuth2ClaimMap>> for ScimValueKanidm {
555    fn from(set: Vec<ScimOAuth2ClaimMap>) -> Self {
556        Self::OAuth2ClaimMap(set)
557    }
558}
559
560impl From<Vec<ScimKeyInternal>> for ScimValueKanidm {
561    fn from(set: Vec<ScimKeyInternal>) -> Self {
562        Self::KeyInternal(set)
563    }
564}
565
566impl From<OutboundMessage> for ScimValueKanidm {
567    fn from(message: OutboundMessage) -> Self {
568        Self::Message(message)
569    }
570}
571
572impl From<Vec<Sha256Output>> for ScimValueKanidm {
573    fn from(set: Vec<Sha256Output>) -> Self {
574        Self::Sha256(set)
575    }
576}