From 22ca34590a67356fcabb4938bbb01d972b860be9 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 16 Jun 2016 16:39:47 -0700 Subject: [PATCH 01/56] add domain-server settings-page UI for groups --- .../resources/describe-settings.json | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bad24dd3a1..a12e83df78 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -176,7 +176,7 @@ "groups": [ { - "label": "User / Group", + "label": "Type of User", "span": 1 }, { @@ -245,7 +245,7 @@ "groups": [ { - "label": "User / Group", + "label": "User", "span": 1 }, { @@ -302,6 +302,77 @@ "default": false } ] + }, + { + "name": "group_permissions", + "type": "table", + "caption": "Permissions for Users in Groups", + "can_add_new_rows": true, + + "groups": [ + { + "label": "Group", + "span": 2 + }, + { + "label": "Permissions ?", + "span": 6 + } + ], + + "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 + } + ] } ] }, From 3072a678393b9f148b4a8aa16b9666d44c19e5f0 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 16 Jun 2016 16:40:07 -0700 Subject: [PATCH 02/56] center help test above group-permissions table --- domain-server/resources/web/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 2862feed87..b9fc8b16e7 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -133,7 +133,7 @@ table .headers + .headers td { color: #222; } -table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td { +table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td, table[name="security.group_permissions"] .headers td + td { text-align: center; } From 52e3be4f5d4265f1ab29ca527e13368b8949096c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 16 Jun 2016 16:40:36 -0700 Subject: [PATCH 03/56] allow for read-only text-fields in table input rows --- domain-server/resources/web/settings/js/settings.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index aecc48b31f..48312e0e84 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1,6 +1,7 @@ var Settings = { showAdvanced: false, - METAVERSE_URL: 'https://metaverse.highfidelity.com', + // METAVERSE_URL: 'https://metaverse.highfidelity.com', + METAVERSE_URL: 'http://localhost:3000', ADVANCED_CLASS: 'advanced-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', @@ -1065,7 +1066,7 @@ function makeTableInputs(setting) { } else { html += "\ \ + value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'" + (col.readonly ? " readonly" : "") + ">\ " } }) From d184aade1b41407034a3a849306f871eaf01d55c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 16 Jun 2016 16:40:57 -0700 Subject: [PATCH 04/56] comments --- domain-server/src/DomainGatekeeper.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index c4a7d1a425..74269a5794 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -262,8 +262,14 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect qDebug() << "user-permissions: no username, so:" << userPerms; } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // they are sent us a username and the signature verifies it + + + // TODO check groups here + + if (_server->_settingsManager.havePermissionsForName(username)) { - // we have specific permissions for this user. + // we have specific permissions for this user. Discard any other permissions and set this + // agent's permissions to be exactly what's indicated on their row of the permissions-grid. userPerms = _server->_settingsManager.getPermissionsForName(username); qDebug() << "user-permissions: specific user matches, so:" << userPerms; } else { From dd24143d16c05ca9118a61e5f7c457b50212aa42 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 16 Jun 2016 16:41:47 -0700 Subject: [PATCH 05/56] start on handling group permissions --- .../src/DomainServerSettingsManager.cpp | 96 ++++++++++++++++++- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 5790eb9178..af0d5afab6 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -269,19 +270,21 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList void DomainServerSettingsManager::packPermissionsForMap(QString mapName, QHash agentPermissions, QString keyPath) { + // find (or create) the "security" section of the settings map QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); if (!security || !security->canConvert(QMetaType::QVariantMap)) { security = valueForKeyPath(_configMap.getUserConfig(), "security", true); (*security) = QVariantMap(); } - // save settings for anonymous / logged-in / localhost + // find (or create) whichever subsection of "security" we are packing QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true); (*permissions) = QVariantList(); } + // convert details for each member of the section QVariantList* permissionsList = reinterpret_cast(permissions); (*permissionsList).clear(); foreach (QString userName, agentPermissions.keys()) { @@ -291,11 +294,16 @@ void DomainServerSettingsManager::packPermissionsForMap(QString mapName, void DomainServerSettingsManager::packPermissions() { // transfer details from _agentPermissions to _configMap + + // save settings for anonymous / logged-in / localhost packPermissionsForMap("standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH); // save settings for specific users packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); + // save settings for groups + packPermissionsForMap("permissions", _groupPermissions, AGENT_PERMISSIONS_KEYPATH); + persistToFile(); _configMap.loadMasterAndUserConfig(_argumentList); } @@ -305,6 +313,7 @@ void DomainServerSettingsManager::unpackPermissions() { _standardAgentPermissions.clear(); _agentPermissions.clear(); + _groupPermissions.clear(); bool foundLocalhost = false; bool foundAnonymous = false; @@ -323,6 +332,12 @@ void DomainServerSettingsManager::unpackPermissions() { permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH, true); (*permissions) = QVariantList(); } + QVariant* groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH); + if (!groupPermissions || !groupPermissions->canConvert(QMetaType::QVariantList)) { + qDebug() << "failed to extract group permissions from settings."; + groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH, true); + (*groupPermissions) = QVariantList(); + } QList standardPermissionsList = standardPermissions->toList(); foreach (QVariant permsHash, standardPermissionsList) { @@ -353,6 +368,19 @@ void DomainServerSettingsManager::unpackPermissions() { } } + QList groupPermissionsList = groupPermissions->toList(); + foreach (QVariant permsHash, groupPermissionsList) { + NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; + QString id = perms->getID(); + if (_groupPermissions.contains(id)) { + qDebug() << "duplicate name in group permissions table: " << id; + _groupPermissions[id] |= perms; + needPack = true; + } else { + _groupPermissions[id] = perms; + } + } + // if any of the standard names are missing, add them if (!foundLocalhost) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; @@ -375,16 +403,24 @@ void DomainServerSettingsManager::unpackPermissions() { packPermissions(); } + // attempt to retrieve any missing group-IDs + requestMissingGroupIDs(); + + #ifdef WANT_DEBUG qDebug() << "--------------- permissions ---------------------"; QList> permissionsSets; - permissionsSets << _standardAgentPermissions << _agentPermissions; + permissionsSets << _standardAgentPermissions << _agentPermissions << _groupPermissions; foreach (auto permissionSet, permissionsSets) { QHashIterator i(permissionSet); while (i.hasNext()) { i.next(); NodePermissionsPointer perms = i.value(); - qDebug() << i.key() << perms; + if (perms->isGroup()) { + qDebug() << i.key() << perms->getGroupID() << perms; + } else { + qDebug() << i.key() << perms; + } } } #endif @@ -827,3 +863,57 @@ void DomainServerSettingsManager::persistToFile() { qCritical("Could not write to JSON settings file. Unable to persist settings."); } } + +void DomainServerSettingsManager::requestMissingGroupIDs() { + QHashIterator i(_groupPermissions); + while (i.hasNext()) { + i.next(); + NodePermissionsPointer perms = i.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()); + } +} + +void DomainServerSettingsManager::getGroupID(const QString& groupname) { + JSONCallbackParameters callbackParams; + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "getGroupIDJSONCallback"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "getGroupIDErrorCallback"; + + const QString GET_GROUP_ID_PATH = "api/v1/get_group_id/%1"; + + qDebug() << "Requesting group ID for group named" << groupname; + + DependencyManager::get()->sendRequest(GET_GROUP_ID_PATH.arg(groupname), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, callbackParams); +} + +void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + + qDebug() << "GOT RESPONSE" << jsonObject["group_id"].toString(); + + if (jsonObject["status"].toString() == "success") { + QString groupName = jsonObject["group_name"].toString(); + QUuid groupID = QUuid(jsonObject["group_id"].toString()); + + if (!_groupPermissions.contains(groupName)) { + qDebug() << "DomainServerSettingsManager::getGroupIDJSONCallback got response for unknown group:" << groupName; + } + + _groupPermissions[groupName]->setGroupID(groupID); + } else { + // XXX what? + } +} + +void DomainServerSettingsManager::getGroupIDErrorCallback(QNetworkReply& requestReply) { + qDebug() << "ERROR" << requestReply.error(); +} From 6fae35d9f293d027f5c7e2bbbadd84857487e126 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 16 Jun 2016 16:42:15 -0700 Subject: [PATCH 06/56] start on handling group permissions --- domain-server/src/DomainServerSettingsManager.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 446e9a2eed..ca9973ba5d 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -27,6 +27,7 @@ const QString SETTINGS_PATH = "/settings"; 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"; class DomainServerSettingsManager : public QObject { Q_OBJECT @@ -50,6 +51,9 @@ public: signals: void updateNodePermissions(); +public slots: + void getGroupIDJSONCallback(QNetworkReply& requestReply); + void getGroupIDErrorCallback(QNetworkReply& requestReply); private slots: void processSettingsRequestPacket(QSharedPointer message); @@ -72,11 +76,15 @@ private: friend class DomainServer; + void requestMissingGroupIDs(); + void getGroupID(const QString& groupname); + void packPermissionsForMap(QString mapName, QHash agentPermissions, QString keyPath); void packPermissions(); void unpackPermissions(); QHash _standardAgentPermissions; // anonymous, logged-in, localhost QHash _agentPermissions; // specific account-names + QHash _groupPermissions; // groups }; #endif // hifi_DomainServerSettingsManager_h From c80ca825b8be0de8ee10ab76bbcab18c70abe57b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 16 Jun 2016 16:43:08 -0700 Subject: [PATCH 07/56] start on handling group permissions --- libraries/networking/src/NodePermissions.cpp | 40 +++++++++++++++++++- libraries/networking/src/NodePermissions.h | 38 +++++-------------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index fb74ccdc94..9f77dd9d21 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -22,6 +22,45 @@ QStringList NodePermissions::standardNames = QList() << NodePermissions::standardNameLoggedIn << NodePermissions::standardNameAnonymous; +NodePermissions::NodePermissions(QMap perms) { + _id = perms["permissions_id"].toString(); + if (perms.contains("group_id")) { + _groupIDSet = true; + _groupID = perms["group_id"].toUuid(); + } + + canConnectToDomain = perms["id_can_connect"].toBool(); + canAdjustLocks = perms["id_can_adjust_locks"].toBool(); + canRezPermanentEntities = perms["id_can_rez"].toBool(); + canRezTemporaryEntities = perms["id_can_rez_tmp"].toBool(); + canWriteToAssetServer = perms["id_can_write_to_asset_server"].toBool(); + canConnectPastMaxCapacity = perms["id_can_connect_past_max_capacity"].toBool(); +} + +QVariant NodePermissions::toVariant() { + QMap values; + values["permissions_id"] = _id; + if (_groupIDSet) { + values["group_id"] = _groupID; + } + values["id_can_connect"] = canConnectToDomain; + values["id_can_adjust_locks"] = canAdjustLocks; + values["id_can_rez"] = canRezPermanentEntities; + values["id_can_rez_tmp"] = canRezTemporaryEntities; + values["id_can_write_to_asset_server"] = canWriteToAssetServer; + values["id_can_connect_past_max_capacity"] = canConnectPastMaxCapacity; + return QVariant(values); +} + +void NodePermissions::setAll(bool value) { + canConnectToDomain = value; + canAdjustLocks = value; + canRezPermanentEntities = value; + canRezTemporaryEntities = value; + canWriteToAssetServer = value; + canConnectPastMaxCapacity = value; +} + NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) { this->canConnectToDomain |= rhs.canConnectToDomain; this->canAdjustLocks |= rhs.canAdjustLocks; @@ -44,7 +83,6 @@ NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermis 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 c153878a7e..a18ccdf97d 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -25,15 +25,7 @@ class NodePermissions { public: NodePermissions() { _id = QUuid::createUuid().toString(); } NodePermissions(const QString& name) { _id = name; } - NodePermissions(QMap perms) { - _id = perms["permissions_id"].toString(); - canConnectToDomain = perms["id_can_connect"].toBool(); - canAdjustLocks = perms["id_can_adjust_locks"].toBool(); - canRezPermanentEntities = perms["id_can_rez"].toBool(); - canRezTemporaryEntities = perms["id_can_rez_tmp"].toBool(); - canWriteToAssetServer = perms["id_can_write_to_asset_server"].toBool(); - canConnectPastMaxCapacity = perms["id_can_connect_past_max_capacity"].toBool(); - } + NodePermissions(QMap perms); QString getID() const { return _id; } @@ -41,6 +33,10 @@ public: void setUserName(QString userName) { _userName = userName; } QString getUserName() { return _userName; } + void setGroupID(QUuid groupID) { _groupID = groupID; } + QUuid getGroupID() { return _groupID; } + bool isGroup() { return _groupIDSet; } + bool isAssignment { false }; // these 3 names have special meaning. @@ -57,26 +53,9 @@ public: bool canWriteToAssetServer { false }; bool canConnectPastMaxCapacity { false }; - void setAll(bool value) { - canConnectToDomain = value; - canAdjustLocks = value; - canRezPermanentEntities = value; - canRezTemporaryEntities = value; - canWriteToAssetServer = value; - canConnectPastMaxCapacity = value; - } + QVariant toVariant(); - QVariant toVariant() { - QMap values; - values["permissions_id"] = _id; - values["id_can_connect"] = canConnectToDomain; - values["id_can_adjust_locks"] = canAdjustLocks; - values["id_can_rez"] = canRezPermanentEntities; - values["id_can_rez_tmp"] = canRezTemporaryEntities; - values["id_can_write_to_asset_server"] = canWriteToAssetServer; - values["id_can_connect_past_max_capacity"] = canConnectPastMaxCapacity; - return QVariant(values); - } + void setAll(bool value); NodePermissions& operator|=(const NodePermissions& rhs); NodePermissions& operator|=(const NodePermissionsPointer& rhs); @@ -86,6 +65,9 @@ public: protected: QString _id; QString _userName; + + bool _groupIDSet { false }; + QUuid _groupID; }; const NodePermissions DEFAULT_AGENT_PERMISSIONS; From 3fc9bdb3d00fe3e7e5dffd1b574e96cf09f3f739 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 16 Jun 2016 16:44:18 -0700 Subject: [PATCH 08/56] undo accidental change --- domain-server/resources/web/settings/js/settings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 48312e0e84..5fc126fbea 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1,7 +1,6 @@ var Settings = { showAdvanced: false, - // METAVERSE_URL: 'https://metaverse.highfidelity.com', - METAVERSE_URL: 'http://localhost:3000', + METAVERSE_URL: 'https://metaverse.highfidelity.com', ADVANCED_CLASS: 'advanced-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', From de100e81417c2839bdfb297675ffaf9f2ca84877 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 17 Jun 2016 13:29:42 -0700 Subject: [PATCH 09/56] after receiving a group's ID from api, save it --- .../src/DomainServerSettingsManager.cpp | 16 ++++++++-------- libraries/networking/src/NodePermissions.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index af0d5afab6..87803895a9 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -302,7 +302,7 @@ void DomainServerSettingsManager::packPermissions() { packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); // save settings for groups - packPermissionsForMap("permissions", _groupPermissions, AGENT_PERMISSIONS_KEYPATH); + packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH); persistToFile(); _configMap.loadMasterAndUserConfig(_argumentList); @@ -898,22 +898,22 @@ void DomainServerSettingsManager::getGroupID(const QString& groupname) { void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestReply) { QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - qDebug() << "GOT RESPONSE" << jsonObject["group_id"].toString(); - if (jsonObject["status"].toString() == "success") { QString groupName = jsonObject["group_name"].toString(); QUuid groupID = QUuid(jsonObject["group_id"].toString()); - if (!_groupPermissions.contains(groupName)) { + if (_groupPermissions.contains(groupName)) { + qDebug() << "ID for group:" << groupName << "is" << groupID; + _groupPermissions[groupName]->setGroupID(groupID); + packPermissions(); + } else { qDebug() << "DomainServerSettingsManager::getGroupIDJSONCallback got response for unknown group:" << groupName; } - - _groupPermissions[groupName]->setGroupID(groupID); } else { - // XXX what? + qDebug() << "getGroupID api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); } } void DomainServerSettingsManager::getGroupIDErrorCallback(QNetworkReply& requestReply) { - qDebug() << "ERROR" << requestReply.error(); + qDebug() << "getGroupID api call failed:" << requestReply.error(); } diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index a18ccdf97d..9c8f4ace44 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -33,7 +33,7 @@ public: void setUserName(QString userName) { _userName = userName; } QString getUserName() { return _userName; } - void setGroupID(QUuid groupID) { _groupID = groupID; } + void setGroupID(QUuid groupID) { _groupID = groupID; _groupIDSet = true; } QUuid getGroupID() { return _groupID; } bool isGroup() { return _groupIDSet; } From e606ffb002f6f0964796107d739e328b33b9199d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 17 Jun 2016 18:12:03 -0700 Subject: [PATCH 10/56] use constant rather than hard-coding metaverse url --- domain-server/resources/web/settings/js/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 5fc126fbea..9d1719adbe 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -617,7 +617,7 @@ function setupPlacesTable() { label: 'Places', html_id: Settings.PLACES_TABLE_ID, help: "The following places currently point to this domain.
To point places to this domain, " - + " go to the My Places " + + " go to the My Places " + "page in your High Fidelity Metaverse account.", read_only: true, columns: [ @@ -759,7 +759,7 @@ function chooseFromHighFidelityDomains(clickedButton) { modal_buttons["success"] = { label: 'Create new domain', callback: function() { - window.open("https://metaverse.highfidelity.com/user/domains", '_blank'); + window.open(Settings.METAVERSE_URL + "/user/domains", '_blank'); } } modal_body = "

You do not have any domains in your High Fidelity account." + From af52f6da823bbd87aef644af250766aa86608b60 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 20 Jun 2016 13:59:56 -0700 Subject: [PATCH 11/56] remove some duplicate the's --- domain-server/resources/describe-settings.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index a12e83df78..4143e6fc8a 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -170,7 +170,7 @@ "name": "standard_permissions", "type": "table", "label": "Domain-Wide User Permissions", - "help": "Indicate which users or groups can have which domain-wide permissions.", + "help": "Indicate which users or groups can have which domain-wide permissions.", "caption": "Standard Permissions", "can_add_new_rows": false, @@ -180,7 +180,7 @@ "span": 1 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], @@ -249,7 +249,7 @@ "span": 1 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], @@ -315,7 +315,7 @@ "span": 2 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], From 82dd36459d81ba7a69a055309e24a09957e975d8 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 20 Jun 2016 14:02:04 -0700 Subject: [PATCH 12/56] apply a group's permissions to users who belong to the group --- domain-server/src/DomainGatekeeper.cpp | 139 ++++++++++++++++--------- 1 file changed, 92 insertions(+), 47 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 74269a5794..213a0a1cc4 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -120,6 +120,45 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost); + qDebug() << "user-permissions: is local user, so:" << userPerms; + } + + if (verifiedUsername.isEmpty()) { + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); + qDebug() << "user-permissions: unverified or no username, so:" << userPerms; + } else { + userPerms.setUserName(verifiedUsername); + if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) { + userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); + qDebug() << "user-permissions: specific user matches, so:" << userPerms; + } else { + // they are logged into metaverse, but we don't have specific permissions for them. + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); + qDebug() << "user-permissions: user is logged-into metavers, so:" << userPerms; + + // 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; + } + if (_server->_settingsManager.isGroupMember(verifiedUsername, groupID)) { + userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID); + qDebug() << "user-permissions: user is in group:" << groupID << "so:" << userPerms; + } + } + } + } + + qDebug() << "user-permissions: final:" << userPerms; + return userPerms; +} + void DomainGatekeeper::updateNodePermissions() { // If the permissions were changed on the domain-server webpage (and nothing else was), a restart isn't required -- // we reprocess the permissions map and update the nodes here. The node list is frequently sent out to all @@ -129,6 +168,8 @@ void DomainGatekeeper::updateNodePermissions() { auto limitedNodeList = DependencyManager::get(); limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){ + // 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); @@ -140,24 +181,10 @@ void DomainGatekeeper::updateNodePermissions() { userPerms.canRezTemporaryEntities = true; } else { // this node is an agent - userPerms.setAll(false); - const QHostAddress& addr = node->getLocalSocket().getAddress(); bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() || addr == QHostAddress::LocalHost); - if (isLocalUser) { - userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost); - } - - if (username.isEmpty()) { - userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); - } else { - if (_server->_settingsManager.havePermissionsForName(username)) { - userPerms = _server->_settingsManager.getPermissionsForName(username); - } else { - userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); - } - } + userPerms = applyPermissionsForUser(isLocalUser, userPerms, username); } node->setPermissions(userPerms); @@ -241,52 +268,28 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress(); bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); - if (isLocalUser) { - userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost); - qDebug() << "user-permissions: is local user, so:" << userPerms; - } if (!username.isEmpty() && usernameSignature.isEmpty()) { // user is attempting to prove their identity to us, but we don't have enough information sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); // ask for their public key right now to make sure we have it requestUserPublicKey(username); - if (!userPerms.canConnectToDomain) { - return SharedNodePointer(); - } + return SharedNodePointer(); } - if (username.isEmpty()) { - // they didn't tell us who they are - userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); - qDebug() << "user-permissions: no username, so:" << userPerms; - } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { + QString verifiedUsername; + if (!username.isEmpty() && verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // they are sent us a username and the signature verifies it - - - // TODO check groups here - - - if (_server->_settingsManager.havePermissionsForName(username)) { - // we have specific permissions for this user. Discard any other permissions and set this - // agent's permissions to be exactly what's indicated on their row of the permissions-grid. - userPerms = _server->_settingsManager.getPermissionsForName(username); - qDebug() << "user-permissions: specific user matches, so:" << userPerms; - } else { - // they are logged into metaverse, but we don't have specific permissions for them. - userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); - qDebug() << "user-permissions: user is logged in, so:" << userPerms; - } userPerms.setUserName(username); - } else { + verifiedUsername = username; + getGroupMemberships(username); + } else if (!username.isEmpty()) { // they sent us a username, but it didn't check out requestUserPublicKey(username); - if (!userPerms.canConnectToDomain) { - return SharedNodePointer(); - } + return SharedNodePointer(); } - qDebug() << "user-permissions: final:" << userPerms; + userPerms = applyPermissionsForUser(isLocalUser, userPerms, verifiedUsername); if (!userPerms.canConnectToDomain) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", @@ -651,3 +654,45 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr()); } } + +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(); + foreach (QUuid groupID, groupIDs) { + if (groupID.isNull()) { + continue; + } + getIsGroupMember(username, groupID); + } +} + +void DomainGatekeeper::getIsGroupMember(const QString& username, const QUuid groupID) { + JSONCallbackParameters callbackParams; + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback"; + + const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/%1/is_member/%2"; + QString groupIDStr = groupID.toString().mid(1,36); + DependencyManager::get()->sendRequest(GET_IS_GROUP_MEMBER_PATH.arg(groupIDStr).arg(username), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, callbackParams); +} + +void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + if (jsonObject["status"].toString() == "success") { + QString username = jsonObject["username"].toString(); + QUuid groupID = QUuid(jsonObject["group_id"].toString()); + bool isMember = jsonObject["is_member"].toBool(); + _server->_settingsManager.recordGroupMembership(username, groupID, isMember); + } else { + qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); + } +} + +void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply& requestReply) { + qDebug() << "getGroupID api call failed:" << requestReply.error(); +} From fdafbd2015e72f11b7b3a07176dd439e2078d456 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 20 Jun 2016 14:02:23 -0700 Subject: [PATCH 13/56] apply a group's permissions to users who belong to the group --- domain-server/src/DomainGatekeeper.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 50bbf38543..0f8ce70cf3 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -51,7 +51,10 @@ public slots: void processICEPeerInformationPacket(QSharedPointer message); void publicKeyJSONCallback(QNetworkReply& requestReply); - + + void getIsGroupMemberJSONCallback(QNetworkReply& requestReply); + void getIsGroupMemberErrorCallback(QNetworkReply& requestReply); + signals: void killNode(SharedNodePointer node); void connectedNode(SharedNodePointer node); @@ -93,6 +96,10 @@ private: QHash _connectionTokenHash; QHash _userPublicKeys; + + NodePermissions applyPermissionsForUser(bool isLocalUser, NodePermissions userPerms, QString verifiedUsername); + void getGroupMemberships(const QString& username); + void getIsGroupMember(const QString& username, const QUuid groupID); }; From ba032ce0b4a04da02189a1c85f2a7a92a1d65012 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 20 Jun 2016 14:06:09 -0700 Subject: [PATCH 14/56] apply a group's permissions to users who belong to the group --- .../src/DomainServerSettingsManager.cpp | 42 ++++++++++++++++++- .../src/DomainServerSettingsManager.h | 16 ++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index ff38af6ad9..5e55a20601 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -379,6 +379,10 @@ void DomainServerSettingsManager::unpackPermissions() { } else { _groupPermissions[id] = perms; } + if (perms->isGroup()) { + // the group-id was cached. hook-up the id in the id->group hash + _groupByID[perms->getGroupID()] = _groupPermissions[id]; + } } // if any of the standard names are missing, add them @@ -444,6 +448,26 @@ NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString return nullPermissions; } +NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupname) const { + if (_groupPermissions.contains(groupname)) { + return *(_groupPermissions[groupname].get()); + } + NodePermissions nullPermissions; + nullPermissions.setAll(false); + return nullPermissions; +} + +NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QUuid& groupID) const { + if (!_groupByID.contains(groupID)) { + NodePermissions nullPermissions; + nullPermissions.setAll(false); + return nullPermissions; + } + QString groupName = _groupByID[groupID]->getID(); + return getPermissionsForGroup(groupName); +} + + QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); @@ -865,7 +889,7 @@ void DomainServerSettingsManager::persistToFile() { } void DomainServerSettingsManager::requestMissingGroupIDs() { - QHashIterator i(_groupPermissions); + QHashIterator i(_groupPermissions.get()); while (i.hasNext()) { i.next(); NodePermissionsPointer perms = i.value(); @@ -879,6 +903,13 @@ void DomainServerSettingsManager::requestMissingGroupIDs() { } } +NodePermissionsPointer DomainServerSettingsManager::lookupGroupByID(const QUuid& id) { + if (_groupByID.contains(id)) { + return _groupByID[id]; + } + return nullptr; +} + void DomainServerSettingsManager::getGroupID(const QString& groupname) { JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; @@ -905,6 +936,7 @@ void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestR if (_groupPermissions.contains(groupName)) { qDebug() << "ID for group:" << groupName << "is" << groupID; _groupPermissions[groupName]->setGroupID(groupID); + _groupByID[groupID] = _groupPermissions[groupName]; packPermissions(); } else { qDebug() << "DomainServerSettingsManager::getGroupIDJSONCallback got response for unknown group:" << groupName; @@ -917,3 +949,11 @@ void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestR void DomainServerSettingsManager::getGroupIDErrorCallback(QNetworkReply& requestReply) { qDebug() << "getGroupID api call failed:" << requestReply.error(); } + +void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, bool isMember) { + _groupMembership[name][groupID] = isMember; +} + +bool DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) { + return _groupMembership[name][groupID]; +} diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index a438e1160a..979d047fb7 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -43,11 +43,21 @@ public: QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); } - bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); } NodePermissions getStandardPermissionsForName(const QString& name) const; + + bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); } NodePermissions getPermissionsForName(const QString& name) const; QStringList getAllNames() { return _agentPermissions.keys(); } + 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 are used to locally cache the result of calling "api/v1/groups/%1/is_member/%2" on metaverse's api + void recordGroupMembership(const QString& name, const QUuid groupID, bool isMember); + bool isGroupMember(const QString& name, const QUuid& groupID); + signals: void updateNodePermissions(); @@ -78,12 +88,16 @@ private: void requestMissingGroupIDs(); void getGroupID(const QString& groupname); + NodePermissionsPointer lookupGroupByID(const QUuid& id); void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath); void packPermissions(); void unpackPermissions(); NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost NodePermissionsMap _agentPermissions; // specific account-names + NodePermissionsMap _groupPermissions; // permissions granted by membershipt to specific groups + QHash _groupByID; + QHash> _groupMembership; }; #endif // hifi_DomainServerSettingsManager_h From b1ad743721d6ac5be20d051550770b72960ba801 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 20 Jun 2016 14:06:45 -0700 Subject: [PATCH 15/56] apply a group's permissions to users who belong to the group --- libraries/networking/src/NodePermissions.h | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index b906ab6f12..c2022bd7ff 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -25,15 +25,7 @@ class NodePermissions { public: NodePermissions() { _id = QUuid::createUuid().toString(); } NodePermissions(const QString& name) { _id = name.toLower(); } - NodePermissions(QMap perms) { - _id = perms["permissions_id"].toString().toLower(); - canConnectToDomain = perms["id_can_connect"].toBool(); - canAdjustLocks = perms["id_can_adjust_locks"].toBool(); - canRezPermanentEntities = perms["id_can_rez"].toBool(); - canRezTemporaryEntities = perms["id_can_rez_tmp"].toBool(); - canWriteToAssetServer = perms["id_can_write_to_asset_server"].toBool(); - canConnectPastMaxCapacity = perms["id_can_connect_past_max_capacity"].toBool(); - } + NodePermissions(QMap perms); QString getID() const { return _id; } @@ -41,7 +33,7 @@ public: void setUserName(QString userName) { _userName = userName.toLower(); } QString getUserName() { return _userName; } - void setGroupID(QUuid groupID) { _groupID = groupID; _groupIDSet = true; } + void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; } } QUuid getGroupID() { return _groupID; } bool isGroup() { return _groupIDSet; } From f397c4760f3f4046af87a2403c15503b0ffec2f6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 21 Jun 2016 09:56:39 -0700 Subject: [PATCH 16/56] comments --- domain-server/src/DomainServerSettingsManager.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 979d047fb7..a67f3d4338 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -42,19 +42,22 @@ public: QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } + // 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; + // 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); } NodePermissions getPermissionsForName(const QString& name) const; QStringList getAllNames() { return _agentPermissions.keys(); } + // 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; QList getKnownGroupIDs() { return _groupByID.keys(); } - // these are used to locally cache the result of calling "api/v1/groups/%1/is_member/%2" on metaverse's api + // 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); bool isGroupMember(const QString& name, const QUuid& groupID); @@ -86,6 +89,7 @@ private: friend class DomainServer; + // these cause calls to metaverse's group api void requestMissingGroupIDs(); void getGroupID(const QString& groupname); NodePermissionsPointer lookupGroupByID(const QUuid& id); @@ -93,10 +97,13 @@ private: void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath); void packPermissions(); void unpackPermissions(); + NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost NodePermissionsMap _agentPermissions; // specific account-names NodePermissionsMap _groupPermissions; // permissions granted by membershipt to specific groups - QHash _groupByID; + 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 QHash> _groupMembership; }; From c97618f8bc6070ec12ba1c8607cd30f9379155e5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 21 Jun 2016 11:40:55 -0700 Subject: [PATCH 17/56] reduce the number of user public-key downloads during a login attempt --- domain-server/src/DomainGatekeeper.cpp | 53 ++++++++++++++++++-------- domain-server/src/DomainGatekeeper.h | 2 + 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 213a0a1cc4..a8b146e12b 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -274,6 +274,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); // ask for their public key right now to make sure we have it requestUserPublicKey(username); + getGroupMemberships(username); // optimistically get started on group memberships return SharedNodePointer(); } @@ -480,10 +481,20 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) { return; } + QString lowerUsername = username.toLower(); + if (_inFlightPublicKeyRequests.contains(lowerUsername)) { + // public-key request for this username is already flight, not rerequesting + return; + } + _inFlightPublicKeyRequests[lowerUsername] = true; + // even if we have a public key for them right now, request a new one in case it has just changed JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "publicKeyJSONErrorCallback"; + const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key"; @@ -494,28 +505,40 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) { QNetworkAccessManager::GetOperation, callbackParams); } +QString extractUsernameFromPublicKeyRequest(QNetworkReply& requestReply) { + // extract the username from the request url + QString username; + const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key"; + QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING); + if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { + username = usernameRegex.cap(1); + } + return username.toLower(); +} + void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QString username = extractUsernameFromPublicKeyRequest(requestReply); - if (jsonObject["status"].toString() == "success") { + if (jsonObject["status"].toString() == "success" && username != "") { // figure out which user this is for - const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key"; - QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING); + qDebug() << "Storing a public key for user" << username; + // pull the public key as a QByteArray from this response + const QString JSON_DATA_KEY = "data"; + const QString JSON_PUBLIC_KEY_KEY = "public_key"; - if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { - QString username = usernameRegex.cap(1); - - qDebug() << "Storing a public key for user" << username; - - // pull the public key as a QByteArray from this response - const QString JSON_DATA_KEY = "data"; - const QString JSON_PUBLIC_KEY_KEY = "public_key"; - - _userPublicKeys[username] = - QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8()); - } + _userPublicKeys[username] = + QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8()); } + + _inFlightPublicKeyRequests.remove(username); +} + +void DomainGatekeeper::publicKeyJSONErrorCallback(QNetworkReply& requestReply) { + qDebug() << "publicKey api call failed:" << requestReply.error(); + QString username = extractUsernameFromPublicKeyRequest(requestReply); + _inFlightPublicKeyRequests.remove(username); } void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) { diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 0f8ce70cf3..bc8a569591 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -51,6 +51,7 @@ public slots: void processICEPeerInformationPacket(QSharedPointer message); void publicKeyJSONCallback(QNetworkReply& requestReply); + void publicKeyJSONErrorCallback(QNetworkReply& requestReply); void getIsGroupMemberJSONCallback(QNetworkReply& requestReply); void getIsGroupMemberErrorCallback(QNetworkReply& requestReply); @@ -96,6 +97,7 @@ private: QHash _connectionTokenHash; QHash _userPublicKeys; + QHash _inFlightPublicKeyRequests; // keep track of which we've already asked for NodePermissions applyPermissionsForUser(bool isLocalUser, NodePermissions userPerms, QString verifiedUsername); void getGroupMemberships(const QString& username); From cc1b6f0cb2b70158114edbab2721137509c82c69 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 24 Jun 2016 11:42:56 -0700 Subject: [PATCH 18/56] add a permissions-grid row for friends-of-domain-owner --- domain-server/src/DomainGatekeeper.cpp | 44 ++++++++++++++++++- domain-server/src/DomainGatekeeper.h | 5 +++ .../src/DomainServerSettingsManager.cpp | 9 ++++ libraries/networking/src/AccountManager.cpp | 42 ++++++++++-------- libraries/networking/src/AccountManager.h | 4 +- libraries/networking/src/NodePermissions.cpp | 4 +- libraries/networking/src/NodePermissions.h | 1 + 7 files changed, 87 insertions(+), 22 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index a8b146e12b..90d0d0adca 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -142,6 +142,12 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); qDebug() << "user-permissions: user is logged-into metavers, so:" << userPerms; + // if this user is a friend of the domain-owner, give them friend's permissions + if (_domainOwnerFriends.contains(verifiedUsername)) { + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameFriends); + qDebug() << "user-permissions: user is friends with domain-owner, so:" << userPerms; + } + // if this user is a known member of a group, give them the implied permissions foreach (QUuid groupID, _server->_settingsManager.getKnownGroupIDs()) { if (groupID.isNull()) { @@ -275,6 +281,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // ask for their public key right now to make sure we have it requestUserPublicKey(username); getGroupMemberships(username); // optimistically get started on group memberships + getDomainOwnerFriendsList(); return SharedNodePointer(); } @@ -284,6 +291,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect userPerms.setUserName(username); verifiedUsername = username; getGroupMemberships(username); + getDomainOwnerFriendsList(); } else if (!username.isEmpty()) { // they sent us a username, but it didn't check out requestUserPublicKey(username); @@ -717,5 +725,39 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) } void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply& requestReply) { - qDebug() << "getGroupID api call failed:" << requestReply.error(); + qDebug() << "getIsGroupMember api call failed:" << requestReply.error(); +} + +void DomainGatekeeper::getDomainOwnerFriendsList() { + JSONCallbackParameters callbackParams; + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "getDomainOwnerFriendsListJSONCallback"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "getDomainOwnerFriendsListErrorCallback"; + + const QString GET_FRIENDS_LIST_PATH = "api/v1/users"; + QUrlQuery query; + query.addQueryItem("filter", "friends"); + + DependencyManager::get()->sendRequest(GET_FRIENDS_LIST_PATH, AccountManagerAuth::Required, + QNetworkAccessManager::GetOperation, callbackParams, QByteArray(), + NULL, QVariantMap(), query); +} + +void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + if (jsonObject["status"].toString() == "success") { + _domainOwnerFriends.clear(); + QJsonArray friends = jsonObject["data"].toObject()["users"].toArray(); + for (int i = 0; i < friends.size(); i++) { + QString friendUserName = friends.at(i).toObject()["username"].toString(); + _domainOwnerFriends[friendUserName] = true; + } + } else { + qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); + } +} + +void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply) { + qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply.error(); } diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index bc8a569591..7d9cfe91e3 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -56,6 +56,9 @@ public slots: void getIsGroupMemberJSONCallback(QNetworkReply& requestReply); void getIsGroupMemberErrorCallback(QNetworkReply& requestReply); + void getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply); + void getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply); + signals: void killNode(SharedNodePointer node); void connectedNode(SharedNodePointer node); @@ -98,10 +101,12 @@ private: QHash _connectionTokenHash; QHash _userPublicKeys; QHash _inFlightPublicKeyRequests; // keep track of which we've already asked for + QHash _domainOwnerFriends; // keep track of friends of the domain owner NodePermissions applyPermissionsForUser(bool isLocalUser, NodePermissions userPerms, QString verifiedUsername); void getGroupMemberships(const QString& username); void getIsGroupMember(const QString& username, const QUuid groupID); + void getDomainOwnerFriendsList(); }; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index d7fcf324e8..78a557ba5b 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -218,6 +218,8 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList new NodePermissions(NodePermissions::standardNameAnonymous)); _standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset( new NodePermissions(NodePermissions::standardNameLoggedIn)); + _standardAgentPermissions[NodePermissions::standardNameFriends].reset( + new NodePermissions(NodePermissions::standardNameFriends)); if (isRestrictedAccess) { // only users in allow-users list can connect @@ -340,6 +342,7 @@ void DomainServerSettingsManager::unpackPermissions() { bool foundLocalhost = false; bool foundAnonymous = false; bool foundLoggedIn = false; + bool foundFriends = false; bool needPack = false; QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); @@ -368,6 +371,7 @@ void DomainServerSettingsManager::unpackPermissions() { foundLocalhost |= (id == NodePermissions::standardNameLocalhost); foundAnonymous |= (id == NodePermissions::standardNameAnonymous); foundLoggedIn |= (id == NodePermissions::standardNameLoggedIn); + foundFriends |= (id == NodePermissions::standardNameFriends); if (_standardAgentPermissions.contains(id)) { qDebug() << "duplicate name in standard permissions table: " << id; _standardAgentPermissions[id] |= perms; @@ -424,6 +428,11 @@ void DomainServerSettingsManager::unpackPermissions() { _standardAgentPermissions[perms->getID()] = perms; needPack = true; } + if (!foundFriends) { + NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameFriends) }; + _standardAgentPermissions[perms->getID()] = perms; + needPack = true; + } if (needPack) { packPermissions(); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index b6c5e691a6..d7976e2036 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -200,8 +200,9 @@ void AccountManager::sendRequest(const QString& path, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart, - const QVariantMap& propertyMap) { - + const QVariantMap& propertyMap, + QUrlQuery query) { + if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "sendRequest", Q_ARG(const QString&, path), @@ -213,9 +214,9 @@ void AccountManager::sendRequest(const QString& path, Q_ARG(QVariantMap, propertyMap)); return; } - + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - + QNetworkRequest networkRequest; networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); @@ -228,13 +229,17 @@ void AccountManager::sendRequest(const QString& path, } QUrl requestURL = _authURL; - + if (path.startsWith("/")) { requestURL.setPath(path); } else { requestURL.setPath("/" + path); } - + + if (!query.isEmpty()) { + requestURL.setQuery(query); + } + if (authType != AccountManagerAuth::None ) { if (hasValidAccessToken()) { networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, @@ -245,22 +250,21 @@ void AccountManager::sendRequest(const QString& path, << path << "that requires authentication"; return; } - } } - + networkRequest.setUrl(requestURL); - + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qCDebug(networking) << "Making a request to" << qPrintable(requestURL.toString()); - + if (!dataByteArray.isEmpty()) { qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray); } } - + QNetworkReply* networkReply = NULL; - + switch (operation) { case QNetworkAccessManager::GetOperation: networkReply = networkAccessManager.get(networkRequest); @@ -273,7 +277,7 @@ void AccountManager::sendRequest(const QString& path, } else { networkReply = networkAccessManager.put(networkRequest, dataMultiPart); } - + // make sure dataMultiPart is destroyed when the reply is connect(networkReply, &QNetworkReply::destroyed, dataMultiPart, &QHttpMultiPart::deleteLater); } else { @@ -284,7 +288,7 @@ void AccountManager::sendRequest(const QString& path, networkReply = networkAccessManager.put(networkRequest, dataByteArray); } } - + break; case QNetworkAccessManager::DeleteOperation: networkReply = networkAccessManager.sendCustomRequest(networkRequest, "DELETE"); @@ -293,7 +297,7 @@ void AccountManager::sendRequest(const QString& path, // other methods not yet handled break; } - + if (networkReply) { if (!propertyMap.isEmpty()) { // we have properties to set on the reply so the user can check them after @@ -301,18 +305,18 @@ void AccountManager::sendRequest(const QString& path, networkReply->setProperty(qPrintable(propertyKey), propertyMap.value(propertyKey)); } } - - + + if (!callbackParams.isEmpty()) { // if we have information for a callback, insert the callbackParams into our local map _pendingCallbackMap.insert(networkReply, callbackParams); - + if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) { callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)), callbackParams.updateSlot.toStdString().c_str()); } } - + // if we ended up firing of a request, hook up to it now connect(networkReply, SIGNAL(finished()), SLOT(processReply())); } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index d30a05fb2c..ef66164b12 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "NetworkAccessManager.h" @@ -67,7 +68,8 @@ public: const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), const QByteArray& dataByteArray = QByteArray(), QHttpMultiPart* dataMultiPart = NULL, - const QVariantMap& propertyMap = QVariantMap()); + const QVariantMap& propertyMap = QVariantMap(), + QUrlQuery query = QUrlQuery()); void setIsAgent(bool isAgent) { _isAgent = isAgent; } diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index 9f77dd9d21..f0c25a764a 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -16,11 +16,13 @@ QString NodePermissions::standardNameLocalhost = QString("localhost"); QString NodePermissions::standardNameLoggedIn = QString("logged-in"); QString NodePermissions::standardNameAnonymous = QString("anonymous"); +QString NodePermissions::standardNameFriends = QString("friends"); QStringList NodePermissions::standardNames = QList() << NodePermissions::standardNameLocalhost << NodePermissions::standardNameLoggedIn - << NodePermissions::standardNameAnonymous; + << NodePermissions::standardNameAnonymous + << NodePermissions::standardNameFriends; NodePermissions::NodePermissions(QMap perms) { _id = perms["permissions_id"].toString(); diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index c2022bd7ff..7971d4e362 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -43,6 +43,7 @@ public: static QString standardNameLocalhost; static QString standardNameLoggedIn; static QString standardNameAnonymous; + static QString standardNameFriends; static QStringList standardNames; // the initializations here should match the defaults in describe-settings.json From ead6e476a9e1acbe43a43025896f53e479e5aa10 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 24 Jun 2016 14:03:43 -0700 Subject: [PATCH 19/56] add a list of blacklist groups which remove permissions rather than grant them --- .../resources/describe-settings.json | 71 ++++++++++++ domain-server/src/DomainGatekeeper.cpp | 16 ++- .../src/DomainServerSettingsManager.cpp | 102 +++++++++++++++++- .../src/DomainServerSettingsManager.h | 13 ++- libraries/networking/src/NodePermissions.cpp | 42 ++++++++ libraries/networking/src/NodePermissions.h | 5 + 6 files changed, 237 insertions(+), 12 deletions(-) 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 From d4cc4bf1e4d7c84efa7272e2ab06d0aa4ae7f8ed Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 27 Jun 2016 14:55:13 -0700 Subject: [PATCH 20/56] represent node permissions as a bitfield rather than a list of bools --- .../resources/describe-settings.json | 8 +- domain-server/src/DomainGatekeeper.cpp | 20 ++-- domain-server/src/DomainMetadata.cpp | 8 +- domain-server/src/DomainServer.cpp | 2 +- .../src/DomainServerSettingsManager.cpp | 25 +++-- libraries/networking/src/LimitedNodeList.cpp | 20 ++-- libraries/networking/src/LimitedNodeList.h | 8 +- libraries/networking/src/Node.h | 8 +- libraries/networking/src/NodePermissions.cpp | 97 ++++++++----------- libraries/networking/src/NodePermissions.h | 25 +++-- 10 files changed, 116 insertions(+), 105 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index b24acd1bce..44d2c025a7 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -409,7 +409,7 @@ "label": "Connect", "type": "checkbox", "editable": true, - "default": true + "default": false }, { "name": "id_can_adjust_locks", @@ -478,7 +478,7 @@ "label": "Connect", "type": "checkbox", "editable": true, - "default": true + "default": false }, { "name": "id_can_adjust_locks", @@ -549,7 +549,7 @@ "label": "Connect", "type": "checkbox", "editable": true, - "default": true + "default": false }, { "name": "id_can_adjust_locks", @@ -620,7 +620,7 @@ "label": "Connect", "type": "checkbox", "editable": true, - "default": true + "default": false }, { "name": "id_can_adjust_locks", diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index b5372ea9bc..9d121cb0ab 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -140,7 +140,7 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, } else { // they are logged into metaverse, but we don't have specific permissions for them. userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); - qDebug() << "user-permissions: user is logged-into metavers, so:" << userPerms; + qDebug() << "user-permissions: user is logged-into metaverse, so:" << userPerms; // if this user is a friend of the domain-owner, give them friend's permissions if (_domainOwnerFriends.contains(verifiedUsername)) { @@ -188,9 +188,9 @@ void DomainGatekeeper::updateNodePermissions() { if (node->getPermissions().isAssignment) { // this node is an assignment-client userPerms.isAssignment = true; - userPerms.canAdjustLocks = true; - userPerms.canRezPermanentEntities = true; - userPerms.canRezTemporaryEntities = true; + userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; + userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; + userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; } else { // this node is an agent const QHostAddress& addr = node->getLocalSocket().getAddress(); @@ -201,7 +201,7 @@ void DomainGatekeeper::updateNodePermissions() { node->setPermissions(userPerms); - if (!userPerms.canConnectToDomain) { + if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { qDebug() << "node" << node->getUUID() << "no longer has permission to connect."; // hang up on this node nodesToKill << node; @@ -257,9 +257,9 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo // always allow assignment clients to create and destroy entities NodePermissions userPerms; userPerms.isAssignment = true; - userPerms.canAdjustLocks = true; - userPerms.canRezPermanentEntities = true; - userPerms.canRezTemporaryEntities = true; + userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; + userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; + userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; newNode->setPermissions(userPerms); return newNode; } @@ -306,13 +306,13 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect userPerms = applyPermissionsForUser(isLocalUser, userPerms, verifiedUsername); - if (!userPerms.canConnectToDomain) { + if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers); return SharedNodePointer(); } - if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) { + if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) { // we can't allow this user to connect because we are at max capacity sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers); diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index c5048ea9d8..927a14cab1 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -184,10 +184,10 @@ void DomainMetadata::securityChanged(bool send) { QString restriction; const auto& settingsManager = static_cast(parent())->_settingsManager; - bool hasAnonymousAccess = - settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).canConnectToDomain; - bool hasHifiAccess = - settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).canConnectToDomain; + bool hasAnonymousAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).can( + NodePermissions::Permission::canConnectToDomain); + bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can( + NodePermissions::Permission::canConnectToDomain); if (hasAnonymousAccess) { restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON; } else if (hasHifiAccess) { diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d75a2c3245..923a82d184 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1099,7 +1099,7 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { // consider the domain to be "restricted" if anonymous connections are disallowed static const QString RESTRICTED_ACCESS_FLAG = "restricted"; NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); - domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; + domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.can(NodePermissions::Permission::canConnectToDomain); const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID()); if (!temporaryDomainKey.isEmpty()) { diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 2411dbdf09..943f0517dc 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -223,14 +223,16 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList if (isRestrictedAccess) { // only users in allow-users list can connect - _standardAgentPermissions[NodePermissions::standardNameAnonymous]->canConnectToDomain = false; - _standardAgentPermissions[NodePermissions::standardNameLoggedIn]->canConnectToDomain = false; + _standardAgentPermissions[NodePermissions::standardNameAnonymous]->clear( + NodePermissions::Permission::canConnectToDomain); + _standardAgentPermissions[NodePermissions::standardNameLoggedIn]->clear( + NodePermissions::Permission::canConnectToDomain); } // else anonymous and logged-in retain default of canConnectToDomain = true foreach (QString allowedUser, allowedUsers) { // even if isRestrictedAccess is false, we have to add explicit rows for these users. - // defaults to canConnectToDomain = true _agentPermissions[allowedUser].reset(new NodePermissions(allowedUser)); + _agentPermissions[allowedUser]->set(NodePermissions::Permission::canConnectToDomain); } foreach (QString allowedEditor, allowedEditors) { @@ -238,10 +240,10 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList _agentPermissions[allowedEditor].reset(new NodePermissions(allowedEditor)); if (isRestrictedAccess) { // they can change locks, but can't connect. - _agentPermissions[allowedEditor]->canConnectToDomain = false; + _agentPermissions[allowedEditor]->clear(NodePermissions::Permission::canConnectToDomain); } } - _agentPermissions[allowedEditor]->canAdjustLocks = true; + _agentPermissions[allowedEditor]->set(NodePermissions::Permission::canAdjustLocks); } QList> permissionsSets; @@ -249,11 +251,16 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList foreach (auto permissionsSet, permissionsSets) { foreach (QString userName, permissionsSet.keys()) { if (onlyEditorsAreRezzers) { - permissionsSet[userName]->canRezPermanentEntities = permissionsSet[userName]->canAdjustLocks; - permissionsSet[userName]->canRezTemporaryEntities = permissionsSet[userName]->canAdjustLocks; + if (permissionsSet[userName]->can(NodePermissions::Permission::canAdjustLocks)) { + permissionsSet[userName]->set(NodePermissions::Permission::canRezPermanentEntities); + permissionsSet[userName]->set(NodePermissions::Permission::canRezTemporaryEntities); + } else { + permissionsSet[userName]->clear(NodePermissions::Permission::canRezPermanentEntities); + permissionsSet[userName]->clear(NodePermissions::Permission::canRezTemporaryEntities); + } } else { - permissionsSet[userName]->canRezPermanentEntities = true; - permissionsSet[userName]->canRezTemporaryEntities = true; + permissionsSet[userName]->set(NodePermissions::Permission::canRezPermanentEntities); + permissionsSet[userName]->set(NodePermissions::Permission::canRezTemporaryEntities); } } } diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index d7a2d47fab..151b6c5012 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -135,17 +135,21 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; - if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) { - emit isAllowedEditorChanged(_permissions.canAdjustLocks); + if (originalPermissions.can(NodePermissions::Permission::canAdjustLocks) != + newPermissions.can(NodePermissions::Permission::canAdjustLocks)) { + emit isAllowedEditorChanged(_permissions.can(NodePermissions::Permission::canAdjustLocks)); } - if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) { - emit canRezChanged(_permissions.canRezPermanentEntities); + if (originalPermissions.can(NodePermissions::Permission::canRezPermanentEntities) != + newPermissions.can(NodePermissions::Permission::canRezPermanentEntities)) { + emit canRezChanged(_permissions.can(NodePermissions::Permission::canRezPermanentEntities)); } - if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) { - emit canRezTmpChanged(_permissions.canRezTemporaryEntities); + if (originalPermissions.can(NodePermissions::Permission::canRezTemporaryEntities) != + newPermissions.can(NodePermissions::Permission::canRezTemporaryEntities)) { + emit canRezTmpChanged(_permissions.can(NodePermissions::Permission::canRezTemporaryEntities)); } - if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) { - emit canWriteAssetsChanged(_permissions.canWriteToAssetServer); + if (originalPermissions.can(NodePermissions::Permission::canWriteToAssetServer) != + newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) { + emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer)); } } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 483aa0734c..3242b7d178 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -105,10 +105,10 @@ public: void setSessionUUID(const QUuid& sessionUUID); void setPermissions(const NodePermissions& newPermissions); - bool isAllowedEditor() const { return _permissions.canAdjustLocks; } - bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; } - bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; } - bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; } + bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); } + bool getThisNodeCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); } + bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } + bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } QUdpSocket& getDTLSSocket(); diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index b277ac0083..08eb9829f8 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -61,10 +61,10 @@ public: void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; } NodePermissions getPermissions() const { return _permissions; } - bool isAllowedEditor() const { return _permissions.canAdjustLocks; } - bool getCanRez() const { return _permissions.canRezPermanentEntities; } - bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; } - bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; } + bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); } + bool getCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); } + bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } + bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index a6e502ff59..023e3cb04a 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -31,12 +31,14 @@ NodePermissions::NodePermissions(QMap perms) { _groupID = perms["group_id"].toUuid(); } - canConnectToDomain = perms["id_can_connect"].toBool(); - canAdjustLocks = perms["id_can_adjust_locks"].toBool(); - canRezPermanentEntities = perms["id_can_rez"].toBool(); - canRezTemporaryEntities = perms["id_can_rez_tmp"].toBool(); - canWriteToAssetServer = perms["id_can_write_to_asset_server"].toBool(); - canConnectPastMaxCapacity = perms["id_can_connect_past_max_capacity"].toBool(); + permissions = NodePermissions::Permissions(); + permissions |= perms["id_can_connect"].toBool() ? Permission::canConnectToDomain : Permission::none; + permissions |= perms["id_can_adjust_locks"].toBool() ? Permission::canAdjustLocks : Permission::none; + permissions |= perms["id_can_rez"].toBool() ? Permission::canRezPermanentEntities : Permission::none; + permissions |= perms["id_can_rez_tmp"].toBool() ? Permission::canRezTemporaryEntities : Permission::none; + permissions |= perms["id_can_write_to_asset_server"].toBool() ? Permission::canWriteToAssetServer : Permission::none; + permissions |= perms["id_can_connect_past_max_capacity"].toBool() ? + Permission::canConnectPastMaxCapacity : Permission::none; } QVariant NodePermissions::toVariant() { @@ -45,31 +47,24 @@ QVariant NodePermissions::toVariant() { if (_groupIDSet) { values["group_id"] = _groupID; } - values["id_can_connect"] = canConnectToDomain; - values["id_can_adjust_locks"] = canAdjustLocks; - values["id_can_rez"] = canRezPermanentEntities; - values["id_can_rez_tmp"] = canRezTemporaryEntities; - values["id_can_write_to_asset_server"] = canWriteToAssetServer; - values["id_can_connect_past_max_capacity"] = canConnectPastMaxCapacity; + values["id_can_connect"] = can(Permission::canConnectToDomain); + values["id_can_adjust_locks"] = can(Permission::canAdjustLocks); + values["id_can_rez"] = can(Permission::canRezPermanentEntities); + values["id_can_rez_tmp"] = can(Permission::canRezTemporaryEntities); + values["id_can_write_to_asset_server"] = can(Permission::canWriteToAssetServer); + values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity); return QVariant(values); } void NodePermissions::setAll(bool value) { - canConnectToDomain = value; - canAdjustLocks = value; - canRezPermanentEntities = value; - canRezTemporaryEntities = value; - canWriteToAssetServer = value; - canConnectPastMaxCapacity = value; + permissions = NodePermissions::Permissions(); + if (value) { + permissions = ~permissions; + } } 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; + permissions |= rhs.permissions; return *this; } NodePermissions& NodePermissions::operator|=(const NodePermissionsPointer& rhs) { @@ -84,14 +79,15 @@ NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermis } return lhs; } +NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, NodePermissions::Permission rhs) { + if (lhs) { + lhs.get()->permissions |= rhs; + } + 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; + permissions &= rhs.permissions; return *this; } NodePermissions& NodePermissions::operator&=(const NodePermissionsPointer& rhs) { @@ -106,15 +102,16 @@ NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, const NodePermis } return lhs; } +NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, NodePermissions::Permission rhs) { + if (lhs) { + lhs.get()->permissions &= 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; + result.permissions = ~permissions; return result; } @@ -128,43 +125,35 @@ NodePermissionsPointer operator~(NodePermissionsPointer& lhs) { } QDataStream& operator<<(QDataStream& out, const NodePermissions& perms) { - out << perms.canConnectToDomain; - out << perms.canAdjustLocks; - out << perms.canRezPermanentEntities; - out << perms.canRezTemporaryEntities; - out << perms.canWriteToAssetServer; - out << perms.canConnectPastMaxCapacity; + out << (uint)perms.permissions; return out; } QDataStream& operator>>(QDataStream& in, NodePermissions& perms) { - in >> perms.canConnectToDomain; - in >> perms.canAdjustLocks; - in >> perms.canRezPermanentEntities; - in >> perms.canRezTemporaryEntities; - in >> perms.canWriteToAssetServer; - in >> perms.canConnectPastMaxCapacity; + uint permissionsInt; + in >> permissionsInt; + perms.permissions = (NodePermissions::Permissions)permissionsInt; return in; } QDebug operator<<(QDebug debug, const NodePermissions& perms) { debug.nospace() << "[permissions: " << perms.getID() << " --"; - if (perms.canConnectToDomain) { + if (perms.can(NodePermissions::Permission::canConnectToDomain)) { debug << " connect"; } - if (perms.canAdjustLocks) { + if (perms.can(NodePermissions::Permission::canAdjustLocks)) { debug << " locks"; } - if (perms.canRezPermanentEntities) { + if (perms.can(NodePermissions::Permission::canRezPermanentEntities)) { debug << " rez"; } - if (perms.canRezTemporaryEntities) { + if (perms.can(NodePermissions::Permission::canRezTemporaryEntities)) { debug << " rez-tmp"; } - if (perms.canWriteToAssetServer) { + if (perms.can(NodePermissions::Permission::canWriteToAssetServer)) { debug << " asset-server"; } - if (perms.canConnectPastMaxCapacity) { + if (perms.can(NodePermissions::Permission::canConnectPastMaxCapacity)) { debug << " ignore-max-cap"; } debug.nospace() << "]"; diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index e0713e5ce3..1dea560543 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -46,13 +46,17 @@ public: static QString standardNameFriends; static QStringList standardNames; - // the initializations here should match the defaults in describe-settings.json - bool canConnectToDomain { true }; - bool canAdjustLocks { false }; - bool canRezPermanentEntities { false }; - bool canRezTemporaryEntities { false }; - bool canWriteToAssetServer { false }; - bool canConnectPastMaxCapacity { false }; + enum class Permission { + none = 0, + canConnectToDomain = 1, + canAdjustLocks = 2, + canRezPermanentEntities = 4, + canRezTemporaryEntities = 8, + canWriteToAssetServer = 16, + canConnectPastMaxCapacity = 32 + }; + Q_DECLARE_FLAGS(Permissions, Permission) + Permissions permissions; QVariant toVariant(); @@ -66,6 +70,10 @@ public: friend QDataStream& operator<<(QDataStream& out, const NodePermissions& perms); friend QDataStream& operator>>(QDataStream& in, NodePermissions& perms); + void clear(Permission p) { permissions &= (Permission) (~(uint)p); } + void set(Permission p) { permissions |= p; } + bool can(Permission p) const { return permissions.testFlag(p); } + protected: QString _id; QString _userName; @@ -73,6 +81,7 @@ protected: bool _groupIDSet { false }; QUuid _groupID; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(NodePermissions::Permissions) // wrap QHash in a class that forces all keys to be lowercase @@ -96,7 +105,9 @@ 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, NodePermissions::Permission rhs); NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs); +NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, NodePermissions::Permission rhs); NodePermissionsPointer operator~(NodePermissionsPointer& lhs); #endif // hifi_NodePermissions_h From aae42e3802574398947457e438571ea9b273b84b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 30 Jun 2016 16:14:46 -0700 Subject: [PATCH 21/56] updates to track zach's changes to api --- domain-server/src/DomainGatekeeper.cpp | 31 +++++-- domain-server/src/DomainServer.cpp | 2 + .../src/DomainServerSettingsManager.cpp | 86 +++++++++++++------ .../src/DomainServerSettingsManager.h | 1 + libraries/networking/src/AccountManager.cpp | 2 + 5 files changed, 92 insertions(+), 30 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 9d121cb0ab..77cd7ffb94 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -711,20 +711,39 @@ 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/is_member/%2"; + const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/%1/membership/%2"; QString groupIDStr = groupID.toString().mid(1,36); DependencyManager::get()->sendRequest(GET_IS_GROUP_MEMBER_PATH.arg(groupIDStr).arg(username), - AccountManagerAuth::None, + AccountManagerAuth::Required, QNetworkAccessManager::GetOperation, callbackParams); } void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) { + // { + // "data":{ + // "username":"sethalves", + // "groups":{ + // "fd55479a-265d-4990-854e-3d04214ad1b0":{ + // "name":"Blerg Blah", + // "rank":{ + // "name":"admin", + // "order":1 + // } + // } + // } + // }, + // "status":"success" + // } + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); if (jsonObject["status"].toString() == "success") { - QString username = jsonObject["username"].toString(); - QUuid groupID = QUuid(jsonObject["group_id"].toString()); - bool isMember = jsonObject["is_member"].toBool(); - _server->_settingsManager.recordGroupMembership(username, groupID, isMember); + QJsonObject data = jsonObject["data"].toObject(); + QJsonObject groups = data["groups"].toObject(); + QString username = data["username"].toString(); + _server->_settingsManager.clearGroupMemberships(username); + foreach (auto groupID, groups.keys()) { + _server->_settingsManager.recordGroupMembership(username, groupID, true); + } } else { qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d78199ba8e..fcbf4caa5b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -112,6 +112,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : return; } + _settingsManager.requestMissingGroupIDs(); + setupNodeListAndAssignments(); setupAutomaticNetworking(); if (!getID().isNull()) { diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 56656f9d7c..daea38de23 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1004,6 +1004,11 @@ void DomainServerSettingsManager::persistToFile() { } void DomainServerSettingsManager::requestMissingGroupIDs() { + if (!DependencyManager::get()->hasAuthEndpoint()) { + // can't yet. + return; + } + QHashIterator i_permissions(_groupPermissions.get()); while (i_permissions.hasNext()) { i_permissions.next(); @@ -1044,40 +1049,73 @@ void DomainServerSettingsManager::getGroupID(const QString& groupname) { callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "getGroupIDErrorCallback"; - const QString GET_GROUP_ID_PATH = "api/v1/get_group_id/%1"; + const QString GET_GROUP_ID_PATH = "api/v1/groups/name/%1"; - qDebug() << "Requesting group ID for group named" << groupname; + qDebug() << "************* Requesting group ID for group named" << groupname; DependencyManager::get()->sendRequest(GET_GROUP_ID_PATH.arg(groupname), - AccountManagerAuth::None, + AccountManagerAuth::Required, QNetworkAccessManager::GetOperation, callbackParams); } void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestReply) { + // { + // "data":{ + // "groups":[{ + // "description":null, + // "id":"fd55479a-265d-4990-854e-3d04214ad1b0", + // "is_list":false, + // "membership":{ + // "permissions":{ + // "custom_1=":false, + // "custom_2=":false, + // "custom_3=":false, + // "custom_4=":false, + // "del_group=":true, + // "invite_member=":true, + // "kick_member=":true, + // "list_members=":true, + // "mv_group=":true, + // "query_members=":true, + // "rank_member=":true + // }, + // "rank":{ + // "name=":"owner", + // "order=":0 + // } + // }, + // "name":"Blerg Blah" + // }] + // }, + // "status":"success" + // } QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - if (jsonObject["status"].toString() == "success") { - QString groupName = jsonObject["group_name"].toString(); - QUuid groupID = QUuid(jsonObject["group_id"].toString()); + QJsonArray groups = jsonObject["data"].toObject()["groups"].toArray(); + for (int i = 0; i < groups.size(); i++) { + QJsonObject group = groups.at(i).toObject(); + 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; - } + 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; + if (found) { + packPermissions(); + } else { + qDebug() << "DomainServerSettingsManager::getGroupIDJSONCallback got response for unknown group:" << groupName; + } } } else { qDebug() << "getGroupID api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); @@ -1085,7 +1123,7 @@ void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestR } void DomainServerSettingsManager::getGroupIDErrorCallback(QNetworkReply& requestReply) { - qDebug() << "getGroupID api call failed:" << requestReply.error(); + qDebug() << "******************** getGroupID api call failed:" << requestReply.error(); } void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, bool isMember) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index a4978c8e91..c343a6bc06 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -68,6 +68,7 @@ public: 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); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 89e6a6ea10..571f54322b 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -253,6 +253,8 @@ void AccountManager::sendRequest(const QString& path, } } + qDebug() << "/////// path =" << path << " requestURL =" << requestURL; + networkRequest.setUrl(requestURL); if (VERBOSE_HTTP_REQUEST_DEBUGGING) { From 2a25f458fc03399c88b48b31f4932b525ee4ed3a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 4 Jul 2016 10:20:25 -0700 Subject: [PATCH 22/56] fetch rank names from web api after getting a group's ID --- .../src/DomainServerSettingsManager.cpp | 96 +++++++++++++++++++ .../src/DomainServerSettingsManager.h | 6 ++ libraries/networking/src/AccountManager.cpp | 2 - 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index daea38de23..5c27f37172 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1090,6 +1090,7 @@ 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++) { @@ -1113,6 +1114,7 @@ void DomainServerSettingsManager::getGroupIDJSONCallback(QNetworkReply& requestR if (found) { packPermissions(); + getGroupRanks(groupID); } else { qDebug() << "DomainServerSettingsManager::getGroupIDJSONCallback got response for unknown group:" << groupName; } @@ -1126,6 +1128,100 @@ void DomainServerSettingsManager::getGroupIDErrorCallback(QNetworkReply& request qDebug() << "******************** getGroupID api call failed:" << requestReply.error(); } +void DomainServerSettingsManager::getGroupRanks(const QUuid& groupID) { + JSONCallbackParameters callbackParams; + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "getGroupRanksJSONCallback"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "getGroupRanksErrorCallback"; + + 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) { + // { + // "current_page":1, + // "data":{ + // "groups":{ + // "fd55479a-265d-4990-854e-3d04214ad1b0":{ + // "ranks":[ + // { + // "name":"owner", + // "order":0, + // "permissions":{ + // "custom_1":false, + // "custom_2":false, + // "custom_3":false, + // "custom_4":false, + // "del_group":true, + // "invite_member":true, + // "kick_member":true, + // "list_members":true, + // "mv_group":true, + // "query_members":true, + // "rank_member":true + // } + // }, + // { + // "name":"admin", + // "order":1, + // "permissions":{ + // "custom_1":false, + // "custom_2":false, + // "custom_3":false, + // "custom_4":false, + // "del_group":false, + // "invite_member":false, + // "kick_member":false, + // "list_members":false, + // "mv_group":false, + // "query_members":false, + // "rank_member":false + // } + // } + // ] + // } + // } + // }, + // "per_page":30, + // "status":"success", + // "total_entries":2, + // "total_pages":1 + // } + + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + if (jsonObject["status"].toString() == "success") { + QJsonObject groups = jsonObject["data"].toObject()["groups"].toObject(); + foreach (auto groupID, groups.keys()) { + QJsonObject group = groups[groupID].toObject(); + QJsonArray ranks = group["ranks"].toArray(); + for (int rankIndex = 0; rankIndex < ranks.size(); rankIndex++) { + 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); + } + ranksForGroup[rankOrder] = rankName; + } + } + } else { + qDebug() << "getGroupRanks api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); + } +} + +void DomainServerSettingsManager::getGroupRanksErrorCallback(QNetworkReply& requestReply) { + qDebug() << "******************** getGroupRanks api call failed:" << requestReply.error(); +} + void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, bool isMember) { _groupMembership[name][groupID] = isMember; } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index c343a6bc06..706717eda8 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -78,6 +78,8 @@ signals: public slots: void getGroupIDJSONCallback(QNetworkReply& requestReply); void getGroupIDErrorCallback(QNetworkReply& requestReply); + void getGroupRanksJSONCallback(QNetworkReply& requestReply); + void getGroupRanksErrorCallback(QNetworkReply& requestReply); private slots: void processSettingsRequestPacket(QSharedPointer message); @@ -106,6 +108,7 @@ private: void requestMissingGroupIDs(); void getGroupID(const QString& groupname); NodePermissionsPointer lookupGroupByID(const QUuid& id); + void getGroupRanks(const QUuid& groupID); void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath); void packPermissions(); @@ -117,6 +120,9 @@ private: NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group QHash _groupByID; // similar to _groupPermissions but key is group-id rather than name + // remember the responses to api/v1/groups/%1/ranks + QHash> _groupRanks; + // keep track of answers to api queries about which users are in which groups QHash> _groupMembership; }; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 34fab3575f..c4bfae7cac 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -249,8 +249,6 @@ void AccountManager::sendRequest(const QString& path, } } - qDebug() << "/////// path =" << path << " requestURL =" << requestURL; - networkRequest.setUrl(requestURL); if (VERBOSE_HTTP_REQUEST_DEBUGGING) { From e6f456f49468fd41f1230236beb0c7abe9022c2a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 6 Jul 2016 16:39:08 -0700 Subject: [PATCH 23/56] each rank in a group has its own permissions --- .../resources/describe-settings.json | 20 +- domain-server/src/DomainGatekeeper.cpp | 19 +- domain-server/src/DomainServer.cpp | 3 +- .../src/DomainServerSettingsManager.cpp | 443 ++++++++++++------ .../src/DomainServerSettingsManager.h | 66 ++- libraries/networking/src/NodePermissions.cpp | 35 +- libraries/networking/src/NodePermissions.h | 57 ++- 7 files changed, 429 insertions(+), 214 deletions(-) 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; }; From 55d76abf48ce783023e881a198362aee09b5495b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 6 Jul 2016 16:42:31 -0700 Subject: [PATCH 24/56] back-out accidently changed api urls --- domain-server/resources/web/settings/js/settings.js | 3 +-- domain-server/src/DomainServer.cpp | 3 +-- libraries/networking/src/NetworkingConstants.h | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index ed40b4c520..25cf860f54 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1,7 +1,6 @@ var Settings = { showAdvanced: false, - // METAVERSE_URL: 'https://metaverse.highfidelity.com', - METAVERSE_URL: 'http://localhost:3000', + METAVERSE_URL: 'https://metaverse.highfidelity.com', ADVANCED_CLASS: 'advanced-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d5bf8d9adc..0126afe059 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -44,8 +44,7 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923; -// const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; -const QString ICE_SERVER_DEFAULT_HOSTNAME = "localhost"; +const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 981d5a7b3a..a512ae8887 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -15,8 +15,7 @@ #include namespace NetworkingConstants { - // const QUrl METAVERSE_SERVER_URL = QUrl("https://metaverse.highfidelity.com"); - const QUrl METAVERSE_SERVER_URL = QUrl("http://localhost:3000"); + const QUrl METAVERSE_SERVER_URL = QUrl("https://metaverse.highfidelity.com"); } #endif // hifi_NetworkingConstants_h From 0e86be86600a899b1095e4614df2eda94da171ed Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 13 Jul 2016 14:48:23 -0700 Subject: [PATCH 25/56] use dev metaverse --- domain-server/resources/describe-settings.json | 8 -------- domain-server/resources/web/settings/js/settings.js | 4 +++- domain-server/src/DomainServer.cpp | 4 +++- libraries/networking/src/NetworkingConstants.h | 4 +++- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 885a289db4..a81b58a30e 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -618,14 +618,6 @@ "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/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 25cf860f54..54be33e764 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1,6 +1,8 @@ var Settings = { showAdvanced: false, - METAVERSE_URL: 'https://metaverse.highfidelity.com', + // METAVERSE_URL: 'https://metaverse.highfidelity.com', + // METAVERSE_URL: 'http://localhost:3000', + METAVERSE_URL: 'https://devdataweb.highfidelity.com', ADVANCED_CLASS: 'advanced-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0395f3a766..b7c61b600a 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -44,7 +44,9 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923; -const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; +// const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; +// const QString ICE_SERVER_DEFAULT_HOSTNAME = "localhost"; +const QString ICE_SERVER_DEFAULT_HOSTNAME = "devdataweb.highfidelity.com"; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index a512ae8887..62a8f29088 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -15,7 +15,9 @@ #include namespace NetworkingConstants { - const QUrl METAVERSE_SERVER_URL = QUrl("https://metaverse.highfidelity.com"); + // const QUrl METAVERSE_SERVER_URL = QUrl("https://metaverse.highfidelity.com"); + // const QUrl METAVERSE_SERVER_URL = QUrl("http://localhost:3000"); + const QUrl METAVERSE_SERVER_URL = QUrl("https://devdataweb.highfidelity.com"); } #endif // hifi_NetworkingConstants_h From 641746f2c0b45e08e472b2a6e114929866499182 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 13 Jul 2016 15:29:00 -0700 Subject: [PATCH 26/56] fix describe-settings --- domain-server/resources/describe-settings.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 6f17b7d76a..dde318b281 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -618,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", From 599b8922964954190342a3be692804597f18419d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 14 Jul 2016 11:20:57 -0700 Subject: [PATCH 27/56] refresh cached group information every 15 seconds --- domain-server/src/DomainGatekeeper.cpp | 23 ++++++++++++++++++- domain-server/src/DomainGatekeeper.h | 2 ++ domain-server/src/DomainServer.cpp | 14 +++++++++++ domain-server/src/DomainServer.h | 15 +++++++----- .../src/DomainServerSettingsManager.cpp | 11 +-------- .../src/DomainServerSettingsManager.h | 5 +++- 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index f85766105e..968d56ca84 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -296,7 +296,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect QString verifiedUsername; if (!username.isEmpty() && verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { - // they are sent us a username and the signature verifies it + // they sent us a username and the signature verifies it userPerms.setUserName(username); verifiedUsername = username; getGroupMemberships(username); @@ -699,6 +699,7 @@ 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.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs(); + // TODO -- use alternative that allows checking entire group list in one call foreach (QUuid groupID, groupIDs) { if (groupID.isNull()) { continue; @@ -789,3 +790,23 @@ void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requ void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply) { qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply.error(); } + +void DomainGatekeeper::refreshGroupsCache() { + // if agents are connected to this domain, refresh our cached information about groups and memberships in such. + + getDomainOwnerFriendsList(); + + int agentCount = 0; + auto nodeList = DependencyManager::get(); + nodeList->eachNode([&](const SharedNodePointer& node) { + if (!node->getPermissions().isAssignment) { + // this node is an agent + getGroupMemberships(node->getPermissions().getUserName()); + agentCount++; + } + }); + + if (agentCount > 0) { + _server->_settingsManager.apiRefreshGroupInformation(); + } +} diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 7d9cfe91e3..a5ff5d90a7 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -59,6 +59,8 @@ public slots: void getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply); void getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply); + void refreshGroupsCache(); + signals: void killNode(SharedNodePointer node); void connectedNode(SharedNodePointer node); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index b7c61b600a..e1ee4906a2 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "DomainServerNodeData.h" #include "NodeConnectionData.h" @@ -108,6 +109,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, &_gatekeeper, &DomainGatekeeper::updateNodePermissions); + setupGroupCacheRefresh(); + // if we were given a certificate/private key or oauth credentials they must succeed if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) { return; @@ -2328,3 +2331,14 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) { // immediately send an update to the metaverse API when our ice-server changes sendICEServerAddressToMetaverseAPI(); } + +void DomainServer::setupGroupCacheRefresh() { + const int REFRESH_GROUPS_INTERVAL_MSECS = 15 * MSECS_PER_SECOND; + + if (!_metaverseGroupCacheTimer) { + // setup a timer to refresh this server's cached group details + _metaverseGroupCacheTimer = new QTimer { this }; + connect(_metaverseGroupCacheTimer, &QTimer::timeout, &_gatekeeper, &DomainGatekeeper::refreshGroupsCache); + _metaverseGroupCacheTimer->start(REFRESH_GROUPS_INTERVAL_MSECS); + } +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 138cb9ca2d..4004333789 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -41,7 +41,7 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler { public: DomainServer(int argc, char* argv[]); ~DomainServer(); - + static int const EXIT_CODE_REBOOT; bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); @@ -64,7 +64,7 @@ public slots: void processNodeDisconnectRequestPacket(QSharedPointer message); void processICEServerHeartbeatDenialPacket(QSharedPointer message); void processICEServerHeartbeatACK(QSharedPointer message); - + private slots: void aboutToQuit(); @@ -74,7 +74,7 @@ private slots: void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); } void sendHeartbeatToIceServer(); - + void handleConnectedNode(SharedNodePointer newNode); void handleTempDomainSuccess(QNetworkReply& requestReply); @@ -96,7 +96,7 @@ signals: void iceServerChanged(); void userConnected(); void userDisconnected(); - + private: const QUuid& getID(); @@ -136,7 +136,7 @@ private: SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment); void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment); void addStaticAssignmentsToQueue(); - + QUrl oauthRedirectURL(); QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid()); @@ -151,7 +151,9 @@ private: QJsonObject jsonForSocket(const HifiSockAddr& socket); QJsonObject jsonObjectForNode(const SharedNodePointer& node); - + + void setupGroupCacheRefresh(); + DomainGatekeeper _gatekeeper; HTTPManager _httpManager; @@ -184,6 +186,7 @@ private: DomainMetadata* _metadata { nullptr }; QTimer* _iceHeartbeatTimer { nullptr }; QTimer* _metaverseHeartbeatTimer { nullptr }; + QTimer* _metaverseGroupCacheTimer { nullptr }; QList _iceServerAddresses; QSet _failedIceServerAddresses; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 3f44b9a293..b9715240ca 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -515,10 +515,6 @@ void DomainServerSettingsManager::unpackPermissions() { packPermissions(); } - // attempt to retrieve any missing group-IDs, etc - apiRefreshGroupInformation(); - - #ifdef WANT_DEBUG qDebug() << "--------------- permissions ---------------------"; QList> permissionsSets; @@ -1136,8 +1132,6 @@ bool DomainServerSettingsManager::setGroupID(const QString& groupName, const QUu } void DomainServerSettingsManager::apiRefreshGroupInformation() { - const int STALE_DATA_AGE = 600; // seconds - if (!DependencyManager::get()->hasAuthEndpoint()) { // can't yet. return; @@ -1152,11 +1146,8 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() { apiGetGroupID(groupName); } - quint64 now = usecTimestampNow(); foreach (QUuid groupID, _groupNames.keys()) { - if (now - _groupRanksLastFetched[groupID] > STALE_DATA_AGE * USECS_PER_SECOND) { - apiGetGroupRanks(groupID); - } + apiGetGroupRanks(groupID); } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 52e4b02e26..f59d76bd1e 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -80,6 +80,10 @@ public: 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 + // calls http api to refresh group information + void apiRefreshGroupInformation(); + + signals: void updateNodePermissions(); @@ -113,7 +117,6 @@ private: void validateDescriptorsMap(); // these cause calls to metaverse's group api - void apiRefreshGroupInformation(); void apiGetGroupID(const QString& groupName); void apiGetGroupRanks(const QUuid& groupID); From 8e6b3ed8c22510619d0da7e8fc60fb50b92d719e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 14 Jul 2016 12:24:44 -0700 Subject: [PATCH 28/56] rename variable to make its use more clear --- domain-server/src/DomainGatekeeper.cpp | 11 +++++++---- libraries/networking/src/NodePermissions.cpp | 2 +- libraries/networking/src/NodePermissions.h | 6 +++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 968d56ca84..a5ab4ce410 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -133,7 +133,7 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); qDebug() << "user-permissions: unverified or no username, so:" << userPerms; } else { - userPerms.setUserName(verifiedUsername); + userPerms.setVerifiedUserName(verifiedUsername); if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) { userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); qDebug() << "user-permissions: specific user matches, so:" << userPerms; @@ -185,7 +185,7 @@ void DomainGatekeeper::updateNodePermissions() { limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){ // 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(); + QString username = node->getPermissions().getVerifiedUserName(); NodePermissions userPerms(NodePermissionsKey(username, 0)); if (node->getPermissions().isAssignment) { @@ -297,7 +297,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect QString verifiedUsername; if (!username.isEmpty() && verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // they sent us a username and the signature verifies it - userPerms.setUserName(username); + userPerms.setVerifiedUserName(username); verifiedUsername = username; getGroupMemberships(username); getDomainOwnerFriendsList(); @@ -801,7 +801,10 @@ void DomainGatekeeper::refreshGroupsCache() { nodeList->eachNode([&](const SharedNodePointer& node) { if (!node->getPermissions().isAssignment) { // this node is an agent - getGroupMemberships(node->getPermissions().getUserName()); + QString verifiedUserName = node->getPermissions().getVerifiedUserName(); + if (verifiedUserName != "") { + getGroupMemberships(verifiedUserName); + } agentCount++; } }); diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index e16b746b96..29b05fc837 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -146,7 +146,7 @@ QDataStream& operator>>(QDataStream& in, NodePermissions& perms) { } QDebug operator<<(QDebug debug, const NodePermissions& perms) { - debug.nospace() << "[permissions: " << perms.getID() << "/" << perms.getUserName() << " -- "; + debug.nospace() << "[permissions: " << perms.getID() << "/" << perms.getVerifiedUserName() << " -- "; debug.nospace() << "rank=" << perms.getRank() << ", groupID=" << perms.getGroupID() << "/" << (perms.isGroup() ? "y" : "n"); if (perms.can(NodePermissions::Permission::canConnectToDomain)) { diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 411312f708..541f90aee8 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -35,8 +35,8 @@ public: NodePermissionsKey getKey() const { return NodePermissionsKey(_id, _rank); } // the _id member isn't authenticated/verified and _username is. - void setUserName(QString userName) { _userName = userName.toLower(); } - QString getUserName() const { return _userName; } + void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); } + QString getVerifiedUserName() const { return _verifiedUserName; } void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }} QUuid getGroupID() const { return _groupID; } @@ -82,7 +82,7 @@ public: protected: QString _id; int _rank { 0 }; // 0 unless this is for a group - QString _userName; + QString _verifiedUserName; bool _groupIDSet { false }; QUuid _groupID; From 97b40ab87d904abac8e969df4d9cee02d829c2d1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 21 Jul 2016 14:04:55 -0700 Subject: [PATCH 29/56] adapting to api changes --- domain-server/src/DomainGatekeeper.cpp | 23 +++++++- .../src/DomainServerSettingsManager.cpp | 59 ++++++++----------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index a5ab4ce410..32c0eb00c9 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -153,18 +153,24 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, 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; + qDebug() << "user-permissions: user is in group:" << groupID << " rank:" << rank << "so:" << userPerms; } } // if this user is a known member of a blacklist group, remove the implied permissions + qDebug() << "------------------ checking blacklists ----------------------"; + qDebug() << _server->_settingsManager.getBlacklistGroupIDs(); foreach (QUuid groupID, _server->_settingsManager.getBlacklistGroupIDs()) { if (_server->_settingsManager.isGroupMember(verifiedUsername, groupID)) { int rank = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); + qDebug() << groupID << verifiedUsername << "is member with rank" << rank; if (rank >= 0) { userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rank); - qDebug() << "user-permissions: user is in blacklist group:" << groupID << "so:" << userPerms; + qDebug() << "user-permissions: user is in blacklist group:" << groupID << " rank:" << rank + << "so:" << userPerms; } + } else { + qDebug() << groupID << verifiedUsername << "is not member."; } } } @@ -739,14 +745,23 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) // "status":"success" // } + + + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + + qDebug() << "********* getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); + + if (jsonObject["status"].toString() == "success") { QJsonObject data = jsonObject["data"].toObject(); QJsonObject groups = data["groups"].toObject(); QString username = data["username"].toString(); _server->_settingsManager.clearGroupMemberships(username); foreach (auto groupID, groups.keys()) { - _server->_settingsManager.recordGroupMembership(username, groupID, true); + QJsonObject group = groups[groupID].toObject(); + int order = group["order"].toInt(); + _server->_settingsManager.recordGroupMembership(username, groupID, order); } } else { qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); @@ -812,4 +827,6 @@ void DomainGatekeeper::refreshGroupsCache() { if (agentCount > 0) { _server->_settingsManager.apiRefreshGroupInformation(); } + + updateNodePermissions(); } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index b9715240ca..53b90551c1 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -481,7 +481,7 @@ void DomainServerSettingsManager::unpackPermissions() { } if (perms->isGroup()) { // the group-id was cached. hook-up the uuid in the uuid->group hash - _groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRank())] = _groupPermissions[idKey]; + _groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRank())] = _groupForbiddens[idKey]; needPack |= setGroupID(perms->getID(), perms->getGroupID()); } } @@ -714,8 +714,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent()); QJsonObject postedObject = postedDocument.object(); - qDebug() << "DomainServerSettingsManager postedObject -" << postedObject; - // we recurse one level deep below each group for the appropriate setting bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject); @@ -747,6 +745,9 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object(); + + qDebug() << QJsonDocument(rootObject).toJson(QJsonDocument::Indented); + connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); } @@ -1149,6 +1150,8 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() { foreach (QUuid groupID, _groupNames.keys()) { apiGetGroupRanks(groupID); } + + unpackPermissions(); } void DomainServerSettingsManager::apiGetGroupID(const QString& groupName) { @@ -1234,13 +1237,17 @@ void DomainServerSettingsManager::apiGetGroupRanks(const QUuid& groupID) { } void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& requestReply) { + + // { - // "current_page":1, // "data":{ // "groups":{ - // "fd55479a-265d-4990-854e-3d04214ad1b0":{ + // "d3500f49-0655-4b1b-9846-ff8dd1b03351":{ + // "members_count":1, // "ranks":[ // { + // "id":"7979b774-e7f8-436c-9df1-912f1019f32f", + // "members_count":1, // "name":"owner", // "order":0, // "permissions":{ @@ -1248,44 +1255,24 @@ void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& re // "custom_2":false, // "custom_3":false, // "custom_4":false, - // "del_group":true, - // "invite_member":true, - // "kick_member":true, + // "edit_group":true, + // "edit_member":true, + // "edit_rank":true, // "list_members":true, - // "mv_group":true, - // "query_members":true, - // "rank_member":true - // } - // }, - // { - // "name":"admin", - // "order":1, - // "permissions":{ - // "custom_1":false, - // "custom_2":false, - // "custom_3":false, - // "custom_4":false, - // "del_group":false, - // "invite_member":false, - // "kick_member":false, - // "list_members":false, - // "mv_group":false, - // "query_members":false, - // "rank_member":false + // "list_permissions":true, + // "list_ranks":true, + // "query_member":true // } // } // ] // } // } - // }, - // "per_page":30, - // "status":"success", - // "total_entries":2, - // "total_pages":1 + // },"status":"success" // } bool changed = false; QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + if (jsonObject["status"].toString() == "success") { QJsonObject groups = jsonObject["data"].toObject()["groups"].toObject(); foreach (auto groupID, groups.keys()) { @@ -1365,6 +1352,12 @@ void DomainServerSettingsManager::debugDumpGroupsState() { qDebug() << "| " << groupKey << perms; } + qDebug() << "_groupForbiddens:"; + foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) { + NodePermissionsPointer perms = _groupForbiddens[groupKey]; + qDebug() << "| " << groupKey << perms; + } + qDebug() << "_groupIDs:"; foreach (QString groupName, _groupIDs.keys()) { qDebug() << "| " << groupName << "==>" << _groupIDs[groupName]; From adba4cde0b166274165c0896a8b01715260a5ff5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 22 Jul 2016 11:40:23 -0700 Subject: [PATCH 30/56] book-keeping around groups and ranks --- .../resources/describe-settings.json | 20 ++- domain-server/src/DomainGatekeeper.cpp | 93 +++++++---- domain-server/src/DomainGatekeeper.h | 2 +- .../src/DomainServerSettingsManager.cpp | 153 +++++++++++++----- .../src/DomainServerSettingsManager.h | 30 ++-- libraries/networking/src/GroupRank.h | 36 +++++ libraries/networking/src/NodePermissions.cpp | 15 +- libraries/networking/src/NodePermissions.h | 23 +-- 8 files changed, 263 insertions(+), 109 deletions(-) create mode 100644 libraries/networking/src/GroupRank.h diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bf238e657f..aca288c452 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -526,7 +526,7 @@ "groups": [ { "label": "Group", - "span": 4 + "span": 5 }, { "label": "Permissions ?", @@ -540,8 +540,12 @@ "label": "Group Name" }, { - "name": "rank", - "label": "Rank" + "name": "rank_id", + "label": "Rank ID" + }, + { + "name": "rank_order", + "label": "Rank Order" }, { "name": "rank_name", @@ -605,7 +609,7 @@ "groups": [ { "label": "Group", - "span": 4 + "span": 5 }, { "label": "Permissions ?", @@ -619,8 +623,12 @@ "label": "Group Name" }, { - "name": "rank", - "label": "Rank" + "name": "rank_id", + "label": "Rank ID" + }, + { + "name": "rank_order", + "label": "Rank Order" }, { "name": "rank_name", diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 32c0eb00c9..08fe40faff 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -150,10 +150,12 @@ 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()) { - int rank = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); - if (rank >= 0) { - userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rank); - qDebug() << "user-permissions: user is in group:" << groupID << " rank:" << rank << "so:" << userPerms; + QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); + if (rankID != QUuid()) { + userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rankID); + + GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID); + qDebug() << "user-permissions: user is in group:" << groupID << " rank:" << rank.name << "so:" << userPerms; } } @@ -161,12 +163,14 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, qDebug() << "------------------ checking blacklists ----------------------"; qDebug() << _server->_settingsManager.getBlacklistGroupIDs(); foreach (QUuid groupID, _server->_settingsManager.getBlacklistGroupIDs()) { - if (_server->_settingsManager.isGroupMember(verifiedUsername, groupID)) { - int rank = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); - qDebug() << groupID << verifiedUsername << "is member with rank" << rank; - if (rank >= 0) { - userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rank); - qDebug() << "user-permissions: user is in blacklist group:" << groupID << " rank:" << rank + QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); + if (rankID != QUuid()) { + QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); + if (rankID != QUuid()) { + userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rankID); + + GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID); + qDebug() << "user-permissions: user is in blacklist group:" << groupID << " rank:" << rank.name << "so:" << userPerms; } } else { @@ -197,6 +201,7 @@ void DomainGatekeeper::updateNodePermissions() { if (node->getPermissions().isAssignment) { // this node is an assignment-client userPerms.isAssignment = true; + userPerms.permissions |= NodePermissions::Permission::canConnectToDomain; userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; @@ -263,9 +268,10 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo // cleanup the PendingAssignedNodeData for this assignment now that it's connecting _pendingAssignedNodes.erase(it); - // always allow assignment clients to create and destroy entities NodePermissions userPerms; userPerms.isAssignment = true; + userPerms.permissions |= NodePermissions::Permission::canConnectToDomain; + // always allow assignment clients to create and destroy entities userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; @@ -701,33 +707,63 @@ 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.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs(); +// // TODO -- use alternative that allows checking entire group list in one call +// foreach (QUuid groupID, groupIDs) { +// if (groupID.isNull()) { +// continue; +// } +// getIsGroupMember(username, groupID); +// } +// } + +// void DomainGatekeeper::getIsGroupMember(const QString& username, const QUuid groupID) { +// JSONCallbackParameters callbackParams; +// callbackParams.jsonCallbackReceiver = this; +// callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback"; +// callbackParams.errorCallbackReceiver = this; +// callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback"; + +// 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, +// QNetworkAccessManager::GetOperation, callbackParams); +// } + + + 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.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs(); - // TODO -- use alternative that allows checking entire group list in one call - foreach (QUuid groupID, groupIDs) { - if (groupID.isNull()) { - continue; - } - getIsGroupMember(username, groupID); - } -} -void DomainGatekeeper::getIsGroupMember(const QString& username, const QUuid groupID) { + QJsonObject json; + QSet groupIDSet; + foreach (QUuid groupID, _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs()) { + groupIDSet += groupID.toString().mid(1,36); + } + QJsonArray groupIDs = QJsonArray::fromStringList(groupIDSet.toList()); + json["groups"] = groupIDs; + JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback"; callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback"; - 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), + const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/members/%2"; + DependencyManager::get()->sendRequest(GET_IS_GROUP_MEMBER_PATH.arg(username), AccountManagerAuth::Required, - QNetworkAccessManager::GetOperation, callbackParams); + QNetworkAccessManager::PostOperation, callbackParams, + QJsonDocument(json).toJson()); + } + + void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) { // { // "data":{ @@ -746,8 +782,6 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) // } - - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); qDebug() << "********* getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); @@ -760,8 +794,9 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) _server->_settingsManager.clearGroupMemberships(username); foreach (auto groupID, groups.keys()) { QJsonObject group = groups[groupID].toObject(); - int order = group["order"].toInt(); - _server->_settingsManager.recordGroupMembership(username, groupID, order); + QJsonObject rank = group["rank"].toObject(); + QUuid rankID = QUuid(rank["id"].toString()); + _server->_settingsManager.recordGroupMembership(username, groupID, rankID); } } else { qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index a5ff5d90a7..a18554338a 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -107,7 +107,7 @@ private: NodePermissions applyPermissionsForUser(bool isLocalUser, NodePermissions userPerms, QString verifiedUsername); void getGroupMemberships(const QString& username); - void getIsGroupMember(const QString& username, const QUuid groupID); + // void getIsGroupMember(const QString& username, const QUuid groupID); void getDomainOwnerFriendsList(); }; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 53b90551c1..bd429029ed 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -347,11 +347,31 @@ void DomainServerSettingsManager::packPermissionsForMap(QString mapName, // convert details for each member of the subsection QVariantList* permissionsList = reinterpret_cast(permissions); (*permissionsList).clear(); - foreach (NodePermissionsKey userKey, agentPermissions.keys()) { + QList permissionsKeys = agentPermissions.keys(); + + // when a group is added from the domain-server settings page, the config map has a group-name with + // no ID or rank. We need to leave that there until we get a valid response back from the api. + // once we have the ranks and IDs, we need to delete the original entry so that it doesn't show + // up in the settings-page with undefined's after it. + QHash groupNamesWithRanks; + // note which groups have rank/ID information + foreach (NodePermissionsKey userKey, permissionsKeys) { + NodePermissionsPointer perms = agentPermissions[userKey]; + if (perms->getRankID() != QUuid()) { + groupNamesWithRanks[userKey.first] = true; + } + } + + // convert each group-name / rank-id pair to a variant-map + foreach (NodePermissionsKey userKey, permissionsKeys) { NodePermissionsPointer perms = agentPermissions[userKey]; if (perms->isGroup()) { - QVector rankNames = _groupRanks[perms->getGroupID()]; - *permissionsList += perms->toVariant(rankNames); + if (perms->getRankID() == QUuid() && groupNamesWithRanks.contains(userKey.first)) { + // skip over the entry that was created when the user added the group. + continue; + } + QHash& groupRanks = _groupRanks[perms->getGroupID()]; + *permissionsList += perms->toVariant(groupRanks); } else { *permissionsList += perms->toVariant(); } @@ -462,7 +482,7 @@ void DomainServerSettingsManager::unpackPermissions() { } if (perms->isGroup()) { // the group-id was cached. hook-up the uuid in the uuid->group hash - _groupPermissionsByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRank())] = _groupPermissions[idKey]; + _groupPermissionsByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupPermissions[idKey]; needPack |= setGroupID(perms->getID(), perms->getGroupID()); } } @@ -481,7 +501,7 @@ void DomainServerSettingsManager::unpackPermissions() { } if (perms->isGroup()) { // the group-id was cached. hook-up the uuid in the uuid->group hash - _groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRank())] = _groupForbiddens[idKey]; + _groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupForbiddens[idKey]; needPack |= setGroupID(perms->getID(), perms->getGroupID()); } } @@ -541,10 +561,10 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { 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); + QHash& ranksForGroup = _groupRanks[groupID]; + foreach (QUuid rankID, ranksForGroup.keys()) { + NodePermissionsKey nameKey = NodePermissionsKey(groupName, rankID); + GroupByUUIDKey idKey = GroupByUUIDKey(groupID, rankID); NodePermissionsPointer perms; if (_groupPermissions.contains(nameKey)) { perms = _groupPermissions[nameKey]; @@ -561,10 +581,10 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { 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); + QHash& ranksForGroup = _groupRanks[groupID]; + foreach (QUuid rankID, ranksForGroup.keys()) { + NodePermissionsKey nameKey = NodePermissionsKey(groupName, rankID); + GroupByUUIDKey idKey = GroupByUUIDKey(groupID, rankID); NodePermissionsPointer perms; if (_groupForbiddens.contains(nameKey)) { perms = _groupForbiddens[nameKey]; @@ -610,8 +630,8 @@ NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString return nullPermissions; } -NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, int rank) const { - NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rank); +NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, QUuid rankID) const { + NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID); if (_groupPermissions.contains(groupRankKey)) { return *(_groupPermissions[groupRankKey].get()); } @@ -620,8 +640,8 @@ NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QStrin return nullPermissions; } -NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QUuid& groupID, int rank) const { - GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rank); +NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QUuid& groupID, QUuid rankID) const { + GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rankID); if (!_groupPermissionsByUUID.contains(byUUIDKey)) { NodePermissions nullPermissions; nullPermissions.setAll(false); @@ -631,8 +651,8 @@ NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QUuid& return getPermissionsForGroup(groupKey.first, groupKey.second); } -NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QString& groupName, int rank) const { - NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rank); +NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QString& groupName, QUuid rankID) const { + NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID); if (_groupForbiddens.contains(groupRankKey)) { return *(_groupForbiddens[groupRankKey].get()); } @@ -642,8 +662,8 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QString return nullForbiddens; } -NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& groupID, int rank) const { - GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rank); +NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& groupID, QUuid rankID) const { + GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rankID); if (!_groupForbiddensByUUID.contains(byUUIDKey)) { NodePermissions nullForbiddens; // XXX should this be setAll(true) ? @@ -1042,9 +1062,14 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) { return v1.toString() < v2.toString(); } - if (m1.contains("rank_name") && m2.contains("rank_name") && + // 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(); + // } + + if (m1.contains("rank_order") && m2.contains("rank_order") && m1["permissions_id"].toString() == m2["permissions_id"].toString()) { - return m1["rank_name"].toString() < m2["rank_name"].toString(); + return m1["rank_order"].toInt() < m2["rank_order"].toInt(); } return m1["permissions_id"].toString() < m2["permissions_id"].toString(); @@ -1062,6 +1087,16 @@ void DomainServerSettingsManager::sortPermissions() { QList* permissionsList = reinterpret_cast(permissions); std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); } + QVariant* groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH); + if (groupPermissions && groupPermissions->canConvert(QMetaType::QVariantList)) { + QList* permissionsList = reinterpret_cast(groupPermissions); + std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); + } + QVariant* forbiddenPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH); + if (forbiddenPermissions && forbiddenPermissions->canConvert(QMetaType::QVariantList)) { + QList* permissionsList = reinterpret_cast(forbiddenPermissions); + std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); + } } void DomainServerSettingsManager::persistToFile() { @@ -1138,10 +1173,15 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() { return; } + bool changed = false; + QStringList groupNames = getAllKnownGroupNames(); foreach (QString groupName, groupNames) { - if (_groupIDs.contains(groupName.toLower())) { - // we already know about this one + QString lowerGroupName = groupName.toLower(); + if (_groupIDs.contains(lowerGroupName)) { + // we already know about this one. recall setGroupID in case the group has been + // added to another section (the same group is found in both groups and blacklists). + changed = setGroupID(groupName, _groupIDs[lowerGroupName]); continue; } apiGetGroupID(groupName); @@ -1151,6 +1191,12 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() { apiGetGroupRanks(groupID); } + changed |= ensurePermissionsForGroupRanks(); + + if (changed) { + packPermissions(); + } + unpackPermissions(); } @@ -1222,8 +1268,6 @@ void DomainServerSettingsManager::apiGetGroupIDErrorCallback(QNetworkReply& requ } void DomainServerSettingsManager::apiGetGroupRanks(const QUuid& groupID) { - _groupRanksLastFetched[groupID] = usecTimestampNow(); - JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "apiGetGroupRanksJSONCallback"; @@ -1278,18 +1322,32 @@ void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& re foreach (auto groupID, groups.keys()) { QJsonObject group = groups[groupID].toObject(); QJsonArray ranks = group["ranks"].toArray(); + + QHash& ranksForGroup = _groupRanks[groupID]; + QHash idsFromThisUpdate; + for (int rankIndex = 0; rankIndex < ranks.size(); rankIndex++) { QJsonObject rank = ranks.at(rankIndex).toObject(); - QString rankName = rank["name"].toString(); + + QUuid rankID = QUuid(rank["id"].toString()); int rankOrder = rank["order"].toInt(); - QVector& ranksForGroup = _groupRanks[groupID]; - if (ranksForGroup.size() < rankOrder + 1) { - ranksForGroup.resize(rankOrder + 1); + QString rankName = rank["name"].toString(); + int rankMembersCount = rank["members_count"].toInt(); + + GroupRank groupRank(rankID, rankOrder, rankName, rankMembersCount); + + if (ranksForGroup[rankID] != groupRank) { + ranksForGroup[rankID] = groupRank; changed = true; } - if (ranksForGroup[rankOrder] != rankName) { - ranksForGroup[rankOrder] = rankName; - changed = true; + + idsFromThisUpdate[rankID] = true; + } + + // clean up any that went away + foreach (QUuid rankID, ranksForGroup.keys()) { + if (!idsFromThisUpdate.contains(rankID)) { + ranksForGroup.remove(rankID); } } } @@ -1307,20 +1365,20 @@ void DomainServerSettingsManager::apiGetGroupRanksErrorCallback(QNetworkReply& r qDebug() << "******************** getGroupRanks api call failed:" << requestReply.error(); } -void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, int rank) { - if (rank >= 0) { - _groupMembership[name][groupID] = rank; +void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID) { + if (rankID != QUuid()) { + _groupMembership[name][groupID] = rankID; } else { _groupMembership[name].remove(groupID); } } -int DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) { - const QHash& groupsForName = _groupMembership[name]; +QUuid DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) { + const QHash& groupsForName = _groupMembership[name]; if (groupsForName.contains(groupID)) { return groupsForName[groupID]; } - return -1; + return QUuid(); } QList DomainServerSettingsManager::getGroupIDs() { @@ -1370,9 +1428,10 @@ void DomainServerSettingsManager::debugDumpGroupsState() { qDebug() << "_groupRanks:"; foreach (QUuid groupID, _groupRanks.keys()) { - QVector& ranksForGroup = _groupRanks[groupID]; + QHash& ranksForGroup = _groupRanks[groupID]; QString readableRanks; - foreach (QString rankName, ranksForGroup) { + foreach (QUuid rankID, ranksForGroup.keys()) { + QString rankName = ranksForGroup[rankID].name; if (readableRanks == "") { readableRanks = rankName; } else { @@ -1381,4 +1440,14 @@ void DomainServerSettingsManager::debugDumpGroupsState() { } qDebug() << "| " << groupID << "==>" << readableRanks; } + + qDebug() << "_groupMembership"; + foreach (QString userName, _groupMembership.keys()) { + QHash& groupsForUser = _groupMembership[userName]; + QString line = ""; + foreach (QUuid groupID, groupsForUser.keys()) { + line += " g=" + groupID.toString() + ",r=" + groupsForUser[groupID].toString(); + } + qDebug() << "| " << userName << line; + } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index f59d76bd1e..c4d70baf21 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -30,7 +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; +using GroupByUUIDKey = QPair; // groupID, rankID + class DomainServerSettingsManager : public QObject { Q_OBJECT @@ -58,32 +59,34 @@ public: QStringList getAllNames() const; // these give access to permissions for specific groups from the domain-server settings page - bool havePermissionsForGroup(const QString& groupName, int rank) const { - return _groupPermissions.contains(groupName, rank); + bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const { + return _groupPermissions.contains(groupName, rankID); } - NodePermissions getPermissionsForGroup(const QString& groupName, int rank) const; - NodePermissions getPermissionsForGroup(const QUuid& groupID, int rank) const; + NodePermissions getPermissionsForGroup(const QString& groupName, QUuid rankID) const; + NodePermissions getPermissionsForGroup(const QUuid& groupID, QUuid rankID) const; // these remove permissions from users in certain groups - 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; + bool haveForbiddensForGroup(const QString& groupName, QUuid rankID) const { + return _groupForbiddens.contains(groupName, rankID); + } + NodePermissions getForbiddensForGroup(const QString& groupName, QUuid rankID) const; + NodePermissions getForbiddensForGroup(const QUuid& groupID, QUuid rankID) const; QStringList getAllKnownGroupNames(); bool setGroupID(const QString& groupName, const QUuid& groupID); + GroupRank getGroupRank(QUuid groupID, QUuid rankID) { return _groupRanks[groupID][rankID]; } 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, int rank); - int isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member + void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID); + QUuid isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member // calls http api to refresh group information void apiRefreshGroupInformation(); - signals: void updateNodePermissions(); @@ -138,11 +141,10 @@ private: QHash _groupNames; // keep track of group-id to group-name mappings // remember the responses to api/v1/groups/%1/ranks - QHash> _groupRanks; // QHash> - QHash _groupRanksLastFetched; // when did we last update _groupRanks + QHash> _groupRanks; // QHash> // keep track of answers to api queries about which users are in which groups - QHash> _groupMembership; // QHash> + QHash> _groupMembership; // QHash> void debugDumpGroupsState(); diff --git a/libraries/networking/src/GroupRank.h b/libraries/networking/src/GroupRank.h new file mode 100644 index 0000000000..89675684d8 --- /dev/null +++ b/libraries/networking/src/GroupRank.h @@ -0,0 +1,36 @@ +// +// GroupRank.h +// libraries/networking/src/ +// +// Created by Seth Alves on 2016-7-21. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_GroupRank_h +#define hifi_GroupRank_h + +class GroupRank { +public: + GroupRank() : id(QUuid()), order(-1), name(""), membersCount(-1) {} + GroupRank(QUuid id, unsigned int order, QString name, unsigned int membersCount) : + id(id), order(order), name(name), membersCount(membersCount) {} + + QUuid id; + int order; + QString name; + int membersCount; +}; + +inline bool operator==(const GroupRank& lhs, const GroupRank& rhs) { + return + lhs.id == rhs.id && + lhs.order == rhs.order && + lhs.name == rhs.name && + lhs.membersCount == rhs.membersCount; +} +inline bool operator!=(const GroupRank& lhs, const GroupRank& rhs) { return !(lhs == rhs); } + +#endif // hifi_GroupRank_h diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index 29b05fc837..c73ca122ff 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -32,8 +32,8 @@ NodePermissions::NodePermissions(QMap perms) { _groupIDSet = true; } } - if (perms.contains("rank")) { - _rank = perms["rank"].toInt(); + if (perms.contains("rank_id")) { + _rankID = QUuid(perms["rank_id"].toString()); } permissions = NodePermissions::Permissions(); @@ -46,14 +46,15 @@ NodePermissions::NodePermissions(QMap perms) { Permission::canConnectPastMaxCapacity : Permission::none; } -QVariant NodePermissions::toVariant(QVector rankNames) { +QVariant NodePermissions::toVariant(QHash groupRanks) { QMap values; values["permissions_id"] = _id; if (_groupIDSet) { values["group_id"] = _groupID; - values["rank"] = _rank; - if (rankNames.size() > _rank) { - values["rank_name"] = rankNames[_rank]; + if (groupRanks.contains(_rankID)) { + values["rank_id"] = _rankID; + values["rank_name"] = groupRanks[_rankID].name; + values["rank_order"] = groupRanks[_rankID].order; } } values["id_can_connect"] = can(Permission::canConnectToDomain); @@ -147,7 +148,7 @@ QDataStream& operator>>(QDataStream& in, NodePermissions& perms) { QDebug operator<<(QDebug debug, const NodePermissions& perms) { debug.nospace() << "[permissions: " << perms.getID() << "/" << perms.getVerifiedUserName() << " -- "; - debug.nospace() << "rank=" << perms.getRank() + debug.nospace() << "rank=" << perms.getRankID() << ", 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 541f90aee8..6d12c4cf7e 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -18,21 +18,24 @@ #include #include +#include "GroupRank.h" + class NodePermissions; using NodePermissionsPointer = std::shared_ptr; -using NodePermissionsKey = QPair; -using NodePermissionsKeyList = QList>; +using NodePermissionsKey = QPair; // name, rankID +using NodePermissionsKeyList = QList>; + class NodePermissions { public: - 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() { _id = QUuid::createUuid().toString(); _rankID = QUuid(); } + NodePermissions(const QString& name) { _id = name.toLower(); _rankID = QUuid(); } + NodePermissions(const NodePermissionsKey& key) { _id = key.first.toLower(); _rankID = key.second; } NodePermissions(QMap perms); 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); } + QUuid getRankID() const { return _rankID; } + NodePermissionsKey getKey() const { return NodePermissionsKey(_id, _rankID); } // the _id member isn't authenticated/verified and _username is. void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); } @@ -63,7 +66,7 @@ public: Q_DECLARE_FLAGS(Permissions, Permission) Permissions permissions; - QVariant toVariant(QVector rankNames = QVector()); + QVariant toVariant(QHash groupRanks = QHash()); void setAll(bool value); @@ -81,7 +84,7 @@ public: protected: QString _id; - int _rank { 0 }; // 0 unless this is for a group + QUuid _rankID { QUuid() }; // 0 unless this is for a group QString _verifiedUserName; bool _groupIDSet { false }; @@ -107,7 +110,7 @@ public: bool contains(const NodePermissionsKey& key) const { return _data.contains(NodePermissionsKey(key.first.toLower(), key.second)); } - bool contains(const QString& keyFirst, int keySecond) const { + bool contains(const QString& keyFirst, QUuid keySecond) const { return _data.contains(NodePermissionsKey(keyFirst.toLower(), keySecond)); } QList keys() const { return _data.keys(); } From 6ec8aff64539ca870b57ff3a9ba4212599326679 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 22 Jul 2016 12:02:17 -0700 Subject: [PATCH 31/56] remove group-related debug prints --- domain-server/src/DomainGatekeeper.cpp | 42 +++++++++++-------- .../src/DomainServerSettingsManager.cpp | 9 ---- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 08fe40faff..6923038ab0 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -126,26 +126,36 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, if (isLocalUser) { userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost); - qDebug() << "user-permissions: is local user, so:" << userPerms; + #ifdef WANT_DEBUG + qDebug() << "| user-permissions: is local user, so:" << userPerms; + #endif } if (verifiedUsername.isEmpty()) { userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); - qDebug() << "user-permissions: unverified or no username, so:" << userPerms; + #ifdef WANT_DEBUG + qDebug() << "| user-permissions: unverified or no username, so:" << userPerms; + #endif } else { userPerms.setVerifiedUserName(verifiedUsername); if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) { userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); - qDebug() << "user-permissions: specific user matches, so:" << userPerms; + #ifdef WANT_DEBUG + qDebug() << "| user-permissions: specific user matches, so:" << userPerms; + #endif } else { // they are logged into metaverse, but we don't have specific permissions for them. userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); - qDebug() << "user-permissions: user is logged-into metaverse, so:" << userPerms; + #ifdef WANT_DEBUG + qDebug() << "| user-permissions: user is logged-into metaverse, so:" << userPerms; + #endif // if this user is a friend of the domain-owner, give them friend's permissions if (_domainOwnerFriends.contains(verifiedUsername)) { userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameFriends); - qDebug() << "user-permissions: user is friends with domain-owner, so:" << userPerms; + #ifdef WANT_DEBUG + qDebug() << "| user-permissions: user is friends with domain-owner, so:" << userPerms; + #endif } // if this user is a known member of a group, give them the implied permissions @@ -155,13 +165,14 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rankID); GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID); - qDebug() << "user-permissions: user is in group:" << groupID << " rank:" << rank.name << "so:" << userPerms; + #ifdef WANT_DEBUG + qDebug() << "| user-permissions: user is in group:" << groupID << " rank:" + << rank.name << "so:" << userPerms; + #endif } } // if this user is a known member of a blacklist group, remove the implied permissions - qDebug() << "------------------ checking blacklists ----------------------"; - qDebug() << _server->_settingsManager.getBlacklistGroupIDs(); foreach (QUuid groupID, _server->_settingsManager.getBlacklistGroupIDs()) { QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID); if (rankID != QUuid()) { @@ -170,17 +181,19 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rankID); GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID); - qDebug() << "user-permissions: user is in blacklist group:" << groupID << " rank:" << rank.name + #ifdef WANT_DEBUG + qDebug() << "| user-permissions: user is in blacklist group:" << groupID << " rank:" << rank.name << "so:" << userPerms; + #endif } - } else { - qDebug() << groupID << verifiedUsername << "is not member."; } } } } - qDebug() << "user-permissions: final:" << userPerms; + #ifdef WANT_DEBUG + qDebug() << "| user-permissions: final:" << userPerms; + #endif return userPerms; } @@ -781,12 +794,7 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) // "status":"success" // } - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - - qDebug() << "********* getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); - - if (jsonObject["status"].toString() == "success") { QJsonObject data = jsonObject["data"].toObject(); QJsonObject groups = data["groups"].toObject(); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index bd429029ed..665ce222ed 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -31,9 +31,6 @@ #include "DomainServerSettingsManager.h" -#define WANT_DEBUG 1 - - const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; const QString DESCRIPTION_SETTINGS_KEY = "settings"; @@ -598,8 +595,6 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { } } - debugDumpGroupsState(); - return changed; } @@ -764,10 +759,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray; rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object(); - - - qDebug() << QJsonDocument(rootObject).toJson(QJsonDocument::Indented); - connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); } From 8e5262872e41af10bd935989f89fb8f25405940e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 22 Jul 2016 14:00:46 -0700 Subject: [PATCH 32/56] fix bug that was losing verified status for users with permissions specific to their account --- domain-server/src/DomainGatekeeper.cpp | 13 ++++++++++--- domain-server/src/DomainServerSettingsManager.h | 5 ++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 6923038ab0..718bfafdc0 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -21,6 +21,9 @@ #include "DomainServer.h" #include "DomainServerNodeData.h" +#define WANT_DEBUG 1 + + using SharedAssignmentPointer = QSharedPointer; DomainGatekeeper::DomainGatekeeper(DomainServer* server) : @@ -134,16 +137,17 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, if (verifiedUsername.isEmpty()) { userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); #ifdef WANT_DEBUG - qDebug() << "| user-permissions: unverified or no username, so:" << userPerms; + qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms; #endif } else { - userPerms.setVerifiedUserName(verifiedUsername); if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) { userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); + userPerms.setVerifiedUserName(verifiedUsername); #ifdef WANT_DEBUG qDebug() << "| user-permissions: specific user matches, so:" << userPerms; #endif } else { + userPerms.setVerifiedUserName(verifiedUsername); // they are logged into metaverse, but we don't have specific permissions for them. userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); #ifdef WANT_DEBUG @@ -851,7 +855,6 @@ void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply& req void DomainGatekeeper::refreshGroupsCache() { // if agents are connected to this domain, refresh our cached information about groups and memberships in such. - getDomainOwnerFriendsList(); int agentCount = 0; @@ -872,4 +875,8 @@ void DomainGatekeeper::refreshGroupsCache() { } updateNodePermissions(); + + #if WANT_DEBUG + _server->_settingsManager.debugDumpGroupsState(); + #endif } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index c4d70baf21..61642d4914 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -87,6 +87,8 @@ public: // calls http api to refresh group information void apiRefreshGroupInformation(); + void debugDumpGroupsState(); + signals: void updateNodePermissions(); @@ -145,9 +147,6 @@ private: // keep track of answers to api queries about which users are in which groups QHash> _groupMembership; // QHash> - - - void debugDumpGroupsState(); }; #endif // hifi_DomainServerSettingsManager_h From 82dfdebd4669df6c58f81a4d2ec6a44dd4ec6199 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 22 Jul 2016 14:14:03 -0700 Subject: [PATCH 33/56] move permissions for specific-users below groups. turn debug-prints back off --- .../resources/describe-settings.json | 132 +++++++++--------- domain-server/src/DomainGatekeeper.cpp | 3 - 2 files changed, 66 insertions(+), 69 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index aca288c452..83c59f81a7 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -451,72 +451,6 @@ "non-deletable-row-key": "permissions_id", "non-deletable-row-values": ["localhost", "anonymous", "logged-in"] }, - { - "name": "permissions", - "type": "table", - "caption": "Permissions for Specific Users", - "can_add_new_rows": true, - - "groups": [ - { - "label": "User", - "span": 1 - }, - { - "label": "Permissions ?", - "span": 6 - } - ], - - "columns": [ - { - "name": "permissions_id", - "label": "" - }, - { - "name": "id_can_connect", - "label": "Connect", - "type": "checkbox", - "editable": true, - "default": false - }, - { - "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_permissions", "type": "table", @@ -682,6 +616,72 @@ "default": false } ] + }, + { + "name": "permissions", + "type": "table", + "caption": "Permissions for Specific Users", + "can_add_new_rows": true, + + "groups": [ + { + "label": "User", + "span": 1 + }, + { + "label": "Permissions ?", + "span": 6 + } + ], + + "columns": [ + { + "name": "permissions_id", + "label": "" + }, + { + "name": "id_can_connect", + "label": "Connect", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "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 + } + ] } ] }, diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 718bfafdc0..eb311d3aa7 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -21,9 +21,6 @@ #include "DomainServer.h" #include "DomainServerNodeData.h" -#define WANT_DEBUG 1 - - using SharedAssignmentPointer = QSharedPointer; DomainGatekeeper::DomainGatekeeper(DomainServer* server) : From f35b9350313164f0a9708f9a5787001c26a7f9d9 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 22 Jul 2016 17:51:31 -0700 Subject: [PATCH 34/56] do some cleaning-up of the domain-server settings page group tables --- .../resources/describe-settings.json | 30 ++++--- .../resources/web/settings/js/settings.js | 10 ++- .../src/DomainServerSettingsManager.cpp | 82 ++++++++++++++----- .../src/DomainServerSettingsManager.h | 3 +- libraries/networking/src/NodePermissions.h | 2 + 5 files changed, 93 insertions(+), 34 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 83c59f81a7..2965276969 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -460,7 +460,7 @@ "groups": [ { "label": "Group", - "span": 5 + "span": 2 }, { "label": "Permissions ?", @@ -475,20 +475,26 @@ }, { "name": "rank_id", - "label": "Rank ID" + "label": "Rank ID", + "readonly": true, + "hidden": true }, { "name": "rank_order", - "label": "Rank Order" + "label": "Rank Order", + "readonly": true, + "hidden": true }, { "name": "rank_name", - "label": "Rank Name" + "label": "Rank Name", + "readonly": true }, { "name": "group_id", "label": "Group ID", - "readonly": true + "readonly": true, + "hidden": true }, { "name": "id_can_connect", @@ -543,7 +549,7 @@ "groups": [ { "label": "Group", - "span": 5 + "span": 2 }, { "label": "Permissions ?", @@ -558,20 +564,24 @@ }, { "name": "rank_id", - "label": "Rank ID" + "label": "Rank ID", + "hidden": true }, { "name": "rank_order", - "label": "Rank Order" + "label": "Rank Order", + "hidden": true }, { "name": "rank_name", - "label": "Rank Name" + "label": "Rank Name", + "readonly": true }, { "name": "group_id", "label": "Group ID", - "readonly": true + "readonly": true, + "hidden": true }, { "name": "id_can_connect", diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 54be33e764..df4509a1ac 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -975,7 +975,8 @@ function makeTable(setting, keypath, setting_value, isLocked) { } _.each(setting.columns, function(col) { - html += "" + col.label + "" // Data + html += "" + col.label + "" // Data }) if (!isLocked && !setting.read_only) { @@ -1027,8 +1028,9 @@ function makeTable(setting, keypath, setting_value, isLocked) { + "name='" + colName + "' value='" + (colValue || col.default || "00:00") + "' />"; } else { // Use a hidden input so that the values are posted. - html += "" - + colValue + ""; + html += "" + colValue + ""; } }) @@ -1081,7 +1083,7 @@ function makeTableInputs(setting) { + ""; } else { - html += "\ + html += "\ \ " diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 665ce222ed..9d2d4c83ee 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -324,8 +324,34 @@ void DomainServerSettingsManager::validateDescriptorsMap() { } } + +void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows, + QString groupName, NodePermissionsPointer perms) { + // this is called when someone has used the domain-settings webpage to add a group. They type the group's name + // and give it some permissions. The domain-server asks api for the group's ranks and populates the map + // with them. Here, that initial user-entered row is removed and it's permissions are copied to all the ranks + // except owner. + + QString groupNameLower = groupName.toLower(); + + foreach (NodePermissionsKey nameKey, permissionsRows.keys()) { + if (nameKey.first.toLower() != groupNameLower) { + continue; + } + QUuid groupID = _groupIDs[groupNameLower]; + QUuid rankID = nameKey.second; + GroupRank rank = _groupRanks[groupID][rankID]; + if (rank.order == 0) { + // we don't copy the initial permissions to the owner. + continue; + } + permissionsRows[nameKey]->setAll(false); + permissionsRows[nameKey] |= perms; + } +} + void DomainServerSettingsManager::packPermissionsForMap(QString mapName, - NodePermissionsMap& agentPermissions, + NodePermissionsMap& permissionsRows, QString keyPath) { // find (or create) the "security" section of the settings map QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); @@ -344,7 +370,7 @@ void DomainServerSettingsManager::packPermissionsForMap(QString mapName, // convert details for each member of the subsection QVariantList* permissionsList = reinterpret_cast(permissions); (*permissionsList).clear(); - QList permissionsKeys = agentPermissions.keys(); + QList permissionsKeys = permissionsRows.keys(); // when a group is added from the domain-server settings page, the config map has a group-name with // no ID or rank. We need to leave that there until we get a valid response back from the api. @@ -353,20 +379,30 @@ void DomainServerSettingsManager::packPermissionsForMap(QString mapName, QHash groupNamesWithRanks; // note which groups have rank/ID information foreach (NodePermissionsKey userKey, permissionsKeys) { - NodePermissionsPointer perms = agentPermissions[userKey]; + NodePermissionsPointer perms = permissionsRows[userKey]; if (perms->getRankID() != QUuid()) { groupNamesWithRanks[userKey.first] = true; } } + foreach (NodePermissionsKey userKey, permissionsKeys) { + NodePermissionsPointer perms = permissionsRows[userKey]; + if (perms->isGroup()) { + QString groupName = userKey.first; + if (perms->getRankID() == QUuid() && groupNamesWithRanks.contains(groupName)) { + // copy the values from this user-added entry to the other (non-owner) ranks and remove it. + permissionsRows.remove(userKey); + initializeGroupPermissions(permissionsRows, groupName, perms); + } + } + } // convert each group-name / rank-id pair to a variant-map foreach (NodePermissionsKey userKey, permissionsKeys) { - NodePermissionsPointer perms = agentPermissions[userKey]; + if (!permissionsRows.contains(userKey)) { + continue; + } + NodePermissionsPointer perms = permissionsRows[userKey]; if (perms->isGroup()) { - if (perms->getRankID() == QUuid() && groupNamesWithRanks.contains(userKey.first)) { - // skip over the entry that was created when the user added the group. - continue; - } QHash& groupRanks = _groupRanks[perms->getGroupID()]; *permissionsList += perms->toVariant(groupRanks); } else { @@ -567,10 +603,17 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { perms = _groupPermissions[nameKey]; } else { perms = NodePermissionsPointer(new NodePermissions(nameKey)); - perms->setGroupID(groupID); _groupPermissions[nameKey] = perms; changed = true; } + if (perms->getGroupID() != groupID) { + perms->setGroupID(groupID); + changed = true; + } + if (perms->getRankID() != rankID) { + perms->setRankID(rankID); + changed = true; + } _groupPermissionsByUUID[idKey] = perms; } } @@ -587,10 +630,17 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { perms = _groupForbiddens[nameKey]; } else { perms = NodePermissionsPointer(new NodePermissions(nameKey)); - perms->setGroupID(groupID); _groupForbiddens[nameKey] = perms; changed = true; } + if (perms->getGroupID() != groupID) { + perms->setGroupID(groupID); + changed = true; + } + if (perms->getRankID() != rankID) { + perms->setRankID(rankID); + changed = true; + } _groupForbiddensByUUID[idKey] = perms; } } @@ -745,6 +795,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); } else { unpackPermissions(); + apiRefreshGroupInformation(); emit updateNodePermissions(); } @@ -1272,8 +1323,6 @@ void DomainServerSettingsManager::apiGetGroupRanks(const QUuid& groupID) { } void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& requestReply) { - - // { // "data":{ // "groups":{ @@ -1420,16 +1469,11 @@ void DomainServerSettingsManager::debugDumpGroupsState() { qDebug() << "_groupRanks:"; foreach (QUuid groupID, _groupRanks.keys()) { QHash& ranksForGroup = _groupRanks[groupID]; - QString readableRanks; + qDebug() << "| " << groupID; foreach (QUuid rankID, ranksForGroup.keys()) { QString rankName = ranksForGroup[rankID].name; - if (readableRanks == "") { - readableRanks = rankName; - } else { - readableRanks += "," + rankName; - } + qDebug() << "| " << rankID << rankName; } - qDebug() << "| " << groupID << "==>" << readableRanks; } qDebug() << "_groupMembership"; diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 61642d4914..f56b1ecd21 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -125,7 +125,8 @@ private: void apiGetGroupID(const QString& groupName); void apiGetGroupRanks(const QUuid& groupID); - void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath); + void initializeGroupPermissions(NodePermissionsMap& permissionsRows, QString groupName, NodePermissionsPointer perms); + void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath); void packPermissions(); void unpackPermissions(); bool ensurePermissionsForGroupRanks(); diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 6d12c4cf7e..37aea6dd05 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -34,6 +34,7 @@ public: NodePermissions(QMap perms); QString getID() const { return _id; } // a user-name or a group-name, not verified + void setRankID(QUuid& rankID) { _rankID = rankID; } QUuid getRankID() const { return _rankID; } NodePermissionsKey getKey() const { return NodePermissionsKey(_id, _rankID); } @@ -116,6 +117,7 @@ public: QList keys() const { return _data.keys(); } QHash get() { return _data; } void clear() { _data.clear(); } + void remove(const NodePermissionsKey& key) { _data.remove(key); } private: QHash _data; From b3e04b53701c1a92abff1fdbea9cab51a9e1545e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 26 Jul 2016 09:35:58 -0700 Subject: [PATCH 35/56] small cleanups for permissions help text --- domain-server/resources/describe-settings.json | 8 ++++---- domain-server/resources/web/css/style.css | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 2965276969..2b7cfc9a74 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -384,7 +384,7 @@ "name": "standard_permissions", "type": "table", "label": "Domain-Wide User Permissions", - "help": "Indicate which users or groups can have which domain-wide permissions.", + "help": "Indicate which types of users can have which domain-wide permissions.", "caption": "Standard Permissions", "can_add_new_rows": false, @@ -463,7 +463,7 @@ "span": 2 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], @@ -552,7 +552,7 @@ "span": 2 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], @@ -639,7 +639,7 @@ "span": 1 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index b9fc8b16e7..b019f59b4e 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -133,7 +133,7 @@ table .headers + .headers td { color: #222; } -table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td, table[name="security.group_permissions"] .headers td + td { +#security table .headers td + td { text-align: center; } From 148793011db1b4fe0ccfa8080e49b867917530ad Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Jul 2016 07:55:09 -0700 Subject: [PATCH 36/56] code review --- .../resources/describe-settings.json | 4 +- domain-server/src/DomainGatekeeper.cpp | 73 ++++++------------- domain-server/src/DomainGatekeeper.h | 4 +- .../src/DomainServerSettingsManager.cpp | 32 ++++---- libraries/networking/src/NodePermissions.h | 8 +- 5 files changed, 46 insertions(+), 75 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 2b7cfc9a74..bb067877aa 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -463,7 +463,7 @@ "span": 2 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], @@ -639,7 +639,7 @@ "span": 1 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index eb311d3aa7..f8d63201be 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -126,37 +126,37 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, if (isLocalUser) { userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost); - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: is local user, so:" << userPerms; - #endif +#endif } if (verifiedUsername.isEmpty()) { userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms; - #endif +#endif } else { if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) { userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); userPerms.setVerifiedUserName(verifiedUsername); - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: specific user matches, so:" << userPerms; - #endif +#endif } else { userPerms.setVerifiedUserName(verifiedUsername); // they are logged into metaverse, but we don't have specific permissions for them. userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: user is logged-into metaverse, so:" << userPerms; - #endif +#endif // if this user is a friend of the domain-owner, give them friend's permissions if (_domainOwnerFriends.contains(verifiedUsername)) { userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameFriends); - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: user is friends with domain-owner, so:" << userPerms; - #endif +#endif } // if this user is a known member of a group, give them the implied permissions @@ -166,10 +166,10 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rankID); GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID); - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: user is in group:" << groupID << " rank:" << rank.name << "so:" << userPerms; - #endif +#endif } } @@ -182,19 +182,19 @@ NodePermissions DomainGatekeeper::applyPermissionsForUser(bool isLocalUser, userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rankID); GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID); - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: user is in blacklist group:" << groupID << " rank:" << rank.name << "so:" << userPerms; - #endif +#endif } } } } } - #ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: final:" << userPerms; - #endif +#endif return userPerms; } @@ -529,7 +529,7 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) { // public-key request for this username is already flight, not rerequesting return; } - _inFlightPublicKeyRequests[lowerUsername] = true; + _inFlightPublicKeyRequests += lowerUsername; // even if we have a public key for them right now, request a new one in case it has just changed JSONCallbackParameters callbackParams; @@ -721,35 +721,6 @@ 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.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs(); -// // TODO -- use alternative that allows checking entire group list in one call -// foreach (QUuid groupID, groupIDs) { -// if (groupID.isNull()) { -// continue; -// } -// getIsGroupMember(username, groupID); -// } -// } - -// void DomainGatekeeper::getIsGroupMember(const QString& username, const QUuid groupID) { -// JSONCallbackParameters callbackParams; -// callbackParams.jsonCallbackReceiver = this; -// callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback"; -// callbackParams.errorCallbackReceiver = this; -// callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback"; - -// 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, -// QNetworkAccessManager::GetOperation, callbackParams); -// } - - - 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. @@ -839,7 +810,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requ QJsonArray friends = jsonObject["data"].toObject()["users"].toArray(); for (int i = 0; i < friends.size(); i++) { QString friendUserName = friends.at(i).toObject()["username"].toString(); - _domainOwnerFriends[friendUserName] = true; + _domainOwnerFriends += friendUserName; } } else { qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); @@ -859,8 +830,8 @@ void DomainGatekeeper::refreshGroupsCache() { nodeList->eachNode([&](const SharedNodePointer& node) { if (!node->getPermissions().isAssignment) { // this node is an agent - QString verifiedUserName = node->getPermissions().getVerifiedUserName(); - if (verifiedUserName != "") { + const QString& verifiedUserName = node->getPermissions().getVerifiedUserName(); + if (verifiedUserName.isEmpty()) { getGroupMemberships(verifiedUserName); } agentCount++; @@ -873,7 +844,7 @@ void DomainGatekeeper::refreshGroupsCache() { updateNodePermissions(); - #if WANT_DEBUG +#if WANT_DEBUG _server->_settingsManager.debugDumpGroupsState(); - #endif +#endif } diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index a18554338a..a10db7420d 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -102,8 +102,8 @@ private: QHash _connectionTokenHash; QHash _userPublicKeys; - QHash _inFlightPublicKeyRequests; // keep track of which we've already asked for - QHash _domainOwnerFriends; // keep track of friends of the domain owner + QSet _inFlightPublicKeyRequests; // keep track of which we've already asked for + QSet _domainOwnerFriends; // keep track of friends of the domain owner NodePermissions applyPermissionsForUser(bool isLocalUser, NodePermissions userPerms, QString verifiedUsername); void getGroupMemberships(const QString& username); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 9d2d4c83ee..2012bf131f 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1164,17 +1164,17 @@ 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(); + QHashIterator i(_groupPermissions.get()); + while (i.hasNext()) { + i.next(); + NodePermissionsKey key = i.key(); result += key.first; } - QHashIterator i_forbiddens(_groupForbiddens.get()); - while (i_forbiddens.hasNext()) { - i_forbiddens.next(); - NodePermissionsKey key = i_forbiddens.key(); + QHashIterator j(_groupForbiddens.get()); + while (j.hasNext()) { + j.next(); + NodePermissionsKey key = j.key(); result += key.first; } @@ -1186,20 +1186,20 @@ bool DomainServerSettingsManager::setGroupID(const QString& groupName, const QUu _groupIDs[groupName.toLower()] = groupID; _groupNames[groupID] = groupName; - QHashIterator i_permissions(_groupPermissions.get()); - while (i_permissions.hasNext()) { - i_permissions.next(); - NodePermissionsPointer perms = i_permissions.value(); + QHashIterator i(_groupPermissions.get()); + while (i.hasNext()) { + i.next(); + NodePermissionsPointer perms = i.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(); + QHashIterator j(_groupForbiddens.get()); + while (j.hasNext()) { + j.next(); + NodePermissionsPointer perms = j.value(); if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) { changed = true; perms->setGroupID(groupID); diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 37aea6dd05..610d0a68aa 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -33,17 +33,17 @@ public: NodePermissions(const NodePermissionsKey& key) { _id = key.first.toLower(); _rankID = key.second; } NodePermissions(QMap perms); - QString getID() const { return _id; } // a user-name or a group-name, not verified + const QString& getID() const { return _id; } // a user-name or a group-name, not verified void setRankID(QUuid& rankID) { _rankID = rankID; } - QUuid getRankID() const { return _rankID; } + const QUuid& getRankID() const { return _rankID; } NodePermissionsKey getKey() const { return NodePermissionsKey(_id, _rankID); } // the _id member isn't authenticated/verified and _username is. void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); } - QString getVerifiedUserName() const { return _verifiedUserName; } + const QString& getVerifiedUserName() const { return _verifiedUserName; } void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }} - QUuid getGroupID() const { return _groupID; } + const QUuid& getGroupID() const { return _groupID; } bool isGroup() const { return _groupIDSet; } bool isAssignment { false }; From bc2ded2f97c37d0b8eee668b6d0087a8120bd7ca Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Jul 2016 08:46:46 -0700 Subject: [PATCH 37/56] code review --- domain-server/src/DomainGatekeeper.cpp | 31 +++++++++---- domain-server/src/DomainGatekeeper.h | 1 + .../src/DomainServerSettingsManager.cpp | 14 +++--- libraries/networking/src/GroupRank.h | 6 +-- libraries/networking/src/NodePermissions.cpp | 45 ------------------- libraries/networking/src/NodePermissions.h | 4 -- 6 files changed, 33 insertions(+), 68 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index f8d63201be..e33c2746f6 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -315,8 +315,6 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); // ask for their public key right now to make sure we have it requestUserPublicKey(username); - getGroupMemberships(username); // optimistically get started on group memberships - getDomainOwnerFriendsList(); return SharedNodePointer(); } @@ -326,7 +324,6 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect userPerms.setVerifiedUserName(username); verifiedUsername = username; getGroupMemberships(username); - getDomainOwnerFriendsList(); } else if (!username.isEmpty()) { // they sent us a username, but it didn't check out requestUserPublicKey(username); @@ -725,6 +722,14 @@ 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. + // if we've already asked, wait for the answer before asking again + QString lowerUsername = username.toLower(); + if (_inFlightGroupMembershipsRequests.contains(lowerUsername)) { + // public-key request for this username is already flight, not rerequesting + return; + } + _inFlightGroupMembershipsRequests += lowerUsername; + QJsonObject json; QSet groupIDSet; foreach (QUuid groupID, _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs()) { @@ -747,7 +752,16 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) { } - +QString extractUsernameFromGroupMembershipsReply(QNetworkReply& requestReply) { + // extract the username from the request url + QString username; + const QString GROUP_MEMBERSHIPS_URL_REGEX_STRING = "api\\/v1\\/groups\\/members\\/([A-Za-z0-9_\\.]+)"; + QRegExp usernameRegex(GROUP_MEMBERSHIPS_URL_REGEX_STRING); + if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { + username = usernameRegex.cap(1); + } + return username.toLower(); +} void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) { // { @@ -781,10 +795,13 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) } else { qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); } + + _inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply)); } void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply& requestReply) { qDebug() << "getIsGroupMember api call failed:" << requestReply.error(); + _inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply)); } void DomainGatekeeper::getDomainOwnerFriendsList() { @@ -825,7 +842,6 @@ void DomainGatekeeper::refreshGroupsCache() { // if agents are connected to this domain, refresh our cached information about groups and memberships in such. getDomainOwnerFriendsList(); - int agentCount = 0; auto nodeList = DependencyManager::get(); nodeList->eachNode([&](const SharedNodePointer& node) { if (!node->getPermissions().isAssignment) { @@ -834,13 +850,10 @@ void DomainGatekeeper::refreshGroupsCache() { if (verifiedUserName.isEmpty()) { getGroupMemberships(verifiedUserName); } - agentCount++; } }); - if (agentCount > 0) { - _server->_settingsManager.apiRefreshGroupInformation(); - } + _server->_settingsManager.apiRefreshGroupInformation(); updateNodePermissions(); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index a10db7420d..1611d14558 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -104,6 +104,7 @@ private: QHash _userPublicKeys; QSet _inFlightPublicKeyRequests; // keep track of which we've already asked for QSet _domainOwnerFriends; // keep track of friends of the domain owner + QSet _inFlightGroupMembershipsRequests; // keep track of which we've already asked for NodePermissions applyPermissionsForUser(bool isLocalUser, NodePermissions userPerms, QString verifiedUsername); void getGroupMemberships(const QString& username); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 2012bf131f..39e3c244d8 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -346,7 +346,7 @@ void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& continue; } permissionsRows[nameKey]->setAll(false); - permissionsRows[nameKey] |= perms; + *(permissionsRows[nameKey]) |= *perms; } } @@ -480,7 +480,7 @@ void DomainServerSettingsManager::unpackPermissions() { foundFriends |= (idKey == NodePermissions::standardNameFriends); if (_standardAgentPermissions.contains(idKey)) { qDebug() << "duplicate name in standard permissions table: " << id; - _standardAgentPermissions[idKey] |= perms; + *(_standardAgentPermissions[idKey]) |= *perms; needPack = true; } else { _standardAgentPermissions[idKey] = perms; @@ -494,7 +494,7 @@ void DomainServerSettingsManager::unpackPermissions() { NodePermissionsKey idKey = NodePermissionsKey(id, 0); if (_agentPermissions.contains(idKey)) { qDebug() << "duplicate name in permissions table: " << id; - _agentPermissions[idKey] |= perms; + *(_agentPermissions[idKey]) |= *perms; needPack = true; } else { _agentPermissions[idKey] = perms; @@ -508,10 +508,10 @@ void DomainServerSettingsManager::unpackPermissions() { NodePermissionsKey idKey = perms->getKey(); if (_groupPermissions.contains(idKey)) { qDebug() << "duplicate name in group permissions table: " << id; - _groupPermissions[idKey] |= perms; + *(_groupPermissions[idKey]) |= *perms; needPack = true; } else { - _groupPermissions[idKey] = perms; + *(_groupPermissions[idKey]) = *perms; } if (perms->isGroup()) { // the group-id was cached. hook-up the uuid in the uuid->group hash @@ -527,7 +527,7 @@ void DomainServerSettingsManager::unpackPermissions() { NodePermissionsKey idKey = perms->getKey(); if (_groupForbiddens.contains(idKey)) { qDebug() << "duplicate name in group forbiddens table: " << id; - _groupForbiddens[idKey] |= perms; + *(_groupForbiddens[idKey]) |= *perms; needPack = true; } else { _groupForbiddens[idKey] = perms; @@ -1367,7 +1367,7 @@ void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& re QHash idsFromThisUpdate; for (int rankIndex = 0; rankIndex < ranks.size(); rankIndex++) { - QJsonObject rank = ranks.at(rankIndex).toObject(); + QJsonObject rank = ranks[rankIndex].toObject(); QUuid rankID = QUuid(rank["id"].toString()); int rankOrder = rank["order"].toInt(); diff --git a/libraries/networking/src/GroupRank.h b/libraries/networking/src/GroupRank.h index 89675684d8..92dadfd220 100644 --- a/libraries/networking/src/GroupRank.h +++ b/libraries/networking/src/GroupRank.h @@ -14,14 +14,14 @@ class GroupRank { public: - GroupRank() : id(QUuid()), order(-1), name(""), membersCount(-1) {} + GroupRank() {} GroupRank(QUuid id, unsigned int order, QString name, unsigned int membersCount) : id(id), order(order), name(name), membersCount(membersCount) {} QUuid id; - int order; + int order { -1 }; QString name; - int membersCount; + int membersCount { -1 }; }; inline bool operator==(const GroupRank& lhs, const GroupRank& rhs) { diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index c73ca122ff..a815884dc5 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -77,47 +77,11 @@ NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) { permissions |= rhs.permissions; 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; -} -NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, NodePermissions::Permission rhs) { - if (lhs) { - lhs.get()->permissions |= rhs; - } - return lhs; -} NodePermissions& NodePermissions::operator&=(const NodePermissions& rhs) { permissions &= rhs.permissions; 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; -} -NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, NodePermissions::Permission rhs) { - if (lhs) { - lhs.get()->permissions &= rhs; - } - return lhs; -} NodePermissions NodePermissions::operator~() { NodePermissions result = *this; @@ -125,15 +89,6 @@ NodePermissions NodePermissions::operator~() { 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 << (uint)perms.permissions; return out; diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 610d0a68aa..07b47037b8 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -72,9 +72,7 @@ public: void setAll(bool value); 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); @@ -128,8 +126,6 @@ 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, NodePermissions::Permission rhs); NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs); NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, NodePermissions::Permission rhs); NodePermissionsPointer operator~(NodePermissionsPointer& lhs); From a2d35df34e4f47c4cbaea03ea7f75ccb811190b1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Jul 2016 09:57:57 -0700 Subject: [PATCH 38/56] fix a couple of mistakes made during code-review response --- domain-server/src/DomainGatekeeper.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index e33c2746f6..891c181c7c 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -315,6 +315,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); // ask for their public key right now to make sure we have it requestUserPublicKey(username); + getGroupMemberships(username); // optimistically get started on group memberships return SharedNodePointer(); } @@ -847,7 +848,7 @@ void DomainGatekeeper::refreshGroupsCache() { if (!node->getPermissions().isAssignment) { // this node is an agent const QString& verifiedUserName = node->getPermissions().getVerifiedUserName(); - if (verifiedUserName.isEmpty()) { + if (!verifiedUserName.isEmpty()) { getGroupMemberships(verifiedUserName); } } From d69d5bff28f667c91284f0104c36be5d95d08db5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Jul 2016 10:06:02 -0700 Subject: [PATCH 39/56] remove redundant call to userPerms.setVerifiedUserName --- domain-server/src/DomainGatekeeper.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 891c181c7c..cab3206031 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -322,7 +322,6 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect QString verifiedUsername; if (!username.isEmpty() && verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // they sent us a username and the signature verifies it - userPerms.setVerifiedUserName(username); verifiedUsername = username; getGroupMemberships(username); } else if (!username.isEmpty()) { From 8663b260f6b6cdac938404d3ab86b0ab025112c2 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Jul 2016 10:14:18 -0700 Subject: [PATCH 40/56] code review -- applyPermissionsForUser to setPermissionsForUser, don't take input permissions as an argument --- domain-server/src/DomainGatekeeper.cpp | 14 +++++++------- domain-server/src/DomainGatekeeper.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index cab3206031..476b99889b 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -120,8 +120,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer(); limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){ // 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().getVerifiedUserName(); - NodePermissions userPerms(NodePermissionsKey(username, 0)); + // authentication and verifiedUsername is only set once they user's key has been confirmed. + QString verifiedUsername = node->getPermissions().getVerifiedUserName(); + NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0)); if (node->getPermissions().isAssignment) { // this node is an assignment-client @@ -224,7 +224,7 @@ void DomainGatekeeper::updateNodePermissions() { const QHostAddress& addr = node->getLocalSocket().getAddress(); bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() || addr == QHostAddress::LocalHost); - userPerms = applyPermissionsForUser(isLocalUser, userPerms, username); + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername); } node->setPermissions(userPerms); @@ -330,7 +330,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect return SharedNodePointer(); } - userPerms = applyPermissionsForUser(isLocalUser, userPerms, verifiedUsername); + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername); if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 1611d14558..12697b8f3b 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -106,7 +106,7 @@ private: QSet _domainOwnerFriends; // keep track of friends of the domain owner QSet _inFlightGroupMembershipsRequests; // keep track of which we've already asked for - NodePermissions applyPermissionsForUser(bool isLocalUser, NodePermissions userPerms, QString verifiedUsername); + NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername); void getGroupMemberships(const QString& username); // void getIsGroupMember(const QString& username, const QUuid groupID); void getDomainOwnerFriendsList(); From 3a3b1489c6cfa29631867ff1e5c7d11be3f145e7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Jul 2016 11:46:52 -0700 Subject: [PATCH 41/56] code review --- .../src/DomainServerSettingsManager.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 39e3c244d8..a02b19f9fd 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -701,19 +701,17 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QString if (_groupForbiddens.contains(groupRankKey)) { return *(_groupForbiddens[groupRankKey].get()); } - NodePermissions nullForbiddens; - // XXX should this be setAll(true) ? - nullForbiddens.setAll(false); - return nullForbiddens; + NodePermissions allForbiddens; + allForbiddens.setAll(true); + return allForbiddens; } NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& groupID, QUuid rankID) const { GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rankID); if (!_groupForbiddensByUUID.contains(byUUIDKey)) { - NodePermissions nullForbiddens; - // XXX should this be setAll(true) ? - nullForbiddens.setAll(false); - return nullForbiddens; + NodePermissions allForbiddens; + allForbiddens.setAll(true); + return allForbiddens; } NodePermissionsKey groupKey = _groupForbiddensByUUID[byUUIDKey]->getKey(); @@ -1104,11 +1102,6 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) { 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(); - // } - if (m1.contains("rank_order") && m2.contains("rank_order") && m1["permissions_id"].toString() == m2["permissions_id"].toString()) { return m1["rank_order"].toInt() < m2["rank_order"].toInt(); From 390ee9aaeb428d4e909e70fe1e01040c06a84085 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Jul 2016 14:03:32 -0700 Subject: [PATCH 42/56] clean up some code, add some debugging prints --- domain-server/src/DomainGatekeeper.cpp | 58 ++++++++++++++++---------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 476b99889b..946e855d0d 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -310,24 +310,30 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); - if (!username.isEmpty() && usernameSignature.isEmpty()) { - // user is attempting to prove their identity to us, but we don't have enough information - sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); - // ask for their public key right now to make sure we have it - requestUserPublicKey(username); - getGroupMemberships(username); // optimistically get started on group memberships - return SharedNodePointer(); - } - - QString verifiedUsername; - if (!username.isEmpty() && verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { - // they sent us a username and the signature verifies it - verifiedUsername = username; - getGroupMemberships(username); - } else if (!username.isEmpty()) { - // they sent us a username, but it didn't check out - requestUserPublicKey(username); - return SharedNodePointer(); + QString verifiedUsername; // if this remains empty, consider this an anonymous connection attempt + if (!username.isEmpty()) { + if (usernameSignature.isEmpty()) { + // user is attempting to prove their identity to us, but we don't have enough information + sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); + // ask for their public key right now to make sure we have it + requestUserPublicKey(username); + getGroupMemberships(username); // optimistically get started on group memberships +#ifdef WANT_DEBUG + qDebug() << "stalling login because we have no username-signature:" << username; +#endif + return SharedNodePointer(); + } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { + // they sent us a username and the signature verifies it + getGroupMemberships(username); + verifiedUsername = username; + } else { + // they sent us a username, but it didn't check out + requestUserPublicKey(username); +#ifdef WANT_DEBUG + qDebug() << "stalling login because signature verification failed:" << username; +#endif + return SharedNodePointer(); + } } userPerms = setPermissionsForUser(isLocalUser, verifiedUsername); @@ -335,6 +341,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers); +#ifdef WANT_DEBUG + qDebug() << "stalling login due to permissions:" << username; +#endif return SharedNodePointer(); } @@ -342,6 +351,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // we can't allow this user to connect because we are at max capacity sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers); +#ifdef WANT_DEBUG + qDebug() << "stalling login due to max capacity:" << username; +#endif return SharedNodePointer(); } @@ -355,10 +367,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // we have a node that already has these exact sockets - this occurs if a node // is unable to connect to the domain hintNodeID = node->getUUID(); - return false; } - return true; }); @@ -378,6 +388,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, uuidStringWithoutCurlyBraces(newNode->getUUID()), username); +#ifdef WANT_DEBUG + qDebug() << "accepting login:" << username; +#endif + return newNode; } @@ -417,7 +431,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, const HifiSockAddr& senderSockAddr) { // it's possible this user can be allowed to connect, but we need to check their username signature - QByteArray publicKeyArray = _userPublicKeys.value(username); + QByteArray publicKeyArray = _userPublicKeys.value(username.toLower()); const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); @@ -568,7 +582,7 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { const QString JSON_DATA_KEY = "data"; const QString JSON_PUBLIC_KEY_KEY = "public_key"; - _userPublicKeys[username] = + _userPublicKeys[username.toLower()] = QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8()); } From cd2f61c90c467fa18853b6f2d362880377defa3a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Jul 2016 14:03:51 -0700 Subject: [PATCH 43/56] if logging in, send username signature even when connecting to localhost --- libraries/networking/src/NodeList.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index a73537aad0..05b5532560 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -283,12 +283,7 @@ void NodeList::sendDomainServerCheckIn() { auto accountManager = DependencyManager::get(); const QUuid& connectionToken = _domainHandler.getConnectionToken(); - // we assume that we're on the same box as the DS if it has the same local address and - // it didn't present us with a connection token to use for username signature - bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost - || (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull()); - - bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain; + bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull(); if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) { qWarning() << "A keypair is required to present a username signature to the domain-server" From 3234de9f9279323d2fdfa8718a58c45a14329f39 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Wed, 27 Jul 2016 11:59:18 -0700 Subject: [PATCH 44/56] Add category headers to groups --- .../resources/describe-settings.json | 22 ++-- domain-server/resources/web/css/style.css | 4 +- .../resources/web/settings/js/settings.js | 116 +++++++++++++----- 3 files changed, 101 insertions(+), 41 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bb067877aa..c1fe975624 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -455,12 +455,13 @@ "name": "group_permissions", "type": "table", "caption": "Permissions for Users in Groups", - "can_add_new_rows": true, + "categorize_by_key": "permissions_id", + "can_add_new_categories": true, "groups": [ { - "label": "Group", - "span": 2 + "label": "Rank", + "span": 1 }, { "label": "Permissions ?", @@ -471,7 +472,9 @@ "columns": [ { "name": "permissions_id", - "label": "Group Name" + "label": "Group Name", + "readonly": true, + "hidden": true }, { "name": "rank_id", @@ -487,7 +490,7 @@ }, { "name": "rank_name", - "label": "Rank Name", + "label": "", "readonly": true }, { @@ -548,8 +551,8 @@ "groups": [ { - "label": "Group", - "span": 2 + "label": "Rank", + "span": 1 }, { "label": "Permissions ?", @@ -560,7 +563,8 @@ "columns": [ { "name": "permissions_id", - "label": "Group Name" + "label": "Group Name", + "hidden": true }, { "name": "rank_id", @@ -574,7 +578,7 @@ }, { "name": "rank_name", - "label": "Rank Name", + "label": "", "readonly": true }, { diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index b019f59b4e..87764237f5 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -20,7 +20,9 @@ body { top: 40px; } -.table .value-row td, .table .inputs td { +.table .value-row td, +.table .value-category td, +.table .inputs td { vertical-align: middle; } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index df4509a1ac..8e5ac25d9a 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -7,10 +7,13 @@ var Settings = { TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', DATA_COL_CLASS: 'value-col', + DATA_CATEGORY_CLASS: 'value-category', ADD_ROW_BUTTON_CLASS: 'add-row', ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row', DEL_ROW_BUTTON_CLASS: 'del-row', DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row', + DEL_CATEGORY_BUTTON_CLASS: 'del-category', + DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category', MOVE_UP_BUTTON_CLASS: 'move-up', MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up', MOVE_DOWN_BUTTON_CLASS: 'move-down', @@ -164,19 +167,23 @@ $(document).ready(function(){ }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ - addTableRow(this); + addTableRow($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ - deleteTableRow(this); + deleteTableRow($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){ + deleteTableCategory($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ - moveTableRow(this, true); + moveTableRow($(this).closest('tr'), true); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){ - moveTableRow(this, false); + moveTableRow($(this).closest('tr'), false); }); $('#' + Settings.FORM_ID).on('keyup', function(e){ @@ -924,7 +931,6 @@ $('body').on('click', '.save-button', function(e){ function makeTable(setting, keypath, setting_value, isLocked) { var isArray = !_.has(setting, 'key'); - var isHash = !isArray; if (!isArray && setting.can_order) { setting.can_order = false; @@ -974,25 +980,60 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" + setting.key.label + "" // Key } + var numVisibleColumns = 0; _.each(setting.columns, function(col) { + if (!col.hidden) numVisibleColumns++; html += "" + col.label + "" // Data }) if (!isLocked && !setting.read_only) { if (setting.can_order) { + numVisibleColumns++; html += ""; } - html += "" + numVisibleColumns++; + html += ""; } // populate rows in the table from existing values var row_num = 1; if (keypath.length > 0 && _.size(setting_value) > 0) { + var rowIsObject = setting.columns.length > 1; + _.each(setting_value, function(row, rowIndexOrName) { - html += "" + var categoryKey = setting.categorize_by_key; + var isCategorized = !!categoryKey; + var categoryData = ""; + if (isCategorized && isArray) { + var predicate = {}; + var categoryValue = row[categoryKey]; + predicate[categoryKey] = categoryValue; + categoryValue = rowIsObject ? row[categoryKey] : row; + categoryData = "data-category='" + categoryValue + "'"; + + if (_.findIndex(setting_value, predicate) === rowIndexOrName) { + html += + "" + + "" + + categoryValue + + "" + + ((setting.can_add_new_categories) ? ( + "" + + "" + + "" + ) : ( + "" + )) + + ""; + } + } + + html += + "" if (setting.numbered === true) { html += "" + row_num + "" @@ -1006,8 +1047,8 @@ function makeTable(setting, keypath, setting_value, isLocked) { _.each(setting.columns, function(col) { + var colValue, colName; if (isArray) { - rowIsObject = setting.columns.length > 1; colValue = rowIsObject ? row[col.name] : row; colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); } else { @@ -1019,18 +1060,25 @@ function makeTable(setting, keypath, setting_value, isLocked) { || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); if (isArray && col.type === "checkbox" && col.editable) { - html += "" - + ""; + html += + "" + + "" + + ""; } else if (isArray && col.type === "time" && col.editable) { - html += "" - + ""; + html += + "" + + "" + + ""; } else { // Use a hidden input so that the values are posted. - html += "" + colValue + ""; + html += + "" + + colValue + + "" + + ""; } }) @@ -1138,9 +1186,7 @@ function badgeSidebarForDifferences(changedElement) { $("a[href='#" + panelParentID + "'] .badge").html(badgeValue); } -function addTableRow(add_glyphicon) { - var row = $(add_glyphicon).closest('tr') - +function addTableRow(row) { var table = row.parents('table') var isArray = table.data('setting-type') === 'array' @@ -1279,9 +1325,7 @@ function addTableRow(add_glyphicon) { row.parent().append(input_clone) } -function deleteTableRow(delete_glyphicon) { - var row = $(delete_glyphicon).closest('tr') - +function deleteTableRow(row) { var table = $(row).closest('table') var isArray = table.data('setting-type') === 'array' @@ -1298,12 +1342,13 @@ function deleteTableRow(delete_glyphicon) { row.remove() } else { // this is the last row, we can't remove it completely since we need to post an empty array - - row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS) - row.addClass('empty-array-row') - - row.html(""); + row + .removeClass(Settings.DATA_ROW_CLASS) + .removeClass(Settings.NEW_ROW_CLASS) + .removeAttr("data-category") + .addClass('empty-array-row') + .html(""); } } @@ -1311,9 +1356,18 @@ function deleteTableRow(delete_glyphicon) { badgeSidebarForDifferences($(table)) } -function moveTableRow(move_glyphicon, move_up) { - var row = $(move_glyphicon).closest('tr') +function deleteTableCategory(categoryHeaderRow) { + var categoryName = categoryHeaderRow.data("category"); + categoryHeaderRow + .closest("table") + .find("tr[data-category='" + categoryName + "']") + .each(function () { + deleteTableRow($(this)); + }); +} + +function moveTableRow(row, move_up) { var table = $(row).closest('table') var isArray = table.data('setting-type') === 'array' if (!isArray) { From 93f9f93b5a1f2a700ad6d3ab2e67034e3349ed99 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Wed, 27 Jul 2016 18:11:08 -0700 Subject: [PATCH 45/56] Enable adding groups --- .../resources/describe-settings.json | 2 + domain-server/resources/web/css/style.css | 13 ++ .../resources/web/settings/js/settings.js | 142 ++++++++++++++---- 3 files changed, 128 insertions(+), 29 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index c1fe975624..5b60b1a82c 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -457,6 +457,8 @@ "caption": "Permissions for Users in Groups", "categorize_by_key": "permissions_id", "can_add_new_categories": true, + "new_category_placeholder": "Add Group", + "new_category_needs_reload": true, "groups": [ { diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 87764237f5..809e9251c4 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -3,6 +3,10 @@ body { padding-bottom: 30px; } +[hidden] { + display: none !important; +} + .table-lead .lead-line { background-color: black; } @@ -33,6 +37,15 @@ body { margin-right: auto; } +.table .value-category { + font-weight: bold; +} + +.table .value-category .message { + font-style: italic; + font-weight: normal; +} + .glyphicon-remove { font-size: 24px; } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 8e5ac25d9a..33afc72891 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -12,6 +12,8 @@ var Settings = { ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row', DEL_ROW_BUTTON_CLASS: 'del-row', DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row', + ADD_CATEGORY_BUTTON_CLASS: 'add-category', + ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category', DEL_CATEGORY_BUTTON_CLASS: 'del-category', DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category', MOVE_UP_BUTTON_CLASS: 'move-up', @@ -174,6 +176,10 @@ $(document).ready(function(){ deleteTableRow($(this).closest('tr')); }); + $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){ + addTableCategory($(this).closest('tr')); + }); + $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){ deleteTableCategory($(this).closest('tr')); }); @@ -205,10 +211,11 @@ $(document).ready(function(){ } if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { - sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click() + sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click(); + sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click(); // set focus to the first input in the new row - $target.closest('table').find('tr.inputs input:first').focus() + $target.closest('table').find('tr.inputs input:first').focus(); } } @@ -1013,21 +1020,8 @@ function makeTable(setting, keypath, setting_value, isLocked) { predicate[categoryKey] = categoryValue; categoryValue = rowIsObject ? row[categoryKey] : row; categoryData = "data-category='" + categoryValue + "'"; - if (_.findIndex(setting_value, predicate) === rowIndexOrName) { - html += - "" + - "" + - categoryValue + - "" + - ((setting.can_add_new_categories) ? ( - "" + - "" + - "" - ) : ( - "" - )) + - ""; + html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, false); } } @@ -1043,7 +1037,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" + rowIndexOrName + "" } - var isNonDeletableRow = !setting.can_add_new_rows; + var isNonDeletableRow = !setting.can_add_new_rows && !setting.can_add_new_categories || setting.new_category_needs_reload; _.each(setting.columns, function(col) { @@ -1104,19 +1098,47 @@ function makeTable(setting, keypath, setting_value, isLocked) { } // populate inputs in the table for new values - if (!isLocked && !setting.read_only && setting.can_add_new_rows) { - html += makeTableInputs(setting) + if (!isLocked && !setting.read_only) { + if (setting.can_add_new_categories) { + html += makeTableCategoryInput(setting, numVisibleColumns); + } + if (setting.can_add_new_rows || setting.can_add_new_categories) { + html += makeTableInputs(setting); + } } html += "" return html; } +function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, needsReload) { + var html = + "" + + "" + + "" + categoryValue + "" + + ((needsReload) ? ( + " - Reload to see contents." + ) : ( + "" + )) + + "" + + ((canRemove) ? ( + "" + + "" + + "" + ) : ( + "" + )) + + ""; + return html; +} + function makeTableInputs(setting) { - var html = "" + var html = ""; if (setting.numbered === true) { - html += "" + html += ""; } if (setting.key) { @@ -1142,12 +1164,28 @@ function makeTableInputs(setting) { html += "" } html += "" + "'>" html += "" return html } +function makeTableCategoryInput(setting, numVisibleColumns) { + var needsReload = setting.new_category_needs_reload; + var categoryKey = setting.categorize_by_key; + var placeholder = setting.new_category_placeholder ? setting.new_category_placeholder : ""; + var html = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + return html; +} + function badgeSidebarForDifferences(changedElement) { // figure out which group this input is in var panelParentID = changedElement.closest('.panel').attr('id'); @@ -1187,10 +1225,11 @@ function badgeSidebarForDifferences(changedElement) { } function addTableRow(row) { - var table = row.parents('table') - var isArray = table.data('setting-type') === 'array' + var table = row.parents('table'); + var isArray = table.data('setting-type') === 'array'; + var keepField = row.data("keep-field"); - var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS) + var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS); if (!isArray) { // Check key spaces @@ -1307,10 +1346,12 @@ function addTableRow(row) { } else { console.log("Unknown table element") } - }) + }); - input_clone.find('input').each(function(){ - $(this).val($(this).attr('data-default')); + input_clone.children('td').each(function () { + if ($(this).attr("name") !== keepField) { + $(this).find("input").val($(this).attr('data-default')); + } }); if (isArray) { @@ -1322,7 +1363,7 @@ function addTableRow(row) { badgeSidebarForDifferences($(table)) - row.parent().append(input_clone) + row.after(input_clone) } function deleteTableRow(row) { @@ -1356,6 +1397,49 @@ function deleteTableRow(row) { badgeSidebarForDifferences($(table)) } +function addTableCategory($categoryInputRow) { + var $input = $categoryInputRow.find("input").first(); + var $rowInput = $categoryInputRow.next(".inputs").clone(); + if (!$rowInput) { + console.error("Error cloning inputs"); + } + + var needsReload = $categoryInputRow.data("needs-reload"); + var categoryKey = $categoryInputRow.data("key"); + var categoryValue = $input.prop("value"); + var width = 0; + $categoryInputRow + .children("td") + .each(function () { + width += $(this).prop("colSpan") || 1; + }); + + $input + .prop("value", "") + .focus(); + + $rowInput.find("td[name='" + categoryKey + "'] > input").first() + .prop("value", categoryValue); + $rowInput + .attr("data-category", categoryValue) + .addClass(Settings.NEW_ROW_CLASS); + + // TODO: create inputs on initial template load + + var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, needsReload)); + $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); + + $categoryInputRow + .before($newCategoryRow) + .before($rowInput); + + if (!needsReload) { + $rowInput.removeAttr("hidden"); + } else { + addTableRow($rowInput); + } +} + function deleteTableCategory(categoryHeaderRow) { var categoryName = categoryHeaderRow.data("category"); From aa909a13663d27351d001c20bbd33b06bbe2dc12 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Thu, 28 Jul 2016 13:47:21 -0700 Subject: [PATCH 46/56] Polish category/row addition and deletion --- .../resources/describe-settings.json | 11 +- domain-server/resources/web/css/style.css | 7 +- .../resources/web/settings/js/settings.js | 140 ++++++++++-------- 3 files changed, 93 insertions(+), 65 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 5b60b1a82c..f4ceb0f432 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -457,8 +457,9 @@ "caption": "Permissions for Users in Groups", "categorize_by_key": "permissions_id", "can_add_new_categories": true, + "can_add_new_rows": false, "new_category_placeholder": "Add Group", - "new_category_needs_reload": true, + "new_category_message": "Reload to see ranks", "groups": [ { @@ -548,8 +549,12 @@ { "name": "group_forbiddens", "type": "table", - "caption": "Permissions denied to Users in Groups", - "can_add_new_rows": true, + "caption": "Permissions Denied to Users in Groups", + "categorize_by_key": "permissions_id", + "can_add_new_categories": true, + "can_add_new_rows": false, + "new_category_placeholder": "Add Blacklist Group", + "new_category_message": "Reload to see ranks", "groups": [ { diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 809e9251c4..0a3dc3dab5 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -41,11 +41,16 @@ body { font-weight: bold; } -.table .value-category .message { +.table .value-category [message]::after { + content: attr(message); font-style: italic; font-weight: normal; } +.table .value-category.inputs { + font-weight: normal; +} + .glyphicon-remove { font-size: 24px; } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 33afc72891..4b258fa9fb 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -42,7 +42,7 @@ var viewHelpers = { form_group = "

"; setting_value = _(values).valueForKeyPath(keypath); - if (typeof setting_value == 'undefined' || setting_value === null) { + if (_.isUndefined(setting_value) || _.isNull(setting_value)) { if (_.has(setting, 'default')) { setting_value = setting.default; } else { @@ -56,11 +56,11 @@ var viewHelpers = { } function common_attrs(extra_classes) { - extra_classes = (typeof extra_classes !== 'undefined' ? extra_classes : ""); + extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') + " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='" + setting.name + "' name='" + keypath + "' " - + "id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath) + "'"; + + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'"; } if (setting.type === 'checkbox') { @@ -937,6 +937,7 @@ $('body').on('click', '.save-button', function(e){ }); function makeTable(setting, keypath, setting_value, isLocked) { + // TODO: enforce category sorting var isArray = !_.has(setting, 'key'); if (!isArray && setting.can_order) { @@ -952,9 +953,10 @@ function makeTable(setting, keypath, setting_value, isLocked) { var nonDeletableRowKey = setting["non-deletable-row-key"]; var nonDeletableRowValues = setting["non-deletable-row-values"]; - html += ""; + html += "
"; if (setting.caption) { html += "" @@ -1012,22 +1014,20 @@ function makeTable(setting, keypath, setting_value, isLocked) { _.each(setting_value, function(row, rowIndexOrName) { var categoryKey = setting.categorize_by_key; - var isCategorized = !!categoryKey; - var categoryData = ""; - if (isCategorized && isArray) { - var predicate = {}; - var categoryValue = row[categoryKey]; - predicate[categoryKey] = categoryValue; + var isCategorized = !!categoryKey && isArray; + var categoryPair = {}; + var categoryValue = ""; + if (isCategorized) { categoryValue = rowIsObject ? row[categoryKey] : row; - categoryData = "data-category='" + categoryValue + "'"; - if (_.findIndex(setting_value, predicate) === rowIndexOrName) { - html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, false); + categoryPair[categoryKey] = categoryValue; + if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) { + html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, ""); } } - html += - "" + html += ""; if (setting.numbered === true) { html += "" @@ -1037,7 +1037,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" } - var isNonDeletableRow = !setting.can_add_new_rows && !setting.can_add_new_categories || setting.new_category_needs_reload; + var isNonDeletableRow = !setting.can_add_new_rows; _.each(setting.columns, function(col) { @@ -1075,7 +1075,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { ""; } - }) + }); if (!isLocked && !setting.read_only) { if (setting.can_order) { @@ -1093,6 +1093,10 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" + if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) { + html += makeTableInputs(setting, categoryPair, categoryValue); + } + row_num++ }); } @@ -1103,7 +1107,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += makeTableCategoryInput(setting, numVisibleColumns); } if (setting.can_add_new_rows || setting.can_add_new_categories) { - html += makeTableInputs(setting); + html += makeTableInputs(setting, {}, ""); } } html += "
" + setting.caption + "
" + row_num + "" + rowIndexOrName + "
" @@ -1111,16 +1115,11 @@ function makeTable(setting, keypath, setting_value, isLocked) { return html; } -function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, needsReload) { +function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) { var html = "" + "" + - "" + categoryValue + "" + - ((needsReload) ? ( - " - Reload to see contents." - ) : ( - "" - )) + + "" + categoryValue + "" + "" + ((canRemove) ? ( "" + @@ -1133,9 +1132,10 @@ function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, return html; } -function makeTableInputs(setting) { - var html = ""; +function makeTableInputs(setting, initialValues, categoryValue) { + var html = ""; if (setting.numbered === true) { html += ""; @@ -1148,15 +1148,21 @@ function makeTableInputs(setting) { } _.each(setting.columns, function(col) { + var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default; if (col.type === "checkbox") { - html += "" - + ""; + html += + "" + + "" + + ""; } else { - html += "\ - \ - " + html += + "" + + "" + + ""; } }) @@ -1171,12 +1177,14 @@ function makeTableInputs(setting) { } function makeTableCategoryInput(setting, numVisibleColumns) { - var needsReload = setting.new_category_needs_reload; + var canAddRows = setting.can_add_new_rows; var categoryKey = setting.categorize_by_key; - var placeholder = setting.new_category_placeholder ? setting.new_category_placeholder : ""; + var placeholder = setting.new_category_placeholder || ""; + var message = setting.new_category_message || ""; var html = - "" + - "" + + "" + + "" + "" + "" + "" + @@ -1366,35 +1374,40 @@ function addTableRow(row) { row.after(input_clone) } -function deleteTableRow(row) { - var table = $(row).closest('table') - var isArray = table.data('setting-type') === 'array' +function deleteTableRow($row) { + var $table = $row.closest('table'); + var categoryName = $row.data("category"); + var isArray = $table.data('setting-type') === 'array'; - row.empty(); + $row.empty(); if (!isArray) { - row.html(""); + $row.html(""); } else { - if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { - updateDataChangedForSiblingRows(row) + if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) { + // This is the last row of the category, so delete the header + $table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove(); + } + + if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { + updateDataChangedForSiblingRows($row); // this isn't the last row - we can just remove it - row.remove() + $row.remove(); } else { // this is the last row, we can't remove it completely since we need to post an empty array - row + $row .removeClass(Settings.DATA_ROW_CLASS) .removeClass(Settings.NEW_ROW_CLASS) .removeAttr("data-category") .addClass('empty-array-row') - .html(""); } } // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated - badgeSidebarForDifferences($(table)) + badgeSidebarForDifferences($table); } function addTableCategory($categoryInputRow) { @@ -1404,7 +1417,8 @@ function addTableCategory($categoryInputRow) { console.error("Error cloning inputs"); } - var needsReload = $categoryInputRow.data("needs-reload"); + var canAddRows = $categoryInputRow.data("can-add-rows"); + var message = $categoryInputRow.data("message"); var categoryKey = $categoryInputRow.data("key"); var categoryValue = $input.prop("value"); var width = 0; @@ -1426,28 +1440,32 @@ function addTableCategory($categoryInputRow) { // TODO: create inputs on initial template load - var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, needsReload)); + var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message)); $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); $categoryInputRow .before($newCategoryRow) .before($rowInput); - if (!needsReload) { + if (canAddRows) { $rowInput.removeAttr("hidden"); } else { addTableRow($rowInput); } } -function deleteTableCategory(categoryHeaderRow) { - var categoryName = categoryHeaderRow.data("category"); +function deleteTableCategory($categoryHeaderRow) { + var categoryName = $categoryHeaderRow.data("category"); - categoryHeaderRow + $categoryHeaderRow .closest("table") .find("tr[data-category='" + categoryName + "']") .each(function () { - deleteTableRow($(this)); + if ($(this).hasClass(Settings.DATA_ROW_CLASS)) { + deleteTableRow($(this)); + } else { + $(this).remove(); + } }); } From 987a8c802f06e0443b1a0e0058e1910d10652803 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Thu, 28 Jul 2016 15:41:47 -0700 Subject: [PATCH 47/56] Add basic input validation --- .../resources/web/settings/js/settings.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 4b258fa9fb..e59405981f 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1412,6 +1412,17 @@ function deleteTableRow($row) { function addTableCategory($categoryInputRow) { var $input = $categoryInputRow.find("input").first(); + var categoryValue = $input.prop("value"); + if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) { + $categoryInputRow.addClass("has-warning"); + + setTimeout(function () { + $categoryInputRow.removeClass("has-warning"); + }, 1000); + + return; + } + var $rowInput = $categoryInputRow.next(".inputs").clone(); if (!$rowInput) { console.error("Error cloning inputs"); @@ -1420,7 +1431,6 @@ function addTableCategory($categoryInputRow) { var canAddRows = $categoryInputRow.data("can-add-rows"); var message = $categoryInputRow.data("message"); var categoryKey = $categoryInputRow.data("key"); - var categoryValue = $input.prop("value"); var width = 0; $categoryInputRow .children("td") @@ -1438,8 +1448,6 @@ function addTableCategory($categoryInputRow) { .attr("data-category", categoryValue) .addClass(Settings.NEW_ROW_CLASS); - // TODO: create inputs on initial template load - var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message)); $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); From bcff561eb89d124e0b5bc9a0a354a325f300c8ce Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Thu, 28 Jul 2016 17:14:48 -0700 Subject: [PATCH 48/56] Add category collapsing and expanding --- domain-server/resources/web/css/style.css | 17 ++++++-- .../resources/web/settings/js/settings.js | 40 ++++++++++++++++--- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 0a3dc3dab5..b66b7df258 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -37,8 +37,9 @@ body { margin-right: auto; } -.table .value-category { +.value-category:not(.inputs) { font-weight: bold; + background: #f5f5f5; } .table .value-category [message]::after { @@ -47,8 +48,18 @@ body { font-weight: normal; } -.table .value-category.inputs { - font-weight: normal; +.table .value-row.contracted, +.table .inputs.contracted { + display: none; +} + +.toggle-category { + cursor: pointer; +} + +.toggle-category-icon { + padding: 4px; + margin-right: 8px; } .glyphicon-remove { diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index e59405981f..06c6520c26 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -14,6 +14,11 @@ var Settings = { DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row', ADD_CATEGORY_BUTTON_CLASS: 'add-category', ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category', + TOGGLE_CATEGORY_COLUMN_CLASS: 'toggle-category', + TOGGLE_CATEGORY_SPAN_CLASS: 'toggle-category-icon', + TOGGLE_CATEGORY_SPAN_CLASSES: 'glyphicon toggle-category-icon', + TOGGLE_CATEGORY_EXPANDED_CLASS: 'glyphicon-triangle-bottom', + TOGGLE_CATEGORY_CONTRACTED_CLASS: 'glyphicon-triangle-right', DEL_CATEGORY_BUTTON_CLASS: 'del-category', DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category', MOVE_UP_BUTTON_CLASS: 'move-up', @@ -184,6 +189,10 @@ $(document).ready(function(){ deleteTableCategory($(this).closest('tr')); }); + $('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){ + toggleTableCategory($(this).closest('tr')); + }); + $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ moveTableRow($(this).closest('tr'), true); }); @@ -937,9 +946,10 @@ $('body').on('click', '.save-button', function(e){ }); function makeTable(setting, keypath, setting_value, isLocked) { - // TODO: enforce category sorting var isArray = !_.has(setting, 'key'); - + var categoryKey = setting.categorize_by_key; + var isCategorized = !!categoryKey && isArray; + if (!isArray && setting.can_order) { setting.can_order = false; } @@ -1013,8 +1023,6 @@ function makeTable(setting, keypath, setting_value, isLocked) { var rowIsObject = setting.columns.length > 1; _.each(setting_value, function(row, rowIndexOrName) { - var categoryKey = setting.categorize_by_key; - var isCategorized = !!categoryKey && isArray; var categoryPair = {}; var categoryValue = ""; if (isCategorized) { @@ -1118,7 +1126,8 @@ function makeTable(setting, keypath, setting_value, isLocked) { function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) { var html = "" + - "" + + "" + + "" + "" + categoryValue + "" + "" + ((canRemove) ? ( @@ -1418,7 +1427,7 @@ function addTableCategory($categoryInputRow) { setTimeout(function () { $categoryInputRow.removeClass("has-warning"); - }, 1000); + }, 400); return; } @@ -1477,6 +1486,25 @@ function deleteTableCategory($categoryHeaderRow) { }); } +function toggleTableCategory($categoryHeaderRow) { + var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first(); + var categoryName = $categoryHeaderRow.data("category"); + var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); + if (wasExpanded) { + $icon + .addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS) + .removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); + } else { + $icon + .addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS) + .removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS); + } + $categoryHeaderRow + .closest("table") + .find("tr[data-category='" + categoryName + "']") + .toggleClass("contracted", wasExpanded); +} + function moveTableRow(row, move_up) { var table = $(row).closest('table') var isArray = table.data('setting-type') === 'array' From 39c8ce2a1af0d933f2978629c8b4057d06ee7424 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 29 Jul 2016 10:19:18 -0700 Subject: [PATCH 49/56] Fix new group message to be more intuitive --- domain-server/resources/describe-settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index f4ceb0f432..7600aefc09 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -459,7 +459,7 @@ "can_add_new_categories": true, "can_add_new_rows": false, "new_category_placeholder": "Add Group", - "new_category_message": "Reload to see ranks", + "new_category_message": "Save and reload to see ranks", "groups": [ { @@ -554,7 +554,7 @@ "can_add_new_categories": true, "can_add_new_rows": false, "new_category_placeholder": "Add Blacklist Group", - "new_category_message": "Reload to see ranks", + "new_category_message": "Save and reload to see ranks", "groups": [ { From 8e1820f911cb42e5f75ae4191d632ddc8f7983c2 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 29 Jul 2016 13:10:14 -0700 Subject: [PATCH 50/56] make sure permissions have the correct ID in the case where a user has been verified --- domain-server/src/DomainGatekeeper.cpp | 1 + libraries/networking/src/NodePermissions.h | 1 + 2 files changed, 2 insertions(+) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 946e855d0d..60ba409d3b 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -137,6 +137,7 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms; #endif } else { + userPerms.setID(verifiedUsername); if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) { userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); userPerms.setVerifiedUserName(verifiedUsername); diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 07b47037b8..6f4309a447 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -34,6 +34,7 @@ public: NodePermissions(QMap perms); const QString& getID() const { return _id; } // a user-name or a group-name, not verified + void setID(const QString& id) { _id = id; } void setRankID(QUuid& rankID) { _rankID = rankID; } const QUuid& getRankID() const { return _rankID; } NodePermissionsKey getKey() const { return NodePermissionsKey(_id, _rankID); } From ebdfede89fe7b3944709f0c4cf0a495cafc9a37a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 29 Jul 2016 16:34:59 -0700 Subject: [PATCH 51/56] switch to new friends api --- domain-server/src/DomainGatekeeper.cpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 60ba409d3b..f3a7febb9d 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -826,20 +826,33 @@ void DomainGatekeeper::getDomainOwnerFriendsList() { callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "getDomainOwnerFriendsListErrorCallback"; - const QString GET_FRIENDS_LIST_PATH = "api/v1/users"; - QUrlQuery query; - query.addQueryItem("filter", "friends"); - + const QString GET_FRIENDS_LIST_PATH = "api/v1/user/friends"; DependencyManager::get()->sendRequest(GET_FRIENDS_LIST_PATH, AccountManagerAuth::Required, QNetworkAccessManager::GetOperation, callbackParams, QByteArray(), - NULL, QVariantMap(), query); + NULL, QVariantMap()); } void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply) { + // { + // status: "success", + // data: { + // friends: [ + // "chris", + // "freidrica", + // "G", + // "huffman", + // "leo", + // "philip", + // "ryan", + // "sam", + // "ZappoMan" + // ] + // } + // } QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); if (jsonObject["status"].toString() == "success") { _domainOwnerFriends.clear(); - QJsonArray friends = jsonObject["data"].toObject()["users"].toArray(); + QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray(); for (int i = 0; i < friends.size(); i++) { QString friendUserName = friends.at(i).toObject()["username"].toString(); _domainOwnerFriends += friendUserName; From 2443166d78dc5706755ebf1a61fb3b7dd12e14bc Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 29 Jul 2016 16:41:17 -0700 Subject: [PATCH 52/56] update for new friends api --- domain-server/src/DomainGatekeeper.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index f3a7febb9d..36f70f5463 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -854,8 +854,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requ _domainOwnerFriends.clear(); QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray(); for (int i = 0; i < friends.size(); i++) { - QString friendUserName = friends.at(i).toObject()["username"].toString(); - _domainOwnerFriends += friendUserName; + _domainOwnerFriends += friends.at(i).toString() } } else { qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); From c72cfb165c7b72add5c3d622099256d8f14e8c90 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 29 Jul 2016 18:33:06 -0700 Subject: [PATCH 53/56] damnit --- domain-server/src/DomainGatekeeper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 36f70f5463..5b2e5a2bb0 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -854,7 +854,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requ _domainOwnerFriends.clear(); QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray(); for (int i = 0; i < friends.size(); i++) { - _domainOwnerFriends += friends.at(i).toString() + _domainOwnerFriends += friends.at(i).toString(); } } else { qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); From 60d66739500b1f057a75d4048d54071dfd158656 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 1 Aug 2016 12:00:08 -0700 Subject: [PATCH 54/56] switch data-web urls to point at production server rather than dev one --- domain-server/resources/web/settings/js/settings.js | 4 +--- domain-server/src/DomainServer.cpp | 4 +--- libraries/networking/src/NetworkingConstants.h | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 06c6520c26..c1005de105 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1,8 +1,6 @@ var Settings = { showAdvanced: false, - // METAVERSE_URL: 'https://metaverse.highfidelity.com', - // METAVERSE_URL: 'http://localhost:3000', - METAVERSE_URL: 'https://devdataweb.highfidelity.com', + METAVERSE_URL: 'https://metaverse.highfidelity.com', ADVANCED_CLASS: 'advanced-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d972b8f641..8af364992c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -45,9 +45,7 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923; -// const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; -// const QString ICE_SERVER_DEFAULT_HOSTNAME = "localhost"; -const QString ICE_SERVER_DEFAULT_HOSTNAME = "devdataweb.highfidelity.com"; +const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 62a8f29088..a512ae8887 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -15,9 +15,7 @@ #include namespace NetworkingConstants { - // const QUrl METAVERSE_SERVER_URL = QUrl("https://metaverse.highfidelity.com"); - // const QUrl METAVERSE_SERVER_URL = QUrl("http://localhost:3000"); - const QUrl METAVERSE_SERVER_URL = QUrl("https://devdataweb.highfidelity.com"); + const QUrl METAVERSE_SERVER_URL = QUrl("https://metaverse.highfidelity.com"); } #endif // hifi_NetworkingConstants_h From 31ac7a2812dc6775d7a4596cfb30283a21af6329 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 2 Aug 2016 12:40:01 +1200 Subject: [PATCH 55/56] Add OverlayWebWindow resized, moved, and closed signals --- interface/resources/qml/QmlWebWindow.qml | 17 +++++++++++++++++ .../qml/windows/DefaultFrameDecoration.qml | 5 ++++- interface/resources/qml/windows/Window.qml | 1 + libraries/ui/src/QmlWindowClass.cpp | 9 +++++++++ libraries/ui/src/QmlWindowClass.h | 1 + 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 542b44b95e..153498e2f7 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -39,6 +39,23 @@ Windows.ScrollingWindow { // missing signal signal sendToScript(var message); + signal moved(vector2d position); + signal resized(size size); + + function notifyMoved() { + moved(Qt.vector2d(x, y)); + } + + function notifyResized() { + resized(Qt.size(width, height)); + } + + onXChanged: notifyMoved(); + onYChanged: notifyMoved(); + + onWidthChanged: notifyResized(); + onHeightChanged: notifyResized(); + Item { width: pane.contentWidth implicitHeight: pane.scrollHeight diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml index ce47b818b1..40e32aaa6b 100644 --- a/interface/resources/qml/windows/DefaultFrameDecoration.qml +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -78,7 +78,10 @@ Decoration { id: closeClickArea anchors.fill: parent hoverEnabled: true - onClicked: window.shown = false; + onClicked: { + window.shown = false; + window.windowClosed(); + } } } } diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index c873872692..40ef74c59b 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -30,6 +30,7 @@ Fadable { // // Signals // + signal windowClosed(); signal windowDestroyed(); signal mouseEntered(); signal mouseExited(); diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 7d56e51495..c3ca5f54d9 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -124,6 +124,10 @@ void QmlWindowClass::initQml(QVariantMap properties) { // Forward messages received from QML on to the script connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection); + + connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection); + connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection); + connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection); }); } Q_ASSERT(_qmlWindow); @@ -259,7 +263,12 @@ void QmlWindowClass::close() { } } +void QmlWindowClass::hasMoved(QVector2D position) { + emit moved(glm::vec2(position.x(), position.y())); +} + void QmlWindowClass::hasClosed() { + emit closed(); } void QmlWindowClass::raise() { diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index c30027df6e..07cf736334 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -62,6 +62,7 @@ signals: void fromQml(const QVariant& message); protected slots: + void hasMoved(QVector2D); void hasClosed(); void qmlToScript(const QVariant& message); From 38ba54e48161ac416b964b9d9c38b52bf459c58e Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Tue, 2 Aug 2016 16:04:54 -0700 Subject: [PATCH 56/56] Fix confirm dialog --- libraries/ui/src/OffscreenUi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 36297d76c7..bc0a6bf544 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -107,7 +107,7 @@ public: QMessageBox::StandardButtons buttons = QMessageBox::Ok, QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); static QMessageBox::StandardButton question(const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); static QMessageBox::StandardButton warning(const QString& title, const QString& text, QMessageBox::StandardButtons buttons = QMessageBox::Ok,