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#[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 Grant,
71 Deny,
73 Allow(BTreeSet<Attribute>),
75}
76
77impl ScimAttributeEffectiveAccess {
78 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 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 pub ident: Uuid,
102 pub delete: bool,
104 pub search: ScimAttributeEffectiveAccess,
106 pub modify_present: ScimAttributeEffectiveAccess,
108 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#[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 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}