DLP and mail flow rules: inbuxa:MailRule over JMAP, and its permissions
Phase 2e of the DLP and mail flow rules spec, the API half. - inbuxa:MailRule/get and /set under urn:inbuxa:jmap. Rules convert through serde, so what a client sends is the stored format. A create or change is validated whole (Rule::validate) and refused with the property at fault; id, createdBy, createdAt and updatedAt are the server's. Every change goes through the request layer's audit record. - Six permissions, ids 674-679 (enum and schema labels): mail flow rules (sysMailRuleGet/Update), DLP rules (sysDlpPolicyGet/Update) and held mail (sysDlpReviewGet/Update, for phase 3). Either kind's permission gets through the gate; the handler shows and changes each rule only with its own kind's. All server-level: a tenant is refused (settled answer 3). - Administrators get all six; the server-level Compliance Officer gets DLP rules to see and held mail to review (settled answer 4), added once to an existing server's officer role by the grant mechanism, which gains an officer audience. - Privacy catalog entry for inbuxa:MailRule. tests/src/system/mail_rules.rs: create, list in order, validation, server-set properties refused, update, kind-separated permissions for an officer, destroy, audit records.
This commit is contained in:
@@ -46,6 +46,21 @@ const ADMIN_GRANTS: &[Permission] = &[
|
||||
Permission::SysLegalHoldUpdate,
|
||||
Permission::SysLegalHoldExport,
|
||||
Permission::SysComplianceGet,
|
||||
Permission::SysMailRuleGet,
|
||||
Permission::SysMailRuleUpdate,
|
||||
Permission::SysDlpPolicyGet,
|
||||
Permission::SysDlpPolicyUpdate,
|
||||
Permission::SysDlpReviewGet,
|
||||
Permission::SysDlpReviewUpdate,
|
||||
];
|
||||
|
||||
/// Granted to the server-level Compliance Officer role once it exists:
|
||||
/// seeing DLP rules and reviewing held mail (dlp-and-mail-flow-rules spec,
|
||||
/// §2.8, settled answer 4). A new install's role has them from the start.
|
||||
const OFFICER_GRANTS: &[Permission] = &[
|
||||
Permission::SysDlpPolicyGet,
|
||||
Permission::SysDlpReviewGet,
|
||||
Permission::SysDlpReviewUpdate,
|
||||
];
|
||||
|
||||
/// Granted to the default tenant administrator roles: reading and exporting
|
||||
@@ -65,13 +80,16 @@ const TENANT_GRANTS: &[Permission] = &[
|
||||
enum Audience {
|
||||
Admin,
|
||||
Tenant,
|
||||
Officer,
|
||||
}
|
||||
|
||||
fn granted_key(permission: Permission, audience: Audience) -> ValueClass {
|
||||
let mut key = b"Pg".to_vec();
|
||||
// Admin grants keep the key they were first recorded under
|
||||
if audience == Audience::Tenant {
|
||||
key.extend_from_slice(b"tenant:");
|
||||
match audience {
|
||||
Audience::Admin => {}
|
||||
Audience::Tenant => key.extend_from_slice(b"tenant:"),
|
||||
Audience::Officer => key.extend_from_slice(b"officer:"),
|
||||
}
|
||||
key.extend_from_slice(permission.as_str().as_bytes());
|
||||
ValueClass::Any(AnyClass {
|
||||
@@ -82,7 +100,8 @@ fn granted_key(permission: Permission, audience: Audience) -> ValueClass {
|
||||
|
||||
pub(crate) async fn grant_new_admin_permissions(bp: &mut Bootstrap) -> trc::Result<()> {
|
||||
grant(bp, Audience::Admin, ADMIN_GRANTS).await?;
|
||||
grant(bp, Audience::Tenant, TENANT_GRANTS).await
|
||||
grant(bp, Audience::Tenant, TENANT_GRANTS).await?;
|
||||
grant(bp, Audience::Officer, OFFICER_GRANTS).await
|
||||
}
|
||||
|
||||
async fn grant(bp: &mut Bootstrap, audience: Audience, grants: &[Permission]) -> trc::Result<()> {
|
||||
@@ -101,39 +120,47 @@ async fn grant(bp: &mut Bootstrap, audience: Audience, grants: &[Permission]) ->
|
||||
if pending.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
// An administrator's default roles include the plain User role, which
|
||||
// every user also holds; only roles that are the audience's alone get it
|
||||
let admin_roles: Vec<Id> = bp
|
||||
.registry
|
||||
.object::<Authentication>(Id::singleton())
|
||||
.await?
|
||||
.map(|auth| {
|
||||
let (own, shared) = match audience {
|
||||
Audience::Admin => (
|
||||
auth.default_admin_role_ids.as_slice(),
|
||||
[
|
||||
auth.default_user_role_ids.as_slice(),
|
||||
auth.default_group_role_ids.as_slice(),
|
||||
auth.default_tenant_role_ids.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
),
|
||||
Audience::Tenant => (
|
||||
auth.default_tenant_role_ids.as_slice(),
|
||||
[
|
||||
auth.default_user_role_ids.as_slice(),
|
||||
auth.default_group_role_ids.as_slice(),
|
||||
// The officer role is the one the server made, if it has made it yet: a
|
||||
// new install makes it after this, with the permissions already in it
|
||||
let admin_roles: Vec<Id> = if audience == Audience::Officer {
|
||||
super::compliance_roles::server_role(&bp.data_store)
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect()
|
||||
} else {
|
||||
// An administrator's default roles include the plain User role, which
|
||||
// every user also holds; only roles that are the audience's alone get it
|
||||
bp.registry
|
||||
.object::<Authentication>(Id::singleton())
|
||||
.await?
|
||||
.map(|auth| {
|
||||
let (own, shared) = match audience {
|
||||
Audience::Admin => (
|
||||
auth.default_admin_role_ids.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
),
|
||||
};
|
||||
own.iter()
|
||||
.filter(|id| !shared.contains(id))
|
||||
.copied()
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
[
|
||||
auth.default_user_role_ids.as_slice(),
|
||||
auth.default_group_role_ids.as_slice(),
|
||||
auth.default_tenant_role_ids.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
),
|
||||
Audience::Tenant | Audience::Officer => (
|
||||
auth.default_tenant_role_ids.as_slice(),
|
||||
[
|
||||
auth.default_user_role_ids.as_slice(),
|
||||
auth.default_group_role_ids.as_slice(),
|
||||
auth.default_admin_role_ids.as_slice(),
|
||||
]
|
||||
.concat(),
|
||||
),
|
||||
};
|
||||
own.iter()
|
||||
.filter(|id| !shared.contains(id))
|
||||
.copied()
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
};
|
||||
// Fetched by id: the registry's listing doesn't reach stored roles
|
||||
for role_id in admin_roles {
|
||||
let Some(stored) = bp
|
||||
|
||||
Reference in New Issue
Block a user