kanidm_proto/scim_v1/
client.rs

1//! These are types that a client will send to the server.
2use super::ScimEntryGetQuery;
3use super::ScimOauth2ClaimMapJoinChar;
4use crate::attribute::Attribute;
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, Debug, Clone)]
99#[serde(rename_all = "snake_case")]
100pub struct ScimEntryApplicationPost {
101    pub name: String,
102    pub displayname: String,
103    pub linked_group: ScimReference,
104}
105
106#[serde_as]
107#[derive(Deserialize, Debug, Clone)]
108#[serde(rename_all = "snake_case")]
109pub struct ScimEntryApplication {
110    #[serde(flatten)]
111    pub header: ScimEntryHeader,
112
113    pub name: String,
114    pub displayname: String,
115
116    pub linked_group: Vec<super::ScimReference>,
117
118    #[serde(flatten)]
119    pub attrs: BTreeMap<Attribute, JsonValue>,
120}
121
122#[serde_as]
123#[derive(Deserialize, Clone, Debug)]
124#[serde(rename_all = "camelCase")]
125pub struct ScimListApplication {
126    pub schemas: Vec<String>,
127    pub total_results: u64,
128    pub items_per_page: Option<NonZeroU64>,
129    pub start_index: Option<NonZeroU64>,
130    pub resources: Vec<ScimEntryApplication>,
131}
132
133#[serde_as]
134#[derive(Deserialize, Debug, Clone)]
135#[serde(rename_all = "snake_case")]
136pub struct ScimEntrySchemaClass {
137    #[serde(flatten)]
138    pub header: ScimEntryHeader,
139
140    // pub name: String,
141    // pub displayname: String,
142    // pub linked_group: Vec<super::ScimReference>,
143    #[serde(flatten)]
144    pub attrs: BTreeMap<Attribute, JsonValue>,
145}
146
147#[serde_as]
148#[derive(Deserialize, Clone, Debug)]
149#[serde(rename_all = "camelCase")]
150pub struct ScimListSchemaClass {
151    pub schemas: Vec<String>,
152    pub total_results: u64,
153    pub items_per_page: Option<NonZeroU64>,
154    pub start_index: Option<NonZeroU64>,
155    pub resources: Vec<ScimEntrySchemaClass>,
156}
157
158#[serde_as]
159#[derive(Deserialize, Debug, Clone)]
160#[serde(rename_all = "snake_case")]
161pub struct ScimEntrySchemaAttribute {
162    #[serde(flatten)]
163    pub header: ScimEntryHeader,
164
165    pub attributename: String,
166    pub description: String,
167    // TODO: To be removed
168    pub multivalue: bool,
169    pub unique: bool,
170    pub syntax: String,
171    // pub linked_group: Vec<super::ScimReference>,
172    #[serde(flatten)]
173    pub attrs: BTreeMap<Attribute, JsonValue>,
174}
175
176#[serde_as]
177#[derive(Deserialize, Clone, Debug)]
178#[serde(rename_all = "camelCase")]
179pub struct ScimListSchemaAttribute {
180    pub schemas: Vec<String>,
181    pub total_results: u64,
182    pub items_per_page: Option<NonZeroU64>,
183    pub start_index: Option<NonZeroU64>,
184    pub resources: Vec<ScimEntrySchemaAttribute>,
185}
186
187#[derive(Serialize, Debug, Clone)]
188pub struct ScimEntryPutKanidm {
189    pub id: Uuid,
190    #[serde(flatten)]
191    pub attrs: BTreeMap<Attribute, Option<super::server::ScimValueKanidm>>,
192}
193
194#[serde_as]
195#[derive(Deserialize, Serialize, Debug, Clone)]
196pub struct ScimStrings(#[serde_as(as = "OneOrMany<_, PreferMany>")] pub Vec<String>);
197
198#[derive(Debug, Clone, Deserialize, Default, ToSchema)]
199pub struct ScimEntryPostGeneric {
200    /// Create an attribute to contain the following value state.
201    #[serde(flatten)]
202    #[schema(value_type = Object, additional_properties = true)]
203    pub attrs: BTreeMap<Attribute, JsonValue>,
204}
205
206#[derive(Debug, Clone, Deserialize, Default)]
207pub struct ScimEntryPutGeneric {
208    // id is only used to target the entry in question
209    pub id: Uuid,
210
211    #[serde(flatten)]
212    /// Non-standard extension - allow query options to be set in a put request. This
213    /// is because a put request also returns the entry state post put, so we want
214    /// to allow putters to adjust and control what is returned here.
215    pub query: ScimEntryGetQuery,
216
217    // external_id can't be set by put
218    // meta is skipped on put
219    // Schemas are decoded as part of "attrs".
220    /// Update an attribute to contain the following value state.
221    /// If the attribute is None, it is removed.
222    #[serde(flatten)]
223    pub attrs: BTreeMap<Attribute, Option<JsonValue>>,
224}
225
226impl TryFrom<ScimEntryPutKanidm> for ScimEntryPutGeneric {
227    type Error = serde_json::Error;
228
229    fn try_from(value: ScimEntryPutKanidm) -> Result<Self, Self::Error> {
230        let ScimEntryPutKanidm { id, attrs } = value;
231
232        let attrs = attrs
233            .into_iter()
234            .map(|(attr, value)| {
235                if let Some(v) = value {
236                    serde_json::to_value(v).map(|json_value| (attr, Some(json_value)))
237                } else {
238                    Ok((attr, None))
239                }
240            })
241            .collect::<Result<_, _>>()?;
242
243        Ok(ScimEntryPutGeneric {
244            id,
245            attrs,
246            query: Default::default(),
247        })
248    }
249}