REFAC(server): Improve readability of Group::isMember

And while we're at it, give it a different name that better describes
what this function is actually doing (spoiler: it is not only checking
whether the given user is part of the specified group).
This commit is contained in:
Robert Adam 2022-12-15 19:05:45 +01:00
parent eb01d1b6d6
commit 18322e6db6
4 changed files with 106 additions and 99 deletions

View File

@ -148,7 +148,7 @@ QFlags< ChanACL::Perm > ChanACL::effectivePermissions(ServerUser *p, Channel *ch
foreach (acl, ch->qlACL) {
bool matchUser = (acl->iUserId != -1) && (acl->iUserId == p->iId);
bool matchGroup = Group::isMember(chan, ch, acl->qsGroup, p);
bool matchGroup = Group::appliesToUser(*chan, *ch, acl->qsGroup, *p);
if (matchUser || matchGroup) {
if (acl->pAllow & Traverse)
traverse = true;

View File

@ -101,146 +101,152 @@ QSet< QString > Group::groupNames(Channel *chan) {
# define RET_FALSE (invert ? true : false)
# define RET_TRUE (invert ? false : true)
bool Group::isMember(Channel *curChan, Channel *aclChan, QString name, ServerUser *pl) {
Channel *p;
Channel *c;
Group *g;
bool Group::appliesToUser(const Channel &currentChannel, const Channel &aclChannel, QString groupSpecification,
const ServerUser &user) {
bool matches = false;
bool invert = false;
bool isAccessToken = false;
bool isCertHash = false;
const Channel *contextChannel = &currentChannel;
bool m = false;
bool invert = false;
bool token = false;
bool hash = false;
c = curChan;
while (true) {
if (name.isEmpty())
return false;
if (name.startsWith(QChar::fromLatin1('!'))) {
invert = true;
name = name.remove(0, 1);
while (!groupSpecification.isEmpty()) {
if (groupSpecification.startsWith(QChar::fromLatin1('!'))) {
invert = true;
groupSpecification = groupSpecification.remove(0, 1);
continue;
}
if (name.startsWith(QChar::fromLatin1('~'))) {
c = aclChan;
name = name.remove(0, 1);
if (groupSpecification.startsWith(QChar::fromLatin1('~'))) {
contextChannel = &aclChannel;
groupSpecification = groupSpecification.remove(0, 1);
continue;
}
if (name.startsWith(QChar::fromLatin1('#'))) {
token = true;
name = name.remove(0, 1);
if (groupSpecification.startsWith(QChar::fromLatin1('#'))) {
isAccessToken = true;
groupSpecification = groupSpecification.remove(0, 1);
continue;
}
if (name.startsWith(QChar::fromLatin1('$'))) {
hash = true;
name = name.remove(0, 1);
if (groupSpecification.startsWith(QChar::fromLatin1('$'))) {
isCertHash = true;
groupSpecification = groupSpecification.remove(0, 1);
continue;
}
break;
}
if (token)
m = pl->qslAccessTokens.contains(name, Group::accessTokenCaseSensitivity);
else if (hash)
m = pl->qsHash == name;
else if (name == QLatin1String("none"))
m = false;
else if (name == QLatin1String("all"))
m = true;
else if (name == QLatin1String("auth"))
m = (pl->iId >= 0);
else if (name == QLatin1String("strong"))
m = pl->bVerified;
else if (name == QLatin1String("in"))
m = (pl->cChannel == c);
else if (name == QLatin1String("out"))
m = !(pl->cChannel == c);
else if (name == QLatin1String("sub") || name.startsWith(QLatin1String("sub,"))) {
name = name.remove(0, 4);
int mindesc = 1;
int maxdesc = 1000;
int minpath = 0;
QStringList args = name.split(QLatin1String(","));
if (args.count() >= 3) {
maxdesc = args[2].isEmpty() ? maxdesc : args[2].toInt();
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) {
mindesc = args[1].isEmpty() ? mindesc : args[1].toInt();
if (args.count() >= 2 && !args[1].isEmpty()) {
minDescendantLevel = args[1].toInt();
}
if (args.count() >= 1) {
minpath = args[0].isEmpty() ? minpath : args[0].toInt();
if (args.count() >= 3 && !args[2].isEmpty()) {
maxDescendantLevel = args[2].toInt();
}
Channel *home = pl->cChannel;
QList< Channel * > playerChain;
QList< Channel * > groupChain;
p = home;
while (p) {
playerChain.prepend(p);
p = p->cParent;
// 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;
}
p = curChan;
while (p) {
groupChain.prepend(p);
p = p->cParent;
// Assemble channel hierarchy from root channel to the channel the ACL containing this specification is
// evaluated for
QList< const Channel * > currentChannelHierarchy;
channel = &currentChannel;
while (channel) {
currentChannelHierarchy.prepend(channel);
channel = channel->cParent;
}
int cofs = groupChain.indexOf(c);
Q_ASSERT(cofs != -1);
int requiredChannelIndex = currentChannelHierarchy.indexOf(contextChannel);
Q_ASSERT(requiredChannelIndex != -1);
cofs += minpath;
requiredChannelIndex += requiredChannelOffset;
if (cofs >= groupChain.count()) {
if (requiredChannelIndex >= currentChannelHierarchy.count()) {
return RET_FALSE;
} else if (cofs < 0) {
cofs = 0;
} else if (requiredChannelIndex < 0) {
requiredChannelIndex = 0;
}
Channel *needed = groupChain[cofs];
if (playerChain.indexOf(needed) == -1) {
const Channel *requiredChannel = currentChannelHierarchy[requiredChannelIndex];
if (homeChannelHierarchy.indexOf(requiredChannel) == -1) {
return RET_FALSE;
}
int mindepth = cofs + mindesc;
int maxdepth = cofs + maxdesc;
const int minDepth = requiredChannelIndex + minDescendantLevel;
const int maxDepth = requiredChannelIndex + maxDescendantLevel;
int pdepth = playerChain.count() - 1;
const int totalDepth = homeChannelHierarchy.count() - 1;
m = (pdepth >= mindepth) && (pdepth <= maxdepth);
matches = (totalDepth >= minDepth) && (totalDepth <= maxDepth);
} else {
QStack< Group * > s;
// The group specification is an actual group name
QStack< const Group * > groupStack;
p = c;
const Channel *channel = contextChannel;
while (p) {
g = p->qhGroups.value(name);
while (channel) {
const Group *group = channel->qhGroups.value(groupSpecification);
if (g) {
if ((p != c) && !g->bInheritable)
if (group) {
if ((channel != contextChannel) && !group->bInheritable)
break;
s.push(g);
if (!g->bInherit)
groupStack.push(group);
if (!group->bInherit)
break;
}
p = p->cParent;
channel = channel->cParent;
}
while (!s.isEmpty()) {
g = s.pop();
if (g->qsAdd.contains(pl->iId) || g->qsTemporary.contains(pl->iId)
|| g->qsTemporary.contains(-static_cast< int >(pl->uiSession)))
m = true;
if (g->qsRemove.contains(pl->iId))
m = false;
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 ? !m : m;
return invert ? !matches : matches;
}
#endif

View File

@ -33,7 +33,8 @@ public:
static QSet< QString > groupNames(Channel *c);
static Group *getGroup(Channel *c, QString name);
static bool isMember(Channel *c, Channel *aclChan, QString name, ServerUser *);
static bool appliesToUser(const Channel &currentChannel, const Channel &aclChannel, QString groupSpecification,
const ServerUser &user);
#endif
};

View File

@ -1236,7 +1236,7 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
foreach (User *p, tc->qlUsers) {
ServerUser *su = static_cast< ServerUser * >(p);
if (!group || Group::isMember(tc, tc, qsg, su)) {
if (!group || Group::appliesToUser(*tc, *tc, qsg, *su)) {
channel.insert(su);
}
}
@ -1245,7 +1245,7 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au
m_channelListenerManager.getListenersForChannel(tc->iId)) {
ServerUser *pDst = static_cast< ServerUser * >(qhUsers.value(currentSession));
if (pDst && (!group || Group::isMember(tc, tc, qsg, pDst))) {
if (pDst && (!group || Group::appliesToUser(*tc, *tc, qsg, *pDst))) {
// Only send audio to listener if the user exists and it is in the group the
// speech is directed at (if any)
addListener(cachedListeners, *pDst, *tc);