1use super::{ScimEntryGeneric, ScimEntryGetQuery, ScimMail, ScimOauth2ClaimMapJoinChar};
3use crate::attribute::Attribute;
4use crate::v1::OutboundMessage;
5use scim_proto::ScimEntryHeader;
6use serde::{Deserialize, Serialize};
7use serde_json::Value as JsonValue;
8use serde_with::formats::PreferMany;
9use serde_with::OneOrMany;
10use serde_with::{base64, formats, serde_as, skip_serializing_none};
11use sshkey_attest::proto::PublicKey as SshPublicKey;
12use std::collections::{BTreeMap, BTreeSet};
13use std::num::NonZeroU64;
14use time::format_description::well_known::Rfc3339;
15use time::OffsetDateTime;
16use url::Url;
17use utoipa::ToSchema;
18use uuid::Uuid;
19
20pub type ScimSshPublicKeys = Vec<ScimSshPublicKey>;
21
22#[derive(Deserialize, Serialize, Debug, Clone)]
23#[serde(deny_unknown_fields, rename_all = "camelCase")]
24pub struct ScimSshPublicKey {
25 pub label: String,
26 pub value: SshPublicKey,
27}
28
29#[derive(Deserialize)]
30#[serde(untagged)]
31enum ScimReferenceAdapter {
32 Complete { uuid: Uuid, value: String },
33 Uuid { uuid: Uuid },
34 UuidX(Uuid),
35 Value { value: String },
36 ValueX(String),
37}
38
39impl From<ScimReferenceAdapter> for ScimReference {
40 fn from(scr: ScimReferenceAdapter) -> Self {
41 match scr {
42 ScimReferenceAdapter::Complete { uuid, value } => ScimReference {
43 uuid: Some(uuid),
44 value: Some(value),
45 },
46 ScimReferenceAdapter::Uuid { uuid } | ScimReferenceAdapter::UuidX(uuid) => {
47 ScimReference {
48 uuid: Some(uuid),
49 value: None,
50 }
51 }
52 ScimReferenceAdapter::Value { value } | ScimReferenceAdapter::ValueX(value) => {
53 ScimReference {
54 uuid: None,
55 value: Some(value),
56 }
57 }
58 }
59 }
60}
61
62#[serde_as]
63#[skip_serializing_none]
64#[derive(Deserialize, Serialize, Debug, Clone)]
65#[serde(
66 deny_unknown_fields,
67 rename_all = "camelCase",
68 from = "ScimReferenceAdapter"
69)]
70pub struct ScimReference {
71 pub uuid: Option<Uuid>,
72 pub value: Option<String>,
73}
74
75impl<T> From<T> for ScimReference
76where
77 T: AsRef<str>,
78{
79 fn from(value: T) -> Self {
80 ScimReference {
81 uuid: None,
82 value: Some(value.as_ref().to_string()),
83 }
84 }
85}
86
87pub type ScimReferences = Vec<ScimReference>;
88
89#[serde_as]
90#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
91#[serde(transparent)]
92pub struct ScimDateTime {
93 #[serde_as(as = "Rfc3339")]
94 pub date_time: OffsetDateTime,
95}
96
97#[serde_as]
98#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
99#[serde(rename_all = "camelCase")]
100pub struct ScimCertificate {
101 #[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
102 pub der: Vec<u8>,
103}
104
105#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
106#[serde(rename_all = "camelCase")]
107pub struct ScimAddress {
108 pub street_address: String,
109 pub locality: String,
110 pub region: String,
111 pub postal_code: String,
112 pub country: String,
113}
114
115#[serde_as]
116#[derive(Deserialize, Serialize, Debug, Clone)]
117#[serde(rename_all = "camelCase")]
118pub struct ScimOAuth2ClaimMap {
119 pub group: Option<String>,
120 pub group_uuid: Option<Uuid>,
121 pub claim: String,
122 pub join_char: ScimOauth2ClaimMapJoinChar,
123 pub values: BTreeSet<String>,
124}
125
126#[serde_as]
127#[derive(Deserialize, Serialize, Debug, Clone)]
128#[serde(rename_all = "camelCase")]
129pub struct ScimOAuth2ScopeMap {
130 pub group: Option<String>,
131 pub group_uuid: Option<Uuid>,
132 pub scopes: BTreeSet<String>,
133}
134
135#[serde_as]
136#[derive(Serialize, Deserialize, Clone, Debug)]
137#[serde(rename_all = "camelCase")]
138pub struct ScimListEntry {
139 pub schemas: Vec<String>,
140 pub total_results: u64,
141 pub items_per_page: Option<NonZeroU64>,
142 pub start_index: Option<NonZeroU64>,
143 pub resources: Vec<ScimEntryGeneric>,
144}
145
146#[serde_as]
147#[derive(Serialize, Debug, Clone)]
148#[serde(rename_all = "snake_case")]
149pub struct ScimEntryApplicationPost {
150 pub name: String,
151 pub displayname: String,
152 pub linked_group: ScimReference,
153}
154
155#[serde_as]
156#[derive(Deserialize, Debug, Clone)]
157#[serde(rename_all = "snake_case")]
158pub struct ScimEntryApplication {
159 #[serde(flatten)]
160 pub header: ScimEntryHeader,
161
162 pub name: String,
163 pub displayname: String,
164
165 pub linked_group: Vec<super::ScimReference>,
166
167 #[serde(flatten)]
168 pub attrs: BTreeMap<Attribute, JsonValue>,
169}
170
171#[serde_as]
172#[derive(Deserialize, Clone, Debug)]
173#[serde(rename_all = "camelCase")]
174pub struct ScimListApplication {
175 pub schemas: Vec<String>,
176 pub total_results: u64,
177 pub items_per_page: Option<NonZeroU64>,
178 pub start_index: Option<NonZeroU64>,
179 pub resources: Vec<ScimEntryApplication>,
180}
181
182#[serde_as]
183#[derive(Serialize, Deserialize, Debug, Clone)]
184#[serde(rename_all = "snake_case")]
185pub struct ScimEntryMessage {
186 #[serde(flatten)]
187 pub header: ScimEntryHeader,
188
189 pub message_template: OutboundMessage,
190 pub send_after: ScimDateTime,
191 pub delete_after: ScimDateTime,
192 pub sent_at: Option<ScimDateTime>,
193 pub mail_destination: Vec<ScimMail>,
194
195 #[serde(flatten)]
196 pub attrs: BTreeMap<Attribute, JsonValue>,
197}
198
199#[serde_as]
200#[derive(Serialize, Deserialize, Clone, Debug)]
201#[serde(rename_all = "camelCase")]
202pub struct ScimListMessage {
203 pub schemas: Vec<String>,
204 pub total_results: u64,
205 pub items_per_page: Option<NonZeroU64>,
206 pub start_index: Option<NonZeroU64>,
207 pub resources: Vec<ScimEntryMessage>,
208}
209
210#[serde_as]
211#[derive(Serialize, Deserialize, Debug, Clone)]
212#[serde(rename_all = "snake_case")]
213pub struct ScimEntrySchemaClass {
214 #[serde(flatten)]
215 pub header: ScimEntryHeader,
216
217 #[serde(flatten)]
221 pub attrs: BTreeMap<Attribute, JsonValue>,
222}
223
224#[serde_as]
225#[derive(Deserialize, Clone, Debug)]
226#[serde(rename_all = "camelCase")]
227pub struct ScimListSchemaClass {
228 pub schemas: Vec<String>,
229 pub total_results: u64,
230 pub items_per_page: Option<NonZeroU64>,
231 pub start_index: Option<NonZeroU64>,
232 pub resources: Vec<ScimEntrySchemaClass>,
233}
234
235#[serde_as]
236#[derive(Deserialize, Debug, Clone, Serialize)]
237#[serde(rename_all = "snake_case")]
238pub struct ScimEntrySchemaAttribute {
239 #[serde(flatten)]
240 pub header: ScimEntryHeader,
241
242 pub attributename: String,
243 pub description: String,
244 pub multivalue: bool,
246 pub unique: bool,
247 pub syntax: String,
248 #[serde(flatten)]
250 pub attrs: BTreeMap<Attribute, JsonValue>,
251}
252
253#[serde_as]
254#[derive(Deserialize, Clone, Debug)]
255#[serde(rename_all = "camelCase")]
256pub struct ScimListSchemaAttribute {
257 pub schemas: Vec<String>,
258 pub total_results: u64,
259 pub items_per_page: Option<NonZeroU64>,
260 pub start_index: Option<NonZeroU64>,
261 pub resources: Vec<ScimEntrySchemaAttribute>,
262}
263
264#[derive(Serialize, Debug, Clone)]
265pub struct ScimEntryPutKanidm {
266 pub id: Uuid,
267 #[serde(flatten)]
268 pub attrs: BTreeMap<Attribute, Option<super::server::ScimValueKanidm>>,
269}
270
271#[serde_as]
272#[derive(Deserialize, Serialize, Debug, Clone)]
273pub struct ScimStrings(#[serde_as(as = "OneOrMany<_, PreferMany>")] pub Vec<String>);
274
275#[serde_as]
276#[derive(Deserialize, Serialize, Debug, Clone)]
277pub struct ScimUrls(#[serde_as(as = "OneOrMany<_, PreferMany>")] pub Vec<Url>);
278
279#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
280pub struct ScimEntryPostGeneric {
281 #[serde(flatten)]
283 #[schema(value_type = Object, additional_properties = true)]
284 pub attrs: BTreeMap<Attribute, JsonValue>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
288#[serde(rename_all = "lowercase", tag = "state")]
289pub enum ScimEntryAssertion {
290 Present {
295 id: Uuid,
296 #[schema(value_type = BTreeMap<String, Value>)]
297 #[serde(flatten)]
298 attrs: BTreeMap<Attribute, Option<JsonValue>>,
299 },
300 Absent { id: Uuid },
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
307pub struct ScimAssertGeneric {
308 pub id: Uuid,
310
311 pub assertions: Vec<ScimEntryAssertion>,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
316pub struct ScimEntryPutGeneric {
317 pub id: Uuid,
319
320 #[serde(flatten)]
321 pub query: ScimEntryGetQuery,
325
326 #[schema(value_type = BTreeMap<String, Value>)]
332 #[serde(flatten)]
333 pub attrs: BTreeMap<Attribute, Option<JsonValue>>,
334}
335
336impl TryFrom<ScimEntryPutKanidm> for ScimEntryPutGeneric {
337 type Error = serde_json::Error;
338
339 fn try_from(value: ScimEntryPutKanidm) -> Result<Self, Self::Error> {
340 let ScimEntryPutKanidm { id, attrs } = value;
341
342 let attrs = attrs
343 .into_iter()
344 .map(|(attr, value)| {
345 if let Some(v) = value {
346 serde_json::to_value(v).map(|json_value| (attr, Some(json_value)))
347 } else {
348 Ok((attr, None))
349 }
350 })
351 .collect::<Result<_, _>>()?;
352
353 Ok(ScimEntryPutGeneric {
354 id,
355 attrs,
356 query: Default::default(),
357 })
358 }
359}