Merge pull request 'Don't let a group's members share its calendars, address books or files' (#147) from fix/group-collections-no-onward-share into main
This commit was merged in pull request #147.
This commit is contained in:
commit
f791c78d17
12 files changed
+267
-4
No files matched your search
@@ -133,6 +133,10 @@ impl DavAclHandler for Server {
|
|||||||
{
|
{
|
||||||
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
||||||
}
|
}
|
||||||
|
// inbuxa: MA-D0: a group's members don't share what it owns on.
|
||||||
|
if access_token.is_group_member_only(account_id) {
|
||||||
|
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
||||||
|
}
|
||||||
|
|
||||||
// Validate ACEs
|
// Validate ACEs
|
||||||
let grants = self
|
let grants = self
|
||||||
@@ -565,7 +569,13 @@ impl Privileges for AccessToken {
|
|||||||
grants: &ArchivedVec<ArchivedAclGrant>,
|
grants: &ArchivedVec<ArchivedAclGrant>,
|
||||||
is_calendar: bool,
|
is_calendar: bool,
|
||||||
) -> Vec<Privilege> {
|
) -> Vec<Privilege> {
|
||||||
if self.is_member(account_id) {
|
if self.is_group_member_only(account_id) {
|
||||||
|
// inbuxa: MA-D0: everything but sharing it on.
|
||||||
|
Privilege::all(is_calendar)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|privilege| !matches!(privilege, Privilege::All | Privilege::WriteAcl))
|
||||||
|
.collect()
|
||||||
|
} else if self.is_member(account_id) {
|
||||||
Privilege::all(is_calendar)
|
Privilege::all(is_calendar)
|
||||||
} else {
|
} else {
|
||||||
current_user_privilege_set(grants.effective_acl(self))
|
current_user_privilege_set(grants.effective_acl(self))
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{api::acl::JmapRights, changes::state::JmapCacheState};
|
use crate::{api::acl::JmapRights, changes::state::JmapCacheState};
|
||||||
@@ -180,7 +182,7 @@ impl AddressBookGet for Server {
|
|||||||
address_book.acls.effective_acl(access_token),
|
address_book.acls.effective_acl(access_token),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
JmapRights::all_rights::<addressbook::AddressBook>()
|
JmapRights::owner_rights::<addressbook::AddressBook>(access_token, account_id)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,14 @@ impl AddressBookSet for Server {
|
|||||||
continue 'create;
|
continue 'create;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inbuxa: MA-D0: a group's members don't share what it owns on.
|
||||||
|
if !address_book.acls.is_empty() && access_token.is_group_member_only(account_id) {
|
||||||
|
response.not_created.append(
|
||||||
|
id,
|
||||||
|
SetError::forbidden().with_description("This belongs to a group. Only an administrator can change who has it."),
|
||||||
|
);
|
||||||
|
continue 'create;
|
||||||
|
}
|
||||||
// Validate ACLs
|
// Validate ACLs
|
||||||
if !address_book.acls.is_empty() {
|
if !address_book.acls.is_empty() {
|
||||||
if let Err(err) = self.acl_validate(account_id, &address_book.acls).await {
|
if let Err(err) = self.acl_validate(account_id, &address_book.acls).await {
|
||||||
@@ -203,6 +211,14 @@ impl AddressBookSet for Server {
|
|||||||
continue 'update;
|
continue 'update;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// inbuxa: MA-D0: a group's members don't share what it owns on.
|
||||||
|
if has_acl_changes && access_token.is_group_member_only(account_id) {
|
||||||
|
response.not_updated.append(
|
||||||
|
id,
|
||||||
|
SetError::forbidden().with_description("This belongs to a group. Only an administrator can change who has it."),
|
||||||
|
);
|
||||||
|
continue 'update;
|
||||||
|
}
|
||||||
if has_acl_changes {
|
if has_acl_changes {
|
||||||
if let Err(err) = self.acl_validate(account_id, &new_address_book.acls).await {
|
if let Err(err) = self.acl_validate(account_id, &new_address_book.acls).await {
|
||||||
response.not_updated.append(id, err.into());
|
response.not_updated.append(id, err.into());
|
||||||
|
|||||||
@@ -187,6 +187,21 @@ impl JmapRights {
|
|||||||
Value::Object(obj)
|
Value::Object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// inbuxa: MA-D0: an owner's rights, which for a group's member are
|
||||||
|
/// everything but sharing it on.
|
||||||
|
pub fn owner_rights<T: JmapSharedObject>(
|
||||||
|
access_token: &AccessToken,
|
||||||
|
account_id: u32,
|
||||||
|
) -> Value<'static, T::Property, T::Element> {
|
||||||
|
if access_token.is_group_member_only(account_id) {
|
||||||
|
let mut acl = Bitmap::<Acl>::all();
|
||||||
|
acl.remove(Acl::Share);
|
||||||
|
Self::rights::<T>(acl)
|
||||||
|
} else {
|
||||||
|
Self::all_rights::<T>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rights<T: JmapSharedObject>(
|
pub fn rights<T: JmapSharedObject>(
|
||||||
acls: Bitmap<Acl>,
|
acls: Bitmap<Acl>,
|
||||||
) -> Value<'static, T::Property, T::Element> {
|
) -> Value<'static, T::Property, T::Element> {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{api::acl::JmapRights, calendar::Availability, changes::state::JmapCacheState};
|
use crate::{api::acl::JmapRights, calendar::Availability, changes::state::JmapCacheState};
|
||||||
@@ -253,7 +255,7 @@ impl CalendarGet for Server {
|
|||||||
calendar.acls.effective_acl(access_token),
|
calendar.acls.effective_acl(access_token),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
JmapRights::all_rights::<calendar::Calendar>()
|
JmapRights::owner_rights::<calendar::Calendar>(access_token, account_id)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,14 @@ impl CalendarSet for Server {
|
|||||||
continue 'create;
|
continue 'create;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inbuxa: MA-D0: a group's members don't share what it owns on.
|
||||||
|
if !calendar.acls.is_empty() && access_token.is_group_member_only(account_id) {
|
||||||
|
response.not_created.append(
|
||||||
|
id,
|
||||||
|
SetError::forbidden().with_description("This belongs to a group. Only an administrator can change who has it."),
|
||||||
|
);
|
||||||
|
continue 'create;
|
||||||
|
}
|
||||||
// Validate ACLs
|
// Validate ACLs
|
||||||
if !calendar.acls.is_empty() {
|
if !calendar.acls.is_empty() {
|
||||||
if let Err(err) = self.acl_validate(account_id, &calendar.acls).await {
|
if let Err(err) = self.acl_validate(account_id, &calendar.acls).await {
|
||||||
@@ -207,6 +215,14 @@ impl CalendarSet for Server {
|
|||||||
continue 'update;
|
continue 'update;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// inbuxa: MA-D0: a group's members don't share what it owns on.
|
||||||
|
if has_acl_changes && access_token.is_group_member_only(account_id) {
|
||||||
|
response.not_updated.append(
|
||||||
|
id,
|
||||||
|
SetError::forbidden().with_description("This belongs to a group. Only an administrator can change who has it."),
|
||||||
|
);
|
||||||
|
continue 'update;
|
||||||
|
}
|
||||||
if has_acl_changes {
|
if has_acl_changes {
|
||||||
if let Err(err) = self.acl_validate(account_id, &new_calendar.acls).await {
|
if let Err(err) = self.acl_validate(account_id, &new_calendar.acls).await {
|
||||||
response.not_updated.append(id, err.into());
|
response.not_updated.append(id, err.into());
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{api::acl::JmapRights, changes::state::JmapCacheState};
|
use crate::{api::acl::JmapRights, changes::state::JmapCacheState};
|
||||||
@@ -172,7 +174,7 @@ impl FileNodeGet for Server {
|
|||||||
file_node.acls.effective_acl(access_token),
|
file_node.acls.effective_acl(access_token),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
JmapRights::all_rights::<file_node::FileNode>()
|
JmapRights::owner_rights::<file_node::FileNode>(access_token, account_id)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,16 @@ impl FileNodeSet for Server {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// inbuxa: MA-D0: a group's members don't share what it owns on,
|
||||||
|
// at the top of its files as anywhere else
|
||||||
|
if has_acl_changes && access_token.is_group_member_only(account_id) {
|
||||||
|
response.not_created.append(
|
||||||
|
id,
|
||||||
|
SetError::forbidden().with_description("This belongs to a group. Only an administrator can change who has it."),
|
||||||
|
);
|
||||||
|
continue 'create;
|
||||||
|
}
|
||||||
|
|
||||||
// Inherit ACLs from parent
|
// Inherit ACLs from parent
|
||||||
if file_node.parent_id > 0 {
|
if file_node.parent_id > 0 {
|
||||||
let parent_id = file_node.parent_id - 1;
|
let parent_id = file_node.parent_id - 1;
|
||||||
@@ -509,6 +519,14 @@ impl FileNodeSet for Server {
|
|||||||
continue 'update;
|
continue 'update;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// inbuxa: MA-D0: a group's members don't share what it owns on.
|
||||||
|
if has_acl_changes && access_token.is_group_member_only(account_id) {
|
||||||
|
response.not_updated.append(
|
||||||
|
id,
|
||||||
|
SetError::forbidden().with_description("This belongs to a group. Only an administrator can change who has it."),
|
||||||
|
);
|
||||||
|
continue 'update;
|
||||||
|
}
|
||||||
if has_acl_changes {
|
if has_acl_changes {
|
||||||
if let Err(err) = self.acl_validate(account_id, &new_file_node.acls).await {
|
if let Err(err) = self.acl_validate(account_id, &new_file_node.acls).await {
|
||||||
response.not_updated.append(id, err.into());
|
response.not_updated.append(id, err.into());
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{AssertResult, ImapConnection, Type, append::assert_append_message};
|
use super::{AssertResult, ImapConnection, Type, append::assert_append_message};
|
||||||
@@ -69,6 +71,15 @@ pub async fn test(
|
|||||||
.await;
|
.await;
|
||||||
imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await;
|
imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await;
|
||||||
|
|
||||||
|
// inbuxa: MA-D0: but she can't share the group's mailbox on
|
||||||
|
imap_jane
|
||||||
|
.send("SETACL \"Shared Folders/[email protected]/INBOX\" [email protected] lr")
|
||||||
|
.await;
|
||||||
|
imap_jane
|
||||||
|
.assert_read(Type::Tagged, ResponseType::No)
|
||||||
|
.await
|
||||||
|
.assert_contains("NOPERM");
|
||||||
|
|
||||||
// John should have no shared folders
|
// John should have no shared folders
|
||||||
imap_john.send("LIST \"\" \"*\"").await;
|
imap_john.send("LIST \"\" \"*\"").await;
|
||||||
imap_john
|
imap_john
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! MA-D0 (specs/multi-account.md): a group's members have its calendars,
|
||||||
|
//! address books and files, but can't share them on. Who is in a group is
|
||||||
|
//! an administrator's decision. Mailboxes are checked in `mail::acl`.
|
||||||
|
|
||||||
|
use crate::utils::{jmap::JmapUtils, server::TestServer};
|
||||||
|
use jmap_proto::request::method::MethodObject;
|
||||||
|
use registry::schema::prelude::ObjectType;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
pub async fn test(test: &TestServer) {
|
||||||
|
println!("Running group sharing tests...");
|
||||||
|
let admin = test.account("[email protected]");
|
||||||
|
let sales = test.account("[email protected]");
|
||||||
|
let bill = test.account("[email protected]");
|
||||||
|
let robert = test.account("[email protected]");
|
||||||
|
let robert_id = robert.id_string().to_string();
|
||||||
|
|
||||||
|
// Bill joins the group; Robert stays outside it
|
||||||
|
admin
|
||||||
|
.registry_update_object(
|
||||||
|
ObjectType::Account,
|
||||||
|
bill.id(),
|
||||||
|
json!({"memberGroupIds": {sales.id_string(): true}}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Each kind's own name for "may read"
|
||||||
|
for (object, read) in [
|
||||||
|
(MethodObject::Calendar, "mayReadItems"),
|
||||||
|
(MethodObject::AddressBook, "mayRead"),
|
||||||
|
(MethodObject::FileNode, "mayRead"),
|
||||||
|
] {
|
||||||
|
// Made with a share: refused
|
||||||
|
let response = bill
|
||||||
|
.jmap_create_account(
|
||||||
|
sales,
|
||||||
|
object,
|
||||||
|
[json!({
|
||||||
|
"name": "Shared on",
|
||||||
|
"shareWith": {&robert_id: {read: true}}
|
||||||
|
})],
|
||||||
|
Vec::<(&str, &str)>::new(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
response.pointer("/methodResponses/0/1/notCreated/i0/type"),
|
||||||
|
Some(&json!("forbidden")),
|
||||||
|
"MA-D0: {object} created with a share: {:?}",
|
||||||
|
response.pointer("/methodResponses/0")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Made without one: fine, and it says it can't be shared
|
||||||
|
let id = bill
|
||||||
|
.jmap_create_account(
|
||||||
|
sales,
|
||||||
|
object,
|
||||||
|
[json!({"name": "The group's"})],
|
||||||
|
Vec::<(&str, &str)>::new(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.created(0)
|
||||||
|
.id()
|
||||||
|
.to_string();
|
||||||
|
let rights = bill
|
||||||
|
.jmap_get_account(sales, object, ["myRights"], [id.as_str()])
|
||||||
|
.await
|
||||||
|
.list()[0]["myRights"]
|
||||||
|
.clone();
|
||||||
|
assert_eq!(rights["mayShare"], false, "MA-D0: {object} myRights {rights}");
|
||||||
|
assert_eq!(rights["mayDelete"], true, "MA-D0: {object} myRights {rights}");
|
||||||
|
|
||||||
|
// Shared afterwards: refused
|
||||||
|
let response = bill
|
||||||
|
.jmap_update_account(
|
||||||
|
sales,
|
||||||
|
object,
|
||||||
|
[(
|
||||||
|
&id,
|
||||||
|
json!({format!("shareWith/{robert_id}"): {read: true}}),
|
||||||
|
)],
|
||||||
|
Vec::<(&str, &str)>::new(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
response.pointer(&format!("/methodResponses/0/1/notUpdated/{id}/type")),
|
||||||
|
Some(&json!("forbidden")),
|
||||||
|
"MA-D0: {object} shared on: {:?}",
|
||||||
|
response.pointer("/methodResponses/0")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Robert still has nothing
|
||||||
|
assert_eq!(
|
||||||
|
robert
|
||||||
|
.jmap_get_account(sales, object, Vec::<&str>::new(), [id.as_str()])
|
||||||
|
.await
|
||||||
|
.method_response()
|
||||||
|
.typ(),
|
||||||
|
"forbidden",
|
||||||
|
"MA-D0: {object} reached from outside"
|
||||||
|
);
|
||||||
|
|
||||||
|
bill.jmap_destroy_account(sales, object, [id.as_str()], Vec::<(&str, &str)>::new())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reaching the group's calendars and address books made its defaults
|
||||||
|
let sales_id = sales.id_string();
|
||||||
|
bill.jmap_method_calls(json!([
|
||||||
|
["Calendar/get", {"accountId": sales_id, "ids": (), "properties": ["id"]}, "c"],
|
||||||
|
["Calendar/set", {"accountId": sales_id, "onDestroyRemoveEvents": true,
|
||||||
|
"#destroy": {"resultOf": "c", "name": "Calendar/get", "path": "/list/*/id"}}, "cd"],
|
||||||
|
["AddressBook/get", {"accountId": sales_id, "ids": (), "properties": ["id"]}, "a"],
|
||||||
|
["AddressBook/set", {"accountId": sales_id, "onDestroyRemoveContents": true,
|
||||||
|
"#destroy": {"resultOf": "a", "name": "AddressBook/get", "path": "/list/*/id"}}, "ad"]
|
||||||
|
]))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
admin
|
||||||
|
.registry_update_object(
|
||||||
|
ObjectType::Account,
|
||||||
|
bill.id(),
|
||||||
|
json!({"memberGroupIds": {sales.id_string(): false}}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::utils::server::TestServerBuilder;
|
use crate::utils::server::TestServerBuilder;
|
||||||
@@ -22,6 +24,7 @@ pub mod compliance;
|
|||||||
pub mod contacts;
|
pub mod contacts;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
|
pub mod group_share;
|
||||||
pub mod mail;
|
pub mod mail;
|
||||||
pub mod principal;
|
pub mod principal;
|
||||||
|
|
||||||
@@ -219,6 +222,7 @@ pub async fn jmap_tests() {
|
|||||||
|
|
||||||
calendar::identity::test(&test).await;
|
calendar::identity::test(&test).await;
|
||||||
calendar::acl::test(&test).await;
|
calendar::acl::test(&test).await;
|
||||||
|
group_share::test(&test).await;
|
||||||
|
|
||||||
principal::get::test(&test).await;
|
principal::get::test(&test).await;
|
||||||
principal::availability::test(&test).await;
|
principal::availability::test(&test).await;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::utils::{server::TestServer, webdav::GenerateTestDavResource};
|
use crate::utils::{server::TestServer, webdav::GenerateTestDavResource};
|
||||||
@@ -407,6 +409,40 @@ pub async fn test(test: &TestServer) {
|
|||||||
.with_status(StatusCode::NO_CONTENT);
|
.with_status(StatusCode::NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inbuxa: MA-D0: Jane, a member of the Support group, can make a folder in
|
||||||
|
// the group's account but can't share it on
|
||||||
|
let member_client = test.account("[email protected]").webdav_client();
|
||||||
|
let john_principal = format!(
|
||||||
|
"{}/john%40example.com/",
|
||||||
|
DavResourceName::Principal.base_path()
|
||||||
|
);
|
||||||
|
for resource_type in [
|
||||||
|
DavResourceName::File,
|
||||||
|
DavResourceName::Cal,
|
||||||
|
DavResourceName::Card,
|
||||||
|
] {
|
||||||
|
let group_folder = format!(
|
||||||
|
"{}/support%40example.com/group-folder/",
|
||||||
|
resource_type.base_path()
|
||||||
|
);
|
||||||
|
member_client
|
||||||
|
.request("MKCOL", &group_folder, "")
|
||||||
|
.await
|
||||||
|
.with_status(StatusCode::CREATED);
|
||||||
|
member_client
|
||||||
|
.acl(&group_folder, john_principal.as_str(), ["read"])
|
||||||
|
.await
|
||||||
|
.with_status(StatusCode::FORBIDDEN);
|
||||||
|
member_client
|
||||||
|
.request("DELETE", &group_folder, "")
|
||||||
|
.await
|
||||||
|
.with_status(StatusCode::NO_CONTENT);
|
||||||
|
}
|
||||||
|
// Reaching the group's calendars and address books made its defaults
|
||||||
|
member_client
|
||||||
|
.delete_default_containers_by_account("[email protected]")
|
||||||
|
.await;
|
||||||
|
|
||||||
sharee_client.delete_default_containers().await;
|
sharee_client.delete_default_containers().await;
|
||||||
owner_client.delete_default_containers().await;
|
owner_client.delete_default_containers().await;
|
||||||
test.assert_is_empty().await;
|
test.assert_is_empty().await;
|
||||||
|
|||||||
Reference in new issue
Block a user