Skip to main content

bitwarden_policies/policies/
password_generator.rs

1use bitwarden_organizations::OrganizationUserType;
2use serde::{Deserialize, Serialize};
3#[cfg(feature = "wasm")]
4use tsify::Tsify;
5
6use crate::{Policy, PolicyType, policy_type::PolicyDataType};
7
8/// Password Generator policy.
9pub struct PasswordGeneratorPolicy;
10
11impl Policy for PasswordGeneratorPolicy {
12    type Data = PasswordGeneratorPolicyData;
13
14    fn policy_type(&self) -> PolicyType {
15        PolicyType::PasswordGenerator
16    }
17
18    fn to_erased(&self, data: Self::Data) -> PolicyDataType {
19        PolicyDataType::PasswordGenerator(data)
20    }
21
22    fn exempt_roles(&self) -> &[OrganizationUserType] {
23        &[]
24    }
25}
26
27/// The generator type the policy forces members to use, overriding their own
28/// preference.
29#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
30#[serde(rename_all = "camelCase")]
31#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
32#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
33pub enum PasswordGeneratorType {
34    /// Force the password generator.
35    Password,
36    /// Force the passphrase generator.
37    Passphrase,
38}
39
40/// Configuration data for the Password Generator policy. Each field, when set,
41/// enforces a minimum or a required option on the member's generator.
42#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
43#[serde(rename_all = "camelCase")]
44#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
45#[cfg_attr(
46    feature = "wasm",
47    derive(tsify::Tsify),
48    tsify(into_wasm_abi, from_wasm_abi)
49)]
50pub struct PasswordGeneratorPolicyData {
51    /// Forces the generator type; `None` leaves the choice to the member.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub override_password_type: Option<PasswordGeneratorType>,
54
55    /// Minimum password length.
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub min_length: Option<i32>,
58
59    /// Require uppercase letters.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub use_upper: Option<bool>,
62
63    /// Require lowercase letters.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub use_lower: Option<bool>,
66
67    /// Require numbers.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub use_numbers: Option<bool>,
70
71    /// Require special characters.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub use_special: Option<bool>,
74
75    /// Minimum number of numeric digits.
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub min_numbers: Option<i32>,
78
79    /// Minimum number of special characters.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub min_special: Option<i32>,
82
83    /// Minimum number of words (passphrase).
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub min_number_words: Option<i32>,
86
87    /// Require the passphrase to capitalize each word.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub capitalize: Option<bool>,
90
91    /// Require the passphrase to include a number.
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub include_number: Option<bool>,
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn round_trips_full() {
102        let data = PasswordGeneratorPolicyData {
103            override_password_type: Some(PasswordGeneratorType::Passphrase),
104            min_length: Some(14),
105            use_upper: Some(true),
106            use_lower: Some(true),
107            use_numbers: Some(true),
108            use_special: Some(false),
109            min_numbers: Some(1),
110            min_special: Some(0),
111            min_number_words: Some(4),
112            capitalize: Some(true),
113            include_number: Some(true),
114        };
115        let json = serde_json::to_string(&data).unwrap();
116        assert!(json.contains(r#""overridePasswordType":"passphrase""#));
117        assert!(json.contains("minNumberWords"));
118        assert_eq!(
119            serde_json::from_str::<PasswordGeneratorPolicyData>(&json).unwrap(),
120            data
121        );
122    }
123
124    #[test]
125    fn override_password_type_serializes_as_camel_case_string() {
126        let json = serde_json::to_string(&PasswordGeneratorPolicyData {
127            override_password_type: Some(PasswordGeneratorType::Password),
128            ..Default::default()
129        })
130        .unwrap();
131        assert_eq!(json, r#"{"overridePasswordType":"password"}"#);
132    }
133
134    #[test]
135    fn empty_serializes_to_empty_object() {
136        assert_eq!(
137            serde_json::to_string(&PasswordGeneratorPolicyData::default()).unwrap(),
138            "{}"
139        );
140    }
141}