351 lines
11 KiB
C++
351 lines
11 KiB
C++
// Copyright 2007-2023 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "ACL.h"
|
|
#include "Channel.h"
|
|
#include "Group.h"
|
|
#include "User.h"
|
|
|
|
#ifdef MURMUR
|
|
# include "ServerUser.h"
|
|
|
|
# include <QtCore/QStack>
|
|
#endif
|
|
|
|
ChanACL::ChanACL(Channel *chan) : QObject(chan) {
|
|
bApplyHere = true;
|
|
bApplySubs = true;
|
|
bInherited = false;
|
|
|
|
iUserId = -1;
|
|
|
|
c = chan;
|
|
if (c)
|
|
c->qlACL << this;
|
|
}
|
|
|
|
ChanACL::operator QString() const {
|
|
QString aclString;
|
|
bool isFirstEntry = true;
|
|
// Iterate over all flags and assume none of the important ones (all other than Cached and All)
|
|
// have a value > 2^32
|
|
for (int i = 0; i < 32; i++) {
|
|
int currentPermInt = 1 << i;
|
|
// If we have a name for the permission, we know it exists.
|
|
// Note that we won't reach 0 with this tactic but we don't care about the None
|
|
// permission anyways.
|
|
QString name = permName(static_cast< Perm >(currentPermInt));
|
|
|
|
if (!name.isEmpty()) {
|
|
if (!((pAllow & currentPermInt) || (pDeny & currentPermInt))) {
|
|
// The current permission is left unchanged by this ACL -> Don't include it in the
|
|
// string representation
|
|
continue;
|
|
}
|
|
|
|
if (!isFirstEntry) {
|
|
aclString += QLatin1String(", ");
|
|
}
|
|
|
|
isFirstEntry = false;
|
|
|
|
aclString += QString::fromLatin1("\"%1\": ").arg(name);
|
|
|
|
if ((pAllow & currentPermInt) && (pDeny & currentPermInt)) {
|
|
// The permissions is allowed and denied. In the current implementation this should
|
|
// result in i tbeing denied, but for printing purposes we don't make assumptions about
|
|
// the handling of this in the implementation.
|
|
aclString += QLatin1String("Allowed && Denied");
|
|
} else {
|
|
if (pAllow & currentPermInt) {
|
|
aclString += QLatin1String("Allowed");
|
|
} else {
|
|
aclString += QLatin1String("Denied");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!aclString.isEmpty()) {
|
|
// Also include info about affected user and/or group
|
|
if (!qsGroup.isEmpty() && iUserId >= 0) {
|
|
// both group and user-id are set
|
|
return QString::fromLatin1("ACL for group \"%1\" and user with ID %2: %3")
|
|
.arg(qsGroup)
|
|
.arg(iUserId)
|
|
.arg(aclString);
|
|
} else if (!qsGroup.isEmpty()) {
|
|
return QString::fromLatin1("ACL for group \"%1\": %2").arg(qsGroup).arg(aclString);
|
|
} else if (iUserId >= 0) {
|
|
return QString::fromLatin1("ACL for user with ID %1: %2").arg(iUserId).arg(qsGroup);
|
|
}
|
|
}
|
|
|
|
return aclString;
|
|
}
|
|
|
|
// Check permissions.
|
|
// This will always return true for the superuser,
|
|
// and will return false if a user isn't allowed to
|
|
// traverse to the channel. (Need "read" in all preceding channels)
|
|
|
|
#ifdef MURMUR
|
|
|
|
bool ChanACL::hasPermission(ServerUser *p, Channel *chan, QFlags< Perm > perm, ACLCache *cache) {
|
|
Permissions granted = effectivePermissions(p, chan, cache);
|
|
|
|
return ((granted & perm) != None);
|
|
}
|
|
|
|
// Return effective permissions.
|
|
QFlags< ChanACL::Perm > ChanACL::effectivePermissions(ServerUser *p, Channel *chan, ACLCache *cache) {
|
|
// Superuser
|
|
if (p->iId == 0) {
|
|
return static_cast< Permissions >(All & ~(Speak | Whisper));
|
|
}
|
|
|
|
# if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
// Qt 5.15 introduced a default constructor that initializes the flags to be set to no flags
|
|
Permissions granted;
|
|
# else
|
|
// Before Qt 5.15 we have emulate the default constructor by assigning a literal zero
|
|
Permissions granted = 0;
|
|
# endif
|
|
|
|
if (cache) {
|
|
QHash< Channel *, Permissions > *h = cache->value(p);
|
|
if (h)
|
|
granted = h->value(chan);
|
|
}
|
|
|
|
if (granted & Cached) {
|
|
return granted;
|
|
}
|
|
|
|
QStack< Channel * > chanstack;
|
|
Channel *ch = chan;
|
|
|
|
while (ch) {
|
|
chanstack.push(ch);
|
|
ch = ch->cParent;
|
|
}
|
|
|
|
// Default permissions
|
|
Permissions def = Traverse | Enter | Speak | Whisper | TextMessage | Listen;
|
|
|
|
granted = def;
|
|
|
|
bool traverse = true;
|
|
bool write = false;
|
|
ChanACL *acl;
|
|
|
|
while (!chanstack.isEmpty()) {
|
|
ch = chanstack.pop();
|
|
if (!ch->bInheritACL)
|
|
granted = def;
|
|
|
|
foreach (acl, ch->qlACL) {
|
|
bool matchUser = (acl->iUserId != -1) && (acl->iUserId == p->iId);
|
|
bool matchGroup = Group::appliesToUser(*chan, *ch, acl->qsGroup, *p);
|
|
if (matchUser || matchGroup) {
|
|
if (acl->pAllow & Traverse)
|
|
traverse = true;
|
|
if (acl->pDeny & Traverse)
|
|
traverse = false;
|
|
if (acl->pAllow & Write)
|
|
write = true;
|
|
if (acl->pDeny & Write)
|
|
write = false;
|
|
if (ch->iId == 0 && chan == ch && acl->bApplyHere) {
|
|
if (acl->pAllow & Kick)
|
|
granted |= Kick;
|
|
if (acl->pAllow & Ban)
|
|
granted |= Ban;
|
|
if (acl->pAllow & ResetUserContent)
|
|
granted |= ResetUserContent;
|
|
if (acl->pAllow & Register)
|
|
granted |= Register;
|
|
if (acl->pAllow & SelfRegister)
|
|
granted |= SelfRegister;
|
|
}
|
|
if ((ch == chan && acl->bApplyHere) || (ch != chan && acl->bApplySubs)) {
|
|
granted |= (acl->pAllow & ~(Kick | Ban | ResetUserContent | Register | SelfRegister | Cached));
|
|
granted &= ~acl->pDeny;
|
|
}
|
|
}
|
|
}
|
|
if (!traverse && !write) {
|
|
granted = None;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (granted & Write) {
|
|
granted |=
|
|
Traverse | Enter | MuteDeafen | Move | MakeChannel | LinkChannel | TextMessage | MakeTempChannel | Listen;
|
|
if (chan->iId == 0)
|
|
granted |= Kick | Ban | ResetUserContent | Register | SelfRegister;
|
|
}
|
|
|
|
if (cache) {
|
|
if (!cache->contains(p))
|
|
cache->insert(p, new QHash< Channel *, Permissions >);
|
|
|
|
cache->value(p)->insert(chan, granted | Cached);
|
|
}
|
|
|
|
return granted;
|
|
}
|
|
|
|
#else
|
|
|
|
QString ChanACL::whatsThis(Perm p) {
|
|
switch (p) {
|
|
case None:
|
|
return tr("This represents no privileges.");
|
|
case Write:
|
|
return tr("This represents total access to the channel, including the ability to change group and ACL "
|
|
"information. "
|
|
"This privilege implies all other privileges.");
|
|
case Traverse:
|
|
return tr(
|
|
"This represents the permission to traverse the channel. If a user is denied this privilege, he will "
|
|
"be "
|
|
"unable to access this channel and any sub-channels in any way, regardless of other permissions in the "
|
|
"sub-channels.");
|
|
case Enter:
|
|
return tr(
|
|
"This represents the permission to join the channel. If you have a hierarchical channel structure, you "
|
|
"might want to give everyone Traverse, but restrict Enter in the root of your hierarchy.");
|
|
case Speak:
|
|
return tr(
|
|
"This represents the permission to speak in a channel. Users without this privilege will be suppressed "
|
|
"by "
|
|
"the server (seen as muted), and will be unable to speak until they are unmuted by someone with the "
|
|
"appropriate privileges.");
|
|
case Whisper:
|
|
return tr("This represents the permission to whisper to this channel from the outside. This works exactly "
|
|
"like the <i>speak</i> "
|
|
"privilege, but applies to packets spoken with the Whisper key held down. This may be used to "
|
|
"broadcast to a hierarchy "
|
|
"of channels without linking.");
|
|
case MuteDeafen:
|
|
return tr(
|
|
"This represents the permission to mute and deafen other users. Once muted, a user will stay muted "
|
|
"until he is unmuted by another privileged user or reconnects to the server.");
|
|
case Move:
|
|
return tr(
|
|
"This represents the permission to move a user to another channel or kick him from the server. To "
|
|
"actually "
|
|
"move the user, either the moving user must have Move privileges in the destination channel, or "
|
|
"the user must normally be allowed to enter the channel. Users with this privilege can move users "
|
|
"into channels the target user normally wouldn't have permission to enter.");
|
|
case MakeChannel:
|
|
return tr("This represents the permission to make sub-channels. The user making the sub-channel will be "
|
|
"added to the "
|
|
"admin group of the sub-channel.");
|
|
case MakeTempChannel:
|
|
return tr("This represents the permission to make a temporary subchannel. The user making the sub-channel "
|
|
"will be added to the "
|
|
"admin group of the sub-channel. Temporary channels are not stored and disappear when the last "
|
|
"user leaves.");
|
|
case LinkChannel:
|
|
return tr(
|
|
"This represents the permission to link channels. Users in linked channels hear each other, as long as "
|
|
"the speaking user has the <i>speak</i> privilege in the channel of the listener. You need the link "
|
|
"privilege in both channels to create a link, but just in either channel to remove it.");
|
|
case TextMessage:
|
|
return tr("This represents the permission to write text messages to other users in this channel.");
|
|
case Kick:
|
|
return tr("This represents the permission to forcibly remove users from the server.");
|
|
case Ban:
|
|
return tr("This represents the permission to permanently remove users from the server.");
|
|
case ResetUserContent:
|
|
return tr("This represents the permission to reset the comment or avatar of a user.");
|
|
case Register:
|
|
return tr("This represents the permission to register and unregister users on the server.");
|
|
case SelfRegister:
|
|
return tr("This represents the permission to register oneself on the server.");
|
|
case Listen:
|
|
return tr("This represents the permission to use the listen-feature allowing to listen to a channel "
|
|
"without being in it.");
|
|
default:
|
|
break;
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
#endif
|
|
|
|
QString ChanACL::permName(QFlags< Perm > p) {
|
|
QStringList qsl;
|
|
for (int i = 0; i <= 31; ++i) {
|
|
if (p & (1 << i))
|
|
qsl << permName(static_cast< Perm >(1 << i));
|
|
}
|
|
return qsl.join(QLatin1String(", "));
|
|
}
|
|
|
|
QString ChanACL::permName(Perm p) {
|
|
switch (p) {
|
|
case None:
|
|
return tr("None");
|
|
case Write:
|
|
return tr("Write ACL");
|
|
case Traverse:
|
|
return tr("Traverse");
|
|
case Enter:
|
|
return tr("Enter");
|
|
case Speak:
|
|
return tr("Speak");
|
|
case Whisper:
|
|
return tr("Whisper");
|
|
case MuteDeafen:
|
|
return tr("Mute/Deafen");
|
|
case Move:
|
|
return tr("Move");
|
|
case MakeChannel:
|
|
return tr("Make channel");
|
|
case MakeTempChannel:
|
|
return tr("Make temporary");
|
|
case LinkChannel:
|
|
return tr("Link channel");
|
|
case TextMessage:
|
|
return tr("Text message");
|
|
case Kick:
|
|
return tr("Kick");
|
|
case Ban:
|
|
return tr("Ban");
|
|
case ResetUserContent:
|
|
return tr("Reset User Content");
|
|
case Register:
|
|
return tr("Register User");
|
|
case SelfRegister:
|
|
return tr("Register Self");
|
|
case Listen:
|
|
return tr("Listen");
|
|
default:
|
|
break;
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
bool ChanACL::isPassword() const {
|
|
// A password is an ACL that applies to a group of the form '#<something>'
|
|
// AND grants 'Enter'
|
|
// AND grants 'Speak', 'Whisper', 'TextMessage', 'LinkChannel' and potentially Traverse but NOTHING else
|
|
// AND does not deny anything.
|
|
// Furthermore the ACL must apply directly to the channel and may not be inherited.
|
|
return this->qsGroup.startsWith(QLatin1Char('#')) && this->bApplyHere && !this->bInherited
|
|
&& (this->pAllow & ChanACL::Enter)
|
|
&& (this->pAllow
|
|
== (ChanACL::Enter | ChanACL::Speak | ChanACL::Whisper | ChanACL::TextMessage | ChanACL::LinkChannel)
|
|
|| // Backwards compat with old behaviour that didn't deny traverse
|
|
this->pAllow
|
|
== (ChanACL::Enter | ChanACL::Speak | ChanACL::Whisper | ChanACL::TextMessage | ChanACL::LinkChannel
|
|
| ChanACL::Traverse))
|
|
&& this->pDeny == ChanACL::None;
|
|
}
|