Skip to content
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

Map from Requests to Domain models #298

Merged
merged 11 commits into from
Dec 12, 2023
3 changes: 3 additions & 0 deletions crates/bitwarden/src/admin_console/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod policy;

pub use policy::Policy;
70 changes: 70 additions & 0 deletions crates/bitwarden/src/admin_console/policy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::collections::HashMap;

use bitwarden_api_api::models::PolicyResponseModel;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use uuid::Uuid;

use crate::error::{Error, Result};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
pub struct Policy {
id: Uuid,
organization_id: Uuid,
r#type: PolicyType,
data: Option<HashMap<String, serde_json::Value>>,
enabled: bool,
}

#[derive(Serialize_repr, Deserialize_repr, Debug, JsonSchema)]
#[repr(u8)]
pub enum PolicyType {
Hinton marked this conversation as resolved.
Show resolved Hide resolved
TwoFactorAuthentication = 0, // Requires users to have 2fa enabled
MasterPassword = 1, // Sets minimum requirements for master password complexity
PasswordGenerator = 2, // Sets minimum requirements/default type for generated passwords/passphrases
SingleOrg = 3, // Allows users to only be apart of one organization
RequireSso = 4, // Requires users to authenticate with SSO
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends
SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends
ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow
MaximumVaultTimeout = 9, // Sets the maximum allowed vault timeout
DisablePersonalVaultExport = 10, // Disable personal vault export
ActivateAutofill = 11, // Activates autofill with page load on the browser extension
}

impl TryFrom<PolicyResponseModel> for Policy {
type Error = Error;

fn try_from(policy: PolicyResponseModel) -> Result<Self> {
Ok(Self {
id: policy.id.ok_or(Error::MissingFields)?,
organization_id: policy.organization_id.ok_or(Error::MissingFields)?,
r#type: policy.r#type.ok_or(Error::MissingFields)?.into(),
data: policy.data,
enabled: policy.enabled.ok_or(Error::MissingFields)?,
})
}
}

impl From<bitwarden_api_api::models::PolicyType> for PolicyType {
fn from(policy_type: bitwarden_api_api::models::PolicyType) -> Self {
match policy_type {
bitwarden_api_api::models::PolicyType::Variant0 => PolicyType::TwoFactorAuthentication,
bitwarden_api_api::models::PolicyType::Variant1 => PolicyType::MasterPassword,
bitwarden_api_api::models::PolicyType::Variant2 => PolicyType::PasswordGenerator,
bitwarden_api_api::models::PolicyType::Variant3 => PolicyType::SingleOrg,
bitwarden_api_api::models::PolicyType::Variant4 => PolicyType::RequireSso,
bitwarden_api_api::models::PolicyType::Variant5 => PolicyType::PersonalOwnership,
bitwarden_api_api::models::PolicyType::Variant6 => PolicyType::DisableSend,
bitwarden_api_api::models::PolicyType::Variant7 => PolicyType::SendOptions,
bitwarden_api_api::models::PolicyType::Variant8 => PolicyType::ResetPassword,
bitwarden_api_api::models::PolicyType::Variant9 => PolicyType::MaximumVaultTimeout,
bitwarden_api_api::models::PolicyType::Variant10 => {
PolicyType::DisablePersonalVaultExport
}
bitwarden_api_api::models::PolicyType::Variant11 => PolicyType::ActivateAutofill,
}
}
}
24 changes: 20 additions & 4 deletions crates/bitwarden/src/crypto/enc_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32};
use base64::Engine;
use serde::{de::Visitor, Deserialize};

use super::{KeyDecryptable, KeyEncryptable, LocateKey};
use crate::{
crypto::{decrypt_aes256_hmac, SymmetricCryptoKey},
error::{CryptoError, EncStringParseError, Error, Result},
util::BASE64_ENGINE,
};

use super::{KeyDecryptable, KeyEncryptable, LocateKey};

/// # Encrypted string primitive
///
/// [EncString] is a Bitwarden specific primitive that represents an encrypted string. They are
Expand Down Expand Up @@ -165,6 +164,12 @@ impl FromStr for EncString {
}

impl EncString {
/// Synthetic sugar for mapping `Option<String>` to `Result<Option<EncString>>`
#[cfg(feature = "internal")]
pub(crate) fn try_from_optional(s: Option<String>) -> Result<Option<EncString>, Error> {
s.map(|s| s.parse()).transpose()
}

#[cfg(feature = "mobile")]
pub(crate) fn from_buffer(buf: &[u8]) -> Result<Self> {
if buf.is_empty() {
Expand Down Expand Up @@ -397,11 +402,22 @@ impl KeyDecryptable<String> for EncString {
}
}

/// Usually we wouldn't want to expose EncStrings in the API or the schemas.
/// But during the transition phase we will expose endpoints using the EncString type.
impl schemars::JsonSchema for crate::crypto::EncString {
fn schema_name() -> String {
"EncString".to_string()
}

fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
gen.subschema_for::<String>()
}
}

#[cfg(test)]
mod tests {
use crate::crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey};

use super::EncString;
use crate::crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey};

#[test]
fn test_enc_string_roundtrip() {
Expand Down
4 changes: 3 additions & 1 deletion crates/bitwarden/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
#[cfg(feature = "mobile")]
uniffi::setup_scaffolding!();

#[cfg(feature = "internal")]
pub mod admin_console;
pub mod auth;
pub mod client;
pub mod crypto;
Expand All @@ -66,7 +68,7 @@ pub mod tool;
#[cfg(feature = "mobile")]
pub(crate) mod uniffi_support;
mod util;
#[cfg(feature = "mobile")]
#[cfg(feature = "internal")]
pub mod vault;
pub mod wordlist;

Expand Down
12 changes: 0 additions & 12 deletions crates/bitwarden/src/mobile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,3 @@ mod client_kdf;

pub use client_crypto::ClientCrypto;
pub use client_kdf::ClientKdf;

// Usually we wouldn't want to expose EncStrings in the API or the schemas,
// but we need them in the mobile API, so define it here to limit the scope
impl schemars::JsonSchema for crate::crypto::EncString {
fn schema_name() -> String {
"EncString".to_string()
}

fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
gen.subschema_for::<String>()
}
}
23 changes: 23 additions & 0 deletions crates/bitwarden/src/platform/domain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::error::{Error, Result};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
pub struct GlobalDomains {
pub r#type: i32,
pub domains: Vec<String>,
pub excluded: bool,
}

impl TryFrom<bitwarden_api_api::models::GlobalDomains> for GlobalDomains {
type Error = Error;

fn try_from(global_domains: bitwarden_api_api::models::GlobalDomains) -> Result<Self> {
Ok(Self {
r#type: global_domains.r#type.ok_or(Error::MissingFields)?,
domains: global_domains.domains.ok_or(Error::MissingFields)?,
excluded: global_domains.excluded.ok_or(Error::MissingFields)?,
})
}
}
1 change: 1 addition & 0 deletions crates/bitwarden/src/platform/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod domain;
mod generate_fingerprint;
mod get_user_api_key;
mod secret_verification_request;
Expand Down
60 changes: 45 additions & 15 deletions crates/bitwarden/src/platform/sync.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use bitwarden_api_api::models::{
CipherDetailsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel,
SyncResponseModel,
DomainsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel, SyncResponseModel,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::domain::GlobalDomains;
use crate::{
admin_console::Policy,
client::{encryption_settings::EncryptionSettings, Client},
error::{Error, Result},
vault::{Cipher, Collection, Folder},
};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
Expand Down Expand Up @@ -59,15 +61,23 @@ pub struct ProfileOrganizationResponse {

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct CipherDetailsResponse {}
pub struct DomainResponse {
pub equivalent_domains: Vec<Vec<String>>,
pub global_equivalent_domains: Vec<GlobalDomains>,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SyncResponse {
/// Data about the user, including their encryption keys and the organizations they are a part of
pub profile: ProfileResponse,
/// List of ciphers accesible by the user
pub ciphers: Vec<CipherDetailsResponse>,
pub folders: Vec<Folder>,
pub collections: Vec<Collection>,
/// List of ciphers accessible by the user
pub ciphers: Vec<Cipher>,
pub domains: Option<DomainResponse>,
pub policies: Vec<Policy>,
pub sends: Vec<crate::vault::Send>,
}

impl SyncResponse {
Expand All @@ -78,22 +88,27 @@ impl SyncResponse {
let profile = *response.profile.ok_or(Error::MissingFields)?;
let ciphers = response.ciphers.ok_or(Error::MissingFields)?;

fn try_into_iter<In, InItem, Out, OutItem>(iter: In) -> Result<Out, InItem::Error>
where
In: IntoIterator<Item = InItem>,
InItem: TryInto<OutItem>,
Out: FromIterator<OutItem>,
{
iter.into_iter().map(|i| i.try_into()).collect()
}

Ok(SyncResponse {
profile: ProfileResponse::process_response(profile, enc)?,
ciphers: ciphers
.into_iter()
.map(CipherDetailsResponse::process_response)
.collect::<Result<_, _>>()?,
folders: try_into_iter(response.folders.ok_or(Error::MissingFields)?)?,
collections: try_into_iter(response.collections.ok_or(Error::MissingFields)?)?,
ciphers: try_into_iter(ciphers)?,
domains: response.domains.map(|d| (*d).try_into()).transpose()?,
policies: try_into_iter(response.policies.ok_or(Error::MissingFields)?)?,
sends: try_into_iter(response.sends.ok_or(Error::MissingFields)?)?,
})
}
}

impl CipherDetailsResponse {
fn process_response(_response: CipherDetailsResponseModel) -> Result<CipherDetailsResponse> {
Ok(CipherDetailsResponse {})
}
}

impl ProfileOrganizationResponse {
fn process_response(
response: ProfileOrganizationResponseModel,
Expand Down Expand Up @@ -124,3 +139,18 @@ impl ProfileResponse {
})
}
}

impl TryFrom<DomainsResponseModel> for DomainResponse {
type Error = Error;
fn try_from(value: DomainsResponseModel) -> Result<Self> {
Ok(Self {
equivalent_domains: value.equivalent_domains.unwrap_or_default(),
global_equivalent_domains: value
.global_equivalent_domains
.unwrap_or_default()
.into_iter()
.map(|s| s.try_into())
.collect::<Result<Vec<GlobalDomains>>>()?,
})
}
}
21 changes: 18 additions & 3 deletions crates/bitwarden/src/vault/cipher/attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};

use crate::{
crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey},
error::Result,
error::{Error, Result},
};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
Expand All @@ -28,7 +28,7 @@ pub struct AttachmentView {
pub size: Option<String>,
pub size_name: Option<String>,
pub file_name: Option<String>,
pub key: Option<String>,
pub key: Option<Vec<u8>>, // TODO: Should be made into SymmetricCryptoKey
}

impl KeyEncryptable<Attachment> for AttachmentView {
Expand All @@ -39,7 +39,7 @@ impl KeyEncryptable<Attachment> for AttachmentView {
size: self.size,
size_name: self.size_name,
file_name: self.file_name.encrypt_with_key(key)?,
key: self.key.encrypt_with_key(key)?,
key: self.key.as_deref().encrypt_with_key(key)?,
})
}
}
Expand All @@ -56,3 +56,18 @@ impl KeyDecryptable<AttachmentView> for Attachment {
})
}
}

impl TryFrom<bitwarden_api_api::models::AttachmentResponseModel> for Attachment {
type Error = Error;

fn try_from(attachment: bitwarden_api_api::models::AttachmentResponseModel) -> Result<Self> {
Ok(Self {
id: attachment.id,
url: attachment.url,
size: attachment.size,
size_name: attachment.size_name,
file_name: EncString::try_from_optional(attachment.file_name)?,
key: EncString::try_from_optional(attachment.key)?,
})
}
}
18 changes: 17 additions & 1 deletion crates/bitwarden/src/vault/cipher/card.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use bitwarden_api_api::models::CipherCardModel;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::{
crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey},
error::Result,
error::{Error, Result},
};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
Expand Down Expand Up @@ -55,3 +56,18 @@ impl KeyDecryptable<CardView> for Card {
})
}
}

impl TryFrom<CipherCardModel> for Card {
type Error = Error;

fn try_from(card: CipherCardModel) -> Result<Self> {
Ok(Self {
cardholder_name: EncString::try_from_optional(card.cardholder_name)?,
exp_month: EncString::try_from_optional(card.exp_month)?,
exp_year: EncString::try_from_optional(card.exp_year)?,
code: EncString::try_from_optional(card.code)?,
brand: EncString::try_from_optional(card.brand)?,
number: EncString::try_from_optional(card.number)?,
})
}
}
Loading