253 lines
6.5 KiB
C++
253 lines
6.5 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 "Group.h"
|
|
|
|
#include "Channel.h"
|
|
#include "User.h"
|
|
#ifdef MURMUR
|
|
# include "ServerUser.h"
|
|
|
|
# include <QtCore/QStack>
|
|
#endif
|
|
|
|
const Qt::CaseSensitivity Group::accessTokenCaseSensitivity = Qt::CaseInsensitive;
|
|
|
|
Group::Group(Channel *assoc, const QString &name) {
|
|
c = assoc;
|
|
bInherit = true;
|
|
bInheritable = true;
|
|
qsName = name;
|
|
if (c)
|
|
c->qhGroups[name] = this;
|
|
}
|
|
|
|
#ifdef MURMUR
|
|
|
|
QSet< int > Group::members() {
|
|
QStack< Group * > s;
|
|
QSet< int > m;
|
|
Channel *p;
|
|
Group *g;
|
|
int i;
|
|
|
|
p = c;
|
|
while (p) {
|
|
g = p->qhGroups.value(qsName);
|
|
if (g) {
|
|
if ((p != c) && !g->bInheritable)
|
|
break;
|
|
s.push(g);
|
|
if (!g->bInherit)
|
|
break;
|
|
}
|
|
p = p->cParent;
|
|
}
|
|
|
|
while (!s.isEmpty()) {
|
|
g = s.pop();
|
|
foreach (i, g->qsAdd)
|
|
m.insert(i);
|
|
foreach (i, g->qsRemove)
|
|
m.remove(i);
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
Group *Group::getGroup(Channel *chan, QString name) {
|
|
Channel *p = chan;
|
|
while (p) {
|
|
Group *g = p->qhGroups.value(name);
|
|
if (g) {
|
|
if (chan == p)
|
|
return g;
|
|
else if (g->bInheritable)
|
|
return g;
|
|
else
|
|
return nullptr;
|
|
}
|
|
p = p->cParent;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QSet< QString > Group::groupNames(Channel *chan) {
|
|
QStack< Channel * > s;
|
|
QSet< QString > m;
|
|
Channel *c = chan;
|
|
Group *g;
|
|
|
|
while (c) {
|
|
s.push(c);
|
|
c = c->cParent;
|
|
}
|
|
|
|
while (!s.isEmpty()) {
|
|
c = s.pop();
|
|
foreach (g, c->qhGroups) {
|
|
if ((chan != c) && (!g->bInheritable))
|
|
m.remove(g->qsName);
|
|
else
|
|
m.insert(g->qsName);
|
|
}
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
# define RET_FALSE (invert ? true : false)
|
|
# define RET_TRUE (invert ? false : true)
|
|
|
|
bool Group::appliesToUser(const Channel ¤tChannel, const Channel &aclChannel, QString groupSpecification,
|
|
const ServerUser &user) {
|
|
bool matches = false;
|
|
bool invert = false;
|
|
bool isAccessToken = false;
|
|
bool isCertHash = false;
|
|
const Channel *contextChannel = ¤tChannel;
|
|
|
|
while (!groupSpecification.isEmpty()) {
|
|
if (groupSpecification.startsWith(QChar::fromLatin1('!'))) {
|
|
invert = true;
|
|
groupSpecification = groupSpecification.remove(0, 1);
|
|
continue;
|
|
}
|
|
|
|
if (groupSpecification.startsWith(QChar::fromLatin1('~'))) {
|
|
contextChannel = &aclChannel;
|
|
groupSpecification = groupSpecification.remove(0, 1);
|
|
continue;
|
|
}
|
|
|
|
if (groupSpecification.startsWith(QChar::fromLatin1('#'))) {
|
|
isAccessToken = true;
|
|
groupSpecification = groupSpecification.remove(0, 1);
|
|
continue;
|
|
}
|
|
if (groupSpecification.startsWith(QChar::fromLatin1('$'))) {
|
|
isCertHash = true;
|
|
groupSpecification = groupSpecification.remove(0, 1);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (groupSpecification.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
|
|
// First, all special cases that aren't even groups and meta groups (groups that don't actually exist as groups but
|
|
// have a special meaning based on their name
|
|
if (isAccessToken)
|
|
matches = user.qslAccessTokens.contains(groupSpecification, Group::accessTokenCaseSensitivity);
|
|
else if (isCertHash)
|
|
matches = user.qsHash == groupSpecification;
|
|
else if (groupSpecification == QLatin1String("none"))
|
|
matches = false;
|
|
else if (groupSpecification == QLatin1String("all"))
|
|
matches = true;
|
|
else if (groupSpecification == QLatin1String("auth"))
|
|
matches = (user.iId >= 0);
|
|
else if (groupSpecification == QLatin1String("strong"))
|
|
matches = user.bVerified;
|
|
else if (groupSpecification == QLatin1String("in"))
|
|
matches = (user.cChannel == contextChannel);
|
|
else if (groupSpecification == QLatin1String("out"))
|
|
matches = !(user.cChannel == contextChannel);
|
|
else if (groupSpecification == QLatin1String("sub") || groupSpecification.startsWith(QLatin1String("sub,"))) {
|
|
groupSpecification = groupSpecification.remove(0, 4);
|
|
|
|
int requiredChannelOffset = 0;
|
|
int minDescendantLevel = 1;
|
|
int maxDescendantLevel = 1000;
|
|
|
|
// Parse arguments, if any
|
|
QStringList args = groupSpecification.split(QLatin1String(","));
|
|
if (args.count() >= 1 && !args[0].isEmpty()) {
|
|
requiredChannelOffset = args[0].toInt();
|
|
}
|
|
if (args.count() >= 2 && !args[1].isEmpty()) {
|
|
minDescendantLevel = args[1].toInt();
|
|
}
|
|
if (args.count() >= 3 && !args[2].isEmpty()) {
|
|
maxDescendantLevel = args[2].toInt();
|
|
}
|
|
|
|
// Assemble channel hierarchy from root channel to the channel the player is currently in
|
|
QList< const Channel * > homeChannelHierarchy;
|
|
const Channel *channel = user.cChannel;
|
|
while (channel) {
|
|
homeChannelHierarchy.prepend(channel);
|
|
channel = channel->cParent;
|
|
}
|
|
|
|
// Assemble channel hierarchy from root channel to the channel the ACL containing this specification is
|
|
// evaluated for
|
|
QList< const Channel * > currentChannelHierarchy;
|
|
channel = ¤tChannel;
|
|
while (channel) {
|
|
currentChannelHierarchy.prepend(channel);
|
|
channel = channel->cParent;
|
|
}
|
|
|
|
int requiredChannelIndex = currentChannelHierarchy.indexOf(contextChannel);
|
|
Q_ASSERT(requiredChannelIndex != -1);
|
|
|
|
requiredChannelIndex += requiredChannelOffset;
|
|
|
|
if (requiredChannelIndex >= currentChannelHierarchy.count()) {
|
|
return RET_FALSE;
|
|
} else if (requiredChannelIndex < 0) {
|
|
requiredChannelIndex = 0;
|
|
}
|
|
|
|
const Channel *requiredChannel = currentChannelHierarchy[requiredChannelIndex];
|
|
if (homeChannelHierarchy.indexOf(requiredChannel) == -1) {
|
|
return RET_FALSE;
|
|
}
|
|
|
|
const int minDepth = requiredChannelIndex + minDescendantLevel;
|
|
const int maxDepth = requiredChannelIndex + maxDescendantLevel;
|
|
|
|
const int totalDepth = homeChannelHierarchy.count() - 1;
|
|
|
|
matches = (totalDepth >= minDepth) && (totalDepth <= maxDepth);
|
|
} else {
|
|
// The group specification is an actual group name
|
|
QStack< const Group * > groupStack;
|
|
|
|
const Channel *channel = contextChannel;
|
|
|
|
while (channel) {
|
|
const Group *group = channel->qhGroups.value(groupSpecification);
|
|
|
|
if (group) {
|
|
if ((channel != contextChannel) && !group->bInheritable)
|
|
break;
|
|
groupStack.push(group);
|
|
if (!group->bInherit)
|
|
break;
|
|
}
|
|
|
|
channel = channel->cParent;
|
|
}
|
|
|
|
while (!groupStack.isEmpty()) {
|
|
const Group *group = groupStack.pop();
|
|
if (group->qsAdd.contains(user.iId) || group->qsTemporary.contains(user.iId)
|
|
|| group->qsTemporary.contains(-static_cast< int >(user.uiSession)))
|
|
matches = true;
|
|
if (group->qsRemove.contains(user.iId))
|
|
matches = false;
|
|
}
|
|
}
|
|
return invert ? !matches : matches;
|
|
}
|
|
|
|
#endif
|