Don't let a group's members share its mailboxes on #146
No files matched your search
@@ -553,6 +553,16 @@ impl AccessToken {
|
|||||||
|| self.inner.access_to.iter().any(|a| a.account_id == account_id)
|
|| self.inner.access_to.iter().any(|a| a.account_id == account_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// inbuxa: MA-D0: in the account only because it is a group this token
|
||||||
|
/// belongs to. Such a member has the group's mailbox but may not share it
|
||||||
|
/// on: who is in a group is an administrator's decision, and a share
|
||||||
|
/// would let anyone in.
|
||||||
|
pub fn is_group_member_only(&self, account_id: u32) -> bool {
|
||||||
|
self.inner.account_id != account_id
|
||||||
|
&& self.inner.member_of.contains(&account_id)
|
||||||
|
&& !self.has_permission(Permission::Impersonate)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_account_id(&self, account_id: u32) -> bool {
|
pub fn is_account_id(&self, account_id: u32) -> bool {
|
||||||
self.inner.account_id == account_id
|
self.inner.account_id == account_id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ impl<T: SessionStream> Session<T> {
|
|||||||
}
|
}
|
||||||
rights
|
rights
|
||||||
} else {
|
} else {
|
||||||
vec![
|
let mut rights = vec![
|
||||||
Rights::Read,
|
Rights::Read,
|
||||||
Rights::Lookup,
|
Rights::Lookup,
|
||||||
Rights::Insert,
|
Rights::Insert,
|
||||||
@@ -223,8 +223,12 @@ impl<T: SessionStream> Session<T> {
|
|||||||
Rights::CreateMailbox,
|
Rights::CreateMailbox,
|
||||||
Rights::DeleteMailbox,
|
Rights::DeleteMailbox,
|
||||||
Rights::Post,
|
Rights::Post,
|
||||||
Rights::Administer,
|
];
|
||||||
]
|
// inbuxa: MA-D0: a group's members don't share its mailboxes on.
|
||||||
|
if !access_token.is_group_member_only(mailbox_id.account_id) {
|
||||||
|
rights.push(Rights::Administer);
|
||||||
|
}
|
||||||
|
rights
|
||||||
};
|
};
|
||||||
|
|
||||||
trc::event!(
|
trc::event!(
|
||||||
@@ -266,10 +270,20 @@ impl<T: SessionStream> Session<T> {
|
|||||||
|
|
||||||
spawn_op!(data, {
|
spawn_op!(data, {
|
||||||
// Validate mailbox
|
// Validate mailbox
|
||||||
let (mailbox_id, current_mailbox, _) = data
|
let (mailbox_id, current_mailbox, access_token) = data
|
||||||
.get_acl_mailbox(&arguments, true)
|
.get_acl_mailbox(&arguments, true)
|
||||||
.await
|
.await
|
||||||
.imap_ctx(&arguments.tag, trc::location!())?;
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
||||||
|
|
||||||
|
// inbuxa: MA-D0: a group's members don't share its mailboxes on.
|
||||||
|
if access_token.is_group_member_only(mailbox_id.account_id) {
|
||||||
|
return Err(trc::ImapEvent::Error
|
||||||
|
.into_err()
|
||||||
|
.details("This mailbox belongs to a group. Only an administrator can change who has it.")
|
||||||
|
.code(ResponseCode::NoPerm)
|
||||||
|
.id(arguments.tag.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
let current_mailbox = current_mailbox
|
let current_mailbox = current_mailbox
|
||||||
.into_deserialized::<email::mailbox::Mailbox>()
|
.into_deserialized::<email::mailbox::Mailbox>()
|
||||||
.imap_ctx(&arguments.tag, trc::location!())?;
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
||||||
|
|||||||
@@ -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 common::{Server, auth::AccessToken, sharing::EffectiveAcl};
|
use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
|
||||||
@@ -14,6 +16,7 @@ use jmap_tools::{Map, Value};
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use store::ahash::AHashSet;
|
use store::ahash::AHashSet;
|
||||||
use types::{acl::Acl, collection::Collection, keyword::Keyword, special_use::SpecialUse};
|
use types::{acl::Acl, collection::Collection, keyword::Keyword, special_use::SpecialUse};
|
||||||
|
use utils::map::bitmap::Bitmap;
|
||||||
|
|
||||||
use crate::{api::acl::JmapRights, changes::state::JmapCacheState};
|
use crate::{api::acl::JmapRights, changes::state::JmapCacheState};
|
||||||
|
|
||||||
@@ -138,6 +141,11 @@ impl MailboxGet for Server {
|
|||||||
JmapRights::rights::<Mailbox>(
|
JmapRights::rights::<Mailbox>(
|
||||||
cached_mailbox.acls.as_slice().effective_acl(access_token),
|
cached_mailbox.acls.as_slice().effective_acl(access_token),
|
||||||
)
|
)
|
||||||
|
} else if access_token.is_group_member_only(account_id) {
|
||||||
|
// inbuxa: MA-D0: everything but sharing it on.
|
||||||
|
let mut acl = Bitmap::<Acl>::all();
|
||||||
|
acl.remove(Acl::Share);
|
||||||
|
JmapRights::rights::<Mailbox>(acl)
|
||||||
} else {
|
} else {
|
||||||
JmapRights::all_rights::<Mailbox>()
|
JmapRights::all_rights::<Mailbox>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -613,6 +613,15 @@ impl MailboxSet for Server {
|
|||||||
// Refresh ACLs
|
// Refresh ACLs
|
||||||
let current = update.map(|(_, current)| current);
|
let current = update.map(|(_, current)| current);
|
||||||
if has_acl_changes {
|
if has_acl_changes {
|
||||||
|
// inbuxa: MA-D0: a group's members don't share its mailboxes on.
|
||||||
|
if ctx.access_token.is_group_member_only(ctx.account_id) {
|
||||||
|
return Ok(Err(SetError::forbidden()
|
||||||
|
.with_property(MailboxProperty::ShareWith)
|
||||||
|
.with_description(
|
||||||
|
"This mailbox belongs to a group. Only an administrator can change who has it.",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
if !changes.acls.is_empty()
|
if !changes.acls.is_empty()
|
||||||
&& let Err(err) = self.acl_validate(ctx.account_id, &changes.acls).await
|
&& let Err(err) = self.acl_validate(ctx.account_id, &changes.acls).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;
|
use crate::utils::server::TestServer;
|
||||||
@@ -713,6 +715,35 @@ pub async fn test(test: &TestServer) {
|
|||||||
.await,
|
.await,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// inbuxa: MA-D0: a member can't share the group's mailbox on, and isn't
|
||||||
|
// told it may. Who is in a group is an administrator's decision.
|
||||||
|
assert_forbidden(
|
||||||
|
john_client
|
||||||
|
.set_default_account_id(sales.id_string())
|
||||||
|
.mailbox_update_acl(&inbox_id, bill.id_string(), [ACL::ReadItems])
|
||||||
|
.await,
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!john_client
|
||||||
|
.set_default_account_id(sales.id_string())
|
||||||
|
.mailbox_get(&inbox_id, [mailbox::Property::MyRights].into())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.my_rights()
|
||||||
|
.unwrap()
|
||||||
|
.acl_list()
|
||||||
|
.contains(&ACL::Administer)
|
||||||
|
);
|
||||||
|
bill_client.refresh_session().await.unwrap();
|
||||||
|
assert!(bill_client.session().account(sales.id_string()).is_none());
|
||||||
|
assert_forbidden(
|
||||||
|
bill_client
|
||||||
|
.set_default_account_id(sales.id_string())
|
||||||
|
.email_get(&email_id, [Property::Subject].into())
|
||||||
|
.await,
|
||||||
|
);
|
||||||
|
|
||||||
// Remove John from the sales group
|
// Remove John from the sales group
|
||||||
admin
|
admin
|
||||||
.registry_update_object(
|
.registry_update_object(
|
||||||
|
|||||||
Reference in new issue
Block a user