Skip to main content

bitwarden_sm/secrets/
update.rs

1use 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    /// ID of the secret to modify
20    pub id: Uuid,
21    /// Organization ID of the secret to modify
22    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}