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