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

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

@ -99,7 +99,8 @@
<script src='/js/underscore-min.js'></script> <script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script> <script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script> <script src='/js/bootbox.min.js'></script>
<script src='/js/sweetalert.min.js'></script> <script src='js/bootstrap-switch.min.js'></script>
<script src='/js/settings.js'></script> <script src='js/sweetalert.min.js'></script>
<script src='/js/form2js.min.js'></script> <script src='js/settings.js'></script>
<script src='js/form2js.min.js'></script>
<!--#include virtual="page-end.html"--> <!--#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) { if (setting.label) {
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>" form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
} }
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
form_group += "<label for='" + keypath + "'>" form_group += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>"
form_group += "<input type='checkbox'" + common_attrs() + (setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>" form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "")
form_group += " " + setting.help + "</label>"; form_group += (isLocked ? " disabled" : "") + "/>"
if (setting.help) {
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
}
form_group += "</div>" form_group += "</div>"
} else { } else {
input_type = _.has(setting, 'type') ? setting.type : "text" 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(){ $('#' + Settings.FORM_ID).on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){
// this input was changed, add the changed data attribute to it // 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(){ $('.advanced-toggle').click(function(){
Settings.showAdvanced = !Settings.showAdvanced Settings.showAdvanced = !Settings.showAdvanced
@ -735,6 +747,9 @@ function reloadSettings() {
// call our method to setup the place names table // call our method to setup the place names table
setupPlacesTable(); setupPlacesTable();
// setup any bootstrap switches
$('.toggle-checkbox').bootstrapSwitch();
// add tooltip to locked settings // add tooltip to locked settings
$('label.locked').tooltip({ $('label.locked').tooltip({
placement: 'right', 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 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 MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
DomainServer::DomainServer(int argc, char* argv[]) : DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv), QCoreApplication(argc, argv),
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _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, const HifiSockAddr& senderSockAddr,
QString& reasonReturn) { QString& reasonReturn) {
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), bool isRestrictingAccess =
ALLOWED_USERS_SETTINGS_KEYPATH); _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
// we always let in a user who is sending a packet from our local socket or from the localhost address // 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() if (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress()
@ -786,45 +782,50 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
return true; return true;
} }
if (allowedUsers.count() > 0) { if (isRestrictingAccess) {
QStringList allowedUsers =
_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
if (allowedUsers.contains(username, Qt::CaseInsensitive)) { if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) { if (!verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true; return false;
} }
} else { } else {
qDebug() << "Connect request denied for user" << username << "not in allowed users list."; qDebug() << "Connect request denied for user" << username << "not in allowed users list.";
reasonReturn = "User not on whitelist."; reasonReturn = "User not on whitelist.";
}
return false;
} else {
// we have no allowed user list.
// if this user is in the editors list, exempt them from the max-capacity check return false;
const QVariant* allowedEditorsVariant =
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
if (allowedEditors.contains(username)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true;
}
} }
// if we haven't reached max-capacity, let them in.
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
if (maximumUserCapacity > 0) {
unsigned int connectedUsers = countConnectedUsers();
if (connectedUsers >= maximumUserCapacity) {
// too many users, deny the new connection.
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
reasonReturn = "Too many connected users.";
return false;
}
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
}
return true;
} }
// 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 =
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
if (allowedEditors.contains(username)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true;
}
}
// if we haven't reached max-capacity, let them in.
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
if (maximumUserCapacity > 0) {
unsigned int connectedUsers = countConnectedUsers();
if (connectedUsers >= maximumUserCapacity) {
// too many users, deny the new connection.
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
reasonReturn = "Too many connected users.";
return false;
}
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
}
return true;
} }
void DomainServer::preloadAllowedUserPublicKeys() { void DomainServer::preloadAllowedUserPublicKeys() {
@ -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 // 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 QString RESTRICTED_ACCESS_FLAG = "restricted";
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), domainObject[RESTRICTED_ACCESS_FLAG] =
ALLOWED_USERS_SETTINGS_KEYPATH); _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
domainObject[RESTRICTED_ACCESS_FLAG] = (allowedUsers.size() > 0);
// add the number of currently connected agent users // add the number of currently connected agent users
int numConnectedAuthedUsers = 0; int numConnectedAuthedUsers = 0;

View file

@ -14,6 +14,7 @@
#include <QtCore/QFile> #include <QtCore/QFile>
#include <QtCore/QJsonArray> #include <QtCore/QJsonArray>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
#include <QtCore/QSettings>
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtCore/QUrlQuery> #include <QtCore/QUrlQuery>
@ -42,33 +43,73 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
descriptionFile.open(QIODevice::ReadOnly); 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) { void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_configMap.loadMasterAndUserConfig(argumentList); _configMap.loadMasterAndUserConfig(argumentList);
// for now we perform a temporary transition from http-username and http-password to http_username and http_password // What settings version were we before and what are we using now?
const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username"); // Do we need to do any re-mapping?
const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password"); 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) { if (oldVersion != _descriptionVersion) {
QVariantMap& settingsMap = *reinterpret_cast<QVariantMap*>(_configMap.getUserConfig()["security"].data()); 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 // we have a version mismatch - for now handle custom behaviour here since there are not many remappings
if (oldUsername) { if (oldVersion < 1.0) {
settingsMap["http_username"] = oldUsername->toString(); // This was prior to the introduction of security.restricted_access
settingsMap.remove("http-username"); // If the user has a list of allowed users then set their value for security.restricted_access to true
QVariant* allowedUsers = valueForKeyPath(_configMap.getMergedConfig(), 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 = valueForKeyPath(_configMap.getUserConfig(),
RESTRICTED_ACCESS_SETTINGS_KEYPATH,
true);
*restrictedAccess = QVariant(true);
// write the new settings to the json file
persistToFile();
}
} }
if (oldPassword) {
settingsMap["http_password"] = oldPassword->toString();
settingsMap.remove("http-password");
}
// save the updated settings
persistToFile();
} }
// write the current description version to our settings
appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion);
} }
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) { QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {

View file

@ -23,6 +23,9 @@ const QString SETTINGS_PATHS_KEY = "paths";
const QString SETTINGS_PATH = "/settings"; const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; 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 { class DomainServerSettingsManager : public QObject {
Q_OBJECT Q_OBJECT
public: public:
@ -44,6 +47,7 @@ private:
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName); QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
void persistToFile(); void persistToFile();
double _descriptionVersion;
QJsonArray _descriptionArray; QJsonArray _descriptionArray;
HifiConfigVariantMap _configMap; 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('.'); int dotIndex = keyPath.indexOf('.');
QString firstKey = (dotIndex == -1) ? keyPath : keyPath.mid(0, dotIndex); QString firstKey = (dotIndex == -1) ? keyPath : keyPath.mid(0, dotIndex);
if (variantMap.contains(firstKey)) { if (shouldCreateIfMissing || variantMap.contains(firstKey)) {
if (dotIndex == -1) { if (dotIndex == -1) {
return &variantMap[firstKey]; return &variantMap[firstKey];
} else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) { } 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); 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 #endif // hifi_HifiConfigVariantMap_h