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