1use crate::be::dbvalue::{DbValueApplicationPassword, DbValueSetV2};
2use crate::credential::{apppwd::ApplicationPassword, Password};
3use crate::prelude::*;
4use crate::schema::SchemaAttribute;
5use crate::valueset::ScimResolveStatus;
6use std::collections::BTreeMap;
7
8use kanidm_proto::scim_v1::server::ScimApplicationPassword;
9
10#[derive(Debug, Clone)]
11pub struct ValueSetApplicationPassword {
12 map: BTreeMap<Uuid, Vec<ApplicationPassword>>,
19}
20
21impl ValueSetApplicationPassword {
22 pub fn new(ap: ApplicationPassword) -> Box<Self> {
23 let mut map: BTreeMap<Uuid, Vec<ApplicationPassword>> = BTreeMap::new();
24 map.entry(ap.application).or_default().push(ap);
25 Box::new(ValueSetApplicationPassword { map })
26 }
27
28 fn from_dbv_iter(
29 data: impl Iterator<Item = DbValueApplicationPassword>,
30 ) -> Result<ValueSet, OperationError> {
31 let mut map: BTreeMap<Uuid, Vec<ApplicationPassword>> = BTreeMap::new();
32 for ap in data {
33 let ap = match ap {
34 DbValueApplicationPassword::V1 {
35 refer,
36 application_refer,
37 label,
38 password,
39 } => {
40 let password = Password::try_from(password)
41 .map_err(|()| OperationError::InvalidValueState)?;
42 ApplicationPassword {
43 uuid: refer,
44 application: application_refer,
45 label,
46 password,
47 }
48 }
49 };
50 map.entry(ap.application).or_default().push(ap);
51 }
52 Ok(Box::new(ValueSetApplicationPassword { map }))
53 }
54
55 pub fn from_dbvs2(data: Vec<DbValueApplicationPassword>) -> Result<ValueSet, OperationError> {
56 Self::from_dbv_iter(data.into_iter())
57 }
58
59 fn to_vec_dbvs(&self) -> Vec<DbValueApplicationPassword> {
60 self.map
61 .iter()
62 .flat_map(|(_, v)| {
63 v.iter().map(|ap| DbValueApplicationPassword::V1 {
64 refer: ap.uuid,
65 application_refer: ap.application,
66 label: ap.label.clone(),
67 password: ap.password.to_dbpasswordv1(),
68 })
69 })
70 .collect()
71 }
72}
73
74impl ValueSetT for ValueSetApplicationPassword {
75 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
76 match value {
77 Value::ApplicationPassword(ap) => {
78 let application_entries = self.map.entry(ap.application).or_default();
79
80 if let Some(application_entry) = application_entries
81 .iter_mut()
82 .find(|entry_app_password| *entry_app_password == &ap)
83 {
84 application_entry.password = ap.password;
86 } else {
87 application_entries.push(ap);
89 }
90 Ok(true)
91 }
92 _ => Err(OperationError::InvalidValueState),
93 }
94 }
95
96 fn clear(&mut self) {
97 self.map.clear();
98 }
99
100 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
101 match pv {
102 PartialValue::Refer(u) => {
103 self.map.remove(u).is_some()
105 }
106 PartialValue::Uuid(u) => {
107 let mut removed = false;
110 self.map.retain(|_, v| {
111 let prev = v.len();
112 v.retain(|y| y.uuid != *u);
114 let post = v.len();
115 removed |= post < prev;
116 !v.is_empty()
118 });
119 removed
120 }
121 _ => false,
122 }
123 }
124
125 fn contains(&self, pv: &PartialValue) -> bool {
126 match pv {
127 PartialValue::Uuid(u) => self.map.values().any(|v| v.iter().any(|ap| ap.uuid == *u)),
128 PartialValue::Refer(u) => self
129 .map
130 .values()
131 .any(|v| v.iter().any(|ap| ap.application == *u)),
132 _ => false,
133 }
134 }
135
136 fn substring(&self, _pv: &PartialValue) -> bool {
137 false
138 }
139
140 fn startswith(&self, _pv: &PartialValue) -> bool {
141 false
142 }
143
144 fn endswith(&self, _pv: &PartialValue) -> bool {
145 false
146 }
147
148 fn lessthan(&self, _pv: &PartialValue) -> bool {
149 false
150 }
151
152 fn len(&self) -> usize {
153 let mut count = 0;
154 for v in self.map.values() {
155 count += v.len();
156 }
157 count
158 }
159
160 fn generate_idx_eq_keys(&self) -> Vec<String> {
161 self.map
162 .keys()
163 .map(|u| u.as_hyphenated().to_string())
164 .collect()
165 }
166
167 fn syntax(&self) -> SyntaxType {
168 SyntaxType::ApplicationPassword
169 }
170
171 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
172 self.map.iter().all(|(_, v)| {
173 v.iter().all(|ap| {
174 Value::validate_str_escapes(ap.label.as_str())
175 && Value::validate_singleline(ap.label.as_str())
176 })
177 })
178 }
179
180 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
181 Box::new(self.map.iter().flat_map(|(_, v)| {
182 v.iter()
183 .map(|ap| format!("App: {} Label: {}", ap.application, ap.label))
184 }))
185 }
186
187 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
188 Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
189 self.map
190 .values()
191 .flatten()
192 .map(|app_pwd| ScimApplicationPassword {
193 uuid: app_pwd.uuid,
194 application_uuid: app_pwd.application,
195 label: app_pwd.label.clone(),
196 })
197 .collect::<Vec<_>>(),
198 )))
199 }
200
201 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
202 let data = self.to_vec_dbvs();
203 DbValueSetV2::ApplicationPassword(data)
204 }
205
206 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
207 Box::new(
208 self.map
209 .iter()
210 .flat_map(|(_, v)| v.iter().map(|ap| ap.uuid))
211 .map(PartialValue::Refer),
212 )
213 }
214
215 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
216 Box::new(
217 self.map
218 .iter()
219 .flat_map(|(_, v)| v.iter().map(|ap| Value::ApplicationPassword(ap.clone()))),
220 )
221 }
222
223 fn equal(&self, other: &ValueSet) -> bool {
224 if let Some(other) = other.as_application_password_map() {
225 &self.map == other
226 } else {
227 debug_assert!(false);
228 false
229 }
230 }
231
232 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
233 if let Some(b) = other.as_application_password_map() {
234 mergemaps!(self.map, b)
235 } else {
236 debug_assert!(false);
237 Err(OperationError::InvalidValueState)
238 }
239 }
240
241 fn as_application_password_map(&self) -> Option<&BTreeMap<Uuid, Vec<ApplicationPassword>>> {
242 Some(&self.map)
243 }
244
245 fn as_ref_uuid_iter(&self) -> Option<Box<dyn Iterator<Item = Uuid> + '_>> {
246 Some(Box::new(self.map.keys().copied()))
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use crate::credential::{apppwd::ApplicationPassword, Password};
254 use crate::prelude::*;
255 use crate::valueset::ValueSetApplicationPassword;
256 use kanidm_lib_crypto::CryptoPolicy;
257
258 #[test]
261 fn test_valueset_application_password_remove() {
262 let app1_uuid = Uuid::new_v4();
263 let app2_uuid = Uuid::new_v4();
264 let ap1_uuid = Uuid::new_v4();
265 let ap2_uuid = Uuid::new_v4();
266 let ap3_uuid = Uuid::new_v4();
267
268 let ap1: ApplicationPassword = ApplicationPassword {
269 uuid: ap1_uuid,
270 application: app1_uuid,
271 label: "apppwd1".to_string(),
272 password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd1")
273 .expect("Failed to create password"),
274 };
275
276 let ap2: ApplicationPassword = ApplicationPassword {
277 uuid: ap2_uuid,
278 application: app1_uuid,
279 label: "apppwd2".to_string(),
280 password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd2")
281 .expect("Failed to create password"),
282 };
283
284 let ap3: ApplicationPassword = ApplicationPassword {
285 uuid: ap3_uuid,
286 application: app2_uuid,
287 label: "apppwd3".to_string(),
288 password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd3")
289 .expect("Failed to create password"),
290 };
291
292 let mut vs: ValueSet = ValueSetApplicationPassword::new(ap1);
293 assert_eq!(vs.len(), 1);
294
295 let res = vs
296 .insert_checked(Value::ApplicationPassword(ap2))
297 .expect("Failed to insert");
298 assert!(res);
299 assert_eq!(vs.len(), 2);
300
301 let res = vs
302 .insert_checked(Value::ApplicationPassword(ap3))
303 .expect("Failed to insert");
304 assert!(res);
305 assert_eq!(vs.len(), 3);
306
307 let res = vs.remove(&PartialValue::Uuid(Uuid::new_v4()), &Cid::new_zero());
308 assert!(!res);
309 assert_eq!(vs.len(), 3);
310
311 let res = vs.remove(&PartialValue::Uuid(ap1_uuid), &Cid::new_zero());
312 assert!(res);
313 assert_eq!(vs.len(), 2);
314
315 let res = vs.remove(&PartialValue::Uuid(ap3_uuid), &Cid::new_zero());
316 assert!(res);
317 assert_eq!(vs.len(), 1);
318
319 let res = vs.remove(&PartialValue::Uuid(ap2_uuid), &Cid::new_zero());
320 assert!(res);
321 assert_eq!(vs.len(), 0);
322
323 let res = vs.as_application_password_map().unwrap();
324 assert_eq!(res.keys().len(), 0);
325 }
326
327 #[test]
328 fn test_scim_application_password() {
329 let app1_uuid = uuid::uuid!("7c3cd2b4-dc0d-43f5-999c-4912c2412405");
330 let app2_uuid = uuid::uuid!("82eaeca8-4250-4b63-a94b-75a3764a9327");
331 let ap1_uuid = uuid::uuid!("f36434ba-087a-4774-90ea-ebcda7f8c549");
332 let ap2_uuid = uuid::uuid!("b78506c7-eb7a-45d8-a994-34e868ee1a9e");
333 let ap3_uuid = uuid::uuid!("740a9d06-1188-4c48-9c5c-dbf863712c66");
334
335 let ap1: ApplicationPassword = ApplicationPassword {
336 uuid: ap1_uuid,
337 application: app1_uuid,
338 label: "apppwd1".to_string(),
339 password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd1")
340 .expect("Failed to create password"),
341 };
342
343 let ap2: ApplicationPassword = ApplicationPassword {
344 uuid: ap2_uuid,
345 application: app1_uuid,
346 label: "apppwd2".to_string(),
347 password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd2")
348 .expect("Failed to create password"),
349 };
350
351 let ap3: ApplicationPassword = ApplicationPassword {
352 uuid: ap3_uuid,
353 application: app2_uuid,
354 label: "apppwd3".to_string(),
355 password: Password::new_pbkdf2(&CryptoPolicy::minimum(), "apppwd3")
356 .expect("Failed to create password"),
357 };
358
359 let mut vs: ValueSet = ValueSetApplicationPassword::new(ap1);
360 vs.insert_checked(Value::ApplicationPassword(ap2))
361 .expect("Failed to insert");
362 vs.insert_checked(Value::ApplicationPassword(ap3))
363 .expect("Failed to insert");
364
365 let data = r#"
366[
367 {
368 "applicationUuid": "7c3cd2b4-dc0d-43f5-999c-4912c2412405",
369 "label": "apppwd1",
370 "uuid": "f36434ba-087a-4774-90ea-ebcda7f8c549"
371 },
372 {
373 "applicationUuid": "7c3cd2b4-dc0d-43f5-999c-4912c2412405",
374 "label": "apppwd2",
375 "uuid": "b78506c7-eb7a-45d8-a994-34e868ee1a9e"
376 },
377 {
378 "applicationUuid": "82eaeca8-4250-4b63-a94b-75a3764a9327",
379 "label": "apppwd3",
380 "uuid": "740a9d06-1188-4c48-9c5c-dbf863712c66"
381 }
382]
383"#;
384 crate::valueset::scim_json_reflexive(&vs, data);
385 }
386}