kanidmd_lib/valueset/
apppwd.rs

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    // The map key is application's UUID
13    // The value is a vector instead of BTreeSet to use
14    // PartialValue::Refer instead of having to implement
15    // PartialValue::ApplicationPassword. For example
16    // btreeset.remove takes a full ApplicationPassword
17    // struct.
18    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                    // Overwrite on duplicated labels for the same application.
85                    application_entry.password = ap.password;
86                } else {
87                    // Or just add it.
88                    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                // Deletes all passwords for the referred application
104                self.map.remove(u).is_some()
105            }
106            PartialValue::Uuid(u) => {
107                // Delete specific application password
108                // TODO Migrate to extract_if when available
109                let mut removed = false;
110                self.map.retain(|_, v| {
111                    let prev = v.len();
112                    // Check the innel vec of passwords related to this application.
113                    v.retain(|y| y.uuid != *u);
114                    let post = v.len();
115                    removed |= post < prev;
116                    // Is the apppwd set for this application id now empty?
117                    !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        // This is what ties us as a type that can be refint checked.
247        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 the remove operation, removing all application passwords for an
259    // application should also remove the KV pair.
260    #[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}