diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3015a638f6..0b49c08f51 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -127,7 +127,7 @@ Menu::Menu() : toggleLoginMenuItem(); // connect to the appropriate slots of the AccountManager so that we can change the Login/Logout menu item - connect(&accountManager, &AccountManager::accessTokenChanged, this, &Menu::toggleLoginMenuItem); + connect(&accountManager, &AccountManager::profileChanged, this, &Menu::toggleLoginMenuItem); connect(&accountManager, &AccountManager::logoutComplete, this, &Menu::toggleLoginMenuItem); addDisabledActionAndSeparator(fileMenu, "Scripts"); @@ -326,7 +326,7 @@ Menu::Menu() : shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true)); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false)); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false)); - + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); @@ -407,7 +407,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisableNackPackets, 0, false); addDisabledActionAndSeparator(developerMenu, "Testing"); - + QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools"); QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true); @@ -462,7 +462,7 @@ Menu::Menu() : false, appInstance->getAudio(), SLOT(toggleToneInjection())); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope, + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, appInstance->getAudio(), SLOT(toggleScope())); @@ -1775,4 +1775,3 @@ QString Menu::getSnapshotsLocation() const { } return _snapshotsLocation; } - diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index 666906681c..ef9db55620 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -23,7 +23,7 @@ XmppClient::XmppClient() : _xmppMUCManager() { AccountManager& accountManager = AccountManager::getInstance(); - connect(&accountManager, SIGNAL(accessTokenChanged()), this, SLOT(connectToServer())); + connect(&accountManager, SIGNAL(profileChanged()), this, SLOT(connectToServer())); connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer())); } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index b4aedbcb7c..918261a953 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -55,13 +55,13 @@ AccountManager::AccountManager() : { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); - + qRegisterMetaType("DataServerAccountInfo"); qRegisterMetaTypeStreamOperators("DataServerAccountInfo"); - + qRegisterMetaType("QNetworkAccessManager::Operation"); qRegisterMetaType("JSONCallbackParameters"); - + connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); } @@ -70,18 +70,18 @@ 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(); - + emit balanceChanged(0); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); - + 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()); @@ -93,7 +93,7 @@ void AccountManager::updateBalance() { JSONCallbackParameters callbackParameters; callbackParameters.jsonCallbackReceiver = &_accountInfo; callbackParameters.jsonCallbackMethod = "setBalanceFromJSON"; - + authenticatedRequest("/api/v1/wallets/mine", QNetworkAccessManager::GetOperation, callbackParameters); } } @@ -105,28 +105,33 @@ void AccountManager::accountInfoBalanceChanged(qint64 newBalance) { 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(); qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString()); - - emit accessTokenChanged(); + + // profile info isn't guaranteed to be saved too + if (_accountInfo.hasProfile()) { + emit profileChanged(); + } else { + requestProfile(); + } } } - + // tell listeners that the auth endpoint has changed emit authEndpointChanged(); } @@ -147,36 +152,36 @@ void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessMan 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; - + if (path.startsWith("/")) { requestURL.setPath(path); } else { 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); @@ -198,24 +203,24 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager:: 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 (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) { callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)), callbackParams.updateSlot.toStdString().c_str()); } } - + // if we ended up firing of a request, hook up to it now connect(networkReply, SIGNAL(finished()), SLOT(processReply())); } @@ -224,7 +229,7 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager:: void AccountManager::processReply() { QNetworkReply* requestReply = reinterpret_cast(sender()); - + if (requestReply->error() == QNetworkReply::NoError) { passSuccessToCallback(requestReply); } else { @@ -235,17 +240,17 @@ void AccountManager::processReply() { void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { 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."; @@ -256,13 +261,13 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { 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, requestReply->error()), Q_ARG(const QString&, requestReply->errorString())); - + // remove the related reply-callback group from the map _pendingCallbackMap.remove(requestReply); } else { @@ -274,12 +279,12 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { } 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; @@ -288,12 +293,12 @@ bool AccountManager::hasValidAccessToken() { 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; } @@ -304,36 +309,36 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas } QNetworkRequest request; - + QUrl grantURL = _authURL; grantURL.setPath("/oauth/token"); - + const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; - + QByteArray postData; postData.append("grant_type=password&"); postData.append("username=" + login + "&"); postData.append("password=" + QUrl::toPercentEncoding(password) + "&"); postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); - + 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))); + connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); + connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } -void AccountManager::requestFinished() { +void AccountManager::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 @@ -342,23 +347,21 @@ void AccountManager::requestFinished() { // 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); - + + _accountInfo = DataServerAccountInfo(); + _accountInfo.setAccessTokenFromJSON(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)); + + requestProfile(); } } else { // TODO: error handling @@ -367,7 +370,53 @@ void AccountManager::requestFinished() { } } -void AccountManager::requestError(QNetworkReply::NetworkError error) { +void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) { // TODO: error handling qDebug() << "AccountManager requestError - " << error; } + +void AccountManager::requestProfile() { + if (!_networkAccessManager) { + _networkAccessManager = new QNetworkAccessManager(this); + } + + QUrl profileURL = _authURL; + profileURL.setPath("/api/v1/users/profile"); + profileURL.setQuery("access_token=" + _accountInfo.getAccessToken().token); + + QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL)); + connect(profileReply, &QNetworkReply::finished, this, &AccountManager::requestProfileFinished); + connect(profileReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError))); +} + +void AccountManager::requestProfileFinished() { + QNetworkReply* profileReply = reinterpret_cast(sender()); + + QJsonDocument jsonResponse = QJsonDocument::fromJson(profileReply->readAll()); + const QJsonObject& rootObject = jsonResponse.object(); + + if (rootObject.contains("status") && rootObject["status"].toString() == "success") { + _accountInfo.setProfileInfoFromJSON(rootObject); + + emit profileChanged(); + + // the username has changed to whatever came back + emit usernameChanged(_accountInfo.getUsername()); + + // store the whole profile into the local settings + QUrl rootURL = profileReply->url(); + rootURL.setPath(""); + 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 profile"; + } +} + +void AccountManager::requestProfileError(QNetworkReply::NetworkError error) { + // TODO: error handling + qDebug() << "AccountManager requestProfileError - " << error; +} diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 628b084ea8..c18836ca54 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -23,9 +23,9 @@ class JSONCallbackParameters { public: JSONCallbackParameters(); - + bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; } - + QObject* jsonCallbackReceiver; QString jsonCallbackMethod; QObject* errorCallbackReceiver; @@ -38,30 +38,33 @@ class AccountManager : public QObject { Q_OBJECT public: static AccountManager& getInstance(); - + void authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), const QByteArray& dataByteArray = QByteArray(), QHttpMultiPart* dataMultiPart = NULL); - + const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); bool hasAuthEndpoint() { return !_authURL.isEmpty(); } - + bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } bool hasValidAccessToken(); Q_INVOKABLE bool checkAndSignalForAccessToken(); - + void requestAccessToken(const QString& login, const QString& password); - + void requestProfile(); + const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; } - + void destroy() { delete _networkAccessManager; } - + public slots: - void requestFinished(); - void requestError(QNetworkReply::NetworkError error); + void requestAccessTokenFinished(); + void requestProfileFinished(); + void requestAccessTokenError(QNetworkReply::NetworkError error); + void requestProfileError(QNetworkReply::NetworkError error); void logout(); void updateBalance(); void accountInfoBalanceChanged(qint64 newBalance); @@ -69,7 +72,7 @@ signals: void authRequired(); void authEndpointChanged(); void usernameChanged(const QString& username); - void accessTokenChanged(); + void profileChanged(); void loginComplete(const QUrl& authURL); void loginFailed(); void logoutComplete(); @@ -80,19 +83,19 @@ private: AccountManager(); AccountManager(AccountManager const& other); // not implemented void operator=(AccountManager const& other); // not implemented - + void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); - + Q_INVOKABLE void invokedRequest(const QString& path, QNetworkAccessManager::Operation operation, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart); - + QUrl _authURL; QNetworkAccessManager* _networkAccessManager; QMap _pendingCallbackMap; - + DataServerAccountInfo _accountInfo; }; diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 809f083e35..c60a17e0d8 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -21,20 +21,7 @@ DataServerAccountInfo::DataServerAccountInfo() : _balance(0), _hasBalance(false) { - -} -DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) : - _accessToken(jsonObject), - _username(), - _xmppPassword(), - _balance(0), - _hasBalance(false) -{ - QJsonObject userJSONObject = jsonObject["user"].toObject(); - setUsername(userJSONObject["username"].toString()); - setXMPPPassword(userJSONObject["xmpp_password"].toString()); - setDiscourseApiKey(userJSONObject["discourse_api_key"].toString()); } DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) { @@ -54,7 +41,7 @@ DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountI void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { using std::swap; - + swap(_accessToken, otherInfo._accessToken); swap(_username, otherInfo._username); swap(_xmppPassword, otherInfo._xmppPassword); @@ -63,10 +50,14 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_hasBalance, otherInfo._hasBalance); } +void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { + _accessToken = OAuthAccessToken(jsonObject); +} + void DataServerAccountInfo::setUsername(const QString& username) { if (_username != username) { _username = username; - + qDebug() << "Username changed to" << username; } } @@ -87,7 +78,7 @@ void DataServerAccountInfo::setBalance(qint64 balance) { if (!_hasBalance || _balance != balance) { _balance = balance; _hasBalance = true; - + emit balanceChanged(_balance); } } @@ -99,6 +90,17 @@ void DataServerAccountInfo::setBalanceFromJSON(const QJsonObject& jsonObject) { } } +bool DataServerAccountInfo::hasProfile() const { + return _username.length() > 0; +} + +void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject) { + QJsonObject user = jsonObject["data"].toObject()["user"].toObject(); + setUsername(user["username"].toString()); + setXMPPPassword(user["xmpp_password"].toString()); + setDiscourseApiKey(user["discourse_api_key"].toString()); +} + QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey; return out; diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index e0209326f9..5800942376 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -22,12 +22,12 @@ class DataServerAccountInfo : public QObject { Q_OBJECT public: DataServerAccountInfo(); - DataServerAccountInfo(const QJsonObject& jsonObject); DataServerAccountInfo(const DataServerAccountInfo& otherInfo); DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo); - + const OAuthAccessToken& getAccessToken() const { return _accessToken; } - + void setAccessTokenFromJSON(const QJsonObject& jsonObject); + const QString& getUsername() const { return _username; } void setUsername(const QString& username); @@ -36,20 +36,24 @@ public: const QString& getDiscourseApiKey() const { return _discourseApiKey; } void setDiscourseApiKey(const QString& discourseApiKey); - + qint64 getBalance() const { return _balance; } void setBalance(qint64 balance); bool hasBalance() const { return _hasBalance; } void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; } Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject); + bool hasProfile() const; + + void setProfileInfoFromJSON(const QJsonObject& jsonObject); + friend QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info); friend QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info); signals: qint64 balanceChanged(qint64 newBalance); private: void swap(DataServerAccountInfo& otherInfo); - + OAuthAccessToken _accessToken; QString _username; QString _xmppPassword;