Merge pull request #4921 from birarda/domain-toggle

add a global toggle for restricted users
This commit is contained in:
Philip Rosedale 2015-05-20 21:27:10 -07:00
commit 1b0ef406ed
14 changed files with 731 additions and 599 deletions

View file

@ -1,4 +1,6 @@
[
{
"version": 1.0,
"settings": [
{
"name": "metaverse",
"label": "Metaverse / Networking",
@ -85,11 +87,18 @@
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
"value-hidden": true
},
{
"name": "restricted_access",
"type": "checkbox",
"label": "Restricted Access",
"default": false,
"help": "Only users listed in \"Allowed Users\" can enter your domain."
},
{
"name": "allowed_users",
"type": "table",
"label": "Allowed Users",
"help": "List the High Fidelity names for people you want to be able to connect to this domain.<br/>An empty list means everyone.<br/>You can always connect from the domain-server machine.",
"help": "You can always connect from the domain-server machine.",
"numbered": false,
"columns": [
{
@ -124,8 +133,8 @@
{
"name": "editors_are_rezzers",
"type": "checkbox",
"label": "Only editors can create new entities",
"help": "When checked, only those who can edit the domain can create new entites.",
"label": "Only Editors Can Create Entities",
"help": "Only users listed in \"Allowed Editors\" can create new entites.",
"default": false
}
]
@ -180,8 +189,9 @@
},
{
"name": "enable_filter",
"label": "Low-pass Filter",
"type": "checkbox",
"help": "positional audio stream uses lowpass filter",
"help": "Positional audio stream uses low-pass filter",
"default": true
},
{
@ -282,7 +292,7 @@
"name": "dynamic_jitter_buffer",
"type": "checkbox",
"label": "Dynamic Jitter Buffers",
"help": "dynamically buffer client audio based on perceived jitter in packet receipt timing",
"help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing",
"default": false,
"advanced": true
},
@ -305,8 +315,8 @@
{
"name": "use_stdev_for_desired_calc",
"type": "checkbox",
"label": "Use Stdev for Desired Jitter Frames Calc:",
"help": "use Philip's method (stdev of timegaps) to calculate desired jitter frames (otherwise Fred's max timegap method is used)",
"label": "Stdev for Desired Jitter Frames Calc",
"help": "Use Philip's method (stdev of timegaps) to calculate desired jitter frames (otherwise Fred's max timegap method is used)",
"default": false,
"advanced": true
},
@ -320,7 +330,7 @@
},
{
"name": "window_seconds_for_desired_calc_on_too_many_starves",
"label": "Timegaps Window (A) Seconds:",
"label": "Timegaps Window (A) Seconds",
"help": "Window A contains a history of timegaps. Its max timegap is used to re-evaluate the desired jitter frames when too many starves occur within it.",
"placeholder": "50",
"default": "50",
@ -328,7 +338,7 @@
},
{
"name": "window_seconds_for_desired_reduction",
"label": "Timegaps Window (B) Seconds:",
"label": "Timegaps Window (B) Seconds",
"help": "Window B contains a history of timegaps. Its max timegap is used as a ceiling for the desired jitter frames value.",
"placeholder": "10",
"default": "10",
@ -337,16 +347,16 @@
{
"name": "repetition_with_fade",
"type": "checkbox",
"label": "Repetition with Fade:",
"help": "dropped frames and mixing during starves repeat the last frame, eventually fading to silence",
"label": "Repetition with Fade",
"help": "Dropped frames and mixing during starves repeat the last frame, eventually fading to silence",
"default": false,
"advanced": true
},
{
"name": "print_stream_stats",
"type": "checkbox",
"label": "Print Stream Stats:",
"help": "audio upstream and downstream stats of each agent printed to audio-mixer stdout",
"label": "Print Stream Stats",
"help": "Audio upstream and downstream stats of each agent printed to audio-mixer stdout",
"default": false,
"advanced": true
}
@ -440,6 +450,7 @@
{
"name": "NoPersist",
"type": "checkbox",
"label": "Disable Persistence",
"help": "Don't persist your entities to a file.",
"default": false,
"advanced": true
@ -447,6 +458,7 @@
{
"name": "NoBackup",
"type": "checkbox",
"label": "Disable Backup",
"help": "Don't regularly backup your persisted entities to a backup file.",
"default": false,
"advanced": true
@ -454,7 +466,7 @@
{
"name": "statusHost",
"label": "Status Hostname",
"help": "host name or IP address of the server for accessing the status page",
"help": "Host name or IP address of the server for accessing the status page",
"placeholder": "",
"default": "",
"advanced": true
@ -462,7 +474,7 @@
{
"name": "statusPort",
"label": "Status Port",
"help": "port of the server for accessing the status page",
"help": "Port of the server for accessing the status page",
"placeholder": "",
"default": "",
"advanced": true
@ -470,6 +482,7 @@
{
"name": "wantEditLogging",
"type": "checkbox",
"label": "Edit Logging",
"help": "Logging of all edits to entities",
"default": true,
"advanced": true
@ -477,28 +490,32 @@
{
"name": "verboseDebug",
"type": "checkbox",
"help": "lots of debugging",
"label": "Verbose Debug",
"help": "Lots of debugging",
"default": false,
"advanced": true
},
{
"name": "debugReceiving",
"type": "checkbox",
"help": "extra debugging on receiving",
"label": "Extra Receiving Debug",
"help": "Extra debugging on receiving",
"default": false,
"advanced": true
},
{
"name": "debugSending",
"type": "checkbox",
"help": "extra debugging on sending",
"label": "Extra Sending Debug",
"help": "Extra debugging on sending",
"default": false,
"advanced": true
},
{
"name": "debugTimestampNow",
"type": "checkbox",
"help": "extra debugging for usecTimestampNow() function",
"label": "Extra Timestamp Debugging",
"help": "Extra debugging for usecTimestampNow() function",
"default": false,
"advanced": true
},
@ -529,3 +546,4 @@
]
}
]
}

File diff suppressed because one or more lines are too long

View file

@ -109,6 +109,13 @@ table {
width: 100%;
}
/* styling for bootstrap-switch toggles */
.checkbox-help {
margin-top: 10px;
}
/* CSS only spinner for AJAX requests */
.spinner {
margin: 30px auto 0;
width: 70px;

View file

@ -7,7 +7,9 @@
<link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="/css/style.css" rel="stylesheet" media="screen">
<link href="/css/sweetalert.css" rel="stylesheet" media="screen">
<link href="/css/bootstrap-switch.min.css" rel="stylesheet" media="screen">
<link href="/stats/css/json.human.css" rel="stylesheet" media="screen">
</head>
<body>
<nav class="navbar navbar-default" role="navigation">

View file

@ -99,7 +99,8 @@
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='/js/sweetalert.min.js'></script>
<script src='/js/settings.js'></script>
<script src='/js/form2js.min.js'></script>
<script src='js/bootstrap-switch.min.js'></script>
<script src='js/sweetalert.min.js'></script>
<script src='js/settings.js'></script>
<script src='js/form2js.min.js'></script>
<!--#include virtual="page-end.html"-->

File diff suppressed because one or more lines are too long

View file

@ -60,10 +60,15 @@ var viewHelpers = {
if (setting.label) {
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
}
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
form_group += "<label for='" + keypath + "'>"
form_group += "<input type='checkbox'" + common_attrs() + (setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>"
form_group += " " + setting.help + "</label>";
form_group += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>"
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "")
form_group += (isLocked ? " disabled" : "") + "/>"
if (setting.help) {
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
}
form_group += "</div>"
} else {
input_type = _.has(setting, 'type') ? setting.type : "text"
@ -201,10 +206,17 @@ $(document).ready(function(){
$('#' + Settings.FORM_ID).on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){
// this input was changed, add the changed data attribute to it
$(this).attr('data-changed', true)
$(this).attr('data-changed', true);
badgeSidebarForDifferences($(this))
})
badgeSidebarForDifferences($(this));
});
$('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){
// this checkbox was changed, add the changed data attribute to it
$(this).attr('data-changed', true);
badgeSidebarForDifferences($(this));
});
$('.advanced-toggle').click(function(){
Settings.showAdvanced = !Settings.showAdvanced
@ -735,6 +747,9 @@ function reloadSettings() {
// call our method to setup the place names table
setupPlacesTable();
// setup any bootstrap switches
$('.toggle-checkbox').bootstrapSwitch();
// add tooltip to locked settings
$('label.locked').tooltip({
placement: 'right',

View file

@ -44,13 +44,10 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923;
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
@ -776,9 +773,8 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
const HifiSockAddr& senderSockAddr,
QString& reasonReturn) {
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
ALLOWED_USERS_SETTINGS_KEYPATH);
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
bool isRestrictingAccess =
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
// we always let in a user who is sending a packet from our local socket or from the localhost address
if (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress()
@ -786,18 +782,24 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
return true;
}
if (allowedUsers.count() > 0) {
if (isRestrictingAccess) {
QStringList allowedUsers =
_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true;
if (!verifyUsersKey(username, usernameSignature, reasonReturn)) {
return false;
}
} else {
qDebug() << "Connect request denied for user" << username << "not in allowed users list.";
reasonReturn = "User not on whitelist.";
}
return false;
} else {
// we have no allowed user list.
}
}
// either we aren't restricting users, or this user is in the allowed list
// if this user is in the editors list, exempt them from the max-capacity check
const QVariant* allowedEditorsVariant =
@ -825,7 +827,6 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
return true;
}
}
void DomainServer::preloadAllowedUserPublicKeys() {
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH);
@ -1255,10 +1256,8 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
const QString RESTRICTED_ACCESS_FLAG = "restricted";
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
ALLOWED_USERS_SETTINGS_KEYPATH);
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
domainObject[RESTRICTED_ACCESS_FLAG] = (allowedUsers.size() > 0);
domainObject[RESTRICTED_ACCESS_FLAG] =
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
// add the number of currently connected agent users
int numConnectedAuthedUsers = 0;

View file

@ -14,6 +14,7 @@
#include <QtCore/QFile>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QSettings>
#include <QtCore/QStandardPaths>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
@ -42,34 +43,74 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
descriptionFile.open(QIODevice::ReadOnly);
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll());
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();
return;
}
}
}
qCritical() << "Did not find settings decription in JSON at" << SETTINGS_DESCRIPTION_RELATIVE_PATH
<< "- Unable to continue. domain-server will quit.";
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
}
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_configMap.loadMasterAndUserConfig(argumentList);
// for now we perform a temporary transition from http-username and http-password to http_username and http_password
const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username");
const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password");
// What settings version were we before and what are we using now?
// Do we need to do any re-mapping?
QSettings appSettings;
const QString JSON_SETTINGS_VERSION_KEY = "json-settings/version";
double oldVersion = appSettings.value(JSON_SETTINGS_VERSION_KEY, 0.0).toDouble();
if (oldUsername || oldPassword) {
QVariantMap& settingsMap = *reinterpret_cast<QVariantMap*>(_configMap.getUserConfig()["security"].data());
if (oldVersion != _descriptionVersion) {
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";
// remove old keys, move to new format
if (oldUsername) {
settingsMap["http_username"] = oldUsername->toString();
settingsMap.remove("http-username");
}
// 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
if (oldPassword) {
settingsMap["http_password"] = oldPassword->toString();
settingsMap.remove("http-password");
}
QVariant* allowedUsers = valueForKeyPath(_configMap.getMergedConfig(), ALLOWED_USERS_SETTINGS_KEYPATH);
// save the updated settings
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 = valueForKeyPath(_configMap.getUserConfig(),
RESTRICTED_ACCESS_SETTINGS_KEYPATH,
true);
*restrictedAccess = QVariant(true);
// write the new settings to the json file
persistToFile();
}
}
}
// write the current description version to our settings
appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion);
}
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);

View file

@ -23,6 +23,9 @@ const QString SETTINGS_PATHS_KEY = "paths";
const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access";
class DomainServerSettingsManager : public QObject {
Q_OBJECT
public:
@ -44,6 +47,7 @@ private:
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
void persistToFile();
double _descriptionVersion;
QJsonArray _descriptionArray;
HifiConfigVariantMap _configMap;
};

View file

@ -159,16 +159,17 @@ void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMa
}
}
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath) {
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing) {
int dotIndex = keyPath.indexOf('.');
QString firstKey = (dotIndex == -1) ? keyPath : keyPath.mid(0, dotIndex);
if (variantMap.contains(firstKey)) {
if (shouldCreateIfMissing || variantMap.contains(firstKey)) {
if (dotIndex == -1) {
return &variantMap[firstKey];
} else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1));
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
shouldCreateIfMissing);
}
}

View file

@ -37,6 +37,6 @@ private:
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
};
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
#endif // hifi_HifiConfigVariantMap_h