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