From 40375b8c81a37a7a53dab5f67b45177b6ab15420 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 4 Aug 2014 14:46:33 -0700 Subject: [PATCH 1/6] add a navbar to domain-server web page --- domain-server/resources/web/header.html | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index 2be603b00e..e1a9d1ea8a 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -8,4 +8,27 @@ +
\ No newline at end of file From 2c1277ef374db722ae7096ac695fefee0271b9fb Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 4 Aug 2014 14:47:17 -0700 Subject: [PATCH 2/6] remove voxel settings from domain-server settings until ready to go --- .../resources/web/settings/describe.json | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/domain-server/resources/web/settings/describe.json b/domain-server/resources/web/settings/describe.json index 2d90680e01..f4920a7b50 100644 --- a/domain-server/resources/web/settings/describe.json +++ b/domain-server/resources/web/settings/describe.json @@ -28,33 +28,5 @@ "default": "" } } - }, - "voxels": { - "label": "Voxels", - "assignment-types": [2,3], - "settings": { - "voxel-wallet": { - "label": "Destination Wallet ID", - "help": "Wallet to be paid for voxel changes", - "placeholder": "00000000-0000-0000-0000-000000000000", - "default": "" - }, - "per-voxel-credits": { - "type": "double", - "label": "Per Voxel Cost", - "help": "Credit cost to change each voxel", - "placeholder": "0.0", - "default": "0.0", - "input_addon": "₵" - }, - "per-meter-cubed-credits": { - "type": "double", - "label": "Per Meter Cubed Cost", - "help": "Credit cost to change each cubed meter of voxel space", - "placeholder": "0.0", - "default": "0.0", - "input_addon": "₵" - } - } } } \ No newline at end of file From 10c94023f199be7ee79a48b94a8fe3b3e1ea9615 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 4 Aug 2014 15:25:14 -0700 Subject: [PATCH 3/6] handle basic authentication for domain-server admin --- domain-server/src/DomainServer.cpp | 64 +++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2ecae73d0a..a76d3a916d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1280,6 +1280,9 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie"; const QString ADMIN_USERS_CONFIG_KEY = "admin-users"; const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles"; + const QString BASIC_AUTH_CONFIG_KEY = "basic-auth"; + + const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server."; if (!_oauthProviderURL.isEmpty() && (_argumentVariantMap.contains(ADMIN_USERS_CONFIG_KEY) || _argumentVariantMap.contains(ADMIN_ROLES_CONFIG_KEY))) { @@ -1293,6 +1296,11 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl cookieUUID = cookieUUIDRegex.cap(1); } + if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) { + qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication." + << "These cannot be combined - using OAuth for authentication."; + } + if (!cookieUUID.isNull() && _cookieSessionHash.contains(cookieUUID)) { // pull the QJSONObject for the user with this cookie UUID DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID); @@ -1315,8 +1323,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl } } - QString unauthenticatedRequest = "You do not have permission to access this domain-server."; - connection->respond(HTTPConnection::StatusCode401, unauthenticatedRequest.toUtf8()); + connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY); // the user does not have allowed username or role, return 401 return false; @@ -1340,6 +1347,59 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl // we don't know about this user yet, so they are not yet authenticated return false; } + } else if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) { + // config file contains username and password combinations for basic auth + const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization"; + + // check if a username and password have been provided with the request + QString basicAuthString = connection->requestHeaders().value(BASIC_AUTH_HEADER_KEY); + + if (!basicAuthString.isEmpty()) { + QStringList splitAuthString = basicAuthString.split(' '); + QString base64String = splitAuthString.size() == 2 ? splitAuthString[1] : ""; + QString credentialString = QByteArray::fromBase64(base64String.toLocal8Bit()); + + if (!credentialString.isEmpty()) { + QStringList credentialList = credentialString.split(':'); + if (credentialList.size() == 2) { + QString username = credentialList[0]; + QString password = credentialList[1]; + + // we've pulled a username and password - now check if there is a match in our basic auth hash + QJsonObject basicAuthObject = _argumentVariantMap.value(BASIC_AUTH_CONFIG_KEY).toJsonValue().toObject(); + + if (basicAuthObject.contains(username)) { + const QString BASIC_AUTH_USER_PASSWORD_KEY = "password"; + QJsonObject userObject = basicAuthObject.value(username).toObject(); + + if (userObject.contains(BASIC_AUTH_USER_PASSWORD_KEY) + && userObject.value(BASIC_AUTH_USER_PASSWORD_KEY).toString() == password) { + // this is username / password match - let this user in + return true; + } + } + } + } + } + + // basic HTTP auth being used but no username and password are present + // or the username and password are not correct + // send back a 401 and ask for basic auth + + const QByteArray HTTP_AUTH_REQUEST_HEADER_KEY = "WWW-Authenticate"; + static QString HTTP_AUTH_REALM_STRING = QString("Basic realm='%1 %2'") + .arg(_hostname.isEmpty() ? "localhost" : _hostname) + .arg("domain-server"); + + Headers basicAuthHeader; + basicAuthHeader.insert(HTTP_AUTH_REQUEST_HEADER_KEY, HTTP_AUTH_REALM_STRING.toUtf8()); + + connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY, + HTTPConnection::DefaultContentType, basicAuthHeader); + + // not authenticated, bubble up false + return false; + } else { // we don't have an OAuth URL + admin roles/usernames, so all users are authenticated return true; From c786ee5c2698304f977b6895a3fae4187f01db16 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 4 Aug 2014 15:27:34 -0700 Subject: [PATCH 4/6] dynamically change active link in domain-server navbar --- domain-server/resources/web/footer.html | 3 ++- domain-server/resources/web/header.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/web/footer.html b/domain-server/resources/web/footer.html index d1a3fc29e8..aa6821a3b9 100644 --- a/domain-server/resources/web/footer.html +++ b/domain-server/resources/web/footer.html @@ -1,3 +1,4 @@
- \ No newline at end of file + + \ No newline at end of file diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index e1a9d1ea8a..c007642724 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -25,7 +25,7 @@ From 6febca3533824d5a2639b7330c81a6721a140ce7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 4 Aug 2014 15:37:06 -0700 Subject: [PATCH 5/6] add missing domain-server javascript file --- domain-server/resources/web/js/domain-server.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 domain-server/resources/web/js/domain-server.js diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js new file mode 100644 index 0000000000..3f78d8f466 --- /dev/null +++ b/domain-server/resources/web/js/domain-server.js @@ -0,0 +1,10 @@ +$(document).ready(function(){ + var url = window.location; + // Will only work if string in href matches with location + $('ul.nav a[href="'+ url +'"]').parent().addClass('active'); + + // Will also work for relative and absolute hrefs + $('ul.nav a').filter(function() { + return this.href == url; + }).parent().addClass('active'); +}); \ No newline at end of file From 9fc545001cb563a9a1dfc015c23797b912a26715 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 4 Aug 2014 15:40:45 -0700 Subject: [PATCH 6/6] look for a config.json file by default in resources --- libraries/shared/src/HifiConfigVariantMap.cpp | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 02cce80104..6fe39a8ccf 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include #include @@ -68,29 +69,35 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL int configIndex = argumentList.indexOf(CONFIG_FILE_OPTION); + QString configFilePath; + if (configIndex != -1) { // we have a config file - try and read it - QString configFilePath = argumentList[configIndex + 1]; - QFile configFile(configFilePath); - - if (configFile.exists()) { - qDebug() << "Reading JSON config file at" << configFilePath; - configFile.open(QIODevice::ReadOnly); - - QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll()); - QJsonObject rootObject = configDocument.object(); - - // enumerate the keys of the configDocument object - foreach(const QString& key, rootObject.keys()) { - - if (!mergedMap.contains(key)) { - // no match in existing list, add it - mergedMap.insert(key, QVariant(rootObject[key])); - } + configFilePath = argumentList[configIndex + 1]; + } else { + // no config file - try to read a file at resources/config.json + configFilePath = QCoreApplication::applicationDirPath() + "/resources/config.json"; + } + + QFile configFile(configFilePath); + + if (configFile.exists()) { + qDebug() << "Reading JSON config file at" << configFilePath; + configFile.open(QIODevice::ReadOnly); + + QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll()); + QJsonObject rootObject = configDocument.object(); + + // enumerate the keys of the configDocument object + foreach(const QString& key, rootObject.keys()) { + + if (!mergedMap.contains(key)) { + // no match in existing list, add it + mergedMap.insert(key, QVariant(rootObject[key])); } - } else { - qDebug() << "Could not find JSON config file at" << configFilePath; } + } else { + qDebug() << "Could not find JSON config file at" << configFilePath; } return mergedMap;