Skip to main content

bitwarden_organizations/
profile.rs

1use bitwarden_core::{OrganizationId, UserId};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use crate::{
7    MemberDecryptionType, OrganizationUserId, OrganizationUserStatusType, OrganizationUserType,
8    Permissions, ProductTierType, ProviderId, ProviderType,
9};
10
11/// Organization membership details from the user's profile sync.
12///
13/// Contains the full set of entitlements, plan features, and metadata for a single
14/// organization that the current user belongs to.
15#[derive(Default, PartialEq, Serialize, Deserialize, Debug, Clone)]
16#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
17#[bitwarden_ffi::wasm_record]
18#[serde(rename_all = "camelCase")]
19pub struct ProfileOrganization {
20    /// Identity of the organization.
21    pub details: OrganizationDetails,
22    /// The organization's plan or license: tier, limits, and feature entitlements.
23    pub plan: OrganizationPlan,
24    /// The current user's relationship to the organization.
25    pub membership: OrganizationMembership,
26    /// Admin-configured collection management settings.
27    pub collection_management: CollectionManagementSettings,
28    /// The provider managing this organization, if any.
29    pub provider: Option<OrganizationProvider>,
30    /// The organization's SSO and Key Connector settings.
31    pub sso: SsoSettings,
32    /// Families sponsorship state for the current user.
33    pub family_sponsorship: FamilySponsorship,
34}
35
36bitwarden_state::register_repository_item!(OrganizationId => ProfileOrganization, "ProfileOrganization");
37
38/// Basic information about the organization.
39#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
40#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
41#[bitwarden_ffi::wasm_record]
42#[serde(rename_all = "camelCase")]
43pub struct OrganizationDetails {
44    /// Unique identifier for the organization.
45    pub id: OrganizationId,
46    /// Display name of the organization.
47    pub name: String,
48    /// Whether the organization is currently enabled.
49    pub enabled: bool,
50    /// Whether the organization has both a public and private key configured.
51    pub has_public_and_private_keys: bool,
52}
53
54impl Default for OrganizationDetails {
55    fn default() -> Self {
56        OrganizationDetails {
57            id: OrganizationId::new(Uuid::nil()),
58            name: String::new(),
59            enabled: true,
60            has_public_and_private_keys: false,
61        }
62    }
63}
64
65/// The organization's plan or license: its tier, limits, and feature entitlements.
66#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
67#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
68#[bitwarden_ffi::wasm_record]
69#[serde(rename_all = "camelCase")]
70pub struct OrganizationPlan {
71    /// The subscription tier of the organization.
72    pub product_tier_type: ProductTierType,
73    /// The number of licensed seats for the organization.
74    pub seats: Option<u32>,
75    /// The maximum number of collections the organization can create.
76    pub max_collections: Option<u32>,
77    /// The maximum encrypted storage in gigabytes, if limited.
78    pub max_storage_gb: Option<u32>,
79    /// Whether the organization can create a license file for a self-hosted instance.
80    pub self_host: bool,
81    /// Whether organization members receive premium features.
82    pub users_get_premium: bool,
83    /// Whether the organization has access to policies features.
84    pub use_policies: bool,
85    /// Whether the organization has access to groups features.
86    pub use_groups: bool,
87    /// Whether the organization has access to directory sync features.
88    pub use_directory: bool,
89    /// Whether the organization has access to event logging features.
90    pub use_events: bool,
91    /// Whether the organization can enforce TOTP for members.
92    pub use_totp: bool,
93    /// Whether the organization has access to two-factor authentication features.
94    pub use_2fa: bool,
95    /// Whether the organization has access to the Bitwarden Public API.
96    pub use_api: bool,
97    /// Whether the organization has access to SSO features.
98    pub use_sso: bool,
99    /// Whether the organization can manage verified domains.
100    pub use_organization_domains: bool,
101    /// Whether the organization can use Key Connector for decryption.
102    pub use_key_connector: bool,
103    /// Whether the organization has access to SCIM provisioning.
104    pub use_scim: bool,
105    /// Whether the organization can use the [`OrganizationUserType::Custom`] role.
106    pub use_custom_permissions: bool,
107    /// Whether the organization has access to the account recovery (admin password reset) feature.
108    pub use_reset_password: bool,
109    /// Whether the organization has access to Secrets Manager.
110    pub use_secrets_manager: bool,
111    /// Whether the organization has access to Password Manager.
112    pub use_password_manager: bool,
113    /// Whether the organization has access to Privileged Access Management features.
114    pub use_pam: bool,
115    /// Whether the organization can use the activate autofill policy.
116    pub use_activate_autofill_policy: bool,
117    /// Whether the organization can automatically confirm new members without manual admin
118    /// approval.
119    pub use_automatic_user_confirmation: bool,
120    /// Whether the organization has access to Access Intelligence features.
121    pub use_access_intelligence: bool,
122    /// Whether the organization can sponsor families plans for members (Families For Enterprises).
123    pub use_admin_sponsored_families: bool,
124    /// Whether Secrets Manager ads are disabled for users.
125    #[serde(rename = "useDisableSMAdsForUsers")]
126    pub use_disable_sm_ads_for_users: bool,
127    /// Whether the organization has access to phishing blocker features.
128    pub use_phishing_blocker: bool,
129    /// Whether the organization has access to the My Items collection feature.
130    /// This allows users to store personal items in the organization vault
131    /// if the Centralize Organization Ownership policy is enabled.
132    pub use_my_items: bool,
133    /// Whether the organization can invite members using invite links.
134    pub use_invite_links: bool,
135}
136
137impl Default for OrganizationPlan {
138    fn default() -> Self {
139        OrganizationPlan {
140            product_tier_type: ProductTierType::Free,
141            seats: Some(10),
142            max_collections: None,
143            max_storage_gb: None,
144            self_host: false,
145            users_get_premium: false,
146            use_policies: false,
147            use_groups: false,
148            use_directory: false,
149            use_events: false,
150            use_totp: false,
151            use_2fa: false,
152            use_api: false,
153            use_sso: false,
154            use_organization_domains: false,
155            use_key_connector: false,
156            use_scim: false,
157            use_custom_permissions: false,
158            use_reset_password: false,
159            use_secrets_manager: false,
160            use_password_manager: false,
161            use_pam: false,
162            use_activate_autofill_policy: false,
163            use_automatic_user_confirmation: false,
164            use_access_intelligence: false,
165            use_admin_sponsored_families: false,
166            use_disable_sm_ads_for_users: false,
167            use_phishing_blocker: false,
168            use_my_items: false,
169            use_invite_links: false,
170        }
171    }
172}
173
174/// The current user's relationship to the organization.
175#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
176#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
177#[bitwarden_ffi::wasm_record]
178#[serde(rename_all = "camelCase")]
179pub struct OrganizationMembership {
180    /// The user's membership status in the organization.
181    pub status: OrganizationUserStatusType,
182    /// The user's role in the organization.
183    pub r#type: OrganizationUserType,
184    /// The current user's custom permissions, relevant when [`OrganizationUserType::Custom`] is
185    /// the user's `type`.
186    pub permissions: Permissions,
187    /// The current user's personal user ID.
188    pub user_id: UserId,
189    /// The current user's organization membership ID.
190    /// None if the current user has access only through a provider.
191    pub organization_user_id: Option<OrganizationUserId>,
192    /// Whether the current user is a direct member of this organization (as opposed to
193    /// provider-only access).
194    pub is_member: bool,
195    /// Whether the current user accesses this organization through a provider.
196    pub is_provider_user: bool,
197    /// Whether the current user is enrolled in account recovery for this organization.
198    pub reset_password_enrolled: bool,
199    /// Whether the current user's account is linked to this organization via SSO.
200    pub sso_bound: bool,
201    /// Whether the current user's account is claimed by this organization.
202    pub user_is_claimed_by_organization: bool,
203    /// Whether the current user has access to Secrets Manager for this organization.
204    pub access_secrets_manager: bool,
205}
206
207impl Default for OrganizationMembership {
208    fn default() -> Self {
209        OrganizationMembership {
210            status: OrganizationUserStatusType::Confirmed,
211            r#type: OrganizationUserType::User,
212            permissions: Permissions::default(),
213            user_id: UserId::new(Uuid::nil()),
214            organization_user_id: None,
215            is_member: true,
216            is_provider_user: false,
217            reset_password_enrolled: false,
218            sso_bound: false,
219            user_is_claimed_by_organization: false,
220            access_secrets_manager: false,
221        }
222    }
223}
224
225/// Admin-configured settings controlling who can create, delete, and manage collections and
226/// items.
227#[derive(Default, PartialEq, Serialize, Deserialize, Debug, Clone)]
228#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
229#[bitwarden_ffi::wasm_record]
230#[serde(rename_all = "camelCase")]
231pub struct CollectionManagementSettings {
232    /// Whether collection creation is restricted to owners and admins only.
233    ///
234    /// When `false`, any member can create collections and automatically receives manage
235    /// permissions over collections they create.
236    pub limit_collection_creation: bool,
237    /// Whether collection deletion is restricted to owners and admins only.
238    ///
239    /// When `true`, regular users cannot delete collections that they manage.
240    pub limit_collection_deletion: bool,
241    /// Whether item deletion is restricted to members with the Manage collection permission.
242    ///
243    /// When `false`, members with Edit permission can also delete items within their collections.
244    pub limit_item_deletion: bool,
245    /// Whether owners and admins have implicit manage permissions over all collections.
246    ///
247    /// When `true`, owners and admins can alter items, groups, and permissions across all
248    /// collections without requiring explicit collection assignments.
249    /// When `false`, admins can only access collections where they have been explicitly assigned.
250    pub allow_admin_access_to_all_collection_items: bool,
251}
252
253/// The provider managing an organization.
254#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
255#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
256#[bitwarden_ffi::wasm_record]
257#[serde(rename_all = "camelCase")]
258pub struct OrganizationProvider {
259    /// The ID of the provider.
260    pub id: ProviderId,
261    /// The type of provider.
262    pub r#type: ProviderType,
263    /// The name of the provider.
264    /// Should never be None, but server validation is not strict enough to guarantee this.
265    pub name: Option<String>,
266}
267
268/// An organization's SSO and Key Connector settings.
269///
270/// This mirrors the server's fields rather than modelling them as an enum, because the server
271/// does not enforce the relationships between them: for example, a decryption type and Key
272/// Connector URL can be present while SSO is disabled.
273#[derive(Default, PartialEq, Serialize, Deserialize, Debug, Clone)]
274#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
275#[bitwarden_ffi::wasm_record]
276#[serde(rename_all = "camelCase")]
277pub struct SsoSettings {
278    /// Whether SSO login is currently enabled for this organization.
279    pub enabled: bool,
280    /// The organization's SSO identifier.
281    pub identifier: Option<String>,
282    /// The decryption type used for SSO members. `None` when no SSO configuration has been
283    /// saved.
284    pub member_decryption_type: Option<MemberDecryptionType>,
285    /// Whether members decrypt via Key Connector, as computed by the server.
286    pub key_connector_enabled: bool,
287    /// The URL of the Key Connector service. Only meaningful when
288    /// [`key_connector_enabled`](Self::key_connector_enabled) is `true`.
289    pub key_connector_url: Option<String>,
290}
291
292/// Families sponsorship state for the current user in an organization.
293#[derive(Default, PartialEq, Serialize, Deserialize, Debug, Clone)]
294#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
295#[bitwarden_ffi::wasm_record]
296#[serde(rename_all = "camelCase")]
297pub struct FamilySponsorship {
298    /// Whether the organization can sponsor a families plan for the current user. This can be
299    /// `true` while a sponsorship exists.
300    pub available: bool,
301    /// The friendly name of the sponsorship, usually the recipient's email address.
302    /// Should never be None, but server validation is not strict enough to guarantee this.
303    pub friendly_name: Option<String>,
304    /// The date the sponsorship expires. `None` while the sponsorship has been offered but not
305    /// yet redeemed.
306    pub valid_until: Option<DateTime<Utc>>,
307    /// The date the sponsorship was last synced between a self-hosted instance and the cloud.
308    pub last_sync_date: Option<DateTime<Utc>>,
309    /// Whether the sponsorship is scheduled for deletion.
310    pub to_delete: Option<bool>,
311    /// Whether the sponsorship was initiated by an organization admin.
312    pub is_admin_initiated: bool,
313}
314
315#[cfg(test)]
316mod tests {
317    use serde_json::json;
318
319    use super::*;
320
321    /// A record in the nested `ProfileOrganization` shape.
322    fn organization_json() -> serde_json::Value {
323        serde_json::from_str(
324            r#"{
325            "details": {
326                "id": "0b5a2d2c-6b39-4c7e-9f4a-0a1b2c3d4e5f",
327                "name": "Test Org",
328                "enabled": true,
329                "hasPublicAndPrivateKeys": true
330            },
331            "plan": {
332                "productTierType": 3,
333                "seats": 25,
334                "maxCollections": null,
335                "maxStorageGb": 5,
336                "selfHost": false,
337                "usersGetPremium": true,
338                "usePolicies": true,
339                "useGroups": true,
340                "useDirectory": false,
341                "useEvents": true,
342                "useTotp": true,
343                "use2fa": true,
344                "useApi": false,
345                "useSso": true,
346                "useOrganizationDomains": false,
347                "useKeyConnector": false,
348                "useScim": false,
349                "useCustomPermissions": true,
350                "useResetPassword": true,
351                "useSecretsManager": false,
352                "usePasswordManager": true,
353                "usePam": false,
354                "useActivateAutofillPolicy": false,
355                "useAutomaticUserConfirmation": false,
356                "useAccessIntelligence": false,
357                "useAdminSponsoredFamilies": false,
358                "useDisableSMAdsForUsers": false,
359                "usePhishingBlocker": false,
360                "useMyItems": false,
361                "useInviteLinks": true
362            },
363            "membership": {
364                "status": 2,
365                "type": 4,
366                "permissions": {
367                    "accessEventLogs": true,
368                    "accessImportExport": false,
369                    "accessReports": true,
370                    "createNewCollections": false,
371                    "editAnyCollection": false,
372                    "deleteAnyCollection": false,
373                    "manageGroups": true,
374                    "manageSso": false,
375                    "managePolicies": false,
376                    "manageUsers": true,
377                    "manageResetPassword": false,
378                    "manageScim": false
379                },
380                "userId": "1c6b3e3d-7c4a-4d8f-8a5b-1b2c3d4e5f60",
381                "organizationUserId": "2d7c4f4e-8d5b-4e9a-9b6c-2c3d4e5f6071",
382                "isMember": true,
383                "isProviderUser": false,
384                "resetPasswordEnrolled": true,
385                "ssoBound": false,
386                "userIsClaimedByOrganization": false,
387                "accessSecretsManager": false
388            },
389            "collectionManagement": {
390                "limitCollectionCreation": true,
391                "limitCollectionDeletion": true,
392                "limitItemDeletion": false,
393                "allowAdminAccessToAllCollectionItems": true
394            },
395            "provider": null,
396            "sso": {
397                "enabled": true,
398                "identifier": "test-org",
399                "memberDecryptionType": 0,
400                "keyConnectorEnabled": false,
401                "keyConnectorUrl": null
402            },
403            "familySponsorship": {
404                "available": false,
405                "friendlyName": null,
406                "validUntil": null,
407                "lastSyncDate": "2024-01-02T03:04:05Z",
408                "toDelete": null,
409                "isAdminInitiated": false
410            }
411        }"#,
412        )
413        .unwrap()
414    }
415
416    #[test]
417    fn round_trips_organization_json() {
418        let json = organization_json();
419
420        let organization: ProfileOrganization = serde_json::from_value(json.clone()).unwrap();
421        assert_eq!(
422            organization.details.id,
423            OrganizationId::new("0b5a2d2c-6b39-4c7e-9f4a-0a1b2c3d4e5f".parse().unwrap())
424        );
425        assert!(organization.plan.use_invite_links);
426        assert_eq!(organization.membership.r#type, OrganizationUserType::Custom);
427
428        assert_eq!(serde_json::to_value(&organization).unwrap(), json);
429    }
430
431    #[test]
432    fn deserializes_js_iso_date_strings() {
433        let mut json = organization_json();
434        json["familySponsorship"]["lastSyncDate"] = json!("2024-01-02T03:04:05.000Z");
435
436        let organization: ProfileOrganization = serde_json::from_value(json).unwrap();
437        assert_eq!(
438            organization.family_sponsorship.last_sync_date,
439            Some("2024-01-02T03:04:05Z".parse().unwrap())
440        );
441    }
442
443    #[test]
444    fn round_trips_provider_without_name() {
445        let mut json = organization_json();
446        json["provider"] = json!({
447            "id": "3e8d5a5f-9e6c-4fab-8c7d-3d4e5f607182",
448            "type": 2,
449            "name": null
450        });
451
452        let organization: ProfileOrganization = serde_json::from_value(json.clone()).unwrap();
453        assert_eq!(
454            organization.provider,
455            Some(OrganizationProvider {
456                id: "3e8d5a5f-9e6c-4fab-8c7d-3d4e5f607182".parse().unwrap(),
457                r#type: ProviderType::BusinessUnit,
458                name: None,
459            })
460        );
461
462        assert_eq!(serde_json::to_value(&organization).unwrap(), json);
463    }
464}