bitwarden_sm/secrets/
create.rs

1use bitwarden_api_api::models::SecretCreateRequestModel;
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#[derive(Serialize, Deserialize, Debug, JsonSchema, Validate)]
15#[serde(rename_all = "camelCase", deny_unknown_fields)]
16pub struct SecretCreateRequest {
17    /// Organization where the secret will be created
18    pub organization_id: Uuid,
19
20    #[validate(length(min = 1, max = 500), custom(function = validate_only_whitespaces))]
21    pub key: String,
22    #[validate(length(min = 1, max = 25_000))]
23    pub value: String,
24    #[validate(length(max = 7_000), custom(function = validate_only_whitespaces))]
25    pub note: String,
26
27    /// IDs of the projects that this secret will belong to
28    pub project_ids: Option<Vec<Uuid>>,
29}
30
31pub(crate) async fn create_secret(
32    client: &Client,
33    input: &SecretCreateRequest,
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(SecretCreateRequestModel {
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 = bitwarden_api_api::apis::secrets_api::organizations_organization_id_secrets_post(
58        &config.api,
59        input.organization_id,
60        secret,
61    )
62    .await?;
63
64    SecretResponse::process_response(res, &mut key_store.context())
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    async fn create_secret(
72        key: Option<String>,
73        value: Option<String>,
74        note: Option<String>,
75    ) -> Result<SecretResponse, SecretsManagerError> {
76        let input = SecretCreateRequest {
77            organization_id: Uuid::new_v4(),
78            key: key.unwrap_or_else(|| "test key".into()),
79            value: value.unwrap_or_else(|| "test value".into()),
80            note: note.unwrap_or_else(|| "test note".into()),
81            project_ids: Some(vec![Uuid::new_v4()]),
82        };
83
84        super::create_secret(&Client::new(None), &input).await
85    }
86
87    #[tokio::test]
88    async fn test_create_secret_request_key_empty_string() {
89        let response = create_secret(Some("".into()), None, None).await;
90        assert!(response.is_err());
91        assert_eq!(response.err().unwrap().to_string(), "key must not be empty");
92    }
93
94    #[tokio::test]
95    async fn test_create_secret_request_key_all_whitespaces_space() {
96        let response = create_secret(Some(" ".into()), None, None).await;
97        assert!(response.is_err());
98        assert_eq!(
99            response.err().unwrap().to_string(),
100            "key must not contain only whitespaces"
101        );
102    }
103
104    #[tokio::test]
105    async fn test_create_secret_request_key_all_whitespaces_tab() {
106        let response = create_secret(Some("\t".into()), None, None).await;
107        assert!(response.is_err());
108        assert_eq!(
109            response.err().unwrap().to_string(),
110            "key must not contain only whitespaces"
111        );
112    }
113
114    #[tokio::test]
115    async fn test_create_secret_request_key_all_whitespaces_newline() {
116        let response = create_secret(Some("\n".into()), None, None).await;
117        assert!(response.is_err());
118        assert_eq!(
119            response.err().unwrap().to_string(),
120            "key must not contain only whitespaces"
121        );
122    }
123
124    #[tokio::test]
125    async fn test_create_secret_request_key_all_whitespaces_combined() {
126        let response = create_secret(Some(" \t\n".into()), None, None).await;
127        assert!(response.is_err());
128        assert_eq!(
129            response.err().unwrap().to_string(),
130            "key must not contain only whitespaces"
131        );
132    }
133
134    #[tokio::test]
135    async fn test_create_secret_request_key_501_character_length() {
136        let response = create_secret(Some("a".repeat(501)), None, None).await;
137        assert!(response.is_err());
138        assert_eq!(
139            response.err().unwrap().to_string(),
140            "key must not exceed 500 characters in length"
141        );
142    }
143
144    #[tokio::test]
145    async fn test_create_secret_request_value_empty_string() {
146        let response = create_secret(None, Some("".into()), None).await;
147        assert!(response.is_err());
148        assert_eq!(
149            response.err().unwrap().to_string(),
150            "value must not be empty"
151        );
152    }
153
154    #[tokio::test]
155    async fn test_create_secret_request_value_25001_character_length() {
156        let response = create_secret(None, Some("a".repeat(25001)), None).await;
157        assert!(response.is_err());
158        assert_eq!(
159            response.err().unwrap().to_string(),
160            "value must not exceed 25000 characters in length"
161        );
162    }
163
164    #[tokio::test]
165    async fn test_create_secret_request_note_all_whitespaces_space() {
166        let response = create_secret(None, None, Some(" ".into())).await;
167        assert!(response.is_err());
168        assert_eq!(
169            response.err().unwrap().to_string(),
170            "note must not contain only whitespaces"
171        );
172    }
173
174    #[tokio::test]
175    async fn test_create_secret_request_note_all_whitespaces_tab() {
176        let response = create_secret(None, None, Some("\t".into())).await;
177        assert!(response.is_err());
178        assert_eq!(
179            response.err().unwrap().to_string(),
180            "note must not contain only whitespaces"
181        );
182    }
183
184    #[tokio::test]
185    async fn test_create_secret_request_note_all_whitespaces_newline() {
186        let response = create_secret(None, None, Some("\n".into())).await;
187        assert!(response.is_err());
188        assert_eq!(
189            response.err().unwrap().to_string(),
190            "note must not contain only whitespaces"
191        );
192    }
193
194    #[tokio::test]
195    async fn test_create_secret_request_note_all_whitespaces_combined() {
196        let response = create_secret(None, None, Some(" \t\n".into())).await;
197        assert!(response.is_err());
198        assert_eq!(
199            response.err().unwrap().to_string(),
200            "note must not contain only whitespaces"
201        );
202    }
203
204    #[tokio::test]
205    async fn test_create_secret_request_note_7001_character_length() {
206        let response = create_secret(None, None, Some("a".repeat(7001))).await;
207        assert!(response.is_err());
208        assert_eq!(
209            response.err().unwrap().to_string(),
210            "note must not exceed 7000 characters in length"
211        );
212    }
213}