Skip to main content

bitwarden_emergency_access/
get_grantor_policies.rs

1use bitwarden_api_api::models::PolicyType as ApiPolicyType;
2use bitwarden_core::ApiError;
3use bitwarden_error::bitwarden_error;
4use bitwarden_policies::{Policy, PolicyParseError};
5use thiserror::Error;
6
7use crate::{EmergencyAccessClient, EmergencyAccessId};
8
9/// Errors returned when fetching the policies that apply to a grantor.
10#[bitwarden_error(flat)]
11#[derive(Debug, Error)]
12pub enum EmergencyAccessGetGrantorPoliciesError {
13    /// The request to the server failed.
14    #[error(transparent)]
15    Api(#[from] ApiError),
16    /// A policy in the server response could not be parsed.
17    #[error(transparent)]
18    PolicyParse(#[from] PolicyParseError),
19}
20
21#[bitwarden_ffi::wasm_export]
22impl EmergencyAccessClient {
23    /// Fetches the policies of the organizations the grantor owns, to enforce them on the new
24    /// master password during a takeover.
25    ///
26    /// The server only returns policies when the grantor owns an organization: other members are
27    /// removed from their organizations on takeover, so their policies don't apply.
28    ///
29    /// Called by the grantee.
30    pub async fn get_grantor_policies(
31        &self,
32        emergency_access_id: EmergencyAccessId,
33    ) -> Result<Vec<Policy>, EmergencyAccessGetGrantorPoliciesError> {
34        let response = self
35            .api_configurations
36            .api_client
37            .emergency_access_api()
38            .policies(emergency_access_id.into())
39            .await?;
40
41        // A missing list means no policies apply. Policy types unknown to this SDK can't be
42        // enforced; skip them rather than failing the whole list.
43        Ok(response
44            .data
45            .unwrap_or_default()
46            .into_iter()
47            .filter(|policy| !matches!(policy.r#type, Some(ApiPolicyType::__Unknown(_))))
48            .map(Policy::try_from)
49            .collect::<Result<_, _>>()?)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use bitwarden_api_api::models::{PolicyResponseModel, PolicyResponseModelListResponseModel};
56    use bitwarden_core::client::test_accounts::test_bitwarden_com_account_v2;
57    use bitwarden_policies::PolicyType;
58
59    use super::*;
60    use crate::{
61        EmergencyAccessClientExt,
62        test_support::{api_error, is_test_id, test_client, test_id},
63    };
64
65    const TEST_POLICY_ID: &str = "0ae2ad8e-2f1c-4d1b-9d7c-7a4c3f4b8a11";
66    const TEST_ORGANIZATION_ID: &str = "5b9e0a0c-7f3e-4b8a-9c1d-2e6f4a8b0c22";
67
68    fn master_password_policy() -> PolicyResponseModel {
69        PolicyResponseModel {
70            id: Some(TEST_POLICY_ID.parse().unwrap()),
71            organization_id: Some(TEST_ORGANIZATION_ID.parse().unwrap()),
72            r#type: Some(ApiPolicyType::MasterPassword),
73            data: Some(serde_json::json!({ "minLength": 12 })),
74            enabled: Some(true),
75            ..Default::default()
76        }
77    }
78
79    async fn policies(
80        data: Option<Vec<PolicyResponseModel>>,
81    ) -> Result<Vec<Policy>, EmergencyAccessGetGrantorPoliciesError> {
82        let response = PolicyResponseModelListResponseModel {
83            data,
84            ..Default::default()
85        };
86
87        let client = test_client(test_bitwarden_com_account_v2(), move |mock| {
88            mock.emergency_access_api
89                .expect_policies()
90                .withf(is_test_id)
91                .returning(move |_| Ok(response.clone()))
92                .once();
93        })
94        .await;
95
96        client
97            .emergency_access()
98            .get_grantor_policies(test_id())
99            .await
100    }
101
102    #[tokio::test]
103    async fn maps_policies() {
104        let result = policies(Some(vec![master_password_policy()]))
105            .await
106            .unwrap();
107
108        assert_eq!(result.len(), 1);
109        assert_eq!(result[0].id.to_string(), TEST_POLICY_ID);
110        assert_eq!(result[0].organization_id.to_string(), TEST_ORGANIZATION_ID);
111        assert_eq!(result[0].r#type, PolicyType::MasterPassword);
112        assert_eq!(result[0].data.as_deref(), Some(r#"{"minLength":12}"#));
113        assert!(result[0].enabled);
114    }
115
116    #[tokio::test]
117    async fn returns_empty_list_when_data_is_missing() {
118        assert!(policies(None).await.unwrap().is_empty());
119    }
120
121    #[tokio::test]
122    async fn maps_enabled_and_disabled_policies() {
123        // A disabled policy is still returned; the caller decides whether it applies.
124        let disabled = PolicyResponseModel {
125            enabled: Some(false),
126            ..master_password_policy()
127        };
128
129        let result = policies(Some(vec![master_password_policy(), disabled]))
130            .await
131            .unwrap();
132
133        let enabled: Vec<bool> = result.iter().map(|policy| policy.enabled).collect();
134        assert_eq!(enabled, vec![true, false]);
135    }
136
137    #[tokio::test]
138    async fn skips_unknown_policy_types() {
139        // Mirrors the server returning every policy of the grantor's organizations, including
140        // types newer than this SDK.
141        let policy = |r#type| PolicyResponseModel {
142            r#type: Some(r#type),
143            ..master_password_policy()
144        };
145
146        let result = policies(Some(vec![
147            policy(ApiPolicyType::__Unknown(998)),
148            policy(ApiPolicyType::MasterPassword),
149            policy(ApiPolicyType::TwoFactorAuthentication),
150            policy(ApiPolicyType::__Unknown(999)),
151            policy(ApiPolicyType::ResetPassword),
152        ]))
153        .await
154        .unwrap();
155
156        let types: Vec<PolicyType> = result.into_iter().map(|policy| policy.r#type).collect();
157        assert_eq!(
158            types,
159            vec![
160                PolicyType::MasterPassword,
161                PolicyType::TwoFactorAuthentication,
162                PolicyType::ResetPassword,
163            ]
164        );
165    }
166
167    #[tokio::test]
168    async fn fails_when_request_fails() {
169        let client = test_client(test_bitwarden_com_account_v2(), |mock| {
170            mock.emergency_access_api
171                .expect_policies()
172                .returning(|_| api_error())
173                .once();
174        })
175        .await;
176
177        let result = client
178            .emergency_access()
179            .get_grantor_policies(test_id())
180            .await;
181
182        assert!(matches!(
183            result,
184            Err(EmergencyAccessGetGrantorPoliciesError::Api(_))
185        ));
186    }
187}