2279 lines · 101.7 KB · C++
RawDownload
////  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.