diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js
index 499e858297..8f97da1ff4 100644
--- a/domain-server/resources/web/js/settings.js
+++ b/domain-server/resources/web/js/settings.js
@@ -119,7 +119,7 @@ function reloadSettings() {
var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!";
-$('#settings').on('click', 'button', function(e){
+$('body').on('click', '.save-button', function(e){
// disable any inputs not changed
$("input:not([data-changed])").each(function(){
$(this).prop('disabled', true);
@@ -133,6 +133,9 @@ $('#settings').on('click', 'button', function(e){
$(this).prop('disabled', false);
});
+ // remove focus from the button
+ $(this).blur()
+
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
$.ajax('/settings.json', {
data: JSON.stringify(formJSON),
diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml
index 66cdd36573..d658bd0712 100644
--- a/domain-server/resources/web/settings/index.shtml
+++ b/domain-server/resources/web/settings/index.shtml
@@ -22,7 +22,7 @@
-
+
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index e47c75a51a..24e23b693c 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -48,13 +48,14 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_cookieSessionHash(),
_settingsManager()
{
-
LogUtils::init();
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("domain-server");
QSettings::setDefaultFormat(QSettings::IniFormat);
+
+ _settingsManager.loadSettingsMap(arguments());
installNativeEventFilter(&_shutdownEventListener);
connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit()));
@@ -62,8 +63,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qRegisterMetaType("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators("DomainServerWebSessionData");
- _argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
-
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
// we either read a certificate and private key or were not passed one
// and completed login or did not need to
@@ -83,8 +82,8 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
const QString X509_PRIVATE_KEY_OPTION = "key";
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
- QString certPath = _argumentVariantMap.value(X509_CERTIFICATE_OPTION).toString();
- QString keyPath = _argumentVariantMap.value(X509_PRIVATE_KEY_OPTION).toString();
+ QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString();
+ QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString();
if (!certPath.isEmpty() && !keyPath.isEmpty()) {
// the user wants to use DTLS to encrypt communication with nodes
@@ -143,10 +142,11 @@ bool DomainServer::optionallySetupOAuth() {
const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
- _oauthProviderURL = QUrl(_argumentVariantMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
- _oauthClientID = _argumentVariantMap.value(OAUTH_CLIENT_ID_OPTION).toString();
+ const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
+ _oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
+ _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
- _hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
+ _hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString();
if (!_oauthClientID.isEmpty()) {
if (_oauthProviderURL.isEmpty()
@@ -171,9 +171,11 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
const QString CUSTOM_PORT_OPTION = "port";
unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT;
+
+ const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
- if (_argumentVariantMap.contains(CUSTOM_PORT_OPTION)) {
- domainServerPort = (unsigned short) _argumentVariantMap.value(CUSTOM_PORT_OPTION).toUInt();
+ if (settingsMap.contains(CUSTOM_PORT_OPTION)) {
+ domainServerPort = (unsigned short) settingsMap.value(CUSTOM_PORT_OPTION).toUInt();
}
unsigned short domainServerDTLSPort = 0;
@@ -183,8 +185,8 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port";
- if (_argumentVariantMap.contains(CUSTOM_DTLS_PORT_OPTION)) {
- domainServerDTLSPort = (unsigned short) _argumentVariantMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt();
+ if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) {
+ domainServerDTLSPort = (unsigned short) settingsMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt();
}
}
@@ -197,7 +199,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
// set our LimitedNodeList UUID to match the UUID from our config
// nodes will currently use this to add resources to data-web that relate to our domain
- nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString());
+ nodeList->setSessionUUID(settingsMap.value(DOMAIN_CONFIG_ID_KEY).toString());
connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded);
connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled);
@@ -260,9 +262,10 @@ bool DomainServer::hasOAuthProviderAndAuthInformation() {
bool DomainServer::optionallySetupAssignmentPayment() {
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
+ const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
- if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
- _argumentVariantMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
+ if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
+ settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
@@ -288,8 +291,10 @@ bool DomainServer::optionallySetupAssignmentPayment() {
void DomainServer::setupDynamicIPAddressUpdating() {
const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip";
- if (_argumentVariantMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
- _argumentVariantMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
+ const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
+
+ if (settingsMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
+ settingsMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
@@ -338,9 +343,11 @@ void DomainServer::parseAssignmentConfigs(QSet& excludedTypes)
// check for configs from the command line, these take precedence
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
+
+ const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
// scan for assignment config keys
- QStringList variantMapKeys = _argumentVariantMap.keys();
+ QStringList variantMapKeys = settingsMap.keys();
int configIndex = variantMapKeys.indexOf(assignmentConfigRegex);
while (configIndex != -1) {
@@ -348,7 +355,7 @@ void DomainServer::parseAssignmentConfigs(QSet& excludedTypes)
Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt();
if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) {
- QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]];
+ QVariant mapValue = settingsMap[variantMapKeys[configIndex]];
QJsonArray assignmentArray;
if (mapValue.type() == QVariant::String) {
@@ -513,7 +520,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
QString connectedUsername;
- if (!isAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
+ if (!isAssignment && !_oauthProviderURL.isEmpty() && _settingsManager.getSettingsMap().contains(ALLOWED_ROLES_CONFIG_KEY)) {
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
if (_sessionAuthenticationHash.contains(packetUUID)) {
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
@@ -1392,8 +1399,10 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
+ const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
+
if (!_oauthProviderURL.isEmpty()
- && (_argumentVariantMap.contains(ADMIN_USERS_CONFIG_KEY) || _argumentVariantMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
+ && (settingsMap.contains(ADMIN_USERS_CONFIG_KEY) || settingsMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
@@ -1404,7 +1413,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
cookieUUID = cookieUUIDRegex.cap(1);
}
- if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
+ if (settingsMap.contains(BASIC_AUTH_CONFIG_KEY)) {
qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication."
<< "These cannot be combined - using OAuth for authentication.";
}
@@ -1414,13 +1423,13 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
QString profileUsername = sessionData.getUsername();
- if (_argumentVariantMap.value(ADMIN_USERS_CONFIG_KEY).toJsonValue().toArray().contains(profileUsername)) {
+ if (settingsMap.value(ADMIN_USERS_CONFIG_KEY).toJsonValue().toArray().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
- QJsonArray adminRolesArray = _argumentVariantMap.value(ADMIN_ROLES_CONFIG_KEY).toJsonValue().toArray();
+ QJsonArray adminRolesArray = settingsMap.value(ADMIN_ROLES_CONFIG_KEY).toJsonValue().toArray();
if (!adminRolesArray.isEmpty()) {
foreach(const QString& userRole, sessionData.getRoles()) {
@@ -1455,7 +1464,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 (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
+ } else if (settingsMap.contains(BASIC_AUTH_CONFIG_KEY)) {
// config file contains username and password combinations for basic auth
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
@@ -1474,7 +1483,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
QString password = credentialList[1];
// we've pulled a username and password - now check if there is a match in our basic auth hash
- QJsonObject basicAuthObject = _argumentVariantMap.value(BASIC_AUTH_CONFIG_KEY).toJsonValue().toObject();
+ QJsonObject basicAuthObject = settingsMap.value(BASIC_AUTH_CONFIG_KEY).toJsonValue().toObject();
if (basicAuthObject.contains(username)) {
const QString BASIC_AUTH_USER_PASSWORD_KEY = "password";
@@ -1557,7 +1566,8 @@ void DomainServer::handleProfileRequestFinished() {
// pull the user roles from the response
QJsonArray userRolesArray = profileJSON.object()["data"].toObject()["user"].toObject()["roles"].toArray();
- QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
+ QJsonArray allowedRolesArray = _settingsManager.getSettingsMap()
+ .value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
QString connectableUsername;
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index f2b1b85e09..95d508c94a 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -115,8 +115,6 @@ private:
QHash _pendingAssignedNodes;
TransactionHash _pendingAssignmentCredits;
- QVariantMap _argumentVariantMap;
-
bool _isUsingDTLS;
QUrl _oauthProviderURL;
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index f5f0bec2c5..63d034bf2f 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -10,19 +10,21 @@
//
#include
+#include
#include
#include
#include
+#include
#include
#include
#include
+#include
#include
#include "DomainServerSettingsManager.h"
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
-const QString SETTINGS_JSON_FILE_RELATIVE_PATH = "/resources/settings.json";
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray(),
@@ -33,20 +35,20 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
descriptionFile.open(QIODevice::ReadOnly);
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
+}
+
+void DomainServerSettingsManager::loadSettingsMap(const QStringList& argumentList) {
+ _settingsMap = HifiConfigVariantMap::mergeMasterConfigWithUserConfig(argumentList);
- // load the existing config file to get the current values
- QFile configFile(QCoreApplication::applicationDirPath() + SETTINGS_JSON_FILE_RELATIVE_PATH);
+ qDebug() << _settingsMap;
- if (configFile.exists()) {
- configFile.open(QIODevice::ReadOnly);
-
- _settingsMap = QJsonDocument::fromJson(configFile.readAll()).toVariant().toMap();
- }
+ // figure out where we are supposed to persist our settings to
+ _settingsFilepath = HifiConfigVariantMap::userConfigFilepath(argumentList);
}
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
-const QString SETTINGS_GROUP_KEY_NAME = "key";
+const QString SETTINGS_GROUP_KEY_NAME = "name";
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
@@ -209,7 +211,15 @@ QByteArray DomainServerSettingsManager::getJSONSettingsMap() const {
}
void DomainServerSettingsManager::persistToFile() {
- QFile settingsFile(QCoreApplication::applicationDirPath() + SETTINGS_JSON_FILE_RELATIVE_PATH);
+
+ // make sure we have the dir the settings file is supposed to live in
+ QFileInfo settingsFileInfo(_settingsFilepath);
+
+ if (!settingsFileInfo.dir().exists()) {
+ settingsFileInfo.dir().mkpath(".");
+ }
+
+ QFile settingsFile(_settingsFilepath);
if (settingsFile.open(QIODevice::WriteOnly)) {
settingsFile.write(getJSONSettingsMap());
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index 9fd9df908c..0b97a821ef 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -24,7 +24,10 @@ public:
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
+ void loadSettingsMap(const QStringList& argumentList);
+
QByteArray getJSONSettingsMap() const;
+ const QVariantMap& getSettingsMap() const { return _settingsMap; }
private:
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
QJsonArray descriptionArray);
@@ -32,6 +35,7 @@ private:
QJsonArray _descriptionArray;
QVariantMap _settingsMap;
+ QString _settingsFilepath;
};
#endif // hifi_DomainServerSettingsManager_h
\ No newline at end of file
diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp
index 6163bd4d8c..baf7d51d01 100644
--- a/libraries/shared/src/HifiConfigVariantMap.cpp
+++ b/libraries/shared/src/HifiConfigVariantMap.cpp
@@ -82,26 +82,78 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
QCoreApplication::applicationName());
}
- QFile configFile(configFilePath);
- if (configFile.exists()) {
- qDebug() << "Reading JSON config file at" << configFilePath;
- configFile.open(QIODevice::ReadOnly);
-
- QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
- QJsonObject rootObject = configDocument.object();
-
- // enumerate the keys of the configDocument object
- foreach(const QString& key, rootObject.keys()) {
-
- if (!mergedMap.contains(key)) {
- // no match in existing list, add it
- mergedMap.insert(key, QVariant(rootObject[key]));
- }
- }
- } else {
- qDebug() << "Could not find JSON config file at" << configFilePath;
- }
return mergedMap;
}
+
+QVariantMap HifiConfigVariantMap::mergeMasterConfigWithUserConfig(const QStringList& argumentList) {
+ // check if there is a master config file
+ const QString MASTER_CONFIG_FILE_OPTION = "--master-config";
+
+ QVariantMap configVariantMap;
+
+ int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION);
+ if (masterConfigIndex != -1) {
+ QString masterConfigFilepath = argumentList[masterConfigIndex + 1];
+
+ mergeMapWithJSONFile(configVariantMap, masterConfigFilepath);
+ }
+
+ // merge the existing configVariantMap with the user config file
+ mergeMapWithJSONFile(configVariantMap, userConfigFilepath(argumentList));
+
+ return configVariantMap;
+}
+
+QString HifiConfigVariantMap::userConfigFilepath(const QStringList& argumentList) {
+ // we've loaded up the master config file, now fill in anything it didn't have with the user config file
+ const QString USER_CONFIG_FILE_OPTION = "--user-config";
+
+ int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION);
+ QString userConfigFilepath;
+ if (userConfigIndex != -1) {
+ userConfigFilepath = argumentList[userConfigIndex + 1];
+ } else {
+ userConfigFilepath = QString("%1/%2/%3/config.json").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
+ QCoreApplication::organizationName(),
+ QCoreApplication::applicationName());
+ }
+
+ return userConfigFilepath;
+}
+
+void HifiConfigVariantMap::mergeMapWithJSONFile(QVariantMap& existingMap, const QString& filename) {
+ QFile configFile(filename);
+
+ if (configFile.exists()) {
+ qDebug() << "Reading JSON config file at" << filename;
+ configFile.open(QIODevice::ReadOnly);
+
+ QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
+
+ if (existingMap.isEmpty()) {
+ existingMap = configDocument.toVariant().toMap();
+ } else {
+ addMissingValuesToExistingMap(existingMap, configDocument.toVariant().toMap());
+ }
+
+ } else {
+ qDebug() << "Could not find JSON config file at" << filename;
+ }
+}
+
+void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap) {
+ foreach(const QString& key, newMap.keys()) {
+ if (existingMap.contains(key)) {
+ // if this is just a regular value, we're done - we don't ovveride
+
+ if (newMap[key].canConvert(QMetaType::QVariantMap) && existingMap[key].canConvert(QMetaType::QVariantMap)) {
+ // there's a variant map below and the existing map has one too, so we need to keep recursing
+ addMissingValuesToExistingMap(reinterpret_cast(existingMap[key]), newMap[key].toMap());
+ }
+ } else {
+ existingMap[key] = newMap[key];
+ }
+ }
+}
diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h
index 378aa749c5..eae5de26d5 100644
--- a/libraries/shared/src/HifiConfigVariantMap.h
+++ b/libraries/shared/src/HifiConfigVariantMap.h
@@ -17,6 +17,11 @@
class HifiConfigVariantMap {
public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
+ static QVariantMap mergeMasterConfigWithUserConfig(const QStringList& argumentList);
+ static QString userConfigFilepath(const QStringList& argumentList);
+private:
+ static void mergeMapWithJSONFile(QVariantMap& existingMap, const QString& filename);
+ static void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
};
#endif // hifi_HifiConfigVariantMap_h