Implement further handling of Interface OAuth2

This is preliminary, needs to be revisited in a more dynamic and clean fashion with time.
This commit is contained in:
Kasen IO 2020-07-29 01:53:21 -04:00
parent 294646a141
commit 5706a42b56
3 changed files with 134 additions and 11 deletions

View file

@ -25,6 +25,7 @@
#include <UserActivityLogger.h>
#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<AccountManager>();
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
// 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<DomainAccountManager>()->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<DomainAccountManager>()->requestAccessToken(username, password, domainAuthProvider);
}
void LoginDialog::loginThroughOculus() {

View file

@ -11,18 +11,132 @@
#include "DomainAccountManager.h"
#include <QTimer>
#include <SettingHandle.h>
#include <QTimer>
#include <QtCore/QDateTime>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QHttpMultiPart>
#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<QString> domainAccessToken {"private/domainAccessToken", "" };
Setting::Handle<QString> domainAccessRefreshToken {"private/domainAccessToken", "" };
Setting::Handle<int> domainAccessTokenExpiresIn {"private/domainAccessTokenExpiresIn", -1 };
Setting::Handle<QString> 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<QNetworkReply*>(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() {

View file

@ -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