mirror of
https://github.com/lubosz/overte.git
synced 2025-04-07 16:42:08 +02:00
DEV-444 - OAuth administration improvements
This commit is contained in:
parent
51ef37fd27
commit
a0ad1f3a68
7 changed files with 446 additions and 37 deletions
|
@ -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",
|
||||
|
|
|
@ -2,6 +2,9 @@ var DomainInfo = null;
|
|||
|
||||
var viewHelpers = {
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced) {
|
||||
if (setting.hidden) {
|
||||
return "";
|
||||
}
|
||||
form_group = "<div class='form-group " +
|
||||
(isAdvanced ? Settings.ADVANCED_CLASS : "") + " " +
|
||||
(setting.deprecated ? Settings.DEPRECATED_CLASS : "" ) + "' " +
|
||||
|
@ -82,8 +85,9 @@ var viewHelpers = {
|
|||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + (_.has(setting, 'password_placeholder') ? setting.password_placeholder : setting_value) + "'/>"
|
||||
}
|
||||
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
if (setting.help) {
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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 "<li class='setting-group'><a href='" + settingsGroupAnchor(base, html_id) + "'>" + group.label + "<span class='badge'></span></a></li>";
|
||||
|
|
|
@ -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.<BR/>";
|
||||
}
|
||||
if (!$('input[name="oauth.provider"]').val()) {
|
||||
oauthErrors += "OAuth requires a provider.<BR/>";
|
||||
}
|
||||
if (!$('input[name="oauth.hostname"]').val()) {
|
||||
oauthErrors += "OAuth requires a hostname.<BR/>";
|
||||
}
|
||||
if (!$('input[name="' + SSL_PRIVATE_KEY_PATH + '"]').val() && !$('input[name="' + SSL_PRIVATE_KEY_CONTENTS_NAME + '"]').val()) {
|
||||
oauthErrors += "OAuth requires an SSL Private Key.<BR/>";
|
||||
}
|
||||
if (!$('input[name="' + SSL_CERT_PATH + '"]').val() && !$('input[name="' + SSL_CERT_CONTENTS_NAME + '"]').val()) {
|
||||
oauthErrors += "OAuth requires an SSL Certificate.<BR/>";
|
||||
}
|
||||
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.<BR/>";
|
||||
}
|
||||
}
|
||||
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 = "<div class='form-group undefined'>";
|
||||
html += "<label class='control-label'>SSL Private Key</label><BR/>";
|
||||
html += "<label id='key-path-label'class='control-label'>Path</label>";
|
||||
html += "<input id='" + SSL_PRIVATE_KEY_FILE_ID + "' type='file' accept='.key'/>";
|
||||
html += "<input id='" + SSL_PRIVATE_KEY_CONTENTS_ID + "' name='" + SSL_PRIVATE_KEY_CONTENTS_NAME + "' type='hidden'/>";
|
||||
html += "</div>";
|
||||
html += "<div class='form-group undefined'>";
|
||||
html += "<label class='control-label'>SSL Cert</label>";
|
||||
html += "<div id='cert-fingerprint'><b>Fingerprint:</b><span id='" + SSL_CERT_FINGERPRINT_SPAN_ID + "'>" + data.values.oauth["cert-fingerprint"] + "</span></div>";
|
||||
html += "<label id='cert-path-label' class='control-label'>Path</label>";
|
||||
html += "<input id='" + SSL_CERT_FILE_ID + "' type='file' accept='.cer,.crt'/>";
|
||||
html += "<input id='" + SSL_CERT_CONTENTS_ID + "' name='" + SSL_CERT_CONTENTS_NAME + "' type='hidden'/>";
|
||||
html += "</div>";
|
||||
|
||||
$('#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';
|
||||
|
||||
|
|
|
@ -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>();
|
||||
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<bool, QString> 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<bool, QString> 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);
|
||||
|
|
|
@ -236,6 +236,7 @@ private:
|
|||
|
||||
bool _isUsingDTLS { false };
|
||||
|
||||
bool _oauthEnable { false };
|
||||
QUrl _oauthProviderURL;
|
||||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
#include <QtCore/QThread>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QUrlQuery>
|
||||
#include <QtNetwork/QSslKey>
|
||||
#include <QSaveFile>
|
||||
#include <QPair>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <Assignment.h>
|
||||
|
@ -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<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) {
|
||||
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];
|
||||
|
|
Loading…
Reference in a new issue