diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json
index 427dc62520..83dd633d22 100644
--- a/domain-server/resources/describe-settings.json
+++ b/domain-server/resources/describe-settings.json
@@ -306,7 +306,37 @@
}
],
"non-deletable-row-key": "permissions_id",
- "non-deletable-row-values": [ "localhost", "anonymous", "logged-in" ]
+ "non-deletable-row-values": [ "localhost", "anonymous", "logged-in" ],
+ "default": [
+ {
+ "id_can_connect": true,
+ "id_can_rez_tmp_certified": true,
+ "permissions_id": "anonymous"
+ },
+ {
+ "id_can_connect": true,
+ "id_can_rez_tmp_certified": true,
+ "permissions_id": "friends"
+ },
+ {
+ "id_can_adjust_locks": true,
+ "id_can_connect": true,
+ "id_can_connect_past_max_capacity": true,
+ "id_can_kick": true,
+ "id_can_replace_content": true,
+ "id_can_rez": true,
+ "id_can_rez_certified": true,
+ "id_can_rez_tmp": true,
+ "id_can_rez_tmp_certified": true,
+ "id_can_write_to_asset_server": true,
+ "permissions_id": "localhost"
+ },
+ {
+ "id_can_connect": true,
+ "id_can_rez_tmp_certified": true,
+ "permissions_id": "logged-in"
+ }
+ ]
},
{
"name": "group_permissions",
diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js
index 1e5b6ac131..525b989259 100644
--- a/domain-server/resources/web/content/js/content.js
+++ b/domain-server/resources/web/content/js/content.js
@@ -2,10 +2,19 @@ $(document).ready(function(){
var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button';
var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file';
+ var UPLOAD_CONTENT_ALLOWED_DIV_ID = 'upload-content-allowed';
+ var UPLOAD_CONTENT_RECOVERING_DIV_ID = 'upload-content-recovering';
+
+ function progressBarHTML(extraClass, label) {
+ var html = "
";
+ html += "
";
+ return html;
+ }
function setupBackupUpload() {
// construct the HTML needed for the settings backup panel
- var html = "";
$('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(html);
}
@@ -66,25 +78,30 @@ $(document).ready(function(){
});
var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button';
+ var CONTENT_ARCHIVES_NORMAL_ID = 'content-archives-success';
+ var CONTENT_ARCHIVES_ERROR_ID = 'content-archives-error';
var AUTOMATIC_ARCHIVES_TABLE_ID = 'automatic-archives-table';
var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody';
var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table';
var MANUAL_ARCHIVES_TBODY_ID = 'manual-archives-tbody';
var AUTO_ARCHIVES_SETTINGS_LINK_ID = 'auto-archives-settings-link';
+ var ACTION_MENU_CLASS = 'action-menu';
var automaticBackups = [];
var manualBackups = [];
function setupContentArchives() {
// construct the HTML needed for the content archives panel
- var html = "";
+
+ html += ""
+ + "There was a problem loading your list of automatic and manual content archives. "
+ + "Please reload the page to try again.
";
// put the base HTML in the content archives panel
$('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html(html);
@@ -104,8 +125,9 @@ $(document).ready(function(){
var BACKUP_RESTORE_LINK_CLASS = 'restore-backup';
var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup';
var BACKUP_DELETE_LINK_CLASS = 'delete-backup';
+ var ACTIVE_BACKUP_ROW_CLASS = 'active-backup';
- function reloadLatestBackups() {
+ function reloadBackupInformation() {
// make a GET request to get backup information to populate the table
$.ajax({
url: '/api/backups',
@@ -123,41 +145,92 @@ $(document).ready(function(){
// populate the backups tables with the backups
function createBackupTableRow(backup) {
return ""
- + "" + backup.name + " | "
+ + " | " + backup.name + " | "
+ moment(backup.createdAtMillis).format('lll')
- + " | "
+ + " | | ";
+ + "Download"
+ + "Delete";
+ }
+
+ function updateProgressBars($progressBar, value) {
+ $progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%');
+ $progressBar.find('.sr-only').html(data.status.recoveryProgress + "% Complete");
+ }
+
+ // before we add any new rows and update existing ones
+ // remove our flag for active rows
+ $('.' + ACTIVE_BACKUP_ROW_CLASS).removeClass(ACTIVE_BACKUP_ROW_CLASS);
+
+ function updateOrAddTableRow(backup, tableBodyID) {
+ // check for a backup with this ID
+ var $backupRow = $("tr[data-backup-id='" + backup.id + "']");
+
+ if ($backupRow.length == 0) {
+ // create a new row and then add it to the table
+ $backupRow = $(createBackupTableRow(backup));
+ $('#' + tableBodyID).append($backupRow);
+ }
+
+ // update the row status column depending on if it is available or recovering
+ if (!backup.isAvailable) {
+ // add a progress bar to the status row for availability
+ $backupRow.find('td.backup-status').html(progressBarHTML('availability', 'Archiving'));
+
+ // set the value of the progress bar based on availability progress
+ updateProgressBars($backupRow.find('.progress-bar'), backup.availabilityProgress * 100);
+ } else if (backup.id == data.status.recoveringBackupId) {
+ // add a progress bar to the status row for recovery
+ $backupRow.find('td.backup-status').html(progressBarHTML('recovery', 'Restoring'));
+ } else {
+ // no special status for this row, use an empty status column
+ $backupRow.find('td.backup-status').html('');
+ }
+
+ $backupRow.find('td.' + ACTION_MENU_CLASS + ' .dropdown').toggle(backup.isAvailable);
+
+ $backupRow.addClass(ACTIVE_BACKUP_ROW_CLASS);
}
var automaticRows = "";
if (automaticBackups.length > 0) {
for (var backupIndex in automaticBackups) {
- // create a table row for this backup and add it to the rows we'll put in the table body
- automaticRows += createBackupTableRow(automaticBackups[backupIndex]);
+ updateOrAddTableRow(automaticBackups[backupIndex], AUTOMATIC_ARCHIVES_TBODY_ID);
+
}
}
- $('#' + AUTOMATIC_ARCHIVES_TBODY_ID).html(automaticRows);
-
- var manualRows = "";
-
if (manualBackups.length > 0) {
for (var backupIndex in manualBackups) {
- // create a table row for this backup and add it to the rows we'll put in the table body
- manualRows += createBackupTableRow(manualBackups[backupIndex]);
+ updateOrAddTableRow(manualBackups[backupIndex], MANUAL_ARCHIVES_TBODY_ID);
}
}
- $('#' + MANUAL_ARCHIVES_TBODY_ID).html(manualRows);
+ // at this point, any rows that no longer have the ACTIVE_BACKUP_ROW_CLASS
+ // are deleted backups, so we remove them from the table
+ $('#' + CONTENT_ARCHIVES_NORMAL_ID + ' tbody tr:not(.' + ACTIVE_BACKUP_ROW_CLASS + ')').remove();
+
+ // check if the restore action on all rows should be enabled or disabled
+ $('.' + BACKUP_RESTORE_LINK_CLASS).parent().toggleClass('disabled', data.status.isRecovering);
+
+ // hide or show the manual content upload file and button depending on our recovering status
+ $('#' + UPLOAD_CONTENT_ALLOWED_DIV_ID).toggle(!data.status.isRecovering);
+ $('#' + UPLOAD_CONTENT_RECOVERING_DIV_ID).toggle(data.status.isRecovering);
+
+ // update the progress bars for current restore status
+ if (data.status.isRecovering) {
+ updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress * 100);
+ }
// tell bootstrap sortable to update for the new rows
$.bootstrapSortable({ applyLast: true });
+ $('#' + CONTENT_ARCHIVES_NORMAL_ID).toggle(true);
+ $('#' + CONTENT_ARCHIVES_ERROR_ID).toggle(false);
+
}).fail(function(){
// we've hit the very rare case where we couldn't load the list of backups from the domain server
@@ -167,11 +240,8 @@ $(document).ready(function(){
// replace the content archives panel with a simple error message
// stating that the user should reload the page
- $('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html(
- "" +
- "There was a problem loading your list of automatic and manual content archives. Please reload the page to try again." +
- "
"
- );
+ $('#' + CONTENT_ARCHIVES_NORMAL_ID).toggle(false);
+ $('#' + CONTENT_ARCHIVES_ERROR_ID).toggle(true);
}).always(function(){
// toggle showing or hiding the tables depending on if they have entries
@@ -197,12 +267,11 @@ $(document).ready(function(){
"Restore content",
function() {
// show a spinner while we send off our request
- showSpinnerAlert("Restoring Content Archive " + backupName);
+ showSpinnerAlert("Starting restore of " + backupName);
// setup an AJAX POST to request content restore
$.post('/api/backups/recover/' + backupID).done(function(data, textStatus, jqXHR) {
swal.close();
- showRestartModal();
}).fail(function(jqXHR, textStatus, errorThrown) {
showErrorMessage(
"Error",
@@ -247,7 +316,7 @@ $(document).ready(function(){
}).always(function(){
// reload the list of content archives in case we deleted a backup
// or it's no longer an available backup for some other reason
- reloadLatestBackups();
+ reloadBackupInformation();
});
}
)
@@ -306,7 +375,7 @@ $(document).ready(function(){
}).done(function(data) {
// since we successfully setup a new content archive, reload the table of archives
// which should show that this archive is pending creation
- reloadLatestBackups();
+ reloadBackupInformation();
}).fail(function(jqXHR, textStatus, errorThrown) {
});
@@ -322,6 +391,9 @@ $(document).ready(function(){
setupContentArchives();
// load the latest backups immediately
- reloadLatestBackups();
+ reloadBackupInformation();
+
+ // setup a timer to reload them every 5 seconds
+ setInterval(reloadBackupInformation, 5000);
};
});
diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css
index 2bcc870ecf..62f442584e 100644
--- a/domain-server/resources/web/css/style.css
+++ b/domain-server/resources/web/css/style.css
@@ -466,6 +466,11 @@ tr.gray-tr {
background-color: #f5f5f5;
}
+table .action-menu {
+ text-align: right;
+ width: 90px;
+}
+
.dropdown-toggle span.glyphicon-option-vertical {
font-size: 110%;
cursor: pointer;
diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js
index 3476792222..bafe5f96cf 100644
--- a/domain-server/resources/web/js/base-settings.js
+++ b/domain-server/resources/web/js/base-settings.js
@@ -263,7 +263,7 @@ $(document).ready(function(){
}
});
- $('#' + Settings.FORM_ID).on('change keyup paste', '.' + Settings.TRIGGER_CHANGE_CLASS , function(e){
+ $('#' + Settings.FORM_ID).on('change input propertychange', '.' + Settings.TRIGGER_CHANGE_CLASS , function(e){
// this input was changed, add the changed data attribute to it
$(this).attr('data-changed', true);
@@ -838,7 +838,7 @@ function addTableRow(row) {
var keyInput = row.children(".key").children("input");
// whenever the keyInput changes, re-badge for differences
- keyInput.on('change keyup paste', function(e){
+ keyInput.on('change input propertychange', function(e){
// update siblings in the row to have the correct name
var currentKey = $(this).val();
diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp
index 379aa640f8..0bef6bb891 100644
--- a/domain-server/src/DomainContentBackupManager.cpp
+++ b/domain-server/src/DomainContentBackupManager.cpp
@@ -57,6 +57,8 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire
_persistInterval(persistInterval),
_lastCheck(usecTimestampNow())
{
+ setObjectName("DomainContentBackupManager");
+
// Make sure the backup directory exists.
QDir(_backupDirectory).mkpath(".");
@@ -309,6 +311,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
}
void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup) {
+
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "recoverFromUploadedBackup", Q_ARG(MiniPromise::Promise, promise),
Q_ARG(QByteArray, uploadedBackup));
diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp
index 3aab7b4563..ac94d953d5 100644
--- a/domain-server/src/DomainGatekeeper.cpp
+++ b/domain-server/src/DomainGatekeeper.cpp
@@ -435,10 +435,11 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) {
// we can't allow this user to connect because we are at max capacity
QString redirectOnMaxCapacity;
- const QVariant* redirectOnMaxCapacityVariant =
- valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION);
- if (redirectOnMaxCapacityVariant && redirectOnMaxCapacityVariant->canConvert()) {
- redirectOnMaxCapacity = redirectOnMaxCapacityVariant->toString();
+
+ QVariant redirectOnMaxCapacityVariant =
+ _server->_settingsManager.valueForKeyPath(MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION);
+ if (redirectOnMaxCapacityVariant.canConvert()) {
+ redirectOnMaxCapacity = redirectOnMaxCapacityVariant.toString();
qDebug() << "Redirection domain:" << redirectOnMaxCapacity;
}
@@ -610,9 +611,9 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
bool DomainGatekeeper::isWithinMaxCapacity() {
// find out what our maximum capacity is
- const QVariant* maximumUserCapacityVariant =
- valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
- unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
+ QVariant maximumUserCapacityVariant =
+ _server->_settingsManager.valueForKeyPath(MAXIMUM_USER_CAPACITY);
+ unsigned int maximumUserCapacity = !maximumUserCapacityVariant.isValid() ? maximumUserCapacityVariant.toUInt() : 0;
if (maximumUserCapacity > 0) {
unsigned int connectedUsers = _server->countConnectedUsers();
diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp
index eee5673af3..24d55d74b6 100644
--- a/domain-server/src/DomainMetadata.cpp
+++ b/domain-server/src/DomainMetadata.cpp
@@ -84,21 +84,22 @@ void DomainMetadata::descriptorsChanged() {
// get descriptors
assert(_metadata[DESCRIPTORS].canConvert());
auto& state = *static_cast(_metadata[DESCRIPTORS].data());
- auto& settings = static_cast(parent())->_settingsManager.getSettingsMap();
- auto& descriptors = static_cast(parent())->_settingsManager.getDescriptorsMap();
+
+ static const QString DESCRIPTORS_GROUP_KEYPATH = "descriptors";
+ auto descriptorsMap = static_cast(parent())->_settingsManager.valueForKeyPath(DESCRIPTORS).toMap();
// copy simple descriptors (description/maturity)
- state[Descriptors::DESCRIPTION] = descriptors[Descriptors::DESCRIPTION];
- state[Descriptors::MATURITY] = descriptors[Descriptors::MATURITY];
+ state[Descriptors::DESCRIPTION] = descriptorsMap[Descriptors::DESCRIPTION];
+ state[Descriptors::MATURITY] = descriptorsMap[Descriptors::MATURITY];
// copy array descriptors (hosts/tags)
- state[Descriptors::HOSTS] = descriptors[Descriptors::HOSTS].toList();
- state[Descriptors::TAGS] = descriptors[Descriptors::TAGS].toList();
+ state[Descriptors::HOSTS] = descriptorsMap[Descriptors::HOSTS].toList();
+ state[Descriptors::TAGS] = descriptorsMap[Descriptors::TAGS].toList();
// parse capacity
static const QString CAPACITY = "security.maximum_user_capacity";
- const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY);
- unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0;
+ QVariant capacityVariant = static_cast(parent())->_settingsManager.valueForKeyPath(CAPACITY);
+ unsigned int capacity = capacityVariant.isValid() ? capacityVariant.toUInt() : 0;
state[Descriptors::CAPACITY] = capacity;
#if DEV_BUILD || PR_BUILD
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 7cd6cd34fe..9cecea5f70 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -75,8 +75,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
std::initializer_list optionalData,
bool requireAccessToken) {
- auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
- if (accessTokenVariant == nullptr && requireAccessToken) {
+ auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);
+ if (!accessTokenVariant.isValid() && requireAccessToken) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
return true;
}
@@ -112,8 +112,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- if (accessTokenVariant != nullptr) {
- auto accessTokenHeader = QString("Bearer ") + accessTokenVariant->toString();
+ if (accessTokenVariant.isValid()) {
+ auto accessTokenHeader = QString("Bearer ") + accessTokenVariant.toString();
req.setRawHeader("Authorization", accessTokenHeader.toLatin1());
}
@@ -380,6 +380,11 @@ void DomainServer::parseCommandLine() {
DomainServer::~DomainServer() {
qInfo() << "Domain Server is shutting down.";
+ if (_contentManager) {
+ _contentManager->aboutToFinish();
+ _contentManager->terminate();
+ }
+
// cleanup the AssetClient thread
DependencyManager::destroy();
_assetClientThread.quit();
@@ -387,11 +392,6 @@ DomainServer::~DomainServer() {
// destroy the LimitedNodeList before the DomainServer QCoreApplication is down
DependencyManager::destroy();
-
- if (_contentManager) {
- _contentManager->aboutToFinish();
- _contentManager->terminate();
- }
}
void DomainServer::queuedQuit(QString quitMessage, int exitCode) {
@@ -417,8 +417,8 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
const QString X509_PRIVATE_KEY_OPTION = "key";
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
- QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString();
- QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString();
+ QString certPath = _settingsManager.valueForKeyPath(X509_CERTIFICATE_OPTION).toString();
+ QString keyPath = _settingsManager.valueForKeyPath(X509_PRIVATE_KEY_OPTION).toString();
if (!certPath.isEmpty() && !keyPath.isEmpty()) {
// the user wants to use the following cert and key for HTTPS
@@ -461,8 +461,7 @@ bool DomainServer::optionallySetupOAuth() {
const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
- const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
- _oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
+ _oauthProviderURL = QUrl(_settingsManager.valueForKeyPath(OAUTH_PROVIDER_URL_OPTION).toString());
// if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) {
@@ -472,9 +471,9 @@ bool DomainServer::optionallySetupOAuth() {
auto accountManager = DependencyManager::get();
accountManager->setAuthURL(_oauthProviderURL);
- _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
+ _oauthClientID = _settingsManager.valueForKeyPath(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
- _hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString();
+ _hostname = _settingsManager.valueForKeyPath(REDIRECT_HOSTNAME_OPTION).toString();
if (!_oauthClientID.isEmpty()) {
if (_oauthProviderURL.isEmpty()
@@ -499,11 +498,11 @@ static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
void DomainServer::getTemporaryName(bool force) {
// check if we already have a domain ID
- const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH);
+ QVariant idValueVariant = _settingsManager.valueForKeyPath(METAVERSE_DOMAIN_ID_KEY_PATH);
qInfo() << "Requesting temporary domain name";
- if (idValueVariant) {
- qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString();
+ if (idValueVariant.isValid()) {
+ qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant.toString();
if (force) {
qDebug() << "Requesting temporary domain name to replace current ID:" << getID();
} else {
@@ -543,9 +542,6 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8());
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings);
- // store the new ID and auto networking setting on disk
- _settingsManager.persistToFile();
-
// store the new token to the account info
auto accountManager = DependencyManager::get();
accountManager->setTemporaryDomain(id, key);
@@ -647,8 +643,6 @@ void DomainServer::setupNodeListAndAssignments() {
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
int domainServerPort = localPortValue.toInt();
- QVariantMap& settingsMap = _settingsManager.getSettingsMap();
-
int domainServerDTLSPort = INVALID_PORT;
if (_isUsingDTLS) {
@@ -656,8 +650,9 @@ void DomainServer::setupNodeListAndAssignments() {
const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port";
- if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) {
- domainServerDTLSPort = (unsigned short) settingsMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt();
+ auto dtlsPortVariant = _settingsManager.valueForKeyPath(CUSTOM_DTLS_PORT_OPTION);
+ if (dtlsPortVariant.isValid()) {
+ domainServerDTLSPort = (unsigned short) dtlsPortVariant.toUInt();
}
}
@@ -687,9 +682,9 @@ void DomainServer::setupNodeListAndAssignments() {
nodeList->setSessionUUID(_overridingDomainID);
isMetaverseDomain = true; // assume metaverse domain
} else {
- const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
- if (idValueVariant) {
- nodeList->setSessionUUID(idValueVariant->toString());
+ QVariant idValueVariant = _settingsManager.valueForKeyPath(METAVERSE_DOMAIN_ID_KEY_PATH);
+ if (idValueVariant.isValid()) {
+ nodeList->setSessionUUID(idValueVariant.toString());
isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain
} else {
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
@@ -758,10 +753,10 @@ bool DomainServer::resetAccountManagerAccessToken() {
QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY);
if (accessToken.isEmpty()) {
- const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
+ QVariant accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);
- if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
- accessToken = accessTokenVariant->toString();
+ if (accessTokenVariant.isValid() && accessTokenVariant.canConvert(QMetaType::QString)) {
+ accessToken = accessTokenVariant.toString();
} else {
qWarning() << "No access token is present. Some operations that use the metaverse API will fail.";
qDebug() << "Set an access token via the web interface, in your user config"
@@ -892,31 +887,26 @@ void DomainServer::updateICEServerAddresses() {
}
void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) {
- const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
- QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
-
- const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
+ const QString ASSIGNMENT_CONFIG_PREFIX = "config-";
// scan for assignment config keys
- QStringList variantMapKeys = settingsMap.keys();
- int configIndex = variantMapKeys.indexOf(assignmentConfigRegex);
+ for (int i = 0; i < Assignment::AllTypes; ++i) {
+ QVariant assignmentConfigVariant = _settingsManager.valueOrDefaultValueForKeyPath(ASSIGNMENT_CONFIG_PREFIX + QString::number(i));
- while (configIndex != -1) {
- // figure out which assignment type this matches
- Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt();
+ if (assignmentConfigVariant.isValid()) {
+ // figure out which assignment type this matches
+ Assignment::Type assignmentType = static_cast(i);
- if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) {
- QVariant mapValue = settingsMap[variantMapKeys[configIndex]];
- QVariantList assignmentList = mapValue.toList();
+ if (!excludedTypes.contains(assignmentType)) {
+ QVariantList assignmentList = assignmentConfigVariant.toList();
- if (assignmentType != Assignment::AgentType) {
- createStaticAssignmentsForType(assignmentType, assignmentList);
+ if (assignmentType != Assignment::AgentType) {
+ createStaticAssignmentsForType(assignmentType, assignmentList);
+ }
+
+ excludedTypes.insert(assignmentType);
}
-
- excludedTypes.insert(assignmentType);
}
-
- configIndex = variantMapKeys.indexOf(assignmentConfigRegex, configIndex + 1);
}
}
@@ -928,10 +918,10 @@ void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment
void DomainServer::populateStaticScriptedAssignmentsFromSettings() {
const QString PERSISTENT_SCRIPTS_KEY_PATH = "scripts.persistent_scripts";
- const QVariant* persistentScriptsVariant = valueForKeyPath(_settingsManager.getSettingsMap(), PERSISTENT_SCRIPTS_KEY_PATH);
+ QVariant persistentScriptsVariant = _settingsManager.valueOrDefaultValueForKeyPath(PERSISTENT_SCRIPTS_KEY_PATH);
- if (persistentScriptsVariant) {
- QVariantList persistentScriptsList = persistentScriptsVariant->toList();
+ if (persistentScriptsVariant.isValid()) {
+ QVariantList persistentScriptsList = persistentScriptsVariant.toList();
foreach(const QVariant& persistentScriptVariant, persistentScriptsList) {
QVariantMap persistentScript = persistentScriptVariant.toMap();
@@ -1761,7 +1751,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer();
- auto getSetting = [this](QString keyPath, QVariant& value) -> bool {
- QVariantMap& settingsMap = _settingsManager.getSettingsMap();
- QVariant* var = valueForKeyPath(settingsMap, keyPath);
- if (var == nullptr) {
+ auto getSetting = [this](QString keyPath, QVariant value) -> bool {
+
+ value = _settingsManager.valueForKeyPath(keyPath);
+ if (!value.isValid()) {
return false;
}
- value = *var;
return true;
};
@@ -2028,8 +2017,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
const QString URI_WIZARD = "/wizard/";
const QString WIZARD_COMPLETED_ONCE_KEY_PATH = "wizard.completed_once";
- const QVariant* wizardCompletedOnce = valueForKeyPath(_settingsManager.getSettingsMap(), WIZARD_COMPLETED_ONCE_KEY_PATH);
- const bool completedOnce = wizardCompletedOnce && wizardCompletedOnce->toBool();
+ QVariant wizardCompletedOnce = _settingsManager.valueForKeyPath(WIZARD_COMPLETED_ONCE_KEY_PATH);
+ const bool completedOnce = wizardCompletedOnce.isValid() && wizardCompletedOnce.toBool();
if (url.path() != URI_WIZARD && url.path().endsWith('/') && !completedOnce) {
// First visit, redirect to the wizard
@@ -2326,8 +2315,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return true;
} else if (url.path() == "/domain_settings") {
- auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
- if (!accessTokenVariant) {
+ auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);
+ if (!accessTokenVariant.isValid()) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
@@ -2360,8 +2349,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + domainID, "domain",
{ }, { "network_address", "network_port", "label" });
} else if (url.path() == URI_API_PLACES) {
- auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
- if (!accessTokenVariant->isValid()) {
+ auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);
+ if (!accessTokenVariant.isValid()) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
return true;
}
@@ -2409,7 +2398,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/v1/places/" + place_id };
- url.setQuery("access_token=" + accessTokenVariant->toString());
+ url.setQuery("access_token=" + accessTokenVariant.toString());
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
@@ -2604,10 +2593,11 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
- QVariantMap& settingsMap = _settingsManager.getSettingsMap();
+ QVariant adminUsersVariant = _settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY);
+ QVariant adminRolesVariant = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY);
if (!_oauthProviderURL.isEmpty()
- && (settingsMap.contains(ADMIN_USERS_CONFIG_KEY) || settingsMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
+ && (adminUsersVariant.isValid() || adminRolesVariant.isValid())) {
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
@@ -2618,7 +2608,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
cookieUUID = cookieUUIDRegex.cap(1);
}
- if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) {
+ if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) {
qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication."
<< "These cannot be combined - using OAuth for authentication.";
}
@@ -2628,13 +2618,13 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
QString profileUsername = sessionData.getUsername();
- if (settingsMap.value(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) {
+ if (_settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) {
// this is an authenticated user
return true;
}
// loop the roles of this user and see if they are in the admin-roles array
- QStringList adminRolesArray = settingsMap.value(ADMIN_ROLES_CONFIG_KEY).toStringList();
+ QStringList adminRolesArray = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY).toStringList();
if (!adminRolesArray.isEmpty()) {
foreach(const QString& userRole, sessionData.getRoles()) {
@@ -2679,7 +2669,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
// we don't know about this user yet, so they are not yet authenticated
return false;
}
- } else if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) {
+ } else if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) {
// config file contains username and password combinations for basic auth
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
@@ -2698,10 +2688,10 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
QString headerPassword = credentialList[1];
// we've pulled a username and password - now check if there is a match in our basic auth hash
- QString settingsUsername = valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)->toString();
- const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH);
+ QString settingsUsername = _settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).toString();
+ QVariant settingsPasswordVariant = _settingsManager.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH);
- QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : "";
+ QString settingsPassword = settingsPasswordVariant.isValid() ? settingsPasswordVariant.toString() : "";
QString hexHeaderPassword = headerPassword.isEmpty() ?
"" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
@@ -2838,13 +2828,14 @@ ReplicationServerInfo serverInformationFromSettings(QVariantMap serverMap, Repli
}
void DomainServer::updateReplicationNodes(ReplicationServerDirection direction) {
- auto settings = _settingsManager.getSettingsMap();
- if (settings.contains(BROADCASTING_SETTINGS_KEY)) {
+ auto broadcastSettingsVariant = _settingsManager.valueForKeyPath(BROADCASTING_SETTINGS_KEY);
+
+ if (broadcastSettingsVariant.isValid()) {
auto nodeList = DependencyManager::get();
std::vector replicationNodesInSettings;
- auto replicationSettings = settings.value(BROADCASTING_SETTINGS_KEY).toMap();
+ auto replicationSettings = broadcastSettingsVariant.toMap();
QString serversKey = direction == Upstream ? "upstream_servers" : "downstream_servers";
QString replicationDirection = direction == Upstream ? "upstream" : "downstream";
@@ -2920,13 +2911,12 @@ void DomainServer::updateUpstreamNodes() {
void DomainServer::updateReplicatedNodes() {
// Make sure we have downstream nodes in our list
- auto settings = _settingsManager.getSettingsMap();
-
static const QString REPLICATED_USERS_KEY = "users";
_replicatedUsernames.clear();
-
- if (settings.contains(BROADCASTING_SETTINGS_KEY)) {
- auto replicationSettings = settings.value(BROADCASTING_SETTINGS_KEY).toMap();
+
+ auto replicationVariant = _settingsManager.valueForKeyPath(BROADCASTING_SETTINGS_KEY);
+ if (replicationVariant.isValid()) {
+ auto replicationSettings = replicationVariant.toMap();
if (replicationSettings.contains(REPLICATED_USERS_KEY)) {
auto usersSettings = replicationSettings.value(REPLICATED_USERS_KEY).toList();
for (auto& username : usersSettings) {
@@ -3114,17 +3104,17 @@ void DomainServer::processPathQueryPacket(QSharedPointer messag
// check out paths in the _configMap to see if we have a match
auto keypath = QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY).arg(pathQuery);
- const QVariant* pathMatch = valueForKeyPath(_settingsManager.getSettingsMap(), keypath);
+ QVariant pathMatch = _settingsManager.valueForKeyPath(keypath);
- if (pathMatch || pathQuery == INDEX_PATH) {
+ if (pathMatch.isValid() || pathQuery == INDEX_PATH) {
// we got a match, respond with the resulting viewpoint
auto nodeList = DependencyManager::get();
QString responseViewpoint;
// if we didn't match the path BUT this is for the index path then send back our default
- if (pathMatch) {
- responseViewpoint = pathMatch->toMap()[PATH_VIEWPOINT_KEY].toString();
+ if (pathMatch.isValid()) {
+ responseViewpoint = pathMatch.toMap()[PATH_VIEWPOINT_KEY].toString();
} else {
const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1";
responseViewpoint = DEFAULT_INDEX_PATH;
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index a3f99facea..5157654c33 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -38,6 +38,9 @@
#include "DomainServerNodeData.h"
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
+const QString SETTINGS_PATH = "/settings";
+const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
+const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json";
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
@@ -190,6 +193,9 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer(getSettingsMap()[DESCRIPTORS].data());
-}
-
void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows,
QString groupName, NodePermissionsPointer perms) {
// this is called when someone has used the domain-settings webpage to add a group. They type the group's name
@@ -487,6 +482,9 @@ void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap&
void DomainServerSettingsManager::packPermissionsForMap(QString mapName,
NodePermissionsMap& permissionsRows,
QString keyPath) {
+ // grab a write lock on the settings mutex since we're about to change the config map
+ QWriteLocker locker(&_settingsLock);
+
// find (or create) the "security" section of the settings map
QVariant* security = _configMap.valueForKeyPath("security", true);
if (!security->canConvert(QMetaType::QVariantMap)) {
@@ -576,15 +574,20 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key
mapPointer->clear();
- QVariant* permissions = _configMap.valueForKeyPath(keyPath, true);
- if (!permissions->canConvert(QMetaType::QVariantList)) {
+ QVariant permissions = valueOrDefaultValueForKeyPath(keyPath);
+
+ if (!permissions.isValid()) {
+ // we don't have a permissions object to unpack for this keypath, bail
+ return false;
+ }
+
+ if (!permissions.canConvert(QMetaType::QVariantList)) {
qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings.";
- (*permissions) = QVariantList();
}
bool needPack = false;
- QList permissionsList = permissions->toList();
+ QList permissionsList = permissions.toList();
foreach (QVariant permsHash, permissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
@@ -611,6 +614,11 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key
void DomainServerSettingsManager::unpackPermissions() {
// transfer details from _configMap to _agentPermissions
+ // NOTE: Defaults for standard permissions (anonymous, friends, localhost, logged-in) used
+ // to be set here and then immediately persisted to the config JSON file.
+ // They have since been moved to describe-settings.json as the default value for AGENT_STANDARD_PERMISSIONS_KEYPATH.
+ // In order to change the default standard permissions you must change the default value in describe-settings.json.
+
bool needPack = false;
needPack |= unpackPermissionsForKeypath(AGENT_STANDARD_PERMISSIONS_KEYPATH, &_standardAgentPermissions);
@@ -670,57 +678,39 @@ void DomainServerSettingsManager::unpackPermissions() {
}
});
- // if any of the standard names are missing, add them
- foreach(const QString& standardName, NodePermissions::standardNames) {
- NodePermissionsKey standardKey { standardName, 0 };
- if (!_standardAgentPermissions.contains(standardKey)) {
- // we don't have permissions for one of the standard groups, so we'll add them now
- NodePermissionsPointer perms { new NodePermissions(standardKey) };
-
- if (standardKey == NodePermissions::standardNameLocalhost) {
- // the localhost user is granted all permissions by default
- perms->setAll(true);
- } else {
- // anonymous, logged in, and friend users get connect permissions by default
- perms->set(NodePermissions::Permission::canConnectToDomain);
- perms->set(NodePermissions::Permission::canRezTemporaryCertifiedEntities);
- }
-
- // add the permissions to the standard map
- _standardAgentPermissions[standardKey] = perms;
-
- // this will require a packing of permissions
- needPack = true;
- }
- }
-
needPack |= ensurePermissionsForGroupRanks();
if (needPack) {
packPermissions();
}
- #ifdef WANT_DEBUG
+#ifdef WANT_DEBUG
qDebug() << "--------------- permissions ---------------------";
- QList> permissionsSets;
- permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
- << _groupPermissions.get() << _groupForbiddens.get()
- << _ipPermissions.get() << _macPermissions.get()
- << _machineFingerprintPermissions.get();
+ std::list permissionsSets {
+ &_standardAgentPermissions, &_agentPermissions,
+ &_groupPermissions, &_groupForbiddens,
+ &_ipPermissions, &_macPermissions,
+ &_machineFingerprintPermissions
+ };
foreach (auto permissionSet, permissionsSets) {
- QHashIterator i(permissionSet);
- while (i.hasNext()) {
- i.next();
- NodePermissionsPointer perms = i.value();
+ auto& permissionKeyMap = permissionSet->get();
+ auto it = permissionKeyMap.begin();
+
+ while (it != permissionKeyMap.end()) {
+
+ NodePermissionsPointer perms = it->second;
if (perms->isGroup()) {
- qDebug() << i.key() << perms->getGroupID() << perms;
+ qDebug() << it->first << perms->getGroupID() << perms;
} else {
- qDebug() << i.key() << perms;
+ qDebug() << it->first << perms;
}
+
+ ++it;
}
}
- #endif
+#endif
+
}
bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() {
@@ -1068,12 +1058,22 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid&
return getForbiddensForGroup(groupKey.first, groupKey.second);
}
+QVariant DomainServerSettingsManager::valueForKeyPath(const QString& keyPath) {
+ QReadLocker locker(&_settingsLock);
+ auto foundValue = _configMap.valueForKeyPath(keyPath);
+ return foundValue ? *foundValue : QVariant();
+}
+
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) {
+ QReadLocker locker(&_settingsLock);
const QVariant* foundValue = _configMap.valueForKeyPath(keyPath);
if (foundValue) {
return *foundValue;
} else {
+ // we don't need the settings lock anymore since we're done reading from the config map
+ locker.unlock();
+
int dotIndex = keyPath.indexOf('.');
QString groupKey = keyPath.mid(0, dotIndex);
@@ -1112,9 +1112,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
// we recurse one level deep below each group for the appropriate setting
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType);
- // store whatever the current _settingsMap is to file
- persistToFile();
-
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
@@ -1216,16 +1213,9 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
}
bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) {
-
- if (thread() != QThread::currentThread()) {
- bool success;
- BLOCKING_INVOKE_METHOD(this, "restoreSettingsFromObject",
- Q_RETURN_ARG(bool, success),
- Q_ARG(QJsonObject, settingsToRestore),
- Q_ARG(SettingsType, settingsType));
- return success;
- }
+ // grab a write lock since we're about to change the settings map
+ QWriteLocker locker(&_settingsLock);
QJsonArray* filteredDescriptionArray = settingsType == DomainSettings
? &_domainSettingsDescription : &_contentSettingsDescription;
@@ -1341,6 +1331,10 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings
} else {
// restore completed, persist the new settings
qDebug() << "Restore completed, persisting restored settings to file";
+
+ // let go of the write lock since we're done making changes to the config map
+ locker.unlock();
+
persistToFile();
return true;
}
@@ -1352,20 +1346,6 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
bool includeDefaults, bool isForBackup) {
QJsonObject responseObject;
- if (thread() != QThread::currentThread()) {
-
- BLOCKING_INVOKE_METHOD(this, "settingsResponseObjectForType",
- Q_RETURN_ARG(QJsonObject, responseObject),
- Q_ARG(const QString&, typeValue),
- Q_ARG(bool, isAuthenticated),
- Q_ARG(bool, includeDomainSettings),
- Q_ARG(bool, includeContentSettings),
- Q_ARG(bool, includeDefaults),
- Q_ARG(bool, isForBackup));
-
- return responseObject;
- }
-
if (!typeValue.isEmpty() || isAuthenticated) {
// convert the string type value to a QJsonValue
QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt());
@@ -1374,6 +1354,7 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
// only enumerate the requested settings type (domain setting or content setting)
QJsonArray* filteredDescriptionArray = &_descriptionArray;
+
if (includeDomainSettings && !includeContentSettings) {
filteredDescriptionArray = &_domainSettingsDescription;
} else if (includeContentSettings && !includeDomainSettings) {
@@ -1413,21 +1394,21 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
QVariant variantValue;
if (!groupKey.isEmpty()) {
- QVariant settingsMapGroupValue = _configMap.value(groupKey);
+ QVariant settingsMapGroupValue = valueForKeyPath(groupKey);
if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingName);
}
} else {
- variantValue = _configMap.value(settingName);
+ variantValue = valueForKeyPath(settingName);
}
// final check for inclusion
// either we include default values or we don't but this isn't a default value
- if (includeDefaults || !variantValue.isNull()) {
+ if (includeDefaults || variantValue.isValid()) {
QJsonValue result;
- if (variantValue.isNull()) {
+ if (!variantValue.isValid()) {
// no value for this setting, pass the default
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
result = settingObject[SETTING_DEFAULT_KEY];
@@ -1566,6 +1547,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
SettingsType settingsType) {
+
+ // take a write lock since we're about to overwrite settings in the config map
+ QWriteLocker locker(&_settingsLock);
+
static const QString SECURITY_ROOT_KEY = "security";
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
static const QString BROADCASTING_KEY = "broadcasting";
@@ -1663,6 +1648,12 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
}
}
+ // we're done making changes to the config map, let go of our read lock
+ locker.unlock();
+
+ // store whatever the current config map is to file
+ persistToFile();
+
return needRestart;
}
@@ -1689,6 +1680,9 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
}
void DomainServerSettingsManager::sortPermissions() {
+ // take a write lock since we're about to change the config map data
+ QWriteLocker locker(&_settingsLock);
+
// sort the permission-names
QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH);
if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) {
@@ -1725,11 +1719,15 @@ void DomainServerSettingsManager::persistToFile() {
QFile settingsFile(_configMap.getUserConfigFilename());
if (settingsFile.open(QIODevice::WriteOnly)) {
+ // take a read lock so we can grab the config and write it to file
+ QReadLocker locker(&_settingsLock);
settingsFile.write(QJsonDocument::fromVariant(_configMap.getConfig()).toJson());
} else {
qCritical("Could not write to JSON settings file. Unable to persist settings.");
// failed to write, reload whatever the current config state is
+ // with a write lock since we're about to overwrite the config map
+ QWriteLocker locker(&_settingsLock);
_configMap.loadConfig(_argumentList);
}
}
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index 897a15485f..d81547410b 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -27,9 +27,6 @@
const QString SETTINGS_PATHS_KEY = "paths";
-const QString SETTINGS_PATH = "/settings";
-const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
-const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json";
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
@@ -53,11 +50,12 @@ public:
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void setupConfigMap(const QStringList& argumentList);
+
+ // each of the three methods in this group takes a read lock of _settingsLock
+ // and cannot be called when the a write lock is held by the same thread
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
-
- QVariantMap& getSettingsMap() { return _configMap.getConfig(); }
-
- QVariantMap& getDescriptorsMap();
+ QVariant valueForKeyPath(const QString& keyPath);
+ bool containsKeyPath(const QString& keyPath) { return valueForKeyPath(keyPath).isValid(); }
// these give access to anonymous/localhost/logged-in settings from the domain-server settings page
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); }
@@ -119,6 +117,8 @@ public:
/// thread safe method to restore settings from a JSON object
Q_INVOKABLE bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType);
+ bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
+
signals:
void updateNodePermissions();
void settingsUpdated();
@@ -138,12 +138,13 @@ private:
QStringList _argumentList;
QJsonArray filteredDescriptionArray(bool isContentSettings);
- bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
-
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription);
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
void sortPermissions();
+
+ // you cannot be holding the _settingsLock when persisting to file from the same thread
+ // since it may take either a read lock or write lock and recursive locking doesn't allow a change in type
void persistToFile();
void splitSettingsDescription();
@@ -155,10 +156,10 @@ private:
QJsonArray _contentSettingsDescription;
QJsonObject _settingsMenuGroups;
+ // any method that calls _valueForKeyPath on this _configMap must get a write lock it keeps until it
+ // is done with the returned QVariant*
HifiConfigVariantMap _configMap;
- friend class DomainServer;
-
// these cause calls to metaverse's group api
void apiGetGroupID(const QString& groupName);
void apiGetGroupRanks(const QUuid& groupID);
@@ -192,6 +193,9 @@ private:
// keep track of answers to api queries about which users are in which groups
QHash> _groupMembership; // QHash>
+
+ /// guard read/write access from multiple threads to settings
+ QReadWriteLock _settingsLock { QReadWriteLock::Recursive };
};
#endif // hifi_DomainServerSettingsManager_h