diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json
index 9cb4c2cab9..b854955953 100644
--- a/domain-server/resources/describe-settings.json
+++ b/domain-server/resources/describe-settings.json
@@ -1,5 +1,5 @@
{
- "version": 2.3,
+ "version": 2.4,
"settings": [
{
"name": "metaverse",
@@ -1705,6 +1705,114 @@
}
]
},
+ {
+ "name": "oauth",
+ "label": "OAuth",
+ "show_on_enable": true,
+ "settings": [
+ {
+ "name": "enable",
+ "type": "checkbox",
+ "default": false,
+ "hidden": true
+ },
+ {
+ "name": "admin-users",
+ "label": "Admin Users",
+ "type": "table",
+ "can_add_new_rows": true,
+ "help": "Any of these users can administer the domain.",
+ "numbered": false,
+ "backup": false,
+ "advanced": false,
+ "columns": [
+ {
+ "name": "username",
+ "label": "Username",
+ "can_set": true
+ }
+ ]
+ },
+ {
+ "name": "admin-roles",
+ "label": "Admin Roles",
+ "type": "table",
+ "can_add_new_rows": true,
+ "help": "Any user with any of these metaverse roles can administer the domain.",
+ "numbered": false,
+ "backup": false,
+ "advanced": true,
+ "columns": [
+ {
+ "name": "role",
+ "label": "Role",
+ "can_set": true
+ }
+ ]
+ },
+ {
+ "name": "client-id",
+ "label": "Client ID",
+ "help": "OAuth client ID.",
+ "default": "",
+ "advanced": true,
+ "backup": false
+ },
+ {
+ "name": "client-secret",
+ "label": "Client Secret",
+ "help": "OAuth client secret.",
+ "type": "password",
+ "password_placeholder": "******",
+ "value-hidden": true,
+ "advanced": true,
+ "backup": false
+ },
+ {
+ "name": "provider",
+ "label": "Provider",
+ "help": "OAuth provider URL.",
+ "default": "https://metaverse.highfidelity.com",
+ "advanced": true,
+ "backup": false
+ },
+ {
+ "name": "hostname",
+ "label": "Hostname",
+ "help": "OAuth hostname.",
+ "default": "",
+ "advanced": true,
+ "backup": false
+ },
+ {
+ "name": "key-passphrase",
+ "label": "SSL Private Key Passphrase",
+ "help": "SSL Private Key Passphrase",
+ "type": "password",
+ "password_placeholder": "******",
+ "value-hidden": true,
+ "advanced": true,
+ "backup": false
+ },
+ {
+ "name": "cert-fingerprint",
+ "type": "hidden",
+ "readonly": true,
+ "advanced": true,
+ "backup": false
+ },
+ {
+ "name": "cert",
+ "advanced": true,
+ "backup": false
+ },
+ {
+ "name": "key",
+ "advanced": true,
+ "backup": false
+ }
+ ]
+ },
{
"name": "automatic_content_archives",
"label": "Automatic Content Archives",
diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js
index bd96f636a8..295013878c 100644
--- a/domain-server/resources/web/js/base-settings.js
+++ b/domain-server/resources/web/js/base-settings.js
@@ -2,6 +2,9 @@ var DomainInfo = null;
var viewHelpers = {
getFormGroup: function(keypath, setting, values, isAdvanced) {
+ if (setting.hidden) {
+ return "";
+ }
form_group = "
"
}
-
- form_group += "" + setting.help + ""
+ if (setting.help) {
+ form_group += "" + setting.help + ""
+ }
}
}
@@ -114,12 +118,17 @@ function reloadSettings(callback) {
data.descriptions.push(Settings.extraGroupsAtEnd[endGroupIndex]);
}
+ data.descriptions = data.descriptions.map(function(x) {
+ x.hidden = x.hidden || (x.show_on_enable && data.values[x.name] && !data.values[x.name].enable);
+ return x;
+ });
+
$('#panels').html(Settings.panelsTemplate(data));
Settings.data = data;
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
- Settings.afterReloadActions();
+ Settings.afterReloadActions(data);
// setup any bootstrap switches
$('.toggle-checkbox').bootstrapSwitch();
@@ -129,10 +138,14 @@ function reloadSettings(callback) {
Settings.pendingChanges = 0;
// call the callback now that settings are loaded
- callback(true);
+ if (callback) {
+ callback(true);
+ }
}).fail(function() {
// call the failure object since settings load faild
- callback(false)
+ if (callback) {
+ callback(false);
+ }
});
}
diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js
index a8b7267b88..9524b18caf 100644
--- a/domain-server/resources/web/js/domain-server.js
+++ b/domain-server/resources/web/js/domain-server.js
@@ -91,6 +91,7 @@ $(document).ready(function(){
// make a JSON request to get the dropdown menus for content and settings
// we don't error handle here because the top level menu is still clickable and usables if this fails
$.getJSON('/settings-menu-groups.json', function(data){
+
function makeGroupDropdownElement(group, base) {
var html_id = group.html_id ? group.html_id : group.name;
return "" + group.label + "";
diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js
index 08d0550841..fcf7700687 100644
--- a/domain-server/resources/web/settings/js/settings.js
+++ b/domain-server/resources/web/settings/js/settings.js
@@ -18,7 +18,19 @@ $(document).ready(function(){
Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex;
var METAVERSE_URL = URLs.METAVERSE_URL;
- Settings.afterReloadActions = function() {
+ var SSL_PRIVATE_KEY_FILE_ID = 'ssl-private-key-file';
+ var SSL_PRIVATE_KEY_CONTENTS_ID = 'key-contents';
+ var SSL_PRIVATE_KEY_CONTENTS_NAME = 'oauth.key-contents';
+ var SSL_CERT_UPLOAD_ID = 'ssl-cert-button';
+ var SSL_CERT_FILE_ID = 'ssl-cert-file';
+ var SSL_CERT_FINGERPRINT_ID = 'cert-fingerprint';
+ var SSL_CERT_FINGERPRINT_SPAN_ID = 'cert-fingerprint-span-id';
+ var SSL_CERT_CONTENTS_ID = 'cert-contents';
+ var SSL_CERT_CONTENTS_NAME = 'oauth.cert-contents';
+ var SSL_PRIVATE_KEY_PATH = 'oauth.key';
+ var SSL_CERT_PATH = 'oauth.cert';
+
+ Settings.afterReloadActions = function(data) {
getMetaverseUrl(function(metaverse_url) {
METAVERSE_URL = metaverse_url;
@@ -32,6 +44,8 @@ $(document).ready(function(){
setupDomainNetworkingSettings();
// setupDomainLabelSetting();
+ setupSettingsOAuth(data);
+
setupSettingsBackup();
if (domainIDIsSet()) {
@@ -124,6 +138,48 @@ $(document).ready(function(){
}
}
+ if (formJSON["oauth"]) {
+ var private_key = formJSON["oauth"]["key-contents"];
+ var cert = formJSON["oauth"]["cert-contents"];
+ var oauthErrors = "";
+ if (private_key != undefined) {
+ var pattern = /-+BEGIN PRIVATE KEY-+[A-Za-z0-9+/\n=]*-+END PRIVATE KEY-+/m;
+ if (!pattern.test(private_key)) {
+ oauthErrors = "Private key must be in PEM format";
+ }
+ }
+ if (cert != undefined) {
+ var pattern = /-+BEGIN CERTIFICATE-+[A-Za-z0-9+/\n=]*-+END CERTIFICATE-+/m;
+ if (!pattern.test(cert)) {
+ oauthErrors = "Certificate must be in PEM format";
+ }
+ }
+ if ($('#oauth.panel').length) {
+ if (!$('input[name="oauth.client-id"]').val()) {
+ oauthErrors += "OAuth requires a client Id.
";
+ }
+ if (!$('input[name="oauth.provider"]').val()) {
+ oauthErrors += "OAuth requires a provider.
";
+ }
+ if (!$('input[name="oauth.hostname"]').val()) {
+ oauthErrors += "OAuth requires a hostname.
";
+ }
+ if (!$('input[name="' + SSL_PRIVATE_KEY_PATH + '"]').val() && !$('input[name="' + SSL_PRIVATE_KEY_CONTENTS_NAME + '"]').val()) {
+ oauthErrors += "OAuth requires an SSL Private Key.
";
+ }
+ if (!$('input[name="' + SSL_CERT_PATH + '"]').val() && !$('input[name="' + SSL_CERT_CONTENTS_NAME + '"]').val()) {
+ oauthErrors += "OAuth requires an SSL Certificate.
";
+ }
+ if (!$("table[name='oauth.admin-users'] tr.value-row").length &&
+ !$("table[name='oauth.admin-roles'] tr.value-row").length) {
+ oauthErrors += "OAuth must have at least one admin user or admin role.
";
+ }
+ }
+ if (oauthErrors) {
+ bootbox.alert({ "message": oauthErrors, "title": "OAuth Configuration Error" });
+ return false;
+ }
+ }
postSettings(formJSON);
};
@@ -1035,6 +1091,67 @@ $(document).ready(function(){
});
}
+ function setupSettingsOAuth(data) {
+ // construct the HTML needed for the settings backup panel
+ var html = "";
+ html += "
";
+ html += "";
+ html += "";
+ html += "";
+ html += "
";
+ html += "";
+
+ $('#oauth-advanced').append(html);
+
+ $('#key-path-label').after($('[data-keypath="' + SSL_PRIVATE_KEY_PATH + '"]'));
+ $('#cert-path-label').after($('[data-keypath="' + SSL_CERT_PATH + '"]'));
+ $('[name="' + SSL_PRIVATE_KEY_PATH + '"]').val(data.values.oauth.key);
+ $('[name="' + SSL_CERT_PATH + '"]').val(data.values.oauth.cert);
+
+ $('body').on('change input propertychange', '#' + SSL_PRIVATE_KEY_FILE_ID, function(e){
+ var f = e.target.files[0];
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ $('#' + SSL_PRIVATE_KEY_CONTENTS_ID).val(reader.result);
+ $('#' + SSL_PRIVATE_KEY_CONTENTS_ID).attr('data-changed', true);
+ $('[name="' + SSL_PRIVATE_KEY_PATH + '"]').val('');
+ badgeForDifferences($('#' + SSL_PRIVATE_KEY_CONTENTS_ID));
+ }
+ reader.readAsText(f);
+ });
+ $('body').on('change input propertychange', '#' + SSL_CERT_FILE_ID, function(e){
+ var f = e.target.files[0];
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ $('#' + SSL_CERT_CONTENTS_ID).val(reader.result);
+ $('#' + SSL_CERT_CONTENTS_ID).attr('data-changed', true);
+ $('[name="' + SSL_CERT_PATH + '"]').val('');
+ $('#' + SSL_CERT_FINGERPRINT_SPAN_ID).text('');
+ badgeForDifferences($('#' + SSL_CERT_CONTENTS_ID));
+ }
+ reader.readAsText(f);
+ });
+
+ $('body').on('change input propertychange', '[name="' + SSL_PRIVATE_KEY_PATH + '"]', function(e){
+ $('#' + SSL_PRIVATE_KEY_FILE_ID).val('');
+ $('#' + SSL_PRIVATE_KEY_CONTENTS_ID).val('');
+ badgeForDifferences($('[name="' + SSL_PRIVATE_KEY_PATH + '"]').attr('data-changed', true));
+ });
+
+ $('body').on('change input propertychange', '[name="' + SSL_CERT_PATH + '"]', function(e){
+ $('#' + SSL_CERT_FILE_ID).val('');
+ $('#' + SSL_CERT_CONTENTS_ID).val('');
+ $('#' + SSL_CERT_FINGERPRINT_SPAN_ID).text('');
+ badgeForDifferences($('[name="' + SSL_CERT_PATH + '"]').attr('data-changed', true));
+ });
+ }
+
var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button';
var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file';
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 74ad014b53..7f6c366bc3 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -226,9 +226,10 @@ DomainServer::DomainServer(int argc, char* argv[]) :
setupGroupCacheRefresh();
- // if we were given a certificate/private key or oauth credentials they must succeed
- if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
- return;
+ optionallySetupOAuth();
+
+ if (_oauthEnable) {
+ _oauthEnable = optionallyReadX509KeyAndCertificate();
}
_settingsManager.apiRefreshGroupInformation();
@@ -447,8 +448,9 @@ QUuid DomainServer::getID() {
}
bool DomainServer::optionallyReadX509KeyAndCertificate() {
- const QString X509_CERTIFICATE_OPTION = "cert";
- const QString X509_PRIVATE_KEY_OPTION = "key";
+ const QString X509_CERTIFICATE_OPTION = "oauth.cert";
+ const QString X509_PRIVATE_KEY_OPTION = "oauth.key";
+ const QString X509_PRIVATE_KEY_PASSPHRASE_OPTION = "oauth.key-passphrase";
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
QString certPath = _settingsManager.valueForKeyPath(X509_CERTIFICATE_OPTION).toString();
@@ -459,7 +461,12 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
// this is used for Oauth callbacks when authorizing users against a data server
// let's make sure we can load the key and certificate
- QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV);
+ QString keyPassphraseEnv = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV);
+ QString keyPassphraseString = _settingsManager.valueForKeyPath(X509_PRIVATE_KEY_PASSPHRASE_OPTION).toString();
+
+ if (!keyPassphraseEnv.isEmpty()) {
+ keyPassphraseString = keyPassphraseEnv;
+ }
qDebug() << "Reading certificate file at" << certPath << "for HTTPS.";
qDebug() << "Reading key file at" << keyPath << "for HTTPS.";
@@ -473,16 +480,15 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
QSslCertificate sslCertificate(&certFile);
QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
+ if (privateKey.isNull()) {
+ qCritical() << "SSL Private Key Not Loading. Bad password or key format?";
+ }
+
_httpsManager.reset(new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this));
qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT;
} else if (!certPath.isEmpty() || !keyPath.isEmpty()) {
- static const QString MISSING_CERT_ERROR_MSG = "Missing certificate or private key. domain-server will now quit.";
- static const int MISSING_CERT_ERROR_CODE = 3;
-
- QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection,
- Q_ARG(QString, MISSING_CERT_ERROR_MSG), Q_ARG(int, MISSING_CERT_ERROR_CODE));
return false;
}
@@ -490,10 +496,12 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
}
bool DomainServer::optionallySetupOAuth() {
- const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider";
- const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id";
+ const QString OAUTH_ENABLE_OPTION = "oauth.enable";
+ const QString OAUTH_PROVIDER_URL_OPTION = "oauth.provider";
+ const QString OAUTH_CLIENT_ID_OPTION = "oauth.client-id";
const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
- const QString REDIRECT_HOSTNAME_OPTION = "hostname";
+ const QString OAUTH_CLIENT_SECRET_OPTION = "oauth.client-secret";
+ const QString REDIRECT_HOSTNAME_OPTION = "oauth.hostname";
_oauthProviderURL = QUrl(_settingsManager.valueForKeyPath(OAUTH_PROVIDER_URL_OPTION).toString());
@@ -502,22 +510,24 @@ bool DomainServer::optionallySetupOAuth() {
_oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL();
}
+ _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
+ if (_oauthClientSecret.isEmpty()) {
+ _oauthClientSecret = _settingsManager.valueForKeyPath(OAUTH_CLIENT_SECRET_OPTION).toString();
+ }
auto accountManager = DependencyManager::get();
accountManager->setAuthURL(_oauthProviderURL);
_oauthClientID = _settingsManager.valueForKeyPath(OAUTH_CLIENT_ID_OPTION).toString();
- _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
_hostname = _settingsManager.valueForKeyPath(REDIRECT_HOSTNAME_OPTION).toString();
- if (!_oauthClientID.isEmpty()) {
+ _oauthEnable = _settingsManager.valueForKeyPath(OAUTH_ENABLE_OPTION).toBool();
+
+ if (_oauthEnable) {
if (_oauthProviderURL.isEmpty()
|| _hostname.isEmpty()
|| _oauthClientID.isEmpty()
|| _oauthClientSecret.isEmpty()) {
- static const QString MISSING_OAUTH_INFO_MSG = "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit.";
- static const int MISSING_OAUTH_INFO_ERROR_CODE = 4;
- QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection,
- Q_ARG(QString, MISSING_OAUTH_INFO_MSG), Q_ARG(int, MISSING_OAUTH_INFO_ERROR_CODE));
+ _oauthEnable = false;
return false;
} else {
qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString();
@@ -2693,8 +2703,8 @@ void DomainServer::profileRequestFinished() {
std::pair DomainServer::isAuthenticatedRequest(HTTPConnection* connection) {
static const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
- static const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
- static const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
+ static const QString ADMIN_USERS_CONFIG_KEY = "oauth.admin-users";
+ static const QString ADMIN_ROLES_CONFIG_KEY = "oauth.admin-roles";
static const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username";
static const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password";
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
@@ -2704,12 +2714,11 @@ std::pair DomainServer::isAuthenticatedRequest(HTTPConnection* c
QVariant adminUsersVariant = _settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY);
QVariant adminRolesVariant = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY);
- if (!_oauthProviderURL.isEmpty()
- && (adminUsersVariant.isValid() || adminRolesVariant.isValid())) {
+ if (_oauthEnable) {
QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY);
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
-
+
QUuid cookieUUID;
if (cookieString.indexOf(cookieUUIDRegex) != -1) {
cookieUUID = cookieUUIDRegex.cap(1);
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index 02362abd7b..5e8eee53fe 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -236,6 +236,7 @@ private:
bool _isUsingDTLS { false };
+ bool _oauthEnable { false };
QUrl _oauthProviderURL;
QString _oauthClientID;
QString _oauthClientSecret;
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index 17d473f02c..2e6ccf8be2 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -22,7 +22,9 @@
#include
#include
#include
+#include
#include
+#include
#include
#include
@@ -46,10 +48,14 @@ 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";
@@ -136,6 +142,10 @@ void DomainServerSettingsManager::splitSettingsDescription() {
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];
@@ -170,9 +180,6 @@ void DomainServerSettingsManager::splitSettingsDescription() {
// populate the settings menu groups with what we've collected
- static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings";
- static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings";
-
_settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups;
_settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups;
}
@@ -448,6 +455,77 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
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 > 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) {
+ if (!adminUsers.contains(user)) {
+ adminUsers.append(user);
+ }
+ }
+ }
+
+ *newAdminUsers = adminUsers;
+
+ QVariant* oldAdminRoles = _configMap.valueForKeyPath(ADMIN_ROLES);
+ QVariant* newAdminRoles = _configMap.valueForKeyPath(OAUTH_ADMIN_ROLES, true);
+ QVariantList adminRoles(newAdminRoles->toList());
+ if (oldAdminRoles) {
+ QStringList adminRoleList = oldAdminRoles->toStringList();
+ for (auto & role : adminRoleList) {
+ if (!adminRoles.contains(role)) {
+ adminRoles.append(role);
+ }
+ }
+ }
+
+ *newAdminRoles = adminRoles;
+ }
+
// write the current description version to our settings
*versionVariant = _descriptionVersion;
@@ -1185,7 +1263,23 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
return true;
} else if (url.path() == SETTINGS_MENU_GROUPS_PATH) {
- connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json");
+
+ QJsonObject settings;
+ for (auto & key : _settingsMenuGroups.keys()) {
+ const QJsonArray& settingGroups = _settingsMenuGroups[key].toArray();
+ QJsonArray groups;
+ foreach (const QJsonValue& group, settingGroups) {
+ QJsonObject groupObject = group.toObject();
+ if (!groupObject.contains(DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY)
+ || (groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY].toBool()
+ && _configMap.valueForKeyPath(groupObject[DESCRIPTION_NAME_KEY].toString() + "." + DESCRIPTION_ENABLE_KEY)->toBool() )) {
+ groups.append(groupObject);
+ }
+ }
+ settings[key] = groups;
+ }
+
+ connection->respond(HTTPConnection::StatusCode200, QJsonDocument(settings).toJson(), "application/json");
return true;
} else if (url.path() == SETTINGS_BACKUP_PATH) {
@@ -1440,12 +1534,35 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
}
if (!groupKey.isEmpty() && !groupResponseObject.isEmpty()) {
+
// set this group's object to the constructed object
responseObject[groupKey] = groupResponseObject;
}
}
}
+ // add 'derived' values used primarily for UI
+
+ const QString X509_CERTIFICATE_OPTION = "oauth.cert";
+
+ QString certPath = valueForKeyPath(X509_CERTIFICATE_OPTION).toString();
+ if (!certPath.isEmpty()) {
+ // the user wants to use the following cert and key for HTTPS
+ // this is used for Oauth callbacks when authorizing users against a data server
+ // let's make sure we can load the key and certificate
+
+ qDebug() << "Reading certificate file at" << certPath << "for HTTPS.";
+
+ QFile certFile(certPath);
+ certFile.open(QIODevice::ReadOnly);
+
+ QSslCertificate sslCertificate(&certFile);
+ QString digest = sslCertificate.digest().toHex(':');
+ auto groupObject = responseObject["oauth"].toObject();
+ groupObject["cert-fingerprint"] = digest;
+ responseObject["oauth"] = groupObject;
+ }
+
return responseObject;
}
@@ -1551,23 +1668,66 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
return QJsonObject();
}
-bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
+bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedSettingsObject,
SettingsType settingsType) {
// take a write lock since we're about to overwrite settings in the config map
QWriteLocker locker(&_settingsLock);
+ QJsonObject postedObject(postedSettingsObject);
+
static const QString SECURITY_ROOT_KEY = "security";
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
static const QString BROADCASTING_KEY = "broadcasting";
static const QString WIZARD_KEY = "wizard";
static const QString DESCRIPTION_ROOT_KEY = "descriptors";
+ static const QString OAUTH_ROOT_KEY = "oauth";
+ static const QString OAUTH_KEY_CONTENTS = "key-contents";
+ static const QString OAUTH_CERT_CONTENTS = "cert-contents";
+ static const QString OAUTH_CERT_PATH = "cert";
+ static const QString OAUTH_KEY_PASSPHRASE = "key-passphrase";
+ static const QString OAUTH_KEY_PATH = "key";
auto& settingsVariant = _configMap.getConfig();
bool needRestart = false;
auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription;
+ auto oauthObject = postedObject[OAUTH_ROOT_KEY].toObject();
+ if (oauthObject.contains(OAUTH_CERT_CONTENTS)) {
+ QSslCertificate cert(oauthObject[OAUTH_CERT_CONTENTS].toString().toUtf8());
+ if (!cert.isNull()) {
+ static const QString CERT_FILE_NAME = "certificate.crt";
+ auto certPath = PathUtils::getAppDataFilePath(CERT_FILE_NAME);
+ QFile file(certPath);
+ if (file.open(QFile::WriteOnly)) {
+ file.write(cert.toPem());
+ file.close();
+ }
+ oauthObject[OAUTH_CERT_PATH] = certPath;
+ }
+ oauthObject.remove(OAUTH_CERT_CONTENTS);
+ }
+ if (oauthObject.contains(OAUTH_KEY_CONTENTS)) {
+ QString keyPassphraseString = oauthObject[OAUTH_KEY_PASSPHRASE].toString();
+ QSslKey key(oauthObject[OAUTH_KEY_CONTENTS].toString().toUtf8(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
+ if (!key.isNull()) {
+ static const QString KEY_FILE_NAME = "certificate.key";
+ auto keyPath = PathUtils::getAppDataFilePath(KEY_FILE_NAME);
+ QFile file(keyPath);
+ if (file.open(QFile::WriteOnly)) {
+ file.write(key.toPem());
+ file.close();
+ file.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
+ }
+ oauthObject[OAUTH_KEY_PATH] = keyPath;
+ }
+ oauthObject.remove(OAUTH_KEY_CONTENTS);
+ }
+
+ postedObject[OAUTH_ROOT_KEY] = oauthObject;
+
+ qDebug() << postedObject;
// Iterate on the setting groups
foreach(const QString& rootKey, postedObject.keys()) {
const QJsonValue& rootValue = postedObject[rootKey];