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