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