-
Notifications
You must be signed in to change notification settings - Fork 24
[PM-25821] Migrate Cipher Admin operation API calls to SDK #560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f426fba
3756f38
99d1685
0cd1aee
9f82e22
f89f43d
fe95edd
bd13498
c173c98
079cb89
f9eaafa
4e48743
f3a06f6
eb3291a
74714e6
3330a78
8307f07
6485b2e
28880f9
0356b2d
2246af5
0a558c1
7dc66a5
28aac12
a0ba6e3
f90129b
8c633b9
3e38626
7467c01
6d59590
ef3fef3
715f75c
feb335f
1f6c8ab
a1c61ec
f029f56
8cbf8c8
8aa3692
16e28b0
ca6ae3f
1cf82de
a3c29f0
2077e04
58e01df
d1d5639
43d7bbb
89e8bf5
c27f110
1aed2c3
13d9d56
bd60140
26bc2ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,10 @@ | ||
| use bitwarden_api_api::{ | ||
| apis::ciphers_api::{PutShareError, PutShareManyError}, | ||
| models::{ | ||
| CipherDetailsResponseModel, CipherRequestModel, CipherResponseModel, | ||
| CipherWithIdRequestModel, | ||
| }, | ||
| use bitwarden_api_api::models::{ | ||
| CipherDetailsResponseModel, CipherMiniDetailsResponseModel, CipherMiniResponseModel, | ||
| CipherRequestModel, CipherResponseModel, CipherWithIdRequestModel, | ||
| }; | ||
| use bitwarden_collections::collection::CollectionId; | ||
| use bitwarden_core::{ | ||
| MissingFieldError, OrganizationId, UserId, | ||
| ApiError, MissingFieldError, OrganizationId, UserId, | ||
| key_management::{KeyIds, MINIMUM_ENFORCE_ICON_URI_HASH_VERSION, SymmetricKeyId}, | ||
| require, | ||
| }; | ||
|
|
@@ -64,15 +61,19 @@ pub enum CipherError { | |
| #[error("This cipher cannot be moved to the specified organization")] | ||
| OrganizationAlreadySet, | ||
| #[error(transparent)] | ||
| PutShare(#[from] bitwarden_api_api::apis::Error<PutShareError>), | ||
| #[error(transparent)] | ||
| PutShareMany(#[from] bitwarden_api_api::apis::Error<PutShareManyError>), | ||
| #[error(transparent)] | ||
| Repository(#[from] RepositoryError), | ||
| #[error(transparent)] | ||
| Chrono(#[from] chrono::ParseError), | ||
| #[error(transparent)] | ||
| SerdeJson(#[from] serde_json::Error), | ||
| #[error(transparent)] | ||
| Api(#[from] ApiError), | ||
| } | ||
|
|
||
| impl<T> From<bitwarden_api_api::apis::Error<T>> for CipherError { | ||
| fn from(value: bitwarden_api_api::apis::Error<T>) -> Self { | ||
| Self::Api(value.into()) | ||
| } | ||
| } | ||
|
|
||
| /// Helper trait for operations on cipher types. | ||
|
|
@@ -639,6 +640,12 @@ impl Cipher { | |
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Marks the cipher as soft deleted by setting `deletion_date` to now. | ||
| pub(crate) fn soft_delete(&mut self) { | ||
nikwithak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| self.deleted_date = Some(Utc::now()); | ||
| self.archived_date = None; | ||
| } | ||
| } | ||
| impl CipherView { | ||
| #[allow(missing_docs)] | ||
|
|
@@ -973,6 +980,15 @@ impl TryFrom<CipherDetailsResponseModel> for Cipher { | |
| } | ||
| } | ||
|
|
||
| impl PartialCipher for CipherDetailsResponseModel { | ||
| fn merge_with_cipher(self, cipher: Option<Cipher>) -> Result<Cipher, VaultParseError> { | ||
| Ok(Cipher { | ||
| local_data: cipher.and_then(|c| c.local_data), | ||
| ..self.try_into()? | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl From<bitwarden_api_api::models::CipherType> for CipherType { | ||
| fn from(t: bitwarden_api_api::models::CipherType) -> Self { | ||
| match t { | ||
|
|
@@ -994,6 +1010,13 @@ impl From<bitwarden_api_api::models::CipherRepromptType> for CipherRepromptType | |
| } | ||
| } | ||
|
|
||
| /// A trait for merging partial cipher data into a full cipher. | ||
| /// Used to convert from API response models to full Cipher structs, | ||
| /// without losing local data that may not be present in the API response. | ||
| pub(crate) trait PartialCipher { | ||
| fn merge_with_cipher(self, cipher: Option<Cipher>) -> Result<Cipher, VaultParseError>; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why accept an optional cipher? The function might be nicer if cipher is required and you just do
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have some situations where we don't necessarily have the original cipher when parsing the response - the intent is to allow the functions to create a |
||
| } | ||
|
|
||
| impl From<CipherType> for bitwarden_api_api::models::CipherType { | ||
| fn from(t: CipherType) -> Self { | ||
| match t { | ||
|
|
@@ -1064,6 +1087,131 @@ impl TryFrom<CipherResponseModel> for Cipher { | |
| } | ||
| } | ||
|
|
||
| impl PartialCipher for CipherMiniResponseModel { | ||
| fn merge_with_cipher(self, cipher: Option<Cipher>) -> Result<Cipher, VaultParseError> { | ||
| let cipher = cipher.as_ref(); | ||
| Ok(Cipher { | ||
| id: self.id.map(CipherId::new), | ||
| organization_id: self.organization_id.map(OrganizationId::new), | ||
| key: EncString::try_from_optional(self.key)?, | ||
| name: require!(EncString::try_from_optional(self.name)?), | ||
| notes: EncString::try_from_optional(self.notes)?, | ||
| r#type: require!(self.r#type).into(), | ||
| login: self.login.map(|l| (*l).try_into()).transpose()?, | ||
| identity: self.identity.map(|i| (*i).try_into()).transpose()?, | ||
| card: self.card.map(|c| (*c).try_into()).transpose()?, | ||
| secure_note: self.secure_note.map(|s| (*s).try_into()).transpose()?, | ||
| ssh_key: self.ssh_key.map(|s| (*s).try_into()).transpose()?, | ||
| reprompt: self | ||
| .reprompt | ||
| .map(|r| r.into()) | ||
| .unwrap_or(CipherRepromptType::None), | ||
| organization_use_totp: self.organization_use_totp.unwrap_or(true), | ||
| attachments: self | ||
| .attachments | ||
| .map(|a| a.into_iter().map(|a| a.try_into()).collect()) | ||
| .transpose()?, | ||
| fields: self | ||
| .fields | ||
| .map(|f| f.into_iter().map(|f| f.try_into()).collect()) | ||
| .transpose()?, | ||
| password_history: self | ||
| .password_history | ||
| .map(|p| p.into_iter().map(|p| p.try_into()).collect()) | ||
| .transpose()?, | ||
| creation_date: require!(self.creation_date) | ||
| .parse() | ||
| .map_err(Into::<VaultParseError>::into)?, | ||
| deleted_date: self | ||
| .deleted_date | ||
| .map(|d| d.parse()) | ||
| .transpose() | ||
| .map_err(Into::<VaultParseError>::into)?, | ||
| revision_date: require!(self.revision_date) | ||
| .parse() | ||
| .map_err(Into::<VaultParseError>::into)?, | ||
| archived_date: self | ||
| .archived_date | ||
| .map(|d| d.parse()) | ||
| .transpose() | ||
| .map_err(Into::<VaultParseError>::into)?, | ||
| folder_id: cipher.map_or(Default::default(), |c| c.folder_id), | ||
| favorite: cipher.map_or(Default::default(), |c| c.favorite), | ||
| edit: cipher.map_or(Default::default(), |c| c.edit), | ||
| permissions: cipher.map_or(Default::default(), |c| c.permissions), | ||
| view_password: cipher.map_or(Default::default(), |c| c.view_password), | ||
| local_data: cipher.map_or(Default::default(), |c| c.local_data.clone()), | ||
| data: cipher.map_or(Default::default(), |c| c.data.clone()), | ||
| collection_ids: cipher.map_or(Default::default(), |c| c.collection_ids.clone()), | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl PartialCipher for CipherMiniDetailsResponseModel { | ||
| fn merge_with_cipher(self, cipher: Option<Cipher>) -> Result<Cipher, VaultParseError> { | ||
| let cipher = cipher.as_ref(); | ||
| Ok(Cipher { | ||
| id: self.id.map(CipherId::new), | ||
| organization_id: self.organization_id.map(OrganizationId::new), | ||
| key: EncString::try_from_optional(self.key)?, | ||
| name: require!(EncString::try_from_optional(self.name)?), | ||
| notes: EncString::try_from_optional(self.notes)?, | ||
| r#type: require!(self.r#type).into(), | ||
| login: self.login.map(|l| (*l).try_into()).transpose()?, | ||
| identity: self.identity.map(|i| (*i).try_into()).transpose()?, | ||
| card: self.card.map(|c| (*c).try_into()).transpose()?, | ||
| secure_note: self.secure_note.map(|s| (*s).try_into()).transpose()?, | ||
| ssh_key: self.ssh_key.map(|s| (*s).try_into()).transpose()?, | ||
| reprompt: self | ||
| .reprompt | ||
| .map(|r| r.into()) | ||
| .unwrap_or(CipherRepromptType::None), | ||
| organization_use_totp: self.organization_use_totp.unwrap_or(true), | ||
| attachments: self | ||
| .attachments | ||
| .map(|a| a.into_iter().map(|a| a.try_into()).collect()) | ||
| .transpose()?, | ||
| fields: self | ||
| .fields | ||
| .map(|f| f.into_iter().map(|f| f.try_into()).collect()) | ||
| .transpose()?, | ||
| password_history: self | ||
| .password_history | ||
| .map(|p| p.into_iter().map(|p| p.try_into()).collect()) | ||
| .transpose()?, | ||
| creation_date: require!(self.creation_date) | ||
| .parse() | ||
| .map_err(Into::<VaultParseError>::into)?, | ||
| deleted_date: self | ||
| .deleted_date | ||
| .map(|d| d.parse()) | ||
| .transpose() | ||
| .map_err(Into::<VaultParseError>::into)?, | ||
| revision_date: require!(self.revision_date) | ||
| .parse() | ||
| .map_err(Into::<VaultParseError>::into)?, | ||
| archived_date: self | ||
| .archived_date | ||
| .map(|d| d.parse()) | ||
| .transpose() | ||
| .map_err(Into::<VaultParseError>::into)?, | ||
| collection_ids: self | ||
| .collection_ids | ||
| .into_iter() | ||
| .flatten() | ||
| .map(CollectionId::new) | ||
| .collect(), | ||
| folder_id: cipher.map_or(Default::default(), |c| c.folder_id), | ||
| favorite: cipher.map_or(Default::default(), |c| c.favorite), | ||
| edit: cipher.map_or(Default::default(), |c| c.edit), | ||
| permissions: cipher.map_or(Default::default(), |c| c.permissions), | ||
| view_password: cipher.map_or(Default::default(), |c| c.view_password), | ||
| data: cipher.map_or(Default::default(), |c| c.data.clone()), | ||
| local_data: cipher.map_or(Default::default(), |c| c.local_data.clone()), | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The methods putting
Apierrors inCipherErrorshould really be updated to not use cipher error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only one still using CipherError is the
shareoperations, which calls existing functions that currently returnsCipherErroralready (e.g. https://github.com/bitwarden/sdk-internal/blob/vault/pm-25821/cipher-admin-ops/crates/bitwarden-vault/src/cipher/cipher_client/share_cipher.rs#L180-L184) - I think we can migrate this one to its own error type in the future.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea sounds good.