make DomainServerSettingsManager thread-safe for use in content backup

This commit is contained in:
Stephen Birarda 2018-02-16 14:09:00 -08:00
parent d9f2e1986f
commit 1c053730eb
6 changed files with 153 additions and 152 deletions

View file

@ -58,7 +58,7 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire
_lastCheck(usecTimestampNow())
{
setObjectName("DomainContentBackupManager");
// Make sure the backup directory exists.
QDir(_backupDirectory).mkpath(".");

View file

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

View file

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

View file

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

View file

@ -38,6 +38,9 @@
#include "DomainServerNodeData.h"
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json";
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
@ -190,6 +193,9 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
}
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;
_configMap.loadConfig(_argumentList);
@ -448,17 +454,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
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,
QString groupName, NodePermissionsPointer perms) {
// this is called when someone has used the domain-settings webpage to add a group. They type the group's name
@ -487,6 +482,9 @@ void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap&
void DomainServerSettingsManager::packPermissionsForMap(QString mapName,
NodePermissionsMap& permissionsRows,
QString keyPath) {
// grab a write lock on the settings mutex since we're about to change the config map
QWriteLocker locker(&_settingsLock);
// find (or create) the "security" section of the settings map
QVariant* security = _configMap.valueForKeyPath("security", true);
if (!security->canConvert(QMetaType::QVariantMap)) {
@ -576,15 +574,15 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key
mapPointer->clear();
QVariant* permissions = _configMap.valueForKeyPath(keyPath, true);
if (!permissions->canConvert(QMetaType::QVariantList)) {
QVariant permissions = valueForKeyPath(keyPath);
if (!permissions.canConvert(QMetaType::QVariantList)) {
qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings.";
(*permissions) = QVariantList();
}
bool needPack = false;
QList<QVariant> permissionsList = permissions->toList();
QList<QVariant> permissionsList = permissions.toList();
foreach (QVariant permsHash, permissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
@ -1068,12 +1066,22 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid&
return getForbiddensForGroup(groupKey.first, groupKey.second);
}
QVariant DomainServerSettingsManager::valueForKeyPath(const QString& keyPath) {
QReadLocker locker(&_settingsLock);
auto foundValue = _configMap.valueForKeyPath(keyPath);
return foundValue ? *foundValue : QVariant();
}
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) {
QReadLocker locker(&_settingsLock);
const QVariant* foundValue = _configMap.valueForKeyPath(keyPath);
if (foundValue) {
return *foundValue;
} else {
// we don't need the settings lock anymore since we're done reading from the config map
_settingsLock.unlock();
int dotIndex = keyPath.indexOf('.');
QString groupKey = keyPath.mid(0, dotIndex);
@ -1112,9 +1120,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
// we recurse one level deep below each group for the appropriate setting
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType);
// store whatever the current _settingsMap is to file
persistToFile();
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
@ -1216,16 +1221,9 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
}
bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) {
if (thread() != QThread::currentThread()) {
bool success;
BLOCKING_INVOKE_METHOD(this, "restoreSettingsFromObject",
Q_RETURN_ARG(bool, success),
Q_ARG(QJsonObject, settingsToRestore),
Q_ARG(SettingsType, settingsType));
return success;
}
// grab a write lock since we're about to change the settings map
QWriteLocker locker(&_settingsLock);
QJsonArray* filteredDescriptionArray = settingsType == DomainSettings
? &_domainSettingsDescription : &_contentSettingsDescription;
@ -1341,6 +1339,10 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings
} else {
// restore completed, persist the new settings
qDebug() << "Restore completed, persisting restored settings to file";
// let go of the write lock since we're done making changes to the config map
locker.unlock();
persistToFile();
return true;
}
@ -1352,20 +1354,6 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
bool includeDefaults, bool isForBackup) {
QJsonObject responseObject;
if (thread() != QThread::currentThread()) {
BLOCKING_INVOKE_METHOD(this, "settingsResponseObjectForType",
Q_RETURN_ARG(QJsonObject, responseObject),
Q_ARG(QString, typeValue),
Q_ARG(bool, isAuthenticated),
Q_ARG(bool, includeDomainSettings),
Q_ARG(bool, includeContentSettings),
Q_ARG(bool, includeDefaults),
Q_ARG(bool, isForBackup));
return responseObject;
}
if (!typeValue.isEmpty() || isAuthenticated) {
// convert the string type value to a QJsonValue
QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt());
@ -1414,21 +1402,21 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
QVariant variantValue;
if (!groupKey.isEmpty()) {
QVariant settingsMapGroupValue = _configMap.value(groupKey);
QVariant settingsMapGroupValue = valueForKeyPath(groupKey);
if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingName);
}
} else {
variantValue = _configMap.value(settingName);
variantValue = valueForKeyPath(settingName);
}
// final check for inclusion
// either we include default values or we don't but this isn't a default value
if (includeDefaults || !variantValue.isNull()) {
if (includeDefaults || variantValue.isValid()) {
QJsonValue result;
if (variantValue.isNull()) {
if (!variantValue.isValid()) {
// no value for this setting, pass the default
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
result = settingObject[SETTING_DEFAULT_KEY];
@ -1567,6 +1555,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
SettingsType settingsType) {
// take a write lock since we're about to overwrite settings in the config map
QWriteLocker locker(&_settingsLock);
static const QString SECURITY_ROOT_KEY = "security";
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
static const QString BROADCASTING_KEY = "broadcasting";
@ -1664,6 +1656,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;
}
@ -1690,6 +1688,9 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
}
void DomainServerSettingsManager::sortPermissions() {
// take a write lock since we're about to change the config map data
QWriteLocker locker(&_settingsLock);
// sort the permission-names
QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH);
if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) {
@ -1726,11 +1727,15 @@ void DomainServerSettingsManager::persistToFile() {
QFile settingsFile(_configMap.getUserConfigFilename());
if (settingsFile.open(QIODevice::WriteOnly)) {
// take a read lock so we can grab the config and write it to file
QReadLocker locker(&_settingsLock);
settingsFile.write(QJsonDocument::fromVariant(_configMap.getConfig()).toJson());
} else {
qCritical("Could not write to JSON settings file. Unable to persist settings.");
// failed to write, reload whatever the current config state is
// with a write lock since we're about to overwrite the config map
QWriteLocker locker(&_settingsLock);
_configMap.loadConfig(_argumentList);
}
}

View file

@ -27,9 +27,6 @@
const QString SETTINGS_PATHS_KEY = "paths";
const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json";
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
@ -53,11 +50,12 @@ public:
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void setupConfigMap(const QStringList& argumentList);
// each of the three methods in this group takes a read lock of _settingsLock
// and cannot be called when the a write lock is held by the same thread
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
QVariantMap& getSettingsMap() { return _configMap.getConfig(); }
QVariantMap& getDescriptorsMap();
QVariant valueForKeyPath(const QString& keyPath);
bool containsKeyPath(const QString& keyPath) { return valueForKeyPath(keyPath).isValid(); }
// these give access to anonymous/localhost/logged-in settings from the domain-server settings page
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); }
@ -119,6 +117,8 @@ public:
/// thread safe method to restore settings from a JSON object
Q_INVOKABLE bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType);
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
signals:
void updateNodePermissions();
void settingsUpdated();
@ -138,12 +138,13 @@ private:
QStringList _argumentList;
QJsonArray filteredDescriptionArray(bool isContentSettings);
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription);
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
void sortPermissions();
// you cannot be holding the _settingsLock when persisting to file from the same thread
// since it may take either a read lock or write lock and recursive locking doesn't allow a change in type
void persistToFile();
void splitSettingsDescription();
@ -155,10 +156,10 @@ private:
QJsonArray _contentSettingsDescription;
QJsonObject _settingsMenuGroups;
// any method that calls _valueForKeyPath on this _configMap must get a write lock it keeps until it
// is done with the returned QVariant*
HifiConfigVariantMap _configMap;
friend class DomainServer;
// these cause calls to metaverse's group api
void apiGetGroupID(const QString& groupName);
void apiGetGroupRanks(const QUuid& groupID);
@ -192,6 +193,9 @@ private:
// keep track of answers to api queries about which users are in which groups
QHash<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