ironoxide/group.rs
1//! Group API
2//!
3//! See [GroupOps](trait.GroupOps.html) for group functions and key terms.
4
5pub use crate::internal::group_api::{
6 GroupAccessEditErr, GroupAccessEditResult, GroupCreateResult, GroupGetResult, GroupId,
7 GroupListResult, GroupMetaResult, GroupName, GroupUpdatePrivateKeyResult,
8};
9use crate::{
10 common::SdkOperation,
11 internal::{add_optional_timeout, group_api, group_api::GroupCreateOptsStd},
12 user::UserId,
13 IronOxideErr, Result,
14};
15use futures::Future;
16use vec1::Vec1;
17
18/// Options for group creation.
19///
20/// Default values are provided with [GroupCreateOpts::default()](struct.GroupCreateOpts.html#method.default)
21#[derive(Clone, Debug, Eq, Hash, PartialEq)]
22pub struct GroupCreateOpts {
23 /// ID of the group. If `None`, the server will assign the ID.
24 id: Option<GroupId>,
25 /// Name of the group.
26 name: Option<GroupName>,
27 /// - `true` (default) - creating user will be added as an admin of the group.
28 /// - `false` - creating user will not be added as an admin of the group.
29 add_as_admin: bool,
30 /// - `true` (default) - creating user will be added to the group's membership.
31 /// - `false` - creating user will not be added to the group's membership
32 add_as_member: bool,
33 /// Specifies who the owner of this group is. Group owners have the same permissions as other admins but they cannot be removed as an administrator.
34 /// - `None` (default) - The creating user will be the owner of the group. Cannot be used if `add_as_admin` is set to false as the owner must be an admin.
35 /// - `Some` - The provided user will be the owner of the group. This ID will automatically be added to the admins list.
36 owner: Option<UserId>,
37 /// List of users to add as admins of the group. Even if `add_as_admin` is false, the calling user will be added as an admin if they are in this list.
38 admins: Vec<UserId>,
39 /// List of users to add as members of the group. Even if `add_as_member` is false, the calling user will be added as a member if they are in this list.
40 members: Vec<UserId>,
41 /// - `true` - group's private key will be marked for rotation
42 /// - `false` (default) - group's private key will not be marked for rotation
43 needs_rotation: bool,
44}
45
46impl GroupCreateOpts {
47 /// # Arguments
48 /// - `id`
49 /// - `None` (default) - The server will assign the group's ID.
50 /// - `Some` - The provided ID will be used as the group's ID.
51 /// - `name`
52 /// - `None` (default) - The group will be created with no name.
53 /// - `Some` - The provided name will be used as the group's name.
54 /// - `add_as_admin`
55 /// - `true` (default) - The creating user will be added as a group admin.
56 /// - `false` - The creating user will not be a group admin.
57 /// - `add_as_member`
58 /// - `true` (default) - The creating user will be added as a group member.
59 /// - `false` - The creating user will not be a group member.
60 /// - `owner`
61 /// - `None` (default) - The creating user will be the owner of the group.
62 /// - `Some` - The provided user will be the owner of the group. This ID will automatically be added to the admin list.
63 /// - `admins`
64 /// - The list of users to be added as group admins. This list takes priority over `add_as_admin`,
65 /// so the calling user will be added as an admin if they are in this list even if `add_as_admin` is false.
66 /// - `members`
67 /// - The list of users to be added as members of the group. This list takes priority over `add_as_member`,
68 /// so the calling user will be added as a member if they are in this list even if `add_as_member` is false.
69 /// - `needs_rotation`
70 /// - `true` - The group's private key will be marked for rotation.
71 /// - `false` (default) - The group's private key will not be marked for rotation.
72 pub fn new(
73 id: Option<GroupId>,
74 name: Option<GroupName>,
75 add_as_admin: bool,
76 add_as_member: bool,
77 owner: Option<UserId>,
78 admins: Vec<UserId>,
79 members: Vec<UserId>,
80 needs_rotation: bool,
81 ) -> GroupCreateOpts {
82 GroupCreateOpts {
83 id,
84 name,
85 add_as_admin,
86 add_as_member,
87 owner,
88 admins,
89 members,
90 needs_rotation,
91 }
92 }
93
94 fn standardize(self, calling_id: &UserId) -> Result<GroupCreateOptsStd> {
95 // if `add_as_member`, make sure the calling user is in the `members` list
96 let standardized_members = if self.add_as_member && !self.members.contains(calling_id) {
97 let mut members = self.members.clone();
98 members.push(calling_id.clone());
99 members
100 } else {
101 self.members
102 };
103 let (standardized_admins, owner_id) = {
104 // if `add_as_admin`, make sure the calling user is in the `admins` list
105 let mut admins = if self.add_as_admin && !self.admins.contains(calling_id) {
106 let mut admins = self.admins.clone();
107 admins.push(calling_id.clone());
108 admins
109 } else {
110 self.admins
111 };
112 let owner: &UserId = match &self.owner {
113 Some(owner_id) => {
114 // if the owner is specified, make sure they're in the `admins` list
115 if !admins.contains(owner_id) {
116 admins.push(owner_id.clone());
117 }
118 owner_id
119 }
120 // if the owner is the calling user (default), they should have been added to the
121 // admins list by `add_as_admin`. If they aren't, it will error later on.
122 None => calling_id,
123 };
124 (admins, owner)
125 };
126
127 let non_empty_admins = Vec1::try_from_vec(standardized_admins).map_err(|_| {
128 IronOxideErr::ValidationError(
129 "admins".to_string(),
130 "admins list cannot be empty".to_string(),
131 )
132 })?;
133
134 if !non_empty_admins.contains(owner_id) {
135 Err(IronOxideErr::ValidationError(
136 "admins".to_string(),
137 "admins list must contain the owner".to_string(),
138 ))
139 } else {
140 Ok(GroupCreateOptsStd {
141 id: self.id,
142 name: self.name,
143 owner: self.owner,
144 admins: non_empty_admins,
145 members: standardized_members,
146 needs_rotation: self.needs_rotation,
147 })
148 }
149 }
150}
151
152impl Default for GroupCreateOpts {
153 /// Default `GroupCreateOpts` for common use cases.
154 ///
155 /// The group will be assigned an ID and have an empty name. The user who calls [group_create](trait.GroupOps.html#tymethod.group_create)
156 /// will be the owner of the group as well as the only admin and member of the group. The group's private key will not be marked for rotation.
157 fn default() -> Self {
158 GroupCreateOpts::new(None, None, true, true, None, vec![], vec![], false)
159 }
160}
161
162/// IronOxide Group Operations
163///
164/// # Key Terms
165/// - ID - The ID representing a group. It must be unique within the group's segment and will **not** be encrypted.
166/// - Name - The human-readable name of a group. It does not need to be unique and will **not** be encrypted.
167/// - Member - A user who is able to encrypt and decrypt data using the group.
168/// - Admin - A user who is able to manage the group's member and admin lists. An admin cannot encrypt or decrypt data using the group
169/// unless they first add themselves as group members or are added by another admin.
170/// - Owner - The user who owns the group. The owner has the same permissions as a group admin, but is protected from being removed as
171/// a group admin.
172/// - Rotation - Changing a group's private key while leaving its public key unchanged. This can be accomplished by calling
173/// [group_rotate_private_key](trait.GroupOps.html#tymethod.group_rotate_private_key).
174
175pub trait GroupOps {
176 /// Creates a group.
177 ///
178 /// With default `GroupCreateOpts`, the group will be assigned an ID and have no name. The creating user will become the
179 /// owner of the group and the only group member and administrator.
180 ///
181 /// # Arguments
182 /// `group_create_opts` - Group creation parameters. Default values are provided with
183 /// [GroupCreateOpts::default()](struct.GroupCreateOpts.html#method.default)
184 ///
185 /// # Examples
186 /// ```
187 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
188 /// # use ironoxide::prelude::*;
189 /// # let sdk: IronOxide = unimplemented!();
190 /// # use std::convert::TryFrom;
191 /// let group_id = Some(GroupId::try_from("empl412")?);
192 /// let opts = GroupCreateOpts::new(group_id, None, true, true, None, vec![], vec![], false);
193 /// let group = sdk.group_create(&opts).await?;
194 /// # Ok(())
195 /// # }
196 /// ```
197 fn group_create(
198 &self,
199 group_create_opts: &GroupCreateOpts,
200 ) -> impl Future<Output = Result<GroupCreateResult>> + Send;
201
202 /// Gets the full metadata for a group.
203 ///
204 /// The encrypted private key for the group will not be returned.
205 ///
206 /// # Arguments
207 /// - `id` - ID of the group to retrieve
208 ///
209 /// # Examples
210 /// ```
211 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
212 /// # use ironoxide::prelude::*;
213 /// # let sdk: IronOxide = unimplemented!();
214 /// # use std::convert::TryFrom;
215 /// let group_id = GroupId::try_from("empl412")?;
216 /// let group_metadata = sdk.group_get_metadata(&group_id).await?;
217 /// # Ok(())
218 /// # }
219 /// ```
220 fn group_get_metadata(
221 &self,
222 id: &GroupId,
223 ) -> impl Future<Output = Result<GroupGetResult>> + Send;
224
225 /// Lists all of the groups that the current user is an admin or a member of.
226 ///
227 /// # Examples
228 /// ```
229 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
230 /// # use ironoxide::prelude::*;
231 /// # let sdk: IronOxide = unimplemented!();
232 /// let group_list = sdk.group_list().await?;
233 /// let groups: Vec<GroupMetaResult> = group_list.result().to_vec();
234 /// # Ok(())
235 /// # }
236 /// ```
237 fn group_list(&self) -> impl Future<Output = Result<GroupListResult>> + Send;
238
239 /// Modifies or removes a group's name.
240 ///
241 /// Returns the updated metadata of the group.
242 ///
243 /// # Arguments
244 /// - `id` - ID of the group to update
245 /// - `name` - New name for the group. Provide a `Some` to update to a new name or a `None` to clear the group's name
246 ///
247 /// # Examples
248 /// ```
249 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
250 /// # use ironoxide::prelude::*;
251 /// # let sdk: IronOxide = unimplemented!();
252 /// # use std::convert::TryFrom;
253 /// let group_id = GroupId::try_from("empl412")?;
254 /// let new_name = GroupName::try_from("HQ Employees")?;
255 /// let new_metadata = sdk.group_update_name(&group_id, Some(&new_name)).await?;
256 /// # Ok(())
257 /// # }
258 /// ```
259 fn group_update_name(
260 &self,
261 id: &GroupId,
262 name: Option<&GroupName>,
263 ) -> impl Future<Output = Result<GroupMetaResult>> + Send;
264
265 /// Rotates a group's private key while leaving its public key unchanged.
266 ///
267 /// There's no black magic here! This is accomplished via multi-party computation with the
268 /// IronCore webservice.
269 ///
270 /// Note: You must be an administrator of a group in order to rotate its private key.
271 ///
272 /// # Arguments
273 /// `id` - ID of the group whose private key should be rotated
274 ///
275 /// # Examples
276 /// ```
277 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
278 /// # use ironoxide::prelude::*;
279 /// # let sdk: IronOxide = unimplemented!();
280 /// # use std::convert::TryFrom;
281 /// let group_id = GroupId::try_from("empl412")?;
282 /// let rotate_result = sdk.group_rotate_private_key(&group_id).await?;
283 /// let new_rotation = rotate_result.needs_rotation();
284 /// # Ok(())
285 /// # }
286 /// ```
287 fn group_rotate_private_key(
288 &self,
289 id: &GroupId,
290 ) -> impl Future<Output = Result<GroupUpdatePrivateKeyResult>> + Send;
291
292 /// Adds members to a group.
293 ///
294 /// Returns successful and failed additions.
295 ///
296 /// # Arguments
297 /// - `id` - ID of the group to add members to
298 /// - `users` - List of users to add as group members
299 ///
300 /// # Examples
301 /// ```
302 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
303 /// # use ironoxide::prelude::*;
304 /// # let sdk: IronOxide = unimplemented!();
305 /// # use std::convert::TryFrom;
306 /// let group_id = GroupId::try_from("empl412")?;
307 /// let user = UserId::try_from("colt")?;
308 /// let add_result = sdk.group_add_members(&group_id, &vec![user]).await?;
309 /// let new_members: Vec<UserId> = add_result.succeeded().to_vec();
310 /// let failures: Vec<GroupAccessEditErr> = add_result.failed().to_vec();
311 /// # Ok(())
312 /// # }
313 /// ```
314 ///
315 /// # Errors
316 /// This operation supports partial success. If the request succeeds, then the resulting `GroupAccessEditResult`
317 /// will indicate which additions succeeded and which failed, and it will provide an explanation for each failure.
318 fn group_add_members(
319 &self,
320 id: &GroupId,
321 users: &[UserId],
322 ) -> impl Future<Output = Result<GroupAccessEditResult>> + Send;
323
324 /// Removes members from a group.
325 ///
326 /// Returns successful and failed removals.
327 ///
328 /// # Arguments
329 /// - `id` - ID of the group to remove members from
330 /// - `revoke_list` - List of users to remove as group members
331 ///
332 /// # Examples
333 /// ```
334 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
335 /// # use ironoxide::prelude::*;
336 /// # let sdk: IronOxide = unimplemented!();
337 /// # use std::convert::TryFrom;
338 /// let group_id = GroupId::try_from("empl412")?;
339 /// let user = UserId::try_from("colt")?;
340 /// let remove_result = sdk.group_remove_members(&group_id, &vec![user]).await?;
341 /// let removed_members: Vec<UserId> = remove_result.succeeded().to_vec();
342 /// let failures: Vec<GroupAccessEditErr> = remove_result.failed().to_vec();
343 /// # Ok(())
344 /// # }
345 /// ```
346 ///
347 /// # Errors
348 /// This operation supports partial success. If the request succeeds, then the resulting `GroupAccessEditResult`
349 /// will indicate which removals succeeded and which failed, and it will provide an explanation for each failure.
350 fn group_remove_members(
351 &self,
352 id: &GroupId,
353 revoke_list: &[UserId],
354 ) -> impl Future<Output = Result<GroupAccessEditResult>> + Send;
355
356 /// Adds administrators to a group.
357 ///
358 /// Returns successful and failed additions.
359 ///
360 /// # Arguments
361 /// - `id` - ID of the group to add administrators to
362 /// - `users` - List of users to add as group administrators
363 ///
364 /// # Examples
365 /// ```
366 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
367 /// # use ironoxide::prelude::*;
368 /// # let sdk: IronOxide = unimplemented!();
369 /// # use std::convert::TryFrom;
370 /// let group_id = GroupId::try_from("empl412")?;
371 /// let user = UserId::try_from("colt")?;
372 /// let add_result = sdk.group_add_admins(&group_id, &vec![user]).await?;
373 /// let new_admins: Vec<UserId> = add_result.succeeded().to_vec();
374 /// let failures: Vec<GroupAccessEditErr> = add_result.failed().to_vec();
375 /// # Ok(())
376 /// # }
377 /// ```
378 ///
379 /// # Errors
380 /// This operation supports partial success. If the request succeeds, then the resulting `GroupAccessEditResult`
381 /// will indicate which additions succeeded and which failed, and it will provide an explanation for each failure.
382 fn group_add_admins(
383 &self,
384 id: &GroupId,
385 users: &[UserId],
386 ) -> impl Future<Output = Result<GroupAccessEditResult>> + Send;
387
388 /// Removes administrators from a group.
389 ///
390 /// Returns successful and failed removals.
391 ///
392 /// # Arguments
393 /// - `id` - ID of the group to remove administrators from
394 /// - `revoke_list` - List of users to remove as group administrators
395 ///
396 /// # Examples
397 /// ```
398 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
399 /// # use ironoxide::prelude::*;
400 /// # let sdk: IronOxide = unimplemented!();
401 /// # use std::convert::TryFrom;
402 /// let group_id = GroupId::try_from("empl412")?;
403 /// let user = UserId::try_from("colt")?;
404 /// let remove_result = sdk.group_remove_admins(&group_id, &vec![user]).await?;
405 /// let removed_admins: Vec<UserId> = remove_result.succeeded().to_vec();
406 /// let failures: Vec<GroupAccessEditErr> = remove_result.failed().to_vec();
407 /// # Ok(())
408 /// # }
409 /// ```
410 ///
411 /// # Errors
412 /// This operation supports partial success. If the request succeeds, then the resulting `GroupAccessEditResult`
413 /// will indicate which removals succeeded and which failed, and it will provide an explanation for each failure.
414 fn group_remove_admins(
415 &self,
416 id: &GroupId,
417 revoke_list: &[UserId],
418 ) -> impl Future<Output = Result<GroupAccessEditResult>> + Send;
419
420 /// Deletes a group.
421 ///
422 /// A group can be deleted even if it has existing members and administrators.
423 ///
424 /// **Warning: Deleting a group will prevent its members from decrypting all of the
425 /// documents previously encrypted to the group. Caution should be used when deleting groups.**
426 ///
427 /// # Arguments
428 /// `id` - ID of the group to delete
429 ///
430 /// # Examples
431 /// ```
432 /// # async fn run() -> Result<(), ironoxide::IronOxideErr> {
433 /// # use ironoxide::prelude::*;
434 /// # let sdk: IronOxide = unimplemented!();
435 /// # use std::convert::TryFrom;
436 /// let group_id = GroupId::try_from("empl412")?;
437 /// let deleted_group_id = sdk.group_delete(&group_id).await?;
438 /// # Ok(())
439 /// # }
440 /// ```
441 fn group_delete(&self, id: &GroupId) -> impl Future<Output = Result<GroupId>> + Send;
442}
443
444impl GroupOps for crate::IronOxide {
445 async fn group_create(&self, opts: &GroupCreateOpts) -> Result<GroupCreateResult> {
446 let standard_opts = opts.clone().standardize(self.device.auth().account_id())?;
447 let all_users = &standard_opts.all_users();
448 let GroupCreateOptsStd {
449 id,
450 name,
451 owner,
452 admins,
453 members,
454 needs_rotation,
455 } = standard_opts;
456
457 add_optional_timeout(
458 group_api::group_create(
459 &self.recrypt,
460 self.device.auth(),
461 id,
462 name,
463 owner,
464 admins,
465 members,
466 all_users,
467 needs_rotation,
468 ),
469 self.config.sdk_operation_timeout,
470 SdkOperation::GroupCreate,
471 )
472 .await?
473 }
474
475 async fn group_get_metadata(&self, id: &GroupId) -> Result<GroupGetResult> {
476 add_optional_timeout(
477 group_api::get_metadata(self.device.auth(), id),
478 self.config.sdk_operation_timeout,
479 SdkOperation::GroupGetMetadata,
480 )
481 .await?
482 }
483
484 async fn group_list(&self) -> Result<GroupListResult> {
485 add_optional_timeout(
486 group_api::list(self.device.auth(), None),
487 self.config.sdk_operation_timeout,
488 SdkOperation::GroupList,
489 )
490 .await?
491 }
492
493 async fn group_update_name(
494 &self,
495 id: &GroupId,
496 name: Option<&GroupName>,
497 ) -> Result<GroupMetaResult> {
498 add_optional_timeout(
499 group_api::update_group_name(self.device.auth(), id, name),
500 self.config.sdk_operation_timeout,
501 SdkOperation::GroupUpdateName,
502 )
503 .await?
504 }
505
506 async fn group_rotate_private_key(&self, id: &GroupId) -> Result<GroupUpdatePrivateKeyResult> {
507 add_optional_timeout(
508 group_api::group_rotate_private_key(
509 &self.recrypt,
510 self.device().auth(),
511 id,
512 self.device().device_private_key(),
513 ),
514 self.config.sdk_operation_timeout,
515 SdkOperation::GroupRotatePrivateKey,
516 )
517 .await?
518 }
519
520 async fn group_add_members(
521 &self,
522 id: &GroupId,
523 grant_list: &[UserId],
524 ) -> Result<GroupAccessEditResult> {
525 add_optional_timeout(
526 group_api::group_add_members(
527 &self.recrypt,
528 self.device.auth(),
529 self.device.device_private_key(),
530 id,
531 grant_list,
532 ),
533 self.config.sdk_operation_timeout,
534 SdkOperation::GroupAddMembers,
535 )
536 .await?
537 }
538
539 async fn group_remove_members(
540 &self,
541 id: &GroupId,
542 revoke_list: &[UserId],
543 ) -> Result<GroupAccessEditResult> {
544 add_optional_timeout(
545 group_api::group_remove_entity(
546 self.device.auth(),
547 id,
548 revoke_list,
549 group_api::GroupEntity::Member,
550 ),
551 self.config.sdk_operation_timeout,
552 SdkOperation::GroupRemoveMembers,
553 )
554 .await?
555 }
556
557 async fn group_add_admins(
558 &self,
559 id: &GroupId,
560 users: &[UserId],
561 ) -> Result<GroupAccessEditResult> {
562 add_optional_timeout(
563 group_api::group_add_admins(
564 &self.recrypt,
565 self.device.auth(),
566 self.device.device_private_key(),
567 id,
568 users,
569 ),
570 self.config.sdk_operation_timeout,
571 SdkOperation::GroupAddAdmins,
572 )
573 .await?
574 }
575
576 async fn group_remove_admins(
577 &self,
578 id: &GroupId,
579 revoke_list: &[UserId],
580 ) -> Result<GroupAccessEditResult> {
581 add_optional_timeout(
582 group_api::group_remove_entity(
583 self.device.auth(),
584 id,
585 revoke_list,
586 group_api::GroupEntity::Admin,
587 ),
588 self.config.sdk_operation_timeout,
589 SdkOperation::GroupRemoveAdmins,
590 )
591 .await?
592 }
593
594 async fn group_delete(&self, id: &GroupId) -> Result<GroupId> {
595 add_optional_timeout(
596 group_api::group_delete(self.device.auth(), id),
597 self.config.sdk_operation_timeout,
598 SdkOperation::GroupDelete,
599 )
600 .await?
601 }
602}
603
604#[cfg(test)]
605mod tests {
606 use crate::{
607 group::GroupCreateOpts,
608 internal::{user_api::UserId, IronOxideErr},
609 };
610
611 #[test]
612 fn build_group_create_opts_default() {
613 let opts = GroupCreateOpts::default();
614 assert_eq!(None, opts.id);
615 assert_eq!(None, opts.name);
616 assert!(opts.add_as_member);
617 }
618
619 #[test]
620 fn group_create_opts_default_standardize() -> Result<(), IronOxideErr> {
621 let calling_user_id = UserId::unsafe_from_string("test_user".to_string());
622 let opts = GroupCreateOpts::default();
623 let std_opts = opts.standardize(&calling_user_id)?;
624 assert_eq!(std_opts.all_users(), [calling_user_id.clone()]);
625 assert_eq!(std_opts.owner, None);
626 assert_eq!(std_opts.admins, [calling_user_id.clone()]);
627 assert_eq!(std_opts.members, [calling_user_id]);
628 assert!(!std_opts.needs_rotation);
629 Ok(())
630 }
631
632 #[test]
633 fn group_create_opts_standardize_non_owner() -> Result<(), IronOxideErr> {
634 let calling_user_id = UserId::unsafe_from_string("test_user".to_string());
635 let owner = UserId::unsafe_from_string("owner".to_string());
636 let opts = GroupCreateOpts::new(
637 None,
638 None,
639 false,
640 false,
641 Some(owner.clone()),
642 vec![],
643 vec![],
644 true,
645 );
646 let std_opts = opts.standardize(&calling_user_id)?;
647 assert_eq!(std_opts.all_users(), [owner.clone()]);
648 assert_eq!(std_opts.owner, Some(owner.clone()));
649 assert_eq!(std_opts.admins, [owner]);
650 assert_eq!(std_opts.members, []);
651 assert!(std_opts.needs_rotation);
652 Ok(())
653 }
654
655 #[test]
656 fn group_create_opts_standardize_invalid() -> Result<(), IronOxideErr> {
657 let calling_user_id = UserId::unsafe_from_string("test_user".to_string());
658 let opts = GroupCreateOpts::new(None, None, false, true, None, vec![], vec![], false);
659 let std_opts = opts.standardize(&calling_user_id);
660 assert!(std_opts.is_err());
661 Ok(())
662 }
663}