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