bitwarden_sm/secrets/
update.rs1use bitwarden_api_api::models::SecretUpdateRequestModel;
2use bitwarden_core::{key_management::SymmetricKeyId, Client};
3use bitwarden_crypto::Encryptable;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7use validator::Validate;
8
9use crate::{
10 error::{validate_only_whitespaces, SecretsManagerError},
11 secrets::SecretResponse,
12};
13
14#[allow(missing_docs)]
15#[derive(Serialize, Deserialize, Debug, JsonSchema, Validate)]
16#[serde(rename_all = "camelCase", deny_unknown_fields)]
17pub struct SecretPutRequest {
18 pub id: Uuid,
20 pub organization_id: Uuid,
22 #[validate(length(min = 1, max = 500), custom(function = validate_only_whitespaces))]
23 pub key: String,
24 #[validate(length(min = 1, max = 25_000))]
25 pub value: String,
26 #[validate(length(max = 7_000), custom(function = validate_only_whitespaces))]
27 pub note: String,
28 pub project_ids: Option<Vec<Uuid>>,
29}
30
31pub(crate) async fn update_secret(
32 client: &Client,
33 input: &SecretPutRequest,
34) -> Result<SecretResponse, SecretsManagerError> {
35 input.validate()?;
36
37 let key_store = client.internal.get_key_store();
38 let key = SymmetricKeyId::Organization(input.organization_id);
39
40 let secret = {
41 let mut ctx = key_store.context();
42 Some(SecretUpdateRequestModel {
43 key: input.key.clone().trim().encrypt(&mut ctx, key)?.to_string(),
44 value: input.value.clone().encrypt(&mut ctx, key)?.to_string(),
45 note: input
46 .note
47 .clone()
48 .trim()
49 .encrypt(&mut ctx, key)?
50 .to_string(),
51 project_ids: input.project_ids.clone(),
52 access_policies_requests: None,
53 })
54 };
55
56 let config = client.internal.get_api_configurations().await;
57 let res =
58 bitwarden_api_api::apis::secrets_api::secrets_id_put(&config.api, input.id, secret).await?;
59
60 SecretResponse::process_response(res, &mut key_store.context())
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 async fn update_secret(
68 key: Option<String>,
69 value: Option<String>,
70 note: Option<String>,
71 ) -> Result<SecretResponse, SecretsManagerError> {
72 let input = SecretPutRequest {
73 id: Uuid::new_v4(),
74 organization_id: Uuid::new_v4(),
75 key: key.unwrap_or_else(|| "test key".into()),
76 value: value.unwrap_or_else(|| "test value".into()),
77 note: note.unwrap_or_else(|| "test note".into()),
78 project_ids: Some(vec![Uuid::new_v4()]),
79 };
80
81 super::update_secret(&Client::new(None), &input).await
82 }
83
84 #[tokio::test]
85 async fn test_update_secret_request_key_empty_string() {
86 let response = update_secret(Some("".into()), None, None).await;
87 assert!(response.is_err());
88 assert_eq!(response.err().unwrap().to_string(), "key must not be empty");
89 }
90
91 #[tokio::test]
92 async fn test_update_secret_request_key_all_whitespaces_space() {
93 let response = update_secret(Some(" ".into()), None, None).await;
94 assert!(response.is_err());
95 assert_eq!(
96 response.err().unwrap().to_string(),
97 "key must not contain only whitespaces"
98 );
99 }
100
101 #[tokio::test]
102 async fn test_update_secret_request_key_all_whitespaces_tab() {
103 let response = update_secret(Some("\t".into()), None, None).await;
104 assert!(response.is_err());
105 assert_eq!(
106 response.err().unwrap().to_string(),
107 "key must not contain only whitespaces"
108 );
109 }
110
111 #[tokio::test]
112 async fn test_update_secret_request_key_all_whitespaces_newline() {
113 let response = update_secret(Some("\n".into()), None, None).await;
114 assert!(response.is_err());
115 assert_eq!(
116 response.err().unwrap().to_string(),
117 "key must not contain only whitespaces"
118 );
119 }
120
121 #[tokio::test]
122 async fn test_update_secret_request_key_all_whitespaces_combined() {
123 let response = update_secret(Some(" \t\n".into()), None, None).await;
124 assert!(response.is_err());
125 assert_eq!(
126 response.err().unwrap().to_string(),
127 "key must not contain only whitespaces"
128 );
129 }
130
131 #[tokio::test]
132 async fn test_update_secret_request_key_501_character_length() {
133 let response = update_secret(Some("a".repeat(501)), None, None).await;
134 assert!(response.is_err());
135 assert_eq!(
136 response.err().unwrap().to_string(),
137 "key must not exceed 500 characters in length"
138 );
139 }
140
141 #[tokio::test]
142 async fn test_update_secret_request_value_empty_string() {
143 let response = update_secret(None, Some("".into()), None).await;
144 assert!(response.is_err());
145 assert_eq!(
146 response.err().unwrap().to_string(),
147 "value must not be empty"
148 );
149 }
150
151 #[tokio::test]
152 async fn test_update_secret_request_value_25001_character_length() {
153 let response = update_secret(None, Some("a".repeat(25001)), None).await;
154 assert!(response.is_err());
155 assert_eq!(
156 response.err().unwrap().to_string(),
157 "value must not exceed 25000 characters in length"
158 );
159 }
160
161 #[tokio::test]
162 async fn test_update_secret_request_note_all_whitespaces_space() {
163 let response = update_secret(None, None, Some(" ".into())).await;
164 assert!(response.is_err());
165 assert_eq!(
166 response.err().unwrap().to_string(),
167 "note must not contain only whitespaces"
168 );
169 }
170
171 #[tokio::test]
172 async fn test_update_secret_request_note_all_whitespaces_tab() {
173 let response = update_secret(None, None, Some("\t".into())).await;
174 assert!(response.is_err());
175 assert_eq!(
176 response.err().unwrap().to_string(),
177 "note must not contain only whitespaces"
178 );
179 }
180
181 #[tokio::test]
182 async fn test_update_secret_request_note_all_whitespaces_newline() {
183 let response = update_secret(None, None, Some("\n".into())).await;
184 assert!(response.is_err());
185 assert_eq!(
186 response.err().unwrap().to_string(),
187 "note must not contain only whitespaces"
188 );
189 }
190
191 #[tokio::test]
192 async fn test_update_secret_request_note_all_whitespaces_combined() {
193 let response = update_secret(None, None, Some(" \t\n".into())).await;
194 assert!(response.is_err());
195 assert_eq!(
196 response.err().unwrap().to_string(),
197 "note must not contain only whitespaces"
198 );
199 }
200
201 #[tokio::test]
202 async fn test_update_secret_request_note_7001_character_length() {
203 let response = update_secret(None, None, Some("a".repeat(7001))).await;
204 assert!(response.is_err());
205 assert_eq!(
206 response.err().unwrap().to_string(),
207 "note must not exceed 7000 characters in length"
208 );
209 }
210}