bitwarden_organization_invite_link/
invite_link_client.rs1use std::sync::Arc;
2
3use bitwarden_core::{
4 Client, FromClient, OrganizationId, client::ApiConfigurations, key_management::KeySlotIds,
5};
6use bitwarden_crypto::KeyStore;
7use bitwarden_organization_crypto::invite::{Invite, InviteSecret};
8
9use crate::{
10 AcceptInviteLinkError, InviteLinkAdminClient, InviteLinkError, InviteLinkUserClient,
11 OrganizationInviteLink,
12};
13
14#[bitwarden_ffi::wasm_object]
21#[derive(FromClient)]
22pub struct InviteLinkClient {
23 pub(crate) key_store: KeyStore<KeySlotIds>,
24 pub(crate) api_configurations: Arc<ApiConfigurations>,
25}
26
27#[allow(deprecated)]
31#[bitwarden_ffi::wasm_export]
32impl InviteLinkClient {
33 pub fn admin(&self) -> InviteLinkAdminClient {
35 InviteLinkAdminClient {
36 key_store: self.key_store.clone(),
37 api_configurations: self.api_configurations.clone(),
38 }
39 }
40
41 pub fn user(&self) -> InviteLinkUserClient {
43 InviteLinkUserClient {
44 key_store: self.key_store.clone(),
45 api_configurations: self.api_configurations.clone(),
46 }
47 }
48
49 #[deprecated(note = "Use `invite_link().admin().create(...)` instead")]
51 pub async fn create_invite_link(
52 &self,
53 organization_id: OrganizationId,
54 allowed_domains: Vec<String>,
55 supports_confirmation: bool,
56 ) -> Result<OrganizationInviteLink, InviteLinkError> {
57 self.admin()
58 .create_invite_link(organization_id, allowed_domains, supports_confirmation)
59 .await
60 }
61
62 #[deprecated(note = "Use `invite_link().admin().refresh(...)` instead")]
65 pub async fn refresh_invite_link(
66 &self,
67 organization_id: OrganizationId,
68 supports_confirmation: bool,
69 ) -> Result<OrganizationInviteLink, InviteLinkError> {
70 self.admin()
71 .refresh_invite_link(organization_id, supports_confirmation)
72 .await
73 }
74
75 #[deprecated(note = "Use `invite_link().admin().update_confirmation(...)` instead")]
78 pub async fn set_invite_confirmation(
79 &self,
80 organization_id: OrganizationId,
81 invite: Invite,
82 supports_confirmation: bool,
83 ) -> Result<OrganizationInviteLink, InviteLinkError> {
84 self.admin()
85 .set_invite_confirmation(organization_id, invite, supports_confirmation)
86 .await
87 }
88
89 #[deprecated(
92 note = "Use `invite_link().admin().create(...)` or `invite_link().admin().refresh(...)`, which return an `OrganizationInviteLinkView`, instead"
93 )]
94 #[cfg_attr(feature = "wasm", wasm_bindgen(unchecked_return_type = "InviteSecret"))]
95 pub fn get_invite_secret(
96 &self,
97 organization_id: OrganizationId,
98 invite: Invite,
99 ) -> Result<InviteSecret, InviteLinkError> {
100 self.admin().get_invite_secret(organization_id, invite)
101 }
102
103 #[deprecated(note = "Use `invite_link().user().accept_and_optionally_confirm(...)` instead")]
107 pub async fn accept_and_optionally_confirm(
108 &self,
109 organization_id: OrganizationId,
110 code: String,
111 invite_secret: InviteSecret,
112 default_collection_name: String,
113 enroll_into_account_recovery: bool,
114 ) -> Result<(), AcceptInviteLinkError> {
115 self.user()
116 .accept_and_optionally_confirm(
117 organization_id,
118 code,
119 invite_secret,
120 default_collection_name,
121 enroll_into_account_recovery,
122 )
123 .await
124 }
125}
126
127pub trait InviteLinkClientExt {
129 fn invite_link(&self) -> InviteLinkClient;
131}
132
133impl InviteLinkClientExt for Client {
134 fn invite_link(&self) -> InviteLinkClient {
135 InviteLinkClient::from_client(self)
136 }
137}
138
139#[cfg(test)]
140#[allow(deprecated)]
141mod tests {
142 use std::sync::Arc;
143
144 use bitwarden_api_api::{
145 apis::ApiClient,
146 models::{OrganizationInviteLinkResponseModel, OrganizationPrivateKeyResponseModel},
147 };
148 use bitwarden_core::{
149 client::ApiConfigurations, key_management::create_test_crypto_with_user_and_org_key,
150 };
151 use bitwarden_crypto::{SymmetricCryptoKey, SymmetricKeyAlgorithm};
152
153 use super::*;
154
155 fn make_client(org_id: OrganizationId, api_client: ApiClient) -> InviteLinkClient {
156 let user_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
157 let org_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
158 let key_store = create_test_crypto_with_user_and_org_key(user_key, org_id, org_key);
159 InviteLinkClient {
160 key_store,
161 api_configurations: Arc::new(ApiConfigurations::from_api_client(api_client)),
162 }
163 }
164
165 #[tokio::test]
168 async fn create_invite_link_redirects_to_admin_client() {
169 use bitwarden_core::key_management::SymmetricKeySlotId;
170 use bitwarden_crypto::PublicKeyEncryptionAlgorithm;
171
172 let org_id = OrganizationId::new_v4();
173 let wrapped = Arc::new(std::sync::Mutex::new(None::<String>));
174 let for_mock = wrapped.clone();
175 let client = make_client(
176 org_id,
177 ApiClient::new_mocked(move |mock| {
178 mock.organizations_api
179 .expect_get_private_key()
180 .returning(move |_org| {
181 Ok(OrganizationPrivateKeyResponseModel {
182 object: None,
183 private_key: for_mock.lock().unwrap().clone(),
184 })
185 })
186 .once();
187 mock.organization_invite_links_api
188 .expect_create()
189 .returning(|org, model| {
190 let model = model.unwrap();
191 Ok(OrganizationInviteLinkResponseModel {
192 object: None,
193 id: Some(uuid::Uuid::new_v4()),
194 code: Some(uuid::Uuid::new_v4()),
195 organization_id: Some(org),
196 allowed_domains: Some(model.allowed_domains),
197 invite: Some(model.invite),
198 supports_confirmation: Some(model.supports_confirmation),
199 creation_date: Some("2024-01-01T00:00:00Z".to_string()),
200 })
201 })
202 .once();
203 }),
204 );
205
206 let wrapped_key = {
208 let mut ctx = client.key_store.context();
209 let org_key = SymmetricKeySlotId::Organization(org_id);
210 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
211 ctx.wrap_private_key(org_key, private_key)
212 .unwrap()
213 .to_string()
214 };
215 *wrapped.lock().unwrap() = Some(wrapped_key);
216
217 let link = client
218 .create_invite_link(org_id, vec!["example.com".to_string()], false)
219 .await
220 .unwrap();
221
222 assert_eq!(link.allowed_domains, vec!["example.com".to_string()]);
223 assert!(!link.supports_confirmation);
224 }
225}