From 429b4ceefa870bd8fc400f36fc7151cff2d8ae38 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Jul 2020 21:23:55 +1200 Subject: [PATCH 1/4] Flesh out domain groups code --- domain-server/src/DomainGatekeeper.cpp | 56 ++++++++++++---------- domain-server/src/DomainGatekeeper.h | 7 ++- libraries/networking/src/NodePermissions.h | 3 -- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 75cb11dab7..3c03583919 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -156,10 +156,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer_settingsManager.getAllKnownGroupNames().contains(group)) { - userPerms |= _server->_settingsManager.getPermissionsForGroup(group, QUuid()); + if (!verifiedDomainUserName.isEmpty()) { + auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; + foreach (QString userGroup, userGroups) { + if (_server->_settingsManager.getAllKnownGroupNames().contains(userGroup, Qt::CaseInsensitive)) { + userPerms |= _server->_settingsManager.getPermissionsForGroup(userGroup, QUuid()); // No rank for domain groups. +// ####### Enable ifdef //#ifdef WANT_DEBUG - qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << group << "so:" << userPerms; + qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << userGroup + << "so:" << userPerms; //#endif - } } userPerms.setVerifiedDomainUserName(verifiedDomainUserName); - userPerms.setVerifiedDomainUserGroups(verifiedDomainUserGroups); } if (verifiedUsername.isEmpty()) { @@ -309,7 +308,6 @@ void DomainGatekeeper::updateNodePermissions() { // authentication and verifiedUsername is only set once they user's key has been confirmed. QString verifiedUsername = node->getPermissions().getVerifiedUserName(); QString verifiedDomainUserName = node->getPermissions().getVerifiedDomainUserName(); - QStringList verifiedDomainUserGroups = node->getPermissions().getVerifiedDomainUserGroups(); NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0)); if (node->getPermissions().isAssignment) { @@ -345,8 +343,7 @@ void DomainGatekeeper::updateNodePermissions() { } userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUserName, - verifiedDomainUserGroups, connectingAddr.getAddress(), - hardwareAddress, machineFingerprint); + connectingAddr.getAddress(), hardwareAddress, machineFingerprint); } node->setPermissions(userPerms); @@ -486,31 +483,28 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // ####### TODO: OAuth2 corollary of metaverse code, above. + getDomainGroupMemberships(domainUsernameSignature); // Optimistically get started on group memberships. +#ifdef WANT_DEBUG + qDebug() << "stalling login because we have no domain username-signature:" << domainUsername; +#endif return SharedNodePointer(); } else if (verifyDomainUserSignature(domainUsername, domainUsernameSignature, nodeConnection.senderSockAddr)) { // User's domain identity is confirmed. - - // ####### TODO: Get user's domain group memberships (WordPress roles) from domain. - // This may already be provided at the same time as the "verify" call to the domain API. - // If it isn't, need to initiate getting them then handle their receipt along the lines of the - // metaverse code, above. - verifiedDomainUserGroups = QString("test-group").toLower().split(" "); - + getDomainGroupMemberships(domainUsername); verifiedDomainUsername = domainUsername.toLower(); - } else { // User's identity didn't check out. // ####### TODO: OAuth2 corollary of metaverse code, above. #ifdef WANT_DEBUG - qDebug() << "stalling login because signature verification failed:" << username; + qDebug() << "stalling login because domain signature verification failed:" << domainUsername; #endif return SharedNodePointer(); } } - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUsername, verifiedDomainUserGroups, + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUsername, nodeConnection.senderSockAddr.getAddress(), nodeConnection.hardwareAddress, nodeConnection.machineFingerprint); @@ -1004,7 +998,6 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) { AccountManagerAuth::Required, QNetworkAccessManager::PostOperation, callbackParams, QJsonDocument(json).toJson()); - } QString extractUsernameFromGroupMembershipsReply(QNetworkReply* requestReply) { @@ -1059,6 +1052,18 @@ void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply* requestReply _inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply)); } + +void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) { + + // ####### TODO: Get user's domain group memberships (WordPress roles) from domain. + // This may be able to be provided at the same time as the "authenticate user" call to the domain API, in which case + // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. + + QStringList wordpressGroupsForUser = QStringList("siLVer"); + _domainGroupMemberships[domainUserName] = wordpressGroupsForUser; +} + + void DomainGatekeeper::getDomainOwnerFriendsList() { JSONCallbackParameters callbackParams; callbackParams.callbackReceiver = this; @@ -1107,6 +1112,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply* req qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply->error(); } +// ####### TODO: Domain equivalent or addition void DomainGatekeeper::refreshGroupsCache() { // if agents are connected to this domain, refresh our cached information about groups and memberships in such. getDomainOwnerFriendsList(); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 172c63028c..99bd875457 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -128,14 +128,17 @@ private: QSet _inFlightGroupMembershipsRequests; // keep track of which we've already asked for NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, QString verifiedDomainUsername, - QStringList verifiedDomainUserGroups, const QHostAddress& senderAddress, - const QString& hardwareAddress, const QUuid& machineFingerprint); + const QHostAddress& senderAddress, const QString& hardwareAddress, + const QUuid& machineFingerprint); void getGroupMemberships(const QString& username); // void getIsGroupMember(const QString& username, const QUuid groupID); void getDomainOwnerFriendsList(); + // Login and groups for domain, separate from metaverse. bool domainHasLogin(); + void getDomainGroupMemberships(const QString& domainUserName); + QHash _domainGroupMemberships; // // Local ID management. void initLocalIDManagement(); diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 6658b7ea12..82c008feef 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -54,8 +54,6 @@ public: void setVerifiedDomainUserName(QString userName) { _verifiedDomainUserName = userName.toLower(); } const QString& getVerifiedDomainUserName() const { return _verifiedDomainUserName; } - void setVerifiedDomainUserGroups(QStringList userGroups) { _verifiedDomainUserGroups = userGroups; } - const QStringList& getVerifiedDomainUserGroups() const { return _verifiedDomainUserGroups; } void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }} QUuid getGroupID() const { return _groupID; } @@ -106,7 +104,6 @@ protected: QUuid _rankID { QUuid() }; // 0 unless this is for a group QString _verifiedUserName; QString _verifiedDomainUserName; - QStringList _verifiedDomainUserGroups; bool _groupIDSet { false }; QUuid _groupID; From 94908fd1a7e567cbc1a203a67a7a322decac9685 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Jul 2020 22:05:40 +1200 Subject: [PATCH 2/4] Add support for group lists in domain server --- domain-server/src/DomainGatekeeper.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 3c03583919..e296328e32 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -174,11 +174,16 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin if (!verifiedDomainUserName.isEmpty()) { auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; foreach (QString userGroup, userGroups) { - if (_server->_settingsManager.getAllKnownGroupNames().contains(userGroup, Qt::CaseInsensitive)) { - userPerms |= _server->_settingsManager.getPermissionsForGroup(userGroup, QUuid()); // No rank for domain groups. + // Domain groups may be specified as comma- and/or space-separated lists of group names. + // For example, "silver gold, platinum". + auto domainGroups = _server->_settingsManager.getAllKnownGroupNames() + .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", + QRegularExpression::CaseInsensitiveOption)); + foreach(QString domainGroup, domainGroups) { + userPerms |= _server->_settingsManager.getPermissionsForGroup(domainGroup, QUuid()); // No rank for domain groups. // ####### Enable ifdef //#ifdef WANT_DEBUG - qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << userGroup + qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << domainGroup << "so:" << userPerms; //#endif } @@ -1059,7 +1064,8 @@ void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) // This may be able to be provided at the same time as the "authenticate user" call to the domain API, in which case // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. - QStringList wordpressGroupsForUser = QStringList("siLVer"); + QStringList wordpressGroupsForUser; + wordpressGroupsForUser << "silVER" << "gold"; _domainGroupMemberships[domainUserName] = wordpressGroupsForUser; } From d5e189422f0ac7e3f7845c4e5c530b3aeac52b8f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2020 08:08:08 +1200 Subject: [PATCH 3/4] Support blacklisting per domain groups --- domain-server/src/DomainGatekeeper.cpp | 32 +++++++++++++++---- .../src/DomainServerSettingsManager.cpp | 18 +++++++++++ .../src/DomainServerSettingsManager.h | 3 ++ 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index e296328e32..5944b6242c 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -169,27 +169,25 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin #endif } - // If this user is a known member of an externally-hosted group, give them the implied permissions. + // If this user is a known member of a domain group, give them the implied permissions. // Do before processing verifiedUsername in case user is logged into the metaverse and is a member of a blacklist group. if (!verifiedDomainUserName.isEmpty()) { auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; foreach (QString userGroup, userGroups) { // Domain groups may be specified as comma- and/or space-separated lists of group names. // For example, "silver gold, platinum". - auto domainGroups = _server->_settingsManager.getAllKnownGroupNames() + auto domainGroups = _server->_settingsManager.getDomainGroupNames() .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", QRegularExpression::CaseInsensitiveOption)); foreach(QString domainGroup, domainGroups) { userPerms |= _server->_settingsManager.getPermissionsForGroup(domainGroup, QUuid()); // No rank for domain groups. -// ####### Enable ifdef -//#ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << domainGroup << "so:" << userPerms; -//#endif +#endif } } - userPerms.setVerifiedDomainUserName(verifiedDomainUserName); } if (verifiedUsername.isEmpty()) { @@ -293,6 +291,26 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin userPerms.setVerifiedUserName(verifiedUsername); } + // If this user is a known member of an domain group that is blacklisted, remove the implied permissions. + if (!verifiedDomainUserName.isEmpty()) { + auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; + foreach(QString userGroup, userGroups) { + // Domain groups may be specified as comma- and/or space-separated lists of group names. + // For example, "silver gold, platinum". + auto domainGroups = _server->_settingsManager.getDomainBlacklistGroupNames() + .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", + QRegularExpression::CaseInsensitiveOption)); + foreach(QString domainGroup, domainGroups) { + userPerms &= ~_server->_settingsManager.getForbiddensForGroup(domainGroup, QUuid()); +#ifdef WANT_DEBUG + qDebug() << "| user-permissions: domain user is in blacklist group:" << domainGroup << "so:" << userPerms; +#endif + } + } + + userPerms.setVerifiedDomainUserName(verifiedDomainUserName); + } + #ifdef WANT_DEBUG qDebug() << "| user-permissions: final:" << userPerms; #endif @@ -1065,7 +1083,7 @@ void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. QStringList wordpressGroupsForUser; - wordpressGroupsForUser << "silVER" << "gold"; + wordpressGroupsForUser << "silVER" << "gold" << "coal"; _domainGroupMemberships[domainUserName] = wordpressGroupsForUser; } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 73d78a5c70..30ca15a51e 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -2185,6 +2185,24 @@ QList DomainServerSettingsManager::getBlacklistGroupIDs() { return result.toList(); } +QStringList DomainServerSettingsManager::getDomainGroupNames() { + // Names as configured in domain server; not necessarily mnetaverse groups. + QSet result; + foreach(NodePermissionsKey groupKey, _groupPermissions.keys()) { + result += _groupPermissions[groupKey]->getID(); + } + return result.toList(); +} + +QStringList DomainServerSettingsManager::getDomainBlacklistGroupNames() { + // Names as configured in domain server; not necessarily mnetaverse groups. + QSet result; + foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) { + result += _groupForbiddens[groupKey]->getID(); + } + return result.toList(); +} + void DomainServerSettingsManager::debugDumpGroupsState() { qDebug() << "--------- GROUPS ---------"; diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index e28b9f6cd1..8c18c22b32 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -105,6 +105,9 @@ public: QList getGroupIDs(); QList getBlacklistGroupIDs(); + QStringList getDomainGroupNames(); + QStringList getDomainBlacklistGroupNames(); + // these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api void clearGroupMemberships(const QString& name) { _groupMembership[name.toLower()].clear(); } void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID); From 7c8993b34f6dc307e207597b8554e3a19bcf408e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2020 10:03:44 +1200 Subject: [PATCH 4/4] Add TODO --- domain-server/src/DomainGatekeeper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 5944b6242c..32b02382da 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -1082,6 +1082,8 @@ void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) // This may be able to be provided at the same time as the "authenticate user" call to the domain API, in which case // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. + // ####### TODO: Check how often this method and the WordPress API is called. + QStringList wordpressGroupsForUser; wordpressGroupsForUser << "silVER" << "gold" << "coal"; _domainGroupMemberships[domainUserName] = wordpressGroupsForUser;