//// DomainServerSettingsManager.cpp// domain-server/src//// Created by Stephen Birarda on 2014-06-24.// Copyright 2014 High Fidelity, Inc.// Copyright 2020 Vircadia contributors.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "DomainServerSettingsManager.h" #include <algorithm> #include <QtCore/QCoreApplication>#include <QtCore/QDir>#include <QtCore/QFile>#include <QtCore/QJsonArray>#include <QtCore/QJsonObject>#include <QtCore/QStandardPaths>#include <QtCore/QThread>#include <QtCore/QUrl>#include <QtCore/QUrlQuery>#include <QtNetwork/QSslKey>#include <QSaveFile>#include <QPair> #include <AccountManager.h>#include <Assignment.h>#include <AvatarData.h>#include <HifiConfigVariantMap.h>#include <HTTPConnection.h>#include <NLPacketList.h>#include <NumericalConstants.h>#include <SettingHandle.h>#include <SettingHelpers.h>#include <FingerprintUtils.h>#include <ModerationFlags.h> #include "DomainServerNodeData.h"#include "NodePermissions.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";const QString DESCRIPTION_NAME_KEY = "name";const QString DESCRIPTION_GROUP_LABEL_KEY = "label";const QString DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY = "show_on_enable";const QString DESCRIPTION_ENABLE_KEY = "enable";const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup";const QString SETTING_DESCRIPTION_TYPE_KEY = "type";const QString DESCRIPTION_COLUMNS_KEY = "columns";const QString CONTENT_SETTING_FLAG_KEY = "content_setting";static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings";static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings"; const QString SETTINGS_VIEWPOINT_KEY = "viewpoint"; DomainServerSettingsManager::DomainServerSettingsManager() { // load the description object from the settings description qDebug() << "Application dir: " << QCoreApplication::applicationDirPath(); QString descriptionFilePath = QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH; QFile descriptionFile(descriptionFilePath); descriptionFile.open(QIODevice::ReadOnly); QJsonParseError parseError; QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll(), &parseError); if (descriptionDocument.isObject()) { QJsonObject descriptionObject = descriptionDocument.object(); const QString DESCRIPTION_VERSION_KEY = "version"; if (descriptionObject.contains(DESCRIPTION_VERSION_KEY)) { // read the version from the settings description _descriptionVersion = descriptionObject[DESCRIPTION_VERSION_KEY].toDouble(); if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) { _descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray(); splitSettingsDescription(); return; } } } static const QString MISSING_SETTINGS_DESC_MSG = QString("Did not find settings description in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3") .arg(descriptionFilePath).arg(parseError.errorString()).arg(parseError.offset); static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6; QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection, Q_ARG(QString, MISSING_SETTINGS_DESC_MSG), Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE));} void DomainServerSettingsManager::splitSettingsDescription() { // construct separate description arrays for domain settings and content settings // since they are displayed on different pages // along the way we also construct one object that holds the groups separated by domain settings // and content settings, so that the DS can setup dropdown menus below "Content" and "Settings" // headers to jump directly to a settings group on the page of either QJsonArray domainSettingsMenuGroups; QJsonArray contentSettingsMenuGroups; foreach(const QJsonValue& group, _descriptionArray) { QJsonObject groupObject = group.toObject(); static const QString HIDDEN_GROUP_KEY = "hidden"; bool groupHidden = groupObject.contains(HIDDEN_GROUP_KEY) && groupObject[HIDDEN_GROUP_KEY].toBool(); QJsonArray domainSettingArray; QJsonArray contentSettingArray; foreach(const QJsonValue& settingDescription, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) { QJsonObject settingDescriptionObject = settingDescription.toObject(); bool isContentSetting = settingDescriptionObject.contains(CONTENT_SETTING_FLAG_KEY) && settingDescriptionObject[CONTENT_SETTING_FLAG_KEY].toBool(); if (isContentSetting) { // push the setting description to the pending content setting array contentSettingArray.push_back(settingDescriptionObject); } else { // push the setting description to the pending domain setting array domainSettingArray.push_back(settingDescriptionObject); } } if (!domainSettingArray.isEmpty() || !contentSettingArray.isEmpty()) { // we know for sure we'll have something to add to our settings menu groups // so setup that object for the group now, as long as the group isn't hidden alltogether QJsonObject settingsDropdownGroup; if (!groupHidden) { if (groupObject.contains(DESCRIPTION_NAME_KEY)) { settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY]; } settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY]; if (groupObject.contains(DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY)) { settingsDropdownGroup[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY] = groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY]; } static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id"; if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) { settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY]; } } if (!domainSettingArray.isEmpty()) { // we have some domain settings from this group, add the group with the filtered settings QJsonObject filteredGroupObject = groupObject; filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = domainSettingArray; _domainSettingsDescription.push_back(filteredGroupObject); // if the group isn't hidden, add its information to the domain settings menu groups if (!groupHidden) { domainSettingsMenuGroups.push_back(settingsDropdownGroup); } } if (!contentSettingArray.isEmpty()) { // we have some content settings from this group, add the group with the filtered settings QJsonObject filteredGroupObject = groupObject; filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = contentSettingArray; _contentSettingsDescription.push_back(filteredGroupObject); // if the group isn't hidden, add its information to the content settings menu groups if (!groupHidden) { contentSettingsMenuGroups.push_back(settingsDropdownGroup); } } } } // populate the settings menu groups with what we've collected _settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups; _settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups;} void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message) { Assignment::Type type; message->readPrimitive(&type); QJsonObject responseObject = settingsResponseObjectForType(QString::number(type)); auto json = QJsonDocument(responseObject).toJson(); auto packetList = NLPacketList::create(PacketType::DomainSettings, QByteArray(), true, true); packetList->write(json); auto nodeList = DependencyManager::get<LimitedNodeList>(); nodeList->sendPacketList(std::move(packetList), message->getSenderSockAddr());} void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilename) { // since we're called from the DomainServerSettingsManager constructor, we don't take a write lock here // even though we change the underlying config map _configMap.setUserConfigFilename(userConfigFilename); _configMap.loadConfig(); static const auto VERSION_SETTINGS_KEYPATH = "version"; QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH); if (!versionVariant) { versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true); *versionVariant = _descriptionVersion; persistToFile(); qDebug() << "No version in config file, setting to current version" << _descriptionVersion; } { // Backward compatibility migration code // The config version used to be stored in a different file // This moves it to the actual config file. Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0); if (JSON_SETTING_VERSION.isSet()) { auto version = JSON_SETTING_VERSION.get(); *versionVariant = version; persistToFile(); QFile::remove(settingsFilename()); } } // What settings version were we before and what are we using now? // Do we need to do any re-mapping? double oldVersion = versionVariant->toDouble(); if (oldVersion != _descriptionVersion) { const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; const QString EDITORS_CAN_REPLACE_CONTENT_KEYPATH = "security.editors_can_replace_content"; qDebug() << "Previous domain-server settings version was" << QString::number(oldVersion, 'g', 8) << "and the new version is" << QString::number(_descriptionVersion, 'g', 8) << "- checking if any re-mapping is required"; // we have a version mismatch - for now handle custom behaviour here since there are not many remappings if (oldVersion < 1.0) { // This was prior to the introduction of security.restricted_access // If the user has a list of allowed users then set their value for security.restricted_access to true QVariant* allowedUsers = _configMap.valueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH); if (allowedUsers && allowedUsers->canConvert(QMetaType::QVariantList) && reinterpret_cast<QVariantList*>(allowedUsers)->size() > 0) { qDebug() << "Forcing security.restricted_access to TRUE since there was an" << "existing list of allowed users."; // In the pre-toggle system the user had a list of allowed users, so // we need to set security.restricted_access to true QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true); *restrictedAccess = QVariant(true); } } if (oldVersion < 1.1) { static const QString ENTITY_SERVER_SETTINGS_KEY = "entity_server_settings"; static const QString ENTITY_FILE_NAME_KEY = "persistFilename"; static const QString ENTITY_FILE_PATH_KEYPATH = ENTITY_SERVER_SETTINGS_KEY + ".persistFilePath"; // this was prior to change of poorly named entitiesFileName to entitiesFilePath QVariant* persistFileNameVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY); if (persistFileNameVariant && persistFileNameVariant->canConvert(QMetaType::QString)) { QString persistFileName = persistFileNameVariant->toString(); qDebug() << "Migrating persistFilename to persistFilePath for entity-server settings"; // grab the persistFilePath option, create it if it doesn't exist QVariant* persistFilePath = _configMap.valueForKeyPath(ENTITY_FILE_PATH_KEYPATH, true); // write the migrated value *persistFilePath = persistFileName; // remove the old setting QVariant* entityServerVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY); if (entityServerVariant && entityServerVariant->canConvert(QMetaType::QVariantMap)) { QVariantMap entityServerMap = entityServerVariant->toMap(); entityServerMap.remove(ENTITY_FILE_NAME_KEY); *entityServerVariant = entityServerMap; } } } if (oldVersion < 1.2) { // This was prior to the base64 encoding of password for HTTP Basic Authentication. // If we have a password in the previous settings file, make it base 64 static const QString BASIC_AUTH_PASSWORD_KEY_PATH { "security.http_password" }; QVariant* passwordVariant = _configMap.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH); if (passwordVariant && passwordVariant->canConvert(QMetaType::QString)) { QString plaintextPassword = passwordVariant->toString(); qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings."; *passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); } } if (oldVersion < 1.4) { // This was prior to the permissions-grid in the domain-server settings page bool isRestrictedAccess = valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); QStringList allowedUsers = valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); QStringList allowedEditors = valueOrDefaultValueForKeyPath(ALLOWED_EDITORS_SETTINGS_KEYPATH).toStringList(); bool onlyEditorsAreRezzers = valueOrDefaultValueForKeyPath(EDITORS_ARE_REZZERS_KEYPATH).toBool(); _standardAgentPermissions[NodePermissions::standardNameLocalhost].reset( new NodePermissions(NodePermissions::standardNameLocalhost)); _standardAgentPermissions[NodePermissions::standardNameLocalhost]->setAll(true); _standardAgentPermissions[NodePermissions::standardNameAnonymous].reset( new NodePermissions(NodePermissions::standardNameAnonymous)); _standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset( new NodePermissions(NodePermissions::standardNameLoggedIn)); _standardAgentPermissions[NodePermissions::standardNameFriends].reset( new NodePermissions(NodePermissions::standardNameFriends)); if (isRestrictedAccess) { // only users in allow-users list can connect _standardAgentPermissions[NodePermissions::standardNameAnonymous]->clear( NodePermissions::Permission::canConnectToDomain); _standardAgentPermissions[NodePermissions::standardNameLoggedIn]->clear( NodePermissions::Permission::canConnectToDomain); } // else anonymous and logged-in retain default of canConnectToDomain = true foreach (QString allowedUser, allowedUsers) { // even if isRestrictedAccess is false, we have to add explicit rows for these users. _agentPermissions[NodePermissionsKey(allowedUser, 0)].reset(new NodePermissions(allowedUser)); _agentPermissions[NodePermissionsKey(allowedUser, 0)]->set(NodePermissions::Permission::canConnectToDomain); } foreach (QString allowedEditor, allowedEditors) { NodePermissionsKey editorKey(allowedEditor, 0); if (!_agentPermissions.contains(editorKey)) { _agentPermissions[editorKey].reset(new NodePermissions(allowedEditor)); if (isRestrictedAccess) { // they can change locks, but can't connect. _agentPermissions[editorKey]->clear(NodePermissions::Permission::canConnectToDomain); } } _agentPermissions[editorKey]->set(NodePermissions::Permission::canAdjustLocks); } std::list<std::unordered_map<NodePermissionsKey, NodePermissionsPointer, NodePermissionsKeyHash>> permissionsSets{ _standardAgentPermissions.get(), _agentPermissions.get() }; foreach (auto permissionsSet, permissionsSets) { for (auto entry : permissionsSet) { const auto& userKey = entry.first; if (onlyEditorsAreRezzers) { if (permissionsSet[userKey]->can(NodePermissions::Permission::canAdjustLocks)) { permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities); permissionsSet[userKey]->set(NodePermissions::Permission::canRezTemporaryEntities); } else { permissionsSet[userKey]->clear(NodePermissions::Permission::canRezPermanentEntities); permissionsSet[userKey]->clear(NodePermissions::Permission::canRezTemporaryEntities); } } else { permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities); permissionsSet[userKey]->set(NodePermissions::Permission::canRezTemporaryEntities); } } } packPermissions(); _standardAgentPermissions.clear(); _agentPermissions.clear(); } if (oldVersion < 1.6) { unpackPermissions(); // This was prior to addition of kick permissions, add that to localhost permissions by default _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canKick); packPermissions(); } if (oldVersion < 1.8) { unpackPermissions(); // This was prior to addition of domain content replacement, add that to localhost permissions by default _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canReplaceDomainContent); packPermissions(); } if (oldVersion < 2.0) { const QString WIZARD_COMPLETED_ONCE = "wizard.completed_once"; QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true); *wizardCompletedOnce = QVariant(true); } if (oldVersion < 2.1) { // convert old avatar scale settings into avatar height. const QString AVATAR_MIN_SCALE_KEYPATH = "avatars.min_avatar_scale"; const QString AVATAR_MAX_SCALE_KEYPATH = "avatars.max_avatar_scale"; const QString AVATAR_MIN_HEIGHT_KEYPATH = "avatars.min_avatar_height"; const QString AVATAR_MAX_HEIGHT_KEYPATH = "avatars.max_avatar_height"; QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); if (avatarMinScale) { auto newMinScaleVariant = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, true); *newMinScaleVariant = avatarMinScale->toFloat() * DEFAULT_AVATAR_HEIGHT; } QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); if (avatarMaxScale) { auto newMaxScaleVariant = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, true); *newMaxScaleVariant = avatarMaxScale->toFloat() * DEFAULT_AVATAR_HEIGHT; } } if (oldVersion < 2.2) { // migrate entity server rolling backup intervals to new location for automatic content archive intervals const QString ENTITY_SERVER_BACKUPS_KEYPATH = "entity_server_settings.backups"; const QString AUTO_CONTENT_ARCHIVES_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules"; QVariant* previousBackupsVariant = _configMap.valueForKeyPath(ENTITY_SERVER_BACKUPS_KEYPATH); if (previousBackupsVariant) { auto migratedBackupsVariant = _configMap.valueForKeyPath(AUTO_CONTENT_ARCHIVES_RULES_KEYPATH, true); *migratedBackupsVariant = *previousBackupsVariant; } } if (oldVersion < 2.3) { unpackPermissions(); _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canGetAndSetPrivateUserData); packPermissions(); } if (oldVersion < 2.4) { // migrate oauth settings to their own group const QString ADMIN_USERS = "admin-users"; const QString OAUTH_ADMIN_USERS = "oauth.admin-users"; const QString OAUTH_CLIENT_ID = "oauth.client-id"; const QString ALT_ADMIN_USERS = "admin.users"; const QString ADMIN_ROLES = "admin-roles"; const QString OAUTH_ADMIN_ROLES = "oauth.admin-roles"; const QString OAUTH_ENABLE = "oauth.enable"; QVector<QPair<const char*, const char*> > conversionMap = { {"key", "oauth.key"}, {"cert", "oauth.cert"}, {"hostname", "oauth.hostname"}, {"oauth-client-id", "oauth.client-id"}, {"oauth-provider", "oauth.provider"} }; for (auto & conversion : conversionMap) { QVariant* prevValue = _configMap.valueForKeyPath(conversion.first); if (prevValue) { auto newValue = _configMap.valueForKeyPath(conversion.second, true); *newValue = *prevValue; } } QVariant* client_id = _configMap.valueForKeyPath(OAUTH_CLIENT_ID); if (client_id) { QVariant* oauthEnable = _configMap.valueForKeyPath(OAUTH_ENABLE, true); *oauthEnable = QVariant(true); } QVariant* oldAdminUsers = _configMap.valueForKeyPath(ADMIN_USERS); QVariant* newAdminUsers = _configMap.valueForKeyPath(OAUTH_ADMIN_USERS, true); QVariantList adminUsers(newAdminUsers->toList()); if (oldAdminUsers) { QStringList adminUsersList = oldAdminUsers->toStringList(); for (auto & user : adminUsersList) { if (!adminUsers.contains(user)) { adminUsers.append(user); } } } QVariant* altAdminUsers = _configMap.valueForKeyPath(ALT_ADMIN_USERS); if (altAdminUsers) { QStringList adminUsersList = altAdminUsers->toStringList(); for (auto & user : adminUsersList) {Lines 1–500 of 2279. Load more
// // DomainServerSettingsManager.cpp // domain-server/src // // Created by Stephen Birarda on 2014-06-24. // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "DomainServerSettingsManager.h" #include <algorithm> #include <QtCore/QCoreApplication> #include <QtCore/QDir> #include <QtCore/QFile> #include <QtCore/QJsonArray> #include <QtCore/QJsonObject> #include <QtCore/QStandardPaths> #include <QtCore/QThread> #include <QtCore/QUrl> #include <QtCore/QUrlQuery> #include <QtNetwork/QSslKey> #include <QSaveFile> #include <QPair> #include <AccountManager.h> #include <Assignment.h> #include <AvatarData.h> #include <HifiConfigVariantMap.h> #include <HTTPConnection.h> #include <NLPacketList.h> #include <NumericalConstants.h> #include <SettingHandle.h> #include <SettingHelpers.h> #include <FingerprintUtils.h> #include <ModerationFlags.h> #include "DomainServerNodeData.h" #include "NodePermissions.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"; const QString DESCRIPTION_NAME_KEY = "name"; const QString DESCRIPTION_GROUP_LABEL_KEY = "label"; const QString DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY = "show_on_enable"; const QString DESCRIPTION_ENABLE_KEY = "enable"; const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup"; const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; const QString DESCRIPTION_COLUMNS_KEY = "columns"; const QString CONTENT_SETTING_FLAG_KEY = "content_setting"; static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings"; static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings"; const QString SETTINGS_VIEWPOINT_KEY = "viewpoint"; DomainServerSettingsManager::DomainServerSettingsManager() { // load the description object from the settings description qDebug() << "Application dir: " << QCoreApplication::applicationDirPath(); QString descriptionFilePath = QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH; QFile descriptionFile(descriptionFilePath); descriptionFile.open(QIODevice::ReadOnly); QJsonParseError parseError; QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll(), &parseError); if (descriptionDocument.isObject()) { QJsonObject descriptionObject = descriptionDocument.object(); const QString DESCRIPTION_VERSION_KEY = "version"; if (descriptionObject.contains(DESCRIPTION_VERSION_KEY)) { // read the version from the settings description _descriptionVersion = descriptionObject[DESCRIPTION_VERSION_KEY].toDouble(); if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) { _descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray(); splitSettingsDescription(); return; } } } static const QString MISSING_SETTINGS_DESC_MSG = QString("Did not find settings description in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3") .arg(descriptionFilePath).arg(parseError.errorString()).arg(parseError.offset); static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6; QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection, Q_ARG(QString, MISSING_SETTINGS_DESC_MSG), Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE)); } void DomainServerSettingsManager::splitSettingsDescription() { // construct separate description arrays for domain settings and content settings // since they are displayed on different pages // along the way we also construct one object that holds the groups separated by domain settings // and content settings, so that the DS can setup dropdown menus below "Content" and "Settings" // headers to jump directly to a settings group on the page of either QJsonArray domainSettingsMenuGroups; QJsonArray contentSettingsMenuGroups; foreach(const QJsonValue& group, _descriptionArray) { QJsonObject groupObject = group.toObject(); static const QString HIDDEN_GROUP_KEY = "hidden"; bool groupHidden = groupObject.contains(HIDDEN_GROUP_KEY) && groupObject[HIDDEN_GROUP_KEY].toBool(); QJsonArray domainSettingArray; QJsonArray contentSettingArray; foreach(const QJsonValue& settingDescription, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) { QJsonObject settingDescriptionObject = settingDescription.toObject(); bool isContentSetting = settingDescriptionObject.contains(CONTENT_SETTING_FLAG_KEY) && settingDescriptionObject[CONTENT_SETTING_FLAG_KEY].toBool(); if (isContentSetting) { // push the setting description to the pending content setting array contentSettingArray.push_back(settingDescriptionObject); } else { // push the setting description to the pending domain setting array domainSettingArray.push_back(settingDescriptionObject); } } if (!domainSettingArray.isEmpty() || !contentSettingArray.isEmpty()) { // we know for sure we'll have something to add to our settings menu groups // so setup that object for the group now, as long as the group isn't hidden alltogether QJsonObject settingsDropdownGroup; if (!groupHidden) { if (groupObject.contains(DESCRIPTION_NAME_KEY)) { settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY]; } settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY]; if (groupObject.contains(DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY)) { settingsDropdownGroup[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY] = groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY]; } static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id"; if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) { settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY]; } } if (!domainSettingArray.isEmpty()) { // we have some domain settings from this group, add the group with the filtered settings QJsonObject filteredGroupObject = groupObject; filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = domainSettingArray; _domainSettingsDescription.push_back(filteredGroupObject); // if the group isn't hidden, add its information to the domain settings menu groups if (!groupHidden) { domainSettingsMenuGroups.push_back(settingsDropdownGroup); } } if (!contentSettingArray.isEmpty()) { // we have some content settings from this group, add the group with the filtered settings QJsonObject filteredGroupObject = groupObject; filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = contentSettingArray; _contentSettingsDescription.push_back(filteredGroupObject); // if the group isn't hidden, add its information to the content settings menu groups if (!groupHidden) { contentSettingsMenuGroups.push_back(settingsDropdownGroup); } } } } // populate the settings menu groups with what we've collected _settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups; _settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups; } void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message) { Assignment::Type type; message->readPrimitive(&type); QJsonObject responseObject = settingsResponseObjectForType(QString::number(type)); auto json = QJsonDocument(responseObject).toJson(); auto packetList = NLPacketList::create(PacketType::DomainSettings, QByteArray(), true, true); packetList->write(json); auto nodeList = DependencyManager::get<LimitedNodeList>(); nodeList->sendPacketList(std::move(packetList), message->getSenderSockAddr()); } void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilename) { // since we're called from the DomainServerSettingsManager constructor, we don't take a write lock here // even though we change the underlying config map _configMap.setUserConfigFilename(userConfigFilename); _configMap.loadConfig(); static const auto VERSION_SETTINGS_KEYPATH = "version"; QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH); if (!versionVariant) { versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true); *versionVariant = _descriptionVersion; persistToFile(); qDebug() << "No version in config file, setting to current version" << _descriptionVersion; } { // Backward compatibility migration code // The config version used to be stored in a different file // This moves it to the actual config file. Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0); if (JSON_SETTING_VERSION.isSet()) { auto version = JSON_SETTING_VERSION.get(); *versionVariant = version; persistToFile(); QFile::remove(settingsFilename()); } } // What settings version were we before and what are we using now? // Do we need to do any re-mapping? double oldVersion = versionVariant->toDouble(); if (oldVersion != _descriptionVersion) { const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; const QString EDITORS_CAN_REPLACE_CONTENT_KEYPATH = "security.editors_can_replace_content"; qDebug() << "Previous domain-server settings version was" << QString::number(oldVersion, 'g', 8) << "and the new version is" << QString::number(_descriptionVersion, 'g', 8) << "- checking if any re-mapping is required"; // we have a version mismatch - for now handle custom behaviour here since there are not many remappings if (oldVersion < 1.0) { // This was prior to the introduction of security.restricted_access // If the user has a list of allowed users then set their value for security.restricted_access to true QVariant* allowedUsers = _configMap.valueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH); if (allowedUsers && allowedUsers->canConvert(QMetaType::QVariantList) && reinterpret_cast<QVariantList*>(allowedUsers)->size() > 0) { qDebug() << "Forcing security.restricted_access to TRUE since there was an" << "existing list of allowed users."; // In the pre-toggle system the user had a list of allowed users, so // we need to set security.restricted_access to true QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true); *restrictedAccess = QVariant(true); } } if (oldVersion < 1.1) { static const QString ENTITY_SERVER_SETTINGS_KEY = "entity_server_settings"; static const QString ENTITY_FILE_NAME_KEY = "persistFilename"; static const QString ENTITY_FILE_PATH_KEYPATH = ENTITY_SERVER_SETTINGS_KEY + ".persistFilePath"; // this was prior to change of poorly named entitiesFileName to entitiesFilePath QVariant* persistFileNameVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY); if (persistFileNameVariant && persistFileNameVariant->canConvert(QMetaType::QString)) { QString persistFileName = persistFileNameVariant->toString(); qDebug() << "Migrating persistFilename to persistFilePath for entity-server settings"; // grab the persistFilePath option, create it if it doesn't exist QVariant* persistFilePath = _configMap.valueForKeyPath(ENTITY_FILE_PATH_KEYPATH, true); // write the migrated value *persistFilePath = persistFileName; // remove the old setting QVariant* entityServerVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY); if (entityServerVariant && entityServerVariant->canConvert(QMetaType::QVariantMap)) { QVariantMap entityServerMap = entityServerVariant->toMap(); entityServerMap.remove(ENTITY_FILE_NAME_KEY); *entityServerVariant = entityServerMap; } } } if (oldVersion < 1.2) { // This was prior to the base64 encoding of password for HTTP Basic Authentication. // If we have a password in the previous settings file, make it base 64 static const QString BASIC_AUTH_PASSWORD_KEY_PATH { "security.http_password" }; QVariant* passwordVariant = _configMap.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH); if (passwordVariant && passwordVariant->canConvert(QMetaType::QString)) { QString plaintextPassword = passwordVariant->toString(); qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings."; *passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); } } if (oldVersion < 1.4) { // This was prior to the permissions-grid in the domain-server settings page bool isRestrictedAccess = valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); QStringList allowedUsers = valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList(); QStringList allowedEditors = valueOrDefaultValueForKeyPath(ALLOWED_EDITORS_SETTINGS_KEYPATH).toStringList(); bool onlyEditorsAreRezzers = valueOrDefaultValueForKeyPath(EDITORS_ARE_REZZERS_KEYPATH).toBool(); _standardAgentPermissions[NodePermissions::standardNameLocalhost].reset( new NodePermissions(NodePermissions::standardNameLocalhost)); _standardAgentPermissions[NodePermissions::standardNameLocalhost]->setAll(true); _standardAgentPermissions[NodePermissions::standardNameAnonymous].reset( new NodePermissions(NodePermissions::standardNameAnonymous)); _standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset( new NodePermissions(NodePermissions::standardNameLoggedIn)); _standardAgentPermissions[NodePermissions::standardNameFriends].reset( new NodePermissions(NodePermissions::standardNameFriends)); if (isRestrictedAccess) { // only users in allow-users list can connect _standardAgentPermissions[NodePermissions::standardNameAnonymous]->clear( NodePermissions::Permission::canConnectToDomain); _standardAgentPermissions[NodePermissions::standardNameLoggedIn]->clear( NodePermissions::Permission::canConnectToDomain); } // else anonymous and logged-in retain default of canConnectToDomain = true foreach (QString allowedUser, allowedUsers) { // even if isRestrictedAccess is false, we have to add explicit rows for these users. _agentPermissions[NodePermissionsKey(allowedUser, 0)].reset(new NodePermissions(allowedUser)); _agentPermissions[NodePermissionsKey(allowedUser, 0)]->set(NodePermissions::Permission::canConnectToDomain); } foreach (QString allowedEditor, allowedEditors) { NodePermissionsKey editorKey(allowedEditor, 0); if (!_agentPermissions.contains(editorKey)) { _agentPermissions[editorKey].reset(new NodePermissions(allowedEditor)); if (isRestrictedAccess) { // they can change locks, but can't connect. _agentPermissions[editorKey]->clear(NodePermissions::Permission::canConnectToDomain); } } _agentPermissions[editorKey]->set(NodePermissions::Permission::canAdjustLocks); } std::list<std::unordered_map<NodePermissionsKey, NodePermissionsPointer, NodePermissionsKeyHash>> permissionsSets{ _standardAgentPermissions.get(), _agentPermissions.get() }; foreach (auto permissionsSet, permissionsSets) { for (auto entry : permissionsSet) { const auto& userKey = entry.first; if (onlyEditorsAreRezzers) { if (permissionsSet[userKey]->can(NodePermissions::Permission::canAdjustLocks)) { permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities); permissionsSet[userKey]->set(NodePermissions::Permission::canRezTemporaryEntities); } else { permissionsSet[userKey]->clear(NodePermissions::Permission::canRezPermanentEntities); permissionsSet[userKey]->clear(NodePermissions::Permission::canRezTemporaryEntities); } } else { permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities); permissionsSet[userKey]->set(NodePermissions::Permission::canRezTemporaryEntities); } } } packPermissions(); _standardAgentPermissions.clear(); _agentPermissions.clear(); } if (oldVersion < 1.6) { unpackPermissions(); // This was prior to addition of kick permissions, add that to localhost permissions by default _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canKick); packPermissions(); } if (oldVersion < 1.8) { unpackPermissions(); // This was prior to addition of domain content replacement, add that to localhost permissions by default _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canReplaceDomainContent); packPermissions(); } if (oldVersion < 2.0) { const QString WIZARD_COMPLETED_ONCE = "wizard.completed_once"; QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true); *wizardCompletedOnce = QVariant(true); } if (oldVersion < 2.1) { // convert old avatar scale settings into avatar height. const QString AVATAR_MIN_SCALE_KEYPATH = "avatars.min_avatar_scale"; const QString AVATAR_MAX_SCALE_KEYPATH = "avatars.max_avatar_scale"; const QString AVATAR_MIN_HEIGHT_KEYPATH = "avatars.min_avatar_height"; const QString AVATAR_MAX_HEIGHT_KEYPATH = "avatars.max_avatar_height"; QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); if (avatarMinScale) { auto newMinScaleVariant = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, true); *newMinScaleVariant = avatarMinScale->toFloat() * DEFAULT_AVATAR_HEIGHT; } QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); if (avatarMaxScale) { auto newMaxScaleVariant = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, true); *newMaxScaleVariant = avatarMaxScale->toFloat() * DEFAULT_AVATAR_HEIGHT; } } if (oldVersion < 2.2) { // migrate entity server rolling backup intervals to new location for automatic content archive intervals const QString ENTITY_SERVER_BACKUPS_KEYPATH = "entity_server_settings.backups"; const QString AUTO_CONTENT_ARCHIVES_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules"; QVariant* previousBackupsVariant = _configMap.valueForKeyPath(ENTITY_SERVER_BACKUPS_KEYPATH); if (previousBackupsVariant) { auto migratedBackupsVariant = _configMap.valueForKeyPath(AUTO_CONTENT_ARCHIVES_RULES_KEYPATH, true); *migratedBackupsVariant = *previousBackupsVariant; } } if (oldVersion < 2.3) { unpackPermissions(); _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canGetAndSetPrivateUserData); packPermissions(); } if (oldVersion < 2.4) { // migrate oauth settings to their own group const QString ADMIN_USERS = "admin-users"; const QString OAUTH_ADMIN_USERS = "oauth.admin-users"; const QString OAUTH_CLIENT_ID = "oauth.client-id"; const QString ALT_ADMIN_USERS = "admin.users"; const QString ADMIN_ROLES = "admin-roles"; const QString OAUTH_ADMIN_ROLES = "oauth.admin-roles"; const QString OAUTH_ENABLE = "oauth.enable"; QVector<QPair<const char*, const char*> > conversionMap = { {"key", "oauth.key"}, {"cert", "oauth.cert"}, {"hostname", "oauth.hostname"}, {"oauth-client-id", "oauth.client-id"}, {"oauth-provider", "oauth.provider"} }; for (auto & conversion : conversionMap) { QVariant* prevValue = _configMap.valueForKeyPath(conversion.first); if (prevValue) { auto newValue = _configMap.valueForKeyPath(conversion.second, true); *newValue = *prevValue; } } QVariant* client_id = _configMap.valueForKeyPath(OAUTH_CLIENT_ID); if (client_id) { QVariant* oauthEnable = _configMap.valueForKeyPath(OAUTH_ENABLE, true); *oauthEnable = QVariant(true); } QVariant* oldAdminUsers = _configMap.valueForKeyPath(ADMIN_USERS); QVariant* newAdminUsers = _configMap.valueForKeyPath(OAUTH_ADMIN_USERS, true); QVariantList adminUsers(newAdminUsers->toList()); if (oldAdminUsers) { QStringList adminUsersList = oldAdminUsers->toStringList(); for (auto & user : adminUsersList) { if (!adminUsers.contains(user)) { adminUsers.append(user); } } } QVariant* altAdminUsers = _configMap.valueForKeyPath(ALT_ADMIN_USERS); if (altAdminUsers) { QStringList adminUsersList = altAdminUsers->toStringList(); for (auto & user : adminUsersList) {