overte-HifiExperiments/libraries/shared/src/AccountManager.cpp
2014-02-21 14:41:43 -08:00

255 lines
No EOL
10 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/QUrlQuery>
#include <QtNetwork/QNetworkRequest>
#include "NodeList.h"
#include "PacketHeaders.h"
#include "AccountManager.h"
QMap<QUrl, OAuthAccessToken> AccountManager::_accessTokens = QMap<QUrl, OAuthAccessToken>();
AccountManager& AccountManager::getInstance() {
static AccountManager sharedInstance;
return sharedInstance;
}
Q_DECLARE_METATYPE(OAuthAccessToken)
Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
Q_DECLARE_METATYPE(JSONCallbackParameters)
const QString ACCOUNT_TOKEN_GROUP = "tokens";
AccountManager::AccountManager() :
_rootURL(),
_username(),
_networkAccessManager(),
_pendingCallbackMap()
{
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaType<QNetworkAccessManager::Operation>("QNetworkAccessManager::Operation");
qRegisterMetaType<JSONCallbackParameters>("JSONCallbackParameters");
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNT_TOKEN_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", "//"));
// pull out the stored access token and put it in our in memory array
_accessTokens.insert(keyURL, settings.value(key).value<OAuthAccessToken>());
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
}
}
void AccountManager::setRootURL(const QUrl& rootURL) {
if (_rootURL != rootURL) {
_rootURL = rootURL;
// we have an auth URL change, set the username empty
// we will need to ask for profile information again
_username.clear();
qDebug() << "URL for node authentication has been changed to" << qPrintable(_rootURL.toString());
qDebug() << "Re-setting authentication flow.";
}
}
void AccountManager::setUsername(const QString& username) {
if (_username != username) {
_username = username;
qDebug() << "Changing username to" << username;
emit usernameChanged(username);
}
}
void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray) {
QMetaObject::invokeMethod(this, "invokedRequest",
Q_ARG(const QString&, path),
Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray));
}
void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray) {
if (hasValidAccessToken()) {
QNetworkRequest authenticatedRequest;
QUrl requestURL = _rootURL;
requestURL.setPath(path);
requestURL.setQuery("access_token=" + _accessTokens.value(_rootURL).token);
authenticatedRequest.setUrl(requestURL);
qDebug() << "Making an authenticated request to" << qPrintable(requestURL.toString());
QNetworkReply* networkReply = NULL;
switch (operation) {
case QNetworkAccessManager::GetOperation:
networkReply = _networkAccessManager.get(authenticatedRequest);
break;
case QNetworkAccessManager::PostOperation:
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
networkReply = _networkAccessManager.post(authenticatedRequest, dataByteArray);
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 {
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 {
qDebug() << "Received error response from data-server that has no matching callback.";
qDebug() << "Error" << errorCode << "-" << requestReply->errorString();
}
}
bool AccountManager::hasValidAccessToken() {
OAuthAccessToken accessToken = _accessTokens.value(_rootURL);
if (accessToken.token.isEmpty() || accessToken.isExpired()) {
qDebug() << "An access token is required for requests to" << qPrintable(_rootURL.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 authenticationRequired();
}
return hasToken;
}
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
QNetworkRequest request;
QUrl grantURL = _rootURL;
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 access token for" << qPrintable(rootURL.toString());
OAuthAccessToken freshAccessToken(rootObject);
_accessTokens.insert(rootURL, freshAccessToken);
// pull username from the response
setUsername(rootObject["user"].toObject()["username"].toString());
emit receivedAccessToken(rootURL);
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNT_TOKEN_GROUP);
localSettings.setValue(rootURL.toString().replace("//", "slashslash"), QVariant::fromValue(freshAccessToken));
}
} 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;
}