Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions crates/bitwarden-api-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[package]
name = "bitwarden-api-api"
description = "Api bindings for the Bitwarden API."
categories = ["api-bindings"]

version.workspace = true
Expand All @@ -13,14 +12,10 @@ license-file.workspace = true
keywords.workspace = true

[dependencies]
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_repr = { workspace = true }
serde_with = { version = ">=3.8, <4", default-features = false, features = [
"base64",
"std",
"macros",
] }
url = ">=2.5, <3"
uuid = { workspace = true }
reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "http2"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
serde_repr = "^0.1"
serde_with = { version = "^3.8", default-features = false, features = ["base64", "std", "macros"] }
url = "^2.5"
uuid = { version = "^1.8", features = ["serde", "v4"] }
2 changes: 1 addition & 1 deletion crates/bitwarden-api-api/src/apis/ciphers_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3055,7 +3055,7 @@ pub async fn ciphers_post(

pub async fn ciphers_purge_post(
configuration: &configuration::Configuration,
organization_id: Option<&str>,
organization_id: Option<uuid::Uuid>,
secret_verification_request_model: Option<models::SecretVerificationRequestModel>,
) -> Result<(), Error<CiphersPurgePostError>> {
// add a prefix to parameters to efficiently prevent name collisions
Expand Down
3 changes: 3 additions & 0 deletions crates/bitwarden-api-api/src/models/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub struct Cipher {
pub reprompt: Option<models::CipherRepromptType>,
#[serde(rename = "key", skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(rename = "version", skip_serializing_if = "Option::is_none")]
pub version: Option<i32>,
}

impl Cipher {
Expand All @@ -58,6 +60,7 @@ impl Cipher {
deleted_date: None,
reprompt: None,
key: None,
version: None,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub struct CipherDetailsResponseModel {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub r#type: Option<models::CipherType>,
#[serde(rename = "data", skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
pub data: Option<String>,
#[serde(rename = "name", skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "notes", skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -59,6 +59,8 @@ pub struct CipherDetailsResponseModel {
pub reprompt: Option<models::CipherRepromptType>,
#[serde(rename = "key", skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(rename = "version", skip_serializing_if = "Option::is_none")]
pub version: Option<i32>,
#[serde(rename = "folderId", skip_serializing_if = "Option::is_none")]
pub folder_id: Option<uuid::Uuid>,
#[serde(rename = "favorite", skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -97,6 +99,7 @@ impl CipherDetailsResponseModel {
deleted_date: None,
reprompt: None,
key: None,
version: None,
folder_id: None,
favorite: None,
edit: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ pub struct CipherMiniDetailsResponseModel {
pub reprompt: Option<models::CipherRepromptType>,
#[serde(rename = "key", skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(rename = "version", skip_serializing_if = "Option::is_none")]
pub version: Option<i32>,
#[serde(rename = "collectionIds", skip_serializing_if = "Option::is_none")]
pub collection_ids: Option<Vec<uuid::Uuid>>,
}
Expand Down Expand Up @@ -87,6 +89,7 @@ impl CipherMiniDetailsResponseModel {
deleted_date: None,
reprompt: None,
key: None,
version: None,
collection_ids: None,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ pub struct CipherMiniResponseModel {
pub reprompt: Option<models::CipherRepromptType>,
#[serde(rename = "key", skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(rename = "version", skip_serializing_if = "Option::is_none")]
pub version: Option<i32>,
}

impl CipherMiniResponseModel {
Expand All @@ -85,6 +87,7 @@ impl CipherMiniResponseModel {
deleted_date: None,
reprompt: None,
key: None,
version: None,
}
}
}
8 changes: 8 additions & 0 deletions crates/bitwarden-api-api/src/models/cipher_request_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ pub struct CipherRequestModel {
pub secure_note: Option<Box<models::CipherSecureNoteModel>>,
#[serde(rename = "sshKey", skip_serializing_if = "Option::is_none")]
pub ssh_key: Option<Box<models::CipherSshKeyModel>>,
/// The version of the cipher data. Default is 1 for backward compatibility.
#[serde(rename = "version", skip_serializing_if = "Option::is_none")]
pub version: Option<i32>,
Comment on lines +54 to +56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿค” Is there any important distinction between version: Some(1) and version: None? If not then you could use serde(default = )to default it to 1 and ditch the Option.

This would mean writing version: 1 on save - would that matter for older clients, or do they already safely ignore unknown fields?

/// Opaque data for versioned clients.
#[serde(rename = "data", skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(
rename = "lastKnownRevisionDate",
skip_serializing_if = "Option::is_none"
Expand Down Expand Up @@ -79,6 +85,8 @@ impl CipherRequestModel {
identity: None,
secure_note: None,
ssh_key: None,
version: None,
data: None,
last_known_revision_date: None,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ pub struct CipherResponseModel {
pub reprompt: Option<models::CipherRepromptType>,
#[serde(rename = "key", skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(rename = "version", skip_serializing_if = "Option::is_none")]
pub version: Option<i32>,
#[serde(rename = "folderId", skip_serializing_if = "Option::is_none")]
pub folder_id: Option<uuid::Uuid>,
#[serde(rename = "favorite", skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -95,6 +97,7 @@ impl CipherResponseModel {
deleted_date: None,
reprompt: None,
key: None,
version: None,
folder_id: None,
favorite: None,
edit: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ pub struct CipherWithIdRequestModel {
pub secure_note: Option<Box<models::CipherSecureNoteModel>>,
#[serde(rename = "sshKey", skip_serializing_if = "Option::is_none")]
pub ssh_key: Option<Box<models::CipherSshKeyModel>>,
/// The version of the cipher data. Default is 1 for backward compatibility.
#[serde(rename = "version", skip_serializing_if = "Option::is_none")]
pub version: Option<i32>,
/// Opaque data for versioned clients.
#[serde(rename = "data", skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(
rename = "lastKnownRevisionDate",
skip_serializing_if = "Option::is_none"
Expand Down Expand Up @@ -81,6 +87,8 @@ impl CipherWithIdRequestModel {
identity: None,
secure_note: None,
ssh_key: None,
version: None,
data: None,
last_known_revision_date: None,
id,
}
Expand Down
19 changes: 7 additions & 12 deletions crates/bitwarden-api-identity/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[package]
name = "bitwarden-api-identity"
description = "Api bindings for the Bitwarden Identity API."
categories = ["api-bindings"]

version.workspace = true
Expand All @@ -13,14 +12,10 @@ license-file.workspace = true
keywords.workspace = true

[dependencies]
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_repr = { workspace = true }
serde_with = { version = ">=3.8, <4", default-features = false, features = [
"base64",
"std",
"macros",
] }
url = ">=2.5, <3"
uuid = { workspace = true }
reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "http2"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
serde_repr = "^0.1"
serde_with = { version = "^3.8", default-features = false, features = ["base64", "std", "macros"] }
url = "^2.5"
uuid = { version = "^1.8", features = ["serde", "v4"] }
1 change: 1 addition & 0 deletions crates/bitwarden-exporters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ impl From<ImportingCipher> for CipherView {
creation_date: value.creation_date,
deleted_date: None,
revision_date: value.revision_date,
version: None,
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/bitwarden-exporters/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ mod tests {
creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
deleted_date: None,
revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
version: None,
};

let login = from_login(&view, &key_store).unwrap();
Expand Down Expand Up @@ -312,6 +313,7 @@ mod tests {
creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
deleted_date: None,
revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
version: None,
};
let encrypted = key_store.encrypt(cipher_view).unwrap();

Expand Down
6 changes: 6 additions & 0 deletions crates/bitwarden-vault/src/cipher/attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ mod tests {
creation_date: "2023-07-24T12:05:09.466666700Z".parse().unwrap(),
deleted_date: None,
revision_date: "2023-07-27T19:28:05.240Z".parse().unwrap(),
version: None,
data: None,
},
attachment,
contents: contents.as_slice(),
Expand Down Expand Up @@ -340,6 +342,8 @@ mod tests {
creation_date: "2023-07-24T12:05:09.466666700Z".parse().unwrap(),
deleted_date: None,
revision_date: "2023-07-27T19:28:05.240Z".parse().unwrap(),
version: None,
data: None,
};

let enc_file = STANDARD.decode(b"Ao00qr1xLsV+ZNQpYZ/UwEwOWo3hheKwCYcOGIbsorZ6JIG2vLWfWEXCVqP0hDuzRvmx8otApNZr8pJYLNwCe1aQ+ySHQYGkdubFjoMojulMbQ959Y4SJ6Its/EnVvpbDnxpXTDpbutDxyhxfq1P3lstL2G9rObJRrxiwdGlRGu1h94UA1fCCkIUQux5LcqUee6W4MyQmRnsUziH8gGzmtI=").unwrap();
Expand Down Expand Up @@ -398,6 +402,8 @@ mod tests {
creation_date: "2023-07-24T12:05:09.466666700Z".parse().unwrap(),
deleted_date: None,
revision_date: "2023-07-27T19:28:05.240Z".parse().unwrap(),
version: None,
data: None,
};

let enc_file = STANDARD.decode(b"AsQLXOBHrJ8porroTUlPxeJOm9XID7LL9D2+KwYATXEpR1EFjLBpcCvMmnqcnYLXIEefe9TCeY4Us50ux43kRSpvdB7YkjxDKV0O1/y6tB7qC4vvv9J9+O/uDEnMx/9yXuEhAW/LA/TsU/WAgxkOM0uTvm8JdD9LUR1z9Ql7zOWycMVzkvGsk2KBNcqAdrotS5FlDftZOXyU8pWecNeyA/w=").unwrap();
Expand Down
22 changes: 22 additions & 0 deletions crates/bitwarden-vault/src/cipher/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ use crate::{
VaultParseError,
};

// Version constant
pub const CURRENT_CIPHER_VERSION: u32 = 3;

#[allow(missing_docs)]
#[bitwarden_error(flat)]
#[derive(Debug, Error)]
Expand All @@ -46,6 +49,12 @@ pub enum CipherError {
EncryptError(#[from] EncryptError),
#[error("This cipher contains attachments without keys. Those attachments will need to be reuploaded to complete the operation")]
AttachmentsWithoutKeys,

// POC - Cipher versioning
#[error("Unsupported cipher version {0}")]
UnsupportedCipherVersion(u32),
#[error("Migration failed: {0}")]
MigrationFailed(String),
}

/// Helper trait for operations on cipher types.
Expand Down Expand Up @@ -136,6 +145,9 @@ pub struct Cipher {
pub creation_date: DateTime<Utc>,
pub deleted_date: Option<DateTime<Utc>>,
pub revision_date: DateTime<Utc>,

pub version: Option<u32>,
pub data: Option<String>,
}

bitwarden_state::register_repository_item!(Cipher, "Cipher");
Expand Down Expand Up @@ -179,6 +191,8 @@ pub struct CipherView {
pub creation_date: DateTime<Utc>,
pub deleted_date: Option<DateTime<Utc>>,
pub revision_date: DateTime<Utc>,

pub version: Option<u32>,
}

#[allow(missing_docs)]
Expand Down Expand Up @@ -333,6 +347,8 @@ impl CompositeEncryptable<KeyIds, SymmetricKeyId, Cipher> for CipherView {
deleted_date: cipher_view.deleted_date,
revision_date: cipher_view.revision_date,
permissions: cipher_view.permissions,
version: cipher_view.version,
data: None,
})
}
}
Expand Down Expand Up @@ -376,6 +392,7 @@ impl Decryptable<KeyIds, SymmetricKeyId, CipherView> for Cipher {
creation_date: self.creation_date,
deleted_date: self.deleted_date,
revision_date: self.revision_date,
version: self.version,
};

// For compatibility we only remove URLs with invalid checksums if the cipher has a key
Expand Down Expand Up @@ -757,6 +774,8 @@ impl TryFrom<CipherDetailsResponseModel> for Cipher {
deleted_date: cipher.deleted_date.map(|d| d.parse()).transpose()?,
revision_date: require!(cipher.revision_date).parse()?,
key: EncString::try_from_optional(cipher.key)?,
version: cipher.version.map(|v| v as u32),
data: cipher.data,
})
}
}
Expand Down Expand Up @@ -831,6 +850,7 @@ mod tests {
creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
deleted_date: None,
revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
version: None,
}
}

Expand Down Expand Up @@ -895,6 +915,8 @@ mod tests {
creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
deleted_date: None,
revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
version: None,
data: None,
};

let view: CipherListView = key_store.decrypt(&cipher).unwrap();
Expand Down
Loading
Loading