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