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, SubAttribute};
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 time::format_description::well_known::Rfc3339;
14use time::OffsetDateTime;
15use uuid::Uuid;
16
17pub type ScimSshPublicKeys = Vec<ScimSshPublicKey>;
18
19#[derive(Deserialize, Serialize, Debug, Clone)]
20#[serde(deny_unknown_fields, rename_all = "camelCase")]
21pub struct ScimSshPublicKey {
22    pub label: String,
23    pub value: SshPublicKey,
24}
25
26#[serde_as]
27#[skip_serializing_none]
28#[derive(Deserialize, Serialize, Debug, Clone)]
29#[serde(deny_unknown_fields, rename_all = "camelCase")]
30pub struct ScimReference {
31    pub uuid: Option<Uuid>,
32    pub value: Option<String>,
33}
34
35impl<T> From<T> for ScimReference
36where
37    T: AsRef<str>,
38{
39    fn from(value: T) -> Self {
40        ScimReference {
41            uuid: None,
42            value: Some(value.as_ref().to_string()),
43        }
44    }
45}
46
47pub type ScimReferences = Vec<ScimReference>;
48
49#[serde_as]
50#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
51#[serde(transparent)]
52pub struct ScimDateTime {
53    #[serde_as(as = "Rfc3339")]
54    pub date_time: OffsetDateTime,
55}
56
57#[serde_as]
58#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
59#[serde(rename_all = "camelCase")]
60pub struct ScimCertificate {
61    #[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
62    pub der: Vec<u8>,
63}
64
65#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
66#[serde(rename_all = "camelCase")]
67pub struct ScimAddress {
68    pub street_address: String,
69    pub locality: String,
70    pub region: String,
71    pub postal_code: String,
72    pub country: String,
73}
74
75#[serde_as]
76#[derive(Deserialize, Serialize, Debug, Clone)]
77#[serde(rename_all = "camelCase")]
78pub struct ScimOAuth2ClaimMap {
79    pub group: Option<String>,
80    pub group_uuid: Option<Uuid>,
81    pub claim: String,
82    pub join_char: ScimOauth2ClaimMapJoinChar,
83    pub values: BTreeSet<String>,
84}
85
86#[serde_as]
87#[derive(Deserialize, Serialize, Debug, Clone)]
88#[serde(rename_all = "camelCase")]
89pub struct ScimOAuth2ScopeMap {
90    pub group: Option<String>,
91    pub group_uuid: Option<Uuid>,
92    pub scopes: BTreeSet<String>,
93}
94
95#[serde_as]
96#[derive(Serialize, Debug, Clone)]
97#[serde(rename_all = "snake_case")]
98pub struct ScimEntryApplicationPost {
99    pub name: String,
100    pub displayname: String,
101    pub linked_group: ScimReference,
102}
103
104#[serde_as]
105#[derive(Deserialize, Debug, Clone)]
106#[serde(rename_all = "snake_case")]
107pub struct ScimEntryApplication {
108    #[serde(flatten)]
109    pub header: ScimEntryHeader,
110
111    pub name: String,
112    pub displayname: String,
113
114    pub linked_group: Vec<super::ScimReference>,
115
116    #[serde(flatten)]
117    pub attrs: BTreeMap<Attribute, JsonValue>,
118}
119
120#[derive(Serialize, Debug, Clone)]
121pub struct ScimEntryPutKanidm {
122    pub id: Uuid,
123    #[serde(flatten)]
124    pub attrs: BTreeMap<Attribute, Option<super::server::ScimValueKanidm>>,
125}
126
127#[serde_as]
128#[derive(Deserialize, Serialize, Debug, Clone)]
129pub struct ScimStrings(#[serde_as(as = "OneOrMany<_, PreferMany>")] pub Vec<String>);
130
131#[derive(Debug, Clone, Deserialize, Default)]
132pub struct ScimEntryPostGeneric {
133    /// Create an attribute to contain the following value state.
134    #[serde(flatten)]
135    pub attrs: BTreeMap<Attribute, JsonValue>,
136}
137
138#[derive(Debug, Clone, Deserialize, Default)]
139pub struct ScimEntryPutGeneric {
140    // id is only used to target the entry in question
141    pub id: Uuid,
142
143    #[serde(flatten)]
144    /// Non-standard extension - allow query options to be set in a put request. This
145    /// is because a put request also returns the entry state post put, so we want
146    /// to allow putters to adjust and control what is returned here.
147    pub query: ScimEntryGetQuery,
148
149    // external_id can't be set by put
150    // meta is skipped on put
151    // Schemas are decoded as part of "attrs".
152    /// Update an attribute to contain the following value state.
153    /// If the attribute is None, it is removed.
154    #[serde(flatten)]
155    pub attrs: BTreeMap<Attribute, Option<JsonValue>>,
156}
157
158impl TryFrom<ScimEntryPutKanidm> for ScimEntryPutGeneric {
159    type Error = serde_json::Error;
160
161    fn try_from(value: ScimEntryPutKanidm) -> Result<Self, Self::Error> {
162        let ScimEntryPutKanidm { id, attrs } = value;
163
164        let attrs = attrs
165            .into_iter()
166            .map(|(attr, value)| {
167                if let Some(v) = value {
168                    serde_json::to_value(v).map(|json_value| (attr, Some(json_value)))
169                } else {
170                    Ok((attr, None))
171                }
172            })
173            .collect::<Result<_, _>>()?;
174
175        Ok(ScimEntryPutGeneric {
176            id,
177            attrs,
178            query: Default::default(),
179        })
180    }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
184pub struct AttrPath {
185    pub a: Attribute,
186    pub s: Option<SubAttribute>,
187}
188
189impl From<Attribute> for AttrPath {
190    fn from(a: Attribute) -> Self {
191        Self { a, s: None }
192    }
193}
194
195impl From<(Attribute, SubAttribute)> for AttrPath {
196    fn from((a, s): (Attribute, SubAttribute)) -> Self {
197        Self { a, s: Some(s) }
198    }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
202pub enum ScimFilter {
203    Or(Box<ScimFilter>, Box<ScimFilter>),
204    And(Box<ScimFilter>, Box<ScimFilter>),
205    Not(Box<ScimFilter>),
206
207    Present(AttrPath),
208    Equal(AttrPath, JsonValue),
209    NotEqual(AttrPath, JsonValue),
210    Contains(AttrPath, JsonValue),
211    StartsWith(AttrPath, JsonValue),
212    EndsWith(AttrPath, JsonValue),
213    Greater(AttrPath, JsonValue),
214    Less(AttrPath, JsonValue),
215    GreaterOrEqual(AttrPath, JsonValue),
216    LessOrEqual(AttrPath, JsonValue),
217
218    Complex(Attribute, Box<ScimComplexFilter>),
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
222pub enum ScimComplexFilter {
223    Or(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
224    And(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
225    Not(Box<ScimComplexFilter>),
226
227    Present(SubAttribute),
228    Equal(SubAttribute, JsonValue),
229    NotEqual(SubAttribute, JsonValue),
230    Contains(SubAttribute, JsonValue),
231    StartsWith(SubAttribute, JsonValue),
232    EndsWith(SubAttribute, JsonValue),
233    Greater(SubAttribute, JsonValue),
234    Less(SubAttribute, JsonValue),
235    GreaterOrEqual(SubAttribute, JsonValue),
236    LessOrEqual(SubAttribute, JsonValue),
237}