From 5706a42b56de695c0f1135814fc24be60c69626f Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 29 Jul 2020 01:53:21 -0400 Subject: [PATCH] Implement further handling of Interface OAuth2 This is preliminary, needs to be revisited in a more dynamic and clean fashion with time. --- interface/src/ui/LoginDialog.cpp | 12 +- .../networking/src/DomainAccountManager.cpp | 122 +++++++++++++++++- .../networking/src/DomainAccountManager.h | 11 +- 3 files changed, 134 insertions(+), 11 deletions(-) diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 94800529b9..ac1aa95f19 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -25,6 +25,7 @@ #include #include "AccountManager.h" +#include "DomainAccountManager.h" #include "DependencyManager.h" #include "DialogsManager.h" #include "Menu.h" @@ -40,12 +41,17 @@ const QUrl LOGIN_DIALOG = PathUtils::qmlUrl("OverlayLoginDialog.qml"); LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) { auto accountManager = DependencyManager::get(); + auto domainAccountManager = DependencyManager::get(); // the login hasn't been dismissed yet if the user isn't logged in and is encouraged to login. #if !defined(Q_OS_ANDROID) connect(accountManager.data(), &AccountManager::loginComplete, this, &LoginDialog::handleLoginCompleted); connect(accountManager.data(), &AccountManager::loginFailed, this, &LoginDialog::handleLoginFailed); + connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, + this, &LoginDialog::handleLoginCompleted); + connect(domainAccountManager.data(), &DomainAccountManager::loginFailed, + this, &LoginDialog::handleLoginFailed); connect(qApp, &Application::loginDialogFocusEnabled, this, &LoginDialog::focusEnabled); connect(qApp, &Application::loginDialogFocusDisabled, this, &LoginDialog::focusDisabled); connect(this, SIGNAL(dismissedLoginDialog()), qApp, SLOT(onDismissedLoginDialog())); @@ -139,11 +145,7 @@ void LoginDialog::login(const QString& username, const QString& password) const void LoginDialog::loginDomain(const QString& username, const QString& password, const QString& domainAuthProvider) const { qDebug() << "Attempting to login" << username << "into a domain through" << domainAuthProvider; - // ####### TODO - // DependencyManager::get()->requestAccessToken(username, password, domainAuthProvider); - - // ####### TODO: It may not be necessary to pass domainAuthProvider to the login dialog and through to here because it was - // originally provided to the QML from C++. + DependencyManager::get()->requestAccessToken(username, password, domainAuthProvider); } void LoginDialog::loginThroughOculus() { diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index d1da09817c..524a7c4b0a 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -11,18 +11,132 @@ #include "DomainAccountManager.h" -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DomainAccountManager.h" +#include "NetworkingConstants.h" +#include "OAuthAccessToken.h" +#include "NetworkLogging.h" +#include "NodeList.h" +#include "udt/PacketHeaders.h" +#include "NetworkAccessManager.h" + +const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; +const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; + +Setting::Handle domainAccessToken {"private/domainAccessToken", "" }; +Setting::Handle domainAccessRefreshToken {"private/domainAccessToken", "" }; +Setting::Handle domainAccessTokenExpiresIn {"private/domainAccessTokenExpiresIn", -1 }; +Setting::Handle domainAccessTokenType {"private/domainAccessTokenType", "" }; + +QUrl _domainAuthProviderURL; + +// FIXME: If you try to authenticate this way on another domain, no one knows what will happen. Probably death. DomainAccountManager::DomainAccountManager() { - + connect(this, &DomainAccountManager::loginComplete, this, &DomainAccountManager::sendInterfaceAccessTokenToServer); } +void DomainAccountManager::requestAccessToken(const QString& login, const QString& password, const QString& domainAuthProvider) { + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest request; + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); + + _domainAuthProviderURL = domainAuthProvider; + _domainAuthProviderURL.setPath("/oauth/token"); + + QByteArray postData; + postData.append("grant_type=password&"); + postData.append("username=" + QUrl::toPercentEncoding(login) + "&"); + postData.append("password=" + QUrl::toPercentEncoding(password) + "&"); + postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); + + request.setUrl(_domainAuthProviderURL); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply* requestReply = networkAccessManager.post(request, postData); + connect(requestReply, &QNetworkReply::finished, this, &DomainAccountManager::requestAccessTokenFinished); +} + +void DomainAccountManager::requestAccessTokenFinished() { + QNetworkReply* requestReply = reinterpret_cast(sender()); + + QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); + const QJsonObject& rootObject = jsonResponse.object(); + + if (!rootObject.contains("error")) { + // construct an OAuthAccessToken from the json object + + if (!rootObject.contains("access_token") || !rootObject.contains("expires_in") + || !rootObject.contains("token_type")) { + // TODO: error handling - malformed token response + qCDebug(networking) << "Received a response for password grant that is missing one or more expected values."; + } else { + // clear the path from the response URL so we have the right root URL for this access token + QUrl rootURL = requestReply->url(); + rootURL.setPath(""); + + qCDebug(networking) << "Storing a domain account with access-token for" << qPrintable(rootURL.toString()); + + setAccessTokenFromJSON(rootObject); + + emit loginComplete(rootURL); + } + } else { + qCDebug(networking) << "Error in response for password grant -" << rootObject["error_description"].toString(); + emit loginFailed(); + } +} + +void DomainAccountManager::sendInterfaceAccessTokenToServer() { + // TODO: Send successful packet to the domain-server. +} + +bool DomainAccountManager::accessTokenIsExpired() { + return domainAccessTokenExpiresIn.get() != -1 && domainAccessTokenExpiresIn.get() <= QDateTime::currentMSecsSinceEpoch(); +} + + bool DomainAccountManager::hasValidAccessToken() { + QString currentDomainAccessToken = domainAccessToken.get(); + + if (currentDomainAccessToken.isEmpty() || accessTokenIsExpired()) { - // #######: TODO + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { + qCDebug(networking) << "An access token is required for requests to" + << qPrintable(_domainAuthProviderURL.toString()); + } - return false; + return false; + } else { + + // ####### TODO:: + + // if (!_isWaitingForTokenRefresh && needsToRefreshToken()) { + // refreshAccessToken(); + // } + + return true; + } + +} + +void DomainAccountManager::setAccessTokenFromJSON(const QJsonObject& jsonObject) { + domainAccessToken.set(jsonObject["access_token"].toString()); + domainAccessRefreshToken.set(jsonObject["refresh_token"].toString()); + domainAccessTokenExpiresIn.set(QDateTime::currentMSecsSinceEpoch() + (jsonObject["expires_in"].toDouble() * 1000)); + domainAccessTokenType.set(jsonObject["token_type"].toString()); } bool DomainAccountManager::checkAndSignalForAccessToken() { diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 696df71ab1..4bb197175e 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -25,15 +25,22 @@ public: Q_INVOKABLE bool checkAndSignalForAccessToken(); public slots: - + void requestAccessToken(const QString& login, const QString& password, const QString& domainAuthProvider); + + void requestAccessTokenFinished(); signals: void authRequired(); + void loginComplete(const QUrl& authURL); + void loginFailed(); + void logoutComplete(); private slots: private: bool hasValidAccessToken(); - + bool accessTokenIsExpired(); + void setAccessTokenFromJSON(const QJsonObject&); + void sendInterfaceAccessTokenToServer(); }; #endif // hifi_DomainAccountManager_h