diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 44d2c025a7..885a289db4 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -526,7 +526,7 @@ "groups": [ { "label": "Group", - "span": 2 + "span": 4 }, { "label": "Permissions ?", @@ -539,6 +539,14 @@ "name": "permissions_id", "label": "Group Name" }, + { + "name": "rank", + "label": "Rank" + }, + { + "name": "rank_name", + "label": "Rank Name" + }, { "name": "group_id", "label": "Group ID", @@ -597,7 +605,7 @@ "groups": [ { "label": "Group", - "span": 2 + "span": 4 }, { "label": "Permissions ?", @@ -610,6 +618,14 @@ "name": "permissions_id", "label": "Group Name" }, + { + "name": "rank", + "label": "Rank" + }, + { + "name": "rank_name", + "label": "Rank Name" + }, { "name": "group_id", "label": "Group ID", diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 77cd7ffb94..f85766105e 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -150,8 +150,9 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, // if this user is a known member of a group, give them the implied permissions foreach (QUuid groupID, _server->_settingsManager.getGroupIDs()) { - if (_server->_settingsManager.isGroupMember(verifiedUsername, groupID)) { - userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID); + int rank = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); + if (rank >= 0) { + userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rank); qDebug() << "user-permissions: user is in group:" << groupID << "so:" << userPerms; } } @@ -159,11 +160,13 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, // if this user is a known member of a blacklist group, remove the implied permissions foreach (QUuid groupID, _server->_settingsManager.getBlacklistGroupIDs()) { if (_server->_settingsManager.isGroupMember(verifiedUsername, groupID)) { - userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID); - qDebug() << "user-permissions: user is in blacklist group:" << groupID << "so:" << userPerms; + int rank = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); + if (rank >= 0) { + userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rank); + qDebug() << "user-permissions: user is in blacklist group:" << groupID << "so:" << userPerms; + } } } - } } @@ -183,7 +186,7 @@ void DomainGatekeeper::updateNodePermissions() { // the id and the username in NodePermissions will often be the same, but id is set before // authentication and username is only set once they user's key has been confirmed. QString username = node->getPermissions().getUserName(); - NodePermissions userPerms(username); + NodePermissions userPerms(NodePermissionsKey(username, 0)); if (node->getPermissions().isAssignment) { // this node is an assignment-client @@ -273,7 +276,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect auto limitedNodeList = DependencyManager::get(); // start with empty permissions - NodePermissions userPerms(username); + NodePermissions userPerms(NodePermissionsKey(username, 0)); userPerms.setAll(false); // check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection @@ -711,7 +714,7 @@ void DomainGatekeeper::getIsGroupMember(const QString& username, const QUuid gro callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback"; - const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/%1/membership/%2"; + const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/%1/members/%2"; QString groupIDStr = groupID.toString().mid(1,36); DependencyManager::get()->sendRequest(GET_IS_GROUP_MEMBER_PATH.arg(groupIDStr).arg(username), AccountManagerAuth::Required, diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index fcbf4caa5b..d5bf8d9adc 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -112,7 +112,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : return; } - _settingsManager.requestMissingGroupIDs(); + _settingsManager.apiRefreshGroupInformation(); setupNodeListAndAssignments(); setupAutomaticNetworking(); @@ -1097,7 +1097,6 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting; - // add access level for anonymous connections // consider the domain to be "restricted" if anonymous connections are disallowed static const QString RESTRICTED_ACCESS_FLAG = "restricted"; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 5c27f37172..3f44b9a293 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -232,36 +232,37 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList foreach (QString allowedUser, allowedUsers) { // even if isRestrictedAccess is false, we have to add explicit rows for these users. - _agentPermissions[allowedUser].reset(new NodePermissions(allowedUser)); - _agentPermissions[allowedUser]->set(NodePermissions::Permission::canConnectToDomain); + _agentPermissions[NodePermissionsKey(allowedUser, 0)].reset(new NodePermissions(allowedUser)); + _agentPermissions[NodePermissionsKey(allowedUser, 0)]->set(NodePermissions::Permission::canConnectToDomain); } foreach (QString allowedEditor, allowedEditors) { - if (!_agentPermissions.contains(allowedEditor)) { - _agentPermissions[allowedEditor].reset(new NodePermissions(allowedEditor)); + NodePermissionsKey editorKey(allowedEditor, 0); + if (!_agentPermissions.contains(editorKey)) { + _agentPermissions[editorKey].reset(new NodePermissions(allowedEditor)); if (isRestrictedAccess) { // they can change locks, but can't connect. - _agentPermissions[allowedEditor]->clear(NodePermissions::Permission::canConnectToDomain); + _agentPermissions[editorKey]->clear(NodePermissions::Permission::canConnectToDomain); } } - _agentPermissions[allowedEditor]->set(NodePermissions::Permission::canAdjustLocks); + _agentPermissions[editorKey]->set(NodePermissions::Permission::canAdjustLocks); } - QList> permissionsSets; + QList> permissionsSets; permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get(); foreach (auto permissionsSet, permissionsSets) { - foreach (QString userName, permissionsSet.keys()) { + foreach (NodePermissionsKey userKey, permissionsSet.keys()) { if (onlyEditorsAreRezzers) { - if (permissionsSet[userName]->can(NodePermissions::Permission::canAdjustLocks)) { - permissionsSet[userName]->set(NodePermissions::Permission::canRezPermanentEntities); - permissionsSet[userName]->set(NodePermissions::Permission::canRezTemporaryEntities); + if (permissionsSet[userKey]->can(NodePermissions::Permission::canAdjustLocks)) { + permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities); + permissionsSet[userKey]->set(NodePermissions::Permission::canRezTemporaryEntities); } else { - permissionsSet[userName]->clear(NodePermissions::Permission::canRezPermanentEntities); - permissionsSet[userName]->clear(NodePermissions::Permission::canRezTemporaryEntities); + permissionsSet[userKey]->clear(NodePermissions::Permission::canRezPermanentEntities); + permissionsSet[userKey]->clear(NodePermissions::Permission::canRezTemporaryEntities); } } else { - permissionsSet[userName]->set(NodePermissions::Permission::canRezPermanentEntities); - permissionsSet[userName]->set(NodePermissions::Permission::canRezTemporaryEntities); + permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities); + permissionsSet[userKey]->set(NodePermissions::Permission::canRezTemporaryEntities); } } } @@ -343,11 +344,17 @@ void DomainServerSettingsManager::packPermissionsForMap(QString mapName, (*permissions) = QVariantList(); } - // convert details for each member of the section + // convert details for each member of the subsection QVariantList* permissionsList = reinterpret_cast(permissions); (*permissionsList).clear(); - foreach (QString userName, agentPermissions.keys()) { - *permissionsList += agentPermissions[userName]->toVariant(); + foreach (NodePermissionsKey userKey, agentPermissions.keys()) { + NodePermissionsPointer perms = agentPermissions[userKey]; + if (perms->isGroup()) { + QVector rankNames = _groupRanks[perms->getGroupID()]; + *permissionsList += perms->toVariant(rankNames); + } else { + *permissionsList += perms->toVariant(); + } } } @@ -413,16 +420,17 @@ void DomainServerSettingsManager::unpackPermissions() { foreach (QVariant permsHash, standardPermissionsList) { NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); - foundLocalhost |= (id == NodePermissions::standardNameLocalhost); - foundAnonymous |= (id == NodePermissions::standardNameAnonymous); - foundLoggedIn |= (id == NodePermissions::standardNameLoggedIn); - foundFriends |= (id == NodePermissions::standardNameFriends); - if (_standardAgentPermissions.contains(id)) { + NodePermissionsKey idKey = NodePermissionsKey(id, 0); + foundLocalhost |= (idKey == NodePermissions::standardNameLocalhost); + foundAnonymous |= (idKey == NodePermissions::standardNameAnonymous); + foundLoggedIn |= (idKey == NodePermissions::standardNameLoggedIn); + foundFriends |= (idKey == NodePermissions::standardNameFriends); + if (_standardAgentPermissions.contains(idKey)) { qDebug() << "duplicate name in standard permissions table: " << id; - _standardAgentPermissions[id] |= perms; + _standardAgentPermissions[idKey] |= perms; needPack = true; } else { - _standardAgentPermissions[id] = perms; + _standardAgentPermissions[idKey] = perms; } } @@ -430,12 +438,13 @@ void DomainServerSettingsManager::unpackPermissions() { foreach (QVariant permsHash, permissionsList) { NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); - if (_agentPermissions.contains(id)) { + NodePermissionsKey idKey = NodePermissionsKey(id, 0); + if (_agentPermissions.contains(idKey)) { qDebug() << "duplicate name in permissions table: " << id; - _agentPermissions[id] |= perms; + _agentPermissions[idKey] |= perms; needPack = true; } else { - _agentPermissions[id] = perms; + _agentPermissions[idKey] = perms; } } @@ -443,16 +452,18 @@ void DomainServerSettingsManager::unpackPermissions() { foreach (QVariant permsHash, groupPermissionsList) { NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); - if (_groupPermissions.contains(id)) { + NodePermissionsKey idKey = perms->getKey(); + if (_groupPermissions.contains(idKey)) { qDebug() << "duplicate name in group permissions table: " << id; - _groupPermissions[id] |= perms; + _groupPermissions[idKey] |= perms; needPack = true; } else { - _groupPermissions[id] = perms; + _groupPermissions[idKey] = perms; } if (perms->isGroup()) { - // the group-id was cached. hook-up the id in the id->group hash - _groupByID[perms->getGroupID()] = _groupPermissions[id]; + // the group-id was cached. hook-up the uuid in the uuid->group hash + _groupPermissionsByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRank())] = _groupPermissions[idKey]; + needPack |= setGroupID(perms->getID(), perms->getGroupID()); } } @@ -460,16 +471,18 @@ void DomainServerSettingsManager::unpackPermissions() { foreach (QVariant permsHash, groupForbiddensList) { NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); - if (_groupForbiddens.contains(id)) { + NodePermissionsKey idKey = perms->getKey(); + if (_groupForbiddens.contains(idKey)) { qDebug() << "duplicate name in group forbiddens table: " << id; - _groupForbiddens[id] |= perms; + _groupForbiddens[idKey] |= perms; needPack = true; } else { - _groupForbiddens[id] = perms; + _groupForbiddens[idKey] = perms; } if (perms->isGroup()) { - // the group-id was cached. hook-up the id in the id->group hash - _groupByID[perms->getGroupID()] = _groupForbiddens[id]; + // the group-id was cached. hook-up the uuid in the uuid->group hash + _groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRank())] = _groupPermissions[idKey]; + needPack |= setGroupID(perms->getID(), perms->getGroupID()); } } @@ -477,40 +490,42 @@ void DomainServerSettingsManager::unpackPermissions() { if (!foundLocalhost) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; perms->setAll(true); - _standardAgentPermissions[perms->getID()] = perms; + _standardAgentPermissions[perms->getKey()] = perms; needPack = true; } if (!foundAnonymous) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) }; - _standardAgentPermissions[perms->getID()] = perms; + _standardAgentPermissions[perms->getKey()] = perms; needPack = true; } if (!foundLoggedIn) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) }; - _standardAgentPermissions[perms->getID()] = perms; + _standardAgentPermissions[perms->getKey()] = perms; needPack = true; } if (!foundFriends) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameFriends) }; - _standardAgentPermissions[perms->getID()] = perms; + _standardAgentPermissions[perms->getKey()] = perms; needPack = true; } + needPack |= ensurePermissionsForGroupRanks(); + if (needPack) { packPermissions(); } - // attempt to retrieve any missing group-IDs - requestMissingGroupIDs(); + // attempt to retrieve any missing group-IDs, etc + apiRefreshGroupInformation(); #ifdef WANT_DEBUG qDebug() << "--------------- permissions ---------------------"; - QList> permissionsSets; + QList> permissionsSets; permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() << _groupPermissions.get() << _groupForbiddens.get(); foreach (auto permissionSet, permissionsSets) { - QHashIterator i(permissionSet); + QHashIterator i(permissionSet); while (i.hasNext()) { i.next(); NodePermissionsPointer perms = i.value(); @@ -524,7 +539,63 @@ void DomainServerSettingsManager::unpackPermissions() { #endif } -NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const QString& name) const { +bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { + // make sure each rank in each group has its own set of permissions + bool changed = false; + QList permissionGroupIDs = getGroupIDs(); + foreach (QUuid groupID, permissionGroupIDs) { + QString groupName = _groupNames[groupID]; + int rankCountForGroup = _groupRanks[groupID].size(); + for (int rank = 0; rank < rankCountForGroup; rank++) { + NodePermissionsKey nameKey = NodePermissionsKey(groupName, rank); + GroupByUUIDKey idKey = GroupByUUIDKey(groupID, rank); + NodePermissionsPointer perms; + if (_groupPermissions.contains(nameKey)) { + perms = _groupPermissions[nameKey]; + } else { + perms = NodePermissionsPointer(new NodePermissions(nameKey)); + perms->setGroupID(groupID); + _groupPermissions[nameKey] = perms; + changed = true; + } + _groupPermissionsByUUID[idKey] = perms; + } + } + + QList forbiddenGroupIDs = getBlacklistGroupIDs(); + foreach (QUuid groupID, forbiddenGroupIDs) { + QString groupName = _groupNames[groupID]; + int rankCountForGroup = _groupRanks[groupID].size(); + for (int rank = 0; rank < rankCountForGroup; rank++) { + NodePermissionsKey nameKey = NodePermissionsKey(groupName, rank); + GroupByUUIDKey idKey = GroupByUUIDKey(groupID, rank); + NodePermissionsPointer perms; + if (_groupForbiddens.contains(nameKey)) { + perms = _groupForbiddens[nameKey]; + } else { + perms = NodePermissionsPointer(new NodePermissions(nameKey)); + perms->setGroupID(groupID); + _groupForbiddens[nameKey] = perms; + changed = true; + } + _groupForbiddensByUUID[idKey] = perms; + } + } + + debugDumpGroupsState(); + + return changed; +} + +QStringList DomainServerSettingsManager::getAllNames() const { + QStringList result; + foreach (auto key, _agentPermissions.keys()) { + result << key.first.toLower(); + } + return result; +} + +NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const NodePermissionsKey& name) const { if (_standardAgentPermissions.contains(name)) { return *(_standardAgentPermissions[name].get()); } @@ -534,55 +605,60 @@ NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const } NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const { - if (_agentPermissions.contains(name)) { - return *(_agentPermissions[name].get()); + NodePermissionsKey nameKey = NodePermissionsKey(name, 0); + if (_agentPermissions.contains(nameKey)) { + return *(_agentPermissions[nameKey].get()); } NodePermissions nullPermissions; nullPermissions.setAll(false); return nullPermissions; } -NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupname) const { - if (_groupPermissions.contains(groupname)) { - return *(_groupPermissions[groupname].get()); +NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, int rank) const { + NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rank); + if (_groupPermissions.contains(groupRankKey)) { + return *(_groupPermissions[groupRankKey].get()); } NodePermissions nullPermissions; nullPermissions.setAll(false); return nullPermissions; } -NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QUuid& groupID) const { - if (!_groupByID.contains(groupID)) { +NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QUuid& groupID, int rank) const { + GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rank); + if (!_groupPermissionsByUUID.contains(byUUIDKey)) { NodePermissions nullPermissions; nullPermissions.setAll(false); return nullPermissions; } - QString groupName = _groupByID[groupID]->getID(); - return getPermissionsForGroup(groupName); + NodePermissionsKey groupKey = _groupPermissionsByUUID[byUUIDKey]->getKey(); + return getPermissionsForGroup(groupKey.first, groupKey.second); } - -NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QString& groupname) const { - if (_groupForbiddens.contains(groupname)) { - return *(_groupForbiddens[groupname].get()); +NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QString& groupName, int rank) const { + NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rank); + if (_groupForbiddens.contains(groupRankKey)) { + return *(_groupForbiddens[groupRankKey].get()); } NodePermissions nullForbiddens; + // XXX should this be setAll(true) ? nullForbiddens.setAll(false); return nullForbiddens; } -NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& groupID) const { - if (!_groupByID.contains(groupID)) { +NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& groupID, int rank) const { + GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rank); + if (!_groupForbiddensByUUID.contains(byUUIDKey)) { NodePermissions nullForbiddens; + // XXX should this be setAll(true) ? nullForbiddens.setAll(false); return nullForbiddens; } - QString groupName = _groupByID[groupID]->getID(); - return getForbiddensForGroup(groupName); + + NodePermissionsKey groupKey = _groupForbiddensByUUID[byUUIDKey]->getKey(); + return getForbiddensForGroup(groupKey.first, groupKey.second); } - - QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); @@ -854,7 +930,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV sortPermissions(); } -QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) { +QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, + const QString& settingName) { foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) { QJsonObject settingObject = settingValue.toObject(); if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) { @@ -967,6 +1044,12 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) { !m2.contains("permissions_id")) { return v1.toString() < v2.toString(); } + + if (m1.contains("rank_name") && m2.contains("rank_name") && + m1["permissions_id"].toString() == m2["permissions_id"].toString()) { + return m1["rank_name"].toString() < m2["rank_name"].toString(); + } + return m1["permissions_id"].toString() < m2["permissions_id"].toString(); } @@ -1003,62 +1086,94 @@ void DomainServerSettingsManager::persistToFile() { } } -void DomainServerSettingsManager::requestMissingGroupIDs() { +QStringList DomainServerSettingsManager::getAllKnownGroupNames() { + // extract all the group names from the group-permissions and group-forbiddens settings + QSet result; + + QHashIterator i_permissions(_groupPermissions.get()); + while (i_permissions.hasNext()) { + i_permissions.next(); + NodePermissionsKey key = i_permissions.key(); + result += key.first; + } + + QHashIterator i_forbiddens(_groupForbiddens.get()); + while (i_forbiddens.hasNext()) { + i_forbiddens.next(); + NodePermissionsKey key = i_forbiddens.key(); + result += key.first; + } + + return result.toList(); +} + +bool DomainServerSettingsManager::setGroupID(const QString& groupName, const QUuid& groupID) { + bool changed = false; + _groupIDs[groupName.toLower()] = groupID; + _groupNames[groupID] = groupName; + + QHashIterator i_permissions(_groupPermissions.get()); + while (i_permissions.hasNext()) { + i_permissions.next(); + NodePermissionsPointer perms = i_permissions.value(); + if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) { + changed = true; + perms->setGroupID(groupID); + } + } + + QHashIterator i_forbiddens(_groupForbiddens.get()); + while (i_forbiddens.hasNext()) { + i_forbiddens.next(); + NodePermissionsPointer perms = i_forbiddens.value(); + if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) { + changed = true; + perms->setGroupID(groupID); + } + } + + return changed; +} + +void DomainServerSettingsManager::apiRefreshGroupInformation() { + const int STALE_DATA_AGE = 600; // seconds + if (!DependencyManager::get()->hasAuthEndpoint()) { // can't yet. return; } - QHashIterator i_permissions(_groupPermissions.get()); - while (i_permissions.hasNext()) { - i_permissions.next(); - NodePermissionsPointer perms = i_permissions.value(); - if (!perms->getGroupID().isNull()) { - // we already know this group's ID + QStringList groupNames = getAllKnownGroupNames(); + foreach (QString groupName, groupNames) { + if (_groupIDs.contains(groupName.toLower())) { + // we already know about this one continue; } - - // make a call to metaverse api to turn the group name into a group ID - getGroupID(perms->getID()); + apiGetGroupID(groupName); } - QHashIterator i_forbiddens(_groupForbiddens.get()); - while (i_forbiddens.hasNext()) { - i_forbiddens.next(); - NodePermissionsPointer perms = i_forbiddens.value(); - if (!perms->getGroupID().isNull()) { - // we already know this group's ID - continue; - } - // make a call to metaverse api to turn the group name into a group ID - getGroupID(perms->getID()); + quint64 now = usecTimestampNow(); + foreach (QUuid groupID, _groupNames.keys()) { + if (now - _groupRanksLastFetched[groupID] > STALE_DATA_AGE * USECS_PER_SECOND) { + apiGetGroupRanks(groupID); + } } } -NodePermissionsPointer DomainServerSettingsManager::lookupGroupByID(const QUuid& id) { - if (_groupByID.contains(id)) { - return _groupByID[id]; - } - return nullptr; -} - -void DomainServerSettingsManager::getGroupID(const QString& groupname) { +void DomainServerSettingsManager::apiGetGroupID(const QString& groupName) { JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "getGroupIDJSONCallback"; + callbackParams.jsonCallbackMethod = "apiGetGroupIDJSONCallback"; callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "getGroupIDErrorCallback"; + callbackParams.errorCallbackMethod = "apiGetGroupIDErrorCallback"; - const QString GET_GROUP_ID_PATH = "api/v1/groups/name/%1"; - - qDebug() << "************* Requesting group ID for group named" << groupname; - - DependencyManager::get()->sendRequest(GET_GROUP_ID_PATH.arg(groupname), + const QString GET_GROUP_ID_PATH = "api/v1/groups/names/%1"; + DependencyManager::get()->sendRequest(GET_GROUP_ID_PATH.arg(groupName), AccountManagerAuth::Required, QNetworkAccessManager::GetOperation, callbackParams); } -void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestReply) { +void DomainServerSettingsManager::apiGetGroupIDJSONCallback(QNetworkReply& requestReply) { // { // "data":{ // "groups":[{ @@ -1090,7 +1205,6 @@ void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestR // "status":"success" // } QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - if (jsonObject["status"].toString() == "success") { QJsonArray groups = jsonObject["data"].toObject()["groups"].toArray(); for (int i = 0; i < groups.size(); i++) { @@ -1098,25 +1212,10 @@ void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestR QString groupName = group["name"].toString(); QUuid groupID = QUuid(group["id"].toString()); - bool found = false; - if (_groupPermissions.contains(groupName)) { - qDebug() << "ID for group:" << groupName << "is" << groupID; - _groupPermissions[groupName]->setGroupID(groupID); - _groupByID[groupID] = _groupPermissions[groupName]; - found = true; - } - if (_groupForbiddens.contains(groupName)) { - qDebug() << "ID for group:" << groupName << "is" << groupID; - _groupForbiddens[groupName]->setGroupID(groupID); - _groupByID[groupID] = _groupForbiddens[groupName]; - found = true; - } - - if (found) { + bool changed = setGroupID(groupName, groupID); + if (changed) { packPermissions(); - getGroupRanks(groupID); - } else { - qDebug() << "DomainServerSettingsManager::getGroupIDJSONCallback got response for unknown group:" << groupName; + apiGetGroupRanks(groupID); } } } else { @@ -1124,27 +1223,26 @@ void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestR } } -void DomainServerSettingsManager::getGroupIDErrorCallback(QNetworkReply& requestReply) { +void DomainServerSettingsManager::apiGetGroupIDErrorCallback(QNetworkReply& requestReply) { qDebug() << "******************** getGroupID api call failed:" << requestReply.error(); } -void DomainServerSettingsManager::getGroupRanks(const QUuid& groupID) { +void DomainServerSettingsManager::apiGetGroupRanks(const QUuid& groupID) { + _groupRanksLastFetched[groupID] = usecTimestampNow(); + JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "getGroupRanksJSONCallback"; + callbackParams.jsonCallbackMethod = "apiGetGroupRanksJSONCallback"; callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "getGroupRanksErrorCallback"; + callbackParams.errorCallbackMethod = "apiGetGroupRanksErrorCallback"; const QString GET_GROUP_RANKS_PATH = "api/v1/groups/%1/ranks"; - - qDebug() << "************* Requesting group ranks for group" << groupID; - DependencyManager::get()->sendRequest(GET_GROUP_RANKS_PATH.arg(groupID.toString().mid(1,36)), AccountManagerAuth::Required, QNetworkAccessManager::GetOperation, callbackParams); } -void DomainServerSettingsManager::getGroupRanksJSONCallback(QNetworkReply& requestReply) { +void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& requestReply) { // { // "current_page":1, // "data":{ @@ -1195,6 +1293,7 @@ void DomainServerSettingsManager::getGroupRanksJSONCallback(QNetworkReply& reque // "total_pages":1 // } + bool changed = false; QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); if (jsonObject["status"].toString() == "success") { QJsonObject groups = jsonObject["data"].toObject()["groups"].toObject(); @@ -1205,47 +1304,97 @@ void DomainServerSettingsManager::getGroupRanksJSONCallback(QNetworkReply& reque QJsonObject rank = ranks.at(rankIndex).toObject(); QString rankName = rank["name"].toString(); int rankOrder = rank["order"].toInt(); - qDebug() << "**** " << groupID << "rank name =" << rankName << " order =" << rankOrder; QVector& ranksForGroup = _groupRanks[groupID]; if (ranksForGroup.size() < rankOrder + 1) { ranksForGroup.resize(rankOrder + 1); + changed = true; + } + if (ranksForGroup[rankOrder] != rankName) { + ranksForGroup[rankOrder] = rankName; + changed = true; } - ranksForGroup[rankOrder] = rankName; } } + + changed |= ensurePermissionsForGroupRanks(); + if (changed) { + packPermissions(); + } } else { qDebug() << "getGroupRanks api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); } } -void DomainServerSettingsManager::getGroupRanksErrorCallback(QNetworkReply& requestReply) { +void DomainServerSettingsManager::apiGetGroupRanksErrorCallback(QNetworkReply& requestReply) { qDebug() << "******************** getGroupRanks api call failed:" << requestReply.error(); } -void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, bool isMember) { - _groupMembership[name][groupID] = isMember; +void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, int rank) { + if (rank >= 0) { + _groupMembership[name][groupID] = rank; + } else { + _groupMembership[name].remove(groupID); + } } -bool DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) { - return _groupMembership[name][groupID]; +int DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) { + const QHash& groupsForName = _groupMembership[name]; + if (groupsForName.contains(groupID)) { + return groupsForName[groupID]; + } + return -1; } QList DomainServerSettingsManager::getGroupIDs() { - QList result; - foreach (QString groupName, _groupPermissions.keys()) { - if (_groupPermissions[groupName]->isGroup()) { - result << _groupPermissions[groupName]->getGroupID(); + QSet result; + foreach (NodePermissionsKey groupKey, _groupPermissions.keys()) { + if (_groupPermissions[groupKey]->isGroup()) { + result += _groupPermissions[groupKey]->getGroupID(); } } - return result; + return result.toList(); } QList DomainServerSettingsManager::getBlacklistGroupIDs() { - QList result; - foreach (QString groupName, _groupForbiddens.keys()) { - if (_groupForbiddens[groupName]->isGroup()) { - result << _groupForbiddens[groupName]->getGroupID(); + QSet result; + foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) { + if (_groupForbiddens[groupKey]->isGroup()) { + result += _groupForbiddens[groupKey]->getGroupID(); } } - return result; + return result.toList(); +} + +void DomainServerSettingsManager::debugDumpGroupsState() { + qDebug() << "--------- GROUPS ---------"; + + qDebug() << "_groupPermissions:"; + foreach (NodePermissionsKey groupKey, _groupPermissions.keys()) { + NodePermissionsPointer perms = _groupPermissions[groupKey]; + qDebug() << "| " << groupKey << perms; + } + + qDebug() << "_groupIDs:"; + foreach (QString groupName, _groupIDs.keys()) { + qDebug() << "| " << groupName << "==>" << _groupIDs[groupName]; + } + + qDebug() << "_groupNames:"; + foreach (QUuid groupID, _groupNames.keys()) { + qDebug() << "| " << groupID << "==>" << _groupNames[groupID]; + } + + qDebug() << "_groupRanks:"; + foreach (QUuid groupID, _groupRanks.keys()) { + QVector& ranksForGroup = _groupRanks[groupID]; + QString readableRanks; + foreach (QString rankName, ranksForGroup) { + if (readableRanks == "") { + readableRanks = rankName; + } else { + readableRanks += "," + rankName; + } + } + qDebug() << "| " << groupID << "==>" << readableRanks; + } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 706717eda8..52e4b02e26 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -30,6 +30,8 @@ const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions"; const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; +using GroupByUUIDKey = QPair; + class DomainServerSettingsManager : public QObject { Q_OBJECT public: @@ -46,40 +48,46 @@ public: QVariantMap& getDescriptorsMap(); // these give access to anonymous/localhost/logged-in settings from the domain-server settings page - bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); } - NodePermissions getStandardPermissionsForName(const QString& name) const; + bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); } + NodePermissions getStandardPermissionsForName(const NodePermissionsKey& name) const; // these give access to permissions for specific user-names from the domain-server settings page - bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); } + bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name, 0); } NodePermissions getPermissionsForName(const QString& name) const; - QStringList getAllNames() { return _agentPermissions.keys(); } + NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); } + QStringList getAllNames() const; // these give access to permissions for specific groups from the domain-server settings page - bool havePermissionsForGroup(const QString& groupname) const { return _groupPermissions.contains(groupname); } - NodePermissions getPermissionsForGroup(const QString& groupname) const; - NodePermissions getPermissionsForGroup(const QUuid& groupID) const; + bool havePermissionsForGroup(const QString& groupName, int rank) const { + return _groupPermissions.contains(groupName, rank); + } + NodePermissions getPermissionsForGroup(const QString& groupName, int rank) const; + NodePermissions getPermissionsForGroup(const QUuid& groupID, int rank) const; // these remove permissions from users in certain groups - bool haveForbiddensForGroup(const QString& groupname) const { return _groupForbiddens.contains(groupname); } - NodePermissions getForbiddensForGroup(const QString& groupname) const; - NodePermissions getForbiddensForGroup(const QUuid& groupID) const; + bool haveForbiddensForGroup(const QString& groupName, int rank) const { return _groupForbiddens.contains(groupName, rank); } + NodePermissions getForbiddensForGroup(const QString& groupName, int rank) const; + NodePermissions getForbiddensForGroup(const QUuid& groupID, int rank) const; + + QStringList getAllKnownGroupNames(); + bool setGroupID(const QString& groupName, const QUuid& groupID); QList getGroupIDs(); QList getBlacklistGroupIDs(); // 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].clear(); } - void recordGroupMembership(const QString& name, const QUuid groupID, bool isMember); - bool isGroupMember(const QString& name, const QUuid& groupID); + void recordGroupMembership(const QString& name, const QUuid groupID, int rank); + int isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member signals: void updateNodePermissions(); public slots: - void getGroupIDJSONCallback(QNetworkReply& requestReply); - void getGroupIDErrorCallback(QNetworkReply& requestReply); - void getGroupRanksJSONCallback(QNetworkReply& requestReply); - void getGroupRanksErrorCallback(QNetworkReply& requestReply); + void apiGetGroupIDJSONCallback(QNetworkReply& requestReply); + void apiGetGroupIDErrorCallback(QNetworkReply& requestReply); + void apiGetGroupRanksJSONCallback(QNetworkReply& requestReply); + void apiGetGroupRanksErrorCallback(QNetworkReply& requestReply); private slots: void processSettingsRequestPacket(QSharedPointer message); @@ -105,26 +113,36 @@ private: void validateDescriptorsMap(); // these cause calls to metaverse's group api - void requestMissingGroupIDs(); - void getGroupID(const QString& groupname); - NodePermissionsPointer lookupGroupByID(const QUuid& id); - void getGroupRanks(const QUuid& groupID); + void apiRefreshGroupInformation(); + void apiGetGroupID(const QString& groupName); + void apiGetGroupRanks(const QUuid& groupID); void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath); void packPermissions(); void unpackPermissions(); + bool ensurePermissionsForGroupRanks(); - NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost + NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner NodePermissionsMap _agentPermissions; // specific account-names + NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group - QHash _groupByID; // similar to _groupPermissions but key is group-id rather than name + // these are like _groupPermissions and _groupForbiddens but with uuids rather than group-names in the keys + QHash _groupPermissionsByUUID; + QHash _groupForbiddensByUUID; + + QHash _groupIDs; // keep track of group-name to group-id mappings + QHash _groupNames; // keep track of group-id to group-name mappings // remember the responses to api/v1/groups/%1/ranks - QHash> _groupRanks; + QHash> _groupRanks; // QHash> + QHash _groupRanksLastFetched; // when did we last update _groupRanks // keep track of answers to api queries about which users are in which groups - QHash> _groupMembership; + QHash> _groupMembership; // QHash> + + + void debugDumpGroupsState(); }; #endif // hifi_DomainServerSettingsManager_h diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index 023e3cb04a..e16b746b96 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -13,22 +13,27 @@ #include #include "NodePermissions.h" -QString NodePermissions::standardNameLocalhost = QString("localhost"); -QString NodePermissions::standardNameLoggedIn = QString("logged-in"); -QString NodePermissions::standardNameAnonymous = QString("anonymous"); -QString NodePermissions::standardNameFriends = QString("friends"); +NodePermissionsKey NodePermissions::standardNameLocalhost = NodePermissionsKey("localhost", 0); +NodePermissionsKey NodePermissions::standardNameLoggedIn = NodePermissionsKey("logged-in", 0); +NodePermissionsKey NodePermissions::standardNameAnonymous = NodePermissionsKey("anonymous", 0); +NodePermissionsKey NodePermissions::standardNameFriends = NodePermissionsKey("friends", 0); QStringList NodePermissions::standardNames = QList() - << NodePermissions::standardNameLocalhost - << NodePermissions::standardNameLoggedIn - << NodePermissions::standardNameAnonymous - << NodePermissions::standardNameFriends; + << NodePermissions::standardNameLocalhost.first + << NodePermissions::standardNameLoggedIn.first + << NodePermissions::standardNameAnonymous.first + << NodePermissions::standardNameFriends.first; NodePermissions::NodePermissions(QMap perms) { - _id = perms["permissions_id"].toString(); + _id = perms["permissions_id"].toString().toLower(); if (perms.contains("group_id")) { - _groupIDSet = true; _groupID = perms["group_id"].toUuid(); + if (!_groupID.isNull()) { + _groupIDSet = true; + } + } + if (perms.contains("rank")) { + _rank = perms["rank"].toInt(); } permissions = NodePermissions::Permissions(); @@ -41,11 +46,15 @@ NodePermissions::NodePermissions(QMap perms) { Permission::canConnectPastMaxCapacity : Permission::none; } -QVariant NodePermissions::toVariant() { +QVariant NodePermissions::toVariant(QVector rankNames) { QMap values; values["permissions_id"] = _id; if (_groupIDSet) { values["group_id"] = _groupID; + values["rank"] = _rank; + if (rankNames.size() > _rank) { + values["rank_name"] = rankNames[_rank]; + } } values["id_can_connect"] = can(Permission::canConnectToDomain); values["id_can_adjust_locks"] = can(Permission::canAdjustLocks); @@ -137,7 +146,9 @@ QDataStream& operator>>(QDataStream& in, NodePermissions& perms) { } QDebug operator<<(QDebug debug, const NodePermissions& perms) { - debug.nospace() << "[permissions: " << perms.getID() << " --"; + debug.nospace() << "[permissions: " << perms.getID() << "/" << perms.getUserName() << " -- "; + debug.nospace() << "rank=" << perms.getRank() + << ", groupID=" << perms.getGroupID() << "/" << (perms.isGroup() ? "y" : "n"); if (perms.can(NodePermissions::Permission::canConnectToDomain)) { debug << " connect"; } diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 1dea560543..411312f708 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -20,30 +20,35 @@ class NodePermissions; using NodePermissionsPointer = std::shared_ptr; +using NodePermissionsKey = QPair; +using NodePermissionsKeyList = QList>; class NodePermissions { public: - NodePermissions() { _id = QUuid::createUuid().toString(); } - NodePermissions(const QString& name) { _id = name.toLower(); } + NodePermissions() { _id = QUuid::createUuid().toString(); _rank = 0; } + NodePermissions(const QString& name) { _id = name.toLower(); _rank = 0; } + NodePermissions(const NodePermissionsKey& key) { _id = key.first.toLower(); _rank = key.second; } NodePermissions(QMap perms); - QString getID() const { return _id; } + QString getID() const { return _id; } // a user-name or a group-name, not verified + int getRank() const { return _rank; } + NodePermissionsKey getKey() const { return NodePermissionsKey(_id, _rank); } - // the _id member isn't authenticated and _username is. + // the _id member isn't authenticated/verified and _username is. void setUserName(QString userName) { _userName = userName.toLower(); } - QString getUserName() { return _userName; } + QString getUserName() const { return _userName; } - void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; } } - QUuid getGroupID() { return _groupID; } - bool isGroup() { return _groupIDSet; } + void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }} + QUuid getGroupID() const { return _groupID; } + bool isGroup() const { return _groupIDSet; } bool isAssignment { false }; // these 3 names have special meaning. - static QString standardNameLocalhost; - static QString standardNameLoggedIn; - static QString standardNameAnonymous; - static QString standardNameFriends; + static NodePermissionsKey standardNameLocalhost; + static NodePermissionsKey standardNameLoggedIn; + static NodePermissionsKey standardNameAnonymous; + static NodePermissionsKey standardNameFriends; static QStringList standardNames; enum class Permission { @@ -58,7 +63,7 @@ public: Q_DECLARE_FLAGS(Permissions, Permission) Permissions permissions; - QVariant toVariant(); + QVariant toVariant(QVector rankNames = QVector()); void setAll(bool value); @@ -76,6 +81,7 @@ public: protected: QString _id; + int _rank { 0 }; // 0 unless this is for a group QString _userName; bool _groupIDSet { false }; @@ -88,15 +94,28 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(NodePermissions::Permissions) class NodePermissionsMap { public: NodePermissionsMap() { } - NodePermissionsPointer& operator[](const QString& key) { return _data[key.toLower()]; } - NodePermissionsPointer operator[](const QString& key) const { return _data.value(key.toLower()); } - bool contains(const QString& key) const { return _data.contains(key.toLower()); } - QList keys() const { return _data.keys(); } - QHash get() { return _data; } + NodePermissionsPointer& operator[](const NodePermissionsKey& key) { + NodePermissionsKey dataKey(key.first.toLower(), key.second); + if (!_data.contains(dataKey)) { + _data[dataKey] = NodePermissionsPointer(new NodePermissions(key)); + } + return _data[dataKey]; + } + NodePermissionsPointer operator[](const NodePermissionsKey& key) const { + return _data.value(NodePermissionsKey(key.first.toLower(), key.second)); + } + bool contains(const NodePermissionsKey& key) const { + return _data.contains(NodePermissionsKey(key.first.toLower(), key.second)); + } + bool contains(const QString& keyFirst, int keySecond) const { + return _data.contains(NodePermissionsKey(keyFirst.toLower(), keySecond)); + } + QList keys() const { return _data.keys(); } + QHash get() { return _data; } void clear() { _data.clear(); } private: - QHash _data; + QHash _data; };