Merge pull request #12429 from birarda/feat/pending-content-archives

availability and restore progress for content archives, move perm defaults, add settings manager thread safety
This commit is contained in:
Stephen Birarda 2018-02-16 17:39:16 -07:00 committed by GitHub
commit 42afdd6502
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 331 additions and 227 deletions

View file

@ -306,7 +306,37 @@
} }
], ],
"non-deletable-row-key": "permissions_id", "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", "name": "group_permissions",

View file

@ -2,10 +2,19 @@ $(document).ready(function(){
var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button'; var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button';
var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file'; 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 = "<div class='progress'>";
html += "<div class='" + extraClass + " progress-bar progress-bar-success progress-bar-striped active' role='progressbar' aria-valuemin='0' aria-valuemax='100'>";
html += label + "<span class='sr-only'></span></div></div>";
return html;
}
function setupBackupUpload() { function setupBackupUpload() {
// construct the HTML needed for the settings backup panel // construct the HTML needed for the settings backup panel
var html = "<div class='form-group'>"; var html = "<div class='form-group'><div id='" + UPLOAD_CONTENT_ALLOWED_DIV_ID + "'>";
html += "<span class='help-block'>Upload a content archive (.zip) or entity file (.json, .json.gz) to replace the content of this domain."; html += "<span class='help-block'>Upload a content archive (.zip) or entity file (.json, .json.gz) to replace the content of this domain.";
html += "<br/>Note: Your domain content will be replaced by the content you upload, but the existing backup files of your domain's content will not immediately be changed.</span>"; html += "<br/>Note: Your domain content will be replaced by the content you upload, but the existing backup files of your domain's content will not immediately be changed.</span>";
@ -13,7 +22,10 @@ $(document).ready(function(){
html += "<input id='restore-settings-file' name='restore-settings' type='file'>"; html += "<input id='restore-settings-file' name='restore-settings' type='file'>";
html += "<button type='button' id='" + RESTORE_SETTINGS_UPLOAD_ID + "' disabled='true' class='btn btn-primary'>Upload Content</button>"; html += "<button type='button' id='" + RESTORE_SETTINGS_UPLOAD_ID + "' disabled='true' class='btn btn-primary'>Upload Content</button>";
html += "</div>"; html += "</div><div id='" + UPLOAD_CONTENT_RECOVERING_DIV_ID + "'>";
html += "<span class='help-block'>Restore in progress</span>";
html += progressBarHTML('recovery', 'Restoring');
html += "</div></div>";
$('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(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 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_TABLE_ID = 'automatic-archives-table';
var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody'; var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody';
var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table'; var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table';
var MANUAL_ARCHIVES_TBODY_ID = 'manual-archives-tbody'; var MANUAL_ARCHIVES_TBODY_ID = 'manual-archives-tbody';
var AUTO_ARCHIVES_SETTINGS_LINK_ID = 'auto-archives-settings-link'; var AUTO_ARCHIVES_SETTINGS_LINK_ID = 'auto-archives-settings-link';
var ACTION_MENU_CLASS = 'action-menu';
var automaticBackups = []; var automaticBackups = [];
var manualBackups = []; var manualBackups = [];
function setupContentArchives() { function setupContentArchives() {
// construct the HTML needed for the content archives panel // construct the HTML needed for the content archives panel
var html = "<div class='form-group'>"; var html = "<div id='" + CONTENT_ARCHIVES_NORMAL_ID + "'><div class='form-group'>";
html += "<label class='control-label'>Automatic Content Archives</label>"; html += "<label class='control-label'>Automatic Content Archives</label>";
html += "<span class='help-block'>Your domain server makes regular archives of the content in your domain. In the list below, you can see and download all of your domain content and settings backups. " html += "<span class='help-block'>Your domain server makes regular archives of the content in your domain. In the list below, you can see and download all of your domain content and settings backups. "
html += "<a href='/settings/#automatic_content_archives' id='" + AUTO_ARCHIVES_SETTINGS_LINK_ID + "'>Click here to manage automatic content archive intervals.</a>"; html += "<a href='/settings/#automatic_content_archives' id='" + AUTO_ARCHIVES_SETTINGS_LINK_ID + "'>Click here to manage automatic content archive intervals.</a></span>";
html += "</div>"; html += "</div>";
html += "<table class='table sortable' id='" + AUTOMATIC_ARCHIVES_TABLE_ID + "'>"; html += "<table class='table sortable' id='" + AUTOMATIC_ARCHIVES_TABLE_ID + "'>";
var backups_table_head = "<thead><tr class='gray-tr'><th>Archive Name</th><th data-defaultsort='desc'>Archive Date</th><th class='text-right' data-defaultsort='disabled'>Actions</th></tr></thead>"; var backups_table_head = "<thead><tr class='gray-tr'><th>Archive Name</th><th data-defaultsort='desc'>Archive Date</th>"
+ "<th data-defaultsort='disabled'></th><th class='" + ACTION_MENU_CLASS + "' data-defaultsort='disabled'>Actions</th>"
+ "</tr></thead>";
html += backups_table_head; html += backups_table_head;
html += "<tbody id='" + AUTOMATIC_ARCHIVES_TBODY_ID + "'></tbody></table>"; html += "<tbody id='" + AUTOMATIC_ARCHIVES_TBODY_ID + "'></tbody></table>";
@ -95,7 +112,11 @@ $(document).ready(function(){
html += "</div>"; html += "</div>";
html += "<table class='table sortable' id='" + MANUAL_ARCHIVES_TABLE_ID + "'>"; html += "<table class='table sortable' id='" + MANUAL_ARCHIVES_TABLE_ID + "'>";
html += backups_table_head; html += backups_table_head;
html += "<tbody id='" + MANUAL_ARCHIVES_TBODY_ID + "'></tbody></table>"; html += "<tbody id='" + MANUAL_ARCHIVES_TBODY_ID + "'></tbody></table></div>";
html += "<div class='form-group' id='" + CONTENT_ARCHIVES_ERROR_ID + "' style='display:none;'>"
+ "<span class='help-block'>There was a problem loading your list of automatic and manual content archives. "
+ "Please reload the page to try again.</span></div>";
// put the base HTML in the content archives panel // put the base HTML in the content archives panel
$('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html(html); $('#' + 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_RESTORE_LINK_CLASS = 'restore-backup';
var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup'; var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup';
var BACKUP_DELETE_LINK_CLASS = 'delete-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 // make a GET request to get backup information to populate the table
$.ajax({ $.ajax({
url: '/api/backups', url: '/api/backups',
@ -123,41 +145,92 @@ $(document).ready(function(){
// populate the backups tables with the backups // populate the backups tables with the backups
function createBackupTableRow(backup) { function createBackupTableRow(backup) {
return "<tr data-backup-id='" + backup.id + "' data-backup-name='" + backup.name + "'>" return "<tr data-backup-id='" + backup.id + "' data-backup-name='" + backup.name + "'>"
+ "<td data-value='" + backup.name.toLowerCase() + "'>" + backup.name + "</td><td data-dateformat='lll'>" + "<td data-value='" + backup.name.toLowerCase() + "'>" + backup.name + "</td><td data-value='" + backup.createdAtMillis + "'>"
+ moment(backup.createdAtMillis).format('lll') + moment(backup.createdAtMillis).format('lll')
+ "</td><td class='text-right'>" + "</td><td class='backup-status'></td><td class='" + ACTION_MENU_CLASS + "'>"
+ "<div class='dropdown'><div class='dropdown-toggle' data-toggle='dropdown' aria-expanded='false'><span class='glyphicon glyphicon-option-vertical'></span></div>" + "<div class='dropdown'><div class='dropdown-toggle' data-toggle='dropdown' aria-expanded='false'><span class='glyphicon glyphicon-option-vertical'></span></div>"
+ "<ul class='dropdown-menu dropdown-menu-right'>" + "<ul class='dropdown-menu dropdown-menu-right'>"
+ "<li><a class='" + BACKUP_RESTORE_LINK_CLASS + "' href='#'>Restore from here</a></li><li class='divider'></li>" + "<li><a class='" + BACKUP_RESTORE_LINK_CLASS + "' href='#'>Restore from here</a></li><li class='divider'></li>"
+ "<li><a class='" + BACKUP_DOWNLOAD_LINK_CLASS + "' href='#'>Download</a></li><li class='divider'></li>" + "<li><a class='" + BACKUP_DOWNLOAD_LINK_CLASS + "' href='/api/backups/" + backup.id + "'>Download</a></li><li class='divider'></li>"
+ "<li><a class='" + BACKUP_DELETE_LINK_CLASS + "' href='/api/backups/" + backup.id + "' target='_blank'>Delete</a></li></ul></div></td>"; + "<li><a class='" + BACKUP_DELETE_LINK_CLASS + "' href='#' target='_blank'>Delete</a></li></ul></div></td>";
}
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 = ""; var automaticRows = "";
if (automaticBackups.length > 0) { if (automaticBackups.length > 0) {
for (var backupIndex in automaticBackups) { 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 updateOrAddTableRow(automaticBackups[backupIndex], AUTOMATIC_ARCHIVES_TBODY_ID);
automaticRows += createBackupTableRow(automaticBackups[backupIndex]);
} }
} }
$('#' + AUTOMATIC_ARCHIVES_TBODY_ID).html(automaticRows);
var manualRows = "";
if (manualBackups.length > 0) { if (manualBackups.length > 0) {
for (var backupIndex in manualBackups) { 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 updateOrAddTableRow(manualBackups[backupIndex], MANUAL_ARCHIVES_TBODY_ID);
manualRows += createBackupTableRow(manualBackups[backupIndex]);
} }
} }
$('#' + 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 // tell bootstrap sortable to update for the new rows
$.bootstrapSortable({ applyLast: true }); $.bootstrapSortable({ applyLast: true });
$('#' + CONTENT_ARCHIVES_NORMAL_ID).toggle(true);
$('#' + CONTENT_ARCHIVES_ERROR_ID).toggle(false);
}).fail(function(){ }).fail(function(){
// we've hit the very rare case where we couldn't load the list of backups from the domain server // 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 // replace the content archives panel with a simple error message
// stating that the user should reload the page // stating that the user should reload the page
$('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html( $('#' + CONTENT_ARCHIVES_NORMAL_ID).toggle(false);
"<div class='form-group'>" + $('#' + CONTENT_ARCHIVES_ERROR_ID).toggle(true);
"<span class='help-block'>There was a problem loading your list of automatic and manual content archives. Please reload the page to try again.</span>" +
"</div>"
);
}).always(function(){ }).always(function(){
// toggle showing or hiding the tables depending on if they have entries // toggle showing or hiding the tables depending on if they have entries
@ -197,12 +267,11 @@ $(document).ready(function(){
"Restore content", "Restore content",
function() { function() {
// show a spinner while we send off our request // 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 // setup an AJAX POST to request content restore
$.post('/api/backups/recover/' + backupID).done(function(data, textStatus, jqXHR) { $.post('/api/backups/recover/' + backupID).done(function(data, textStatus, jqXHR) {
swal.close(); swal.close();
showRestartModal();
}).fail(function(jqXHR, textStatus, errorThrown) { }).fail(function(jqXHR, textStatus, errorThrown) {
showErrorMessage( showErrorMessage(
"Error", "Error",
@ -247,7 +316,7 @@ $(document).ready(function(){
}).always(function(){ }).always(function(){
// reload the list of content archives in case we deleted a backup // reload the list of content archives in case we deleted a backup
// or it's no longer an available backup for some other reason // or it's no longer an available backup for some other reason
reloadLatestBackups(); reloadBackupInformation();
}); });
} }
) )
@ -306,7 +375,7 @@ $(document).ready(function(){
}).done(function(data) { }).done(function(data) {
// since we successfully setup a new content archive, reload the table of archives // since we successfully setup a new content archive, reload the table of archives
// which should show that this archive is pending creation // which should show that this archive is pending creation
reloadLatestBackups(); reloadBackupInformation();
}).fail(function(jqXHR, textStatus, errorThrown) { }).fail(function(jqXHR, textStatus, errorThrown) {
}); });
@ -322,6 +391,9 @@ $(document).ready(function(){
setupContentArchives(); setupContentArchives();
// load the latest backups immediately // load the latest backups immediately
reloadLatestBackups(); reloadBackupInformation();
// setup a timer to reload them every 5 seconds
setInterval(reloadBackupInformation, 5000);
}; };
}); });

View file

@ -466,6 +466,11 @@ tr.gray-tr {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
table .action-menu {
text-align: right;
width: 90px;
}
.dropdown-toggle span.glyphicon-option-vertical { .dropdown-toggle span.glyphicon-option-vertical {
font-size: 110%; font-size: 110%;
cursor: pointer; cursor: pointer;

View file

@ -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 input was changed, add the changed data attribute to it
$(this).attr('data-changed', true); $(this).attr('data-changed', true);
@ -838,7 +838,7 @@ function addTableRow(row) {
var keyInput = row.children(".key").children("input"); var keyInput = row.children(".key").children("input");
// whenever the keyInput changes, re-badge for differences // 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 // update siblings in the row to have the correct name
var currentKey = $(this).val(); var currentKey = $(this).val();

View file

@ -57,6 +57,8 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire
_persistInterval(persistInterval), _persistInterval(persistInterval),
_lastCheck(usecTimestampNow()) _lastCheck(usecTimestampNow())
{ {
setObjectName("DomainContentBackupManager");
// Make sure the backup directory exists. // Make sure the backup directory exists.
QDir(_backupDirectory).mkpath("."); QDir(_backupDirectory).mkpath(".");
@ -309,6 +311,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
} }
void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup) { void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "recoverFromUploadedBackup", Q_ARG(MiniPromise::Promise, promise), QMetaObject::invokeMethod(this, "recoverFromUploadedBackup", Q_ARG(MiniPromise::Promise, promise),
Q_ARG(QByteArray, uploadedBackup)); Q_ARG(QByteArray, uploadedBackup));

View file

@ -435,10 +435,11 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) { if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) {
// we can't allow this user to connect because we are at max capacity // we can't allow this user to connect because we are at max capacity
QString redirectOnMaxCapacity; QString redirectOnMaxCapacity;
const QVariant* redirectOnMaxCapacityVariant =
valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION); QVariant redirectOnMaxCapacityVariant =
if (redirectOnMaxCapacityVariant && redirectOnMaxCapacityVariant->canConvert<QString>()) { _server->_settingsManager.valueForKeyPath(MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION);
redirectOnMaxCapacity = redirectOnMaxCapacityVariant->toString(); if (redirectOnMaxCapacityVariant.canConvert<QString>()) {
redirectOnMaxCapacity = redirectOnMaxCapacityVariant.toString();
qDebug() << "Redirection domain:" << redirectOnMaxCapacity; qDebug() << "Redirection domain:" << redirectOnMaxCapacity;
} }
@ -610,9 +611,9 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
bool DomainGatekeeper::isWithinMaxCapacity() { bool DomainGatekeeper::isWithinMaxCapacity() {
// find out what our maximum capacity is // find out what our maximum capacity is
const QVariant* maximumUserCapacityVariant = QVariant maximumUserCapacityVariant =
valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); _server->_settingsManager.valueForKeyPath(MAXIMUM_USER_CAPACITY);
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; unsigned int maximumUserCapacity = !maximumUserCapacityVariant.isValid() ? maximumUserCapacityVariant.toUInt() : 0;
if (maximumUserCapacity > 0) { if (maximumUserCapacity > 0) {
unsigned int connectedUsers = _server->countConnectedUsers(); unsigned int connectedUsers = _server->countConnectedUsers();

View file

@ -84,21 +84,22 @@ void DomainMetadata::descriptorsChanged() {
// get descriptors // get descriptors
assert(_metadata[DESCRIPTORS].canConvert<QVariantMap>()); assert(_metadata[DESCRIPTORS].canConvert<QVariantMap>());
auto& state = *static_cast<QVariantMap*>(_metadata[DESCRIPTORS].data()); auto& state = *static_cast<QVariantMap*>(_metadata[DESCRIPTORS].data());
auto& settings = static_cast<DomainServer*>(parent())->_settingsManager.getSettingsMap();
auto& descriptors = static_cast<DomainServer*>(parent())->_settingsManager.getDescriptorsMap(); static const QString DESCRIPTORS_GROUP_KEYPATH = "descriptors";
auto descriptorsMap = static_cast<DomainServer*>(parent())->_settingsManager.valueForKeyPath(DESCRIPTORS).toMap();
// copy simple descriptors (description/maturity) // copy simple descriptors (description/maturity)
state[Descriptors::DESCRIPTION] = descriptors[Descriptors::DESCRIPTION]; state[Descriptors::DESCRIPTION] = descriptorsMap[Descriptors::DESCRIPTION];
state[Descriptors::MATURITY] = descriptors[Descriptors::MATURITY]; state[Descriptors::MATURITY] = descriptorsMap[Descriptors::MATURITY];
// copy array descriptors (hosts/tags) // copy array descriptors (hosts/tags)
state[Descriptors::HOSTS] = descriptors[Descriptors::HOSTS].toList(); state[Descriptors::HOSTS] = descriptorsMap[Descriptors::HOSTS].toList();
state[Descriptors::TAGS] = descriptors[Descriptors::TAGS].toList(); state[Descriptors::TAGS] = descriptorsMap[Descriptors::TAGS].toList();
// parse capacity // parse capacity
static const QString CAPACITY = "security.maximum_user_capacity"; static const QString CAPACITY = "security.maximum_user_capacity";
const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY); QVariant capacityVariant = static_cast<DomainServer*>(parent())->_settingsManager.valueForKeyPath(CAPACITY);
unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0; unsigned int capacity = capacityVariant.isValid() ? capacityVariant.toUInt() : 0;
state[Descriptors::CAPACITY] = capacity; state[Descriptors::CAPACITY] = capacity;
#if DEV_BUILD || PR_BUILD #if DEV_BUILD || PR_BUILD

View file

@ -75,8 +75,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
std::initializer_list<QString> optionalData, std::initializer_list<QString> optionalData,
bool requireAccessToken) { bool requireAccessToken) {
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);
if (accessTokenVariant == nullptr && requireAccessToken) { if (!accessTokenVariant.isValid() && requireAccessToken) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set"); connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
return true; return true;
} }
@ -112,8 +112,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (accessTokenVariant != nullptr) { if (accessTokenVariant.isValid()) {
auto accessTokenHeader = QString("Bearer ") + accessTokenVariant->toString(); auto accessTokenHeader = QString("Bearer ") + accessTokenVariant.toString();
req.setRawHeader("Authorization", accessTokenHeader.toLatin1()); req.setRawHeader("Authorization", accessTokenHeader.toLatin1());
} }
@ -380,6 +380,11 @@ void DomainServer::parseCommandLine() {
DomainServer::~DomainServer() { DomainServer::~DomainServer() {
qInfo() << "Domain Server is shutting down."; qInfo() << "Domain Server is shutting down.";
if (_contentManager) {
_contentManager->aboutToFinish();
_contentManager->terminate();
}
// cleanup the AssetClient thread // cleanup the AssetClient thread
DependencyManager::destroy<AssetClient>(); DependencyManager::destroy<AssetClient>();
_assetClientThread.quit(); _assetClientThread.quit();
@ -387,11 +392,6 @@ DomainServer::~DomainServer() {
// destroy the LimitedNodeList before the DomainServer QCoreApplication is down // destroy the LimitedNodeList before the DomainServer QCoreApplication is down
DependencyManager::destroy<LimitedNodeList>(); DependencyManager::destroy<LimitedNodeList>();
if (_contentManager) {
_contentManager->aboutToFinish();
_contentManager->terminate();
}
} }
void DomainServer::queuedQuit(QString quitMessage, int exitCode) { void DomainServer::queuedQuit(QString quitMessage, int exitCode) {
@ -417,8 +417,8 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
const QString X509_PRIVATE_KEY_OPTION = "key"; const QString X509_PRIVATE_KEY_OPTION = "key";
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString(); QString certPath = _settingsManager.valueForKeyPath(X509_CERTIFICATE_OPTION).toString();
QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString(); QString keyPath = _settingsManager.valueForKeyPath(X509_PRIVATE_KEY_OPTION).toString();
if (!certPath.isEmpty() && !keyPath.isEmpty()) { if (!certPath.isEmpty() && !keyPath.isEmpty()) {
// the user wants to use the following cert and key for HTTPS // 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 OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
const QString REDIRECT_HOSTNAME_OPTION = "hostname"; const QString REDIRECT_HOSTNAME_OPTION = "hostname";
const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); _oauthProviderURL = QUrl(_settingsManager.valueForKeyPath(OAUTH_PROVIDER_URL_OPTION).toString());
_oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
// if we don't have an oauth provider URL then we default to the default node auth url // if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) { if (_oauthProviderURL.isEmpty()) {
@ -472,9 +471,9 @@ bool DomainServer::optionallySetupOAuth() {
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
accountManager->setAuthURL(_oauthProviderURL); 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); _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 (!_oauthClientID.isEmpty()) {
if (_oauthProviderURL.isEmpty() if (_oauthProviderURL.isEmpty()
@ -499,11 +498,11 @@ static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
void DomainServer::getTemporaryName(bool force) { void DomainServer::getTemporaryName(bool force) {
// check if we already have a domain ID // 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"; qInfo() << "Requesting temporary domain name";
if (idValueVariant) { if (idValueVariant.isValid()) {
qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString(); qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant.toString();
if (force) { if (force) {
qDebug() << "Requesting temporary domain name to replace current ID:" << getID(); qDebug() << "Requesting temporary domain name to replace current ID:" << getID();
} else { } else {
@ -543,9 +542,6 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8()); auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8());
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings); _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 // store the new token to the account info
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
accountManager->setTemporaryDomain(id, key); accountManager->setTemporaryDomain(id, key);
@ -647,8 +643,6 @@ void DomainServer::setupNodeListAndAssignments() {
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
int domainServerPort = localPortValue.toInt(); int domainServerPort = localPortValue.toInt();
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
int domainServerDTLSPort = INVALID_PORT; int domainServerDTLSPort = INVALID_PORT;
if (_isUsingDTLS) { if (_isUsingDTLS) {
@ -656,8 +650,9 @@ void DomainServer::setupNodeListAndAssignments() {
const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port";
if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) { auto dtlsPortVariant = _settingsManager.valueForKeyPath(CUSTOM_DTLS_PORT_OPTION);
domainServerDTLSPort = (unsigned short) settingsMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); if (dtlsPortVariant.isValid()) {
domainServerDTLSPort = (unsigned short) dtlsPortVariant.toUInt();
} }
} }
@ -687,9 +682,9 @@ void DomainServer::setupNodeListAndAssignments() {
nodeList->setSessionUUID(_overridingDomainID); nodeList->setSessionUUID(_overridingDomainID);
isMetaverseDomain = true; // assume metaverse domain isMetaverseDomain = true; // assume metaverse domain
} else { } else {
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); QVariant idValueVariant = _settingsManager.valueForKeyPath(METAVERSE_DOMAIN_ID_KEY_PATH);
if (idValueVariant) { if (idValueVariant.isValid()) {
nodeList->setSessionUUID(idValueVariant->toString()); nodeList->setSessionUUID(idValueVariant.toString());
isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain
} else { } else {
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
@ -758,10 +753,10 @@ bool DomainServer::resetAccountManagerAccessToken() {
QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY); QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY);
if (accessToken.isEmpty()) { 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)) { if (accessTokenVariant.isValid() && accessTokenVariant.canConvert(QMetaType::QString)) {
accessToken = accessTokenVariant->toString(); accessToken = accessTokenVariant.toString();
} else { } else {
qWarning() << "No access token is present. Some operations that use the metaverse API will fail."; 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" qDebug() << "Set an access token via the web interface, in your user config"
@ -892,31 +887,26 @@ void DomainServer::updateICEServerAddresses() {
} }
void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes) { void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes) {
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; const QString ASSIGNMENT_CONFIG_PREFIX = "config-";
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
// scan for assignment config keys // scan for assignment config keys
QStringList variantMapKeys = settingsMap.keys(); for (int i = 0; i < Assignment::AllTypes; ++i) {
int configIndex = variantMapKeys.indexOf(assignmentConfigRegex); QVariant assignmentConfigVariant = _settingsManager.valueOrDefaultValueForKeyPath(ASSIGNMENT_CONFIG_PREFIX + QString::number(i));
while (configIndex != -1) { if (assignmentConfigVariant.isValid()) {
// figure out which assignment type this matches // figure out which assignment type this matches
Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt(); Assignment::Type assignmentType = static_cast<Assignment::Type>(i);
if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) { if (!excludedTypes.contains(assignmentType)) {
QVariant mapValue = settingsMap[variantMapKeys[configIndex]]; QVariantList assignmentList = assignmentConfigVariant.toList();
QVariantList assignmentList = mapValue.toList();
if (assignmentType != Assignment::AgentType) { if (assignmentType != Assignment::AgentType) {
createStaticAssignmentsForType(assignmentType, assignmentList); 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() { void DomainServer::populateStaticScriptedAssignmentsFromSettings() {
const QString PERSISTENT_SCRIPTS_KEY_PATH = "scripts.persistent_scripts"; 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) { if (persistentScriptsVariant.isValid()) {
QVariantList persistentScriptsList = persistentScriptsVariant->toList(); QVariantList persistentScriptsList = persistentScriptsVariant.toList();
foreach(const QVariant& persistentScriptVariant, persistentScriptsList) { foreach(const QVariant& persistentScriptVariant, persistentScriptsList) {
QVariantMap persistentScript = persistentScriptVariant.toMap(); QVariantMap persistentScript = persistentScriptVariant.toMap();
@ -1761,7 +1751,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer<ReceivedMessag
f.write(data); f.write(data);
OctreeUtils::RawOctreeData octreeData; OctreeUtils::RawOctreeData octreeData;
if (OctreeUtils::readOctreeDataInfoFromData(data, &octreeData)) { if (OctreeUtils::readOctreeDataInfoFromData(data, &octreeData)) {
qCDebug(domain_server) << "Wrote new entiteis file" << octreeData.id << octreeData.version; qCDebug(domain_server) << "Wrote new entities file" << octreeData.id << octreeData.version;
} else { } else {
qCDebug(domain_server) << "Failed to read new octree data info"; qCDebug(domain_server) << "Failed to read new octree data info";
} }
@ -1954,13 +1944,12 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
auto nodeList = DependencyManager::get<LimitedNodeList>(); auto nodeList = DependencyManager::get<LimitedNodeList>();
auto getSetting = [this](QString keyPath, QVariant& value) -> bool { auto getSetting = [this](QString keyPath, QVariant value) -> bool {
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
QVariant* var = valueForKeyPath(settingsMap, keyPath); value = _settingsManager.valueForKeyPath(keyPath);
if (var == nullptr) { if (!value.isValid()) {
return false; return false;
} }
value = *var;
return true; return true;
}; };
@ -2028,8 +2017,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
const QString URI_WIZARD = "/wizard/"; const QString URI_WIZARD = "/wizard/";
const QString WIZARD_COMPLETED_ONCE_KEY_PATH = "wizard.completed_once"; const QString WIZARD_COMPLETED_ONCE_KEY_PATH = "wizard.completed_once";
const QVariant* wizardCompletedOnce = valueForKeyPath(_settingsManager.getSettingsMap(), WIZARD_COMPLETED_ONCE_KEY_PATH); QVariant wizardCompletedOnce = _settingsManager.valueForKeyPath(WIZARD_COMPLETED_ONCE_KEY_PATH);
const bool completedOnce = wizardCompletedOnce && wizardCompletedOnce->toBool(); const bool completedOnce = wizardCompletedOnce.isValid() && wizardCompletedOnce.toBool();
if (url.path() != URI_WIZARD && url.path().endsWith('/') && !completedOnce) { if (url.path() != URI_WIZARD && url.path().endsWith('/') && !completedOnce) {
// First visit, redirect to the wizard // First visit, redirect to the wizard
@ -2326,8 +2315,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return true; return true;
} else if (url.path() == "/domain_settings") { } else if (url.path() == "/domain_settings") {
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant) { if (!accessTokenVariant.isValid()) {
connection->respond(HTTPConnection::StatusCode400); connection->respond(HTTPConnection::StatusCode400);
return true; return true;
} }
@ -2360,8 +2349,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + domainID, "domain", return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + domainID, "domain",
{ }, { "network_address", "network_port", "label" }); { }, { "network_address", "network_port", "label" });
} else if (url.path() == URI_API_PLACES) { } else if (url.path() == URI_API_PLACES) {
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant->isValid()) { if (!accessTokenVariant.isValid()) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set"); connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
return true; 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 }; 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); QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); 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."; 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() 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); QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)"; 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); 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." qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication."
<< "These cannot be combined - using OAuth for 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); DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
QString profileUsername = sessionData.getUsername(); 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 // this is an authenticated user
return true; return true;
} }
// loop the roles of this user and see if they are in the admin-roles array // 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()) { if (!adminRolesArray.isEmpty()) {
foreach(const QString& userRole, sessionData.getRoles()) { 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 // we don't know about this user yet, so they are not yet authenticated
return false; 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 // config file contains username and password combinations for basic auth
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization"; const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
@ -2698,10 +2688,10 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
QString headerPassword = credentialList[1]; QString headerPassword = credentialList[1];
// we've pulled a username and password - now check if there is a match in our basic auth hash // 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(); QString settingsUsername = _settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).toString();
const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH); QVariant settingsPasswordVariant = _settingsManager.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH);
QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : ""; QString settingsPassword = settingsPasswordVariant.isValid() ? settingsPasswordVariant.toString() : "";
QString hexHeaderPassword = headerPassword.isEmpty() ? QString hexHeaderPassword = headerPassword.isEmpty() ?
"" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); "" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
@ -2838,13 +2828,14 @@ ReplicationServerInfo serverInformationFromSettings(QVariantMap serverMap, Repli
} }
void DomainServer::updateReplicationNodes(ReplicationServerDirection direction) { 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<LimitedNodeList>(); auto nodeList = DependencyManager::get<LimitedNodeList>();
std::vector<HifiSockAddr> replicationNodesInSettings; std::vector<HifiSockAddr> replicationNodesInSettings;
auto replicationSettings = settings.value(BROADCASTING_SETTINGS_KEY).toMap(); auto replicationSettings = broadcastSettingsVariant.toMap();
QString serversKey = direction == Upstream ? "upstream_servers" : "downstream_servers"; QString serversKey = direction == Upstream ? "upstream_servers" : "downstream_servers";
QString replicationDirection = direction == Upstream ? "upstream" : "downstream"; QString replicationDirection = direction == Upstream ? "upstream" : "downstream";
@ -2920,13 +2911,12 @@ void DomainServer::updateUpstreamNodes() {
void DomainServer::updateReplicatedNodes() { void DomainServer::updateReplicatedNodes() {
// Make sure we have downstream nodes in our list // Make sure we have downstream nodes in our list
auto settings = _settingsManager.getSettingsMap();
static const QString REPLICATED_USERS_KEY = "users"; static const QString REPLICATED_USERS_KEY = "users";
_replicatedUsernames.clear(); _replicatedUsernames.clear();
if (settings.contains(BROADCASTING_SETTINGS_KEY)) { auto replicationVariant = _settingsManager.valueForKeyPath(BROADCASTING_SETTINGS_KEY);
auto replicationSettings = settings.value(BROADCASTING_SETTINGS_KEY).toMap(); if (replicationVariant.isValid()) {
auto replicationSettings = replicationVariant.toMap();
if (replicationSettings.contains(REPLICATED_USERS_KEY)) { if (replicationSettings.contains(REPLICATED_USERS_KEY)) {
auto usersSettings = replicationSettings.value(REPLICATED_USERS_KEY).toList(); auto usersSettings = replicationSettings.value(REPLICATED_USERS_KEY).toList();
for (auto& username : usersSettings) { for (auto& username : usersSettings) {
@ -3114,17 +3104,17 @@ void DomainServer::processPathQueryPacket(QSharedPointer<ReceivedMessage> messag
// check out paths in the _configMap to see if we have a match // 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); 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 // we got a match, respond with the resulting viewpoint
auto nodeList = DependencyManager::get<LimitedNodeList>(); auto nodeList = DependencyManager::get<LimitedNodeList>();
QString responseViewpoint; QString responseViewpoint;
// if we didn't match the path BUT this is for the index path then send back our default // if we didn't match the path BUT this is for the index path then send back our default
if (pathMatch) { if (pathMatch.isValid()) {
responseViewpoint = pathMatch->toMap()[PATH_VIEWPOINT_KEY].toString(); responseViewpoint = pathMatch.toMap()[PATH_VIEWPOINT_KEY].toString();
} else { } else {
const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1"; const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1";
responseViewpoint = DEFAULT_INDEX_PATH; responseViewpoint = DEFAULT_INDEX_PATH;

View file

@ -38,6 +38,9 @@
#include "DomainServerNodeData.h" #include "DomainServerNodeData.h"
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; 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 DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default"; const QString SETTING_DEFAULT_KEY = "default";
@ -190,6 +193,9 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
} }
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) { void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
// since we're called from the DomainServerSettingsManager constructor, we don't take a write lock here
// even though we change the underlying config map
_argumentList = argumentList; _argumentList = argumentList;
_configMap.loadConfig(_argumentList); _configMap.loadConfig(_argumentList);
@ -448,17 +454,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
unpackPermissions(); unpackPermissions();
} }
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
static const QString DESCRIPTORS{ "descriptors" };
auto& settingsMap = getSettingsMap();
if (!getSettingsMap().contains(DESCRIPTORS)) {
settingsMap.insert(DESCRIPTORS, QVariantMap());
}
return *static_cast<QVariantMap*>(getSettingsMap()[DESCRIPTORS].data());
}
void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows, void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows,
QString groupName, NodePermissionsPointer perms) { 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 // 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, void DomainServerSettingsManager::packPermissionsForMap(QString mapName,
NodePermissionsMap& permissionsRows, NodePermissionsMap& permissionsRows,
QString keyPath) { 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 // find (or create) the "security" section of the settings map
QVariant* security = _configMap.valueForKeyPath("security", true); QVariant* security = _configMap.valueForKeyPath("security", true);
if (!security->canConvert(QMetaType::QVariantMap)) { if (!security->canConvert(QMetaType::QVariantMap)) {
@ -576,15 +574,20 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key
mapPointer->clear(); mapPointer->clear();
QVariant* permissions = _configMap.valueForKeyPath(keyPath, true); QVariant permissions = valueOrDefaultValueForKeyPath(keyPath);
if (!permissions->canConvert(QMetaType::QVariantList)) {
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."; qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings.";
(*permissions) = QVariantList();
} }
bool needPack = false; bool needPack = false;
QList<QVariant> permissionsList = permissions->toList(); QList<QVariant> permissionsList = permissions.toList();
foreach (QVariant permsHash, permissionsList) { foreach (QVariant permsHash, permissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID(); QString id = perms->getID();
@ -611,6 +614,11 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key
void DomainServerSettingsManager::unpackPermissions() { void DomainServerSettingsManager::unpackPermissions() {
// transfer details from _configMap to _agentPermissions // 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; bool needPack = false;
needPack |= unpackPermissionsForKeypath(AGENT_STANDARD_PERMISSIONS_KEYPATH, &_standardAgentPermissions); 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(); needPack |= ensurePermissionsForGroupRanks();
if (needPack) { if (needPack) {
packPermissions(); packPermissions();
} }
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "--------------- permissions ---------------------"; qDebug() << "--------------- permissions ---------------------";
QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets; std::list<NodePermissionsMap*> permissionsSets {
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() &_standardAgentPermissions, &_agentPermissions,
<< _groupPermissions.get() << _groupForbiddens.get() &_groupPermissions, &_groupForbiddens,
<< _ipPermissions.get() << _macPermissions.get() &_ipPermissions, &_macPermissions,
<< _machineFingerprintPermissions.get(); &_machineFingerprintPermissions
};
foreach (auto permissionSet, permissionsSets) { foreach (auto permissionSet, permissionsSets) {
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(permissionSet); auto& permissionKeyMap = permissionSet->get();
while (i.hasNext()) { auto it = permissionKeyMap.begin();
i.next();
NodePermissionsPointer perms = i.value(); while (it != permissionKeyMap.end()) {
NodePermissionsPointer perms = it->second;
if (perms->isGroup()) { if (perms->isGroup()) {
qDebug() << i.key() << perms->getGroupID() << perms; qDebug() << it->first << perms->getGroupID() << perms;
} else { } else {
qDebug() << i.key() << perms; qDebug() << it->first << perms;
} }
++it;
} }
} }
#endif #endif
} }
bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() {
@ -1068,12 +1058,22 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid&
return getForbiddensForGroup(groupKey.first, groupKey.second); 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) { QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) {
QReadLocker locker(&_settingsLock);
const QVariant* foundValue = _configMap.valueForKeyPath(keyPath); const QVariant* foundValue = _configMap.valueForKeyPath(keyPath);
if (foundValue) { if (foundValue) {
return *foundValue; return *foundValue;
} else { } else {
// we don't need the settings lock anymore since we're done reading from the config map
locker.unlock();
int dotIndex = keyPath.indexOf('.'); int dotIndex = keyPath.indexOf('.');
QString groupKey = keyPath.mid(0, dotIndex); 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 // we recurse one level deep below each group for the appropriate setting
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType); bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType);
// store whatever the current _settingsMap is to file
persistToFile();
// return success to the caller // return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}"; QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
@ -1216,16 +1213,9 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
} }
bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) { bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) {
if (thread() != QThread::currentThread()) {
bool success;
BLOCKING_INVOKE_METHOD(this, "restoreSettingsFromObject", // grab a write lock since we're about to change the settings map
Q_RETURN_ARG(bool, success), QWriteLocker locker(&_settingsLock);
Q_ARG(QJsonObject, settingsToRestore),
Q_ARG(SettingsType, settingsType));
return success;
}
QJsonArray* filteredDescriptionArray = settingsType == DomainSettings QJsonArray* filteredDescriptionArray = settingsType == DomainSettings
? &_domainSettingsDescription : &_contentSettingsDescription; ? &_domainSettingsDescription : &_contentSettingsDescription;
@ -1341,6 +1331,10 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings
} else { } else {
// restore completed, persist the new settings // restore completed, persist the new settings
qDebug() << "Restore completed, persisting restored settings to file"; 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(); persistToFile();
return true; return true;
} }
@ -1352,20 +1346,6 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
bool includeDefaults, bool isForBackup) { bool includeDefaults, bool isForBackup) {
QJsonObject responseObject; 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) { if (!typeValue.isEmpty() || isAuthenticated) {
// convert the string type value to a QJsonValue // convert the string type value to a QJsonValue
QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt()); 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) // only enumerate the requested settings type (domain setting or content setting)
QJsonArray* filteredDescriptionArray = &_descriptionArray; QJsonArray* filteredDescriptionArray = &_descriptionArray;
if (includeDomainSettings && !includeContentSettings) { if (includeDomainSettings && !includeContentSettings) {
filteredDescriptionArray = &_domainSettingsDescription; filteredDescriptionArray = &_domainSettingsDescription;
} else if (includeContentSettings && !includeDomainSettings) { } else if (includeContentSettings && !includeDomainSettings) {
@ -1413,21 +1394,21 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
QVariant variantValue; QVariant variantValue;
if (!groupKey.isEmpty()) { if (!groupKey.isEmpty()) {
QVariant settingsMapGroupValue = _configMap.value(groupKey); QVariant settingsMapGroupValue = valueForKeyPath(groupKey);
if (!settingsMapGroupValue.isNull()) { if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingName); variantValue = settingsMapGroupValue.toMap().value(settingName);
} }
} else { } else {
variantValue = _configMap.value(settingName); variantValue = valueForKeyPath(settingName);
} }
// final check for inclusion // final check for inclusion
// either we include default values or we don't but this isn't a default value // 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; QJsonValue result;
if (variantValue.isNull()) { if (!variantValue.isValid()) {
// no value for this setting, pass the default // no value for this setting, pass the default
if (settingObject.contains(SETTING_DEFAULT_KEY)) { if (settingObject.contains(SETTING_DEFAULT_KEY)) {
result = settingObject[SETTING_DEFAULT_KEY]; result = settingObject[SETTING_DEFAULT_KEY];
@ -1566,6 +1547,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
SettingsType settingsType) { 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 SECURITY_ROOT_KEY = "security";
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
static const QString BROADCASTING_KEY = "broadcasting"; 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; return needRestart;
} }
@ -1689,6 +1680,9 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
} }
void DomainServerSettingsManager::sortPermissions() { void DomainServerSettingsManager::sortPermissions() {
// take a write lock since we're about to change the config map data
QWriteLocker locker(&_settingsLock);
// sort the permission-names // sort the permission-names
QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH); QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH);
if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) { if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) {
@ -1725,11 +1719,15 @@ void DomainServerSettingsManager::persistToFile() {
QFile settingsFile(_configMap.getUserConfigFilename()); QFile settingsFile(_configMap.getUserConfigFilename());
if (settingsFile.open(QIODevice::WriteOnly)) { 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()); settingsFile.write(QJsonDocument::fromVariant(_configMap.getConfig()).toJson());
} else { } else {
qCritical("Could not write to JSON settings file. Unable to persist settings."); qCritical("Could not write to JSON settings file. Unable to persist settings.");
// failed to write, reload whatever the current config state is // 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); _configMap.loadConfig(_argumentList);
} }
} }

View file

@ -27,9 +27,6 @@
const QString SETTINGS_PATHS_KEY = "paths"; 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_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions"; const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
@ -53,11 +50,12 @@ public:
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url); bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void setupConfigMap(const QStringList& argumentList); 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); QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
QVariant valueForKeyPath(const QString& keyPath);
QVariantMap& getSettingsMap() { return _configMap.getConfig(); } bool containsKeyPath(const QString& keyPath) { return valueForKeyPath(keyPath).isValid(); }
QVariantMap& getDescriptorsMap();
// these give access to anonymous/localhost/logged-in settings from the domain-server settings page // 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); } 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 /// thread safe method to restore settings from a JSON object
Q_INVOKABLE bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType); Q_INVOKABLE bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType);
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
signals: signals:
void updateNodePermissions(); void updateNodePermissions();
void settingsUpdated(); void settingsUpdated();
@ -138,12 +138,13 @@ private:
QStringList _argumentList; QStringList _argumentList;
QJsonArray filteredDescriptionArray(bool isContentSettings); QJsonArray filteredDescriptionArray(bool isContentSettings);
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription); const QJsonObject& settingDescription);
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName); QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
void sortPermissions(); 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 persistToFile();
void splitSettingsDescription(); void splitSettingsDescription();
@ -155,10 +156,10 @@ private:
QJsonArray _contentSettingsDescription; QJsonArray _contentSettingsDescription;
QJsonObject _settingsMenuGroups; 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; HifiConfigVariantMap _configMap;
friend class DomainServer;
// these cause calls to metaverse's group api // these cause calls to metaverse's group api
void apiGetGroupID(const QString& groupName); void apiGetGroupID(const QString& groupName);
void apiGetGroupRanks(const QUuid& groupID); void apiGetGroupRanks(const QUuid& groupID);
@ -192,6 +193,9 @@ private:
// keep track of answers to api queries about which users are in which groups // keep track of answers to api queries about which users are in which groups
QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>> QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>>
/// guard read/write access from multiple threads to settings
QReadWriteLock _settingsLock { QReadWriteLock::Recursive };
}; };
#endif // hifi_DomainServerSettingsManager_h #endif // hifi_DomainServerSettingsManager_h