diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 80af7900ac..fe2077f752 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1352,10 +1352,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); auto domainAccountManager = DependencyManager::get(); - connect(domainAccountManager.data(), &DomainAccountManager::authRequired, dialogsManager.data(), - &DialogsManager::showDomainLoginDialog); - connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, - &Application::updateWindowTitle); + connect(domainAccountManager.data(), &DomainAccountManager::authRequired, + dialogsManager.data(), &DialogsManager::showDomainLoginDialog); + connect(domainAccountManager.data(), &DomainAccountManager::authRequired, this, &Application::updateWindowTitle); + connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, &Application::updateWindowTitle); // ####### TODO: Connect any other signals from domainAccountManager. // use our MyAvatar position and quat for address manager path @@ -7086,8 +7086,9 @@ void Application::updateWindowTitle() const { auto domainAccountManager = DependencyManager::get(); auto isInErrorState = nodeList->getDomainHandler().isInErrorState(); bool isMetaverseLoggedIn = accountManager->isLoggedIn(); + bool hasDomainLogIn = domainAccountManager->hasLogIn(); bool isDomainLoggedIn = domainAccountManager->isLoggedIn(); - QString authedDomain = domainAccountManager->getAuthedDomain(); + QString authedDomainName = domainAccountManager->getAuthedDomainName(); QString buildVersion = " - Vircadia - " + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) @@ -7117,20 +7118,23 @@ void Application::updateWindowTitle() const { QString metaverseDetails; if (isMetaverseLoggedIn) { - metaverseDetails = "Metaverse: Logged in as " + metaverseUsername; + metaverseDetails = " (Metaverse: Logged in as " + metaverseUsername + ")"; } else { - metaverseDetails = "Metaverse: Not Logged In"; + metaverseDetails = " (Metaverse: Not Logged In)"; } QString domainDetails; - if (currentPlaceName == authedDomain && isDomainLoggedIn) { - domainDetails = "Domain: Logged in as " + domainUsername; + if (hasDomainLogIn) { + if (currentPlaceName == authedDomainName && isDomainLoggedIn) { + domainDetails = " (Domain: Logged in as " + domainUsername + ")"; + } else { + domainDetails = " (Domain: Not Logged In)"; + } } else { - domainDetails = "Domain: Not Logged In"; + domainDetails = ""; } - QString title = QString() + currentPlaceName + connectionStatus + " (" + metaverseDetails + ") (" + domainDetails + ")" - + buildVersion; + QString title = currentPlaceName + connectionStatus + metaverseDetails + domainDetails + buildVersion; #ifndef WIN32 // crashes with vs2013/win32 diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e639b0b23d..e460b4b56b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -43,6 +43,7 @@ #include "avatar/AvatarManager.h" #include "avatar/AvatarPackager.h" #include "AvatarBookmarks.h" +#include "DomainAccountManager.h" #include "MainWindow.h" #include "render/DrawStatus.h" #include "scripting/MenuScriptingInterface.h" @@ -73,6 +74,7 @@ const char* EXCLUSION_GROUP_KEY = "exclusionGroup"; Menu::Menu() { auto dialogsManager = DependencyManager::get(); auto accountManager = DependencyManager::get(); + auto domainAccountManager = DependencyManager::get(); // File/Application menu ---------------------------------- MenuWrapper* fileMenu = addMenu("File"); @@ -89,11 +91,15 @@ Menu::Menu() { } auto domainLogin = addActionToQMenuAndActionHash(fileMenu, "Domain: Log In"); + domainLogin->setVisible(false); connect(domainLogin, &QAction::triggered, [] { auto dialogsManager = DependencyManager::get(); dialogsManager->setDomainLoginState(); dialogsManager->showDomainLoginDialog(); }); + connect(domainAccountManager.data(), &DomainAccountManager::hasLogInChanged, [domainLogin](bool hasLogIn) { + domainLogin->setVisible(hasLogIn); + }); // File > Quit addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, qApp, SLOT(quit()), QAction::QuitRole); diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 928b581a5b..23c52143f6 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -29,50 +29,56 @@ const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; -// ####### TODO: Enable and use these? -// ####### TODO: Add storing domain URL and check against it when retrieving values? -// ####### TODO: Add storing _authURL and check against it when retrieving values? -/* -Setting::Handle domainAccessToken {"private/domainAccessToken", "" }; -Setting::Handle domainAccessRefreshToken {"private/domainAccessToken", "" }; -Setting::Handle domainAccessTokenExpiresIn {"private/domainAccessTokenExpiresIn", -1 }; -Setting::Handle domainAccessTokenType {"private/domainAccessTokenType", "" }; -*/ - -DomainAccountManager::DomainAccountManager() : - _authURL(), - _username(), - _access_token(), - _refresh_token(), - _domain_name() -{ +DomainAccountManager::DomainAccountManager() { connect(this, &DomainAccountManager::loginComplete, this, &DomainAccountManager::sendInterfaceAccessTokenToServer); } -void DomainAccountManager::setAuthURL(const QUrl& authURL) { - if (_authURL != authURL) { - _authURL = authURL; - - qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); - - _access_token = ""; - _refresh_token = ""; - - // ####### TODO: Restore and refresh OAuth2 tokens if have them for this domain. - - // ####### TODO: Handle "keep me logged in". +void DomainAccountManager::setDomainURL(const QUrl& domainURL) { + if (domainURL == _currentAuth.domainURL) { + return; } + + qCDebug(networking) << "DomainAccountManager domain URL has been changed to" << qPrintable(domainURL.toString()); + + // Restore OAuth2 authorization if have it for this domain. + if (_knownAuths.contains(domainURL)) { + _currentAuth = _knownAuths.value(domainURL); + } else { + _currentAuth = DomainAccountDetails(); + _currentAuth.domainURL = domainURL; + } + + emit hasLogInChanged(hasLogIn()); +} + +void DomainAccountManager::setAuthURL(const QUrl& authURL) { + if (authURL == _currentAuth.authURL) { + return; + } + + _currentAuth.authURL = authURL; + qCDebug(networking) << "DomainAccountManager URL for authenticated requests has been changed to" + << qPrintable(_currentAuth.authURL.toString()); + + _currentAuth.accessToken = ""; + _currentAuth.refreshToken = ""; + + emit hasLogInChanged(hasLogIn()); +} + +bool DomainAccountManager::hasLogIn() { + return !_currentAuth.authURL.isEmpty(); } bool DomainAccountManager::isLoggedIn() { - return !_authURL.isEmpty() && hasValidAccessToken(); + return !_currentAuth.authURL.isEmpty() && hasValidAccessToken(); } void DomainAccountManager::requestAccessToken(const QString& username, const QString& password) { - _username = username; - _access_token = ""; - _refresh_token = ""; + _currentAuth.username = username; + _currentAuth.accessToken = ""; + _currentAuth.refreshToken = ""; QNetworkRequest request; @@ -86,9 +92,9 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt formData.append("grant_type=password&"); formData.append("username=" + QUrl::toPercentEncoding(username) + "&"); formData.append("password=" + QUrl::toPercentEncoding(password) + "&"); - formData.append("client_id=" + _clientID); + formData.append("client_id=" + _currentAuth.clientID); - request.setUrl(_authURL); + request.setUrl(_currentAuth.authURL); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -111,10 +117,16 @@ void DomainAccountManager::requestAccessTokenFinished() { if (rootObject.contains("access_token")) { // Success. auto nodeList = DependencyManager::get(); - _domain_name = nodeList->getDomainHandler().getHostname(); + _currentAuth.authedDomainName = nodeList->getDomainHandler().getHostname(); QUrl rootURL = requestReply->url(); rootURL.setPath(""); setTokensFromJSON(rootObject, rootURL); + + // Remember domain login for the current Interface session. + _knownAuths.insert(_currentAuth.domainURL, _currentAuth); + + // ####### TODO: Handle "keep me logged in". + emit loginComplete(); } else { // Failure. @@ -137,22 +149,19 @@ void DomainAccountManager::sendInterfaceAccessTokenToServer() { bool DomainAccountManager::accessTokenIsExpired() { // ####### TODO: accessTokenIsExpired() return true; - /* - return domainAccessTokenExpiresIn.get() != -1 && domainAccessTokenExpiresIn.get() <= QDateTime::currentMSecsSinceEpoch(); - */ } bool DomainAccountManager::hasValidAccessToken() { // ###### TODO: wire this up to actually retrieve a token (based on session or storage) and confirm that it is in fact valid and relevant to the current domain. // QString currentDomainAccessToken = domainAccessToken.get(); - QString currentDomainAccessToken = _access_token; + QString currentDomainAccessToken = _currentAuth.accessToken; // if (currentDomainAccessToken.isEmpty() || accessTokenIsExpired()) { if (currentDomainAccessToken.isEmpty()) { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qCDebug(networking) << "An access token is required for requests to" - << qPrintable(_authURL.toString()); + << qPrintable(_currentAuth.authURL.toString()); } return false; @@ -168,17 +177,8 @@ bool DomainAccountManager::hasValidAccessToken() { } void DomainAccountManager::setTokensFromJSON(const QJsonObject& jsonObject, const QUrl& url) { - _access_token = jsonObject["access_token"].toString(); - _refresh_token = jsonObject["refresh_token"].toString(); - - // ####### TODO: Enable and use these? - // ####### TODO: Protect these per AccountManager? - // ######: TODO: clientID needed? - // qCDebug(networking) << "Storing a domain account with access-token for" << qPrintable(url.toString()); - // 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()); + _currentAuth.accessToken = jsonObject["access_token"].toString(); + _currentAuth.refreshToken = jsonObject["refresh_token"].toString(); } bool DomainAccountManager::checkAndSignalForAccessToken() { @@ -193,7 +193,7 @@ bool DomainAccountManager::checkAndSignalForAccessToken() { // Emit a signal so somebody can call back to us and request an access token given a user name and password. // Dialog can be hidden immediately after showing if we've just teleported to the domain, unless the signal is delayed. - auto domain = _authURL.host(); + auto domain = _currentAuth.authURL.host(); QTimer::singleShot(500, this, [this, domain] { emit this->authRequired(domain); }); diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 31226d6990..03c56d9215 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -18,49 +18,56 @@ #include +struct DomainAccountDetails { + QUrl domainURL; + QUrl authURL; + QString clientID; + QString username; + QString accessToken; + QString refreshToken; + QString authedDomainName; +}; + + class DomainAccountManager : public QObject, public Dependency { Q_OBJECT public: DomainAccountManager(); + void setDomainURL(const QUrl& domainURL); void setAuthURL(const QUrl& authURL); - void setClientID(const QString& clientID) { _clientID = clientID; } + void setClientID(const QString& clientID) { _currentAuth.clientID = clientID; } - QString getUsername() { return _username; } - QString getAccessToken() { return _access_token; } - QString getRefreshToken() { return _refresh_token; } - QString getAuthedDomain() { return _domain_name; } + const QString& getUsername() { return _currentAuth.username; } + const QString& getAccessToken() { return _currentAuth.accessToken; } + const QString& getRefreshToken() { return _currentAuth.refreshToken; } + const QString& getAuthedDomainName() { return _currentAuth.authedDomainName; } + bool hasLogIn(); bool isLoggedIn(); Q_INVOKABLE bool checkAndSignalForAccessToken(); public slots: void requestAccessToken(const QString& username, const QString& password); - void requestAccessTokenFinished(); signals: + void hasLogInChanged(bool hasLogIn); void authRequired(const QString& domain); void loginComplete(); void loginFailed(); void logoutComplete(); void newTokens(); -private slots: - private: bool hasValidAccessToken(); bool accessTokenIsExpired(); void setTokensFromJSON(const QJsonObject&, const QUrl& url); void sendInterfaceAccessTokenToServer(); - QUrl _authURL; - QString _clientID; - QString _username; // ####### TODO: Store elsewhere? - QString _access_token; // ####... "" - QString _refresh_token; // ####... "" - QString _domain_name; // ####... "" + DomainAccountDetails _currentAuth; + QHash _knownAuths; // }; #endif // hifi_DomainAccountManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index c1a24748f4..6a47d74864 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -241,6 +241,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { } } + DependencyManager::get()->setDomainURL(_domainURL); + emit domainURLChanged(_domainURL); if (_sockAddr.getPort() != domainPort) { @@ -527,6 +529,12 @@ bool DomainHandler::reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode } void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer message) { + + // Ignore any residual packets from previous domain. + if (!message->getSenderSockAddr().getAddress().isEqual(_sockAddr.getAddress())) { + return; + } + // we're hearing from this domain-server, don't need to refresh API info _apiRefreshTimer.stop(); @@ -584,18 +592,28 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointergenerateNewUserKeypair(); _connectionDenialsSinceKeypairRegen = 0; } + + // Server with domain login will prompt for domain login, not metaverse, so reset domain values if asked for metaverse. + auto domainAccountManager = DependencyManager::get(); + domainAccountManager->setAuthURL(QUrl()); + domainAccountManager->setClientID(QString()); + } else if (reasonSuggestsDomainLogin(reasonCode)) { qCWarning(networking) << "Make sure you are logged in to the domain."; - auto accountManager = DependencyManager::get(); + auto domainAccountManager = DependencyManager::get(); if (!extraInfo.isEmpty()) { auto extraInfoComponents = extraInfo.split("|"); - accountManager->setAuthURL(extraInfoComponents.value(0)); - accountManager->setClientID(extraInfoComponents.value(1)); + domainAccountManager->setAuthURL(extraInfoComponents.value(0)); + domainAccountManager->setClientID(extraInfoComponents.value(1)); + } else { + // Shouldn't occur, but just in case. + domainAccountManager->setAuthURL(QUrl()); + domainAccountManager->setClientID(QString()); } if (!_hasCheckedForDomainAccessToken) { - accountManager->checkAndSignalForAccessToken(); + domainAccountManager->checkAndSignalForAccessToken(); _hasCheckedForDomainAccessToken = true; } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index e02a8dd56e..64663def22 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -476,33 +476,28 @@ void NodeList::sendDomainServerCheckIn() { packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList(); packetStream << DependencyManager::get()->getPlaceName(); - // ####### TODO: Also send if need to send new domainLogin data? if (!domainIsConnected) { + + // Metaverse account. DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); packetStream << accountInfo.getUsername(); - // if this is a connect request, and we can present a username signature, send it along if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) { const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken); packetStream << usernameSignature; } else { - // ####### TODO: Only append if are going to send domain username? packetStream << QString(""); // Placeholder in case have domain username. } - } else { - // ####### TODO: Only append if are going to send domainUsername? - packetStream << QString("") << QString(""); // Placeholders in case have domain username. - } - // Send domain domain login data from Interface to domain server. - if (_hasDomainAccountManager) { - auto domainAccountManager = DependencyManager::get(); - if (!domainAccountManager->getUsername().isEmpty()) { - packetStream << domainAccountManager->getUsername(); - if (!domainAccountManager->getAccessToken().isEmpty()) { + // Domain account. + if (_hasDomainAccountManager) { + auto domainAccountManager = DependencyManager::get(); + if (!domainAccountManager->getUsername().isEmpty() && !domainAccountManager->getAccessToken().isEmpty()) { + packetStream << domainAccountManager->getUsername(); packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken()); } } + } flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn);