From 7810ed631392d4597af7076a6e93e82e6fbe332c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 31 May 2016 16:56:08 -0700 Subject: [PATCH 01/35] working toward a permissions grid in domain-server settings --- .../resources/describe-settings.json | 71 ++++++++-------- .../resources/web/settings/js/settings.js | 81 ++++++++++++++++--- .../src/DomainServerSettingsManager.cpp | 7 +- 3 files changed, 109 insertions(+), 50 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 53d062d4bd..e0767b0256 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -87,27 +87,6 @@ "help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.", "value-hidden": true }, - { - "name": "restricted_access", - "type": "checkbox", - "label": "Restricted Access", - "default": false, - "help": "Only users listed in \"Allowed Users\" can enter your domain." - }, - { - "name": "allowed_users", - "type": "table", - "label": "Allowed Users", - "help": "You can always connect from the domain-server machine.", - "numbered": false, - "columns": [ - { - "name": "username", - "label": "Username", - "can_set": true - } - ] - }, { "name": "maximum_user_capacity", "label": "Maximum User Capacity", @@ -117,25 +96,47 @@ "advanced": false }, { - "name": "allowed_editors", + "name": "permissions", "type": "table", - "label": "Allowed Editors", - "help": "List the High Fidelity names for people you want to be able lock or unlock entities in this domain.
An empty list means everyone.", - "numbered": false, + "label": "Domain-Wide User Permissions", + "help": "Indicate which users or groups can have which domain-wide permissions.", + "columns": [ { - "name": "username", - "label": "Username", - "can_set": true + "name": "permissions_id", + "label": "User/Group" + }, + { + "name": "id_can_connect", + "label": "Connect", + "type": "checkbox", + "default": true + }, + { + "name": "id_can_adjust_locks", + "label": "Lock/Unlock", + "type": "checkbox", + "default": false + }, + { + "name": "id_can_rez", + "label": "Rez", + "type": "checkbox", + "default": false + }, + { + "name": "id_can_rez_tmp", + "label": "Rez Temp", + "type": "checkbox", + "default": false + }, + { + "name": "id_can_write_to_asset_server", + "label": "Write Assets", + "type": "checkbox", + "default": false } ] - }, - { - "name": "editors_are_rezzers", - "type": "checkbox", - "label": "Only Editors Can Create Entities", - "help": "Only users listed in \"Allowed Editors\" can create new entites.", - "default": false } ] }, diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index e17a886e10..1628844d15 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -875,6 +875,7 @@ function saveSettings() { } } + console.log("----- SAVING ------"); console.log(formJSON); // re-enable all inputs @@ -957,7 +958,11 @@ function makeTable(setting, keypath, setting_value, isLocked) { colValue = rowIsObject ? row[col.name] : row; colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); } else { - colValue = row[col.name]; + if (col.type === "checkbox") { + colValue = (row[col.name] ? "X" : "") + } else { + colValue = row[col.name]; + } colName = keypath + "." + rowIndexOrName + "." + col.name; } @@ -1012,10 +1017,22 @@ function makeTableInputs(setting) { } _.each(setting.columns, function(col) { - html += "\ - \ - " + if (col.type === "checkbox") { + html += "" + // html += "
" + html += "\ + \ + " + } }) if (setting.can_order) { @@ -1072,6 +1089,10 @@ function addTableRow(add_glyphicon) { var table = row.parents('table') var isArray = table.data('setting-type') === 'array' + console.log("------------------------"); + console.log("table = " + table.name + " " + table.id); + console.log("isArray = " + isArray); + var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS) if (!isArray) { @@ -1115,11 +1136,17 @@ function addTableRow(add_glyphicon) { var table = row.parents("table") var setting_name = table.attr("name") var full_name = setting_name + "." + key + + console.log("table = " + table); + console.log("setting_name = " + setting_name); + console.log("full_name = " + full_name); + row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS) row.removeClass("inputs") _.each(row.children(), function(element) { if ($(element).hasClass("numbered")) { + console.log("A"); // Index row var numbers = columns.children(".numbered") if (numbers.length > 0) { @@ -1127,40 +1154,70 @@ function addTableRow(add_glyphicon) { } else { $(element).html(1) } - } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { - $(element).html("") - } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { + } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { + console.log("B"); + $(element).html("") + } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { + console.log("C"); // Change buttons var anchor = $(element).children("a") anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) } else if ($(element).hasClass("key")) { + console.log("D"); var input = $(element).children("input") $(element).html(input.val()) input.remove() } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { + console.log("E"); // Hide inputs - var input = $(element).children("input") + console.log("element = " + element); + var input = $(element).find("input") + + var val = input.val(); + if (input.attr("type") == "checkbox") { + val = input.is(':checked'); + $(element).children().hide(); + } + input.attr("type", "hidden") if (isArray) { var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length var key = $(element).attr('name') + console.log("row_index = " + row_index); + console.log("key = " + key); + // are there multiple columns or just one? // with multiple we have an array of Objects, with one we have an array of whatever the value type is var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length + + console.log("num_columns = " + num_columns); + + console.log("input = " + JSON.stringify(input)); + console.log("new name = " + setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")); + input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + + } else { input.attr("name", full_name + "." + $(element).attr("name")) } input.attr("data-changed", "true") - $(element).append(input.val()) + // if the input is a bootstrapSwitch, we need to move this input up to where it will be found + var inputElement = $(input).detach(); + $(element).append(inputElement); + + console.log("input.val() = " + val); + + $(element).append(val) } else { + console.log("F"); console.log("Unknown table element") } }) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 0ca0cf8232..23bb3e7abf 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -44,7 +44,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() : QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); descriptionFile.open(QIODevice::ReadOnly); - QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll()); + QJsonParseError parseError; + QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll(), &parseError); if (descriptionDocument.isObject()) { QJsonObject descriptionObject = descriptionDocument.object(); @@ -63,8 +64,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() : } static const QString MISSING_SETTINGS_DESC_MSG = - QString("Did not find settings decription in JSON at %1 - Unable to continue. domain-server will quit.") - .arg(SETTINGS_DESCRIPTION_RELATIVE_PATH); + QString("Did not find settings decription in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3") + .arg(SETTINGS_DESCRIPTION_RELATIVE_PATH).arg(parseError.errorString()).arg(parseError.offset); static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6; QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection, From f082201941c14d1370648eb6fa42357fc01455fb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 1 Jun 2016 15:32:27 -0700 Subject: [PATCH 02/35] working toward a grid on domain-server settigns page --- .../resources/describe-settings.json | 2 +- .../resources/web/settings/js/settings.js | 64 +++++++------------ 2 files changed, 23 insertions(+), 43 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index e0767b0256..a856666bc9 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 1.2, + "version": 1.3, "settings": [ { "name": "metaverse", diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 1628844d15..52337db601 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1089,10 +1089,6 @@ function addTableRow(add_glyphicon) { var table = row.parents('table') var isArray = table.data('setting-type') === 'array' - console.log("------------------------"); - console.log("table = " + table.name + " " + table.id); - console.log("isArray = " + isArray); - var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS) if (!isArray) { @@ -1136,17 +1132,11 @@ function addTableRow(add_glyphicon) { var table = row.parents("table") var setting_name = table.attr("name") var full_name = setting_name + "." + key - - console.log("table = " + table); - console.log("setting_name = " + setting_name); - console.log("full_name = " + full_name); - row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS) row.removeClass("inputs") _.each(row.children(), function(element) { if ($(element).hasClass("numbered")) { - console.log("A"); // Index row var numbers = columns.children(".numbered") if (numbers.length > 0) { @@ -1155,69 +1145,59 @@ function addTableRow(add_glyphicon) { $(element).html(1) } } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { - console.log("B"); $(element).html("") } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { - console.log("C"); // Change buttons var anchor = $(element).children("a") anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) } else if ($(element).hasClass("key")) { - console.log("D"); var input = $(element).children("input") $(element).html(input.val()) input.remove() } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { - console.log("E"); // Hide inputs - console.log("element = " + element); var input = $(element).find("input") - - var val = input.val(); - if (input.attr("type") == "checkbox") { - val = input.is(':checked'); - $(element).children().hide(); + var isCheckbox = false; + if (input.hasClass("toggle-checkbox")) { + input = $(input).parent().parent(); + isCheckbox = true; } - input.attr("type", "hidden") + var val = input.val(); + if (isCheckbox) { + val = $(input).find("input").is(':checked'); + // don't hide the checkbox + } else { + input.attr("type", "hidden") + } if (isArray) { var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length var key = $(element).attr('name') - console.log("row_index = " + row_index); - console.log("key = " + key); - // are there multiple columns or just one? // with multiple we have an array of Objects, with one we have an array of whatever the value type is var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length - console.log("num_columns = " + num_columns); - - console.log("input = " + JSON.stringify(input)); - console.log("new name = " + setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")); - - input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) - - + if (isCheckbox) { + $(input).find("input").attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + } else { + input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + } } else { input.attr("name", full_name + "." + $(element).attr("name")) } - input.attr("data-changed", "true") - - // if the input is a bootstrapSwitch, we need to move this input up to where it will be found - var inputElement = $(input).detach(); - $(element).append(inputElement); - - console.log("input.val() = " + val); - - $(element).append(val) + if (isCheckbox) { + $(input).find("input").attr("data-changed", "true"); + } else { + input.attr("data-changed", "true"); + $(element).append(val); + } } else { - console.log("F"); console.log("Unknown table element") } }) From ae0c8a96f2bd5883865e1b6f606ee266e38d46cf Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 1 Jun 2016 15:33:24 -0700 Subject: [PATCH 03/35] code to unpack permissions into a usable data structure --- domain-server/src/AgentPermissions.h | 40 +++++++++++++++++++ .../src/DomainServerSettingsManager.cpp | 40 +++++++++++++++++++ .../src/DomainServerSettingsManager.h | 7 ++++ 3 files changed, 87 insertions(+) create mode 100644 domain-server/src/AgentPermissions.h diff --git a/domain-server/src/AgentPermissions.h b/domain-server/src/AgentPermissions.h new file mode 100644 index 0000000000..50c90581c1 --- /dev/null +++ b/domain-server/src/AgentPermissions.h @@ -0,0 +1,40 @@ +// +// AgentPermissions.h +// domain-server/src +// +// Created by Seth Alves on 2016-6-1. +// 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_AgentPermissions_h +#define hifi_AgentPermissions_h + +class AgentPermissions { +public: + AgentPermissions(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(); + }; + + QString getID() { return _id; } + + bool canConnectToDomain { false }; + bool canAdjustLocks { false }; + bool canRezPermanentEntities { false }; + bool canRezTemporaryEntities { false }; + bool canWriteToAssetServer { false }; + +protected: + QString _id; +}; + +using AgentPermissionsPointer = std::shared_ptr; + +#endif // hifi_AgentPermissions_h diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 23bb3e7abf..0d6214d702 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -26,6 +26,9 @@ #include "DomainServerSettingsManager.h" +#define WANT_DEBUG 1 + + const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; const QString DESCRIPTION_SETTINGS_KEY = "settings"; @@ -190,12 +193,49 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList _configMap.loadMasterAndUserConfig(argumentList); } } + + if (oldVersion < 1.3) { + // This was prior to the permissions-grid in the domain-server settings page + + } } + unpackPermissions(); + // write the current description version to our settings appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion); } +void DomainServerSettingsManager::unpackPermissions() { + QList permsHashList = valueOrDefaultValueForKeyPath(AGENT_PERMISSIONS_KEYPATH).toList(); + foreach (QVariant permsHash, permsHashList) { + AgentPermissionsPointer perms { new AgentPermissions(permsHash.toMap()) }; + _agentPermissions[perms->getID()] = perms; + } + + #ifdef WANT_DEBUG + qDebug() << "--------------- permissions ---------------------"; + QHashIterator i(_agentPermissions); + while (i.hasNext()) { + i.next(); + AgentPermissionsPointer perms = i.value(); + qDebug() << i.key() + << perms->canConnectToDomain + << perms->canAdjustLocks + << perms->canRezPermanentEntities + << perms->canRezTemporaryEntities + << perms->canWriteToAssetServer; + } + #endif +} + +AgentPermissionsPointer DomainServerSettingsManager::getPermissionsForName(QString name) const { + if (_agentPermissions.contains(name)) { + return _agentPermissions[name]; + } + return nullptr; +} + QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index d6dd5070a9..4d305b2b5b 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -19,6 +19,7 @@ #include #include +#include "AgentPermissions.h" const QString SETTINGS_PATHS_KEY = "paths"; @@ -27,6 +28,7 @@ const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; +const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; class DomainServerSettingsManager : public QObject { Q_OBJECT @@ -41,6 +43,8 @@ public: QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } + AgentPermissionsPointer getPermissionsForName(QString name) const; + private slots: void processSettingsRequestPacket(QSharedPointer message); @@ -58,6 +62,9 @@ private: HifiConfigVariantMap _configMap; friend class DomainServer; + + void unpackPermissions(); + QHash _agentPermissions; }; #endif // hifi_DomainServerSettingsManager_h From ce7fa5146529d1b243c6bee06464ecd5f935381e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 2 Jun 2016 19:59:41 +1200 Subject: [PATCH 04/35] Make checkboxes in user permissions table editable --- .../resources/web/settings/js/settings.js | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 52337db601..f5b7a61d2a 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -232,6 +232,16 @@ $(document).ready(function(){ badgeSidebarForDifferences($(this)); }); + + // Bootstrap switch in table + $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function () { + // Bootstrap switches in table: set the changed data attribute for all rows. + var row = $(this).closest('tr'); + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + }); + + $('.advanced-toggle').click(function(){ Settings.showAdvanced = !Settings.showAdvanced var advancedSelector = $('.' + Settings.ADVANCED_CLASS) @@ -966,16 +976,30 @@ function makeTable(setting, keypath, setting_value, isLocked) { colName = keypath + "." + rowIndexOrName + "." + col.name; } - // setup the td for this column - html += ""; + if (isArray && col.type === "checkbox") { - // add the actual value to the td so it is displayed - html += colValue; + html += "" + html += ""; + } else { + + // setup the td for this column + html += ""; + + // add the actual value to the td so it is displayed + html += colValue; + + // for values to be posted properly we add a hidden input to this td + html += ""; + + html += ""; + } - html += ""; }) if (!isLocked && !setting.read_only) { From e24ba4caf67e370c95fb33001110f14150cc7c9b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 2 Jun 2016 19:59:53 +1200 Subject: [PATCH 05/35] Code tidying --- domain-server/resources/web/settings/js/settings.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index f5b7a61d2a..35e9d64d84 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -968,11 +968,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { colValue = rowIsObject ? row[col.name] : row; colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); } else { - if (col.type === "checkbox") { - colValue = (row[col.name] ? "X" : "") - } else { - colValue = row[col.name]; - } + colValue = row[col.name]; colName = keypath + "." + rowIndexOrName + "." + col.name; } @@ -1043,13 +1039,11 @@ function makeTableInputs(setting) { _.each(setting.columns, function(col) { if (col.type === "checkbox") { html += "" - // html += "
" html += "\ From 83f2c723eb3749a7c939429fd9724142fcd0c993 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 2 Jun 2016 13:15:30 -0700 Subject: [PATCH 06/35] collect permissions into their own data structure --- .../src/AssignmentClientMonitor.cpp | 4 +- .../resources/describe-settings.json | 6 + domain-server/src/AgentPermissions.h | 40 -- domain-server/src/DomainGatekeeper.cpp | 361 +++++++----------- domain-server/src/DomainGatekeeper.h | 6 +- domain-server/src/DomainServer.cpp | 3 +- .../src/DomainServerSettingsManager.cpp | 53 ++- .../src/DomainServerSettingsManager.h | 6 +- libraries/networking/src/AgentPermissions.cpp | 43 +++ libraries/networking/src/AgentPermissions.h | 79 ++++ libraries/networking/src/LimitedNodeList.cpp | 37 +- libraries/networking/src/LimitedNodeList.h | 13 +- libraries/networking/src/Node.cpp | 15 +- libraries/networking/src/Node.h | 15 +- libraries/networking/src/NodeList.cpp | 19 +- 15 files changed, 361 insertions(+), 339 deletions(-) delete mode 100644 domain-server/src/AgentPermissions.h create mode 100644 libraries/networking/src/AgentPermissions.cpp create mode 100644 libraries/networking/src/AgentPermissions.h diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 322fe6e57e..8ba253d549 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -286,8 +286,8 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer()->addOrUpdateNode - (senderID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false, false); + matchingNode = DependencyManager::get()->addOrUpdateNode(senderID, NodeType::Unassigned, + senderSockAddr, senderSockAddr); auto childData = std::unique_ptr { new AssignmentClientChildData(Assignment::Type::AllTypes) }; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index a856666bc9..b82a3ae1b1 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -135,6 +135,12 @@ "label": "Write Assets", "type": "checkbox", "default": false + }, + { + "name": "id_can_connect_past_max_capacity", + "label": "Ignore Max Capacity", + "type": "checkbox", + "default": false } ] } diff --git a/domain-server/src/AgentPermissions.h b/domain-server/src/AgentPermissions.h deleted file mode 100644 index 50c90581c1..0000000000 --- a/domain-server/src/AgentPermissions.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// AgentPermissions.h -// domain-server/src -// -// Created by Seth Alves on 2016-6-1. -// 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_AgentPermissions_h -#define hifi_AgentPermissions_h - -class AgentPermissions { -public: - AgentPermissions(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(); - }; - - QString getID() { return _id; } - - bool canConnectToDomain { false }; - bool canAdjustLocks { false }; - bool canRezPermanentEntities { false }; - bool canRezTemporaryEntities { false }; - bool canWriteToAssetServer { false }; - -protected: - QString _id; -}; - -using AgentPermissionsPointer = std::shared_ptr; - -#endif // hifi_AgentPermissions_h diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index b940d46849..c80969c8b4 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -26,7 +26,7 @@ using SharedAssignmentPointer = QSharedPointer; DomainGatekeeper::DomainGatekeeper(DomainServer* server) : _server(server) { - + } void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID, @@ -38,7 +38,7 @@ void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID) { auto it = _pendingAssignedNodes.find(tempUUID); - + if (it != _pendingAssignedNodes.end()) { return it->second.getAssignmentUUID(); } else { @@ -64,57 +64,57 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSenderSockAddr(), + sendConnectionDeniedPacket(protocolVersionError, message->getSenderSockAddr(), DomainHandler::ConnectionRefusedReason::ProtocolMismatch); return; } - + if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) { qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection."; return; } - + static const NodeSet VALID_NODE_TYPES { NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer }; - + if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) { qDebug() << "Received an invalid node type with connect request. Will not allow connection from" << nodeConnection.senderSockAddr << ": " << nodeConnection.nodeType; return; } - + // check if this connect request matches an assignment in the queue auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID); - + SharedNodePointer node; - + if (pendingAssignment != _pendingAssignedNodes.end()) { node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second); } else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) { QString username; QByteArray usernameSignature; - + if (message->getBytesLeftToRead() > 0) { // read username from packet packetStream >> username; - + if (message->getBytesLeftToRead() > 0) { // read user signature from packet packetStream >> usernameSignature; } } - + node = processAgentConnectRequest(nodeConnection, username, usernameSignature); } - + if (node) { // set the sending sock addr and node interest set on this node DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); nodeData->setSendingSockAddr(message->getSenderSockAddr()); nodeData->setNodeInterestSet(nodeConnection.interestList.toSet()); nodeData->setPlaceName(nodeConnection.placeName); - + // signal that we just connected a node so the DomainServer can get it a list // and broadcast its presence right away emit connectedNode(node); @@ -125,16 +125,16 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointerdequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType); - + if (matchingQueuedAssignment) { qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID) << "matches unfulfilled assignment" @@ -149,124 +149,85 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo qDebug() << "No assignment was deployed with UUID" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID); return SharedNodePointer(); } - + // add the new node SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); - + DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); - + // set assignment related data on the linked data for this node nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); nodeData->setWalletUUID(it->second.getWalletUUID()); nodeData->setNodeVersion(it->second.getNodeVersion()); nodeData->setWasAssigned(true); - + // cleanup the PendingAssignedNodeData for this assignment now that it's connecting _pendingAssignedNodes.erase(it); - + // always allow assignment clients to create and destroy entities - newNode->setIsAllowedEditor(true); - newNode->setCanRez(true); - + AgentPermissions userPerms; + userPerms.canAdjustLocks = true; + userPerms.canRezPermanentEntities = true; + newNode->setPermissions(userPerms); return newNode; } const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; -const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; -const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; +// const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; +// const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection, const QString& username, const QByteArray& usernameSignature) { - + auto limitedNodeList = DependencyManager::get(); - - bool isRestrictingAccess = - _server->_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); - + + // start with empty permissions + AgentPermissions userPerms(username); + userPerms.setAll(false); + // check if this user is on our local machine - if this is true they are always allowed to connect QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress(); bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); - - // if we're using restricted access and this user is not local make sure we got a user signature - if (isRestrictingAccess && !isLocalUser) { - if (!username.isEmpty()) { - if (usernameSignature.isEmpty()) { - // if user didn't include usernameSignature in connect request, send a connectionToken packet - sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); - - // ask for their public key right now to make sure we have it - requestUserPublicKey(username); - - return SharedNodePointer(); - } - } + if (isLocalUser) { + userPerms |= _server->_settingsManager.getPermissionsForName("localhost"); } - - bool verifiedUsername = false; - - // if we do not have a local user we need to subject them to our verification and capacity checks - if (!isLocalUser) { - - // check if we need to look at the username signature - if (isRestrictingAccess) { - if (isVerifiedAllowedUser(username, usernameSignature, nodeConnection.senderSockAddr)) { - // we verified the user via their username and signature - set the verifiedUsername - // so we don't re-decrypt their sig if we're trying to exempt them from max capacity check (due to - // being in the allowed editors list) - verifiedUsername = true; - } else { - // failed to verify user - return a null shared ptr - return SharedNodePointer(); - } - } - - if (!isWithinMaxCapacity(username, usernameSignature, verifiedUsername, nodeConnection.senderSockAddr)) { - // we can't allow this user to connect because we are at max capacity (and they either aren't an allowed editor - // or couldn't be verified as one) + + 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 (!isLocalUser) { return SharedNodePointer(); } } - - // if this user is in the editors list (or if the editors list is empty) set the user's node's isAllowedEditor to true - const QVariant* allowedEditorsVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); - QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); - - // if the allowed editors list is empty then everyone can adjust locks - bool isAllowedEditor = allowedEditors.empty(); - - if (allowedEditors.contains(username, Qt::CaseInsensitive)) { - // we have a non-empty allowed editors list - check if this user is verified to be in it - if (!verifiedUsername) { - if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) { - // failed to verify a user that is in the allowed editors list - - // TODO: fix public key refresh in interface/metaverse and force this check - qDebug() << "Could not verify user" << username << "as allowed editor. In the interim this user" - << "will be given edit rights to avoid a thrasing of public key requests and connect requests."; - } - - isAllowedEditor = true; - } else { - // already verified this user and they are in the allowed editors list - isAllowedEditor = true; + + if (username.isEmpty()) { + // they didn't tell us who they are + userPerms |= _server->_settingsManager.getPermissionsForName("anonymous"); + } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { + // they are sent us a username and the signature verifies it + userPerms |= _server->_settingsManager.getPermissionsForName(username); + userPerms |= _server->_settingsManager.getPermissionsForName("logged-in"); + } else { + // they sent us a username, but it didn't check out + requestUserPublicKey(username); + if (!isLocalUser) { + return SharedNodePointer(); } } - - // check if only editors should be able to rez entities - const QVariant* editorsAreRezzersVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH); - - bool onlyEditorsAreRezzers = false; - if (editorsAreRezzersVariant) { - onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool(); + + if (!userPerms.canConnectToDomain) { + return SharedNodePointer(); } - - bool canRez = true; - if (onlyEditorsAreRezzers) { - canRez = isAllowedEditor; + + if (!userPerms.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); + return SharedNodePointer(); } QUuid hintNodeID; @@ -285,24 +246,23 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect return true; }); - + // add the connecting node (or re-use the matched one from eachNodeBreakable above) SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID); - + // set the edit rights for this user - newNode->setIsAllowedEditor(isAllowedEditor); - newNode->setCanRez(canRez); + newNode->setPermissions(userPerms); // grab the linked data for our new node so we can set the username DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); - + // if we have a username from the connect request, set it on the DomainServerNodeData nodeData->setUsername(username); - + // also add an interpolation to DomainServerNodeData so that servers can get username in stats nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, uuidStringWithoutCurlyBraces(newNode->getUUID()), username); - + return newNode; } @@ -310,11 +270,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node QUuid nodeID) { HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr; SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID); - + if (connectedPeer) { // this user negotiated a connection with us via ICE, so re-use their ICE client ID nodeID = nodeConnection.connectUUID; - + if (connectedPeer->getActiveSocket()) { // set their discovered socket to whatever the activated socket on the network peer object was discoveredSocket = *connectedPeer->getActiveSocket(); @@ -325,15 +285,15 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node nodeID = QUuid::createUuid(); } } - + auto limitedNodeList = DependencyManager::get(); - + SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType, nodeConnection.publicSockAddr, nodeConnection.localSockAddr); - + // So that we can send messages to this node at will - we need to activate the correct socket on this node now newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket); - + return newNode; } @@ -343,21 +303,21 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); - + const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); - + if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) { // if we do have a public key for the user, check for a signature match - + const unsigned char* publicKeyData = reinterpret_cast(publicKeyArray.constData()); - + // first load up the public key into an RSA struct RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size()); - + QByteArray lowercaseUsername = username.toLower().toUtf8(); QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), QCryptographicHash::Sha256); - + if (rsaPublicKey) { int decryptResult = RSA_verify(NID_sha256, reinterpret_cast(usernameWithToken.constData()), @@ -365,29 +325,29 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, reinterpret_cast(usernameSignature.constData()), usernameSignature.size(), rsaPublicKey); - + if (decryptResult == 1) { qDebug() << "Username signature matches for" << username << "- allowing connection."; - + // free up the public key and remove connection token before we return RSA_free(rsaPublicKey); _connectionTokenHash.remove(username); - + return true; - + } else { if (!senderSockAddr.isNull()) { qDebug() << "Error decrypting username signature for " << username << "- denying connection."; sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr, DomainHandler::ConnectionRefusedReason::LoginError); } - + // free up the public key, we don't need it anymore RSA_free(rsaPublicKey); } - + } else { - + // we can't let this user in since we couldn't convert their public key to an RSA key we could use if (!senderSockAddr.isNull()) { qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; @@ -402,86 +362,35 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, DomainHandler::ConnectionRefusedReason::LoginError); } } - + requestUserPublicKey(username); // no joy. maybe next time? return false; } -bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, - const HifiSockAddr& senderSockAddr) { - - if (username.isEmpty()) { - qDebug() << "Connect request denied - no username provided."; - - sendConnectionDeniedPacket("No username provided", senderSockAddr, - DomainHandler::ConnectionRefusedReason::LoginError); - - return false; - } - - QStringList allowedUsers = - _server->_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); - - if (allowedUsers.contains(username, Qt::CaseInsensitive)) { - if (!verifyUserSignature(username, usernameSignature, senderSockAddr)) { - return false; - } - } else { - qDebug() << "Connect request denied for user" << username << "- not in allowed users list."; - sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr, - DomainHandler::ConnectionRefusedReason::NotAuthorized); - - return false; - } - - return true; -} - -bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature, - bool& verifiedUsername, - const HifiSockAddr& senderSockAddr) { +bool DomainGatekeeper::isWithinMaxCapacity() { // find out what our maximum capacity is - const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); + const QVariant* maximumUserCapacityVariant = + valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; if (maximumUserCapacity > 0) { unsigned int connectedUsers = _server->countConnectedUsers(); if (connectedUsers >= maximumUserCapacity) { - // too many users, deny the new connection unless this user is an allowed editor - - const QVariant* allowedEditorsVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); - - QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); - if (allowedEditors.contains(username)) { - if (verifiedUsername || verifyUserSignature(username, usernameSignature, senderSockAddr)) { - verifiedUsername = true; - qDebug() << "Above maximum capacity -" << connectedUsers << "/" << maximumUserCapacity << - "but user" << username << "is in allowed editors list so will be allowed to connect."; - return true; - } - } - - // deny connection from this user qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection."; - sendConnectionDeniedPacket("Too many connected users.", senderSockAddr, - DomainHandler::ConnectionRefusedReason::TooManyUsers); - return false; } - + qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, allowing new connection."; } - + return true; } void DomainGatekeeper::preloadAllowedUserPublicKeys() { - const QVariant* allowedUsersVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH); - QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList(); - + QStringList allowedUsers = _server->_settingsManager.getAllNames(); + if (allowedUsers.size() > 0) { // in the future we may need to limit how many requests here - for now assume that lists of allowed users are not // going to create > 100 requests @@ -496,11 +405,11 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) { JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; - + const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key"; - + qDebug() << "Requesting public key for user" << username; - + DependencyManager::get()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, callbackParams); @@ -508,38 +417,38 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) { void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - + if (jsonObject["status"].toString() == "success") { // 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); - + 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()); } } } -void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, +void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, DomainHandler::ConnectionRefusedReason reasonCode) { // this is an agent and we've decided we won't let them connect - send them a packet to deny connection QByteArray utfString = reason.toUtf8(); quint16 payloadSize = utfString.size(); - + // setup the DomainConnectionDenied packet - auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, + auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize) + sizeof(uint8_t)); - + // pack in the reason the connection was denied (the client displays this) if (payloadSize > 0) { uint8_t reasonCodeWire = (uint8_t)reasonCode; @@ -547,7 +456,7 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H connectionDeniedPacket->writePrimitive(payloadSize); connectionDeniedPacket->write(utfString); } - + // send the packet off DependencyManager::get()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr); } @@ -555,20 +464,20 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr) { // get the existing connection token or create a new one QUuid& connectionToken = _connectionTokenHash[username.toLower()]; - + if (connectionToken.isNull()) { connectionToken = QUuid::createUuid(); } - + // setup a static connection token packet static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID); - + // reset the packet before each time we send connectionTokenPacket->reset(); - + // write the connection token connectionTokenPacket->write(connectionToken.toRfc4122()); - + // send off the packet unreliably DependencyManager::get()->sendUnreliablePacket(*connectionTokenPacket, senderSockAddr); } @@ -576,33 +485,33 @@ void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS; void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { - + if (peer->getConnectionAttempts() >= NUM_PEER_PINGS_BEFORE_DELETE) { // we've reached the maximum number of ping attempts qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID(); qDebug() << "Removing from list of connecting peers."; - + _icePeers.remove(peer->getUUID()); } else { auto limitedNodeList = DependencyManager::get(); - + // send the ping packet to the local and public sockets for this node auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID()); limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket()); - + auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID()); limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket()); - + peer->incrementConnectionAttempts(); } } void DomainGatekeeper::handlePeerPingTimeout() { NetworkPeer* senderPeer = qobject_cast(sender()); - + if (senderPeer) { SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID()); - + if (sharedPeer && !sharedPeer->getActiveSocket()) { pingPunchForConnectingPeer(sharedPeer); } @@ -613,24 +522,24 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointergetMessage()); - + NetworkPeer* receivedPeer = new NetworkPeer; iceResponseStream >> *receivedPeer; - + if (!_icePeers.contains(receivedPeer->getUUID())) { qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer; SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer); _icePeers[receivedPeer->getUUID()] = newPeer; - + // make sure we know when we should ping this peer connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout); - + // immediately ping the new peer, and start a timer to continue pinging it until we connect to it newPeer->startPingTimer(); - + qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" << newPeer->getUUID(); - + pingPunchForConnectingPeer(newPeer); } else { delete receivedPeer; @@ -640,18 +549,18 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer message) { auto limitedNodeList = DependencyManager::get(); auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID()); - + limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr()); } void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer message) { QDataStream packetStream(message->getMessage()); - + QUuid nodeUUID; packetStream >> nodeUUID; - + SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID); - + if (sendingPeer) { // we had this NetworkPeer in our connecting list - add the right sock addr to our connected list sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr()); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 09e3b04ed7..352f84385e 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -66,11 +66,7 @@ private: bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); - bool isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, - const HifiSockAddr& senderSockAddr); - bool isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature, - bool& verifiedUsername, - const HifiSockAddr& senderSockAddr); + bool isWithinMaxCapacity(); bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f6fbb3f470..ea3d953c63 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1084,8 +1084,7 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { // add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings const QString RESTRICTED_ACCESS_FLAG = "restricted"; - domainObject[RESTRICTED_ACCESS_FLAG] = - _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); + domainObject[RESTRICTED_ACCESS_FLAG] = _settingsManager.getAllNames().length() > 0; // figure out the breakdown of currently connected interface clients int numConnectedUnassigned = 0; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 0d6214d702..df299734cf 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -110,6 +110,8 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // This was prior to the introduction of security.restricted_access // If the user has a list of allowed users then set their value for security.restricted_access to true + const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; + const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; QVariant* allowedUsers = valueForKeyPath(_configMap.getMergedConfig(), ALLOWED_USERS_SETTINGS_KEYPATH); if (allowedUsers @@ -207,10 +209,47 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList } void DomainServerSettingsManager::unpackPermissions() { - QList permsHashList = valueOrDefaultValueForKeyPath(AGENT_PERMISSIONS_KEYPATH).toList(); - foreach (QVariant permsHash, permsHashList) { + bool foundLocalhost = false; + bool foundAnonymous = false; + bool foundLoggedIn = false; + + // XXX check for duplicate IDs + + QVariant* permissions = valueForKeyPath(_configMap.getMergedConfig(), AGENT_PERMISSIONS_KEYPATH); + if (!permissions->canConvert(QMetaType::QVariantList)) { + qDebug() << "failed to extract permissions from settings."; + return; + } + + // QList permissionsList = permissions->toList(); + + QVariantList* permissionsList = reinterpret_cast(permissions); + + foreach (QVariant permsHash, *permissionsList) { AgentPermissionsPointer perms { new AgentPermissions(permsHash.toMap()) }; - _agentPermissions[perms->getID()] = perms; + QString id = perms->getID(); + foundLoggedIn |= (id == "localhost"); + foundAnonymous |= (id == "anonymous"); + foundLoggedIn |= (id == "logged-in"); + _agentPermissions[id] = perms; + } + + // if any of the standard names are missing, add them + if (!foundLocalhost) { + AgentPermissionsPointer perms { new AgentPermissions("localhost") }; + perms->setAll(true); + _agentPermissions["localhost"] = perms; + *permissionsList += perms->toVariant(); + } + if (!foundAnonymous) { + AgentPermissionsPointer perms { new AgentPermissions("anonymous") }; + _agentPermissions["anonymous"] = perms; + *permissionsList += perms->toVariant(); + } + if (!foundLoggedIn) { + AgentPermissionsPointer perms { new AgentPermissions("logged-in") }; + _agentPermissions["logged-in"] = perms; + *permissionsList += perms->toVariant(); } #ifdef WANT_DEBUG @@ -229,11 +268,13 @@ void DomainServerSettingsManager::unpackPermissions() { #endif } -AgentPermissionsPointer DomainServerSettingsManager::getPermissionsForName(QString name) const { +AgentPermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const { if (_agentPermissions.contains(name)) { - return _agentPermissions[name]; + return *(_agentPermissions[name].get()); } - return nullptr; + AgentPermissions nullPermissions; + nullPermissions.setAll(false); + return nullPermissions; } QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 4d305b2b5b..3012e06c4d 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -25,9 +25,6 @@ const QString SETTINGS_PATHS_KEY = "paths"; const QString SETTINGS_PATH = "/settings"; const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; - -const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; -const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; class DomainServerSettingsManager : public QObject { @@ -43,7 +40,8 @@ public: QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } - AgentPermissionsPointer getPermissionsForName(QString name) const; + AgentPermissions getPermissionsForName(const QString& name) const; + QStringList getAllNames() { return _agentPermissions.keys(); } private slots: void processSettingsRequestPacket(QSharedPointer message); diff --git a/libraries/networking/src/AgentPermissions.cpp b/libraries/networking/src/AgentPermissions.cpp new file mode 100644 index 0000000000..0d3483975b --- /dev/null +++ b/libraries/networking/src/AgentPermissions.cpp @@ -0,0 +1,43 @@ +// +// AgentPermissions.cpp +// libraries/networking/src/ +// +// Created by Seth Alves on 2016-6-1. +// 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 +// + +#include +#include "AgentPermissions.h" + +AgentPermissions& AgentPermissions::operator|=(const AgentPermissions& 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; +} + +QDataStream& operator<<(QDataStream& out, const AgentPermissions& perms) { + out << perms.canConnectToDomain; + out << perms.canAdjustLocks; + out << perms.canRezPermanentEntities; + out << perms.canRezTemporaryEntities; + out << perms.canWriteToAssetServer; + out << perms.canConnectPastMaxCapacity; + return out; +} + +QDataStream& operator>>(QDataStream& in, AgentPermissions& perms) { + in >> perms.canConnectToDomain; + in >> perms.canAdjustLocks; + in >> perms.canRezPermanentEntities; + in >> perms.canRezTemporaryEntities; + in >> perms.canWriteToAssetServer; + in >> perms.canConnectPastMaxCapacity; + return in; +} diff --git a/libraries/networking/src/AgentPermissions.h b/libraries/networking/src/AgentPermissions.h new file mode 100644 index 0000000000..d6598b8cbf --- /dev/null +++ b/libraries/networking/src/AgentPermissions.h @@ -0,0 +1,79 @@ +// +// AgentPermissions.h +// libraries/networking/src/ +// +// Created by Seth Alves on 2016-6-1. +// 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_AgentPermissions_h +#define hifi_AgentPermissions_h + +#include +#include +#include +#include +#include + +class AgentPermissions; +using AgentPermissionsPointer = std::shared_ptr; + +class AgentPermissions { +public: + AgentPermissions() { _id = QUuid::createUuid().toString(); } + AgentPermissions(const QString& name) { _id = name; } + AgentPermissions(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(); + } + + QString getID() { return _id; } + + // 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 }; + + void setAll(bool value) { + canConnectToDomain = value; + canAdjustLocks = value; + canRezPermanentEntities = value; + canRezTemporaryEntities = value; + canWriteToAssetServer = value; + canConnectPastMaxCapacity = value; + } + + 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); + } + + AgentPermissions& operator|=(const AgentPermissions& rhs); + friend QDataStream& operator<<(QDataStream& out, const AgentPermissions& perms); + friend QDataStream& operator>>(QDataStream& in, AgentPermissions& perms); + +protected: + QString _id; +}; + +const AgentPermissions DEFAULT_AGENT_PERMISSIONS; + +#endif // hifi_AgentPermissions_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 9efe51183e..274d022ab5 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -52,7 +52,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _numCollectedPackets(0), _numCollectedBytes(0), _packetStatTimer(), - _thisNodeCanRez(true) + _permissions(AgentPermissions()) { static bool firstCall = true; if (firstCall) { @@ -130,17 +130,25 @@ void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { } } -void LimitedNodeList::setIsAllowedEditor(bool isAllowedEditor) { - if (_isAllowedEditor != isAllowedEditor) { - _isAllowedEditor = isAllowedEditor; - emit isAllowedEditorChanged(isAllowedEditor); - } -} -void LimitedNodeList::setThisNodeCanRez(bool canRez) { - if (_thisNodeCanRez != canRez) { - _thisNodeCanRez = canRez; - emit canRezChanged(canRez); +void LimitedNodeList::setPermissions(const AgentPermissions& newPermissions) { + bool emitIsAllowedEditorChanged { false }; + bool emitCanRezChanged { false }; + + if (_permissions.canAdjustLocks != newPermissions.canAdjustLocks) { + emitIsAllowedEditorChanged = true; + } + if (_permissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) { + emitCanRezChanged = true; + } + + _permissions = newPermissions; + + if (emitIsAllowedEditorChanged) { + emit isAllowedEditorChanged(_permissions.canAdjustLocks); + } + if (emitCanRezChanged) { + emit canRezChanged(_permissions.canRezPermanentEntities); } } @@ -515,7 +523,7 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool isAllowedEditor, bool canRez, + const AgentPermissions& permissions, const QUuid& connectionSecret) { NodeHash::const_iterator it = _nodeHash.find(uuid); @@ -524,14 +532,13 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t matchingNode->setPublicSocket(publicSocket); matchingNode->setLocalSocket(localSocket); - matchingNode->setIsAllowedEditor(isAllowedEditor); - matchingNode->setCanRez(canRez); + matchingNode->setPermissions(permissions); matchingNode->setConnectionSecret(connectionSecret); return matchingNode; } else { // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, isAllowedEditor, canRez, connectionSecret, this); + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, permissions, connectionSecret, this); if (nodeType == NodeType::AudioMixer) { LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 5a3c10e8c3..c3378a4cf8 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -104,12 +104,10 @@ public: const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); - bool isAllowedEditor() const { return _isAllowedEditor; } - void setIsAllowedEditor(bool isAllowedEditor); + void setPermissions(const AgentPermissions& newPermissions); + bool isAllowedEditor() const { return _permissions.canAdjustLocks; } + bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; } - bool getThisNodeCanRez() const { return _thisNodeCanRez; } - void setThisNodeCanRez(bool canRez); - quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } QUdpSocket& getDTLSSocket(); @@ -137,7 +135,7 @@ public: SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool isAllowedEditor = false, bool canRez = false, + const AgentPermissions& permissions = DEFAULT_AGENT_PERMISSIONS, const QUuid& connectionSecret = QUuid()); bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } @@ -300,8 +298,7 @@ protected: int _numCollectedBytes; QElapsedTimer _packetStatTimer; - bool _isAllowedEditor { false }; - bool _thisNodeCanRez; + AgentPermissions _permissions; QPointer _initialSTUNTimer; diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 1e1cec2413..94f32a4e01 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -16,6 +16,7 @@ #include "Node.h" #include "SharedUtil.h" +#include "AgentPermissions.h" #include #include @@ -47,7 +48,7 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { } Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, - const HifiSockAddr& localSocket, bool isAllowedEditor, bool canRez, const QUuid& connectionSecret, + const HifiSockAddr& localSocket, const AgentPermissions& permissions, const QUuid& connectionSecret, QObject* parent) : NetworkPeer(uuid, publicSocket, localSocket, parent), _type(type), @@ -57,8 +58,7 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, _clockSkewUsec(0), _mutex(), _clockSkewMovingPercentile(30, 0.8f), // moving 80th percentile of 30 samples - _isAllowedEditor(isAllowedEditor), - _canRez(canRez) + _permissions(permissions) { // Update socket's object name setType(_type); @@ -78,15 +78,12 @@ void Node::updateClockSkewUsec(qint64 clockSkewSample) { _clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile(); } - QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._type; out << node._uuid; out << node._publicSocket; out << node._localSocket; - out << node._isAllowedEditor; - out << node._canRez; - + out << node._permissions; return out; } @@ -95,9 +92,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) { in >> node._uuid; in >> node._publicSocket; in >> node._localSocket; - in >> node._isAllowedEditor; - in >> node._canRez; - + in >> node._permissions; return in; } diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 3927672319..abc3cfa181 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -27,13 +27,14 @@ #include "NodeType.h" #include "SimpleMovingAverage.h" #include "MovingPercentile.h" +#include "AgentPermissions.h" class Node : public NetworkPeer { Q_OBJECT public: Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool isAllowedEditor, bool canRez, const QUuid& connectionSecret = QUuid(), + const AgentPermissions& permissions, const QUuid& connectionSecret = QUuid(), QObject* parent = 0); bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; } @@ -58,11 +59,10 @@ public: void updateClockSkewUsec(qint64 clockSkewSample); QMutex& getMutex() { return _mutex; } - void setIsAllowedEditor(bool isAllowedEditor) { _isAllowedEditor = isAllowedEditor; } - bool isAllowedEditor() { return _isAllowedEditor; } - - void setCanRez(bool canRez) { _canRez = canRez; } - bool getCanRez() { return _canRez; } + void setPermissions(const AgentPermissions& newPermissions) { _permissions = newPermissions; } + AgentPermissions getPermissions() const { return _permissions; } + bool isAllowedEditor() const { return _permissions.canAdjustLocks; } + bool getCanRez() const { return _permissions.canRezPermanentEntities; } friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); @@ -81,8 +81,7 @@ private: qint64 _clockSkewUsec; QMutex _mutex; MovingPercentile _clockSkewMovingPercentile; - bool _isAllowedEditor; - bool _canRez; + AgentPermissions _permissions; }; Q_DECLARE_METATYPE(Node*) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 16a4083b08..b9f6da8c00 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -527,7 +527,7 @@ void NodeList::processDomainServerList(QSharedPointer message) DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); QDataStream packetStream(message->getMessage()); - + // grab the domain's ID from the beginning of the packet QUuid domainUUID; packetStream >> domainUUID; @@ -543,14 +543,9 @@ void NodeList::processDomainServerList(QSharedPointer message) packetStream >> newUUID; setSessionUUID(newUUID); - quint8 isAllowedEditor; - packetStream >> isAllowedEditor; - setIsAllowedEditor((bool) isAllowedEditor); + // pull the permissions/right/privileges for this node out of the stream + packetStream >> _permissions; - quint8 thisNodeCanRez; - packetStream >> thisNodeCanRez; - setThisNodeCanRez((bool) thisNodeCanRez); - // pull each node in the packet while (packetStream.device()->pos() < message->getSize()) { parseNodeFromPacketStream(packetStream); @@ -577,10 +572,9 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { qint8 nodeType; QUuid nodeUUID, connectionUUID; HifiSockAddr nodePublicSocket, nodeLocalSocket; - bool isAllowedEditor; - bool canRez; + AgentPermissions permissions; - packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> isAllowedEditor >> canRez; + packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> permissions; // if the public socket address is 0 then it's reachable at the same IP // as the domain server @@ -591,8 +585,7 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { packetStream >> connectionUUID; SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, - nodeLocalSocket, isAllowedEditor, canRez, - connectionUUID); + nodeLocalSocket, permissions, connectionUUID); } void NodeList::sendAssignment(Assignment& assignment) { From 83cba2dd82cbd3d4abcea7fcd6b328727e6bd762 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 2 Jun 2016 13:28:36 -0700 Subject: [PATCH 07/35] start on code to convert from settings version 1.2 to 1.3 --- domain-server/src/DomainGatekeeper.cpp | 2 -- .../src/DomainServerSettingsManager.cpp | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index c80969c8b4..cc631abfb7 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -173,8 +173,6 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo } const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; -// const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; -// const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection, const QString& username, diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index df299734cf..aa1297c3de 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -101,6 +101,11 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList double oldVersion = appSettings.value(JSON_SETTINGS_VERSION_KEY, 0.0).toDouble(); if (oldVersion != _descriptionVersion) { + const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; + const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; + const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; + const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; + qDebug() << "Previous domain-server settings version was" << QString::number(oldVersion, 'g', 8) << "and the new version is" << QString::number(_descriptionVersion, 'g', 8) << "- checking if any re-mapping is required"; @@ -110,8 +115,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // This was prior to the introduction of security.restricted_access // If the user has a list of allowed users then set their value for security.restricted_access to true - const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; - const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; QVariant* allowedUsers = valueForKeyPath(_configMap.getMergedConfig(), ALLOWED_USERS_SETTINGS_KEYPATH); if (allowedUsers @@ -198,7 +201,17 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList if (oldVersion < 1.3) { // This was prior to the permissions-grid in the domain-server settings page + // bool isRestrictingAccess = valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); + // const QVariant* allowedEditorsVariant = valueForKeyPath(getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); + + // const QVariant* editorsAreRezzersVariant = valueForKeyPath(getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH); + // bool onlyEditorsAreRezzers = false; + // if (editorsAreRezzersVariant) { + // onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool(); + // } + + // XXX } } From d010dc9699179c33f7b80a0dca2e75675633c37c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 2 Jun 2016 15:08:16 -0700 Subject: [PATCH 08/35] keep localhost line from being added every save. added some debugging print stuff --- domain-server/src/DomainGatekeeper.cpp | 4 +++ .../src/DomainServerSettingsManager.cpp | 2 +- libraries/networking/src/AgentPermissions.cpp | 25 +++++++++++++++++++ libraries/networking/src/AgentPermissions.h | 4 ++- 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index cc631abfb7..5503bbb5b0 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -169,6 +169,10 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo userPerms.canAdjustLocks = true; userPerms.canRezPermanentEntities = true; newNode->setPermissions(userPerms); + + qDebug() << "----------------------------"; + qDebug() << "AC perms are" << userPerms; + return newNode; } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index aa1297c3de..de35188f54 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -241,7 +241,7 @@ void DomainServerSettingsManager::unpackPermissions() { foreach (QVariant permsHash, *permissionsList) { AgentPermissionsPointer perms { new AgentPermissions(permsHash.toMap()) }; QString id = perms->getID(); - foundLoggedIn |= (id == "localhost"); + foundLocalhost |= (id == "localhost"); foundAnonymous |= (id == "anonymous"); foundLoggedIn |= (id == "logged-in"); _agentPermissions[id] = perms; diff --git a/libraries/networking/src/AgentPermissions.cpp b/libraries/networking/src/AgentPermissions.cpp index 0d3483975b..a2b46f49d5 100644 --- a/libraries/networking/src/AgentPermissions.cpp +++ b/libraries/networking/src/AgentPermissions.cpp @@ -10,6 +10,7 @@ // #include +#include #include "AgentPermissions.h" AgentPermissions& AgentPermissions::operator|=(const AgentPermissions& rhs) { @@ -41,3 +42,27 @@ QDataStream& operator>>(QDataStream& in, AgentPermissions& perms) { in >> perms.canConnectPastMaxCapacity; return in; } + +QDebug operator<<(QDebug debug, const AgentPermissions& perms) { + debug.nospace() << "[permissions: " << perms.getID() << " --"; + if (perms.canConnectToDomain) { + debug << " connect"; + } + if (perms.canAdjustLocks) { + debug << " locks"; + } + if (perms.canRezPermanentEntities) { + debug << " rez"; + } + if (perms.canRezTemporaryEntities) { + debug << " rez-tmp"; + } + if (perms.canWriteToAssetServer) { + debug << " asset-server"; + } + if (perms.canConnectPastMaxCapacity) { + debug << " ignore-max-cap"; + } + debug.nospace() << "]"; + return debug.nospace(); +} diff --git a/libraries/networking/src/AgentPermissions.h b/libraries/networking/src/AgentPermissions.h index d6598b8cbf..4ad381959a 100644 --- a/libraries/networking/src/AgentPermissions.h +++ b/libraries/networking/src/AgentPermissions.h @@ -35,7 +35,7 @@ public: canConnectPastMaxCapacity = perms["id_can_connect_past_max_capacity"].toBool(); } - QString getID() { return _id; } + QString getID() const { return _id; } // the initializations here should match the defaults in describe-settings.json bool canConnectToDomain { true }; @@ -76,4 +76,6 @@ protected: const AgentPermissions DEFAULT_AGENT_PERMISSIONS; +QDebug operator<<(QDebug debug, const AgentPermissions& node); + #endif // hifi_AgentPermissions_h From b36b1491af7d55b027cab5cf9bd7782416af3d73 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 3 Jun 2016 10:11:13 +1200 Subject: [PATCH 09/35] Restrict marking table rows dirty to table row switch events --- 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 35e9d64d84..6f6e1c9bd5 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -234,7 +234,7 @@ $(document).ready(function(){ // Bootstrap switch in table - $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function () { + $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.table-checkbox', function () { // Bootstrap switches in table: set the changed data attribute for all rows. var row = $(this).closest('tr'); row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); @@ -976,7 +976,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" html += " Date: Fri, 3 Jun 2016 10:27:38 +1200 Subject: [PATCH 10/35] Make editibility of switch columns be configurable --- domain-server/resources/describe-settings.json | 6 ++++++ domain-server/resources/web/settings/js/settings.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index b82a3ae1b1..6ddc75bcef 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -110,36 +110,42 @@ "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 Temp", "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/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 6f6e1c9bd5..0e34f1af24 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -972,7 +972,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { colName = keypath + "." + rowIndexOrName + "." + col.name; } - if (isArray && col.type === "checkbox") { + if (isArray && col.type === "checkbox" && col.editable) { html += "" html += " Date: Fri, 3 Jun 2016 10:40:32 +1200 Subject: [PATCH 11/35] Style new code like old --- .../resources/web/settings/js/settings.js | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 0e34f1af24..29bb430893 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -973,27 +973,13 @@ function makeTable(setting, keypath, setting_value, isLocked) { } if (isArray && col.type === "checkbox" && col.editable) { - html += "" - html += ""; } else { - - // setup the td for this column - html += ""; - - // add the actual value to the td so it is displayed - html += colValue; - - // for values to be posted properly we add a hidden input to this td - html += ""; - - html += ""; + // Use a hidden input so that the values are posted. + html += "" + + colValue + ""; } }) @@ -1039,12 +1025,8 @@ function makeTableInputs(setting) { _.each(setting.columns, function(col) { if (col.type === "checkbox") { html += "" - html += ""; } else { html += "\ Date: Thu, 2 Jun 2016 16:53:52 -0700 Subject: [PATCH 12/35] missed a protocol change --- domain-server/src/DomainServer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 6467951bcf..5ad6862b62 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -789,8 +789,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif extendedHeaderStream << limitedNodeList->getSessionUUID(); extendedHeaderStream << node->getUUID(); - extendedHeaderStream << (quint8) node->isAllowedEditor(); - extendedHeaderStream << (quint8) node->getCanRez(); + extendedHeaderStream << node->getPermissions(); auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader); From 8a2685325b41b9e032b9a85f71846acfa0b24b15 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 3 Jun 2016 12:06:51 +1200 Subject: [PATCH 13/35] Make localhost, anonymous, and logged-in rows non-deletable --- domain-server/resources/describe-settings.json | 5 ++++- .../resources/web/settings/js/settings.js | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 6ddc75bcef..7ee45d5ccd 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -148,7 +148,10 @@ "editable": true, "default": false } - ] + ], + + "non-deletable-row-key": "permissions_id", + "non-deletable-row-values": ["localhost", "anonymous", "logged-in"] } ] }, diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 29bb430893..44b2c692d2 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -919,6 +919,9 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" + setting.help + "" } + var nonDeletableRowKey = setting["non-deletable-row-key"]; + var nonDeletableRowValues = setting["non-deletable-row-values"]; + html += ""; @@ -961,6 +964,8 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" } + var isNonDeletableRow = false; + _.each(setting.columns, function(col) { if (isArray) { @@ -972,6 +977,9 @@ function makeTable(setting, keypath, setting_value, isLocked) { colName = keypath + "." + rowIndexOrName + "." + col.name; } + isNonDeletableRow = isNonDeletableRow + || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); + if (isArray && col.type === "checkbox" && col.editable) { html += "" } - html += "" + if (isNonDeletableRow) { + html += ""; + } else { + html += ""; + } } html += "" From f2e5b6056fa996d8a08483e199794b5423554f79 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 3 Jun 2016 14:03:19 +1200 Subject: [PATCH 14/35] Make table switches smaller --- domain-server/resources/web/settings/js/settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 44b2c692d2..3ddf9722fb 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -982,7 +982,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { if (isArray && col.type === "checkbox" && col.editable) { html += ""; } else { // Use a hidden input so that the values are posted. @@ -1037,8 +1037,8 @@ function makeTableInputs(setting) { _.each(setting.columns, function(col) { if (col.type === "checkbox") { html += ""; + + ""; } else { html += ""; } else { // Use a hidden input so that the values are posted. @@ -1037,7 +1039,7 @@ function makeTableInputs(setting) { _.each(setting.columns, function(col) { if (col.type === "checkbox") { html += ""; } else { html += "
" + rowIndexOrName + "" + "" + "
" - + "" - + "\ Date: Fri, 3 Jun 2016 13:15:53 -0700 Subject: [PATCH 15/35] add code to convert older domain-settings to current style --- domain-server/src/DomainGatekeeper.cpp | 13 ++- .../src/DomainServerSettingsManager.cpp | 109 +++++++++++++----- .../src/DomainServerSettingsManager.h | 4 +- libraries/networking/src/AgentPermissions.cpp | 20 ++++ libraries/networking/src/AgentPermissions.h | 5 +- 5 files changed, 115 insertions(+), 36 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 5503bbb5b0..cf32e804dc 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -169,10 +169,6 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo userPerms.canAdjustLocks = true; userPerms.canRezPermanentEntities = true; newNode->setPermissions(userPerms); - - qDebug() << "----------------------------"; - qDebug() << "AC perms are" << userPerms; - return newNode; } @@ -211,8 +207,13 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect userPerms |= _server->_settingsManager.getPermissionsForName("anonymous"); } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // they are sent us a username and the signature verifies it - userPerms |= _server->_settingsManager.getPermissionsForName(username); - userPerms |= _server->_settingsManager.getPermissionsForName("logged-in"); + if (_server->_settingsManager.havePermissionsForName(username)) { + // we have specific permissions for this user. + userPerms |= _server->_settingsManager.getPermissionsForName(username); + } else { + // they are logged into metaverse, but we don't have specific permissions for them. + userPerms |= _server->_settingsManager.getPermissionsForName("logged-in"); + } } else { // they sent us a username, but it didn't check out requestUserPublicKey(username); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index de35188f54..9d5da9deb9 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -201,50 +201,105 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList if (oldVersion < 1.3) { // This was prior to the permissions-grid in the domain-server settings page - // bool isRestrictingAccess = valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); + bool isRestrictedAccess = valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); + QStringList allowedUsers = valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); + QStringList allowedEditors = valueOrDefaultValueForKeyPath(ALLOWED_EDITORS_SETTINGS_KEYPATH).toStringList(); + bool onlyEditorsAreRezzers = valueOrDefaultValueForKeyPath(EDITORS_ARE_REZZERS_KEYPATH).toBool(); - // const QVariant* allowedEditorsVariant = valueForKeyPath(getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); + _agentPermissions["localhost"].reset(new AgentPermissions("localhost")); + _agentPermissions["localhost"]->setAll(true); + _agentPermissions["anonymous"].reset(new AgentPermissions("anonymous")); + _agentPermissions["logged-in"].reset(new AgentPermissions("logged-in")); - // const QVariant* editorsAreRezzersVariant = valueForKeyPath(getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH); - // bool onlyEditorsAreRezzers = false; - // if (editorsAreRezzersVariant) { - // onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool(); - // } + if (isRestrictedAccess) { + // only users in allow-users list can connect + _agentPermissions["anonymous"]->canConnectToDomain = false; + _agentPermissions["logged-in"]->canConnectToDomain = false; + } // else anonymous and logged-in retain default of canConnectToDomain = true - // XXX + 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 AgentPermissions(allowedUser)); + } + + foreach (QString allowedEditor, allowedEditors) { + if (!_agentPermissions.contains(allowedEditor)) { + _agentPermissions[allowedEditor].reset(new AgentPermissions(allowedEditor)); + if (isRestrictedAccess) { + // they can change locks, but can't connect. + _agentPermissions[allowedEditor]->canConnectToDomain = false; + } + } + _agentPermissions[allowedEditor]->canAdjustLocks = true; + } + + foreach (QString userName, _agentPermissions.keys()) { + if (onlyEditorsAreRezzers) { + _agentPermissions[userName]->canRezPermanentEntities = _agentPermissions[userName]->canAdjustLocks; + } else { + _agentPermissions[userName]->canRezPermanentEntities = true; + } + } + packPermissions(argumentList); + _agentPermissions.clear(); } } - unpackPermissions(); + unpackPermissions(argumentList); // write the current description version to our settings appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion); } -void DomainServerSettingsManager::unpackPermissions() { +void DomainServerSettingsManager::packPermissions(const QStringList& argumentList) { + // transfer details from _agentPermissions to _configMap + QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); + QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { + QVariantMap securityMap = security->toMap(); + QVariantList userList; + securityMap["permissions"] = userList; + _configMap.getUserConfig()["security"] = securityMap; + permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + } + + QVariantList* permissionsList = reinterpret_cast(permissions); + foreach (QString userName, _agentPermissions.keys()) { + *permissionsList += _agentPermissions[userName]->toVariant(); + } + persistToFile(); + _configMap.loadMasterAndUserConfig(argumentList); +} + +void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentList) { + // transfer details from _configMap to _agentPermissions; + bool foundLocalhost = false; bool foundAnonymous = false; bool foundLoggedIn = false; - // XXX check for duplicate IDs - - QVariant* permissions = valueForKeyPath(_configMap.getMergedConfig(), AGENT_PERMISSIONS_KEYPATH); - if (!permissions->canConvert(QMetaType::QVariantList)) { + QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { qDebug() << "failed to extract permissions from settings."; return; } - // QList permissionsList = permissions->toList(); + QList permissionsList = permissions->toList(); + // QVariantList* permissionsList = reinterpret_cast(permissions); - QVariantList* permissionsList = reinterpret_cast(permissions); - - foreach (QVariant permsHash, *permissionsList) { + foreach (QVariant permsHash, permissionsList) { AgentPermissionsPointer perms { new AgentPermissions(permsHash.toMap()) }; QString id = perms->getID(); foundLocalhost |= (id == "localhost"); foundAnonymous |= (id == "anonymous"); foundLoggedIn |= (id == "logged-in"); - _agentPermissions[id] = perms; + if (_agentPermissions.contains(id)) { + qDebug() << "duplicate name in permissions table: " << id; + _agentPermissions[id] |= perms; + } else { + _agentPermissions[id] = perms; + } } // if any of the standard names are missing, add them @@ -252,17 +307,20 @@ void DomainServerSettingsManager::unpackPermissions() { AgentPermissionsPointer perms { new AgentPermissions("localhost") }; perms->setAll(true); _agentPermissions["localhost"] = perms; - *permissionsList += perms->toVariant(); + // *permissionsList += perms->toVariant(); } if (!foundAnonymous) { AgentPermissionsPointer perms { new AgentPermissions("anonymous") }; _agentPermissions["anonymous"] = perms; - *permissionsList += perms->toVariant(); + // *permissionsList += perms->toVariant(); } if (!foundLoggedIn) { AgentPermissionsPointer perms { new AgentPermissions("logged-in") }; _agentPermissions["logged-in"] = perms; - *permissionsList += perms->toVariant(); + // *permissionsList += perms->toVariant(); + } + if (!foundLocalhost || !foundAnonymous || !foundLoggedIn) { + packPermissions(argumentList); } #ifdef WANT_DEBUG @@ -271,12 +329,7 @@ void DomainServerSettingsManager::unpackPermissions() { while (i.hasNext()) { i.next(); AgentPermissionsPointer perms = i.value(); - qDebug() << i.key() - << perms->canConnectToDomain - << perms->canAdjustLocks - << perms->canRezPermanentEntities - << perms->canRezTemporaryEntities - << perms->canWriteToAssetServer; + qDebug() << i.key() << perms; } #endif } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 3012e06c4d..8df0473eb3 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -40,6 +40,7 @@ public: QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } + bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); } AgentPermissions getPermissionsForName(const QString& name) const; QStringList getAllNames() { return _agentPermissions.keys(); } @@ -61,7 +62,8 @@ private: friend class DomainServer; - void unpackPermissions(); + void packPermissions(const QStringList& argumentList); + void unpackPermissions(const QStringList& argumentList); QHash _agentPermissions; }; diff --git a/libraries/networking/src/AgentPermissions.cpp b/libraries/networking/src/AgentPermissions.cpp index a2b46f49d5..afe523d15b 100644 --- a/libraries/networking/src/AgentPermissions.cpp +++ b/libraries/networking/src/AgentPermissions.cpp @@ -22,6 +22,19 @@ AgentPermissions& AgentPermissions::operator|=(const AgentPermissions& rhs) { this->canConnectPastMaxCapacity |= rhs.canConnectPastMaxCapacity; return *this; } +AgentPermissions& AgentPermissions::operator|=(const AgentPermissionsPointer& rhs) { + if (rhs) { + *this |= *rhs.get(); + } + return *this; +} +AgentPermissionsPointer& operator|=(AgentPermissionsPointer& lhs, const AgentPermissionsPointer& rhs) { + if (lhs && rhs) { + *lhs.get() |= rhs; + } + return lhs; +} + QDataStream& operator<<(QDataStream& out, const AgentPermissions& perms) { out << perms.canConnectToDomain; @@ -66,3 +79,10 @@ QDebug operator<<(QDebug debug, const AgentPermissions& perms) { debug.nospace() << "]"; return debug.nospace(); } +QDebug operator<<(QDebug debug, const AgentPermissionsPointer& perms) { + if (perms) { + return operator<<(debug, *perms.get()); + } + debug.nospace() << "[permissions: null]"; + return debug.nospace(); +} diff --git a/libraries/networking/src/AgentPermissions.h b/libraries/networking/src/AgentPermissions.h index 4ad381959a..9cbda08d92 100644 --- a/libraries/networking/src/AgentPermissions.h +++ b/libraries/networking/src/AgentPermissions.h @@ -67,6 +67,7 @@ public: } AgentPermissions& operator|=(const AgentPermissions& rhs); + AgentPermissions& operator|=(const AgentPermissionsPointer& rhs); friend QDataStream& operator<<(QDataStream& out, const AgentPermissions& perms); friend QDataStream& operator>>(QDataStream& in, AgentPermissions& perms); @@ -76,6 +77,8 @@ protected: const AgentPermissions DEFAULT_AGENT_PERMISSIONS; -QDebug operator<<(QDebug debug, const AgentPermissions& node); +QDebug operator<<(QDebug debug, const AgentPermissions& perms); +QDebug operator<<(QDebug debug, const AgentPermissionsPointer& perms); +AgentPermissionsPointer& operator|=(AgentPermissionsPointer& lhs, const AgentPermissionsPointer& rhs); #endif // hifi_AgentPermissions_h From 2eaf401bd6b5391db41c592b46b3553c9a4b4928 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 4 Jun 2016 12:39:55 +1200 Subject: [PATCH 16/35] Fix spurious data being submitted from changed permissions in new row --- .../resources/web/settings/js/settings.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 3ddf9722fb..43a3c89534 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -232,16 +232,17 @@ $(document).ready(function(){ badgeSidebarForDifferences($(this)); }); - // Bootstrap switch in table $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.table-checkbox', function () { - // Bootstrap switches in table: set the changed data attribute for all rows. + // Bootstrap switches in table: set the changed data attribute for all rows in table. var row = $(this).closest('tr'); - row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); - updateDataChangedForSiblingRows(row, true); + if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + badgeSidebarForDifferences($(this)); + } }); - $('.advanced-toggle').click(function(){ Settings.showAdvanced = !Settings.showAdvanced var advancedSelector = $('.' + Settings.ADVANCED_CLASS) @@ -850,6 +851,7 @@ function reloadSettings(callback) { // setup any bootstrap switches $('.toggle-checkbox').bootstrapSwitch(); + $('.table-checkbox').bootstrapSwitch(); // add tooltip to locked settings $('label.locked').tooltip({ @@ -982,7 +984,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { if (isArray && col.type === "checkbox" && col.editable) { html += "" - + "" - + "\ @@ -1173,7 +1175,7 @@ function addTableRow(add_glyphicon) { // Hide inputs var input = $(element).find("input") var isCheckbox = false; - if (input.hasClass("toggle-checkbox")) { + if (input.hasClass("table-checkbox")) { input = $(input).parent().parent(); isCheckbox = true; } From 09b0e3eaaf8df438115b0659d83217ee3927023b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 4 Jun 2016 09:07:34 -0700 Subject: [PATCH 17/35] use constants rather than bare strings for standard psuedo-account-names --- domain-server/src/DomainGatekeeper.cpp | 6 ++-- .../src/DomainServerSettingsManager.cpp | 36 +++++++++---------- libraries/networking/src/AgentPermissions.cpp | 5 +++ libraries/networking/src/AgentPermissions.h | 5 +++ 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index cf32e804dc..24334b5e2e 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -189,7 +189,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); if (isLocalUser) { - userPerms |= _server->_settingsManager.getPermissionsForName("localhost"); + userPerms |= _server->_settingsManager.getPermissionsForName(AgentPermissions::standardNameLocalhost); } if (!username.isEmpty() && usernameSignature.isEmpty()) { @@ -204,7 +204,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (username.isEmpty()) { // they didn't tell us who they are - userPerms |= _server->_settingsManager.getPermissionsForName("anonymous"); + userPerms |= _server->_settingsManager.getPermissionsForName(AgentPermissions::standardNameAnonymous); } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // they are sent us a username and the signature verifies it if (_server->_settingsManager.havePermissionsForName(username)) { @@ -212,7 +212,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect userPerms |= _server->_settingsManager.getPermissionsForName(username); } else { // they are logged into metaverse, but we don't have specific permissions for them. - userPerms |= _server->_settingsManager.getPermissionsForName("logged-in"); + userPerms |= _server->_settingsManager.getPermissionsForName(AgentPermissions::standardNameLoggedIn); } } else { // they sent us a username, but it didn't check out diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 9d5da9deb9..36f030f249 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -206,15 +206,18 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QStringList allowedEditors = valueOrDefaultValueForKeyPath(ALLOWED_EDITORS_SETTINGS_KEYPATH).toStringList(); bool onlyEditorsAreRezzers = valueOrDefaultValueForKeyPath(EDITORS_ARE_REZZERS_KEYPATH).toBool(); - _agentPermissions["localhost"].reset(new AgentPermissions("localhost")); - _agentPermissions["localhost"]->setAll(true); - _agentPermissions["anonymous"].reset(new AgentPermissions("anonymous")); - _agentPermissions["logged-in"].reset(new AgentPermissions("logged-in")); + _agentPermissions[AgentPermissions::standardNameLocalhost].reset( + new AgentPermissions(AgentPermissions::standardNameLocalhost)); + _agentPermissions[AgentPermissions::standardNameLocalhost]->setAll(true); + _agentPermissions[AgentPermissions::standardNameAnonymous].reset( + new AgentPermissions(AgentPermissions::standardNameAnonymous)); + _agentPermissions[AgentPermissions::standardNameLoggedIn].reset( + new AgentPermissions(AgentPermissions::standardNameLoggedIn)); if (isRestrictedAccess) { // only users in allow-users list can connect - _agentPermissions["anonymous"]->canConnectToDomain = false; - _agentPermissions["logged-in"]->canConnectToDomain = false; + _agentPermissions[AgentPermissions::standardNameAnonymous]->canConnectToDomain = false; + _agentPermissions[AgentPermissions::standardNameLoggedIn]->canConnectToDomain = false; } // else anonymous and logged-in retain default of canConnectToDomain = true foreach (QString allowedUser, allowedUsers) { @@ -291,9 +294,9 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL foreach (QVariant permsHash, permissionsList) { AgentPermissionsPointer perms { new AgentPermissions(permsHash.toMap()) }; QString id = perms->getID(); - foundLocalhost |= (id == "localhost"); - foundAnonymous |= (id == "anonymous"); - foundLoggedIn |= (id == "logged-in"); + foundLocalhost |= (id == AgentPermissions::standardNameLocalhost); + foundAnonymous |= (id == AgentPermissions::standardNameAnonymous); + foundLoggedIn |= (id == AgentPermissions::standardNameLoggedIn); if (_agentPermissions.contains(id)) { qDebug() << "duplicate name in permissions table: " << id; _agentPermissions[id] |= perms; @@ -304,20 +307,17 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL // if any of the standard names are missing, add them if (!foundLocalhost) { - AgentPermissionsPointer perms { new AgentPermissions("localhost") }; + AgentPermissionsPointer perms { new AgentPermissions(AgentPermissions::standardNameLocalhost) }; perms->setAll(true); - _agentPermissions["localhost"] = perms; - // *permissionsList += perms->toVariant(); + _agentPermissions[perms->getID()] = perms; } if (!foundAnonymous) { - AgentPermissionsPointer perms { new AgentPermissions("anonymous") }; - _agentPermissions["anonymous"] = perms; - // *permissionsList += perms->toVariant(); + AgentPermissionsPointer perms { new AgentPermissions(AgentPermissions::standardNameAnonymous) }; + _agentPermissions[perms->getID()] = perms; } if (!foundLoggedIn) { - AgentPermissionsPointer perms { new AgentPermissions("logged-in") }; - _agentPermissions["logged-in"] = perms; - // *permissionsList += perms->toVariant(); + AgentPermissionsPointer perms { new AgentPermissions(AgentPermissions::standardNameLoggedIn) }; + _agentPermissions[perms->getID()] = perms; } if (!foundLocalhost || !foundAnonymous || !foundLoggedIn) { packPermissions(argumentList); diff --git a/libraries/networking/src/AgentPermissions.cpp b/libraries/networking/src/AgentPermissions.cpp index afe523d15b..49c3e74eba 100644 --- a/libraries/networking/src/AgentPermissions.cpp +++ b/libraries/networking/src/AgentPermissions.cpp @@ -13,6 +13,11 @@ #include #include "AgentPermissions.h" +QString AgentPermissions::standardNameLocalhost = QString("localhost"); +QString AgentPermissions::standardNameLoggedIn = QString("logged-in"); +QString AgentPermissions::standardNameAnonymous = QString("anonymous"); + + AgentPermissions& AgentPermissions::operator|=(const AgentPermissions& rhs) { this->canConnectToDomain |= rhs.canConnectToDomain; this->canAdjustLocks |= rhs.canAdjustLocks; diff --git a/libraries/networking/src/AgentPermissions.h b/libraries/networking/src/AgentPermissions.h index 9cbda08d92..087c3c6fc8 100644 --- a/libraries/networking/src/AgentPermissions.h +++ b/libraries/networking/src/AgentPermissions.h @@ -37,6 +37,11 @@ public: QString getID() const { return _id; } + // these 3 names have special meaning. + static QString standardNameLocalhost; + static QString standardNameLoggedIn; + static QString standardNameAnonymous; + // the initializations here should match the defaults in describe-settings.json bool canConnectToDomain { true }; bool canAdjustLocks { false }; From adf893ea1934e1376891b5f7c59127bdc0de86ea Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 4 Jun 2016 09:50:36 -0700 Subject: [PATCH 18/35] rename AgentPermissions to NodePermissions --- domain-server/src/DomainGatekeeper.cpp | 10 ++--- .../src/DomainServerSettingsManager.cpp | 44 +++++++++---------- .../src/DomainServerSettingsManager.h | 6 +-- libraries/networking/src/LimitedNodeList.cpp | 6 +-- libraries/networking/src/LimitedNodeList.h | 6 +-- libraries/networking/src/Node.cpp | 4 +- libraries/networking/src/Node.h | 10 ++--- libraries/networking/src/NodeList.cpp | 2 +- ...entPermissions.cpp => NodePermissions.cpp} | 24 +++++----- .../{AgentPermissions.h => NodePermissions.h} | 36 +++++++-------- 10 files changed, 74 insertions(+), 74 deletions(-) rename libraries/networking/src/{AgentPermissions.cpp => NodePermissions.cpp} (71%) rename libraries/networking/src/{AgentPermissions.h => NodePermissions.h} (69%) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 24334b5e2e..98ac30b8b2 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -165,7 +165,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo _pendingAssignedNodes.erase(it); // always allow assignment clients to create and destroy entities - AgentPermissions userPerms; + NodePermissions userPerms; userPerms.canAdjustLocks = true; userPerms.canRezPermanentEntities = true; newNode->setPermissions(userPerms); @@ -181,7 +181,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect auto limitedNodeList = DependencyManager::get(); // start with empty permissions - AgentPermissions userPerms(username); + NodePermissions userPerms(username); userPerms.setAll(false); // check if this user is on our local machine - if this is true they are always allowed to connect @@ -189,7 +189,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); if (isLocalUser) { - userPerms |= _server->_settingsManager.getPermissionsForName(AgentPermissions::standardNameLocalhost); + userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameLocalhost); } if (!username.isEmpty() && usernameSignature.isEmpty()) { @@ -204,7 +204,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (username.isEmpty()) { // they didn't tell us who they are - userPerms |= _server->_settingsManager.getPermissionsForName(AgentPermissions::standardNameAnonymous); + userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // they are sent us a username and the signature verifies it if (_server->_settingsManager.havePermissionsForName(username)) { @@ -212,7 +212,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect userPerms |= _server->_settingsManager.getPermissionsForName(username); } else { // they are logged into metaverse, but we don't have specific permissions for them. - userPerms |= _server->_settingsManager.getPermissionsForName(AgentPermissions::standardNameLoggedIn); + userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameLoggedIn); } } else { // they sent us a username, but it didn't check out diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 36f030f249..9faf00ab56 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -206,29 +206,29 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QStringList allowedEditors = valueOrDefaultValueForKeyPath(ALLOWED_EDITORS_SETTINGS_KEYPATH).toStringList(); bool onlyEditorsAreRezzers = valueOrDefaultValueForKeyPath(EDITORS_ARE_REZZERS_KEYPATH).toBool(); - _agentPermissions[AgentPermissions::standardNameLocalhost].reset( - new AgentPermissions(AgentPermissions::standardNameLocalhost)); - _agentPermissions[AgentPermissions::standardNameLocalhost]->setAll(true); - _agentPermissions[AgentPermissions::standardNameAnonymous].reset( - new AgentPermissions(AgentPermissions::standardNameAnonymous)); - _agentPermissions[AgentPermissions::standardNameLoggedIn].reset( - new AgentPermissions(AgentPermissions::standardNameLoggedIn)); + _agentPermissions[NodePermissions::standardNameLocalhost].reset( + new NodePermissions(NodePermissions::standardNameLocalhost)); + _agentPermissions[NodePermissions::standardNameLocalhost]->setAll(true); + _agentPermissions[NodePermissions::standardNameAnonymous].reset( + new NodePermissions(NodePermissions::standardNameAnonymous)); + _agentPermissions[NodePermissions::standardNameLoggedIn].reset( + new NodePermissions(NodePermissions::standardNameLoggedIn)); if (isRestrictedAccess) { // only users in allow-users list can connect - _agentPermissions[AgentPermissions::standardNameAnonymous]->canConnectToDomain = false; - _agentPermissions[AgentPermissions::standardNameLoggedIn]->canConnectToDomain = false; + _agentPermissions[NodePermissions::standardNameAnonymous]->canConnectToDomain = false; + _agentPermissions[NodePermissions::standardNameLoggedIn]->canConnectToDomain = false; } // 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 AgentPermissions(allowedUser)); + _agentPermissions[allowedUser].reset(new NodePermissions(allowedUser)); } foreach (QString allowedEditor, allowedEditors) { if (!_agentPermissions.contains(allowedEditor)) { - _agentPermissions[allowedEditor].reset(new AgentPermissions(allowedEditor)); + _agentPermissions[allowedEditor].reset(new NodePermissions(allowedEditor)); if (isRestrictedAccess) { // they can change locks, but can't connect. _agentPermissions[allowedEditor]->canConnectToDomain = false; @@ -292,11 +292,11 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL // QVariantList* permissionsList = reinterpret_cast(permissions); foreach (QVariant permsHash, permissionsList) { - AgentPermissionsPointer perms { new AgentPermissions(permsHash.toMap()) }; + NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); - foundLocalhost |= (id == AgentPermissions::standardNameLocalhost); - foundAnonymous |= (id == AgentPermissions::standardNameAnonymous); - foundLoggedIn |= (id == AgentPermissions::standardNameLoggedIn); + foundLocalhost |= (id == NodePermissions::standardNameLocalhost); + foundAnonymous |= (id == NodePermissions::standardNameAnonymous); + foundLoggedIn |= (id == NodePermissions::standardNameLoggedIn); if (_agentPermissions.contains(id)) { qDebug() << "duplicate name in permissions table: " << id; _agentPermissions[id] |= perms; @@ -307,16 +307,16 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL // if any of the standard names are missing, add them if (!foundLocalhost) { - AgentPermissionsPointer perms { new AgentPermissions(AgentPermissions::standardNameLocalhost) }; + NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; perms->setAll(true); _agentPermissions[perms->getID()] = perms; } if (!foundAnonymous) { - AgentPermissionsPointer perms { new AgentPermissions(AgentPermissions::standardNameAnonymous) }; + NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) }; _agentPermissions[perms->getID()] = perms; } if (!foundLoggedIn) { - AgentPermissionsPointer perms { new AgentPermissions(AgentPermissions::standardNameLoggedIn) }; + NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) }; _agentPermissions[perms->getID()] = perms; } if (!foundLocalhost || !foundAnonymous || !foundLoggedIn) { @@ -325,20 +325,20 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL #ifdef WANT_DEBUG qDebug() << "--------------- permissions ---------------------"; - QHashIterator i(_agentPermissions); + QHashIterator i(_agentPermissions); while (i.hasNext()) { i.next(); - AgentPermissionsPointer perms = i.value(); + NodePermissionsPointer perms = i.value(); qDebug() << i.key() << perms; } #endif } -AgentPermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const { +NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const { if (_agentPermissions.contains(name)) { return *(_agentPermissions[name].get()); } - AgentPermissions nullPermissions; + NodePermissions nullPermissions; nullPermissions.setAll(false); return nullPermissions; } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 8df0473eb3..d82f5e588c 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -19,7 +19,7 @@ #include #include -#include "AgentPermissions.h" +#include "NodePermissions.h" const QString SETTINGS_PATHS_KEY = "paths"; @@ -41,7 +41,7 @@ public: QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); } - AgentPermissions getPermissionsForName(const QString& name) const; + NodePermissions getPermissionsForName(const QString& name) const; QStringList getAllNames() { return _agentPermissions.keys(); } private slots: @@ -64,7 +64,7 @@ private: void packPermissions(const QStringList& argumentList); void unpackPermissions(const QStringList& argumentList); - QHash _agentPermissions; + QHash _agentPermissions; }; #endif // hifi_DomainServerSettingsManager_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 274d022ab5..a4daff5d35 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -52,7 +52,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _numCollectedPackets(0), _numCollectedBytes(0), _packetStatTimer(), - _permissions(AgentPermissions()) + _permissions(NodePermissions()) { static bool firstCall = true; if (firstCall) { @@ -131,7 +131,7 @@ void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { } -void LimitedNodeList::setPermissions(const AgentPermissions& newPermissions) { +void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) { bool emitIsAllowedEditorChanged { false }; bool emitCanRezChanged { false }; @@ -523,7 +523,7 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - const AgentPermissions& permissions, + const NodePermissions& permissions, const QUuid& connectionSecret) { NodeHash::const_iterator it = _nodeHash.find(uuid); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index c3378a4cf8..e4c3ae9dec 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -104,7 +104,7 @@ public: const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); - void setPermissions(const AgentPermissions& newPermissions); + void setPermissions(const NodePermissions& newPermissions); bool isAllowedEditor() const { return _permissions.canAdjustLocks; } bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; } @@ -135,7 +135,7 @@ public: SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - const AgentPermissions& permissions = DEFAULT_AGENT_PERMISSIONS, + const NodePermissions& permissions = DEFAULT_AGENT_PERMISSIONS, const QUuid& connectionSecret = QUuid()); bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } @@ -298,7 +298,7 @@ protected: int _numCollectedBytes; QElapsedTimer _packetStatTimer; - AgentPermissions _permissions; + NodePermissions _permissions; QPointer _initialSTUNTimer; diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 94f32a4e01..7201b2fd9a 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -16,7 +16,7 @@ #include "Node.h" #include "SharedUtil.h" -#include "AgentPermissions.h" +#include "NodePermissions.h" #include #include @@ -48,7 +48,7 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { } Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, - const HifiSockAddr& localSocket, const AgentPermissions& permissions, const QUuid& connectionSecret, + const HifiSockAddr& localSocket, const NodePermissions& permissions, const QUuid& connectionSecret, QObject* parent) : NetworkPeer(uuid, publicSocket, localSocket, parent), _type(type), diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index abc3cfa181..367e17d345 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -27,14 +27,14 @@ #include "NodeType.h" #include "SimpleMovingAverage.h" #include "MovingPercentile.h" -#include "AgentPermissions.h" +#include "NodePermissions.h" class Node : public NetworkPeer { Q_OBJECT public: Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - const AgentPermissions& permissions, const QUuid& connectionSecret = QUuid(), + const NodePermissions& permissions, const QUuid& connectionSecret = QUuid(), QObject* parent = 0); bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; } @@ -59,8 +59,8 @@ public: void updateClockSkewUsec(qint64 clockSkewSample); QMutex& getMutex() { return _mutex; } - void setPermissions(const AgentPermissions& newPermissions) { _permissions = newPermissions; } - AgentPermissions getPermissions() const { return _permissions; } + void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; } + NodePermissions getPermissions() const { return _permissions; } bool isAllowedEditor() const { return _permissions.canAdjustLocks; } bool getCanRez() const { return _permissions.canRezPermanentEntities; } @@ -81,7 +81,7 @@ private: qint64 _clockSkewUsec; QMutex _mutex; MovingPercentile _clockSkewMovingPercentile; - AgentPermissions _permissions; + NodePermissions _permissions; }; Q_DECLARE_METATYPE(Node*) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index b9f6da8c00..3bcea740e7 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -572,7 +572,7 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { qint8 nodeType; QUuid nodeUUID, connectionUUID; HifiSockAddr nodePublicSocket, nodeLocalSocket; - AgentPermissions permissions; + NodePermissions permissions; packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> permissions; diff --git a/libraries/networking/src/AgentPermissions.cpp b/libraries/networking/src/NodePermissions.cpp similarity index 71% rename from libraries/networking/src/AgentPermissions.cpp rename to libraries/networking/src/NodePermissions.cpp index 49c3e74eba..7edff601b1 100644 --- a/libraries/networking/src/AgentPermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -1,5 +1,5 @@ // -// AgentPermissions.cpp +// NodePermissions.cpp // libraries/networking/src/ // // Created by Seth Alves on 2016-6-1. @@ -11,14 +11,14 @@ #include #include -#include "AgentPermissions.h" +#include "NodePermissions.h" -QString AgentPermissions::standardNameLocalhost = QString("localhost"); -QString AgentPermissions::standardNameLoggedIn = QString("logged-in"); -QString AgentPermissions::standardNameAnonymous = QString("anonymous"); +QString NodePermissions::standardNameLocalhost = QString("localhost"); +QString NodePermissions::standardNameLoggedIn = QString("logged-in"); +QString NodePermissions::standardNameAnonymous = QString("anonymous"); -AgentPermissions& AgentPermissions::operator|=(const AgentPermissions& rhs) { +NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) { this->canConnectToDomain |= rhs.canConnectToDomain; this->canAdjustLocks |= rhs.canAdjustLocks; this->canRezPermanentEntities |= rhs.canRezPermanentEntities; @@ -27,13 +27,13 @@ AgentPermissions& AgentPermissions::operator|=(const AgentPermissions& rhs) { this->canConnectPastMaxCapacity |= rhs.canConnectPastMaxCapacity; return *this; } -AgentPermissions& AgentPermissions::operator|=(const AgentPermissionsPointer& rhs) { +NodePermissions& NodePermissions::operator|=(const NodePermissionsPointer& rhs) { if (rhs) { *this |= *rhs.get(); } return *this; } -AgentPermissionsPointer& operator|=(AgentPermissionsPointer& lhs, const AgentPermissionsPointer& rhs) { +NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs) { if (lhs && rhs) { *lhs.get() |= rhs; } @@ -41,7 +41,7 @@ AgentPermissionsPointer& operator|=(AgentPermissionsPointer& lhs, const AgentPer } -QDataStream& operator<<(QDataStream& out, const AgentPermissions& perms) { +QDataStream& operator<<(QDataStream& out, const NodePermissions& perms) { out << perms.canConnectToDomain; out << perms.canAdjustLocks; out << perms.canRezPermanentEntities; @@ -51,7 +51,7 @@ QDataStream& operator<<(QDataStream& out, const AgentPermissions& perms) { return out; } -QDataStream& operator>>(QDataStream& in, AgentPermissions& perms) { +QDataStream& operator>>(QDataStream& in, NodePermissions& perms) { in >> perms.canConnectToDomain; in >> perms.canAdjustLocks; in >> perms.canRezPermanentEntities; @@ -61,7 +61,7 @@ QDataStream& operator>>(QDataStream& in, AgentPermissions& perms) { return in; } -QDebug operator<<(QDebug debug, const AgentPermissions& perms) { +QDebug operator<<(QDebug debug, const NodePermissions& perms) { debug.nospace() << "[permissions: " << perms.getID() << " --"; if (perms.canConnectToDomain) { debug << " connect"; @@ -84,7 +84,7 @@ QDebug operator<<(QDebug debug, const AgentPermissions& perms) { debug.nospace() << "]"; return debug.nospace(); } -QDebug operator<<(QDebug debug, const AgentPermissionsPointer& perms) { +QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms) { if (perms) { return operator<<(debug, *perms.get()); } diff --git a/libraries/networking/src/AgentPermissions.h b/libraries/networking/src/NodePermissions.h similarity index 69% rename from libraries/networking/src/AgentPermissions.h rename to libraries/networking/src/NodePermissions.h index 087c3c6fc8..82584580cc 100644 --- a/libraries/networking/src/AgentPermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -1,5 +1,5 @@ // -// AgentPermissions.h +// NodePermissions.h // libraries/networking/src/ // // Created by Seth Alves on 2016-6-1. @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_AgentPermissions_h -#define hifi_AgentPermissions_h +#ifndef hifi_NodePermissions_h +#define hifi_NodePermissions_h #include #include @@ -18,14 +18,14 @@ #include #include -class AgentPermissions; -using AgentPermissionsPointer = std::shared_ptr; +class NodePermissions; +using NodePermissionsPointer = std::shared_ptr; -class AgentPermissions { +class NodePermissions { public: - AgentPermissions() { _id = QUuid::createUuid().toString(); } - AgentPermissions(const QString& name) { _id = name; } - AgentPermissions(QMap perms) { + 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(); @@ -71,19 +71,19 @@ public: return QVariant(values); } - AgentPermissions& operator|=(const AgentPermissions& rhs); - AgentPermissions& operator|=(const AgentPermissionsPointer& rhs); - friend QDataStream& operator<<(QDataStream& out, const AgentPermissions& perms); - friend QDataStream& operator>>(QDataStream& in, AgentPermissions& perms); + NodePermissions& operator|=(const NodePermissions& rhs); + NodePermissions& operator|=(const NodePermissionsPointer& rhs); + friend QDataStream& operator<<(QDataStream& out, const NodePermissions& perms); + friend QDataStream& operator>>(QDataStream& in, NodePermissions& perms); protected: QString _id; }; -const AgentPermissions DEFAULT_AGENT_PERMISSIONS; +const NodePermissions DEFAULT_AGENT_PERMISSIONS; -QDebug operator<<(QDebug debug, const AgentPermissions& perms); -QDebug operator<<(QDebug debug, const AgentPermissionsPointer& perms); -AgentPermissionsPointer& operator|=(AgentPermissionsPointer& lhs, const AgentPermissionsPointer& rhs); +QDebug operator<<(QDebug debug, const NodePermissions& perms); +QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms); +NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs); -#endif // hifi_AgentPermissions_h +#endif // hifi_NodePermissions_h From 80eeff5a64f80170e363143daf223b771907fb2c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 4 Jun 2016 10:32:07 -0700 Subject: [PATCH 19/35] bump packet versions. change how domainObject[RESTRICTED_ACCESS_FLAG] is decided --- domain-server/src/DomainServer.cpp | 4 +++- libraries/networking/src/NodePermissions.cpp | 4 ++++ libraries/networking/src/NodePermissions.h | 1 + libraries/networking/src/udt/PacketHeaders.cpp | 5 ++++- libraries/networking/src/udt/PacketHeaders.h | 10 ++++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 5ad6862b62..020983eb03 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1087,7 +1087,9 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { // add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings const QString RESTRICTED_ACCESS_FLAG = "restricted"; - domainObject[RESTRICTED_ACCESS_FLAG] = _settingsManager.getAllNames().length() > 0; + // consider the domain to have restricted access if "anonymous" connections can't connect to the domain. + NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); + domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; // figure out the breakdown of currently connected interface clients int numConnectedUnassigned = 0; diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index 7edff601b1..5f6dfa6b2f 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -17,6 +17,10 @@ QString NodePermissions::standardNameLocalhost = QString("localhost"); QString NodePermissions::standardNameLoggedIn = QString("logged-in"); QString NodePermissions::standardNameAnonymous = QString("anonymous"); +QList NodePermissions::standardNames = QList() + << NodePermissions::standardNameLocalhost + << NodePermissions::standardNameLoggedIn + << NodePermissions::standardNameAnonymous; NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) { this->canConnectToDomain |= rhs.canConnectToDomain; diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 82584580cc..22e5950426 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -41,6 +41,7 @@ public: static QString standardNameLocalhost; static QString standardNameLoggedIn; static QString standardNameAnonymous; + static QList standardNames; // the initializations here should match the defaults in describe-settings.json bool canConnectToDomain { true }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index db743f81e4..6ca50420f3 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -45,7 +45,7 @@ const QSet RELIABLE_PACKETS = QSet(); PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { case PacketType::DomainList: - return 18; + return static_cast(DomainListVersion::PermissionsGrid); case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: @@ -69,6 +69,9 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::DomainConnectRequest: return static_cast(DomainConnectRequestVersion::HasProtocolVersions); + case PacketType::DomainServerAddedNode: + return static_cast(DomainServerAddedNodeVersion::PermissionsGrid); + default: return 17; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 320635379d..ae54450fee 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -199,4 +199,14 @@ enum class DomainConnectionDeniedVersion : PacketVersion { IncludesReasonCode }; +enum class DomainServerAddedNodeVersion : PacketVersion { + PrePermissionsGrid = 17, + PermissionsGrid +}; + +enum class DomainListVersion : PacketVersion { + PrePermissionsGrid = 18, + PermissionsGrid +}; + #endif // hifi_PacketHeaders_h From be403865fc207f67942da8a3d54ab54a0acdd067 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 4 Jun 2016 10:44:59 -0700 Subject: [PATCH 20/35] don't request public keys for the standard psuedo-account-names --- domain-server/src/DomainGatekeeper.cpp | 5 +++++ libraries/networking/src/NodePermissions.cpp | 2 +- libraries/networking/src/NodePermissions.h | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 98ac30b8b2..10e22c3293 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -404,6 +404,11 @@ void DomainGatekeeper::preloadAllowedUserPublicKeys() { } void DomainGatekeeper::requestUserPublicKey(const QString& username) { + // don't request public keys for the standard psuedo-account-names + if (NodePermissions::standardNames.contains(username, Qt::CaseInsensitive)) { + return; + } + // 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; diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index 5f6dfa6b2f..fb74ccdc94 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -17,7 +17,7 @@ QString NodePermissions::standardNameLocalhost = QString("localhost"); QString NodePermissions::standardNameLoggedIn = QString("logged-in"); QString NodePermissions::standardNameAnonymous = QString("anonymous"); -QList NodePermissions::standardNames = QList() +QStringList NodePermissions::standardNames = QList() << NodePermissions::standardNameLocalhost << NodePermissions::standardNameLoggedIn << NodePermissions::standardNameAnonymous; diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 22e5950426..adc13ba375 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -41,7 +41,7 @@ public: static QString standardNameLocalhost; static QString standardNameLoggedIn; static QString standardNameAnonymous; - static QList standardNames; + static QStringList standardNames; // the initializations here should match the defaults in describe-settings.json bool canConnectToDomain { true }; From dd009bf8b9d08ad31a2189dd03c16ece05dc41de Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 4 Jun 2016 11:06:37 -0700 Subject: [PATCH 21/35] add some debugging prints. make blacklisting work --- domain-server/src/DomainGatekeeper.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 10e22c3293..c58ccc4036 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -190,6 +190,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); if (isLocalUser) { userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameLocalhost); + qDebug() << "user-permissions: is local user, so:" << userPerms; } if (!username.isEmpty() && usernameSignature.isEmpty()) { @@ -197,7 +198,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); - if (!isLocalUser) { + if (!userPerms.canConnectToDomain) { return SharedNodePointer(); } } @@ -205,24 +206,31 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (username.isEmpty()) { // they didn't tell us who they are userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); + 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 if (_server->_settingsManager.havePermissionsForName(username)) { // we have specific permissions for this user. 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.getPermissionsForName(NodePermissions::standardNameLoggedIn); + qDebug() << "user-permissions: user is logged in, so:" << userPerms; } } else { // they sent us a username, but it didn't check out requestUserPublicKey(username); - if (!isLocalUser) { + if (!userPerms.canConnectToDomain) { return SharedNodePointer(); } } + qDebug() << "user-permissions: final:" << userPerms; + if (!userPerms.canConnectToDomain) { + sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", + nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers); return SharedNodePointer(); } @@ -330,7 +338,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, rsaPublicKey); if (decryptResult == 1) { - qDebug() << "Username signature matches for" << username << "- allowing connection."; + qDebug() << "Username signature matches for" << username; // free up the public key and remove connection token before we return RSA_free(rsaPublicKey); From a4e4093a18c058d14c4b893653514e9d636195c7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 6 Jun 2016 10:39:47 -0700 Subject: [PATCH 22/35] split permissions into two sets, one for 'standard' ones and one for specific users --- .../resources/describe-settings.json | 73 ++++++++++- .../resources/web/settings/js/settings.js | 6 +- domain-server/src/DomainGatekeeper.cpp | 2 +- .../src/DomainServerSettingsManager.cpp | 115 +++++++++++++----- .../src/DomainServerSettingsManager.h | 8 +- 5 files changed, 165 insertions(+), 39 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index a4d6424997..26a368fd04 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -56,6 +56,7 @@ "label": "Paths", "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", + "can_add_new_rows": true, "key": { "name": "path", "label": "Path", @@ -96,10 +97,11 @@ "advanced": false }, { - "name": "permissions", + "name": "standard_permissions", "type": "table", - "label": "Domain-Wide User Permissions", - "help": "Indicate which users or groups can have which domain-wide permissions.", + "label": "Domain-Wide Permissions", + "help": "Standard Permissions:", + "can_add_new_rows": false, "columns": [ { @@ -152,6 +154,61 @@ "non-deletable-row-key": "permissions_id", "non-deletable-row-values": ["localhost", "anonymous", "logged-in"] + }, + { + "name": "permissions", + "type": "table", + "help": "Permissions for Specific Users:", + "can_add_new_rows": true, + + "columns": [ + { + "name": "permissions_id", + "label": "User/Group" + }, + { + "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 Temp", + "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 + } + ] } ] }, @@ -164,6 +221,8 @@ "type": "table", "label": "Persistent Scripts", "help": "Add the URLs for scripts that you would like to ensure are always running in your domain.", + "can_add_new_rows": true, + "columns": [ { "name": "url", @@ -248,6 +307,8 @@ "label": "Zones", "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, + "can_add_new_rows": true, + "key": { "name": "name", "label": "Name", @@ -299,6 +360,8 @@ "help": "In this table you can set custom attenuation coefficients between audio zones", "numbered": true, "can_order": true, + "can_add_new_rows": true, + "columns": [ { "name": "source", @@ -326,6 +389,8 @@ "label": "Reverb Settings", "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, + "can_add_new_rows": true, + "columns": [ { "name": "zone", @@ -447,6 +512,8 @@ "label": "Backup Rules", "help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.", "numbered": false, + "can_add_new_rows": true, + "default": [ {"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5}, {"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7}, diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 43a3c89534..970e7551c4 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1015,7 +1015,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { } // populate inputs in the table for new values - if (!isLocked && !setting.read_only) { + if (!isLocked && !setting.read_only && setting.can_add_new_rows) { html += makeTableInputs(setting) } html += "
" @@ -1052,8 +1052,8 @@ function makeTableInputs(setting) { if (setting.can_order) { html += "" } - html += "
" + html += "" html += "" return html diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index c58ccc4036..adc95f3c38 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -189,7 +189,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); if (isLocalUser) { - userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameLocalhost); + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost); qDebug() << "user-permissions: is local user, so:" << userPerms; } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 9faf00ab56..1197bf023b 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -206,18 +206,18 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QStringList allowedEditors = valueOrDefaultValueForKeyPath(ALLOWED_EDITORS_SETTINGS_KEYPATH).toStringList(); bool onlyEditorsAreRezzers = valueOrDefaultValueForKeyPath(EDITORS_ARE_REZZERS_KEYPATH).toBool(); - _agentPermissions[NodePermissions::standardNameLocalhost].reset( + _standardAgentPermissions[NodePermissions::standardNameLocalhost].reset( new NodePermissions(NodePermissions::standardNameLocalhost)); - _agentPermissions[NodePermissions::standardNameLocalhost]->setAll(true); - _agentPermissions[NodePermissions::standardNameAnonymous].reset( + _standardAgentPermissions[NodePermissions::standardNameLocalhost]->setAll(true); + _standardAgentPermissions[NodePermissions::standardNameAnonymous].reset( new NodePermissions(NodePermissions::standardNameAnonymous)); - _agentPermissions[NodePermissions::standardNameLoggedIn].reset( + _standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset( new NodePermissions(NodePermissions::standardNameLoggedIn)); if (isRestrictedAccess) { // only users in allow-users list can connect - _agentPermissions[NodePermissions::standardNameAnonymous]->canConnectToDomain = false; - _agentPermissions[NodePermissions::standardNameLoggedIn]->canConnectToDomain = false; + _standardAgentPermissions[NodePermissions::standardNameAnonymous]->canConnectToDomain = false; + _standardAgentPermissions[NodePermissions::standardNameLoggedIn]->canConnectToDomain = false; } // else anonymous and logged-in retain default of canConnectToDomain = true foreach (QString allowedUser, allowedUsers) { @@ -237,14 +237,20 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList _agentPermissions[allowedEditor]->canAdjustLocks = true; } - foreach (QString userName, _agentPermissions.keys()) { - if (onlyEditorsAreRezzers) { - _agentPermissions[userName]->canRezPermanentEntities = _agentPermissions[userName]->canAdjustLocks; - } else { - _agentPermissions[userName]->canRezPermanentEntities = true; + QList> permissionsSets; + permissionsSets << _standardAgentPermissions << _agentPermissions; + foreach (auto permissionsSet, permissionsSets) { + foreach (QString userName, permissionsSet.keys()) { + if (onlyEditorsAreRezzers) { + permissionsSet[userName]->canRezPermanentEntities = permissionsSet[userName]->canAdjustLocks; + } else { + permissionsSet[userName]->canRezPermanentEntities = true; + } } } + packPermissions(argumentList); + _standardAgentPermissions.clear(); _agentPermissions.clear(); } } @@ -255,22 +261,36 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion); } -void DomainServerSettingsManager::packPermissions(const QStringList& argumentList) { - // transfer details from _agentPermissions to _configMap +void DomainServerSettingsManager::packPermissionsForMap(const QStringList& argumentList, + QString mapName, + QHash agentPermissions, + QString keyPath) { QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); - QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + + // save settings for anonymous / logged-in / localhost + QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { QVariantMap securityMap = security->toMap(); QVariantList userList; - securityMap["permissions"] = userList; + securityMap[mapName] = userList; _configMap.getUserConfig()["security"] = securityMap; - permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); } QVariantList* permissionsList = reinterpret_cast(permissions); - foreach (QString userName, _agentPermissions.keys()) { - *permissionsList += _agentPermissions[userName]->toVariant(); + (*permissionsList).clear(); + foreach (QString userName, agentPermissions.keys()) { + *permissionsList += agentPermissions[userName]->toVariant(); } +} + +void DomainServerSettingsManager::packPermissions(const QStringList& argumentList) { + // transfer details from _agentPermissions to _configMap + packPermissionsForMap(argumentList, "standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH); + + // save settings for specific users + packPermissionsForMap(argumentList, "permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); + persistToFile(); _configMap.loadMasterAndUserConfig(argumentList); } @@ -281,25 +301,43 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL bool foundLocalhost = false; bool foundAnonymous = false; bool foundLoggedIn = false; + bool needPack = false; + QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); + if (!standardPermissions || !standardPermissions->canConvert(QMetaType::QVariantList)) { + qDebug() << "failed to extract standard permissions from settings."; + return; + } QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { qDebug() << "failed to extract permissions from settings."; return; } - QList permissionsList = permissions->toList(); - // QVariantList* permissionsList = reinterpret_cast(permissions); - - foreach (QVariant permsHash, permissionsList) { + QList standardPermissionsList = standardPermissions->toList(); + 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); + if (_standardAgentPermissions.contains(id)) { + qDebug() << "duplicate name in standard permissions table: " << id; + _standardAgentPermissions[id] |= perms; + needPack = true; + } else { + _standardAgentPermissions[id] = perms; + } + } + + QList permissionsList = permissions->toList(); + foreach (QVariant permsHash, permissionsList) { + NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; + QString id = perms->getID(); if (_agentPermissions.contains(id)) { qDebug() << "duplicate name in permissions table: " << id; _agentPermissions[id] |= perms; + needPack = true; } else { _agentPermissions[id] = perms; } @@ -309,31 +347,46 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL if (!foundLocalhost) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; perms->setAll(true); - _agentPermissions[perms->getID()] = perms; + _standardAgentPermissions[perms->getID()] = perms; } if (!foundAnonymous) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) }; - _agentPermissions[perms->getID()] = perms; + needPack = true; } if (!foundLoggedIn) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) }; - _agentPermissions[perms->getID()] = perms; + needPack = true; } - if (!foundLocalhost || !foundAnonymous || !foundLoggedIn) { + + if (needPack) { packPermissions(argumentList); } + #ifdef WANT_DEBUG qDebug() << "--------------- permissions ---------------------"; - QHashIterator i(_agentPermissions); - while (i.hasNext()) { - i.next(); - NodePermissionsPointer perms = i.value(); - qDebug() << i.key() << perms; + QList> permissionsSets; + permissionsSets << _standardAgentPermissions << _agentPermissions; + foreach (auto permissionSet, permissionsSets) { + QHashIterator i(permissionSet); + while (i.hasNext()) { + i.next(); + NodePermissionsPointer perms = i.value(); + qDebug() << i.key() << perms; + } } #endif } +NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const QString& name) const { + if (_standardAgentPermissions.contains(name)) { + return *(_standardAgentPermissions[name].get()); + } + NodePermissions nullPermissions; + nullPermissions.setAll(false); + return nullPermissions; +} + NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const { if (_agentPermissions.contains(name)) { return *(_agentPermissions[name].get()); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index d82f5e588c..51ee657961 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -25,6 +25,7 @@ const QString SETTINGS_PATHS_KEY = "paths"; 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"; class DomainServerSettingsManager : public QObject { @@ -40,7 +41,9 @@ public: QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } 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; NodePermissions getPermissionsForName(const QString& name) const; QStringList getAllNames() { return _agentPermissions.keys(); } @@ -62,9 +65,12 @@ private: friend class DomainServer; + void packPermissionsForMap(const QStringList& argumentList, QString mapName, + QHash agentPermissions, QString keyPath); void packPermissions(const QStringList& argumentList); void unpackPermissions(const QStringList& argumentList); - QHash _agentPermissions; + QHash _standardAgentPermissions; // anonymous, logged-in, localhost + QHash _agentPermissions; // specific account-names }; #endif // hifi_DomainServerSettingsManager_h From d202a2bf1170dfd378401a04e1ee84203e13e3e7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 6 Jun 2016 11:59:39 -0700 Subject: [PATCH 23/35] better handling of missing settings keys --- .../src/DomainServerSettingsManager.cpp | 21 ++++++++++++------- libraries/shared/src/HifiConfigVariantMap.cpp | 8 ++++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 1197bf023b..d52d926816 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -266,15 +266,16 @@ void DomainServerSettingsManager::packPermissionsForMap(const QStringList& argum QHash agentPermissions, QString keyPath) { 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 QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { - QVariantMap securityMap = security->toMap(); - QVariantList userList; - securityMap[mapName] = userList; - _configMap.getUserConfig()["security"] = securityMap; - permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); + permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true); + (*permissions) = QVariantList(); } QVariantList* permissionsList = reinterpret_cast(permissions); @@ -306,12 +307,14 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); if (!standardPermissions || !standardPermissions->canConvert(QMetaType::QVariantList)) { qDebug() << "failed to extract standard permissions from settings."; - return; + standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH, true); + (*standardPermissions) = QVariantList(); } QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { qDebug() << "failed to extract permissions from settings."; - return; + permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH, true); + (*permissions) = QVariantList(); } QList standardPermissionsList = standardPermissions->toList(); @@ -348,13 +351,16 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; perms->setAll(true); _standardAgentPermissions[perms->getID()] = perms; + needPack = true; } if (!foundAnonymous) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) }; + _standardAgentPermissions[perms->getID()] = perms; needPack = true; } if (!foundLoggedIn) { NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) }; + _standardAgentPermissions[perms->getID()] = perms; needPack = true; } @@ -362,7 +368,6 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL packPermissions(argumentList); } - #ifdef WANT_DEBUG qDebug() << "--------------- permissions ---------------------"; QList> permissionsSets; diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index b3920e70bc..5ae5ff740d 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -213,10 +213,12 @@ QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool if (shouldCreateIfMissing || variantMap.contains(firstKey)) { if (dotIndex == -1) { return &variantMap[firstKey]; - } else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) { - return valueForKeyPath(*static_cast(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1), - shouldCreateIfMissing); } + if (!variantMap[firstKey].canConvert(QMetaType::QVariantMap)) { + variantMap[firstKey] = QVariantMap(); + } + return valueForKeyPath(*static_cast(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1), + shouldCreateIfMissing); } return NULL; From 0c18df62782aac62ef325ecebfbc7fc0b0aab95c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 6 Jun 2016 16:32:18 -0700 Subject: [PATCH 24/35] don't restart domain-server if the only settings changes where permissions --- domain-server/src/DomainGatekeeper.cpp | 61 ++++++++++++++++++- domain-server/src/DomainGatekeeper.h | 6 +- domain-server/src/DomainServer.cpp | 54 ++++++++++------ domain-server/src/DomainServer.h | 2 + .../src/DomainServerSettingsManager.cpp | 54 ++++++++++------ .../src/DomainServerSettingsManager.h | 15 +++-- libraries/networking/src/NodePermissions.h | 7 +++ 7 files changed, 152 insertions(+), 47 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index adc95f3c38..0abae339c6 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -123,6 +123,61 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer nodesToKill; + + auto limitedNodeList = DependencyManager::get(); + limitedNodeList->eachNodeBreakable([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){ + QString username = node->getPermissions().getUserName(); + NodePermissions userPerms(username); + + if (node->getPermissions().isAssignment) { + // this node is an assignment-client + userPerms.isAssignment = true; + userPerms.canAdjustLocks = true; + userPerms.canRezPermanentEntities = 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.getPermissionsForName(NodePermissions::standardNameAnonymous); + } else { + if (_server->_settingsManager.havePermissionsForName(username)) { + userPerms = _server->_settingsManager.getPermissionsForName(username); + } else { + userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameLoggedIn); + } + } + } + + node->setPermissions(userPerms); + + if (!userPerms.canConnectToDomain) { + qDebug() << "node" << node->getUUID() << "no longer has permission to connect."; + // hang up on this node + nodesToKill << node; + } + + return true; + }); + + foreach (auto node, nodesToKill) { + emit killNode(node); + } +} + SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection, const PendingAssignedNodeData& pendingAssignment) { @@ -166,6 +221,7 @@ 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; newNode->setPermissions(userPerms); @@ -184,7 +240,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect NodePermissions userPerms(username); userPerms.setAll(false); - // check if this user is on our local machine - if this is true they are always allowed to connect + // check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress(); bool isLocalUser = (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); @@ -209,9 +265,10 @@ 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 + userPerms.setUserName(username); if (_server->_settingsManager.havePermissionsForName(username)) { // we have specific permissions for this user. - userPerms |= _server->_settingsManager.getPermissionsForName(username); + 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. diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 352f84385e..bac50af2ec 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -51,8 +51,12 @@ public slots: void publicKeyJSONCallback(QNetworkReply& requestReply); signals: + void killNode(SharedNodePointer node); void connectedNode(SharedNodePointer node); - + +public slots: + void updateNodePermissions(); + private slots: void handlePeerPingTimeout(); private: diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 020983eb03..9abc605912 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -97,6 +97,13 @@ DomainServer::DomainServer(int argc, char* argv[]) : // make sure we hear about newly connected nodes from our gatekeeper connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); + // if a connected node loses connection privileges, hang up on it + connect(&_gatekeeper, &DomainGatekeeper::killNode, this, &DomainServer::handleKillNode); + + // if permissions are updated, relay the changes to the Node datastructures + connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, + &_gatekeeper, &DomainGatekeeper::updateNodePermissions); + if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { // we either read a certificate and private key or were not passed one // and completed login or did not need to @@ -2113,35 +2120,42 @@ void DomainServer::processPathQueryPacket(QSharedPointer messag void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer message) { // This packet has been matched to a source node and they're asking not to be in the domain anymore auto limitedNodeList = DependencyManager::get(); - + const QUuid& nodeUUID = message->getSourceID(); - + qDebug() << "Received a disconnect request from node with UUID" << nodeUUID; - + // we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode // packet to nodes that don't care about this type auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID); - + if (nodeToKill) { - auto nodeType = nodeToKill->getType(); - limitedNodeList->killNodeWithUUID(nodeUUID); - - static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID); - - removedNodePacket->reset(); - removedNodePacket->write(nodeUUID.toRfc4122()); - - // broadcast out the DomainServerRemovedNode message - limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool { - // only send the removed node packet to nodes that care about the type of node this was - auto nodeLinkedData = dynamic_cast(otherNode->getLinkedData()); - return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType); - }, [&limitedNodeList](const SharedNodePointer& otherNode){ - limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode); - }); + handleKillNode(nodeToKill); } } +void DomainServer::handleKillNode(SharedNodePointer nodeToKill) { + auto nodeType = nodeToKill->getType(); + auto limitedNodeList = DependencyManager::get(); + const QUuid& nodeUUID = nodeToKill->getUUID(); + + limitedNodeList->killNodeWithUUID(nodeUUID); + + static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID); + + removedNodePacket->reset(); + removedNodePacket->write(nodeUUID.toRfc4122()); + + // broadcast out the DomainServerRemovedNode message + limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool { + // only send the removed node packet to nodes that care about the type of node this was + auto nodeLinkedData = dynamic_cast(otherNode->getLinkedData()); + return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType); + }, [&limitedNodeList](const SharedNodePointer& otherNode){ + limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode); + }); +} + void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer message) { static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c39e405380..fb8a7d4e1a 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -111,6 +111,8 @@ private: unsigned int countConnectedUsers(); + void handleKillNode(SharedNodePointer nodeToKill); + void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr); QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index d52d926816..68e3ddcc2b 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -92,7 +92,8 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer agentPermissions, QString keyPath) { QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); @@ -285,20 +285,23 @@ void DomainServerSettingsManager::packPermissionsForMap(const QStringList& argum } } -void DomainServerSettingsManager::packPermissions(const QStringList& argumentList) { +void DomainServerSettingsManager::packPermissions() { // transfer details from _agentPermissions to _configMap - packPermissionsForMap(argumentList, "standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH); + packPermissionsForMap("standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH); // save settings for specific users - packPermissionsForMap(argumentList, "permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); + packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); persistToFile(); - _configMap.loadMasterAndUserConfig(argumentList); + _configMap.loadMasterAndUserConfig(_argumentList); } -void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentList) { +void DomainServerSettingsManager::unpackPermissions() { // transfer details from _configMap to _agentPermissions; + _standardAgentPermissions.clear(); + _agentPermissions.clear(); + bool foundLocalhost = false; bool foundAnonymous = false; bool foundLoggedIn = false; @@ -365,7 +368,7 @@ void DomainServerSettingsManager::unpackPermissions(const QStringList& argumentL } if (needPack) { - packPermissions(argumentList); + packPermissions(); } #ifdef WANT_DEBUG @@ -463,7 +466,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection qDebug() << "DomainServerSettingsManager postedObject -" << postedObject; // we recurse one level deep below each group for the appropriate setting - recurseJSONObjectAndOverwriteSettings(postedObject); + bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject); // store whatever the current _settingsMap is to file persistToFile(); @@ -473,8 +476,13 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); // defer a restart to the domain-server, this gives our HTTPConnection enough time to respond - const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000; - QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); + if (restartRequired) { + const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000; + QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); + } else { + unpackPermissions(); + emit updateNodePermissions(); + } return true; } else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) { @@ -677,9 +685,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson return QJsonObject(); } -void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { +bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { auto& settingsVariant = _configMap.getUserConfig(); - + bool needRestart = false; + // Iterate on the setting groups foreach(const QString& rootKey, postedObject.keys()) { QJsonValue rootValue = postedObject[rootKey]; @@ -727,6 +736,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); + if (rootKey != "security") { + needRestart = true; + } } else { qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting."; } @@ -740,6 +752,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { QJsonValue settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); + if (rootKey != "security") { + needRestart = true; + } } else { qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey << "- cannot update setting."; @@ -755,6 +770,7 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ // re-merge the user and master configs after a settings change _configMap.mergeMasterAndUserConfigs(); + return needRestart; } void DomainServerSettingsManager::persistToFile() { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 51ee657961..f6a5ef6f89 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -47,12 +47,18 @@ public: NodePermissions getPermissionsForName(const QString& name) const; QStringList getAllNames() { return _agentPermissions.keys(); } +signals: + void updateNodePermissions(); + + private slots: void processSettingsRequestPacket(QSharedPointer message); private: + QStringList _argumentList; + QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false); - void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject); + bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); @@ -65,10 +71,9 @@ private: friend class DomainServer; - void packPermissionsForMap(const QStringList& argumentList, QString mapName, - QHash agentPermissions, QString keyPath); - void packPermissions(const QStringList& argumentList); - void unpackPermissions(const QStringList& argumentList); + void packPermissionsForMap(QString mapName, QHash agentPermissions, QString keyPath); + void packPermissions(); + void unpackPermissions(); QHash _standardAgentPermissions; // anonymous, logged-in, localhost QHash _agentPermissions; // specific account-names }; diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index adc13ba375..c153878a7e 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -37,6 +37,12 @@ public: QString getID() const { return _id; } + // the _id member isn't authenticated and _username is. + void setUserName(QString userName) { _userName = userName; } + QString getUserName() { return _userName; } + + bool isAssignment { false }; + // these 3 names have special meaning. static QString standardNameLocalhost; static QString standardNameLoggedIn; @@ -79,6 +85,7 @@ public: protected: QString _id; + QString _userName; }; const NodePermissions DEFAULT_AGENT_PERMISSIONS; From a4be536a82ad5c8222ed784ea7928a0d79c8778f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 7 Jun 2016 12:54:01 +1200 Subject: [PATCH 25/35] Change table switches to checkboxes; vertically align table rows --- domain-server/resources/web/css/style.css | 4 ++++ domain-server/resources/web/settings/js/settings.js | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index efb9e907c5..4bf9c81b39 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -20,6 +20,10 @@ body { top: 40px; } +.table .value-row td, .table .inputs td { + vertical-align: middle; +} + .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 970e7551c4..a38d544502 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -233,7 +233,7 @@ $(document).ready(function(){ }); // Bootstrap switch in table - $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.table-checkbox', function () { + $('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () { // Bootstrap switches in table: set the changed data attribute for all rows in table. var row = $(this).closest('tr'); if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. @@ -851,7 +851,6 @@ function reloadSettings(callback) { // setup any bootstrap switches $('.toggle-checkbox').bootstrapSwitch(); - $('.table-checkbox').bootstrapSwitch(); // add tooltip to locked settings $('label.locked').tooltip({ @@ -1176,7 +1175,7 @@ function addTableRow(add_glyphicon) { var input = $(element).find("input") var isCheckbox = false; if (input.hasClass("table-checkbox")) { - input = $(input).parent().parent(); + input = $(input).parent(); isCheckbox = true; } From 0119af11d6d02201c2d55f07fdd666e05db1301e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 7 Jun 2016 13:58:13 +1200 Subject: [PATCH 26/35] Finesse security table text and general layout --- .../resources/describe-settings.json | 19 ++++++++++--------- domain-server/resources/web/css/style.css | 10 ++++++++++ .../resources/web/settings/js/settings.js | 4 ++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 26a368fd04..2314a0551c 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -99,14 +99,15 @@ { "name": "standard_permissions", "type": "table", - "label": "Domain-Wide Permissions", - "help": "Standard Permissions:", + "label": "Domain-Wide User Permissions", + "help": "Indicate which users or groups can have which domain-wide permissions.", + "caption": "Standard Permissions", "can_add_new_rows": false, "columns": [ { "name": "permissions_id", - "label": "User/Group" + "label": "User / Group" }, { "name": "id_can_connect", @@ -117,7 +118,7 @@ }, { "name": "id_can_adjust_locks", - "label": "Lock/Unlock", + "label": "Lock / Unlock", "type": "checkbox", "editable": true, "default": false @@ -131,7 +132,7 @@ }, { "name": "id_can_rez_tmp", - "label": "Rez Temp", + "label": "Rez Temporary", "type": "checkbox", "editable": true, "default": false @@ -158,13 +159,13 @@ { "name": "permissions", "type": "table", - "help": "Permissions for Specific Users:", + "caption": "Permissions for Specific Users", "can_add_new_rows": true, "columns": [ { "name": "permissions_id", - "label": "User/Group" + "label": "User / Group" }, { "name": "id_can_connect", @@ -175,7 +176,7 @@ }, { "name": "id_can_adjust_locks", - "label": "Lock/Unlock", + "label": "Lock / Unlock", "type": "checkbox", "editable": true, "default": false @@ -189,7 +190,7 @@ }, { "name": "id_can_rez_tmp", - "label": "Rez Temp", + "label": "Rez Temporary", "type": "checkbox", "editable": true, "default": false diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 4bf9c81b39..35cb6ad492 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -111,6 +111,16 @@ table { word-wrap: break-word; } +caption { + color: #333; + font-weight: 700; + padding-top: 0; +} + +table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td { + text-align: center; +} + #xs-advanced-container { margin-bottom: 20px; } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index a38d544502..71abae76f8 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -927,6 +927,10 @@ function makeTable(setting, keypath, setting_value, isLocked) { + "' name='" + keypath + "' id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath) + "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"; + if (setting.caption) { + html += "" + setting.caption + "" + } + // Column names html += "" From f2d81f1338ab7634f16e86ff57f7246ee4e3e87a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 7 Jun 2016 16:42:23 +1200 Subject: [PATCH 27/35] Add security table heading groups --- .../resources/describe-settings.json | 26 +++++++++++++++++-- .../resources/web/settings/js/settings.js | 16 ++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 2314a0551c..397c7fe6d0 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -104,10 +104,21 @@ "caption": "Standard Permissions", "can_add_new_rows": false, + "groups": [ + { + "label": "User / Group", + "span": 1 + }, + { + "label": "Permissions", + "span": 6 + } + ], + "columns": [ { "name": "permissions_id", - "label": "User / Group" + "label": "" }, { "name": "id_can_connect", @@ -162,10 +173,21 @@ "caption": "Permissions for Specific Users", "can_add_new_rows": true, + "groups": [ + { + "label": "User / Group", + "span": 1 + }, + { + "label": "Permissions", + "span": 6 + } + ], + "columns": [ { "name": "permissions_id", - "label": "User / Group" + "label": "" }, { "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 71abae76f8..97da975748 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -931,6 +931,22 @@ function makeTable(setting, keypath, setting_value, isLocked) { html += "" + setting.caption + "" } + // Column groups + if (setting.groups) { + html += "" + _.each(setting.groups, function (group) { + html += "" + group.label + "" + }) + if (!isLocked && !setting.read_only) { + if (setting.can_order) { + html += ""; + } + html += "" + } + html += "" + } + // Column names html += "" From 801981816bd4fa9eafad10ab4c3bffe6ec242039 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 8 Jun 2016 09:11:37 +1200 Subject: [PATCH 28/35] Add permissions tool-tips --- domain-server/resources/describe-settings.json | 14 +++++++------- domain-server/resources/web/css/style.css | 5 +++++ .../resources/web/settings/js/settings.js | 2 ++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 397c7fe6d0..43eb5c7ee9 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -100,18 +100,18 @@ "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, "groups": [ { "label": "User / Group", - "span": 1 + "span": 1 }, { - "label": "Permissions", - "span": 6 + "label": "Permissions ?", + "span": 6 } ], @@ -176,11 +176,11 @@ "groups": [ { "label": "User / Group", - "span": 1 + "span": 1 }, { - "label": "Permissions", - "span": 6 + "label": "Permissions ?", + "span": 6 } ], diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 35cb6ad492..21cf753bfd 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -121,6 +121,11 @@ table[name="security.standard_permissions"] .headers td + td, table[name="securi text-align: center; } +#security .tooltip-inner { + max-width: 520px; + text-align: left; +} + #xs-advanced-container { margin-bottom: 20px; } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 97da975748..9188772ec9 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -852,6 +852,8 @@ function reloadSettings(callback) { // setup any bootstrap switches $('.toggle-checkbox').bootstrapSwitch(); + $('[data-toggle="tooltip"]').tooltip(); + // add tooltip to locked settings $('label.locked').tooltip({ placement: 'right', From f3f04fba9754abfd647e265cb464df4c6b06e13f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 8 Jun 2016 09:41:31 +1200 Subject: [PATCH 29/35] Fix IE displaying checkboxes too large --- domain-server/resources/web/css/style.css | 7 +++++++ domain-server/resources/web/settings/js/settings.js | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 21cf753bfd..4c419a1baa 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -24,6 +24,13 @@ body { vertical-align: middle; } +.table .table-checkbox { + /* Fix IE sizing checkboxes to fill table cell */ + width: auto; + margin-left: auto; + margin-right: auto; +} + .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 9188772ec9..aecc48b31f 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1005,7 +1005,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { if (isArray && col.type === "checkbox" && col.editable) { html += "" - + ""; } else { // Use a hidden input so that the values are posted. @@ -1060,7 +1060,7 @@ function makeTableInputs(setting) { _.each(setting.columns, function(col) { if (col.type === "checkbox") { html += "" - + ""; } else { html += "\ From b2f63a2132fc0767fce87738cf62e475132da60b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 7 Jun 2016 16:17:58 -0700 Subject: [PATCH 30/35] hook up can-rez-tmp and can-write-to-asset-server --- assignment-client/src/assets/AssetServer.cpp | 8 ++-- domain-server/src/DomainGatekeeper.cpp | 2 + .../src/DomainServerSettingsManager.cpp | 2 + interface/src/Application.cpp | 4 +- interface/src/Menu.cpp | 4 +- .../entities/src/EntityScriptingInterface.cpp | 6 +++ .../entities/src/EntityScriptingInterface.h | 2 + libraries/entities/src/EntityTree.cpp | 45 ++++++++++++++++--- libraries/networking/src/LimitedNodeList.cpp | 21 ++++----- libraries/networking/src/LimitedNodeList.h | 4 ++ libraries/networking/src/Node.h | 2 + 11 files changed, 74 insertions(+), 26 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 1fb0674e7d..7f43b86328 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -235,7 +235,7 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN } void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanRez()) { + if (senderNode->getCanWriteToAssetServer()) { QString assetPath = message.readString(); auto assetHash = message.read(SHA256_HASH_LENGTH).toHex(); @@ -251,7 +251,7 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode } void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanRez()) { + if (senderNode->getCanWriteToAssetServer()) { int numberOfDeletedMappings { 0 }; message.readPrimitive(&numberOfDeletedMappings); @@ -272,7 +272,7 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared } void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanRez()) { + if (senderNode->getCanWriteToAssetServer()) { QString oldPath = message.readString(); QString newPath = message.readString(); @@ -337,7 +337,7 @@ void AssetServer::handleAssetGet(QSharedPointer message, Shared void AssetServer::handleAssetUpload(QSharedPointer message, SharedNodePointer senderNode) { - if (senderNode->getCanRez()) { + if (senderNode->getCanWriteToAssetServer()) { qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); auto task = new UploadAssetTask(message, senderNode, _filesDirectory); diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 0abae339c6..af449eadec 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -140,6 +140,7 @@ void DomainGatekeeper::updateNodePermissions() { userPerms.isAssignment = true; userPerms.canAdjustLocks = true; userPerms.canRezPermanentEntities = true; + userPerms.canRezTemporaryEntities = true; } else { // this node is an agent userPerms.setAll(false); @@ -224,6 +225,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo userPerms.isAssignment = true; userPerms.canAdjustLocks = true; userPerms.canRezPermanentEntities = true; + userPerms.canRezTemporaryEntities = true; newNode->setPermissions(userPerms); return newNode; } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 68e3ddcc2b..64fce73191 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -244,8 +244,10 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList foreach (QString userName, permissionsSet.keys()) { if (onlyEditorsAreRezzers) { permissionsSet[userName]->canRezPermanentEntities = permissionsSet[userName]->canAdjustLocks; + permissionsSet[userName]->canRezTemporaryEntities = permissionsSet[userName]->canAdjustLocks; } else { permissionsSet[userName]->canRezPermanentEntities = true; + permissionsSet[userName]->canRezTemporaryEntities = true; } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e789b7c508..e102c13122 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4279,7 +4279,7 @@ void Application::nodeActivated(SharedNodePointer node) { if (assetDialog) { auto nodeList = DependencyManager::get(); - if (nodeList->getThisNodeCanRez()) { + if (nodeList->getThisNodeCanWriteAssets()) { // call reload on the shown asset browser dialog to get the mappings (if permissions allow) QMetaObject::invokeMethod(assetDialog, "reload"); } else { @@ -4786,7 +4786,7 @@ void Application::toggleRunningScriptsWidget() const { } void Application::toggleAssetServerWidget(QString filePath) { - if (!DependencyManager::get()->getThisNodeCanRez()) { + if (!DependencyManager::get()->getThisNodeCanWriteAssets()) { return; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a21aa71753..7b8f835672 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -136,8 +136,8 @@ Menu::Menu() { Qt::CTRL | Qt::SHIFT | Qt::Key_A, qApp, SLOT(toggleAssetServerWidget())); auto nodeList = DependencyManager::get(); - QObject::connect(nodeList.data(), &NodeList::canRezChanged, assetServerAction, &QAction::setEnabled); - assetServerAction->setEnabled(nodeList->getThisNodeCanRez()); + QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled); + assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets()); // Edit > Package Model... [advanced] addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index d09fc60d9b..e0863041a1 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -32,6 +32,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership auto nodeList = DependencyManager::get(); connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged); connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged); + connect(nodeList.data(), &NodeList::canRezTmpChanged, this, &EntityScriptingInterface::canRezTmpChanged); } void EntityScriptingInterface::queueEntityMessage(PacketType packetType, @@ -49,6 +50,11 @@ bool EntityScriptingInterface::canRez() { return nodeList->getThisNodeCanRez(); } +bool EntityScriptingInterface::canRezTmp() { + auto nodeList = DependencyManager::get(); + return nodeList->getThisNodeCanRezTmp(); +} + void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 8ae6a77dab..e9024eb721 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -80,6 +80,7 @@ public slots: // returns true if the DomainServer will allow this Node/Avatar to rez new entities Q_INVOKABLE bool canRez(); + Q_INVOKABLE bool canRezTmp(); /// adds a model with the specific properties Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false); @@ -179,6 +180,7 @@ signals: void canAdjustLocksChanged(bool canAdjustLocks); void canRezChanged(bool canRez); + void canRezTmpChanged(bool canRez); void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 581e0a9568..911ebd88b3 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -26,6 +26,8 @@ #include "LogHandler.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; +static const float MAX_TMP_ENTITY_LIFETIME = 10 * 60; // 10 minutes + EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), @@ -128,13 +130,16 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI EntityItemProperties properties = origProperties; bool allowLockChange; + bool canRezPermanentEntities; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get(); allowLockChange = nodeList->isAllowedEditor(); + canRezPermanentEntities = nodeList->getThisNodeCanRez(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->isAllowedEditor(); + canRezPermanentEntities = senderNode->getCanRez(); senderID = senderNode->getUUID(); } @@ -143,6 +148,12 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return false; } + if (!canRezPermanentEntities && (entity->getLifetime() != properties.getLifetime())) { + // we don't allow a Node that can't create permanent entities to adjust lifetimes on existing ones + qCDebug(entities) << "Refusing disallowed entity lifetime adjustment."; + return false; + } + // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { @@ -308,17 +319,39 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return true; } +bool permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) { + float lifeTime = properties.getLifetime(); + + if (lifeTime == 0.0f || lifeTime > MAX_TMP_ENTITY_LIFETIME) { + // this is an attempt to rez a permanent entity. + if (!canRez) { + return false; + } + } else { + // this is an attempt to rez a temporary entity. + if (!canRezTmp) { + return false; + } + } + + return true; +} + EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer result = NULL; + auto nodeList = DependencyManager::get(); + if (!nodeList) { + qDebug() << "EntityTree::addEntity -- can't get NodeList"; + return nullptr; + } + bool clientOnly = properties.getClientOnly(); - if (!clientOnly && getIsClient()) { + if (!clientOnly && getIsClient() && + !permissionsAllowRez(properties, nodeList->getThisNodeCanRez(), nodeList->getThisNodeCanRezTmp())) { // if our Node isn't allowed to create entities in this domain, don't try. - auto nodeList = DependencyManager::get(); - if (nodeList && !nodeList->getThisNodeCanRez()) { - return NULL; - } + return nullptr; } bool recordCreationTime = false; @@ -920,7 +953,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endUpdate = usecTimestampNow(); _totalUpdates++; } else if (message.getType() == PacketType::EntityAdd) { - if (senderNode->getCanRez()) { + if (permissionsAllowRez(properties, senderNode->getCanRez(), senderNode->getCanRezTmp())) { // this is a new entity... assign a new entityID properties.setCreated(properties.getLastEdited()); startCreate = usecTimestampNow(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index a4daff5d35..d7a2d47fab 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -130,26 +130,23 @@ void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { } } - void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) { - bool emitIsAllowedEditorChanged { false }; - bool emitCanRezChanged { false }; - - if (_permissions.canAdjustLocks != newPermissions.canAdjustLocks) { - emitIsAllowedEditorChanged = true; - } - if (_permissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) { - emitCanRezChanged = true; - } + NodePermissions originalPermissions = _permissions; _permissions = newPermissions; - if (emitIsAllowedEditorChanged) { + if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) { emit isAllowedEditorChanged(_permissions.canAdjustLocks); } - if (emitCanRezChanged) { + if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) { emit canRezChanged(_permissions.canRezPermanentEntities); } + if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) { + emit canRezTmpChanged(_permissions.canRezTemporaryEntities); + } + if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) { + emit canWriteAssetsChanged(_permissions.canWriteToAssetServer); + } } QUdpSocket& LimitedNodeList::getDTLSSocket() { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index e4c3ae9dec..483aa0734c 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -107,6 +107,8 @@ public: 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; } quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } QUdpSocket& getDTLSSocket(); @@ -252,6 +254,8 @@ signals: void isAllowedEditorChanged(bool isAllowedEditor); void canRezChanged(bool canRez); + void canRezTmpChanged(bool canRezTmp); + void canWriteAssetsChanged(bool canWriteAssets); protected slots: void connectedForLocalSocketTest(); diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 367e17d345..b277ac0083 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -63,6 +63,8 @@ public: 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; } friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); From 7696ead0126c9232a0e388c61fb13fe72352c8c4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 7 Jun 2016 16:38:52 -0700 Subject: [PATCH 31/35] look for standard permissions in standard table rather than in user table --- domain-server/src/DomainGatekeeper.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index af449eadec..6377a37f34 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -153,12 +153,12 @@ void DomainGatekeeper::updateNodePermissions() { } if (username.isEmpty()) { - userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); } else { if (_server->_settingsManager.havePermissionsForName(username)) { userPerms = _server->_settingsManager.getPermissionsForName(username); } else { - userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameLoggedIn); + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); } } } @@ -263,7 +263,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (username.isEmpty()) { // they didn't tell us who they are - userPerms |= _server->_settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); 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 @@ -274,7 +274,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect 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.getPermissionsForName(NodePermissions::standardNameLoggedIn); + userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); qDebug() << "user-permissions: user is logged in, so:" << userPerms; } } else { From 8085266c1d6e4b4020e270351d3ce08745ed47a9 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 8 Jun 2016 10:36:44 -0700 Subject: [PATCH 32/35] cause names in permissions tables to be alpha-sorted --- .../src/DomainServerSettingsManager.cpp | 37 ++++++++++++++++++- .../src/DomainServerSettingsManager.h | 1 + 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 64fce73191..fe569d54b9 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include #include @@ -498,7 +500,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object(); - connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); } @@ -674,6 +675,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV // TODO: we still need to recurse here with the description in case values in the array have special types settingMap[key] = newValue.toArray().toVariantList(); } + + sortPermissions(); } QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) { @@ -772,10 +775,42 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ // re-merge the user and master configs after a settings change _configMap.mergeMasterAndUserConfigs(); + return needRestart; } +// Compare two members of a permissions list +bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) { + if (!v1.canConvert(QMetaType::QVariantMap) || + !v2.canConvert(QMetaType::QVariantMap)) { + return v1.toString() < v2.toString(); + } + QVariantMap m1 = v1.toMap(); + QVariantMap m2 = v2.toMap(); + + if (!m1.contains("permissions_id") || + !m2.contains("permissions_id")) { + return v1.toString() < v2.toString(); + } + return m1["permissions_id"].toString() < m2["permissions_id"].toString(); +} + +void DomainServerSettingsManager::sortPermissions() { + // sort the permission-names + QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); + if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) { + QList* standardPermissionsList = reinterpret_cast(standardPermissions); + std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan); + } + QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + if (permissions && permissions->canConvert(QMetaType::QVariantList)) { + QList* permissionsList = reinterpret_cast(permissions); + std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); + } +} + void DomainServerSettingsManager::persistToFile() { + sortPermissions(); // make sure we have the dir the settings file is supposed to live in QFileInfo settingsFileInfo(_configMap.getUserConfigFilename()); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index f6a5ef6f89..446e9a2eed 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -63,6 +63,7 @@ private: void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName); + void sortPermissions(); void persistToFile(); double _descriptionVersion; From bade215907c4f01e5334e8c885cd245c94f43660 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 9 Jun 2016 10:41:30 -0700 Subject: [PATCH 33/35] maximum 'tmp' entity lifetime is now a domain-server setting, defaults to 1 hour --- assignment-client/src/entities/EntityServer.cpp | 8 ++++++++ domain-server/resources/describe-settings.json | 8 ++++++++ libraries/entities/src/EntityTree.cpp | 6 +++--- libraries/entities/src/EntityTree.h | 8 ++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 0555f95c65..7594d5dd2c 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -268,6 +268,14 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio qDebug("wantTerseEditLogging=%s", debug::valueOf(wantTerseEditLogging)); EntityTreePointer tree = std::static_pointer_cast(_tree); + + int maxTmpEntityLifetime; + if (readOptionInt("maxTmpLifetime", settingsSectionObject, maxTmpEntityLifetime)) { + tree->setEntityMaxTmpLifetime(maxTmpEntityLifetime); + } else { + tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME); + } + tree->setWantEditLogging(wantEditLogging); tree->setWantTerseEditLogging(wantTerseEditLogging); } diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 43eb5c7ee9..6d15cf75ed 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -513,6 +513,14 @@ "label": "Entity Server Settings", "assignment-types": [6], "settings": [ + { + "name": "maxTmpLifetime", + "label": "Maximum Lifetime of Temporary Entities", + "help": "The maximum number of seconds for the lifetime of an entity which will be considered \"temporary\".", + "placeholder": "3600", + "default": "3600", + "advanced": true + }, { "name": "persistFilePath", "label": "Entities File Path", diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 911ebd88b3..5892ac0e54 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -26,7 +26,7 @@ #include "LogHandler.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; -static const float MAX_TMP_ENTITY_LIFETIME = 10 * 60; // 10 minutes +const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour EntityTree::EntityTree(bool shouldReaverage) : @@ -319,10 +319,10 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return true; } -bool permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) { +bool EntityTree::permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) { float lifeTime = properties.getLifetime(); - if (lifeTime == 0.0f || lifeTime > MAX_TMP_ENTITY_LIFETIME) { + if (lifeTime == 0.0f || lifeTime > _maxTmpEntityLifetime) { // this is an attempt to rez a permanent entity. if (!canRez) { return false; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index a85624c9ae..8afb8d878f 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -62,6 +62,10 @@ public: void createRootElement(); + + void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; } + bool permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp); + /// Implements our type specific root element factory virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override; @@ -252,6 +256,8 @@ public: void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID); + static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; + public slots: void callLoader(EntityItemID entityID); @@ -331,6 +337,8 @@ protected: // we maintain a list of avatarIDs to notice when an entity is a child of one. QSet _avatarIDs; // IDs of avatars connected to entity server QHash> _childrenOfAvatars; // which entities are children of which avatars + + float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME }; }; #endif // hifi_EntityTree_h From 786c74d7f94834ea5b6fcb4fdacec2bc3c8d7001 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Jun 2016 08:57:52 +1200 Subject: [PATCH 34/35] Vertically center table headings, reduce font size in subheadings --- domain-server/resources/web/css/style.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 4c419a1baa..177fa42fef 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -124,6 +124,15 @@ caption { padding-top: 0; } +table > tbody > .headers > td { + vertical-align: middle; +} + +table .headers + .headers td { + font-size: 13px; + color: #222; +} + table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td { text-align: center; } From d54247da8d2f8a28cfd05b6a8b30f70e4a1e186c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 10 Jun 2016 10:13:54 +1200 Subject: [PATCH 35/35] Format hover popups --- .../resources/describe-settings.json | 6 ++-- domain-server/resources/web/css/style.css | 30 ++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 1c55ea3859..bad24dd3a1 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 } ], diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 177fa42fef..2862feed87 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -137,9 +137,37 @@ table[name="security.standard_permissions"] .headers td + td, table[name="securi text-align: center; } +.tooltip.top .tooltip-arrow { + border-top-color: #fff; + border-width: 10px 10px 0; + margin-bottom: -5px; +} + +.tooltip-inner { + padding: 20px 20px 10px 20px; + font-size: 14px; + text-align: left; + color: #333; + background-color: #fff; + box-shadow: 0 3px 8px 8px #e8e8e8; +} + +.tooltip.in { + opacity: 1; +} + +.tooltip-inner ul { + padding-left: 0; + margin-bottom: 15px; +} + +.tooltip-inner li { + list-style-type: none; + margin-bottom: 5px; +} + #security .tooltip-inner { max-width: 520px; - text-align: left; } #xs-advanced-container {