diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 23a4302d2c..b24acd1bce 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -534,6 +534,77 @@ } ], + "columns": [ + { + "name": "permissions_id", + "label": "Group Name" + }, + { + "name": "group_id", + "label": "Group ID", + "readonly": true + }, + { + "name": "id_can_connect", + "label": "Connect", + "type": "checkbox", + "editable": true, + "default": true + }, + { + "name": "id_can_adjust_locks", + "label": "Lock / Unlock", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez", + "label": "Rez", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp", + "label": "Rez Temporary", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_write_to_asset_server", + "label": "Write Assets", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_connect_past_max_capacity", + "label": "Ignore Max Capacity", + "type": "checkbox", + "editable": true, + "default": false + } + ] + }, + { + "name": "group_forbiddens", + "type": "table", + "caption": "Permissions denied to Users in Groups", + "can_add_new_rows": true, + + "groups": [ + { + "label": "Group", + "span": 2 + }, + { + "label": "Permissions ?", + "span": 6 + } + ], + "columns": [ { "name": "permissions_id", diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 90d0d0adca..b5372ea9bc 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -149,15 +149,21 @@ 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.getKnownGroupIDs()) { - if (groupID.isNull()) { - continue; - } + foreach (QUuid groupID, _server->_settingsManager.getGroupIDs()) { if (_server->_settingsManager.isGroupMember(verifiedUsername, groupID)) { userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID); qDebug() << "user-permissions: user is in group:" << groupID << "so:" << userPerms; } } + + // 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; + } + } + } } @@ -689,7 +695,7 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer void DomainGatekeeper::getGroupMemberships(const QString& username) { // loop through the groups mentioned on the settings page and ask if this user is in each. The replies // will be received asynchronously and permissions will be updated as the answers come in. - QList groupIDs = _server->_settingsManager.getKnownGroupIDs(); + QList groupIDs = _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs(); foreach (QUuid groupID, groupIDs) { if (groupID.isNull()) { continue; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 78a557ba5b..2411dbdf09 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -328,6 +328,9 @@ void DomainServerSettingsManager::packPermissions() { // save settings for groups packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH); + // save settings for blacklist groups + packPermissionsForMap("permissions", _groupForbiddens, GROUP_FORBIDDENS_KEYPATH); + persistToFile(); _configMap.loadMasterAndUserConfig(_argumentList); } @@ -338,6 +341,7 @@ void DomainServerSettingsManager::unpackPermissions() { _standardAgentPermissions.clear(); _agentPermissions.clear(); _groupPermissions.clear(); + _groupForbiddens.clear(); bool foundLocalhost = false; bool foundAnonymous = false; @@ -363,6 +367,12 @@ void DomainServerSettingsManager::unpackPermissions() { groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH, true); (*groupPermissions) = QVariantList(); } + QVariant* groupForbiddens = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH); + if (!groupForbiddens || !groupForbiddens->canConvert(QMetaType::QVariantList)) { + qDebug() << "failed to extract group forbiddens from settings."; + groupForbiddens = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH, true); + (*groupForbiddens) = QVariantList(); + } QList standardPermissionsList = standardPermissions->toList(); foreach (QVariant permsHash, standardPermissionsList) { @@ -411,6 +421,23 @@ void DomainServerSettingsManager::unpackPermissions() { } } + QList groupForbiddensList = groupForbiddens->toList(); + foreach (QVariant permsHash, groupForbiddensList) { + NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; + QString id = perms->getID(); + if (_groupForbiddens.contains(id)) { + qDebug() << "duplicate name in group forbiddens table: " << id; + _groupForbiddens[id] |= perms; + needPack = true; + } else { + _groupForbiddens[id] = perms; + } + if (perms->isGroup()) { + // the group-id was cached. hook-up the id in the id->group hash + _groupByID[perms->getGroupID()] = _groupForbiddens[id]; + } + } + // if any of the standard names are missing, add them if (!foundLocalhost) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; @@ -445,7 +472,8 @@ void DomainServerSettingsManager::unpackPermissions() { #ifdef WANT_DEBUG qDebug() << "--------------- permissions ---------------------"; QList> permissionsSets; - permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() << _groupPermissions.get(); + permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() + << _groupPermissions.get() << _groupForbiddens.get(); foreach (auto permissionSet, permissionsSets) { QHashIterator i(permissionSet); while (i.hasNext()) { @@ -499,6 +527,27 @@ NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QUuid& } +NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QString& groupname) const { + if (_groupForbiddens.contains(groupname)) { + return *(_groupForbiddens[groupname].get()); + } + NodePermissions nullForbiddens; + nullForbiddens.setAll(false); + return nullForbiddens; +} + +NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& groupID) const { + if (!_groupByID.contains(groupID)) { + NodePermissions nullForbiddens; + nullForbiddens.setAll(false); + return nullForbiddens; + } + QString groupName = _groupByID[groupID]->getID(); + return getForbiddensForGroup(groupName); +} + + + QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); @@ -920,10 +969,22 @@ void DomainServerSettingsManager::persistToFile() { } void DomainServerSettingsManager::requestMissingGroupIDs() { - QHashIterator i(_groupPermissions.get()); - while (i.hasNext()) { - i.next(); - NodePermissionsPointer perms = i.value(); + 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 + continue; + } + + // make a call to metaverse api to turn the group name into a group ID + getGroupID(perms->getID()); + } + 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; @@ -964,10 +1025,21 @@ void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestR QString groupName = jsonObject["group_name"].toString(); QUuid groupID = QUuid(jsonObject["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) { packPermissions(); } else { qDebug() << "DomainServerSettingsManager::getGroupIDJSONCallback got response for unknown group:" << groupName; @@ -988,3 +1060,23 @@ void DomainServerSettingsManager::recordGroupMembership(const QString& name, con bool DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) { return _groupMembership[name][groupID]; } + +QList DomainServerSettingsManager::getGroupIDs() { + QList result; + foreach (QString groupName, _groupPermissions.keys()) { + if (_groupPermissions[groupName]->isGroup()) { + result << _groupPermissions[groupName]->getGroupID(); + } + } + return result; +} + +QList DomainServerSettingsManager::getBlacklistGroupIDs() { + QList result; + foreach (QString groupName, _groupForbiddens.keys()) { + if (_groupForbiddens[groupName]->isGroup()) { + result << _groupForbiddens[groupName]->getGroupID(); + } + } + return result; +} diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index a67f3d4338..7354955684 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -28,6 +28,7 @@ const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions"; +const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; class DomainServerSettingsManager : public QObject { Q_OBJECT @@ -55,7 +56,14 @@ public: bool havePermissionsForGroup(const QString& groupname) const { return _groupPermissions.contains(groupname); } NodePermissions getPermissionsForGroup(const QString& groupname) const; NodePermissions getPermissionsForGroup(const QUuid& groupID) const; - QList getKnownGroupIDs() { return _groupByID.keys(); } + + // 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; + + QList getGroupIDs(); + QList getBlacklistGroupIDs(); // these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api void recordGroupMembership(const QString& name, const QUuid groupID, bool isMember); @@ -100,7 +108,8 @@ private: NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost NodePermissionsMap _agentPermissions; // specific account-names - NodePermissionsMap _groupPermissions; // permissions granted by membershipt to specific groups + 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 // keep track of answers to api queries about which users are in which groups diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index f0c25a764a..a6e502ff59 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -85,6 +85,48 @@ NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermis return lhs; } +NodePermissions& NodePermissions::operator&=(const NodePermissions& rhs) { + this->canConnectToDomain &= rhs.canConnectToDomain; + this->canAdjustLocks &= rhs.canAdjustLocks; + this->canRezPermanentEntities &= rhs.canRezPermanentEntities; + this->canRezTemporaryEntities &= rhs.canRezTemporaryEntities; + this->canWriteToAssetServer &= rhs.canWriteToAssetServer; + this->canConnectPastMaxCapacity &= rhs.canConnectPastMaxCapacity; + return *this; +} +NodePermissions& NodePermissions::operator&=(const NodePermissionsPointer& rhs) { + if (rhs) { + *this &= *rhs.get(); + } + return *this; +} +NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs) { + if (lhs && rhs) { + *lhs.get() &= rhs; + } + return lhs; +} + +NodePermissions NodePermissions::operator~() { + NodePermissions result = *this; + result.canConnectToDomain = !result.canConnectToDomain; + result.canAdjustLocks = !result.canAdjustLocks; + result.canRezPermanentEntities = !result.canRezPermanentEntities; + result.canRezTemporaryEntities = !result.canRezTemporaryEntities; + result.canWriteToAssetServer = !result.canWriteToAssetServer; + result.canConnectPastMaxCapacity = !result.canConnectPastMaxCapacity; + return result; +} + +NodePermissionsPointer operator~(NodePermissionsPointer& lhs) { + if (lhs) { + NodePermissionsPointer result { new NodePermissions }; + (*result.get()) = ~(*lhs.get()); + return result; + } + return lhs; +} + QDataStream& operator<<(QDataStream& out, const NodePermissions& perms) { out << perms.canConnectToDomain; out << perms.canAdjustLocks; diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 7971d4e362..e0713e5ce3 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -60,6 +60,9 @@ public: NodePermissions& operator|=(const NodePermissions& rhs); NodePermissions& operator|=(const NodePermissionsPointer& rhs); + NodePermissions& operator&=(const NodePermissions& rhs); + NodePermissions& operator&=(const NodePermissionsPointer& rhs); + NodePermissions operator~(); friend QDataStream& operator<<(QDataStream& out, const NodePermissions& perms); friend QDataStream& operator>>(QDataStream& in, NodePermissions& perms); @@ -93,5 +96,7 @@ const NodePermissions DEFAULT_AGENT_PERMISSIONS; QDebug operator<<(QDebug debug, const NodePermissions& perms); QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms); NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs); +NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs); +NodePermissionsPointer operator~(NodePermissionsPointer& lhs); #endif // hifi_NodePermissions_h