|
|
@ -79,19 +79,39 @@ impl Cipher { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pub fn validate_notes(cipher_data: &[CipherData]) -> EmptyResult { |
|
|
|
pub fn validate_cipher_data(cipher_data: &[CipherData]) -> EmptyResult { |
|
|
|
let mut validation_errors = serde_json::Map::new(); |
|
|
|
let max_note_size = CONFIG._max_note_size(); |
|
|
|
let max_note_size_msg = |
|
|
|
format!("The field Notes exceeds the maximum encrypted value length of {} characters.", &max_note_size); |
|
|
|
for (index, cipher) in cipher_data.iter().enumerate() { |
|
|
|
// Validate the note size and if it is exceeded return a warning
|
|
|
|
if let Some(note) = &cipher.notes { |
|
|
|
if note.len() > max_note_size { |
|
|
|
validation_errors |
|
|
|
.insert(format!("Ciphers[{index}].Notes"), serde_json::to_value([&max_note_size_msg]).unwrap()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Validate the password history if it contains `null` values and if so, return a warning
|
|
|
|
if let Some(Value::Array(password_history)) = &cipher.password_history { |
|
|
|
for pwh in password_history { |
|
|
|
if let Value::Object(pwo) = pwh { |
|
|
|
if pwo.get("password").is_some_and(|p| !p.is_string()) { |
|
|
|
validation_errors.insert( |
|
|
|
format!("Ciphers[{index}].Notes"), |
|
|
|
serde_json::to_value([ |
|
|
|
"The password history contains a `null` value. Only strings are allowed.", |
|
|
|
]) |
|
|
|
.unwrap(), |
|
|
|
); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if !validation_errors.is_empty() { |
|
|
|
let err_json = json!({ |
|
|
|
"message": "The model state is invalid.", |
|
|
@ -153,27 +173,39 @@ impl Cipher { |
|
|
|
.as_ref() |
|
|
|
.and_then(|s| { |
|
|
|
serde_json::from_str::<Vec<LowerCase<Value>>>(s) |
|
|
|
.inspect_err(|e| warn!("Error parsing fields {:?}", e)) |
|
|
|
.inspect_err(|e| warn!("Error parsing fields {e:?} for {}", self.uuid)) |
|
|
|
.ok() |
|
|
|
}) |
|
|
|
.map(|d| d.into_iter().map(|d| d.data).collect()) |
|
|
|
.unwrap_or_default(); |
|
|
|
|
|
|
|
let password_history_json: Vec<_> = self |
|
|
|
.password_history |
|
|
|
.as_ref() |
|
|
|
.and_then(|s| { |
|
|
|
serde_json::from_str::<Vec<LowerCase<Value>>>(s) |
|
|
|
.inspect_err(|e| warn!("Error parsing password history {:?}", e)) |
|
|
|
.inspect_err(|e| warn!("Error parsing password history {e:?} for {}", self.uuid)) |
|
|
|
.ok() |
|
|
|
}) |
|
|
|
.map(|d| d.into_iter().map(|d| d.data).collect()) |
|
|
|
.map(|d| { |
|
|
|
// Check every password history item if they are valid and return it.
|
|
|
|
// If a password field has the type `null` skip it, it breaks newer Bitwarden clients
|
|
|
|
d.into_iter() |
|
|
|
.filter_map(|d| match d.data.get("password") { |
|
|
|
Some(p) if p.is_string() => Some(d.data), |
|
|
|
_ => None, |
|
|
|
}) |
|
|
|
.collect() |
|
|
|
}) |
|
|
|
.unwrap_or_default(); |
|
|
|
|
|
|
|
// Get the type_data or a default to an empty json object '{}'.
|
|
|
|
// If not passing an empty object, mobile clients will crash.
|
|
|
|
let mut type_data_json = serde_json::from_str::<LowerCase<Value>>(&self.data) |
|
|
|
.map(|d| d.data) |
|
|
|
.unwrap_or_else(|_| Value::Object(serde_json::Map::new())); |
|
|
|
let mut type_data_json = |
|
|
|
serde_json::from_str::<LowerCase<Value>>(&self.data).map(|d| d.data).unwrap_or_else(|_| { |
|
|
|
warn!("Error parsing data field for {}", self.uuid); |
|
|
|
Value::Object(serde_json::Map::new()) |
|
|
|
}); |
|
|
|
|
|
|
|
// NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream
|
|
|
|
// Set the first element of the Uris array as Uri, this is needed several (mobile) clients.
|
|
|
@ -189,10 +221,13 @@ impl Cipher { |
|
|
|
|
|
|
|
// Fix secure note issues when data is invalid
|
|
|
|
// This breaks at least the native mobile clients
|
|
|
|
if self.atype == 2 |
|
|
|
&& (self.data.is_empty() || self.data.eq("{}") || self.data.to_ascii_lowercase().eq("{\"type\":null}")) |
|
|
|
{ |
|
|
|
type_data_json = json!({"type": 0}); |
|
|
|
if self.atype == 2 { |
|
|
|
match type_data_json { |
|
|
|
Value::Object(ref t) if t.get("type").is_some_and(|t| t.is_number()) => {} |
|
|
|
_ => { |
|
|
|
type_data_json = json!({"type": 0}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Clone the type_data and add some default value.
|
|
|
|