bitwarden_policies/
policy.rs1use bitwarden_core::{MissingFieldError, OrganizationId, require};
4use bitwarden_uuid::uuid_newtype;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8use crate::policy_type::PolicyType;
9
10uuid_newtype!(pub PolicyId);
11
12#[derive(Serialize, Deserialize, Debug, Clone)]
17#[serde(rename_all = "camelCase")]
18#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
19#[bitwarden_ffi::wasm_record]
20pub struct Policy {
21 pub id: PolicyId,
23 pub organization_id: OrganizationId,
25 pub r#type: PolicyType,
27 pub data: Option<String>,
29 pub enabled: bool,
31 pub revision_date: Option<DateTime<Utc>>,
33}
34
35bitwarden_state::register_repository_item!(PolicyId => Policy, "Policy");
36
37#[derive(Debug, thiserror::Error)]
39pub enum PolicyParseError {
40 #[error(transparent)]
42 MissingField(#[from] MissingFieldError),
43 #[error("Unknown policy type: {0}")]
45 UnknownPolicyType(i64),
46 #[error(transparent)]
48 InvalidRevisionDate(#[from] chrono::ParseError),
49}
50
51impl TryFrom<bitwarden_api_api::models::PolicyResponseModel> for Policy {
52 type Error = PolicyParseError;
53
54 fn try_from(
55 policy: bitwarden_api_api::models::PolicyResponseModel,
56 ) -> Result<Self, Self::Error> {
57 Ok(Policy {
58 id: PolicyId::new(require!(policy.id)),
59 organization_id: OrganizationId::new(require!(policy.organization_id)),
60 r#type: require!(policy.r#type).try_into()?,
61 data: policy.data.map(|d| d.to_string()),
62 enabled: require!(policy.enabled),
63 revision_date: policy.revision_date.map(|d| d.parse()).transpose()?,
64 })
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use bitwarden_api_api::models::{PolicyResponseModel, PolicyType as ApiPolicyType};
71
72 use super::*;
73
74 const TEST_POLICY_ID: &str = "0ae2ad8e-2f1c-4d1b-9d7c-7a4c3f4b8a11";
75 const TEST_ORGANIZATION_ID: &str = "5b9e0a0c-7f3e-4b8a-9c1d-2e6f4a8b0c22";
76
77 fn response() -> PolicyResponseModel {
78 PolicyResponseModel {
79 object: None,
80 id: Some(TEST_POLICY_ID.parse().unwrap()),
81 organization_id: Some(TEST_ORGANIZATION_ID.parse().unwrap()),
82 r#type: Some(ApiPolicyType::MasterPassword),
83 data: Some(serde_json::json!({ "minLength": 12 })),
84 enabled: Some(true),
85 revision_date: Some("2024-01-01T00:00:00Z".to_owned()),
86 }
87 }
88
89 #[test]
90 fn converts_policy_response() {
91 let policy = Policy::try_from(response()).unwrap();
92
93 assert_eq!(policy.id.to_string(), TEST_POLICY_ID);
94 assert_eq!(policy.organization_id.to_string(), TEST_ORGANIZATION_ID);
95 assert_eq!(policy.r#type, PolicyType::MasterPassword);
96 assert_eq!(policy.data.as_deref(), Some(r#"{"minLength":12}"#));
97 assert!(policy.enabled);
98 assert_eq!(
99 policy.revision_date.unwrap().to_rfc3339(),
100 "2024-01-01T00:00:00+00:00"
101 );
102 }
103
104 #[test]
105 fn rejects_unknown_policy_type() {
106 let response = PolicyResponseModel {
107 r#type: Some(ApiPolicyType::__Unknown(999)),
108 ..response()
109 };
110
111 assert!(Policy::try_from(response).is_err());
112 }
113
114 #[test]
115 fn rejects_malformed_revision_date() {
116 let response = PolicyResponseModel {
117 revision_date: Some("yesterday".to_owned()),
118 ..response()
119 };
120
121 assert!(Policy::try_from(response).is_err());
122 }
123}