From bc56eb5ac75b5f677c01faa72e5f39f325f4f477 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2020 17:12:31 +1200 Subject: [PATCH] Update to work with WordPress plugin --- .../resources/describe-settings.json | 14 ++++---- domain-server/src/DomainGatekeeper.cpp | 36 +++++++++---------- .../networking/src/DomainAccountManager.cpp | 26 +++++--------- .../networking/src/DomainAccountManager.h | 2 ++ libraries/networking/src/DomainHandler.cpp | 4 ++- 5 files changed, 36 insertions(+), 46 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 5560fdba56..c901772296 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -77,13 +77,6 @@ "type": "checkbox", "advanced": true }, - { - "name": "domain_access_token", - "label": "Domain API Access Token", - "help": "This is the access token that your domain-server will use to verify users and their roles. This token must grant access to that permission set on your REST API server.", - "advanced": true, - "backup": false - }, { "name": "oauth2_url_path", "label": "Authentication URL", @@ -95,6 +88,13 @@ "label": "WordPress API URL Base", "help": "The URL base that the domain server will use to make WordPress API calls. Typically \"https://oursite.com/wp-json/\". However, if using non-pretty permalinks or otherwise get a 404 error then try \"https://oursite.com/?rest_route=/\".", "advanced": true + }, + { + "name": "plugin_client_id", + "label": "WordPress Plugin Client ID", + "help": "This is the client ID from the WordPress plugin configuration.", + "advanced": true, + "backup": false } ] }, diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 9019a530d7..ad637f3c17 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -453,6 +453,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo const QString AUTHENTICATION_ENAABLED = "authentication.enable_oauth2"; const QString AUTHENTICATION_OAUTH2_URL_PATH = "authentication.oauth2_url_path"; const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base"; +const QString AUTHENTICATION_PLUGIN_CLIENT_ID = "authentication.plugin_client_id"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location"; @@ -553,8 +554,15 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (domainAuthURLVariant.canConvert()) { domainAuthURL = domainAuthURLVariant.toString(); } + QString domainAuthClientID; + auto domainAuthClientIDVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_PLUGIN_CLIENT_ID); + if (domainAuthClientIDVariant.canConvert()) { + domainAuthClientID = domainAuthClientIDVariant.toString(); + } + sendConnectionDeniedPacket("You lack the required domain permissions to connect to this domain.", - nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, domainAuthURL); + nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, + domainAuthURL + "|" + domainAuthClientID); } else { sendConnectionDeniedPacket("You lack the required metaverse permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedMetaverse); @@ -1242,11 +1250,9 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& } // Get data pertaining to "me", the user who generated the access token. - // ####### TODO: Confirm API route and data w.r.t. OAuth2 plugin's capabilities. [plugin] - const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; - QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE; - - // ####### TODO: Append a random key to check in response? [security] + const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me"; + const QString WORDPRESS_USER_QUERY = "_fields=username,roles"; + QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE + (apiBase.contains("?") ? "&" : "?") + WORDPRESS_USER_QUERY; QNetworkRequest request; @@ -1275,34 +1281,24 @@ void DomainGatekeeper::requestDomainUserFinished() { auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (200 <= httpStatus && httpStatus < 300) { - // Success. - // ####### TODO: Verify Expected response. [plugin] - /* - { - id: 2, - username : 'apiuser', - roles : ['subscriber'] , - } - */ QString username = rootObject["username"].toString().toLower(); if (_inFlightDomainUserIdentityRequests.contains(username)) { // Success! Verified user. _verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username)); _inFlightDomainUserIdentityRequests.remove(username); + + // ####### TODO: Handle roles. + } else { // Failure. qDebug() << "Unexpected username in response for user details -" << username; } - // ####### TODO: Handle roles if available. [plugin] - } else { // Failure. - - // ####### TODO: Error fields to report. [plugin] qDebug() << "Error in response for user details -" << httpStatus << requestReply->error() - << "-" << rootObject["error"].toString(); + << "-" << rootObject["error"].toString() << rootObject["error_description"].toString(); _inFlightDomainUserIdentityRequests.clear(); } diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index cb67d30d3e..9910252317 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -26,8 +26,6 @@ // FIXME: Generalize to other OAuth2 sources for domain login. const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; -const QString DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE = "foo bar"; // ####### TODO: WordPress plugin's required scope. [plugin] -// ####### TODO: Should scope be configured in domain server settings? [plugin] // ####### TODO: Add storing domain URL and check against it when retrieving values? // ####### TODO: Add storing _authURL and check against it when retrieving values? @@ -71,15 +69,15 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - // ####### TODO: WordPress plugin's authorization requirements. [plugin] - request.setRawHeader(QByteArray("Authorization"), QByteArray("Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x")); + // miniOrange WordPress API Authentication plugin: + // - Requires "client_id" parameter. + // - Ignores "state" parameter. QByteArray formData; formData.append("grant_type=password&"); formData.append("username=" + QUrl::toPercentEncoding(username) + "&"); formData.append("password=" + QUrl::toPercentEncoding(password) + "&"); - formData.append("scope=" + DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE); - // ####### TODO: Include state? [plugin] + formData.append("client_id=" + _clientID); request.setUrl(_authURL); @@ -100,20 +98,13 @@ void DomainAccountManager::requestAccessTokenFinished() { auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (200 <= httpStatus && httpStatus < 300) { - // ####### TODO: Process response state? [plugin] - // ####### TODO: Process response scope? [plugin] - - // ####### TODO: Which method are the tokens provided in? [plugin] + // miniOrange plugin provides no scope. if (rootObject.contains("access_token")) { // Success. - QUrl rootURL = requestReply->url(); rootURL.setPath(""); - setTokensFromJSON(rootObject, rootURL); - emit loginComplete(); - } else { // Failure. qCDebug(networking) << "Received a response for password grant that is missing one or more expected values."; @@ -122,10 +113,8 @@ void DomainAccountManager::requestAccessTokenFinished() { } else { // Failure. - - // ####### TODO: Error object fields to report. [plugin] - qCDebug(networking) << "Error in response for password grant -" << httpStatus << requestReply->error() - << "_" << rootObject["error"].toString(); + qCDebug(networking) << "Error in response for password grant -" << httpStatus << requestReply->error() + << "-" << rootObject["error"].toString() << rootObject["error_description"].toString(); emit loginFailed(); } } @@ -173,6 +162,7 @@ void DomainAccountManager::setTokensFromJSON(const QJsonObject& jsonObject, cons // ####### TODO: Enable and use these. // ####### TODO: Protect these per AccountManager? + // ######: TODO: clientID needed? /* qCDebug(networking) << "Storing a domain account with access-token for" << qPrintable(url.toString()); domainAccessToken.set(jsonObject["access_token"].toString()); diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 6578963bf8..213d40a8d8 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -24,6 +24,7 @@ public: DomainAccountManager(); void setAuthURL(const QUrl& authURL); + void setClientID(const QString& clientID) { _clientID = clientID; } QString getUsername() { return _username; } QString getAccessToken() { return _access_token; } @@ -51,6 +52,7 @@ private: void sendInterfaceAccessTokenToServer(); QUrl _authURL; + QString _clientID; QString _username; // ####### TODO: Store elsewhere? QString _access_token; // ####... "" QString _refresh_token; // ####... "" diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 7049f2aef6..c1a24748f4 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -589,7 +589,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); if (!extraInfo.isEmpty()) { - accountManager->setAuthURL(extraInfo); + auto extraInfoComponents = extraInfo.split("|"); + accountManager->setAuthURL(extraInfoComponents.value(0)); + accountManager->setClientID(extraInfoComponents.value(1)); } if (!_hasCheckedForDomainAccessToken) {