Skip to main content

kanidm_proto/scim_v1/
client.rs

1//! These are types that a client will send to the server.
2use 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    // pub name: String,
218    // pub displayname: String,
219    // pub linked_group: Vec<super::ScimReference>,
220    #[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    // TODO: To be removed
245    pub multivalue: bool,
246    pub unique: bool,
247    pub syntax: String,
248    // pub linked_group: Vec<super::ScimReference>,
249    #[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    /// Create an attribute to contain the following value state.
282    #[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    /// The entry should be present, with this id/UUID, and
291    /// the content of these attributes must be as shown. If an
292    /// attribute is not present in the assertion, it will not be
293    /// altered. To remove an attribute, set the attribute to "null".
294    Present {
295        id: Uuid,
296        #[schema(value_type = BTreeMap<String, Value>)]
297        #[serde(flatten)]
298        attrs: BTreeMap<Attribute, Option<JsonValue>>,
299    },
300    /// The entry should be absent (removed) from the database. Once
301    /// removed, the entry can not be re-asserted. You will need to create
302    /// a new entry with a unique ID.
303    Absent { id: Uuid },
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
307pub struct ScimAssertGeneric {
308    /// The ID of this assertion.
309    pub id: Uuid,
310
311    /// A set of assertions about expected entry state.
312    pub assertions: Vec<ScimEntryAssertion>,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
316pub struct ScimEntryPutGeneric {
317    // id is only used to target the entry in question
318    pub id: Uuid,
319
320    #[serde(flatten)]
321    /// Non-standard extension - allow query options to be set in a put request. This
322    /// is because a put request also returns the entry state post put, so we want
323    /// to allow putters to adjust and control what is returned here.
324    pub query: ScimEntryGetQuery,
325
326    // external_id can't be set by put
327    // meta is skipped on put
328    // Schemas are decoded as part of "attrs".
329    /// Update an attribute to contain the following value state.
330    /// If the attribute is None, it is removed.
331    #[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}