1use core::fmt;
17use std::collections::{HashMap, HashSet};
18
19use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
20use bherror::{
21 traits::{ErrorContext, ForeignError},
22 Error,
23};
24
25use super::{error::DecodingResult, path_map::PathMapObject, JsonNodePath, Value};
26use crate::{
27 error::FormatError,
28 utils::{self},
29 DecodingError,
30};
31
32#[derive(Debug, PartialEq, Eq, Hash, Clone)]
35pub struct Disclosure {
36 pub(crate) data: DisclosureData,
37 serialized: String,
39}
40
41impl TryFrom<String> for Disclosure {
42 type Error = Error<FormatError>;
43
44 fn try_from(serialized: String) -> Result<Self, Self::Error> {
45 let decoded = URL_SAFE_NO_PAD
46 .decode(&serialized)
47 .foreign_err(|| {
48 FormatError::InvalidDisclosure("provided string is not base64 ".to_string())
49 })
50 .ctx(|| serialized.clone())?;
51
52 let array: Vec<Value> = serde_json::from_slice(&decoded)
53 .foreign_err(|| {
54 FormatError::InvalidDisclosure(
55 "serde json could not parse decoded base64 string ".to_string(),
56 )
57 })
58 .ctx(|| serialized.clone())?;
59
60 let data = match array.len() {
61 3 => {
62 let [salt, key, value] = array.try_into().unwrap();
63 create_disclosure_data_key_value(salt, key, value)
64 }
65 2 => {
66 let [salt, value] = array.try_into().unwrap();
67 create_disclosure_data_array_element(salt, value)
68 }
69 _ => Err(Error::root(FormatError::InvalidDisclosure(format!(
70 "deserialized disclosure array has invalid length {}",
71 array.len(),
72 )))),
73 }
74 .ctx(|| "error while creating a disclosure from base64 serialized string ".to_string())
75 .ctx(|| serialized.clone())?;
76
77 Ok(Self { data, serialized })
78 }
79}
80
81fn create_disclosure_data_key_value(
82 salt: Value,
83 key: Value,
84 value: Value,
85) -> crate::Result<DisclosureData, FormatError> {
86 let Value::String(salt) = salt else {
87 return Err(Error::root(FormatError::InvalidDisclosure(
88 "salt value is not a string".to_string(),
89 )));
90 };
91 let Value::String(key) = key else {
92 return Err(Error::root(FormatError::InvalidDisclosure(
93 "key value is not a string".to_string(),
94 )));
95 };
96
97 Ok(DisclosureData::KeyValue { salt, key, value })
98}
99
100fn create_disclosure_data_array_element(
101 salt: Value,
102 value: Value,
103) -> crate::Result<DisclosureData, FormatError> {
104 let Value::String(salt) = salt else {
105 return Err(Error::root(FormatError::InvalidDisclosure(
106 "salt value is not a string".to_string(),
107 )));
108 };
109
110 Ok(DisclosureData::ArrayElement { salt, value })
111}
112
113impl fmt::Display for Disclosure {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match &self.data {
116 DisclosureData::KeyValue { salt, key, value } => {
117 write!(f, "[{}, {}, {}]", salt, key, value)
118 }
119 DisclosureData::ArrayElement { salt, value } => write!(f, "[{}, {}]", salt, value),
120 }
121 }
122}
123
124impl Disclosure {
125 pub fn new(salt: String, claim_name: Option<String>, claim_value: Value) -> Self {
127 let input = if let Some(name) = &claim_name {
128 format!("[\"{}\", \"{}\", {}]", &salt, &name, &claim_value)
129 } else {
130 format!("[\"{}\", {}]", &salt, &claim_value)
131 };
132
133 let encoded = bh_jws_utils::base64_url_encode(input);
134
135 let data = if let Some(name) = claim_name {
136 DisclosureData::KeyValue {
137 salt,
138 key: name,
139 value: claim_value,
140 }
141 } else {
142 DisclosureData::ArrayElement {
143 salt,
144 value: claim_value,
145 }
146 };
147
148 Self {
149 data,
150 serialized: encoded,
151 }
152 }
153
154 pub fn value(&self) -> &Value {
156 match &self.data {
157 DisclosureData::KeyValue { value, .. } => value,
158 DisclosureData::ArrayElement { value, .. } => value,
159 }
160 }
161
162 pub fn claim_name(&self) -> Option<&str> {
164 match &self.data {
165 DisclosureData::KeyValue { key, .. } => Some(key),
166 _ => None,
167 }
168 }
169
170 pub fn as_str(&self) -> &str {
172 &self.serialized
173 }
174
175 pub fn into_string(self) -> String {
177 self.serialized
178 }
179}
180
181#[derive(Debug, PartialEq, Eq, Hash, Clone)]
183pub enum DisclosureData {
184 KeyValue {
186 salt: Salt,
188 key: String,
190 value: Value,
192 },
193 ArrayElement {
195 salt: Salt,
197 value: Value,
199 },
200}
201
202pub type Salt = String;
204
205pub type Digest = String;
207
208#[derive(Debug)]
209pub(crate) struct DisclosureByDigestTable<'a>(pub(crate) HashMap<Digest, &'a Disclosure>);
210
211impl<'a> DisclosureByDigestTable<'a> {
212 pub(crate) fn new(
213 disclosures: &'a [Disclosure],
214 hasher: impl crate::Hasher,
215 ) -> DecodingResult<Self> {
216 let mut disclosure_by_digest = HashMap::new();
217 for disclosure in disclosures {
218 let digest = utils::base64_url_digest(disclosure.as_str().as_bytes(), &hasher);
219 if disclosure_by_digest.insert(digest, disclosure).is_some() {
220 return Err(Error::root(DecodingError::DisclosureDigestCollision));
221 }
222 }
223 Ok(Self(disclosure_by_digest))
224 }
225}
226
227#[derive(Debug, yoke::Yokeable)]
234pub(crate) struct DisclosureByPathTable<'model>(PathMapObject<&'model Disclosure>);
235
236impl<'model> DisclosureByPathTable<'model> {
237 pub(crate) fn new(inner: PathMapObject<&'model Disclosure>) -> Self {
238 Self(inner)
239 }
240
241 pub(crate) fn disclosures_covering_paths(
256 &self,
257 paths: &[&JsonNodePath],
258 ) -> impl Iterator<Item = &'model Disclosure> {
259 let mut set = HashSet::new();
260
261 for path in paths {
262 let _result = self.0.traverse_path(path.iter().copied(), |disclosure| {
269 set.insert(*disclosure);
270 });
271 }
272
273 set.into_iter()
274 }
275}
276
277#[cfg(test)]
278mod tests {
279
280 use bh_jws_utils::base64_url_encode;
281 use serde_json::{json, Value};
282
283 use crate::{error::FormatError, Disclosure};
284
285 type Result = std::result::Result<(), Box<dyn std::error::Error>>;
286
287 fn test_disclosure_encode_and_parse(
288 salt: &str,
289 claim_name: Option<&str>,
290 claim_value: Value,
291 encoded: &str,
292 ) -> Result {
293 let disclosure =
294 Disclosure::new(salt.to_owned(), claim_name.map(str::to_owned), claim_value);
295
296 assert_eq!(disclosure.as_str(), encoded);
297
298 let parsed = Disclosure::try_from(encoded.to_owned()).unwrap();
299
300 assert_eq!(parsed, disclosure);
301
302 Ok(())
303 }
304
305 #[test]
309 fn test_disclosure_encode_and_parse_object_property() -> Result {
310 test_disclosure_encode_and_parse(
311 "_26bc4LT-ac6q2KI6cBW5es",
312 Some("family_name"),
313 Value::String("Möbius".to_owned()),
314 "WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0",
315 )
316 }
317
318 #[test]
322 fn test_disclosure_encode_array_element() -> Result {
323 test_disclosure_encode_and_parse(
324 "lklxF5jMYlGTPUovMNIvCA",
325 None,
326 Value::String("FR".to_owned()),
327 "WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0",
328 )
329 }
330
331 #[test]
332 fn invalid_disclosure_not_a_base64_string() {
333 let invalid_base64 = "bla";
334
335 let decoded = Disclosure::try_from(invalid_base64.to_string());
336
337 assert_eq!(
338 decoded.unwrap_err().error,
339 FormatError::InvalidDisclosure("provided string is not base64 ".to_string())
340 )
341 }
342
343 #[test]
344 fn invalid_disclosure_too_few_elements_in_deserialized_array() {
345 let input = json!(["bla"]);
346 let encoded = base64_url_encode(input.to_string());
347
348 let decoded = Disclosure::try_from(encoded.clone());
349
350 assert_eq!(
351 decoded.unwrap_err().error,
352 FormatError::InvalidDisclosure(
353 "deserialized disclosure array has invalid length 1".to_string(),
354 )
355 );
356 }
357
358 #[test]
359 fn invalid_disclosure_too_many_elements_in_deserialized_array() {
360 let input = json!(["bla", "bla", 5, "bla"]);
361 let encoded = base64_url_encode(input.to_string());
362
363 let decoded = Disclosure::try_from(encoded.clone());
364
365 assert_eq!(
366 decoded.unwrap_err().error,
367 FormatError::InvalidDisclosure(
368 "deserialized disclosure array has invalid length 4".to_string()
369 )
370 );
371 }
372
373 #[test]
374 fn invalid_disclosure_salt_not_a_string() {
375 let input = json!([{"bla": "bla"}, 10.0]);
376
377 let encoded = base64_url_encode(input.to_string());
378
379 let decoded = Disclosure::try_from(encoded.clone());
380
381 assert_eq!(
382 decoded.unwrap_err().error,
383 FormatError::InvalidDisclosure("salt value is not a string".to_string())
384 );
385 }
386
387 #[test]
388 fn invalid_disclosure_key_is_not_a_string() {
389 let input = json!(["bla", {"bla": "bla"}, 10.0]);
390
391 let encoded = base64_url_encode(input.to_string());
392
393 let decoded = Disclosure::try_from(encoded.clone());
394
395 assert_eq!(
396 decoded.unwrap_err().error,
397 FormatError::InvalidDisclosure("key value is not a string".to_string())
398 );
399 }
400}