kanidmd_lib/valueset/
ssh.rs
1use crate::be::dbvalue::DbValueTaggedStringV1;
2use crate::prelude::*;
3use crate::schema::SchemaAttribute;
4use crate::utils::trigraph_iter;
5use crate::valueset::{
6 DbValueSetV2, ScimResolveStatus, ValueSet, ValueSetResolveStatus, ValueSetScimPut,
7};
8use kanidm_proto::scim_v1::JsonValue;
9use kanidm_proto::scim_v1::ScimSshPublicKey;
10use sshkey_attest::proto::PublicKey as SshPublicKey;
11use std::collections::btree_map::Entry as BTreeEntry;
12use std::collections::BTreeMap;
13
14#[derive(Debug, Clone)]
15pub struct ValueSetSshKey {
16 map: BTreeMap<String, SshPublicKey>,
17}
18
19impl ValueSetSshKey {
20 pub fn new(t: String, k: SshPublicKey) -> Box<Self> {
21 let mut map = BTreeMap::new();
22 map.insert(t, k);
23 Box::new(ValueSetSshKey { map })
24 }
25
26 pub fn push(&mut self, t: String, k: SshPublicKey) -> bool {
27 self.map.insert(t, k).is_none()
28 }
29
30 pub fn from_dbvs2(data: Vec<DbValueTaggedStringV1>) -> Result<ValueSet, OperationError> {
31 let map = data
32 .into_iter()
33 .filter_map(|DbValueTaggedStringV1 { tag, data }| {
34 SshPublicKey::from_string(&data)
35 .map_err(|err| {
36 warn!(%tag, ?err, "discarding corrupted ssh public key");
37 })
38 .map(|pk| (tag, pk))
39 .ok()
40 })
41 .collect();
42 Ok(Box::new(ValueSetSshKey { map }))
43 }
44
45 #[allow(clippy::should_implement_trait)]
48 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
49 where
50 T: IntoIterator<Item = (String, SshPublicKey)>,
51 {
52 let map = iter.into_iter().collect();
53 Some(Box::new(ValueSetSshKey { map }))
54 }
55}
56
57impl ValueSetScimPut for ValueSetSshKey {
58 fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
59 let value: Vec<ScimSshPublicKey> = serde_json::from_value(value).map_err(|err| {
60 error!(?err, "SCIM Ssh Public Key syntax invalid");
61 OperationError::SC0024SshPublicKeySyntaxInvalid
62 })?;
63
64 let map = value
65 .into_iter()
66 .map(|ScimSshPublicKey { label, value }| (label, value))
67 .collect();
68
69 Ok(ValueSetResolveStatus::Resolved(Box::new(ValueSetSshKey {
70 map,
71 })))
72 }
73}
74
75impl ValueSetT for ValueSetSshKey {
76 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
77 match value {
78 Value::SshKey(t, k) => {
79 if let BTreeEntry::Vacant(e) = self.map.entry(t) {
80 e.insert(k);
81 Ok(true)
82 } else {
83 Ok(false)
84 }
85 }
86 _ => Err(OperationError::InvalidValueState),
87 }
88 }
89
90 fn clear(&mut self) {
91 self.map.clear();
92 }
93
94 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
95 match pv {
96 PartialValue::SshKey(t) => self.map.remove(t.as_str()).is_some(),
97 _ => false,
98 }
99 }
100
101 fn contains(&self, pv: &PartialValue) -> bool {
102 match pv {
103 PartialValue::SshKey(t) => self.map.contains_key(t.as_str()),
104 _ => false,
105 }
106 }
107
108 fn substring(&self, _pv: &PartialValue) -> bool {
109 false
110 }
111
112 fn startswith(&self, _pv: &PartialValue) -> bool {
113 false
114 }
115
116 fn endswith(&self, _pv: &PartialValue) -> bool {
117 false
118 }
119
120 fn lessthan(&self, _pv: &PartialValue) -> bool {
121 false
122 }
123
124 fn len(&self) -> usize {
125 self.map.len()
126 }
127
128 fn generate_idx_eq_keys(&self) -> Vec<String> {
129 self.map.keys().cloned().collect()
130 }
131
132 fn generate_idx_sub_keys(&self) -> Vec<String> {
133 let lower: Vec<_> = self.map.keys().map(|s| s.to_lowercase()).collect();
134 let mut trigraphs: Vec<_> = lower.iter().flat_map(|v| trigraph_iter(v)).collect();
135
136 trigraphs.sort_unstable();
137 trigraphs.dedup();
138
139 trigraphs.into_iter().map(String::from).collect()
140 }
141
142 fn syntax(&self) -> SyntaxType {
143 SyntaxType::SshKey
144 }
145
146 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
147 self.map.iter().all(|(s, _key)| {
148 Value::validate_str_escapes(s)
149 && Value::validate_singleline(s)
151 })
152 }
153
154 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
155 Box::new(self.map.iter().map(|(tag, pk)| format!("{}: {}", tag, pk)))
156 }
157
158 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
159 Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
160 self.map
161 .iter()
162 .map(|(label, value)| ScimSshPublicKey {
163 label: label.clone(),
164 value: value.clone(),
165 })
166 .collect::<Vec<_>>(),
167 )))
168 }
169
170 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
171 DbValueSetV2::SshKey(
172 self.map
173 .iter()
174 .map(|(tag, key)| DbValueTaggedStringV1 {
175 tag: tag.clone(),
176 data: key.to_string(),
177 })
178 .collect(),
179 )
180 }
181
182 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
183 Box::new(self.map.keys().cloned().map(PartialValue::SshKey))
184 }
185
186 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
187 Box::new(
188 self.map
189 .iter()
190 .map(|(t, k)| Value::SshKey(t.clone(), k.clone())),
191 )
192 }
193
194 fn equal(&self, other: &ValueSet) -> bool {
195 if let Some(other) = other.as_sshkey_map() {
196 &self.map == other
197 } else {
198 debug_assert!(false);
199 false
200 }
201 }
202
203 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
204 if let Some(b) = other.as_sshkey_map() {
205 mergemaps!(self.map, b)
206 } else {
207 debug_assert!(false);
208 Err(OperationError::InvalidValueState)
209 }
210 }
211
212 fn as_sshkey_map(&self) -> Option<&BTreeMap<String, SshPublicKey>> {
213 Some(&self.map)
214 }
215
216 fn get_ssh_tag(&self, tag: &str) -> Option<&SshPublicKey> {
217 self.map.get(tag)
218 }
219
220 fn as_sshpubkey_string_iter(&self) -> Option<Box<dyn Iterator<Item = String> + '_>> {
221 Some(Box::new(self.map.values().map(|pk| pk.to_string())))
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::{SshPublicKey, ValueSetSshKey};
228 use crate::prelude::ValueSet;
229
230 #[test]
231 fn test_scim_ssh_public_key() {
232 let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
233 "tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
234 "zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@a",
235 "methyst");
236
237 let vs: ValueSet = ValueSetSshKey::new(
238 "label".to_string(),
239 SshPublicKey::from_string(ecdsa).unwrap(),
240 );
241
242 let data = r#"
243[
244 {
245 "label": "label",
246 "value": "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3BtOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81lzPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@amethyst"
247 }
248]
249 "#;
250 crate::valueset::scim_json_reflexive(&vs, data);
251
252 crate::valueset::scim_json_put_reflexive::<ValueSetSshKey>(&vs, &[])
254 }
255}