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#[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 Grant,
68 Deny,
70 Allow(BTreeSet<Attribute>),
72}
73
74impl ScimAttributeEffectiveAccess {
75 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 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 pub ident: Uuid,
99 pub delete: bool,
101 pub search: ScimAttributeEffectiveAccess,
103 pub modify_present: ScimAttributeEffectiveAccess,
105 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#[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 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}