mongodb_atlas_cli/secrets/
encoding.rs1use std::borrow::Cow;
2use std::string::FromUtf8Error;
3
4#[cfg(target_os = "macos")]
5use base64::{DecodeError, Engine, prelude::*};
6use hex::FromHexError;
7use thiserror::Error;
8
9use crate::secrets::SecretStoreError;
10
11#[cfg(target_os = "macos")]
12const HEX_ENCODING_PREFIX: &str = "go-keyring-encoded:";
13#[cfg(target_os = "macos")]
14const BASE64_ENCODING_PREFIX: &str = "go-keyring-base64:";
15
16#[derive(Debug, Error)]
17pub enum DecodePasswordError {
18 #[error("Invalid hex value: {0}")]
19 InvalidHexValue(#[from] FromHexError),
20 #[error("Invalid utf8 value: {0}")]
21 InvalidUtf8Value(#[from] FromUtf8Error),
22 #[cfg(target_os = "macos")]
23 #[error("Invalid base64 value: {0}")]
24 InvalidBase64Value(#[from] DecodeError),
25}
26
27impl From<DecodePasswordError> for SecretStoreError {
28 fn from(error: DecodePasswordError) -> Self {
29 SecretStoreError::Serialization {
30 reason: (error.to_string()),
31 }
32 }
33}
34
35#[cfg(target_os = "macos")]
36pub fn decode_password(password: String) -> Result<Option<String>, DecodePasswordError> {
37 if let Some(hex_encoded_value) = password.strip_prefix(HEX_ENCODING_PREFIX) {
38 let hex = hex::decode(hex_encoded_value)?;
39 let decoded = String::from_utf8(hex)?;
40 return Ok(none_if_empty(decoded));
41 }
42
43 if let Some(base64_encoded_value) = password.strip_prefix(BASE64_ENCODING_PREFIX) {
44 let base64 = BASE64_STANDARD.decode(base64_encoded_value)?;
45 let decoded = String::from_utf8(base64)?;
46 return Ok(none_if_empty(decoded));
47 }
48
49 Ok(none_if_empty(password))
50}
51
52#[cfg(not(target_os = "macos"))]
53pub fn decode_password(password: String) -> Result<Option<String>, DecodePasswordError> {
54 Ok(none_if_empty(password))
55}
56
57fn none_if_empty(password: String) -> Option<String> {
58 if password.is_empty() {
59 None
60 } else {
61 Some(password)
62 }
63}
64
65#[cfg(target_os = "macos")]
66pub fn encode_password<'a>(password: &'a str) -> Cow<'a, str> {
67 let base64 = BASE64_STANDARD.encode(password);
69 Cow::Owned(format!("{}{}", BASE64_ENCODING_PREFIX, base64))
70}
71
72#[cfg(not(target_os = "macos"))]
73pub fn encode_password<'a>(password: &'a str) -> Cow<'a, str> {
74 Cow::Borrowed(password)
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_decode_hex_password() {
83 let password = "go-keyring-encoded:616263";
84 let decoded = decode_password(password.to_string()).unwrap();
85 assert_eq!(decoded, Some("abc".to_string()));
86 }
87
88 #[test]
89 fn test_decode_base64_password() {
90 let password = "go-keyring-base64:YWJj";
91 let decoded = decode_password(password.to_string()).unwrap();
92 assert_eq!(decoded, Some("abc".to_string()));
93 }
94
95 #[test]
96 fn test_decode_without_prefix() {
97 let password = "abc";
98 let decoded = decode_password(password.to_string()).unwrap();
99 assert_eq!(decoded, Some("abc".to_string()));
100 }
101
102 #[test]
103 fn test_encode_password() {
104 let password = "abc";
105 let encoded = encode_password(password);
106 assert_eq!(encoded, "go-keyring-base64:YWJj");
107 }
108}