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