bitwarden_vault/cipher/
field.rs

1use bitwarden_api_api::models::CipherFieldModel;
2use bitwarden_core::{
3    key_management::{KeyIds, SymmetricKeyId},
4    require, MissingFieldError,
5};
6use bitwarden_crypto::{
7    CompositeEncryptable, CryptoError, Decryptable, EncString, KeyStoreContext,
8    PrimitiveEncryptable,
9};
10use serde::{Deserialize, Serialize};
11use serde_repr::{Deserialize_repr, Serialize_repr};
12#[cfg(feature = "wasm")]
13use tsify::Tsify;
14#[cfg(feature = "wasm")]
15use wasm_bindgen::prelude::wasm_bindgen;
16
17use super::linked_id::LinkedIdType;
18use crate::VaultParseError;
19
20/// Represents the type of a [FieldView].
21#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, PartialEq, Eq)]
22#[repr(u8)]
23#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
24#[cfg_attr(feature = "wasm", wasm_bindgen)]
25pub enum FieldType {
26    /// Text field
27    Text = 0,
28    /// Hidden text field
29    Hidden = 1,
30    /// Boolean field
31    Boolean = 2,
32    /// Linked field
33    Linked = 3,
34}
35
36impl TryFrom<u8> for FieldType {
37    type Error = MissingFieldError;
38
39    fn try_from(value: u8) -> Result<Self, Self::Error> {
40        match value {
41            0 => Ok(FieldType::Text),
42            1 => Ok(FieldType::Hidden),
43            2 => Ok(FieldType::Boolean),
44            3 => Ok(FieldType::Linked),
45            _ => Err(MissingFieldError("FieldType")),
46        }
47    }
48}
49
50#[derive(Serialize, Deserialize, Debug, Clone)]
51#[serde(rename_all = "camelCase", deny_unknown_fields)]
52#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
53#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
54pub struct Field {
55    name: Option<EncString>,
56    value: Option<EncString>,
57    r#type: FieldType,
58
59    linked_id: Option<LinkedIdType>,
60}
61
62#[allow(missing_docs)]
63#[derive(Serialize, Deserialize, Debug, Clone)]
64#[serde(rename_all = "camelCase", deny_unknown_fields)]
65#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
66#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
67pub struct FieldView {
68    pub name: Option<String>,
69    pub value: Option<String>,
70    pub r#type: FieldType,
71
72    pub linked_id: Option<LinkedIdType>,
73}
74
75impl CompositeEncryptable<KeyIds, SymmetricKeyId, Field> for FieldView {
76    fn encrypt_composite(
77        &self,
78        ctx: &mut KeyStoreContext<KeyIds>,
79        key: SymmetricKeyId,
80    ) -> Result<Field, CryptoError> {
81        Ok(Field {
82            name: self.name.encrypt(ctx, key)?,
83            value: self.value.encrypt(ctx, key)?,
84            r#type: self.r#type,
85            linked_id: self.linked_id,
86        })
87    }
88}
89
90impl Decryptable<KeyIds, SymmetricKeyId, FieldView> for Field {
91    fn decrypt(
92        &self,
93        ctx: &mut KeyStoreContext<KeyIds>,
94        key: SymmetricKeyId,
95    ) -> Result<FieldView, CryptoError> {
96        Ok(FieldView {
97            name: self.name.decrypt(ctx, key).ok().flatten(),
98            value: self.value.decrypt(ctx, key).ok().flatten(),
99            r#type: self.r#type,
100            linked_id: self.linked_id,
101        })
102    }
103}
104
105impl TryFrom<CipherFieldModel> for Field {
106    type Error = VaultParseError;
107
108    fn try_from(model: CipherFieldModel) -> Result<Self, Self::Error> {
109        Ok(Self {
110            name: EncString::try_from_optional(model.name)?,
111            value: EncString::try_from_optional(model.value)?,
112            r#type: require!(model.r#type).into(),
113            linked_id: model
114                .linked_id
115                .map(|id| (id as u32).try_into())
116                .transpose()?,
117        })
118    }
119}
120
121impl From<bitwarden_api_api::models::FieldType> for FieldType {
122    fn from(model: bitwarden_api_api::models::FieldType) -> Self {
123        match model {
124            bitwarden_api_api::models::FieldType::Text => FieldType::Text,
125            bitwarden_api_api::models::FieldType::Hidden => FieldType::Hidden,
126            bitwarden_api_api::models::FieldType::Boolean => FieldType::Boolean,
127            bitwarden_api_api::models::FieldType::Linked => FieldType::Linked,
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_field_type_try_from_u8_valid() {
138        assert_eq!(FieldType::try_from(0).unwrap(), FieldType::Text);
139        assert_eq!(FieldType::try_from(1).unwrap(), FieldType::Hidden);
140        assert_eq!(FieldType::try_from(2).unwrap(), FieldType::Boolean);
141        assert_eq!(FieldType::try_from(3).unwrap(), FieldType::Linked);
142    }
143
144    #[test]
145    fn test_field_type_try_from_u8_invalid() {
146        assert!(FieldType::try_from(4).is_err());
147        assert!(FieldType::try_from(255).is_err());
148    }
149}