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 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#[derive(Serialize, Debug, Clone)]
134pub struct ScimEntryPutKanidm {
135    pub id: Uuid,
136    #[serde(flatten)]
137    pub attrs: BTreeMap<Attribute, Option<super::server::ScimValueKanidm>>,
138}
139
140#[serde_as]
141#[derive(Deserialize, Serialize, Debug, Clone)]
142pub struct ScimStrings(#[serde_as(as = "OneOrMany<_, PreferMany>")] pub Vec<String>);
143
144#[derive(Debug, Clone, Deserialize, Default, ToSchema)]
145pub struct ScimEntryPostGeneric {
146    /// Create an attribute to contain the following value state.
147    #[serde(flatten)]
148    #[schema(value_type = Object, additional_properties = true)]
149    pub attrs: BTreeMap<Attribute, JsonValue>,
150}
151
152#[derive(Debug, Clone, Deserialize, Default)]
153pub struct ScimEntryPutGeneric {
154    // id is only used to target the entry in question
155    pub id: Uuid,
156
157    #[serde(flatten)]
158    /// Non-standard extension - allow query options to be set in a put request. This
159    /// is because a put request also returns the entry state post put, so we want
160    /// to allow putters to adjust and control what is returned here.
161    pub query: ScimEntryGetQuery,
162
163    // external_id can't be set by put
164    // meta is skipped on put
165    // Schemas are decoded as part of "attrs".
166    /// Update an attribute to contain the following value state.
167    /// If the attribute is None, it is removed.
168    #[serde(flatten)]
169    pub attrs: BTreeMap<Attribute, Option<JsonValue>>,
170}
171
172impl TryFrom<ScimEntryPutKanidm> for ScimEntryPutGeneric {
173    type Error = serde_json::Error;
174
175    fn try_from(value: ScimEntryPutKanidm) -> Result<Self, Self::Error> {
176        let ScimEntryPutKanidm { id, attrs } = value;
177
178        let attrs = attrs
179            .into_iter()
180            .map(|(attr, value)| {
181                if let Some(v) = value {
182                    serde_json::to_value(v).map(|json_value| (attr, Some(json_value)))
183                } else {
184                    Ok((attr, None))
185                }
186            })
187            .collect::<Result<_, _>>()?;
188
189        Ok(ScimEntryPutGeneric {
190            id,
191            attrs,
192            query: Default::default(),
193        })
194    }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
198pub struct AttrPath {
199    pub a: Attribute,
200    pub s: Option<SubAttribute>,
201}
202
203impl From<Attribute> for AttrPath {
204    fn from(a: Attribute) -> Self {
205        Self { a, s: None }
206    }
207}
208
209impl From<(Attribute, SubAttribute)> for AttrPath {
210    fn from((a, s): (Attribute, SubAttribute)) -> Self {
211        Self { a, s: Some(s) }
212    }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
216pub enum ScimFilter {
217    Or(Box<ScimFilter>, Box<ScimFilter>),
218    And(Box<ScimFilter>, Box<ScimFilter>),
219    Not(Box<ScimFilter>),
220
221    Present(AttrPath),
222    Equal(AttrPath, JsonValue),
223    NotEqual(AttrPath, JsonValue),
224    Contains(AttrPath, JsonValue),
225    StartsWith(AttrPath, JsonValue),
226    EndsWith(AttrPath, JsonValue),
227    Greater(AttrPath, JsonValue),
228    Less(AttrPath, JsonValue),
229    GreaterOrEqual(AttrPath, JsonValue),
230    LessOrEqual(AttrPath, JsonValue),
231
232    Complex(Attribute, Box<ScimComplexFilter>),
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
236pub enum ScimComplexFilter {
237    Or(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
238    And(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
239    Not(Box<ScimComplexFilter>),
240
241    Present(SubAttribute),
242    Equal(SubAttribute, JsonValue),
243    NotEqual(SubAttribute, JsonValue),
244    Contains(SubAttribute, JsonValue),
245    StartsWith(SubAttribute, JsonValue),
246    EndsWith(SubAttribute, JsonValue),
247    Greater(SubAttribute, JsonValue),
248    Less(SubAttribute, JsonValue),
249    GreaterOrEqual(SubAttribute, JsonValue),
250    LessOrEqual(SubAttribute, JsonValue),
251}