Skip to content

Commit

Permalink
FIX(server): Make sure context is applied to traverse and write ACL
Browse files Browse the repository at this point in the history
Previously, both the traverse and write ACL would be evaluated
without taking the "in this channel" and "in sub-channels" context
options into account.
This would lead to denying traverse for channels that were
actually supposed to be traversable.

This commit refactors the ACL calculation for readability
and makes sure the write and traverse checks are actually taking the
context into account.

Fixes mumble-voip#3579
  • Loading branch information
Hartmnt committed Oct 1, 2024
1 parent dfa59b1 commit 306de66
Showing 1 changed file with 56 additions and 14 deletions.
70 changes: 56 additions & 14 deletions src/ACL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,38 +139,80 @@ QFlags< ChanACL::Perm > ChanACL::effectivePermissions(ServerUser *p, Channel *ch

bool traverse = true;
bool write = false;
ChanACL *acl;

// Iterate over all parent channels from root to the channel the user is in (inclusive)
while (!chanstack.isEmpty()) {
ch = chanstack.pop();
if (!ch->bInheritACL)
if (!ch->bInheritACL) {
granted = def;
}

foreach (acl, ch->qlACL) {
for (ChanACL *acl : ch->qlACL) {
bool matchUser = (acl->iUserId != -1) && (acl->iUserId == p->iId);
bool matchGroup = Group::appliesToUser(*chan, *ch, acl->qsGroup, *p);

bool applyFromSelf = (ch == chan && acl->bApplyHere);
bool applyFromParent = (ch != chan && acl->bApplySubs);

// Not using applyFromAny will ignore the context flags for the current ACL
bool applyFromAny = applyFromSelf || applyFromParent;

// applyFromAny will be true for ACLs set in the reference channel directly (applyHere),
// or from a parent channel which hands the ACLs down (applySubs).
// However, we have one ACL that needs to be evaluated differently - the Traverse ACL.
// Consider this channel layout:
// Root
// - A (Traverse denied for THIS channel, but not sub channels)
// - B
// - C
// If the user tries to enter C, we need to deny Traverse, because the user
// should already be blocked from traversing A. But applyFromAny will be false,
// as the "normal" ACL inheritence rules do not apply here.
// Therefore, we need applyDenyTraverse which will be true, if any channel
// from root to the reference channel denies Traverse without necessarily
// handing it down.
bool applyDenyTraverse = applyFromParent || acl->bApplyHere;

if (matchUser || matchGroup) {
if (acl->pAllow & Traverse)
// These do not grant or deny anything. We merely
// check, if we are missing traverse AND write in this
// channel and therefore abort without any permissions later on.
if (applyFromAny && (acl->pAllow & Traverse)) {
traverse = true;
if (acl->pDeny & Traverse)
}
if (applyDenyTraverse && (acl->pDeny & Traverse)) {
traverse = false;
if (acl->pAllow & Write)
}
if (applyFromAny && (acl->pAllow & Write)) {
write = true;
if (acl->pDeny & Write)
}
if (applyFromAny && (acl->pDeny & Write)) {
write = false;
if (ch->iId == 0 && chan == ch && acl->bApplyHere) {
if (acl->pAllow & Kick)
}

// These permissions are only grantable from the root channel
// as they affect the users globally. For example: You can not
// kick a client from a channel without kicking them from the server.
if (ch->iId == 0 && applyFromSelf) {
if (acl->pAllow & Kick) {
granted |= Kick;
if (acl->pAllow & Ban)
}
if (acl->pAllow & Ban) {
granted |= Ban;
if (acl->pAllow & ResetUserContent)
}
if (acl->pAllow & ResetUserContent) {
granted |= ResetUserContent;
if (acl->pAllow & Register)
}
if (acl->pAllow & Register) {
granted |= Register;
if (acl->pAllow & SelfRegister)
}
if (acl->pAllow & SelfRegister) {
granted |= SelfRegister;
}
}
if ((ch == chan && acl->bApplyHere) || (ch != chan && acl->bApplySubs)) {

// Every other regular ACL is handled here
if (applyFromAny) {
granted |= (acl->pAllow & ~(Kick | Ban | ResetUserContent | Register | SelfRegister | Cached));
granted &= ~acl->pDeny;
}
Expand Down

0 comments on commit 306de66

Please sign in to comment.