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 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#[serde_as]
29#[skip_serializing_none]
30#[derive(Deserialize, Serialize, Debug, Clone)]
31#[serde(deny_unknown_fields, rename_all = "camelCase")]
32pub struct ScimReference {
33    pub uuid: Option<Uuid>,
34    pub value: Option<String>,
35}
36
37impl<T> From<T> for ScimReference
38where
39    T: AsRef<str>,
40{
41    fn from(value: T) -> Self {
42        ScimReference {
43            uuid: None,
44            value: Some(value.as_ref().to_string()),
45        }
46    }
47}
48
49pub type ScimReferences = Vec<ScimReference>;
50
51#[serde_as]
52#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
53#[serde(transparent)]
54pub struct ScimDateTime {
55    #[serde_as(as = "Rfc3339")]
56    pub date_time: OffsetDateTime,
57}
58
59#[serde_as]
60#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
61#[serde(rename_all = "camelCase")]
62pub struct ScimCertificate {
63    #[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
64    pub der: Vec<u8>,
65}
66
67#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
68#[serde(rename_all = "camelCase")]
69pub struct ScimAddress {
70    pub street_address: String,
71    pub locality: String,
72    pub region: String,
73    pub postal_code: String,
74    pub country: String,
75}
76
77#[serde_as]
78#[derive(Deserialize, Serialize, Debug, Clone)]
79#[serde(rename_all = "camelCase")]
80pub struct ScimOAuth2ClaimMap {
81    pub group: Option<String>,
82    pub group_uuid: Option<Uuid>,
83    pub claim: String,
84    pub join_char: ScimOauth2ClaimMapJoinChar,
85    pub values: BTreeSet<String>,
86}
87
88#[serde_as]
89#[derive(Deserialize, Serialize, Debug, Clone)]
90#[serde(rename_all = "camelCase")]
91pub struct ScimOAuth2ScopeMap {
92    pub group: Option<String>,
93    pub group_uuid: Option<Uuid>,
94    pub scopes: BTreeSet<String>,
95}
96
97#[serde_as]
98#[derive(Serialize, Deserialize, Clone, Debug)]
99#[serde(rename_all = "camelCase")]
100pub struct ScimListEntry {
101    pub schemas: Vec<String>,
102    pub total_results: u64,
103    pub items_per_page: Option<NonZeroU64>,
104    pub start_index: Option<NonZeroU64>,
105    pub resources: Vec<ScimEntryGeneric>,
106}
107
108#[serde_as]
109#[derive(Serialize, Debug, Clone)]
110#[serde(rename_all = "snake_case")]
111pub struct ScimEntryApplicationPost {
112    pub name: String,
113    pub displayname: String,
114    pub linked_group: ScimReference,
115}
116
117#[serde_as]
118#[derive(Deserialize, Debug, Clone)]
119#[serde(rename_all = "snake_case")]
120pub struct ScimEntryApplication {
121    #[serde(flatten)]
122    pub header: ScimEntryHeader,
123
124    pub name: String,
125    pub displayname: String,
126
127    pub linked_group: Vec<super::ScimReference>,
128
129    #[serde(flatten)]
130    pub attrs: BTreeMap<Attribute, JsonValue>,
131}
132
133#[serde_as]
134#[derive(Deserialize, Clone, Debug)]
135#[serde(rename_all = "camelCase")]
136pub struct ScimListApplication {
137    pub schemas: Vec<String>,
138    pub total_results: u64,
139    pub items_per_page: Option<NonZeroU64>,
140    pub start_index: Option<NonZeroU64>,
141    pub resources: Vec<ScimEntryApplication>,
142}
143
144#[serde_as]
145#[derive(Serialize, Deserialize, Debug, Clone)]
146#[serde(rename_all = "snake_case")]
147pub struct ScimEntryMessage {
148    #[serde(flatten)]
149    pub header: ScimEntryHeader,
150
151    pub message_template: OutboundMessage,
152    pub send_after: ScimDateTime,
153    pub delete_after: ScimDateTime,
154    pub sent_at: Option<ScimDateTime>,
155    pub mail_destination: Vec<ScimMail>,
156
157    #[serde(flatten)]
158    pub attrs: BTreeMap<Attribute, JsonValue>,
159}
160
161#[serde_as]
162#[derive(Serialize, Deserialize, Clone, Debug)]
163#[serde(rename_all = "camelCase")]
164pub struct ScimListMessage {
165    pub schemas: Vec<String>,
166    pub total_results: u64,
167    pub items_per_page: Option<NonZeroU64>,
168    pub start_index: Option<NonZeroU64>,
169    pub resources: Vec<ScimEntryMessage>,
170}
171
172#[serde_as]
173#[derive(Serialize, Deserialize, Debug, Clone)]
174#[serde(rename_all = "snake_case")]
175pub struct ScimEntrySchemaClass {
176    #[serde(flatten)]
177    pub header: ScimEntryHeader,
178
179    // pub name: String,
180    // pub displayname: String,
181    // pub linked_group: Vec<super::ScimReference>,
182    #[serde(flatten)]
183    pub attrs: BTreeMap<Attribute, JsonValue>,
184}
185
186#[serde_as]
187#[derive(Deserialize, Clone, Debug)]
188#[serde(rename_all = "camelCase")]
189pub struct ScimListSchemaClass {
190    pub schemas: Vec<String>,
191    pub total_results: u64,
192    pub items_per_page: Option<NonZeroU64>,
193    pub start_index: Option<NonZeroU64>,
194    pub resources: Vec<ScimEntrySchemaClass>,
195}
196
197#[serde_as]
198#[derive(Deserialize, Debug, Clone, Serialize)]
199#[serde(rename_all = "snake_case")]
200pub struct ScimEntrySchemaAttribute {
201    #[serde(flatten)]
202    pub header: ScimEntryHeader,
203
204    pub attributename: String,
205    pub description: String,
206    // TODO: To be removed
207    pub multivalue: bool,
208    pub unique: bool,
209    pub syntax: String,
210    // pub linked_group: Vec<super::ScimReference>,
211    #[serde(flatten)]
212    pub attrs: BTreeMap<Attribute, JsonValue>,
213}
214
215#[serde_as]
216#[derive(Deserialize, Clone, Debug)]
217#[serde(rename_all = "camelCase")]
218pub struct ScimListSchemaAttribute {
219    pub schemas: Vec<String>,
220    pub total_results: u64,
221    pub items_per_page: Option<NonZeroU64>,
222    pub start_index: Option<NonZeroU64>,
223    pub resources: Vec<ScimEntrySchemaAttribute>,
224}
225
226#[derive(Serialize, Debug, Clone)]
227pub struct ScimEntryPutKanidm {
228    pub id: Uuid,
229    #[serde(flatten)]
230    pub attrs: BTreeMap<Attribute, Option<super::server::ScimValueKanidm>>,
231}
232
233#[serde_as]
234#[derive(Deserialize, Serialize, Debug, Clone)]
235pub struct ScimStrings(#[serde_as(as = "OneOrMany<_, PreferMany>")] pub Vec<String>);
236
237#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
238pub struct ScimEntryPostGeneric {
239    /// Create an attribute to contain the following value state.
240    #[serde(flatten)]
241    #[schema(value_type = Object, additional_properties = true)]
242    pub attrs: BTreeMap<Attribute, JsonValue>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
246pub struct ScimEntryPutGeneric {
247    // id is only used to target the entry in question
248    pub id: Uuid,
249
250    #[serde(flatten)]
251    /// Non-standard extension - allow query options to be set in a put request. This
252    /// is because a put request also returns the entry state post put, so we want
253    /// to allow putters to adjust and control what is returned here.
254    pub query: ScimEntryGetQuery,
255
256    // external_id can't be set by put
257    // meta is skipped on put
258    // Schemas are decoded as part of "attrs".
259    /// Update an attribute to contain the following value state.
260    /// If the attribute is None, it is removed.
261    #[schema(value_type = BTreeMap<String, Value>)]
262    #[serde(flatten)]
263    pub attrs: BTreeMap<Attribute, Option<JsonValue>>,
264}
265
266impl TryFrom<ScimEntryPutKanidm> for ScimEntryPutGeneric {
267    type Error = serde_json::Error;
268
269    fn try_from(value: ScimEntryPutKanidm) -> Result<Self, Self::Error> {
270        let ScimEntryPutKanidm { id, attrs } = value;
271
272        let attrs = attrs
273            .into_iter()
274            .map(|(attr, value)| {
275                if let Some(v) = value {
276                    serde_json::to_value(v).map(|json_value| (attr, Some(json_value)))
277                } else {
278                    Ok((attr, None))
279                }
280            })
281            .collect::<Result<_, _>>()?;
282
283        Ok(ScimEntryPutGeneric {
284            id,
285            attrs,
286            query: Default::default(),
287        })
288    }
289}