overte-HifiExperiments/libraries/shared/src/AccountManager.cpp
2014-03-13 15:26:02 -07:00

317 lines
12 KiB
C++

//
// AccountManager.cpp
// hifi
//
// Created by Stephen Birarda on 2/18/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QDataStream>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QMap>
#include <QtCore/QStringList>
#include <QtCore/QUrlQuery>
#include <QtNetwork/QNetworkRequest>
#include <QHttpMultiPart>
#include "NodeList.h"
#include "PacketHeaders.h"
#include "AccountManager.h"
const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false;
AccountManager& AccountManager::getInstance() {
static AccountManager sharedInstance;
return sharedInstance;
}
Q_DECLARE_METATYPE(OAuthAccessToken)
Q_DECLARE_METATYPE(DataServerAccountInfo)
Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
Q_DECLARE_METATYPE(JSONCallbackParameters)
const QString ACCOUNTS_GROUP = "accounts";
AccountManager::AccountManager() :
_authURL(),
_networkAccessManager(NULL),
_pendingCallbackMap(),
_accountInfo()
{
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaType<DataServerAccountInfo>("DataServerAccountInfo");
qRegisterMetaTypeStreamOperators<DataServerAccountInfo>("DataServerAccountInfo");
qRegisterMetaType<QNetworkAccessManager::Operation>("QNetworkAccessManager::Operation");
qRegisterMetaType<JSONCallbackParameters>("JSONCallbackParameters");
}
const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
void AccountManager::logout() {
// a logout means we want to delete the DataServerAccountInfo we currently have for this URL, in-memory and in file
_accountInfo = DataServerAccountInfo();
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
settings.remove(keyURLString);
qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
emit logoutComplete();
// the username has changed to blank
emit usernameChanged(QString());
}
void AccountManager::setAuthURL(const QUrl& authURL) {
if (_authURL != authURL) {
_authURL = authURL;
qDebug() << "URL for node authentication has been changed to" << qPrintable(_authURL.toString());
qDebug() << "Re-setting authentication flow.";
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace("slashslash", "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
emit accessTokenChanged();
}
}
// tell listeners that the auth endpoint has changed
emit authEndpointChanged();
}
}
void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart) {
QMetaObject::invokeMethod(this, "invokedRequest",
Q_ARG(const QString&, path),
Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray),
Q_ARG(QHttpMultiPart*, dataMultiPart));
}
void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) {
if (!_networkAccessManager) {
_networkAccessManager = new QNetworkAccessManager(this);
}
if (hasValidAccessToken()) {
QNetworkRequest authenticatedRequest;
QUrl requestURL = _authURL;
requestURL.setPath(path);
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
authenticatedRequest.setUrl(requestURL);
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Making an authenticated request to" << qPrintable(requestURL.toString());
if (!dataByteArray.isEmpty()) {
qDebug() << "The POST/PUT body -" << QString(dataByteArray);
}
}
QNetworkReply* networkReply = NULL;
switch (operation) {
case QNetworkAccessManager::GetOperation:
networkReply = _networkAccessManager->get(authenticatedRequest);
break;
case QNetworkAccessManager::PostOperation:
case QNetworkAccessManager::PutOperation:
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (dataMultiPart) {
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = _networkAccessManager->post(authenticatedRequest, dataMultiPart);
} else {
networkReply = _networkAccessManager->put(authenticatedRequest, dataMultiPart);
}
} else {
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = _networkAccessManager->post(authenticatedRequest, dataByteArray);
} else {
networkReply = _networkAccessManager->put(authenticatedRequest, dataByteArray);
}
}
break;
default:
// other methods not yet handled
break;
}
if (networkReply) {
if (!callbackParams.isEmpty()) {
// if we have information for a callback, insert the callbackParams into our local map
_pendingCallbackMap.insert(networkReply, callbackParams);
}
// if we ended up firing of a request, hook up to it now
connect(networkReply, SIGNAL(finished()), this, SLOT(passSuccessToCallback()));
connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(passErrorToCallback(QNetworkReply::NetworkError)));
}
}
}
void AccountManager::passSuccessToCallback() {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
if (callbackParams.jsonCallbackReceiver) {
// invoke the right method on the callback receiver
QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod),
Q_ARG(const QJsonObject&, jsonResponse.object()));
// remove the related reply-callback group from the map
_pendingCallbackMap.remove(requestReply);
} else {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Received JSON response from data-server that has no matching callback.";
qDebug() << jsonResponse;
}
}
}
void AccountManager::passErrorToCallback(QNetworkReply::NetworkError errorCode) {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
if (callbackParams.errorCallbackReceiver) {
// invoke the right method on the callback receiver
QMetaObject::invokeMethod(callbackParams.errorCallbackReceiver, qPrintable(callbackParams.errorCallbackMethod),
Q_ARG(QNetworkReply::NetworkError, errorCode),
Q_ARG(const QString&, requestReply->errorString()));
// remove the related reply-callback group from the map
_pendingCallbackMap.remove(requestReply);
} else {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Received error response from data-server that has no matching callback.";
qDebug() << "Error" << errorCode << "-" << requestReply->errorString();
}
}
}
bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "An access token is required for requests to" << qPrintable(_authURL.toString());
}
return false;
} else {
return true;
}
}
bool AccountManager::checkAndSignalForAccessToken() {
bool hasToken = hasValidAccessToken();
if (!hasToken) {
// emit a signal so somebody can call back to us and request an access token given a username and password
emit authRequired();
}
return hasToken;
}
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
if (!_networkAccessManager) {
_networkAccessManager = new QNetworkAccessManager(this);
}
QNetworkRequest request;
QUrl grantURL = _authURL;
grantURL.setPath("/oauth/token");
QByteArray postData;
postData.append("grant_type=password&");
postData.append("username=" + login + "&");
postData.append("password=" + password);
request.setUrl(grantURL);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* requestReply = _networkAccessManager->post(request, postData);
connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestFinished);
connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError)));
}
void AccountManager::requestFinished() {
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
qDebug() << "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("");
qDebug() << "Storing an account with access-token for" << qPrintable(rootURL.toString());
_accountInfo = DataServerAccountInfo(rootObject);
emit loginComplete(rootURL);
// the username has changed to whatever came back
emit usernameChanged(_accountInfo.getUsername());
// we have found or requested an access token
emit accessTokenChanged();
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
} else {
// TODO: error handling
qDebug() << "Error in response for password grant -" << rootObject["error_description"].toString();
}
}
void AccountManager::requestError(QNetworkReply::NetworkError error) {
// TODO: error handling
qDebug() << "AccountManager requestError - " << error;
}