From 1c1a3a88dc6d4dce939cc2ac5c72c513be69fe58 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Wed, 12 Aug 2026 00:50:21 -0400 Subject: [PATCH] [PM-39979] Item-type Send backend integration work --- Cargo.lock | 1 + .../.openapi-generator/FILES | 2 + crates/bitwarden-api-api/src/models/mod.rs | 4 + .../src/models/send_access_response_model.rs | 3 +- .../src/models/send_data_model.rs | 38 +++++ .../src/models/send_encryption_type.rs | 78 ++++++++++ .../src/models/send_request_model.rs | 3 +- .../src/models/send_response_model.rs | 3 +- .../src/models/send_with_id_request_model.rs | 3 +- crates/bitwarden-api-identity/README.md | 3 +- crates/bitwarden-send/Cargo.toml | 3 +- crates/bitwarden-send/src/access.rs | 1 + crates/bitwarden-send/src/create.rs | 6 +- crates/bitwarden-send/src/delete.rs | 1 + crates/bitwarden-send/src/edit.rs | 8 +- crates/bitwarden-send/src/error.rs | 7 + crates/bitwarden-send/src/get_list.rs | 3 + crates/bitwarden-send/src/remove_password.rs | 1 + crates/bitwarden-send/src/send.rs | 134 +++++++++++++++++- .../src/key_rotation/data.rs | 1 + .../bitwarden-vault/src/cipher/attachment.rs | 2 +- crates/bitwarden-vault/src/cipher/card.rs | 2 +- crates/bitwarden-vault/src/cipher/cipher.rs | 2 +- crates/bitwarden-vault/src/cipher/field.rs | 2 +- crates/bitwarden-vault/src/cipher/identity.rs | 2 +- .../bitwarden-vault/src/cipher/secure_note.rs | 2 +- crates/bitwarden-vault/src/cipher/ssh_key.rs | 2 +- .../bitwarden-vault/src/password_history.rs | 2 +- crates/bw/src/tools/send.rs | 11 +- 29 files changed, 300 insertions(+), 30 deletions(-) create mode 100644 crates/bitwarden-api-api/src/models/send_data_model.rs create mode 100644 crates/bitwarden-api-api/src/models/send_encryption_type.rs diff --git a/Cargo.lock b/Cargo.lock index 02e6043a39..e00722783b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1138,6 +1138,7 @@ dependencies = [ "bitwarden-state", "bitwarden-test", "bitwarden-uuid", + "bitwarden-vault", "chrono", "reqwest", "serde", diff --git a/crates/bitwarden-api-api/.openapi-generator/FILES b/crates/bitwarden-api-api/.openapi-generator/FILES index 492916b620..f20885047f 100644 --- a/crates/bitwarden-api-api/.openapi-generator/FILES +++ b/crates/bitwarden-api-api/.openapi-generator/FILES @@ -528,6 +528,8 @@ src/models/selection_read_only_request_model.rs src/models/selection_read_only_response_model.rs src/models/self_hosted_organization_license_request_model.rs src/models/send_access_response_model.rs +src/models/send_data_model.rs +src/models/send_encryption_type.rs src/models/send_file_download_data_response_model.rs src/models/send_file_model.rs src/models/send_file_upload_data_response_model.rs diff --git a/crates/bitwarden-api-api/src/models/mod.rs b/crates/bitwarden-api-api/src/models/mod.rs index 7afcb87dfd..d87c2cc649 100644 --- a/crates/bitwarden-api-api/src/models/mod.rs +++ b/crates/bitwarden-api-api/src/models/mod.rs @@ -906,6 +906,10 @@ pub mod self_hosted_organization_license_request_model; pub use self::self_hosted_organization_license_request_model::SelfHostedOrganizationLicenseRequestModel; pub mod send_access_response_model; pub use self::send_access_response_model::SendAccessResponseModel; +pub mod send_data_model; +pub use self::send_data_model::SendDataModel; +pub mod send_encryption_type; +pub use self::send_encryption_type::SendEncryptionType; pub mod send_file_download_data_response_model; pub use self::send_file_download_data_response_model::SendFileDownloadDataResponseModel; pub mod send_file_model; diff --git a/crates/bitwarden-api-api/src/models/send_access_response_model.rs b/crates/bitwarden-api-api/src/models/send_access_response_model.rs index 382257cc31..aec634e62e 100644 --- a/crates/bitwarden-api-api/src/models/send_access_response_model.rs +++ b/crates/bitwarden-api-api/src/models/send_access_response_model.rs @@ -56,13 +56,12 @@ pub struct SendAccessResponseModel { skip_serializing_if = "Option::is_none" )] pub text: Option>, - /// Encrypted string containing secret Send data #[serde( rename = "data", alias = "Data", skip_serializing_if = "Option::is_none" )] - pub data: Option, + pub data: Option>, /// The date after which a send cannot be accessed. When this value is null, there is no /// expiration date. #[serde( diff --git a/crates/bitwarden-api-api/src/models/send_data_model.rs b/crates/bitwarden-api-api/src/models/send_data_model.rs new file mode 100644 index 0000000000..7cf30d55fa --- /dev/null +++ b/crates/bitwarden-api-api/src/models/send_data_model.rs @@ -0,0 +1,38 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SendDataModel { + #[serde( + rename = "encryptionVersion", + alias = "EncryptionVersion", + skip_serializing_if = "Option::is_none" + )] + pub encryption_version: Option, + #[serde( + rename = "data", + alias = "Data", + skip_serializing_if = "Option::is_none" + )] + pub data: Option, +} + +impl SendDataModel { + pub fn new() -> SendDataModel { + SendDataModel { + encryption_version: None, + data: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/send_encryption_type.rs b/crates/bitwarden-api-api/src/models/send_encryption_type.rs new file mode 100644 index 0000000000..4bd4ca0fc6 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/send_encryption_type.rs @@ -0,0 +1,78 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; + +use crate::models; +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum SendEncryptionType { + V1, + + /// Unknown value returned from the server. This is used to handle forward compatibility. + __Unknown(i64), +} + +impl SendEncryptionType { + pub fn as_i64(&self) -> i64 { + match self { + Self::V1 => 1, + Self::__Unknown(v) => *v, + } + } + + pub fn from_i64(value: i64) -> Self { + match value { + 1 => Self::V1, + v => Self::__Unknown(v), + } + } +} + +impl serde::Serialize for SendEncryptionType { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_i64(self.as_i64()) + } +} + +impl<'de> serde::Deserialize<'de> for SendEncryptionType { + fn deserialize>(deserializer: D) -> Result { + struct SendEncryptionTypeVisitor; + + impl Visitor<'_> for SendEncryptionTypeVisitor { + type Value = SendEncryptionType; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("an integer") + } + + fn visit_i64(self, v: i64) -> Result { + Ok(SendEncryptionType::from_i64(v)) + } + + fn visit_u64(self, v: u64) -> Result { + Ok(SendEncryptionType::from_i64(v as i64)) + } + } + + deserializer.deserialize_i64(SendEncryptionTypeVisitor) + } +} + +impl std::fmt::Display for SendEncryptionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_i64()) + } +} +impl Default for SendEncryptionType { + fn default() -> SendEncryptionType { + Self::V1 + } +} diff --git a/crates/bitwarden-api-api/src/models/send_request_model.rs b/crates/bitwarden-api-api/src/models/send_request_model.rs index d400825d67..379ef184b0 100644 --- a/crates/bitwarden-api-api/src/models/send_request_model.rs +++ b/crates/bitwarden-api-api/src/models/send_request_model.rs @@ -86,13 +86,12 @@ pub struct SendRequestModel { skip_serializing_if = "Option::is_none" )] pub text: Option>, - /// String containing secret Send data #[serde( rename = "data", alias = "Data", skip_serializing_if = "Option::is_none" )] - pub data: Option, + pub data: Option>, /// Base64-encoded byte array of a password hash that grants access to the send. Mutually /// exclusive with Bit.Api.Tools.Models.Request.SendRequestModel.Emails. #[serde( diff --git a/crates/bitwarden-api-api/src/models/send_response_model.rs b/crates/bitwarden-api-api/src/models/send_response_model.rs index 0e98916529..c5159db550 100644 --- a/crates/bitwarden-api-api/src/models/send_response_model.rs +++ b/crates/bitwarden-api-api/src/models/send_response_model.rs @@ -69,13 +69,12 @@ pub struct SendResponseModel { skip_serializing_if = "Option::is_none" )] pub text: Option>, - /// Encrypted string containing secret Send data #[serde( rename = "data", alias = "Data", skip_serializing_if = "Option::is_none" )] - pub data: Option, + pub data: Option>, /// A base64-encoded byte array containing the Send's encryption key. It's also provided to /// send recipients in the Send's URL. #[serde(rename = "key", alias = "Key", skip_serializing_if = "Option::is_none")] diff --git a/crates/bitwarden-api-api/src/models/send_with_id_request_model.rs b/crates/bitwarden-api-api/src/models/send_with_id_request_model.rs index feb0d43de4..1f3975197a 100644 --- a/crates/bitwarden-api-api/src/models/send_with_id_request_model.rs +++ b/crates/bitwarden-api-api/src/models/send_with_id_request_model.rs @@ -86,13 +86,12 @@ pub struct SendWithIdRequestModel { skip_serializing_if = "Option::is_none" )] pub text: Option>, - /// String containing secret Send data #[serde( rename = "data", alias = "Data", skip_serializing_if = "Option::is_none" )] - pub data: Option, + pub data: Option>, /// Base64-encoded byte array of a password hash that grants access to the send. Mutually /// exclusive with Bit.Api.Tools.Models.Request.SendRequestModel.Emails. #[serde( diff --git a/crates/bitwarden-api-identity/README.md b/crates/bitwarden-api-identity/README.md index aacfa49e8f..59bf59a506 100644 --- a/crates/bitwarden-api-identity/README.md +++ b/crates/bitwarden-api-identity/README.md @@ -22,7 +22,7 @@ client. - API version: v1 - Package version: 3.0.0 - Server Git commit: - [`c2d97d5ff2019c524405c36f7f3afc992ec0ef03`](https://github.com/bitwarden/server/commit/c2d97d5ff2019c524405c36f7f3afc992ec0ef03) + [`4f8c0f010712cbd860c29b225bec84b2592a331c`](https://github.com/bitwarden/server/commit/4f8c0f010712cbd860c29b225bec84b2592a331c) - Generator version: 7.15.0 - Build package: `org.openapitools.codegen.languages.RustClientCodegen` @@ -39,7 +39,6 @@ All URIs are relative to *https://identity.bitwarden.com* | _AccountsApi_ | [**post_register_verification_email_clicked**](docs/AccountsApi.md#accounts_post_register_verification_email_clicked) | **POST** /accounts/register/verification-email-clicked | | _AccountsApi_ | [**post_trial_initiation_send_verification_email**](docs/AccountsApi.md#accounts_post_trial_initiation_send_verification_email) | **POST** /accounts/trial/send-verification-email | | _InfoApi_ | [**get_alive**](docs/InfoApi.md#info_get_alive) | **GET** /alive | -| _InfoApi_ | [**get_version**](docs/InfoApi.md#info_get_version) | **GET** /version | | _SsoApi_ | [**external_callback**](docs/SsoApi.md#sso_external_callback) | **GET** /sso/ExternalCallback | | _SsoApi_ | [**external_challenge**](docs/SsoApi.md#sso_external_challenge) | **GET** /sso/ExternalChallenge | | _SsoApi_ | [**login**](docs/SsoApi.md#sso_login) | **GET** /sso/Login | diff --git a/crates/bitwarden-send/Cargo.toml b/crates/bitwarden-send/Cargo.toml index 74afcb4a5f..d1664eb419 100644 --- a/crates/bitwarden-send/Cargo.toml +++ b/crates/bitwarden-send/Cargo.toml @@ -34,9 +34,11 @@ bitwarden-encoding = { workspace = true } bitwarden-error = { workspace = true } bitwarden-state = { workspace = true } bitwarden-uuid = { workspace = true } +bitwarden-vault = { workspace = true } chrono = { workspace = true } reqwest = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } serde_repr = { workspace = true } sha2 = { workspace = true } thiserror = { workspace = true } @@ -50,7 +52,6 @@ zeroize = { workspace = true } [dev-dependencies] bitwarden-api-api = { workspace = true, features = ["mockall"] } bitwarden-test = { workspace = true } -serde_json = { workspace = true } tokio = { workspace = true, features = ["rt"] } wiremock = { workspace = true } diff --git a/crates/bitwarden-send/src/access.rs b/crates/bitwarden-send/src/access.rs index 5ef8f8a424..764737f488 100644 --- a/crates/bitwarden-send/src/access.rs +++ b/crates/bitwarden-send/src/access.rs @@ -614,6 +614,7 @@ mod tests { text: Some(text.to_owned()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, diff --git a/crates/bitwarden-send/src/create.rs b/crates/bitwarden-send/src/create.rs index 636fa7569d..0dbce127b5 100644 --- a/crates/bitwarden-send/src/create.rs +++ b/crates/bitwarden-send/src/create.rs @@ -87,7 +87,8 @@ impl // Derive the shareable send key for encrypting content let send_key = Send::derive_shareable_key(ctx, &k)?; - let (send_type, file, text) = self.view_type.clone().encrypt_composite(ctx, send_key)?; + let (send_type, file, text, data) = + self.view_type.clone().encrypt_composite(ctx, send_key)?; let (password, emails) = self.auth.auth_data(&k); @@ -109,8 +110,7 @@ impl deletion_date: self.deletion_date.to_rfc3339(), file, text, - // TODO: Implement logic for item-based Sends - data: None, + data, password, emails, disabled: self.disabled, diff --git a/crates/bitwarden-send/src/delete.rs b/crates/bitwarden-send/src/delete.rs index 8b5b08a3c6..6032aebbe2 100644 --- a/crates/bitwarden-send/src/delete.rs +++ b/crates/bitwarden-send/src/delete.rs @@ -77,6 +77,7 @@ mod tests { text: Some("Secret text".to_string()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, diff --git a/crates/bitwarden-send/src/edit.rs b/crates/bitwarden-send/src/edit.rs index 220241637c..f9899b97ce 100644 --- a/crates/bitwarden-send/src/edit.rs +++ b/crates/bitwarden-send/src/edit.rs @@ -134,7 +134,7 @@ impl let send_key = Send::derive_shareable_key(ctx, &k)?; - let (send_type, file, text) = self + let (send_type, file, text, data) = self .request .view_type .clone() @@ -167,8 +167,7 @@ impl deletion_date: self.request.deletion_date.to_rfc3339(), file, text, - // TODO: Implement logic for item-based Sends - data: None, + data, password, emails, disabled: self.request.disabled, @@ -344,6 +343,7 @@ mod tests { text: Some("original text".to_string()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -511,6 +511,7 @@ mod tests { text: Some("original text".to_string()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -596,6 +597,7 @@ mod tests { text: Some("secret".to_string()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, diff --git a/crates/bitwarden-send/src/error.rs b/crates/bitwarden-send/src/error.rs index 6d69c13720..b5c4d058a3 100644 --- a/crates/bitwarden-send/src/error.rs +++ b/crates/bitwarden-send/src/error.rs @@ -9,9 +9,16 @@ pub enum SendParseError { Crypto(#[from] bitwarden_crypto::CryptoError), #[error(transparent)] MissingField(#[from] bitwarden_core::MissingFieldError), + #[error(transparent)] + DeserializationFailure(#[from] SendItemDeserializationFailure), } /// Item does not exist error. #[derive(Debug, thiserror::Error)] #[error("Item does not exist")] pub struct ItemNotFoundError; + +/// Unable to deserialize Item-type Send data +#[derive(Debug, thiserror::Error)] +#[error("Send item deserialization failure")] +pub struct SendItemDeserializationFailure; diff --git a/crates/bitwarden-send/src/get_list.rs b/crates/bitwarden-send/src/get_list.rs index 20a666244f..67cc5978f6 100644 --- a/crates/bitwarden-send/src/get_list.rs +++ b/crates/bitwarden-send/src/get_list.rs @@ -98,6 +98,7 @@ mod tests { text: Some("Secret text".to_string()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -177,6 +178,7 @@ mod tests { text: Some("Text 1".to_string()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -209,6 +211,7 @@ mod tests { text: Some("Text 2".to_string()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, diff --git a/crates/bitwarden-send/src/remove_password.rs b/crates/bitwarden-send/src/remove_password.rs index b5f0346b5f..03a4c980de 100644 --- a/crates/bitwarden-send/src/remove_password.rs +++ b/crates/bitwarden-send/src/remove_password.rs @@ -94,6 +94,7 @@ mod tests { text: Some("Secret text".to_string()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, diff --git a/crates/bitwarden-send/src/send.rs b/crates/bitwarden-send/src/send.rs index 700fd388c3..bc372afeb3 100644 --- a/crates/bitwarden-send/src/send.rs +++ b/crates/bitwarden-send/src/send.rs @@ -1,5 +1,5 @@ use bitwarden_api_api::models::{ - SendFileModel, SendResponseModel, SendTextModel, SendWithIdRequestModel, + SendDataModel, SendFileModel, SendResponseModel, SendTextModel, SendWithIdRequestModel, }; use bitwarden_core::{ key_management::{KeySlotIds, SymmetricKeySlotId}, @@ -11,6 +11,7 @@ use bitwarden_crypto::{ }; use bitwarden_encoding::{B64, B64Url}; use bitwarden_uuid::uuid_newtype; +use bitwarden_vault::{Cipher, CipherView, EncryptMode}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -19,7 +20,7 @@ use zeroize::Zeroizing; #[cfg(feature = "wasm")] use {tsify::Tsify, wasm_bindgen::prelude::*}; -use crate::SendParseError; +use crate::{SendParseError, error::SendItemDeserializationFailure}; pub const SEND_ITERATIONS: u32 = 100_000; uuid_newtype!(pub SendId); @@ -71,6 +72,26 @@ pub struct SendText { pub hidden: bool, } +/// View model for decrypted SendItem +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub struct SendItemView { + /// The item content of the send + pub data: CipherView, +} + +/// Item-based send content +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub struct SendItem { + pub encryption_version: SendEncryptionType, + pub data: Cipher, +} + /// View model for decrypted SendText #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -83,7 +104,7 @@ pub struct SendTextView { pub hidden: bool, } -/// The type of Send, either text or file +/// The type of Send, either text, file, or item #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, PartialEq)] #[repr(u8)] #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] @@ -113,6 +134,16 @@ pub enum AuthType { None = 2, } +/// Indicates the version of Send data encryption that is being used +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] +pub enum SendEncryptionType { + /// V1 encryption (field by field) + V1 = 1, +} + /// Type-safe authentication method for a Send, including the authentication data. /// This ensures that password and email authentication are mutually exclusive. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -212,6 +243,8 @@ pub enum SendViewType { File(SendFileView), /// Text-based send Text(SendTextView), + /// Item-based send + Item(SendItemView), } /// Type alias for the tuple returned by SendViewType::into_api_models @@ -219,6 +252,7 @@ type SendApiModels = ( bitwarden_api_api::models::SendType, Option>, Option>, + Option>, ); impl CompositeEncryptable for SendViewType { @@ -237,6 +271,7 @@ impl CompositeEncryptable for Sen size_name: f.size_name.clone(), })), None, + None, )), SendViewType::Text(t) => Ok(( bitwarden_api_api::models::SendType::Text, @@ -250,7 +285,22 @@ impl CompositeEncryptable for Sen .map(|e| e.to_string()), hidden: Some(t.hidden), })), + None, )), + SendViewType::Item(i) => { + let encrypted = i.encrypt_composite(ctx, key)?; + let serialized_cipher = + serde_json::to_string(&encrypted.data).unwrap_or("{}".to_string()); + Ok(( + bitwarden_api_api::models::SendType::Item, + None, + None, + Some(Box::new(bitwarden_api_api::models::SendDataModel { + encryption_version: Some(SendEncryptionType::V1.into()), + data: Some(serialized_cipher), + })), + )) + } } } } @@ -272,6 +322,7 @@ pub struct Send { pub r#type: SendType, pub file: Option, pub text: Option, + pub data: Option, pub max_access_count: Option, pub access_count: u32, @@ -350,6 +401,7 @@ pub struct SendView { pub r#type: SendType, pub file: Option, pub text: Option, + pub data: Option, pub max_access_count: Option, pub access_count: u32, @@ -477,6 +529,31 @@ impl CompositeEncryptable for SendFile } } +impl Decryptable for SendItem { + fn decrypt( + &self, + ctx: &mut KeyStoreContext, + key: SymmetricKeySlotId, + ) -> Result { + let data: CipherView = self.data.decrypt(ctx, key)?; + Ok(SendItemView { data }) + } +} + +impl CompositeEncryptable for SendItemView { + fn encrypt_composite( + &self, + ctx: &mut KeyStoreContext, + key: SymmetricKeySlotId, + ) -> Result { + let cipher: Cipher = EncryptMode::Legacy(self.data.clone()).encrypt_composite(ctx, key)?; + Ok(SendItem { + encryption_version: SendEncryptionType::V1, + data: cipher, + }) + } +} + impl Decryptable for Send { fn decrypt( &self, @@ -502,6 +579,7 @@ impl Decryptable for Send { r#type: self.r#type, file: self.file.decrypt(ctx, key).ok().flatten(), text: self.text.decrypt(ctx, key).ok().flatten(), + data: self.data.decrypt(ctx, key).ok().flatten(), max_access_count: self.max_access_count, access_count: self.access_count, @@ -595,6 +673,7 @@ impl CompositeEncryptable for SendView { r#type: self.r#type, file: self.file.encrypt_composite(ctx, send_key)?, text: self.text.encrypt_composite(ctx, send_key)?, + data: self.data.encrypt_composite(ctx, send_key)?, max_access_count: self.max_access_count, access_count: self.access_count, @@ -637,6 +716,7 @@ impl TryFrom for Send { r#type: require!(send.r#type).try_into()?, file: send.file.map(|f| (*f).try_into()).transpose()?, text: send.text.map(|t| (*t).try_into()).transpose()?, + data: send.data.map(|d| (*d).try_into()).transpose()?, max_access_count: send.max_access_count.map(|s| s as u32), access_count: require!(send.access_count) as u32, disabled: send.disabled.unwrap_or(false), @@ -711,6 +791,27 @@ impl From for SendFileModel { } } +impl From for bitwarden_api_api::models::SendEncryptionType { + fn from(t: SendEncryptionType) -> Self { + match t { + SendEncryptionType::V1 => bitwarden_api_api::models::SendEncryptionType::V1, + } + } +} + +impl TryFrom for SendEncryptionType { + type Error = bitwarden_core::MissingFieldError; + + fn try_from(value: bitwarden_api_api::models::SendEncryptionType) -> Result { + Ok(match value { + bitwarden_api_api::models::SendEncryptionType::V1 => SendEncryptionType::V1, + bitwarden_api_api::models::SendEncryptionType::__Unknown(_) => { + return Err(bitwarden_core::MissingFieldError("encryption_version")); + } + }) + } +} + impl From for SendTextModel { fn from(text: SendText) -> Self { SendTextModel { @@ -744,6 +845,26 @@ impl TryFrom for SendText { } } +impl TryFrom for SendItem { + type Error = SendParseError; + + fn try_from(data: SendDataModel) -> Result { + let cipher = serde_json::from_str::(data.data.unwrap_or("{}".to_string()).as_str()); + match cipher { + Err(_e) => Err(SendParseError::DeserializationFailure( + SendItemDeserializationFailure, + )), + Ok(c) => Ok(SendItem { + encryption_version: SendEncryptionType::try_from( + data.encryption_version + .unwrap_or(SendEncryptionType::V1.into()), + )?, + data: c, + }), + } + } +} + #[cfg(test)] mod tests { use bitwarden_core::key_management::create_test_crypto_with_user_key; @@ -790,6 +911,7 @@ mod tests { text: "2.2VPyLzk1tMLug0X3x7RkaQ==|mrMt9vbZsCJhJIj4eebKyg==|aZ7JeyndytEMR1+uEBupEvaZuUE69D/ejhfdJL8oKq0=".parse().ok(), hidden: false, }), + data: None, key: "2.KLv/j0V4Ebs0dwyPdtt4vw==|jcrFuNYN1Qb3onBlwvtxUV/KpdnR1LPRL4EsCoXNAt4=|gHSywGy4Rj/RsCIZFwze4s2AACYKBtqDXTrQXjkgtIE=".parse().unwrap(), max_access_count: None, access_count: 0, @@ -819,6 +941,7 @@ mod tests { text: Some("This is a test".to_owned()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -852,6 +975,7 @@ mod tests { text: Some("This is a test".to_owned()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -889,6 +1013,7 @@ mod tests { text: Some("This is a test".to_owned()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -929,6 +1054,7 @@ mod tests { text: Some("This is a test".to_owned()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -973,6 +1099,7 @@ mod tests { text: Some("This is a test".to_owned()), hidden: false, }), + data: None, max_access_count: None, access_count: 0, disabled: false, @@ -1032,6 +1159,7 @@ mod tests { text: Some(text_value.parse().unwrap()), hidden: true, }), + data: None, max_access_count: Some(42), access_count: 0, disabled: true, diff --git a/crates/bitwarden-user-crypto-management/src/key_rotation/data.rs b/crates/bitwarden-user-crypto-management/src/key_rotation/data.rs index 290519cfb9..17597eaae2 100644 --- a/crates/bitwarden-user-crypto-management/src/key_rotation/data.rs +++ b/crates/bitwarden-user-crypto-management/src/key_rotation/data.rs @@ -507,6 +507,7 @@ mod tests { text: Some("This is a test send".to_string()), hidden: false, }), + data: None, r#type: bitwarden_send::SendType::Text, max_access_count: None, access_count: 0, diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index dd7cbc8fa7..4344c4801f 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -65,7 +65,7 @@ impl Attachment { } #[allow(missing_docs)] -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] diff --git a/crates/bitwarden-vault/src/cipher/card.rs b/crates/bitwarden-vault/src/cipher/card.rs index 5c4b5b595d..7d70e3b09a 100644 --- a/crates/bitwarden-vault/src/cipher/card.rs +++ b/crates/bitwarden-vault/src/cipher/card.rs @@ -25,7 +25,7 @@ pub struct Card { } #[allow(missing_docs)] -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 2fb23f0b82..eaae9a380f 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -427,7 +427,7 @@ impl TryFrom for CipherRequestModel { } #[allow(missing_docs)] -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] diff --git a/crates/bitwarden-vault/src/cipher/field.rs b/crates/bitwarden-vault/src/cipher/field.rs index 9a16d2c358..6c47b79bbd 100644 --- a/crates/bitwarden-vault/src/cipher/field.rs +++ b/crates/bitwarden-vault/src/cipher/field.rs @@ -63,7 +63,7 @@ pub struct Field { } #[allow(missing_docs)] -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] diff --git a/crates/bitwarden-vault/src/cipher/identity.rs b/crates/bitwarden-vault/src/cipher/identity.rs index 47b7937bf2..6f708a5aec 100644 --- a/crates/bitwarden-vault/src/cipher/identity.rs +++ b/crates/bitwarden-vault/src/cipher/identity.rs @@ -37,7 +37,7 @@ pub struct Identity { } #[allow(missing_docs)] -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] diff --git a/crates/bitwarden-vault/src/cipher/secure_note.rs b/crates/bitwarden-vault/src/cipher/secure_note.rs index 87e7a4173f..2687ad416e 100644 --- a/crates/bitwarden-vault/src/cipher/secure_note.rs +++ b/crates/bitwarden-vault/src/cipher/secure_note.rs @@ -52,7 +52,7 @@ impl<'de> Deserialize<'de> for SecureNoteType { } #[allow(missing_docs)] -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] diff --git a/crates/bitwarden-vault/src/cipher/ssh_key.rs b/crates/bitwarden-vault/src/cipher/ssh_key.rs index f4abf90153..e51a50699c 100644 --- a/crates/bitwarden-vault/src/cipher/ssh_key.rs +++ b/crates/bitwarden-vault/src/cipher/ssh_key.rs @@ -28,7 +28,7 @@ pub struct SshKey { } #[allow(missing_docs)] -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] diff --git a/crates/bitwarden-vault/src/password_history.rs b/crates/bitwarden-vault/src/password_history.rs index f74dbcd303..75ff914fa5 100644 --- a/crates/bitwarden-vault/src/password_history.rs +++ b/crates/bitwarden-vault/src/password_history.rs @@ -25,7 +25,7 @@ pub struct PasswordHistory { } #[allow(missing_docs)] -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] diff --git a/crates/bw/src/tools/send.rs b/crates/bw/src/tools/send.rs index d758859648..af3c209739 100644 --- a/crates/bw/src/tools/send.rs +++ b/crates/bw/src/tools/send.rs @@ -687,6 +687,8 @@ impl From for SendView { r#type: input.r#type, file: input.file, text: input.text, + // TODO - Use the `data` field when implementing item-type Send support + data: None, max_access_count: input.max_access_count, access_count: input.access_count, disabled: input.disabled, @@ -784,14 +786,17 @@ fn build_create_request(inputs: CreateInputs) -> color_eyre::eyre::Result n, (None, SendViewType::File(f)) => f.file_name.clone(), (None, SendViewType::Text(_)) => { return Err(eyre!("--name is required for text Sends.")); } + (None, SendViewType::Item(_)) => { + return Err(eyre!("--name is required for item Sends.")); + } }; let auth = build_auth(password, emails.as_deref())?; @@ -1814,6 +1819,7 @@ mod tests { text: Some("existing text".to_string()), hidden: false, }), + data: None, max_access_count: Some(42), access_count: 0, disabled: false, @@ -2087,6 +2093,7 @@ mod tests { text: Some("body".to_string()), hidden, }), + data: None, max_access_count: max, access_count: 0, disabled: false,