bitwarden_vault/cipher/cipher_client/admin/
create.rs1use bitwarden_api_api::models::CipherCreateRequestModel;
2use bitwarden_core::{
3 ApiError, MissingFieldError, NotAuthenticatedError, UserId, key_management::KeySlotIds,
4};
5use bitwarden_crypto::{CryptoError, IdentifyKey, KeyStore};
6use bitwarden_error::bitwarden_error;
7use thiserror::Error;
8#[cfg(feature = "wasm")]
9use wasm_bindgen::prelude::*;
10
11use crate::{
12 Cipher, CipherView, VaultParseError,
13 cipher::cipher::{PartialCipher, StrictDecrypt},
14 cipher_client::{
15 admin::CipherAdminClient,
16 create::{CipherCreateRequest, CipherCreateRequestInternal},
17 },
18};
19
20#[allow(missing_docs)]
21#[bitwarden_error(flat)]
22#[derive(Debug, Error)]
23pub enum CreateCipherAdminError {
24 #[error(transparent)]
25 Crypto(#[from] CryptoError),
26 #[error(transparent)]
27 Api(#[from] ApiError),
28 #[error(transparent)]
29 VaultParse(#[from] VaultParseError),
30 #[error(transparent)]
31 MissingField(#[from] MissingFieldError),
32 #[error(transparent)]
33 NotAuthenticated(#[from] NotAuthenticatedError),
34}
35
36impl<T> From<bitwarden_api_api::apis::Error<T>> for CreateCipherAdminError {
37 fn from(val: bitwarden_api_api::apis::Error<T>) -> Self {
38 Self::Api(val.into())
39 }
40}
41
42async fn create_cipher(
44 request: CipherCreateRequestInternal,
45 encrypted_for: UserId,
46 api_client: &bitwarden_api_api::apis::ApiClient,
47 key_store: &KeyStore<KeySlotIds>,
48 use_strict_decryption: bool,
49) -> Result<CipherView, CreateCipherAdminError> {
50 let collection_ids = request.create_request.collection_ids.clone();
51 let folder_id = request.create_request.folder_id;
54 let favorite = request.create_request.favorite;
55 let mut cipher_request = key_store.encrypt(request)?;
56 cipher_request.encrypted_for = Some(encrypted_for.into());
57
58 let mut cipher: Cipher = api_client
59 .ciphers_api()
60 .post_admin(Some(CipherCreateRequestModel {
61 collection_ids: Some(collection_ids.iter().cloned().map(Into::into).collect()),
62 cipher: Box::new(cipher_request),
63 }))
64 .await?
65 .merge_with_cipher(None)?;
66
67 cipher.collection_ids = collection_ids;
68 cipher.folder_id = folder_id;
69 cipher.favorite = favorite;
70 cipher.edit = true;
71 cipher.view_password = true;
72
73 if use_strict_decryption {
74 Ok(key_store.decrypt(&StrictDecrypt(cipher))?)
75 } else {
76 Ok(key_store.decrypt(&cipher)?)
77 }
78}
79
80#[cfg_attr(feature = "wasm", wasm_bindgen)]
81impl CipherAdminClient {
82 pub async fn create(
85 &self,
86 request: CipherCreateRequest,
87 ) -> Result<CipherView, CreateCipherAdminError> {
88 let key_store = self.client.internal.get_key_store();
89 let config = self.client.internal.get_api_configurations();
90 let mut internal_request: CipherCreateRequestInternal = request.into();
91
92 let user_id = self
93 .client
94 .internal
95 .get_user_id()
96 .ok_or(NotAuthenticatedError)?;
97
98 if self
101 .client
102 .internal
103 .get_flags()
104 .await
105 .enable_cipher_key_encryption
106 {
107 let key = internal_request.key_identifier();
108 internal_request.generate_cipher_key(&mut key_store.context(), key)?;
109 }
110
111 create_cipher(
112 internal_request,
113 user_id,
114 &config.api_client,
115 key_store,
116 self.is_strict_decrypt().await,
117 )
118 .await
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use bitwarden_api_api::models::CipherMiniResponseModel;
125 use bitwarden_core::{OrganizationId, key_management::SymmetricKeySlotId};
126 use bitwarden_crypto::SymmetricCryptoKey;
127 use chrono::Utc;
128
129 use super::*;
130 use crate::{CipherRepromptType, CipherViewType, LoginView};
131
132 const TEST_CIPHER_ID: &str = "5faa9684-c793-4a2d-8a12-b33900187097";
133 const TEST_COLLECTION_ID: &str = "73546b86-8802-4449-ad2a-69ea981b4ffd";
134 const TEST_USER_ID: &str = "550e8400-e29b-41d4-a716-446655440000";
135 const TEST_ORG_ID: &str = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8";
136
137 #[tokio::test]
138 async fn test_create_org_cipher() {
139 let api_client = bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
140 mock.ciphers_api
141 .expect_post_admin()
142 .returning(move |request| {
143 let request = request.unwrap();
144
145 Ok(CipherMiniResponseModel {
146 id: Some(TEST_CIPHER_ID.try_into().unwrap()),
147 organization_id: request
148 .cipher
149 .organization_id
150 .and_then(|id| id.parse().ok()),
151 name: Some(request.cipher.name.clone()),
152 r#type: request.cipher.r#type,
153 creation_date: Some(
154 Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
155 ),
156 revision_date: Some(
157 Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
158 ),
159 ..Default::default()
160 })
161 });
162 });
163
164 let store: KeyStore<KeySlotIds> = KeyStore::default();
165 #[allow(deprecated)]
166 let _ = store.context_mut().set_symmetric_key(
167 SymmetricKeySlotId::User,
168 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
169 );
170 #[allow(deprecated)]
171 let _ = store.context_mut().set_symmetric_key(
172 SymmetricKeySlotId::Organization(TEST_ORG_ID.parse::<OrganizationId>().unwrap()),
173 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
174 );
175
176 let test_folder_id: crate::FolderId =
177 "a4e13cc0-1234-5678-abcd-b181009709b8".parse().unwrap();
178 let test_collection_id: bitwarden_collections::collection::CollectionId =
179 TEST_COLLECTION_ID.parse().unwrap();
180
181 let cipher_request: CipherCreateRequestInternal = CipherCreateRequest {
182 organization_id: Some(TEST_ORG_ID.parse().unwrap()),
183 collection_ids: vec![test_collection_id],
184 folder_id: Some(test_folder_id),
185 name: "Test Cipher".into(),
186 notes: None,
187 favorite: true,
188 reprompt: CipherRepromptType::None,
189 r#type: CipherViewType::Login(LoginView {
190 username: None,
191 password: None,
192 password_revision_date: None,
193 uris: None,
194 totp: None,
195 autofill_on_page_load: None,
196 fido2_credentials: None,
197 }),
198 fields: vec![],
199 }
200 .into();
201
202 let response = create_cipher(
203 cipher_request.clone(),
204 TEST_USER_ID.parse().unwrap(),
205 &api_client,
206 &store,
207 false,
208 )
209 .await
210 .unwrap();
211
212 assert_eq!(response.id, Some(TEST_CIPHER_ID.parse().unwrap()));
213 assert_eq!(
214 response.organization_id,
215 cipher_request.create_request.organization_id
216 );
217 assert_eq!(
219 response.collection_ids,
220 cipher_request.create_request.collection_ids
221 );
222 assert_eq!(response.folder_id, cipher_request.create_request.folder_id);
223 assert_eq!(response.favorite, cipher_request.create_request.favorite);
224 assert!(response.edit, "edit should be true after admin create");
225 assert!(
226 response.view_password,
227 "view_password should be true after admin create"
228 );
229 }
230}