From e4c9657ab991d8289c694638bed40ad85d80da9d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 23 Jul 2020 16:08:09 +1200 Subject: [PATCH 01/60] Initial auxiliary groups code --- domain-server/src/DomainGatekeeper.cpp | 50 +++++++++++++++++++--- domain-server/src/DomainGatekeeper.h | 3 +- libraries/networking/src/NodePermissions.h | 7 +++ 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 09a04464685..59a17d3ba97 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -142,8 +142,11 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer_settingsManager.getAllKnownGroupNames().contains(group)) { + userPerms |= _server->_settingsManager.getPermissionsForGroup(group, QUuid()); +//#ifdef WANT_DEBUG + qDebug() << "| user-permissions: auxiliary user " << verifiedAuxliaryUsername << "is in group:" << group << "so:" << userPerms; +//#endif + + } + } + + userPerms.setVerifiedAuxiliaryUserName(verifiedAuxliaryUsername); + userPerms.setVerifiedAuxiliaryUserGroups(verifiedAuxiliaryUserGroups); + } + if (verifiedUsername.isEmpty()) { userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); #ifdef WANT_DEBUG @@ -275,6 +295,8 @@ void DomainGatekeeper::updateNodePermissions() { // the id and the username in NodePermissions will often be the same, but id is set before // authentication and verifiedUsername is only set once they user's key has been confirmed. QString verifiedUsername = node->getPermissions().getVerifiedUserName(); + QString verifiedAuxiliaryUsername = node->getPermissions().getVerifiedAuxiliaryUserName(); + QStringList verifiedAuxiliaryUserGroups = node->getPermissions().getVerifiedAuxiliaryUserGroups(); NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0)); if (node->getPermissions().isAssignment) { @@ -309,7 +331,9 @@ void DomainGatekeeper::updateNodePermissions() { sendingAddress == QHostAddress::LocalHost); } - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint); + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedAuxiliaryUsername, + verifiedAuxiliaryUserGroups, connectingAddr.getAddress(), + hardwareAddress, machineFingerprint); } node->setPermissions(userPerms); @@ -434,8 +458,22 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect } } - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress(), - nodeConnection.hardwareAddress, nodeConnection.machineFingerprint); + // Auxiliary user name and groups may be provided by an external authentication service. + // This is enabled in the server settings by ... #######: TODO: What server name or tag to set in the server's settings? + QString verifiedAuxiliaryUsername; + QStringList verifiedAuxiliaryUserGroups; + + // #######: TODO: Obtain auxiliary login's user name and auxiliary groups if server tags indicate that this is required. + // May already have auxiliary login's user name, in which case groups should probably be re-obtained to + // ensure that they're up to date. + + // #######: TODO: Delete this development code. + verifiedAuxiliaryUsername = "a@b.c"; + verifiedAuxiliaryUserGroups = QString("test-group").toLower().split(" "); + + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedAuxiliaryUsername, verifiedAuxiliaryUserGroups, + nodeConnection.senderSockAddr.getAddress(), nodeConnection.hardwareAddress, + nodeConnection.machineFingerprint); if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", @@ -1029,7 +1067,7 @@ void DomainGatekeeper::refreshGroupsCache() { updateNodePermissions(); -#if WANT_DEBUG +#ifdef WANT_DEBUG _server->_settingsManager.debugDumpGroupsState(); #endif } diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 92b400882ea..0fb9a8e36ac 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -120,7 +120,8 @@ private slots: QSet _domainOwnerFriends; // keep track of friends of the domain owner QSet _inFlightGroupMembershipsRequests; // keep track of which we've already asked for - NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress, + NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, QString verifiedAuxliaryUsername, + QStringList verifiedAuxiliaryUserGroups, const QHostAddress& senderAddress, const QString& hardwareAddress, const QUuid& machineFingerprint); void getGroupMemberships(const QString& username); diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 583c1b29ac8..ebbe2104c70 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -51,6 +51,11 @@ class NodePermissions { void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); } const QString& getVerifiedUserName() const { return _verifiedUserName; } + void setVerifiedAuxiliaryUserName(QString userName) { _verifiedAuxiliaryUserName = userName.toLower(); } + const QString& getVerifiedAuxiliaryUserName() const { return _verifiedAuxiliaryUserName; } + void setVerifiedAuxiliaryUserGroups(QStringList userGroups) { _verifiedAuxiliaryUserGroups = userGroups; } + const QStringList& getVerifiedAuxiliaryUserGroups() const { return _verifiedAuxiliaryUserGroups; } + void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }} QUuid getGroupID() const { return _groupID; } bool isGroup() const { return _groupIDSet; } @@ -99,6 +104,8 @@ class NodePermissions { QString _id; QUuid _rankID { QUuid() }; // 0 unless this is for a group QString _verifiedUserName; + QString _verifiedAuxiliaryUserName; + QStringList _verifiedAuxiliaryUserGroups; bool _groupIDSet { false }; QUuid _groupID; From d3320ab17b8fbbac8f3cd405ef02513bc607e014 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Thu, 23 Jul 2020 00:18:04 -0400 Subject: [PATCH 02/60] Separate domain vs metaverse login denials for Interface. --- domain-server/src/DomainGatekeeper.cpp | 2 +- libraries/networking/src/DomainHandler.cpp | 32 ++++++++++++++++++---- libraries/networking/src/DomainHandler.h | 6 ++-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 09a04464685..0ec39c10faf 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -439,7 +439,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", - nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorized); + nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedMetaverse); #ifdef WANT_DEBUG qDebug() << "stalling login due to permissions:" << username; #endif diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index d34b5cf090f..cd6af03e476 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -144,7 +144,8 @@ void DomainHandler::hardReset(QString reason) { bool DomainHandler::isHardRefusal(int reasonCode) { return (reasonCode == (int)ConnectionRefusedReason::ProtocolMismatch || reasonCode == (int)ConnectionRefusedReason::TooManyUsers || - reasonCode == (int)ConnectionRefusedReason::NotAuthorized || + reasonCode == (int)ConnectionRefusedReason::NotAuthorizedMetaverse || + reasonCode == (int)ConnectionRefusedReason::NotAuthorizedDomain || reasonCode == (int)ConnectionRefusedReason::TimedOut); } @@ -489,10 +490,25 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes } } -bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) { +bool DomainHandler::reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonCode) { switch (reasonCode) { case ConnectionRefusedReason::LoginError: - case ConnectionRefusedReason::NotAuthorized: + case ConnectionRefusedReason::NotAuthorizedMetaverse: + return true; + + default: + case ConnectionRefusedReason::Unknown: + case ConnectionRefusedReason::ProtocolMismatch: + case ConnectionRefusedReason::TooManyUsers: + return false; + } + return false; +} + +bool DomainHandler::reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode) { + switch (reasonCode) { + case ConnectionRefusedReason::LoginError: + case ConnectionRefusedReason::NotAuthorizedDomain: return true; default: @@ -543,9 +559,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); - // Some connection refusal reasons imply that a login is required. If so, suggest a new login - if (reasonSuggestsLogin(reasonCode)) { - qCWarning(networking) << "Make sure you are logged in."; + // Some connection refusal reasons imply that a login is required. If so, suggest a new login. + if (reasonSuggestsMetaverseLogin(reasonCode)) { + qCWarning(networking) << "Make sure you are logged in to the metaverse."; if (!_hasCheckedForAccessToken) { accountManager->checkAndSignalForAccessToken(); @@ -559,6 +575,10 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointergenerateNewUserKeypair(); _connectionDenialsSinceKeypairRegen = 0; } + } else if (reasonSuggestsDomainLogin(reasonCode)) { + qCWarning(networking) << "Make sure you are logged in to the domain."; + + } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 178c56c34a2..1f97327ab07 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -199,7 +199,8 @@ class DomainHandler : public QObject { Unknown, ProtocolMismatch, LoginError, - NotAuthorized, + NotAuthorizedMetaverse, + NotAuthorizedDomain, TooManyUsers, TimedOut }; @@ -247,7 +248,8 @@ private slots: void limitOfSilentDomainCheckInsReached(); private: - bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode); + bool reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonCode); + bool reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode); void sendDisconnectPacket(); void hardReset(QString reason); From 9fd3f764ece3c62af369842c62094b1a3514e000 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Thu, 23 Jul 2020 00:18:11 -0400 Subject: [PATCH 03/60] Remove supposedly unused logic? --- libraries/networking/src/DomainHandler.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index cd6af03e476..c708d901a36 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -495,12 +495,6 @@ bool DomainHandler::reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonC case ConnectionRefusedReason::LoginError: case ConnectionRefusedReason::NotAuthorizedMetaverse: return true; - - default: - case ConnectionRefusedReason::Unknown: - case ConnectionRefusedReason::ProtocolMismatch: - case ConnectionRefusedReason::TooManyUsers: - return false; } return false; } @@ -510,12 +504,6 @@ bool DomainHandler::reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode case ConnectionRefusedReason::LoginError: case ConnectionRefusedReason::NotAuthorizedDomain: return true; - - default: - case ConnectionRefusedReason::Unknown: - case ConnectionRefusedReason::ProtocolMismatch: - case ConnectionRefusedReason::TooManyUsers: - return false; } return false; } From a5d0c80c87b0cbe294c96648e7904c29f62e6bc1 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Thu, 23 Jul 2020 00:37:50 -0400 Subject: [PATCH 04/60] Revert "Remove supposedly unused logic?" This reverts commit 9fd3f764ece3c62af369842c62094b1a3514e000. --- libraries/networking/src/DomainHandler.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index c708d901a36..cd6af03e476 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -495,6 +495,12 @@ bool DomainHandler::reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonC case ConnectionRefusedReason::LoginError: case ConnectionRefusedReason::NotAuthorizedMetaverse: return true; + + default: + case ConnectionRefusedReason::Unknown: + case ConnectionRefusedReason::ProtocolMismatch: + case ConnectionRefusedReason::TooManyUsers: + return false; } return false; } @@ -504,6 +510,12 @@ bool DomainHandler::reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode case ConnectionRefusedReason::LoginError: case ConnectionRefusedReason::NotAuthorizedDomain: return true; + + default: + case ConnectionRefusedReason::Unknown: + case ConnectionRefusedReason::ProtocolMismatch: + case ConnectionRefusedReason::TooManyUsers: + return false; } return false; } From 07504232a9cb3ec29905f579b9dcdc7637c06bd5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 24 Jul 2020 08:43:40 +1200 Subject: [PATCH 05/60] Move new connection refused reason to end to maintain compatability --- libraries/networking/src/DomainHandler.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 1f97327ab07..3bfd742d3aa 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -177,9 +177,9 @@ class DomainHandler : public QObject { * You could not be logged into the domain. * * - * NotAuthorized + * NotAuthorizedMetaverse * 3 - * You are not authorized to connect to the domain. + * You are not authorized to connect to the domain per your metaverse login. * * * TooManyUsers @@ -191,6 +191,11 @@ class DomainHandler : public QObject { * 5 * Connecting to the domain timed out. * + * + * NotAuthorizedDomain + * 6 + * You are not authorized to connect to the domain per your domain login. + * * * * @typedef {number} Window.ConnectionRefusedReason @@ -200,9 +205,9 @@ class DomainHandler : public QObject { ProtocolMismatch, LoginError, NotAuthorizedMetaverse, - NotAuthorizedDomain, TooManyUsers, - TimedOut + TimedOut, + NotAuthorizedDomain }; public slots: From 2e9355da587ea1baa44c1e6f3ce9b7dcd4ffaf08 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 24 Jul 2020 11:13:41 +1200 Subject: [PATCH 06/60] Distinguish not logged into metaverse vs domain --- domain-server/src/DomainGatekeeper.cpp | 10 +++++----- libraries/networking/src/DomainHandler.cpp | 4 ++-- libraries/networking/src/DomainHandler.h | 12 +++++++++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 0ec39c10faf..21a308c793c 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -600,15 +600,15 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, return true; } else { - // we only send back a LoginError if this wasn't an "optimistic" key + // we only send back a LoginErrorMetaverse if this wasn't an "optimistic" key // (a key that we hoped would work but is probably stale) if (!senderSockAddr.isNull() && !isOptimisticKey) { - qDebug() << "Error decrypting username signature for" << username << "- denying connection."; + qDebug() << "Error decrypting metaverse username signature for" << username << "- denying connection."; sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr, - DomainHandler::ConnectionRefusedReason::LoginError); + DomainHandler::ConnectionRefusedReason::LoginErrorMetaverse); } else if (!senderSockAddr.isNull()) { - qDebug() << "Error decrypting username signature for" << username << "with optimisitic key -" + qDebug() << "Error decrypting metaverse username signature for" << username << "with optimistic key -" << "re-requesting public key and delaying connection"; } @@ -622,7 +622,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, if (!senderSockAddr.isNull()) { qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr, - DomainHandler::ConnectionRefusedReason::LoginError); + DomainHandler::ConnectionRefusedReason::LoginErrorMetaverse); } } } else { diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index cd6af03e476..1ad371721f4 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -492,7 +492,7 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes bool DomainHandler::reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonCode) { switch (reasonCode) { - case ConnectionRefusedReason::LoginError: + case ConnectionRefusedReason::LoginErrorMetaverse: case ConnectionRefusedReason::NotAuthorizedMetaverse: return true; @@ -507,7 +507,7 @@ bool DomainHandler::reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonC bool DomainHandler::reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode) { switch (reasonCode) { - case ConnectionRefusedReason::LoginError: + case ConnectionRefusedReason::LoginErrorDomain: case ConnectionRefusedReason::NotAuthorizedDomain: return true; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 3bfd742d3aa..5bbaac18c52 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -172,9 +172,9 @@ class DomainHandler : public QObject { * The communications protocols of the domain and your Interface are not the same. * * - * LoginError + * LoginErrorMetaverse * 2 - * You could not be logged into the domain. + * You could not be logged into the domain per your metaverse login. * * * NotAuthorizedMetaverse @@ -192,6 +192,11 @@ class DomainHandler : public QObject { * Connecting to the domain timed out. * * + * LoginErrorDomain + * 2 + * You could not be logged into the domain per your domain login. + * + * * NotAuthorizedDomain * 6 * You are not authorized to connect to the domain per your domain login. @@ -203,10 +208,11 @@ class DomainHandler : public QObject { enum class ConnectionRefusedReason : uint8_t { Unknown, ProtocolMismatch, - LoginError, + LoginErrorMetaverse, NotAuthorizedMetaverse, TooManyUsers, TimedOut, + LoginErrorDomain, NotAuthorizedDomain }; From d7c0493b7a4380882e5b74a2a132ceef930ba14f Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Thu, 23 Jul 2020 22:14:21 -0400 Subject: [PATCH 07/60] Updated login QML to handle domain logins. --- .../qml/LoginDialog/LinkAccountBody.qml | 19 +++++++++++++++---- .../qml/LoginDialog/LoggingInBody.qml | 4 ++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 571b7e074c2..cd196629314 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -45,6 +45,11 @@ Item { property bool lostFocus: false readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + // TODO: + // readonly property bool isLoggingInToDomain: loginDialog.getDomainLoginRequested() + // readonly property bool domainAuthProvider: loginDialog.getDomainLoginAuthProvider() + readonly property bool isLoggingInToDomain: true + readonly property string domainAuthProvider: "https://example.com/oauth2" QtObject { id: d @@ -71,7 +76,12 @@ Item { } function login() { - loginDialog.login(emailField.text, passwordField.text); + if (!isLoggingInToDomain) { + loginDialog.login(emailField.text, passwordField.text); + } else { + loginDialog.loginDomain(emailField.text, passwordField.text, domainAuthProvider); + } + if (linkAccountBody.loginDialogPoppedUp) { var data; if (linkAccountBody.linkSteam) { @@ -87,7 +97,7 @@ Item { } bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus, - "displayName":displayNameField.text }); + "displayName":displayNameField.text, "isLoggingInToDomain": linkAccountBody.isLoggingInToDomain }); } function init() { @@ -99,6 +109,7 @@ Item { errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height; } loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; + loginButton.text = (!isLoggingInToDomain) ? "Log In" : "Log In to Domain"; loginButton.color = hifi.buttons.blue; displayNameField.placeholderText = "Display Name (optional)"; var savedDisplayName = Settings.getValue("Avatar/displayName", ""); @@ -393,7 +404,7 @@ Item { HifiStylesUit.ShortcutText { id: cantAccessText z: 10 - visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus + visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus && !linkAccountBody.isLoggingInToDomain anchors { top: loginButton.bottom topMargin: hifi.dimensions.contentSpacing.y @@ -492,7 +503,7 @@ Item { id: signUpContainer width: loginContainer.width height: signUpTextMetrics.height - visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus + visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus && !linkAccountBody.isLoggingInToDomain anchors { left: loginContainer.left top: loginContainer.bottom diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index a0029dc40b5..8824be8625d 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -31,6 +31,7 @@ Item { property bool linkSteam: linkSteam property bool linkOculus: linkOculus property bool createOculus: createOculus + property bool isLoggingInToDomain: isLoggingInToDomain property string displayName: "" readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -106,6 +107,9 @@ Item { loggingInGlyph.visible = true; loggingInText.text = "Logging in to Oculus"; loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; + } else if (loggingInBody.isLoggingInToDomain) { + loggingInText.text = "Logging in to Domain"; + loggingInText.anchors.centerIn = loggingInHeader; } else { loggingInText.text = "Logging in"; loggingInText.anchors.centerIn = loggingInHeader; From cd9f47004be388a3b7c2c68426a65879b23074c5 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Thu, 23 Jul 2020 22:14:35 -0400 Subject: [PATCH 08/60] Added loginDomain stub. --- interface/src/ui/LoginDialog.cpp | 6 ++++++ interface/src/ui/LoginDialog.h | 1 + 2 files changed, 7 insertions(+) diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index c0e96fe8bbf..ca9ae4282de 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -135,6 +135,12 @@ void LoginDialog::login(const QString& username, const QString& password) const DependencyManager::get()->requestAccessToken(username, password); } +void LoginDialog::loginDomain(const QString& username, const QString& password, const QUrl domainAuthProvider) const { + qDebug() << "Attempting to login " << username << "into a domain through" << domainAuthProvider; + // TODO: + // DependencyManager::get()->requestAccessToken(username, password, domainAuthProvider); +} + void LoginDialog::loginThroughOculus() { qDebug() << "Attempting to login through Oculus"; if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 7c659a93204..6ccef3fa393 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -71,6 +71,7 @@ protected slots: Q_INVOKABLE QString oculusUserID() const; Q_INVOKABLE void login(const QString& username, const QString& password) const; + Q_INVOKABLE void loginDomain(const QString& username, const QString& password, const QUrl domainAuthProvider) const; Q_INVOKABLE void loginThroughSteam(); Q_INVOKABLE void linkSteam(); Q_INVOKABLE void createAccountFromSteam(QString username = QString()); From 6d310eb999ed32b6c853f41545c8d8771bbe2199 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 24 Jul 2020 21:22:00 +1200 Subject: [PATCH 09/60] Generate "NotAuthorizedDomain" condition --- domain-server/src/DomainGatekeeper.cpp | 101 +++++++++++++++++++++---- domain-server/src/DomainGatekeeper.h | 10 ++- libraries/networking/src/NodeList.cpp | 16 ++++ 3 files changed, 110 insertions(+), 17 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 93e5ece0f6b..9766307f5ef 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -89,10 +89,12 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointersecond); } else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) { QByteArray usernameSignature; + QByteArray domainUsernameSignature; if (message->getBytesLeftToRead() > 0) { // read username from packet @@ -101,10 +103,20 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetBytesLeftToRead() > 0) { // read user signature from packet packetStream >> usernameSignature; + + if (message->getBytesLeftToRead() > 0) { + // Read domain username from packet. + packetStream >> domainUsername; + + if (message->getBytesLeftToRead() > 0) { + // Read domain signature from packet. + packetStream >> domainUsernameSignature; + } + } } } - node = processAgentConnectRequest(nodeConnection, username, usernameSignature); + node = processAgentConnectRequest(nodeConnection, username, usernameSignature, domainUsername, domainUsernameSignature); } if (node) { @@ -416,7 +428,9 @@ const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_c SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection, const QString& username, - const QByteArray& usernameSignature) { + const QByteArray& usernameSignature, + const QString& domainUsername, + const QByteArray& domainUsernameSignature) { auto limitedNodeList = DependencyManager::get(); @@ -443,7 +457,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect #ifdef WANT_DEBUG qDebug() << "stalling login because we have no username-signature:" << username; #endif - return SharedNodePointer(); + if (!domainHasLogin() || domainUsername.isEmpty()) { + return SharedNodePointer(); + } } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { // they sent us a username and the signature verifies it getGroupMemberships(username); @@ -454,30 +470,57 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect #ifdef WANT_DEBUG qDebug() << "stalling login because signature verification failed:" << username; #endif - return SharedNodePointer(); + if (!domainHasLogin() || domainUsername.isEmpty()) { + return SharedNodePointer(); + } } } - // Auxiliary user name and groups may be provided by an external authentication service. - // This is enabled in the server settings by ... #######: TODO: What server name or tag to set in the server's settings? - QString verifiedAuxiliaryUsername; - QStringList verifiedAuxiliaryUserGroups; + // The domain may have its own users and groups. + QString verifiedDomainUsername; + QStringList verifiedDomainUserGroups; + if (domainHasLogin() && !domainUsername.isEmpty()) { + if (domainUsernameSignature.isEmpty()) { + // User is attempting to prove their domain identity. + + // ####### TODO: OAuth2 corollary of metaverse code, above. - // #######: TODO: Obtain auxiliary login's user name and auxiliary groups if server tags indicate that this is required. - // May already have auxiliary login's user name, in which case groups should probably be re-obtained to - // ensure that they're up to date. + return SharedNodePointer(); + } else if (verifyDomainUserSignature(domainUsername, domainUsernameSignature, nodeConnection.senderSockAddr)) { + // User's domain identity is confirmed. + + // ####### TODO: Get user's domain group memberships (WordPress roles) from domain. + // This may already be provided at the same time as the "verify" call to the domain API. + // If it isn't, need to initiate getting them then handle their receipt along the lines of the + // metaverse code, above. + verifiedDomainUserGroups = QString("test-group").toLower().split(" "); + + verifiedDomainUsername = domainUsername.toLower(); + + } else { + // User's identity didn't check out. - // #######: TODO: Delete this development code. - verifiedAuxiliaryUsername = "a@b.c"; - verifiedAuxiliaryUserGroups = QString("test-group").toLower().split(" "); + // ####### TODO: OAuth2 corollary of metaverse code, above. - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedAuxiliaryUsername, verifiedAuxiliaryUserGroups, +#ifdef WANT_DEBUG + qDebug() << "stalling login because signature verification failed:" << username; +#endif + return SharedNodePointer(); + } + } + + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUsername, verifiedDomainUserGroups, nodeConnection.senderSockAddr.getAddress(), nodeConnection.hardwareAddress, nodeConnection.machineFingerprint); if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { - sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", + if (domainHasLogin() && !domainUsername.isEmpty()) { + sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", + nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain); + } else { + sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedMetaverse); + } #ifdef WANT_DEBUG qDebug() << "stalling login due to permissions:" << username; #endif @@ -673,6 +716,21 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, return false; } +bool DomainGatekeeper::verifyDomainUserSignature(const QString& domainUsername, + const QByteArray& domainUsernameSignature, + const HifiSockAddr& senderSockAddr) { + + // ####### TODO: Verify via domain OAuth2. + bool success = true; + if (success) { + return true; + } + + sendConnectionDeniedPacket("Error decrypting domain username signature.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginErrorDomain); + return false; +} + bool DomainGatekeeper::isWithinMaxCapacity() { // find out what our maximum capacity is QVariant maximumUserCapacityVariant = @@ -1072,6 +1130,17 @@ void DomainGatekeeper::refreshGroupsCache() { #endif } +bool DomainGatekeeper::domainHasLogin() { + // The domain may have its own users and groups. This is enabled in the server settings by ... + // ####### TODO: Use a particular string in the server name or set a particular tag in the server's settings? + // Or add a new server setting? + + // ####### TODO: Also configure URL for getting user's group memberships, in the server's settings? + + // ####### TODO + return true; +} + void DomainGatekeeper::initLocalIDManagement() { std::uniform_int_distribution sixteenBitRand; std::random_device randomDevice; diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 0fb9a8e36ac..ac80a103016 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -76,11 +76,17 @@ private slots: const PendingAssignedNodeData& pendingAssignment); SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection, const QString& username, - const QByteArray& usernameSignature); + const QByteArray& usernameSignature, + const QString& domainUsername, + const QByteArray& domainUsernameSignature); SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection); bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); + + bool verifyDomainUserSignature(const QString& domainUsername, const QByteArray& domainUsernameSignature, + const HifiSockAddr& senderSockAddr); + bool isWithinMaxCapacity(); bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, @@ -128,6 +134,8 @@ private slots: // void getIsGroupMember(const QString& username, const QUuid groupID); void getDomainOwnerFriendsList(); + bool domainHasLogin(); + // Local ID management. void initLocalIDManagement(); using UUIDToLocalID = std::unordered_map ; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 2c584b1c481..977d2e2dfe0 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -474,6 +474,22 @@ void NodeList::sendDomainServerCheckIn() { if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) { const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken); packetStream << usernameSignature; + } else { + packetStream << QString(""); // Placeholder in case have domainUsername. + } + } else { + packetStream << QString(""); // Placeholder in case have domainUsername. + } + + // ####### TODO: Send domain username and signature if domain has these and aren't logged in. + // ####### If get into difficulties, could perhaps send domain's username and signature instead of metaverse. + bool domainLoginIsConnected = false; + if (!domainLoginIsConnected) { + if (true) { + packetStream << QString("a@b.c"); + if (true) { + packetStream << QString("signature"); + } } } From 0b667e34a2f30f12646f209107f30062022a4e88 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 24 Jul 2020 22:02:23 +1200 Subject: [PATCH 10/60] Regularize naming --- domain-server/src/DomainGatekeeper.cpp | 22 +++++++++++----------- domain-server/src/DomainGatekeeper.h | 4 ++-- libraries/networking/src/NodeList.cpp | 1 + libraries/networking/src/NodePermissions.h | 12 ++++++------ 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 9766307f5ef..ea7f0d2d91d 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -155,8 +155,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer_settingsManager.getAllKnownGroupNames().contains(group)) { userPerms |= _server->_settingsManager.getPermissionsForGroup(group, QUuid()); //#ifdef WANT_DEBUG - qDebug() << "| user-permissions: auxiliary user " << verifiedAuxliaryUsername << "is in group:" << group << "so:" << userPerms; + qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << group << "so:" << userPerms; //#endif } } - userPerms.setVerifiedAuxiliaryUserName(verifiedAuxliaryUsername); - userPerms.setVerifiedAuxiliaryUserGroups(verifiedAuxiliaryUserGroups); + userPerms.setVerifiedDomainUserName(verifiedDomainUserName); + userPerms.setVerifiedDomainUserGroups(verifiedDomainUserGroups); } if (verifiedUsername.isEmpty()) { @@ -307,8 +307,8 @@ void DomainGatekeeper::updateNodePermissions() { // the id and the username in NodePermissions will often be the same, but id is set before // authentication and verifiedUsername is only set once they user's key has been confirmed. QString verifiedUsername = node->getPermissions().getVerifiedUserName(); - QString verifiedAuxiliaryUsername = node->getPermissions().getVerifiedAuxiliaryUserName(); - QStringList verifiedAuxiliaryUserGroups = node->getPermissions().getVerifiedAuxiliaryUserGroups(); + QString verifiedDomainUserName = node->getPermissions().getVerifiedDomainUserName(); + QStringList verifiedDomainUserGroups = node->getPermissions().getVerifiedDomainUserGroups(); NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0)); if (node->getPermissions().isAssignment) { @@ -343,8 +343,8 @@ void DomainGatekeeper::updateNodePermissions() { sendingAddress == QHostAddress::LocalHost); } - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedAuxiliaryUsername, - verifiedAuxiliaryUserGroups, connectingAddr.getAddress(), + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUserName, + verifiedDomainUserGroups, connectingAddr.getAddress(), hardwareAddress, machineFingerprint); } diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index ac80a103016..fcf8e5aede8 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -126,8 +126,8 @@ private slots: QSet _domainOwnerFriends; // keep track of friends of the domain owner QSet _inFlightGroupMembershipsRequests; // keep track of which we've already asked for - NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, QString verifiedAuxliaryUsername, - QStringList verifiedAuxiliaryUserGroups, const QHostAddress& senderAddress, + NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, QString verifiedDomainUsername, + QStringList verifiedDomainUserGroups, const QHostAddress& senderAddress, const QString& hardwareAddress, const QUuid& machineFingerprint); void getGroupMemberships(const QString& username); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 977d2e2dfe0..63c2e9c902d 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -379,6 +379,7 @@ void NodeList::sendDomainServerCheckIn() { if (domainPacketType == PacketType::DomainConnectRequest) { #if (PR_BUILD || DEV_BUILD) + // ####### if (_shouldSendNewerVersion) { domainPacket->setVersion(versionForPacketType(domainPacketType) + 1); } diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index ebbe2104c70..2b681eb7ee9 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -51,10 +51,10 @@ class NodePermissions { void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); } const QString& getVerifiedUserName() const { return _verifiedUserName; } - void setVerifiedAuxiliaryUserName(QString userName) { _verifiedAuxiliaryUserName = userName.toLower(); } - const QString& getVerifiedAuxiliaryUserName() const { return _verifiedAuxiliaryUserName; } - void setVerifiedAuxiliaryUserGroups(QStringList userGroups) { _verifiedAuxiliaryUserGroups = userGroups; } - const QStringList& getVerifiedAuxiliaryUserGroups() const { return _verifiedAuxiliaryUserGroups; } + void setVerifiedDomainUserName(QString userName) { _verifiedDomainUserName = userName.toLower(); } + const QString& getVerifiedDomainUserName() const { return _verifiedDomainUserName; } + void setVerifiedDomainUserGroups(QStringList userGroups) { _verifiedDomainUserGroups = userGroups; } + const QStringList& getVerifiedDomainUserGroups() const { return _verifiedDomainUserGroups; } void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }} QUuid getGroupID() const { return _groupID; } @@ -104,8 +104,8 @@ class NodePermissions { QString _id; QUuid _rankID { QUuid() }; // 0 unless this is for a group QString _verifiedUserName; - QString _verifiedAuxiliaryUserName; - QStringList _verifiedAuxiliaryUserGroups; + QString _verifiedDomainUserName; + QStringList _verifiedDomainUserGroups; bool _groupIDSet { false }; QUuid _groupID; From 9bb913983d935a249625c6029667e274ab85aaf4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jul 2020 15:31:17 +1200 Subject: [PATCH 11/60] Stub out basics for displaying the domain login dialog --- interface/src/Application.cpp | 14 +++++++ interface/src/ui/DialogsManager.cpp | 13 +++++++ interface/src/ui/DialogsManager.h | 1 + .../networking/src/DomainAccountManager.cpp | 35 +++++++++++++++++ .../networking/src/DomainAccountManager.h | 39 +++++++++++++++++++ libraries/networking/src/DomainHandler.cpp | 16 +++++++- libraries/networking/src/DomainHandler.h | 1 + 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 libraries/networking/src/DomainAccountManager.cpp create mode 100644 libraries/networking/src/DomainAccountManager.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b7b2fea67db..df72f0cc4ed 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -65,6 +65,7 @@ #include #include #include +#include #include #include #include @@ -852,6 +853,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { #else DependencyManager::set(true, std::bind(&Application::getUserAgent, qApp)); #endif + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(ScriptEngine::CLIENT_SCRIPT, defaultScriptsOverrideOption); DependencyManager::set(); @@ -1348,6 +1350,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo #endif connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); + + auto domainAccountManager = DependencyManager::get(); + connect(domainAccountManager.data(), &DomainAccountManager::authRequired, dialogsManager.data(), + &DialogsManager::showDomainLoginDialog); + + // ####### TODO + + // use our MyAvatar position and quat for address manager path addressManager->setPositionGetter([] { auto avatarManager = DependencyManager::get(); @@ -2801,6 +2811,7 @@ void Application::cleanupBeforeQuit() { if (!keepMeLoggedIn) { DependencyManager::get()->removeAccountFromFile(); } + // ####### TODO _displayPlugin.reset(); PluginManager::getInstance()->shutdown(); @@ -3150,6 +3161,7 @@ extern void setupPreferences(); static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active = false); #endif +// ####### TODO void Application::showLoginScreen() { #if !defined(DISABLE_QML) auto accountManager = DependencyManager::get(); @@ -7072,6 +7084,7 @@ void Application::updateWindowTitle() const { + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) + " " + applicationVersion(); + // ####### TODO QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)"; QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" : @@ -9430,6 +9443,7 @@ void Application::forceDisplayName(const QString& displayName) { getMyAvatar()->setDisplayName(displayName); } void Application::forceLoginWithTokens(const QString& tokens) { + // ####### TODO DependencyManager::get()->setAccessTokens(tokens); Setting::Handle(KEEP_ME_LOGGED_IN_SETTING_NAME, true).set(true); } diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 0a655de5e53..7cba608feb3 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -121,10 +121,23 @@ void DialogsManager::hideLoginDialog() { LoginDialog::hide(); } + +void DialogsManager::showDomainLoginDialog() { + + // #######: TODO + + qDebug() << "#######: showDomainLoginDialog()"; + +} + +// #######: TODO + + void DialogsManager::showUpdateDialog() { UpdateDialog::show(); } + void DialogsManager::octreeStatsDetails() { if (!_octreeStatsDialog) { _octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), qApp->getOcteeSceneStats()); diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 949c86c2409..10bdb4bb206 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -49,6 +49,7 @@ public slots: void toggleLoginDialog(); void showLoginDialog(); void hideLoginDialog(); + void showDomainLoginDialog(); void octreeStatsDetails(); void lodTools(); void hmdTools(bool showTools); diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp new file mode 100644 index 00000000000..0d7f38d3f6e --- /dev/null +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -0,0 +1,35 @@ +// +// DomainAccountManager.cpp +// libraries/networking/src +// +// Created by David Rowe on 23 Jul 2020. +// Copyright 2020 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DomainAccountManager.h" + + +DomainAccountManager::DomainAccountManager() { + +} + +bool DomainAccountManager::hasValidAccessToken() { + + // #######: TODO + + return false; +} + +bool DomainAccountManager::checkAndSignalForAccessToken() { + bool hasToken = hasValidAccessToken(); + + if (!hasToken) { + // Emit a signal so somebody can call back to us and request an access token given a user name and password. + emit authRequired(); + } + + return hasToken; +} diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h new file mode 100644 index 00000000000..696df71ab1d --- /dev/null +++ b/libraries/networking/src/DomainAccountManager.h @@ -0,0 +1,39 @@ +// +// DomainAccountManager.h +// libraries/networking/src +// +// Created by David Rowe on 23 Jul 2020. +// Copyright 2020 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DomainAccountManager_h +#define hifi_DomainAccountManager_h + +#include + +#include + + +class DomainAccountManager : public QObject, public Dependency { + Q_OBJECT +public: + DomainAccountManager(); + + Q_INVOKABLE bool checkAndSignalForAccessToken(); + +public slots: + +signals: + void authRequired(); + +private slots: + +private: + bool hasValidAccessToken(); + +}; + +#endif // hifi_DomainAccountManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 1ad371721f4..ceb2928c93b 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -24,6 +24,7 @@ #include "AddressManager.h" #include "Assignment.h" +#include "DomainAccountManager.h" #include "HifiSockAddr.h" #include "NodeList.h" #include "udt/Packet.h" @@ -500,6 +501,7 @@ bool DomainHandler::reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonC case ConnectionRefusedReason::Unknown: case ConnectionRefusedReason::ProtocolMismatch: case ConnectionRefusedReason::TooManyUsers: + case ConnectionRefusedReason::NotAuthorizedDomain: return false; } return false; @@ -515,6 +517,7 @@ bool DomainHandler::reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode case ConnectionRefusedReason::Unknown: case ConnectionRefusedReason::ProtocolMismatch: case ConnectionRefusedReason::TooManyUsers: + case ConnectionRefusedReason::NotAuthorizedMetaverse: return false; } return false; @@ -557,12 +560,13 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); // Some connection refusal reasons imply that a login is required. If so, suggest a new login. if (reasonSuggestsMetaverseLogin(reasonCode)) { qCWarning(networking) << "Make sure you are logged in to the metaverse."; + auto accountManager = DependencyManager::get(); + if (!_hasCheckedForAccessToken) { accountManager->checkAndSignalForAccessToken(); _hasCheckedForAccessToken = true; @@ -578,7 +582,15 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); + + if (!_hasCheckedForDomainAccessToken) { + accountManager->checkAndSignalForAccessToken(); + _hasCheckedForDomainAccessToken = true; + } + + // ####### TODO: regenerate key-pair after several failed connection attempts, similar to metaverse login code? + } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 5bbaac18c52..b9e6aa65b6f 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -291,6 +291,7 @@ private slots: QSet _domainConnectionRefusals; bool _hasCheckedForAccessToken { false }; + bool _hasCheckedForDomainAccessToken { false }; int _connectionDenialsSinceKeypairRegen { 0 }; int _checkInPacketsSinceLastReply { 0 }; From 82252cdc08f6b7e33b8fb0f85f5167808de8dab3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jul 2020 16:22:52 +1200 Subject: [PATCH 12/60] Get basics working --- domain-server/src/DomainGatekeeper.cpp | 2 +- libraries/networking/src/DomainHandler.cpp | 2 ++ libraries/networking/src/NodeList.cpp | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index ea7f0d2d91d..350eda7bf01 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -514,7 +514,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect nodeConnection.machineFingerprint); if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { - if (domainHasLogin() && !domainUsername.isEmpty()) { + if (domainHasLogin()) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain); } else { diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index ceb2928c93b..8a8ffe9f8a2 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -221,6 +221,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { QString previousHost = _domainURL.host(); _domainURL = domainURL; + _hasCheckedForDomainAccessToken = false; + if (previousHost != domainURL.host()) { qCDebug(networking) << "Updated domain hostname to" << domainURL.host(); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 63c2e9c902d..79b939d4099 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -486,10 +486,10 @@ void NodeList::sendDomainServerCheckIn() { // ####### If get into difficulties, could perhaps send domain's username and signature instead of metaverse. bool domainLoginIsConnected = false; if (!domainLoginIsConnected) { - if (true) { + if (false) { // ####### For testing, false causes user to be considered "not logged in". packetStream << QString("a@b.c"); - if (true) { - packetStream << QString("signature"); + if (true) { // ####### For testing, false is unhandled at this stage. + packetStream << QString("signature"); // #######: Consider "logged in" if this is sent during testing. } } } From 34eb74ac00fb20a1a879d66dbd7179bdf47f98d5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jul 2020 16:26:57 +1200 Subject: [PATCH 13/60] Update copyrights --- domain-server/src/DomainGatekeeper.cpp | 1 + domain-server/src/DomainGatekeeper.h | 1 + interface/src/Application.cpp | 1 + interface/src/ui/DialogsManager.cpp | 1 + interface/src/ui/DialogsManager.h | 1 + libraries/networking/src/DomainHandler.cpp | 1 + libraries/networking/src/DomainHandler.h | 1 + libraries/networking/src/NodeList.cpp | 1 + libraries/networking/src/NodePermissions.h | 1 + 9 files changed, 9 insertions(+) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 350eda7bf01..75cb11dab7a 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -4,6 +4,7 @@ // // Created by Stephen Birarda on 2015-08-24. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index fcf8e5aede8..172c63028c6 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -4,6 +4,7 @@ // // Created by Stephen Birarda on 2015-08-24. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index df72f0cc4ed..c401733b098 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4,6 +4,7 @@ // // Created by Andrzej Kapolka on 5/10/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 7cba608feb3..c74342cb45d 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -4,6 +4,7 @@ // // Created by Clement on 1/18/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 10bdb4bb206..6e701df9ef0 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -4,6 +4,7 @@ // // Created by Clement on 1/18/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 8a8ffe9f8a2..66d4c58a34d 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -4,6 +4,7 @@ // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index b9e6aa65b6f..ff252426a9a 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -4,6 +4,7 @@ // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 79b939d4099..262ad0d2a4d 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -4,6 +4,7 @@ // // Created by Stephen Birarda on 2/15/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 2b681eb7ee9..6658b7ea12b 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -4,6 +4,7 @@ // // Created by Seth Alves on 2016-6-1. // Copyright 2016 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html From 0618427d0f0d1912bb7d4fa7a6b13802d7c134a8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jul 2020 20:40:52 +1200 Subject: [PATCH 14/60] Wire up domain login dialog --- .../qml/LoginDialog/LinkAccountBody.qml | 7 ++----- .../qml/LoginDialog/LoggingInBody.qml | 1 + interface/src/Application.cpp | 2 +- interface/src/ui/DialogsManager.cpp | 16 +++++++++++--- interface/src/ui/DialogsManager.h | 3 +++ interface/src/ui/LoginDialog.cpp | 21 +++++++++++++++++-- interface/src/ui/LoginDialog.h | 7 ++++++- 7 files changed, 45 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index cd196629314..5c02de08d57 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -45,11 +45,8 @@ Item { property bool lostFocus: false readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() - // TODO: - // readonly property bool isLoggingInToDomain: loginDialog.getDomainLoginRequested() - // readonly property bool domainAuthProvider: loginDialog.getDomainLoginAuthProvider() - readonly property bool isLoggingInToDomain: true - readonly property string domainAuthProvider: "https://example.com/oauth2" + readonly property bool isLoggingInToDomain: loginDialog.getDomainLoginRequested() + readonly property string domainAuthProvider: loginDialog.getDomainLoginAuthProvider() QtObject { id: d diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index 8824be8625d..796798a2de7 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -3,6 +3,7 @@ // // Created by Wayne Chen on 10/18/18 // Copyright 2018 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c401733b098..72e6af74ec0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1356,7 +1356,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(domainAccountManager.data(), &DomainAccountManager::authRequired, dialogsManager.data(), &DialogsManager::showDomainLoginDialog); - // ####### TODO + // ####### TODO: Connect any other signals from domainAccountManager. // use our MyAvatar position and quat for address manager path diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index c74342cb45d..272c495789d 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -111,10 +111,18 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { } void DialogsManager::toggleLoginDialog() { + _isDomainLogin = false; LoginDialog::toggleAction(); } void DialogsManager::showLoginDialog() { + + qDebug() << "#######: showLoginDialog()"; + + // ####### TODO: May be called from script via DialogsManagerScriptingInterface. Need to handle the case that it's already + // displayed and may be the domain login version. + + _isDomainLogin = false; LoginDialog::showWithSelection(); } @@ -125,13 +133,15 @@ void DialogsManager::hideLoginDialog() { void DialogsManager::showDomainLoginDialog() { - // #######: TODO - qDebug() << "#######: showDomainLoginDialog()"; + _isDomainLogin = true; + LoginDialog::showWithSelection(); } -// #######: TODO +// #######: TODO: Domain version of toggleLoginDialog()? + +// #######: TODO: Domain version of hiadLoginDialog()? void DialogsManager::showUpdateDialog() { diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 6e701df9ef0..30127ced688 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -41,6 +41,7 @@ class DialogsManager : public QObject, public Dependency { QPointer getTestingDialog() const { return _testingDialog; } void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } void setAddressBarVisible(bool addressBarVisible); + bool getIsDomainLogin() { return _isDomainLogin; } public slots: void showAddressBar(); @@ -84,6 +85,8 @@ private slots: QPointer _domainConnectionDialog; bool _dialogCreatedWhileShown { false }; bool _addressBarVisible { false }; + + bool _isDomainLogin { false }; }; #endif // hifi_DialogsManager_h diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index ca9ae4282de..f185b0ab2bf 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -4,6 +4,7 @@ // // Created by Bradley Austin Davis on 2015/04/14 // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -25,6 +26,7 @@ #include "AccountManager.h" #include "DependencyManager.h" +#include "DialogsManager.h" #include "Menu.h" #include "Application.h" @@ -135,10 +137,15 @@ void LoginDialog::login(const QString& username, const QString& password) const DependencyManager::get()->requestAccessToken(username, password); } -void LoginDialog::loginDomain(const QString& username, const QString& password, const QUrl domainAuthProvider) const { +void LoginDialog::loginDomain(const QString& username, const QString& password, const QString& domainAuthProvider) const { + qDebug() << "####### LoginDialog::loginDomain()"; + qDebug() << "Attempting to login " << username << "into a domain through" << domainAuthProvider; - // TODO: + // ####### TODO // DependencyManager::get()->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++. } void LoginDialog::loginThroughOculus() { @@ -416,3 +423,13 @@ void LoginDialog::signupFailed(QNetworkReply* reply) { emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE); } } + +bool LoginDialog::getDomainLoginRequested() const { + return DependencyManager::get()->getIsDomainLogin(); +} + +// ####### TODO: This method may not be necessary. +QString LoginDialog::getDomainLoginAuthProvider() const { + // ####### TODO + return QString("https://example.com/oauth2"); +} diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 6ccef3fa393..49d5dc8fac4 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -4,6 +4,7 @@ // // Created by Bradley Austin Davis on 2015/04/14 // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -71,7 +72,7 @@ protected slots: Q_INVOKABLE QString oculusUserID() const; Q_INVOKABLE void login(const QString& username, const QString& password) const; - Q_INVOKABLE void loginDomain(const QString& username, const QString& password, const QUrl domainAuthProvider) const; + Q_INVOKABLE void loginDomain(const QString& username, const QString& password, const QString& domainAuthProvider) const; Q_INVOKABLE void loginThroughSteam(); Q_INVOKABLE void linkSteam(); Q_INVOKABLE void createAccountFromSteam(QString username = QString()); @@ -82,6 +83,10 @@ protected slots: Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password); Q_INVOKABLE bool getLoginDialogPoppedUp() const; + + Q_INVOKABLE bool getDomainLoginRequested() const; + Q_INVOKABLE QString getDomainLoginAuthProvider() const; + }; #endif // hifi_LoginDialog_h From 361ab97d83c003349ab23a6c91d73593201dc676 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jul 2020 20:53:23 +1200 Subject: [PATCH 15/60] Fix saving metaverse login --- .../qml/LoginDialog/LinkAccountBody.qml | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 5c02de08d57..6f437bb9919 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -112,8 +112,13 @@ Item { var savedDisplayName = Settings.getValue("Avatar/displayName", ""); displayNameField.text = savedDisplayName; emailField.placeholderText = "Username or Email"; - var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); - emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : ""; + if (!isLoggingInToDomain) { + var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); + emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : ""; + } else { + // ####### TODO + } + if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) { loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2; loginButton.anchors.right = displayNameField.right; @@ -201,7 +206,11 @@ Item { case Qt.Key_Return: event.accepted = true; if (keepMeLoggedInCheckbox.checked) { - Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + if (!isLoggingInToDomain) { + Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + } else { + // ####### TODO + } } linkAccountBody.login(); break; @@ -240,7 +249,11 @@ Item { case Qt.Key_Return: event.accepted = true; if (keepMeLoggedInCheckbox.checked) { - Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + if (!isLoggingInToDomain) { + Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + } else { + // ####### TODO + } } linkAccountBody.login(); break; @@ -320,7 +333,11 @@ Item { case Qt.Key_Return: event.accepted = true; if (keepMeLoggedInCheckbox.checked) { - Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + if (!isLoggingInToDomain) { + Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + } else { + // ####### TODO + } } linkAccountBody.login(); break; @@ -329,7 +346,7 @@ Item { } HifiControlsUit.CheckBox { id: keepMeLoggedInCheckbox - checked: Settings.getValue("keepMeLoggedIn", false); + checked: !isLoggingInToDomain ? Settings.getValue("keepMeLoggedIn", false) : false; // ####### TODO text: qsTr("Keep Me Logged In"); boxSize: 18; labelFontFamily: linkAccountBody.fontFamily @@ -342,14 +359,22 @@ Item { } onCheckedChanged: { Settings.setValue("keepMeLoggedIn", checked); - if (keepMeLoggedInCheckbox.checked) { - Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + if (!isLoggingInToDomain) { + if (keepMeLoggedInCheckbox.checked) { + Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); + } else { + Settings.setValue("keepMeLoggedIn/savedUsername", ""); + } } else { - Settings.setValue("keepMeLoggedIn/savedUsername", ""); + // ####### TODO } } Component.onCompleted: { - keepMeLoggedInCheckbox.checked = !Account.loggedIn; + if (!isLoggingInToDomain) { + keepMeLoggedInCheckbox.checked = !Account.loggedIn; + } else { + // ####### TODO + } } } HifiControlsUit.Button { From ed444383872c365190a37bbe44795684d91f1be0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Jul 2020 21:46:18 +1200 Subject: [PATCH 16/60] Fix domain login dialog disappearing when teleport to domain --- interface/src/ui/DialogsManager.cpp | 5 ----- interface/src/ui/LoginDialog.cpp | 6 ++---- libraries/networking/src/DomainAccountManager.cpp | 6 +++++- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 272c495789d..848663967f5 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -117,8 +117,6 @@ void DialogsManager::toggleLoginDialog() { void DialogsManager::showLoginDialog() { - qDebug() << "#######: showLoginDialog()"; - // ####### TODO: May be called from script via DialogsManagerScriptingInterface. Need to handle the case that it's already // displayed and may be the domain login version. @@ -132,9 +130,6 @@ void DialogsManager::hideLoginDialog() { void DialogsManager::showDomainLoginDialog() { - - qDebug() << "#######: showDomainLoginDialog()"; - _isDomainLogin = true; LoginDialog::showWithSelection(); } diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index f185b0ab2bf..94800529b93 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -133,14 +133,12 @@ void LoginDialog::dismissLoginDialog() { } void LoginDialog::login(const QString& username, const QString& password) const { - qDebug() << "Attempting to login " << username; + qDebug() << "Attempting to login" << username; DependencyManager::get()->requestAccessToken(username, password); } void LoginDialog::loginDomain(const QString& username, const QString& password, const QString& domainAuthProvider) const { - qDebug() << "####### LoginDialog::loginDomain()"; - - qDebug() << "Attempting to login " << username << "into a domain through" << domainAuthProvider; + qDebug() << "Attempting to login" << username << "into a domain through" << domainAuthProvider; // ####### TODO // DependencyManager::get()->requestAccessToken(username, password, domainAuthProvider); diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 0d7f38d3f6e..d1da09817c6 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -11,6 +11,8 @@ #include "DomainAccountManager.h" +#include + DomainAccountManager::DomainAccountManager() { @@ -28,7 +30,9 @@ bool DomainAccountManager::checkAndSignalForAccessToken() { if (!hasToken) { // Emit a signal so somebody can call back to us and request an access token given a user name and password. - emit authRequired(); + + // Dialog can be hidden immediately after showing if we've just teleported to the domain, unless the signal is delayed. + QTimer::singleShot(500, this, [this] { emit this->authRequired(); }); } return hasToken; From 294646a14167503994e7746e67da6fb1edffe9e1 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Mon, 27 Jul 2020 23:04:59 -0400 Subject: [PATCH 17/60] Add OAuth2 to domain settings. --- .../resources/describe-settings.json | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 3ae92651b81..f6b6656d7d1 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -3,7 +3,7 @@ "settings": [ { "name": "metaverse", - "label": "Metaverse / Networking", + "label": "Networking / Metaverse", "settings": [ { "name": "access_token", @@ -57,6 +57,41 @@ } ] }, + { + "name": "authentication", + "label": "Networking / Authentication", + "settings": [ + { + "name": "enable_oauth2", + "label": "Enable OAuth2 Authentication", + "help": "Allow a WordPress-based OAuth2 service to assign users to groups based on their role with the service.", + "default": false, + "type": "checkbox", + "advanced": true + }, + { + "name": "require_oauth2", + "label": "Require OAuth2 Authentication", + "help": "For any users not explicitly authorized in these settings, notify the Interface to authenticate through this method.", + "default": false, + "type": "checkbox", + "advanced": true + }, + { + "name": "domain_access_token", + "label": "Domain API Access Token", + "help": "This is the access token that your domain-server will use to verify users and their roles. This token must grant access to that permission set on your REST API server.", + "advanced": true, + "backup": false + }, + { + "name": "authentication_oauth2_url_base", + "label": "Authentication URL Base", + "help": "The URL base that the Interface and domain-server will use to make API requests.", + "advanced": true + } + ] + }, { "label": "Monitoring", "name": "monitoring", From 429b4ceefa870bd8fc400f36fc7151cff2d8ae38 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Jul 2020 21:23:55 +1200 Subject: [PATCH 18/60] Flesh out domain groups code --- domain-server/src/DomainGatekeeper.cpp | 56 ++++++++++++---------- domain-server/src/DomainGatekeeper.h | 7 ++- libraries/networking/src/NodePermissions.h | 3 -- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 75cb11dab7a..3c035839196 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -156,10 +156,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer_settingsManager.getAllKnownGroupNames().contains(group)) { - userPerms |= _server->_settingsManager.getPermissionsForGroup(group, QUuid()); + if (!verifiedDomainUserName.isEmpty()) { + auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; + foreach (QString userGroup, userGroups) { + if (_server->_settingsManager.getAllKnownGroupNames().contains(userGroup, Qt::CaseInsensitive)) { + userPerms |= _server->_settingsManager.getPermissionsForGroup(userGroup, QUuid()); // No rank for domain groups. +// ####### Enable ifdef //#ifdef WANT_DEBUG - qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << group << "so:" << userPerms; + qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << userGroup + << "so:" << userPerms; //#endif - } } userPerms.setVerifiedDomainUserName(verifiedDomainUserName); - userPerms.setVerifiedDomainUserGroups(verifiedDomainUserGroups); } if (verifiedUsername.isEmpty()) { @@ -309,7 +308,6 @@ void DomainGatekeeper::updateNodePermissions() { // authentication and verifiedUsername is only set once they user's key has been confirmed. QString verifiedUsername = node->getPermissions().getVerifiedUserName(); QString verifiedDomainUserName = node->getPermissions().getVerifiedDomainUserName(); - QStringList verifiedDomainUserGroups = node->getPermissions().getVerifiedDomainUserGroups(); NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0)); if (node->getPermissions().isAssignment) { @@ -345,8 +343,7 @@ void DomainGatekeeper::updateNodePermissions() { } userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUserName, - verifiedDomainUserGroups, connectingAddr.getAddress(), - hardwareAddress, machineFingerprint); + connectingAddr.getAddress(), hardwareAddress, machineFingerprint); } node->setPermissions(userPerms); @@ -486,31 +483,28 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // ####### TODO: OAuth2 corollary of metaverse code, above. + getDomainGroupMemberships(domainUsernameSignature); // Optimistically get started on group memberships. +#ifdef WANT_DEBUG + qDebug() << "stalling login because we have no domain username-signature:" << domainUsername; +#endif return SharedNodePointer(); } else if (verifyDomainUserSignature(domainUsername, domainUsernameSignature, nodeConnection.senderSockAddr)) { // User's domain identity is confirmed. - - // ####### TODO: Get user's domain group memberships (WordPress roles) from domain. - // This may already be provided at the same time as the "verify" call to the domain API. - // If it isn't, need to initiate getting them then handle their receipt along the lines of the - // metaverse code, above. - verifiedDomainUserGroups = QString("test-group").toLower().split(" "); - + getDomainGroupMemberships(domainUsername); verifiedDomainUsername = domainUsername.toLower(); - } else { // User's identity didn't check out. // ####### TODO: OAuth2 corollary of metaverse code, above. #ifdef WANT_DEBUG - qDebug() << "stalling login because signature verification failed:" << username; + qDebug() << "stalling login because domain signature verification failed:" << domainUsername; #endif return SharedNodePointer(); } } - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUsername, verifiedDomainUserGroups, + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUsername, nodeConnection.senderSockAddr.getAddress(), nodeConnection.hardwareAddress, nodeConnection.machineFingerprint); @@ -1004,7 +998,6 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) { AccountManagerAuth::Required, QNetworkAccessManager::PostOperation, callbackParams, QJsonDocument(json).toJson()); - } QString extractUsernameFromGroupMembershipsReply(QNetworkReply* requestReply) { @@ -1059,6 +1052,18 @@ void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply* requestReply _inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply)); } + +void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) { + + // ####### TODO: Get user's domain group memberships (WordPress roles) from domain. + // This may be able to be provided at the same time as the "authenticate user" call to the domain API, in which case + // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. + + QStringList wordpressGroupsForUser = QStringList("siLVer"); + _domainGroupMemberships[domainUserName] = wordpressGroupsForUser; +} + + void DomainGatekeeper::getDomainOwnerFriendsList() { JSONCallbackParameters callbackParams; callbackParams.callbackReceiver = this; @@ -1107,6 +1112,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply* req qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply->error(); } +// ####### TODO: Domain equivalent or addition void DomainGatekeeper::refreshGroupsCache() { // if agents are connected to this domain, refresh our cached information about groups and memberships in such. getDomainOwnerFriendsList(); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 172c63028c6..99bd875457f 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -128,14 +128,17 @@ private slots: QSet _inFlightGroupMembershipsRequests; // keep track of which we've already asked for NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, QString verifiedDomainUsername, - QStringList verifiedDomainUserGroups, const QHostAddress& senderAddress, - const QString& hardwareAddress, const QUuid& machineFingerprint); + const QHostAddress& senderAddress, const QString& hardwareAddress, + const QUuid& machineFingerprint); void getGroupMemberships(const QString& username); // void getIsGroupMember(const QString& username, const QUuid groupID); void getDomainOwnerFriendsList(); + // Login and groups for domain, separate from metaverse. bool domainHasLogin(); + void getDomainGroupMemberships(const QString& domainUserName); + QHash _domainGroupMemberships; // // Local ID management. void initLocalIDManagement(); diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 6658b7ea12b..82c008feef0 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -54,8 +54,6 @@ class NodePermissions { void setVerifiedDomainUserName(QString userName) { _verifiedDomainUserName = userName.toLower(); } const QString& getVerifiedDomainUserName() const { return _verifiedDomainUserName; } - void setVerifiedDomainUserGroups(QStringList userGroups) { _verifiedDomainUserGroups = userGroups; } - const QStringList& getVerifiedDomainUserGroups() const { return _verifiedDomainUserGroups; } void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }} QUuid getGroupID() const { return _groupID; } @@ -106,7 +104,6 @@ class NodePermissions { QUuid _rankID { QUuid() }; // 0 unless this is for a group QString _verifiedUserName; QString _verifiedDomainUserName; - QStringList _verifiedDomainUserGroups; bool _groupIDSet { false }; QUuid _groupID; From 94908fd1a7e567cbc1a203a67a7a322decac9685 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Jul 2020 22:05:40 +1200 Subject: [PATCH 19/60] Add support for group lists in domain server --- domain-server/src/DomainGatekeeper.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 3c035839196..e296328e324 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -174,11 +174,16 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin if (!verifiedDomainUserName.isEmpty()) { auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; foreach (QString userGroup, userGroups) { - if (_server->_settingsManager.getAllKnownGroupNames().contains(userGroup, Qt::CaseInsensitive)) { - userPerms |= _server->_settingsManager.getPermissionsForGroup(userGroup, QUuid()); // No rank for domain groups. + // Domain groups may be specified as comma- and/or space-separated lists of group names. + // For example, "silver gold, platinum". + auto domainGroups = _server->_settingsManager.getAllKnownGroupNames() + .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", + QRegularExpression::CaseInsensitiveOption)); + foreach(QString domainGroup, domainGroups) { + userPerms |= _server->_settingsManager.getPermissionsForGroup(domainGroup, QUuid()); // No rank for domain groups. // ####### Enable ifdef //#ifdef WANT_DEBUG - qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << userGroup + qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << domainGroup << "so:" << userPerms; //#endif } @@ -1059,7 +1064,8 @@ void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) // This may be able to be provided at the same time as the "authenticate user" call to the domain API, in which case // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. - QStringList wordpressGroupsForUser = QStringList("siLVer"); + QStringList wordpressGroupsForUser; + wordpressGroupsForUser << "silVER" << "gold"; _domainGroupMemberships[domainUserName] = wordpressGroupsForUser; } From d5e189422f0ac7e3f7845c4e5c530b3aeac52b8f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2020 08:08:08 +1200 Subject: [PATCH 20/60] Support blacklisting per domain groups --- domain-server/src/DomainGatekeeper.cpp | 32 +++++++++++++++---- .../src/DomainServerSettingsManager.cpp | 18 +++++++++++ .../src/DomainServerSettingsManager.h | 3 ++ 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index e296328e324..5944b6242c4 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -169,27 +169,25 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin #endif } - // If this user is a known member of an externally-hosted group, give them the implied permissions. + // If this user is a known member of a domain group, give them the implied permissions. // Do before processing verifiedUsername in case user is logged into the metaverse and is a member of a blacklist group. if (!verifiedDomainUserName.isEmpty()) { auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; foreach (QString userGroup, userGroups) { // Domain groups may be specified as comma- and/or space-separated lists of group names. // For example, "silver gold, platinum". - auto domainGroups = _server->_settingsManager.getAllKnownGroupNames() + auto domainGroups = _server->_settingsManager.getDomainGroupNames() .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", QRegularExpression::CaseInsensitiveOption)); foreach(QString domainGroup, domainGroups) { userPerms |= _server->_settingsManager.getPermissionsForGroup(domainGroup, QUuid()); // No rank for domain groups. -// ####### Enable ifdef -//#ifdef WANT_DEBUG +#ifdef WANT_DEBUG qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << domainGroup << "so:" << userPerms; -//#endif +#endif } } - userPerms.setVerifiedDomainUserName(verifiedDomainUserName); } if (verifiedUsername.isEmpty()) { @@ -293,6 +291,26 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin userPerms.setVerifiedUserName(verifiedUsername); } + // If this user is a known member of an domain group that is blacklisted, remove the implied permissions. + if (!verifiedDomainUserName.isEmpty()) { + auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; + foreach(QString userGroup, userGroups) { + // Domain groups may be specified as comma- and/or space-separated lists of group names. + // For example, "silver gold, platinum". + auto domainGroups = _server->_settingsManager.getDomainBlacklistGroupNames() + .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", + QRegularExpression::CaseInsensitiveOption)); + foreach(QString domainGroup, domainGroups) { + userPerms &= ~_server->_settingsManager.getForbiddensForGroup(domainGroup, QUuid()); +#ifdef WANT_DEBUG + qDebug() << "| user-permissions: domain user is in blacklist group:" << domainGroup << "so:" << userPerms; +#endif + } + } + + userPerms.setVerifiedDomainUserName(verifiedDomainUserName); + } + #ifdef WANT_DEBUG qDebug() << "| user-permissions: final:" << userPerms; #endif @@ -1065,7 +1083,7 @@ void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. QStringList wordpressGroupsForUser; - wordpressGroupsForUser << "silVER" << "gold"; + wordpressGroupsForUser << "silVER" << "gold" << "coal"; _domainGroupMemberships[domainUserName] = wordpressGroupsForUser; } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 73d78a5c708..30ca15a51e2 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -2185,6 +2185,24 @@ QList DomainServerSettingsManager::getBlacklistGroupIDs() { return result.toList(); } +QStringList DomainServerSettingsManager::getDomainGroupNames() { + // Names as configured in domain server; not necessarily mnetaverse groups. + QSet result; + foreach(NodePermissionsKey groupKey, _groupPermissions.keys()) { + result += _groupPermissions[groupKey]->getID(); + } + return result.toList(); +} + +QStringList DomainServerSettingsManager::getDomainBlacklistGroupNames() { + // Names as configured in domain server; not necessarily mnetaverse groups. + QSet result; + foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) { + result += _groupForbiddens[groupKey]->getID(); + } + return result.toList(); +} + void DomainServerSettingsManager::debugDumpGroupsState() { qDebug() << "--------- GROUPS ---------"; diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index e28b9f6cd14..8c18c22b32f 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -105,6 +105,9 @@ class DomainServerSettingsManager : public QObject { QList getGroupIDs(); QList getBlacklistGroupIDs(); + QStringList getDomainGroupNames(); + QStringList getDomainBlacklistGroupNames(); + // these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api void clearGroupMemberships(const QString& name) { _groupMembership[name.toLower()].clear(); } void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID); From 7c8993b34f6dc307e207597b8554e3a19bcf408e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2020 10:03:44 +1200 Subject: [PATCH 21/60] Add TODO --- domain-server/src/DomainGatekeeper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 5944b6242c4..32b02382dac 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -1082,6 +1082,8 @@ void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) // This may be able to be provided at the same time as the "authenticate user" call to the domain API, in which case // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. + // ####### TODO: Check how often this method and the WordPress API is called. + QStringList wordpressGroupsForUser; wordpressGroupsForUser << "silVER" << "gold" << "coal"; _domainGroupMemberships[domainUserName] = wordpressGroupsForUser; From 5706a42b56de695c0f1135814fc24be60c69626f Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 29 Jul 2020 01:53:21 -0400 Subject: [PATCH 22/60] Implement further handling of Interface OAuth2 This is preliminary, needs to be revisited in a more dynamic and clean fashion with time. --- interface/src/ui/LoginDialog.cpp | 12 +- .../networking/src/DomainAccountManager.cpp | 118 +++++++++++++++++- .../networking/src/DomainAccountManager.h | 11 +- 3 files changed, 132 insertions(+), 9 deletions(-) diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 94800529b93..ac1aa95f19e 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -25,6 +25,7 @@ #include #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(); + auto domainAccountManager = DependencyManager::get(); // 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()->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()->requestAccessToken(username, password, domainAuthProvider); } void LoginDialog::loginThroughOculus() { diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index d1da09817c6..524a7c4b0ae 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -11,18 +11,132 @@ #include "DomainAccountManager.h" +#include + #include +#include +#include +#include +#include +#include +#include + +#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 domainAccessToken {"private/domainAccessToken", "" }; +Setting::Handle domainAccessRefreshToken {"private/domainAccessToken", "" }; +Setting::Handle domainAccessTokenExpiresIn {"private/domainAccessTokenExpiresIn", -1 }; +Setting::Handle 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(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()) { + + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { + qCDebug(networking) << "An access token is required for requests to" + << qPrintable(_domainAuthProviderURL.toString()); + } - // #######: TODO + return false; + } else { + + // ####### TODO:: + + // if (!_isWaitingForTokenRefresh && needsToRefreshToken()) { + // refreshAccessToken(); + // } + + return true; + } + +} - return false; +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() { diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 696df71ab1d..4bb197175ef 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -25,15 +25,22 @@ class DomainAccountManager : public QObject, public Dependency { 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 From af34536a09aea56ccb8fdadd9ffe859f98dec906 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Thu, 30 Jul 2020 00:34:02 -0400 Subject: [PATCH 23/60] Further complete the loop. --- domain-server/resources/describe-settings.json | 4 ++-- interface/src/ui/DialogsManager.cpp | 15 ++++++++++----- interface/src/ui/DialogsManager.h | 2 ++ interface/src/ui/LoginDialog.cpp | 4 +--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index f6b6656d7d1..281a0be9cc6 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -59,12 +59,12 @@ }, { "name": "authentication", - "label": "Networking / Authentication", + "label": "Networking / WordPress OAuth2", "settings": [ { "name": "enable_oauth2", "label": "Enable OAuth2 Authentication", - "help": "Allow a WordPress-based OAuth2 service to assign users to groups based on their role with the service.", + "help": "Allow a WordPress-based (miniOrange) OAuth2 service to assign users to groups based on their role with the service.", "default": false, "type": "checkbox", "advanced": true diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 848663967f5..e96b8c56267 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -29,6 +29,7 @@ #include "OctreeStatsDialog.h" #include "PreferencesDialog.h" #include "UpdateDialog.h" +#include "DomainHandler.h" #include "scripting/HMDScriptingInterface.h" @@ -130,15 +131,19 @@ void DialogsManager::hideLoginDialog() { void DialogsManager::showDomainLoginDialog() { + const QJsonObject& settingsObject = DependencyManager::get()->getDomainHandler().getSettingsObject(); + static const QString WP_OAUTH2_SERVER_URL = "authentication_oauth2_url_base"; + + if (!settingsObject.contains(WP_OAUTH2_SERVER_URL)) { + qDebug() << "Cannot log in to domain because an OAuth2 authorization was required but no authorization server was given."; + return; + } + + _domainLoginAuthProvider = settingsObject[WP_OAUTH2_SERVER_URL].toString(); _isDomainLogin = true; LoginDialog::showWithSelection(); } -// #######: TODO: Domain version of toggleLoginDialog()? - -// #######: TODO: Domain version of hiadLoginDialog()? - - void DialogsManager::showUpdateDialog() { UpdateDialog::show(); } diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 30127ced688..b76ff69386a 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -42,6 +42,7 @@ class DialogsManager : public QObject, public Dependency { void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } void setAddressBarVisible(bool addressBarVisible); bool getIsDomainLogin() { return _isDomainLogin; } + QString getDomainLoginAuthProvider() { return _domainLoginAuthProvider; } public slots: void showAddressBar(); @@ -87,6 +88,7 @@ private slots: bool _addressBarVisible { false }; bool _isDomainLogin { false }; + QString _domainLoginAuthProvider { "" }; }; #endif // hifi_DialogsManager_h diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index ac1aa95f19e..d64ebdf42a7 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -428,8 +428,6 @@ bool LoginDialog::getDomainLoginRequested() const { return DependencyManager::get()->getIsDomainLogin(); } -// ####### TODO: This method may not be necessary. QString LoginDialog::getDomainLoginAuthProvider() const { - // ####### TODO - return QString("https://example.com/oauth2"); + return DependencyManager::get()->getDomainLoginAuthProvider(); } From 856e1f1ca3826a2065f6ddae182f218f21fcbd84 Mon Sep 17 00:00:00 2001 From: kasenvr <52365539+kasenvr@users.noreply.github.com> Date: Thu, 30 Jul 2020 22:25:24 -0400 Subject: [PATCH 24/60] Update interface/src/ui/DialogsManager.cpp Co-authored-by: David Rowe --- interface/src/ui/DialogsManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index e96b8c56267..c61d4c4069d 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -135,7 +135,7 @@ void DialogsManager::showDomainLoginDialog() { static const QString WP_OAUTH2_SERVER_URL = "authentication_oauth2_url_base"; if (!settingsObject.contains(WP_OAUTH2_SERVER_URL)) { - qDebug() << "Cannot log in to domain because an OAuth2 authorization was required but no authorization server was given."; + qDebug() << "Cannot log in to domain because an OAuth2 authorization was required but no authorization server was specified."; return; } From 6b28f3ea0dd1536bd480403bb8c3e3407cd7b99e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 31 Jul 2020 15:20:13 +1200 Subject: [PATCH 25/60] Reinstate TODOs --- interface/src/ui/DialogsManager.cpp | 5 +++++ interface/src/ui/LoginDialog.cpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index c61d4c4069d..9cad1b8a9ea 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -144,6 +144,11 @@ void DialogsManager::showDomainLoginDialog() { LoginDialog::showWithSelection(); } +// #######: TODO: Domain version of toggleLoginDialog()? + +// #######: TODO: Domain version of hiadLoginDialog()? + + void DialogsManager::showUpdateDialog() { UpdateDialog::show(); } diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index d64ebdf42a7..cd686719ea6 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -146,6 +146,9 @@ 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; DependencyManager::get()->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++. } void LoginDialog::loginThroughOculus() { From 56ba137ee3b4b4b81500ce41d89b6d5c07c7b908 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 31 Jul 2020 20:48:27 +1200 Subject: [PATCH 26/60] Get OAuth2 URL from server settings --- domain-server/resources/describe-settings.json | 2 +- domain-server/src/DomainGatekeeper.cpp | 18 ++++++++++-------- .../qml/LoginDialog/LinkAccountBody.qml | 3 +-- interface/src/ui/DialogsManager.cpp | 10 ---------- interface/src/ui/DialogsManager.h | 2 -- interface/src/ui/LoginDialog.cpp | 13 +++---------- interface/src/ui/LoginDialog.h | 3 +-- .../networking/src/DomainAccountManager.cpp | 14 ++++++++++++-- .../networking/src/DomainAccountManager.h | 7 ++++++- libraries/networking/src/DomainHandler.cpp | 5 ++++- 10 files changed, 38 insertions(+), 39 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 281a0be9cc6..14c15e1e3d4 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -85,7 +85,7 @@ "backup": false }, { - "name": "authentication_oauth2_url_base", + "name": "oauth2_url_base", "label": "Authentication URL Base", "help": "The URL base that the Interface and domain-server will use to make API requests.", "advanced": true diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 32b02382dac..f41c20e2455 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -444,6 +444,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo return newNode; } +const QString AUTHENTICATION_OAUTH2_URL_BASE = "authentication.oauth2_url_base"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location"; @@ -533,8 +534,14 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { if (domainHasLogin()) { + QString domainAuthURL; + auto domainAuthURLVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_BASE); + if (domainAuthURLVariant.canConvert()) { + domainAuthURL = domainAuthURLVariant.toString(); + qDebug() << "Domain authorization URL:" << domainAuthURL; + } sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", - nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain); + nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, domainAuthURL); } else { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedMetaverse); @@ -1164,13 +1171,8 @@ void DomainGatekeeper::refreshGroupsCache() { } bool DomainGatekeeper::domainHasLogin() { - // The domain may have its own users and groups. This is enabled in the server settings by ... - // ####### TODO: Use a particular string in the server name or set a particular tag in the server's settings? - // Or add a new server setting? - - // ####### TODO: Also configure URL for getting user's group memberships, in the server's settings? - - // ####### TODO + // The domain may have its own users and groups. This is enabled in the server settings by ... ####### + // ####### TODO: Base on server settings. return true; } diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 6f437bb9919..3d715d1a397 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -46,7 +46,6 @@ Item { readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() readonly property bool isLoggingInToDomain: loginDialog.getDomainLoginRequested() - readonly property string domainAuthProvider: loginDialog.getDomainLoginAuthProvider() QtObject { id: d @@ -76,7 +75,7 @@ Item { if (!isLoggingInToDomain) { loginDialog.login(emailField.text, passwordField.text); } else { - loginDialog.loginDomain(emailField.text, passwordField.text, domainAuthProvider); + loginDialog.loginDomain(emailField.text, passwordField.text); } if (linkAccountBody.loginDialogPoppedUp) { diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 9cad1b8a9ea..848663967f5 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -29,7 +29,6 @@ #include "OctreeStatsDialog.h" #include "PreferencesDialog.h" #include "UpdateDialog.h" -#include "DomainHandler.h" #include "scripting/HMDScriptingInterface.h" @@ -131,15 +130,6 @@ void DialogsManager::hideLoginDialog() { void DialogsManager::showDomainLoginDialog() { - const QJsonObject& settingsObject = DependencyManager::get()->getDomainHandler().getSettingsObject(); - static const QString WP_OAUTH2_SERVER_URL = "authentication_oauth2_url_base"; - - if (!settingsObject.contains(WP_OAUTH2_SERVER_URL)) { - qDebug() << "Cannot log in to domain because an OAuth2 authorization was required but no authorization server was specified."; - return; - } - - _domainLoginAuthProvider = settingsObject[WP_OAUTH2_SERVER_URL].toString(); _isDomainLogin = true; LoginDialog::showWithSelection(); } diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index b76ff69386a..30127ced688 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -42,7 +42,6 @@ class DialogsManager : public QObject, public Dependency { void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } void setAddressBarVisible(bool addressBarVisible); bool getIsDomainLogin() { return _isDomainLogin; } - QString getDomainLoginAuthProvider() { return _domainLoginAuthProvider; } public slots: void showAddressBar(); @@ -88,7 +87,6 @@ private slots: bool _addressBarVisible { false }; bool _isDomainLogin { false }; - QString _domainLoginAuthProvider { "" }; }; #endif // hifi_DialogsManager_h diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index cd686719ea6..3cc37bcadb1 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -143,12 +143,9 @@ void LoginDialog::login(const QString& username, const QString& password) const DependencyManager::get()->requestAccessToken(username, password); } -void LoginDialog::loginDomain(const QString& username, const QString& password, const QString& domainAuthProvider) const { - qDebug() << "Attempting to login" << username << "into a domain through" << domainAuthProvider; - DependencyManager::get()->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++. +void LoginDialog::loginDomain(const QString& username, const QString& password) const { + qDebug() << "Attempting to login" << username << "into a domain"; + DependencyManager::get()->requestAccessToken(username, password); } void LoginDialog::loginThroughOculus() { @@ -430,7 +427,3 @@ void LoginDialog::signupFailed(QNetworkReply* reply) { bool LoginDialog::getDomainLoginRequested() const { return DependencyManager::get()->getIsDomainLogin(); } - -QString LoginDialog::getDomainLoginAuthProvider() const { - return DependencyManager::get()->getDomainLoginAuthProvider(); -} diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 49d5dc8fac4..310a5db2551 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -72,7 +72,7 @@ protected slots: Q_INVOKABLE QString oculusUserID() const; Q_INVOKABLE void login(const QString& username, const QString& password) const; - Q_INVOKABLE void loginDomain(const QString& username, const QString& password, const QString& domainAuthProvider) const; + Q_INVOKABLE void loginDomain(const QString& username, const QString& password) const; Q_INVOKABLE void loginThroughSteam(); Q_INVOKABLE void linkSteam(); Q_INVOKABLE void createAccountFromSteam(QString username = QString()); @@ -85,7 +85,6 @@ protected slots: Q_INVOKABLE bool getLoginDialogPoppedUp() const; Q_INVOKABLE bool getDomainLoginRequested() const; - Q_INVOKABLE QString getDomainLoginAuthProvider() const; }; diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 524a7c4b0ae..78ae779fcab 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -45,7 +45,17 @@ DomainAccountManager::DomainAccountManager() { connect(this, &DomainAccountManager::loginComplete, this, &DomainAccountManager::sendInterfaceAccessTokenToServer); } -void DomainAccountManager::requestAccessToken(const QString& login, const QString& password, const QString& domainAuthProvider) { +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()); + + // ####### TODO: See AccountManager::setAuthURL(). + } +} + +void DomainAccountManager::requestAccessToken(const QString& login, const QString& password) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); @@ -53,7 +63,7 @@ void DomainAccountManager::requestAccessToken(const QString& login, const QStrin request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); - _domainAuthProviderURL = domainAuthProvider; + _domainAuthProviderURL = _authURL; _domainAuthProviderURL.setPath("/oauth/token"); QByteArray postData; diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 4bb197175ef..eb0f2659dd5 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -13,6 +13,7 @@ #define hifi_DomainAccountManager_h #include +#include #include @@ -22,10 +23,12 @@ class DomainAccountManager : public QObject, public Dependency { public: DomainAccountManager(); + void setAuthURL(const QUrl& authURL); + Q_INVOKABLE bool checkAndSignalForAccessToken(); public slots: - void requestAccessToken(const QString& login, const QString& password, const QString& domainAuthProvider); + void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenFinished(); signals: @@ -41,6 +44,8 @@ private slots: bool accessTokenIsExpired(); void setAccessTokenFromJSON(const QJsonObject&); void sendInterfaceAccessTokenToServer(); + + QUrl _authURL; }; #endif // hifi_DomainAccountManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 66d4c58a34d..0a61036b961 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -584,8 +584,11 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); + if (!extraInfo.isEmpty()) { + accountManager->setAuthURL(extraInfo); + } if (!_hasCheckedForDomainAccessToken) { accountManager->checkAndSignalForAccessToken(); From aff327520760c7c40bc59f77e5fb18aa31d7b721 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 1 Aug 2020 10:17:27 +1200 Subject: [PATCH 27/60] Initial OAuth2 login working --- .../networking/src/DomainAccountManager.cpp | 66 +++++++++++++------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 78ae779fcab..d36f4b5f092 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -29,17 +29,19 @@ #include "udt/PacketHeaders.h" #include "NetworkAccessManager.h" +// FIXME: Generalize to other OAuth2 sources for domain login. + const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; -const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; +const QString DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE = "foo bar"; // ####### TODO: WordPress plugin's required scope. +// ####### TODO: Should scope be configured in domain server settings? +// ####### 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", "" }; -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); @@ -57,25 +59,29 @@ void DomainAccountManager::setAuthURL(const QUrl& authURL) { void DomainAccountManager::requestAccessToken(const QString& login, const QString& password) { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request; - request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + // ####### TODO: WordPress plugin's authorization requirements. + request.setRawHeader(QByteArray("Authorization"), QByteArray("Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x")); - _domainAuthProviderURL = _authURL; - _domainAuthProviderURL.setPath("/oauth/token"); + QByteArray formData; + formData.append("grant_type=password&"); + formData.append("username=" + QUrl::toPercentEncoding(login) + "&"); + formData.append("password=" + QUrl::toPercentEncoding(password) + "&"); + formData.append("scope=" + DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE); + // ####### TODO: Include state? - 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); + QUrl domainAuthURL = _authURL; + domainAuthURL.setPath("/token"); // ####### TODO: miniOrange-mandated URL. ####### TODO: Should this be included in the server settings value? + request.setUrl(domainAuthURL); - request.setUrl(_domainAuthProviderURL); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - QNetworkReply* requestReply = networkAccessManager.post(request, postData); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkReply* requestReply = networkAccessManager.post(request, formData); connect(requestReply, &QNetworkReply::finished, this, &DomainAccountManager::requestAccessTokenFinished); } @@ -86,19 +92,25 @@ void DomainAccountManager::requestAccessTokenFinished() { const QJsonObject& rootObject = jsonResponse.object(); if (!rootObject.contains("error")) { - // construct an OAuthAccessToken from the json object + // ####### TODO: Process response scope? + // ####### TODO: Process response state? - if (!rootObject.contains("access_token") || !rootObject.contains("expires_in") + if (!rootObject.contains("access_token") + // ####### TODO: Does WordPRess plugin provide "expires_in"? + // If so, handle here, or is it just the domain server that needs to use it? + //|| !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."; + emit loginFailed(); + } 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); @@ -114,7 +126,11 @@ void DomainAccountManager::sendInterfaceAccessTokenToServer() { } bool DomainAccountManager::accessTokenIsExpired() { + // ####### TODO: accessTokenIsExpired() + return true; + /* return domainAccessTokenExpiresIn.get() != -1 && domainAccessTokenExpiresIn.get() <= QDateTime::currentMSecsSinceEpoch(); + */ } @@ -125,7 +141,7 @@ bool DomainAccountManager::hasValidAccessToken() { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qCDebug(networking) << "An access token is required for requests to" - << qPrintable(_domainAuthProviderURL.toString()); + << qPrintable(_authURL.toString()); } return false; @@ -143,15 +159,23 @@ bool DomainAccountManager::hasValidAccessToken() { } void DomainAccountManager::setAccessTokenFromJSON(const QJsonObject& jsonObject) { + // ####### TODO: Enable and use these. + /* 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() { bool hasToken = hasValidAccessToken(); + // ####### TODO: Handle hasToken == true. + // It causes the login dialog not to display (OK) but somewhere the domain server needs to be sent it (and if domain server + // gets error when trying to use it then user should be prompted to login). + hasToken = false; + if (!hasToken) { // Emit a signal so somebody can call back to us and request an access token given a user name and password. From 15c6baceb8ed6a21a4a6a1e741b8c854ebecfb26 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 1 Aug 2020 18:57:21 +1200 Subject: [PATCH 28/60] Send OAuth2 username and tokens to domain server --- domain-server/src/DomainGatekeeper.cpp | 21 ++++++------- domain-server/src/DomainGatekeeper.h | 4 +-- .../networking/src/DomainAccountManager.cpp | 22 +++++++++++--- .../networking/src/DomainAccountManager.h | 10 ++++++- libraries/networking/src/NodeList.cpp | 30 ++++++++++++------- libraries/networking/src/NodeList.h | 2 ++ 6 files changed, 61 insertions(+), 28 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index f41c20e2455..a1581c26f14 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -95,7 +95,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointersecond); } else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) { QByteArray usernameSignature; - QByteArray domainUsernameSignature; + QString domainTokens; if (message->getBytesLeftToRead() > 0) { // read username from packet @@ -110,14 +110,14 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer> domainUsername; if (message->getBytesLeftToRead() > 0) { - // Read domain signature from packet. - packetStream >> domainUsernameSignature; + // Read domain tokens from packet. + packetStream >> domainTokens; } } } } - node = processAgentConnectRequest(nodeConnection, username, usernameSignature, domainUsername, domainUsernameSignature); + node = processAgentConnectRequest(nodeConnection, username, usernameSignature, domainUsername, domainTokens); } if (node) { @@ -452,7 +452,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect const QString& username, const QByteArray& usernameSignature, const QString& domainUsername, - const QByteArray& domainUsernameSignature) { + const QString& domainTokens) { auto limitedNodeList = DependencyManager::get(); @@ -502,17 +502,18 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect QString verifiedDomainUsername; QStringList verifiedDomainUserGroups; if (domainHasLogin() && !domainUsername.isEmpty()) { - if (domainUsernameSignature.isEmpty()) { + if (domainTokens.isEmpty()) { // User is attempting to prove their domain identity. // ####### TODO: OAuth2 corollary of metaverse code, above. - getDomainGroupMemberships(domainUsernameSignature); // Optimistically get started on group memberships. + // ####### TODO: Do the following now? Probably can't! + //getDomainGroupMemberships(domainUsername); // Optimistically get started on group memberships. #ifdef WANT_DEBUG qDebug() << "stalling login because we have no domain username-signature:" << domainUsername; #endif return SharedNodePointer(); - } else if (verifyDomainUserSignature(domainUsername, domainUsernameSignature, nodeConnection.senderSockAddr)) { + } else if (verifyDomainUserSignature(domainUsername, domainTokens, nodeConnection.senderSockAddr)) { // User's domain identity is confirmed. getDomainGroupMemberships(domainUsername); verifiedDomainUsername = domainUsername.toLower(); @@ -742,10 +743,10 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, } bool DomainGatekeeper::verifyDomainUserSignature(const QString& domainUsername, - const QByteArray& domainUsernameSignature, + const QString& domainTokens, const HifiSockAddr& senderSockAddr) { - // ####### TODO: Verify via domain OAuth2. + // ####### TODO: Verify response from domain OAuth2 request to WordPress, if it's arrived yet. bool success = true; if (success) { return true; diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 99bd875457f..0cb757a9eae 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -79,13 +79,13 @@ private slots: const QString& username, const QByteArray& usernameSignature, const QString& domainUsername, - const QByteArray& domainUsernameSignature); + const QString& domainTokens); SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection); bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); - bool verifyDomainUserSignature(const QString& domainUsername, const QByteArray& domainUsernameSignature, + bool verifyDomainUserSignature(const QString& domainUsername, const QString& domainUsernameSignature, const HifiSockAddr& senderSockAddr); bool isWithinMaxCapacity(); diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index d36f4b5f092..cb0b93232e8 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -11,6 +11,8 @@ #include "DomainAccountManager.h" +// ####### TODO: Check that all #includes are still needed. + #include #include @@ -43,7 +45,12 @@ Setting::Handle domainAccessTokenExpiresIn {"private/domainAccessTokenExpir Setting::Handle domainAccessTokenType {"private/domainAccessTokenType", "" }; -DomainAccountManager::DomainAccountManager() { +DomainAccountManager::DomainAccountManager() : + _authURL(), + _username(), + _access_token(), + _refresh_token() +{ connect(this, &DomainAccountManager::loginComplete, this, &DomainAccountManager::sendInterfaceAccessTokenToServer); } @@ -57,8 +64,11 @@ void DomainAccountManager::setAuthURL(const QUrl& authURL) { } } -void DomainAccountManager::requestAccessToken(const QString& login, const QString& password) { +void DomainAccountManager::requestAccessToken(const QString& username, const QString& password) { + _username = username; + _access_token = ""; + _refresh_token = ""; QNetworkRequest request; @@ -69,7 +79,7 @@ void DomainAccountManager::requestAccessToken(const QString& login, const QStrin QByteArray formData; formData.append("grant_type=password&"); - formData.append("username=" + QUrl::toPercentEncoding(login) + "&"); + formData.append("username=" + QUrl::toPercentEncoding(username) + "&"); formData.append("password=" + QUrl::toPercentEncoding(password) + "&"); formData.append("scope=" + DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE); // ####### TODO: Include state? @@ -122,7 +132,7 @@ void DomainAccountManager::requestAccessTokenFinished() { } void DomainAccountManager::sendInterfaceAccessTokenToServer() { - // TODO: Send successful packet to the domain-server. + emit newTokens(); } bool DomainAccountManager::accessTokenIsExpired() { @@ -159,7 +169,11 @@ bool DomainAccountManager::hasValidAccessToken() { } void DomainAccountManager::setAccessTokenFromJSON(const QJsonObject& jsonObject) { + _access_token = jsonObject["access_token"].toString(); + _refresh_token = jsonObject["refresh_token"].toString(); + // ####### TODO: Enable and use these. + // ####### TODO: Protect these per AccountManager? /* domainAccessToken.set(jsonObject["access_token"].toString()); domainAccessRefreshToken.set(jsonObject["refresh_token"].toString()); diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index eb0f2659dd5..08b625a2464 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -25,10 +25,14 @@ class DomainAccountManager : public QObject, public Dependency { void setAuthURL(const QUrl& authURL); + QString getUsername() { return _username; } + QString getAccessToken() { return _access_token; } + QString getRefreshToken() { return _refresh_token; } + Q_INVOKABLE bool checkAndSignalForAccessToken(); public slots: - void requestAccessToken(const QString& login, const QString& password); + void requestAccessToken(const QString& username, const QString& password); void requestAccessTokenFinished(); signals: @@ -36,6 +40,7 @@ public slots: void loginComplete(const QUrl& authURL); void loginFailed(); void logoutComplete(); + void newTokens(); private slots: @@ -46,6 +51,9 @@ private slots: void sendInterfaceAccessTokenToServer(); QUrl _authURL; + QString _username; // ####### TODO: Store elsewhere? + QString _access_token; // ####... "" + QString _refresh_token; // ####... "" }; #endif // hifi_DomainAccountManager_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 262ad0d2a4d..16eba6c6a16 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -34,6 +34,7 @@ #include "AddressManager.h" #include "Assignment.h" #include "AudioHelpers.h" +#include "DomainAccountManager.h" #include "HifiSockAddr.h" #include "FingerprintUtils.h" @@ -104,6 +105,13 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) // clear our NodeList when logout is requested connect(accountManager.data(), &AccountManager::logoutComplete , this, [this]{ reset("Logged out"); }); + // Only used in Interface. + auto domainAccountManager = DependencyManager::get(); + if (domainAccountManager) { + _hasDomainAccountManager = true; + connect(domainAccountManager.data(), &DomainAccountManager::newTokens, this, &NodeList::sendDomainServerCheckIn); + } + // anytime we get a new node we will want to attempt to punch to it connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); connect(this, &LimitedNodeList::nodeSocketUpdated, this, &NodeList::startNodeHolePunch); @@ -468,6 +476,7 @@ 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) { DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); packetStream << accountInfo.getUsername(); @@ -477,21 +486,20 @@ void NodeList::sendDomainServerCheckIn() { const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken); packetStream << usernameSignature; } else { - packetStream << QString(""); // Placeholder in case have domainUsername. + // ####### TODO: Only append if are going to send domain username? + packetStream << QString(""); // Placeholder in case have domain username. } } else { - packetStream << QString(""); // Placeholder in case have domainUsername. + // ####### TODO: Only append if are going to send domainUsername? + packetStream << QString("") << QString(""); // Placeholders in case have domain username. } - // ####### TODO: Send domain username and signature if domain has these and aren't logged in. - // ####### If get into difficulties, could perhaps send domain's username and signature instead of metaverse. - bool domainLoginIsConnected = false; - if (!domainLoginIsConnected) { - if (false) { // ####### For testing, false causes user to be considered "not logged in". - packetStream << QString("a@b.c"); - if (true) { // ####### For testing, false is unhandled at this stage. - packetStream << QString("signature"); // #######: Consider "logged in" if this is sent during testing. - } + // Send domain domain login data from Interface to domain server. + if (_hasDomainAccountManager) { + auto domainAccountManager = DependencyManager::get(); + if (!domainAccountManager->getUsername().isEmpty()) { + packetStream << domainAccountManager->getUsername(); + packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken()); } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index c377ea89cb9..4954c53c845 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -196,6 +196,8 @@ private slots: #if (PR_BUILD || DEV_BUILD) bool _shouldSendNewerVersion { false }; #endif + + bool _hasDomainAccountManager { false }; }; #endif // hifi_NodeList_h From 8cdd76a42e1b1d3562050531590db419b0f1eda9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 2 Aug 2020 16:17:04 +1200 Subject: [PATCH 29/60] Verify user at domain server --- domain-server/src/DomainGatekeeper.cpp | 163 ++++++++++++++---- domain-server/src/DomainGatekeeper.h | 26 ++- .../networking/src/DomainAccountManager.cpp | 3 + libraries/networking/src/DomainHandler.h | 1 + libraries/networking/src/NodeList.cpp | 3 +- 5 files changed, 157 insertions(+), 39 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index a1581c26f14..210dabece13 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -90,12 +90,13 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointersecond); } else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) { QByteArray usernameSignature; - QString domainTokens; + + QString domainUsername; + QStringList domainTokens; if (message->getBytesLeftToRead() > 0) { // read username from packet @@ -111,13 +112,17 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetBytesLeftToRead() > 0) { // Read domain tokens from packet. - packetStream >> domainTokens; + + QString domainTokensString; + packetStream >> domainTokensString; + domainTokens = domainTokensString.split(":"); } } } } - node = processAgentConnectRequest(nodeConnection, username, usernameSignature, domainUsername, domainTokens); + node = processAgentConnectRequest(nodeConnection, username, usernameSignature, + domainUsername, domainTokens.value(0), domainTokens.value(1)); } if (node) { @@ -452,7 +457,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect const QString& username, const QByteArray& usernameSignature, const QString& domainUsername, - const QString& domainTokens) { + const QString& domainAccessToken, + const QString& domainRefreshToken) { auto limitedNodeList = DependencyManager::get(); @@ -502,30 +508,39 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect QString verifiedDomainUsername; QStringList verifiedDomainUserGroups; if (domainHasLogin() && !domainUsername.isEmpty()) { - if (domainTokens.isEmpty()) { - // User is attempting to prove their domain identity. - - // ####### TODO: OAuth2 corollary of metaverse code, above. - // ####### TODO: Do the following now? Probably can't! - //getDomainGroupMemberships(domainUsername); // Optimistically get started on group memberships. + if (domainAccessToken.isEmpty()) { + // User is attempting to prove their domain identity. #ifdef WANT_DEBUG - qDebug() << "stalling login because we have no domain username-signature:" << domainUsername; + qDebug() << "Stalling login because we have no domain OAuth2 tokens:" << domainUsername; #endif return SharedNodePointer(); - } else if (verifyDomainUserSignature(domainUsername, domainTokens, nodeConnection.senderSockAddr)) { + + } else if (!_verifiedDomainUserIdentities.contains(domainUsername) + || _verifiedDomainUserIdentities[domainUsername] != QPair(domainAccessToken, domainRefreshToken)) { + // ####### TODO: Write a function for the above test. + // User's domain identity needs to be confirmed. + if (_verifiedDomainUserIdentities.contains(domainUsername)) { + _verifiedDomainUserIdentities.remove(domainUsername); + } + requestDomainUser(domainUsername, domainAccessToken, domainRefreshToken); +#ifdef WANT_DEBUG + qDebug() << "Stalling login because we haven't authenticated user yet:" << domainUsername; +#endif + + } else if (verifyDomainUserSignature(domainUsername, domainAccessToken, domainRefreshToken, + nodeConnection.senderSockAddr)) { // User's domain identity is confirmed. getDomainGroupMemberships(domainUsername); verifiedDomainUsername = domainUsername.toLower(); - } else { - // User's identity didn't check out. - - // ####### TODO: OAuth2 corollary of metaverse code, above. + } else { + // User's domain identity didn't check out. #ifdef WANT_DEBUG - qDebug() << "stalling login because domain signature verification failed:" << domainUsername; + qDebug() << "Stalling login because domain user verification failed:" << domainUsername; #endif return SharedNodePointer(); + } } @@ -742,17 +757,17 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, return false; } -bool DomainGatekeeper::verifyDomainUserSignature(const QString& domainUsername, - const QString& domainTokens, - const HifiSockAddr& senderSockAddr) { +// ####### TODO: Rename to verifyDomainUser()? +bool DomainGatekeeper::verifyDomainUserSignature(const QString& username, const QString& accessToken, + const QString& refreshToken, const HifiSockAddr& senderSockAddr) { // ####### TODO: Verify response from domain OAuth2 request to WordPress, if it's arrived yet. - bool success = true; - if (success) { + // #### Or assume the verification step has already occurred? + if (_verifiedDomainUserIdentities.contains(username)) { return true; } - sendConnectionDeniedPacket("Error decrypting domain username signature.", senderSockAddr, + sendConnectionDeniedPacket("Error verifying domain user.", senderSockAddr, DomainHandler::ConnectionRefusedReason::LoginErrorDomain); return false; } @@ -1171,12 +1186,6 @@ void DomainGatekeeper::refreshGroupsCache() { #endif } -bool DomainGatekeeper::domainHasLogin() { - // The domain may have its own users and groups. This is enabled in the server settings by ... ####### - // ####### TODO: Base on server settings. - return true; -} - void DomainGatekeeper::initLocalIDManagement() { std::uniform_int_distribution sixteenBitRand; std::random_device randomDevice; @@ -1204,3 +1213,97 @@ Node::LocalID DomainGatekeeper::findOrCreateLocalID(const QUuid& uuid) { _localIDs.insert(newLocalID); return newLocalID; } + + +bool DomainGatekeeper::domainHasLogin() { + // The domain may have its own users and groups. This is enabled in the server settings by ... ####### + // ####### TODO: Base on server settings. + return true; +} + +void DomainGatekeeper::requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken) { + + // ####### TODO: Move this further up the chain such that generates "invalid username or password" condition? + // Don't request identity for the standard psuedo-account-names. + if (NodePermissions::standardNames.contains(username, Qt::CaseInsensitive)) { + return; + } + + if (_inFlightDomainUserIdentityRequests.contains(username)) { + // Domain identify request for this username is already flight. + return; + } + _inFlightDomainUserIdentityRequests.insert(username, QPair(accessToken, refreshToken)); + + QString API_BASE = "http://127.0.0.1:9001/wp-json/"; + // Typically "http://oursite.com/wp-json/". + // However, if using non-pretty permalinks or otherwise get a 404 error then use "http://oursite.com/?rest_route=/". + + // ####### TODO: Confirm API w.r.t. OAuth2 plugin's capabilities. + // Get data pertaining to "me", the user who generated the access token. + QString API_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; + + // ####### TODO: Append a random key to check in response? + + QNetworkRequest request; + + request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + // ####### TODO: WordPress plugin's authorization requirements. + request.setRawHeader(QByteArray("Authorization"), QString("Bearer " + accessToken).toUtf8()); + + QByteArray formData; // No data to send. + + QUrl domainUserURL = API_BASE + API_ROUTE; + domainUserURL = "http://localhost:9002/resource"; // ####### TODO: Delete + request.setUrl(domainUserURL); + + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + + // ####### TODO: Handle invalid URL (e.g., set timeout or similar). + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkReply* requestReply = networkAccessManager.post(request, formData); + connect(requestReply, &QNetworkReply::finished, this, &DomainGatekeeper::requestDomainUserFinished); +} + +void DomainGatekeeper::requestDomainUserFinished() { + + QNetworkReply* requestReply = reinterpret_cast(sender()); + + auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (200 <= httpStatus && httpStatus < 300) { + // Success. + QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); + const QJsonObject& rootObject = jsonResponse.object(); + // ####### Expected response: + /* + { + id: 2, + username : 'apiuser', + roles : ['subscriber'] , + } + */ + + // ####### TODO: Handle invalid / unexpected response. + + QString username = rootObject["username"].toString().toLower(); + // ####### TODO: Handle invalid username or one that isn't in the _inFlight list. + + if (_inFlightDomainUserIdentityRequests.contains(username)) { + // Success! Verified user. + _verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username)); + _inFlightDomainUserIdentityRequests.remove(username); + } else { + // Unexpected response. + // ####### TODO + } + + } else { + // Failure. + // ####### TODO: Is this the best way to handle _inFlightDomainUserIdentityRequests? + // If there's a brief network glitch will it recover? + // Perhaps clear on a timer? Cancel timer upon subsequent successful responses? + _inFlightDomainUserIdentityRequests.clear(); + } +} diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 0cb757a9eae..eaf20a6285e 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -72,6 +72,10 @@ public slots: private slots: void handlePeerPingTimeout(); + + // Login and groups for domain, separate from metaverse. + void requestDomainUserFinished(); + private: SharedNodePointer processAssignmentConnectRequest(const NodeConnectionData& nodeConnection, const PendingAssignedNodeData& pendingAssignment); @@ -79,13 +83,14 @@ private slots: const QString& username, const QByteArray& usernameSignature, const QString& domainUsername, - const QString& domainTokens); + const QString& domainAccessToken, + const QString& domainRefreshToken); SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection); bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); - bool verifyDomainUserSignature(const QString& domainUsername, const QString& domainUsernameSignature, + bool verifyDomainUserSignature(const QString& username, const QString& accessToken, const QString& refreshToken, const HifiSockAddr& senderSockAddr); bool isWithinMaxCapacity(); @@ -135,20 +140,25 @@ private slots: // void getIsGroupMember(const QString& username, const QUuid groupID); void getDomainOwnerFriendsList(); - // Login and groups for domain, separate from metaverse. - bool domainHasLogin(); - void getDomainGroupMemberships(const QString& domainUserName); - QHash _domainGroupMemberships; // - // Local ID management. void initLocalIDManagement(); using UUIDToLocalID = std::unordered_map ; using LocalIDs = std::unordered_set; LocalIDs _localIDs; UUIDToLocalID _uuidToLocalID; - Node::LocalID _currentLocalID; Node::LocalID _idIncrement; + + // Login and groups for domain, separate from metaverse. + bool domainHasLogin(); + void requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken); + + typedef QHash> DomainUserIdentities; // > + DomainUserIdentities _inFlightDomainUserIdentityRequests; // Keep track of domain user identity requests in progress. + DomainUserIdentities _verifiedDomainUserIdentities; // Verified domain users. + + void getDomainGroupMemberships(const QString& domainUserName); + QHash _domainGroupMemberships; // }; diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index cb0b93232e8..7fc02893eba 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -101,9 +101,12 @@ void DomainAccountManager::requestAccessTokenFinished() { QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); const QJsonObject& rootObject = jsonResponse.object(); + // ####### TODO: Test HTTP response codes rather than object contains "error". + // #### reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200 if (!rootObject.contains("error")) { // ####### TODO: Process response scope? // ####### TODO: Process response state? + // ####### TODO: Check that token type == "Bearer"? if (!rootObject.contains("access_token") // ####### TODO: Does WordPRess plugin provide "expires_in"? diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index ff252426a9a..d0bd40f7a7f 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -125,6 +125,7 @@ class DomainHandler : public QObject { bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); + bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } bool getInterstitialModeEnabled() const; void setInterstitialModeEnabled(bool enableInterstitialMode); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 16eba6c6a16..539d044c1e4 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -499,7 +499,8 @@ void NodeList::sendDomainServerCheckIn() { auto domainAccountManager = DependencyManager::get(); if (!domainAccountManager->getUsername().isEmpty()) { packetStream << domainAccountManager->getUsername(); - packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken()); + if (!domainAccountManager->getAccessToken().isEmpty()) { + packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken()); } } From c3769a5f7426855d8acfb2e10a6af469b6ad9fc1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 2 Aug 2020 17:33:13 +1200 Subject: [PATCH 30/60] Get WordPress API path from domain server settings --- domain-server/resources/describe-settings.json | 8 +++++++- domain-server/src/DomainGatekeeper.cpp | 15 ++++++++------- libraries/networking/src/NodeList.cpp | 1 + 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 14c15e1e3d4..ba019e3fe7d 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -87,7 +87,13 @@ { "name": "oauth2_url_base", "label": "Authentication URL Base", - "help": "The URL base that the Interface and domain-server will use to make API requests.", + "help": "The URL base that the Interface will use to login via OAuth2.", + "advanced": true + }, + { + "name": "wordpress_url_base", + "label": "WordPress API URL Base", + "help": "The URL base that the domain server will use to make WordPress API calls. Typically \"http://oursite.com/wp-json/\". However, if using non-pretty permalinks or otherwise get a 404 error then use \"http://oursite.com/?rest_route=/\".", "advanced": true } ] diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 210dabece13..29667b0fd5e 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -450,6 +450,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo } const QString AUTHENTICATION_OAUTH2_URL_BASE = "authentication.oauth2_url_base"; +const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location"; @@ -1235,13 +1236,15 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& } _inFlightDomainUserIdentityRequests.insert(username, QPair(accessToken, refreshToken)); - QString API_BASE = "http://127.0.0.1:9001/wp-json/"; - // Typically "http://oursite.com/wp-json/". - // However, if using non-pretty permalinks or otherwise get a 404 error then use "http://oursite.com/?rest_route=/". + QString apiBase = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString(); + if (!apiBase.endsWith("/")) { + apiBase += "/"; + } - // ####### TODO: Confirm API w.r.t. OAuth2 plugin's capabilities. // Get data pertaining to "me", the user who generated the access token. - QString API_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; + // ####### TODO: Confirm API_ROUTE w.r.t. OAuth2 plugin's capabilities. + QString API_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; + QUrl domainUserURL = apiBase + API_ROUTE; // ####### TODO: Append a random key to check in response? @@ -1254,8 +1257,6 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& QByteArray formData; // No data to send. - QUrl domainUserURL = API_BASE + API_ROUTE; - domainUserURL = "http://localhost:9002/resource"; // ####### TODO: Delete request.setUrl(domainUserURL); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 539d044c1e4..e02a8dd56e1 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -501,6 +501,7 @@ void NodeList::sendDomainServerCheckIn() { packetStream << domainAccountManager->getUsername(); if (!domainAccountManager->getAccessToken().isEmpty()) { packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken()); + } } } From fdb4a5605a58a5e4c0d76325e4467e64195da192 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 2 Aug 2020 20:54:09 +1200 Subject: [PATCH 31/60] Tidy processing user connect request --- domain-server/src/DomainGatekeeper.cpp | 31 ++++++++++++++------------ domain-server/src/DomainGatekeeper.h | 5 +++-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 29667b0fd5e..ee8da95d6c4 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -517,20 +517,15 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect #endif return SharedNodePointer(); - } else if (!_verifiedDomainUserIdentities.contains(domainUsername) - || _verifiedDomainUserIdentities[domainUsername] != QPair(domainAccessToken, domainRefreshToken)) { - // ####### TODO: Write a function for the above test. + } else if (needToVerifyDomainUserIdentity(domainUsername, domainAccessToken, domainRefreshToken)) { // User's domain identity needs to be confirmed. - if (_verifiedDomainUserIdentities.contains(domainUsername)) { - _verifiedDomainUserIdentities.remove(domainUsername); - } requestDomainUser(domainUsername, domainAccessToken, domainRefreshToken); #ifdef WANT_DEBUG qDebug() << "Stalling login because we haven't authenticated user yet:" << domainUsername; #endif - } else if (verifyDomainUserSignature(domainUsername, domainAccessToken, domainRefreshToken, - nodeConnection.senderSockAddr)) { + } else if (verifyDomainUserIdentity(domainUsername, domainAccessToken, domainRefreshToken, + nodeConnection.senderSockAddr)) { // User's domain identity is confirmed. getDomainGroupMemberships(domainUsername); verifiedDomainUsername = domainUsername.toLower(); @@ -758,13 +753,17 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, return false; } -// ####### TODO: Rename to verifyDomainUser()? -bool DomainGatekeeper::verifyDomainUserSignature(const QString& username, const QString& accessToken, - const QString& refreshToken, const HifiSockAddr& senderSockAddr) { - // ####### TODO: Verify response from domain OAuth2 request to WordPress, if it's arrived yet. - // #### Or assume the verification step has already occurred? - if (_verifiedDomainUserIdentities.contains(username)) { +bool DomainGatekeeper::needToVerifyDomainUserIdentity(const QString& username, const QString& accessToken, + const QString& refreshToken) { + return !_verifiedDomainUserIdentities.contains(username) + || _verifiedDomainUserIdentities.value(username) != QPair(accessToken, refreshToken); +} + +bool DomainGatekeeper::verifyDomainUserIdentity(const QString& username, const QString& accessToken, + const QString& refreshToken, const HifiSockAddr& senderSockAddr) { + if (_verifiedDomainUserIdentities.contains(username) + && _verifiedDomainUserIdentities.value(username) == QPair(accessToken, refreshToken)) { return true; } @@ -1236,6 +1235,10 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& } _inFlightDomainUserIdentityRequests.insert(username, QPair(accessToken, refreshToken)); + if (_verifiedDomainUserIdentities.contains(username)) { + _verifiedDomainUserIdentities.remove(username); + } + QString apiBase = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString(); if (!apiBase.endsWith("/")) { apiBase += "/"; diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index eaf20a6285e..263d5b853d4 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -90,8 +90,9 @@ private slots: bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr); - bool verifyDomainUserSignature(const QString& username, const QString& accessToken, const QString& refreshToken, - const HifiSockAddr& senderSockAddr); + bool needToVerifyDomainUserIdentity(const QString& username, const QString& accessToken, const QString& refreshToken); + bool verifyDomainUserIdentity(const QString& username, const QString& accessToken, const QString& refreshToken, + const HifiSockAddr& senderSockAddr); bool isWithinMaxCapacity(); From dfbad4efc097fd4a9c930034822ea3ac707d5c11 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 2 Aug 2020 21:22:59 +1200 Subject: [PATCH 32/60] Use "enable domain authentication" server setting --- domain-server/resources/describe-settings.json | 2 +- domain-server/src/DomainGatekeeper.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index ba019e3fe7d..25937409696 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -93,7 +93,7 @@ { "name": "wordpress_url_base", "label": "WordPress API URL Base", - "help": "The URL base that the domain server will use to make WordPress API calls. Typically \"http://oursite.com/wp-json/\". However, if using non-pretty permalinks or otherwise get a 404 error then use \"http://oursite.com/?rest_route=/\".", + "help": "The URL base that the domain server will use to make WordPress API calls. Typically \"https://oursite.com/wp-json/\". However, if using non-pretty permalinks or otherwise get a 404 error then try \"https://oursite.com/?rest_route=/\".", "advanced": true } ] diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index ee8da95d6c4..6227ddb6340 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -449,6 +449,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo return newNode; } +const QString AUTHENTICATION_ENAABLED = "authentication.enable_oauth2"; const QString AUTHENTICATION_OAUTH2_URL_BASE = "authentication.oauth2_url_base"; const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; @@ -1216,9 +1217,11 @@ Node::LocalID DomainGatekeeper::findOrCreateLocalID(const QUuid& uuid) { bool DomainGatekeeper::domainHasLogin() { - // The domain may have its own users and groups. This is enabled in the server settings by ... ####### - // ####### TODO: Base on server settings. - return true; + // The domain may have its own users and groups in a WordPress site. + // ####### TODO: Add checks of any further domain server settings used. + return _server->_settingsManager.valueForKeyPath(AUTHENTICATION_ENAABLED).toBool() + && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_BASE).toString().isEmpty() + && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString().isEmpty(); } void DomainGatekeeper::requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken) { From 43b6e77235afe395ac3684419a86064a3710504c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 3 Aug 2020 09:17:12 +1200 Subject: [PATCH 33/60] Tidy networking code --- .../resources/describe-settings.json | 6 +-- domain-server/src/DomainGatekeeper.cpp | 37 ++++++------- domain-server/src/DomainGatekeeper.h | 2 +- .../networking/src/DomainAccountManager.cpp | 54 ++++++++++--------- .../networking/src/DomainAccountManager.h | 4 +- 5 files changed, 54 insertions(+), 49 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 25937409696..5560fdba564 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -85,9 +85,9 @@ "backup": false }, { - "name": "oauth2_url_base", - "label": "Authentication URL Base", - "help": "The URL base that the Interface will use to login via OAuth2.", + "name": "oauth2_url_path", + "label": "Authentication URL", + "help": "The URL that the Interface will use to login via OAuth2.", "advanced": true }, { diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 6227ddb6340..16c7383bf9c 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -450,7 +450,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo } const QString AUTHENTICATION_ENAABLED = "authentication.enable_oauth2"; -const QString AUTHENTICATION_OAUTH2_URL_BASE = "authentication.oauth2_url_base"; +const QString AUTHENTICATION_OAUTH2_URL_PATH = "authentication.oauth2_url_path"; const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location"; @@ -548,10 +548,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { if (domainHasLogin()) { QString domainAuthURL; - auto domainAuthURLVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_BASE); + auto domainAuthURLVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH); if (domainAuthURLVariant.canConvert()) { domainAuthURL = domainAuthURLVariant.toString(); - qDebug() << "Domain authorization URL:" << domainAuthURL; } sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, domainAuthURL); @@ -1220,7 +1219,7 @@ bool DomainGatekeeper::domainHasLogin() { // The domain may have its own users and groups in a WordPress site. // ####### TODO: Add checks of any further domain server settings used. return _server->_settingsManager.valueForKeyPath(AUTHENTICATION_ENAABLED).toBool() - && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_BASE).toString().isEmpty() + && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH).toString().isEmpty() && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString().isEmpty(); } @@ -1233,7 +1232,7 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& } if (_inFlightDomainUserIdentityRequests.contains(username)) { - // Domain identify request for this username is already flight. + // Domain identify request for this username is already in progress. return; } _inFlightDomainUserIdentityRequests.insert(username, QPair(accessToken, refreshToken)); @@ -1248,9 +1247,9 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& } // Get data pertaining to "me", the user who generated the access token. - // ####### TODO: Confirm API_ROUTE w.r.t. OAuth2 plugin's capabilities. - QString API_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; - QUrl domainUserURL = apiBase + API_ROUTE; + // ####### TODO: Confirm API route and data w.r.t. OAuth2 plugin's capabilities. [plugin] + const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; + QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE; // ####### TODO: Append a random key to check in response? @@ -1258,7 +1257,6 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - // ####### TODO: WordPress plugin's authorization requirements. request.setRawHeader(QByteArray("Authorization"), QString("Bearer " + accessToken).toUtf8()); QByteArray formData; // No data to send. @@ -1278,12 +1276,13 @@ void DomainGatekeeper::requestDomainUserFinished() { QNetworkReply* requestReply = reinterpret_cast(sender()); + QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); + const QJsonObject& rootObject = jsonResponse.object(); + auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (200 <= httpStatus && httpStatus < 300) { // Success. - QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); - const QJsonObject& rootObject = jsonResponse.object(); - // ####### Expected response: + // ####### TODO: Verify Expected response. [plugin] /* { id: 2, @@ -1292,22 +1291,24 @@ void DomainGatekeeper::requestDomainUserFinished() { } */ - // ####### TODO: Handle invalid / unexpected response. - QString username = rootObject["username"].toString().toLower(); - // ####### TODO: Handle invalid username or one that isn't in the _inFlight list. - if (_inFlightDomainUserIdentityRequests.contains(username)) { // Success! Verified user. _verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username)); _inFlightDomainUserIdentityRequests.remove(username); } else { - // Unexpected response. - // ####### TODO + // Failure. + qDebug() << "Unexpected username in response for user details -" << username; } + // ####### TODO: Handle roles if available. [plugin] + } else { // Failure. + + // ####### TODO: Error fields to report. [plugin] + qDebug() << "Error in response for user details -" << rootObject["error"].toString(); + // ####### TODO: Is this the best way to handle _inFlightDomainUserIdentityRequests? // If there's a brief network glitch will it recover? // Perhaps clear on a timer? Cancel timer upon subsequent successful responses? diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 263d5b853d4..0e1da5d6ee7 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -155,7 +155,7 @@ private slots: void requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken); typedef QHash> DomainUserIdentities; // > - DomainUserIdentities _inFlightDomainUserIdentityRequests; // Keep track of domain user identity requests in progress. + DomainUserIdentities _inFlightDomainUserIdentityRequests; // Domain user identity requests currently in progress. DomainUserIdentities _verifiedDomainUserIdentities; // Verified domain users. void getDomainGroupMemberships(const QString& domainUserName); diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 7fc02893eba..5cf707e732c 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -60,7 +60,12 @@ void DomainAccountManager::setAuthURL(const QUrl& authURL) { qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); - // ####### TODO: See AccountManager::setAuthURL(). + _access_token = ""; + _refresh_token = ""; + + // ####### TODO: Restore and refresh OAuth2 tokens if have them for this domain. + + // ####### TODO: Handle "keep me logged in". } } @@ -84,9 +89,7 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt formData.append("scope=" + DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE); // ####### TODO: Include state? - QUrl domainAuthURL = _authURL; - domainAuthURL.setPath("/token"); // ####### TODO: miniOrange-mandated URL. ####### TODO: Should this be included in the server settings value? - request.setUrl(domainAuthURL); + request.setUrl(_authURL); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -96,40 +99,40 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt } void DomainAccountManager::requestAccessTokenFinished() { + QNetworkReply* requestReply = reinterpret_cast(sender()); QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); const QJsonObject& rootObject = jsonResponse.object(); - // ####### TODO: Test HTTP response codes rather than object contains "error". - // #### reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200 - if (!rootObject.contains("error")) { - // ####### TODO: Process response scope? - // ####### TODO: Process response state? - // ####### TODO: Check that token type == "Bearer"? - - if (!rootObject.contains("access_token") - // ####### TODO: Does WordPRess plugin provide "expires_in"? - // If so, handle here, or is it just the domain server that needs to use it? - //|| !rootObject.contains("expires_in") - || !rootObject.contains("token_type")) { + auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (200 <= httpStatus && httpStatus < 300) { - qCDebug(networking) << "Received a response for password grant that is missing one or more expected values."; - emit loginFailed(); + // ####### TODO: Check that token type == "Bearer"? + // ####### TODO: Process response state? + // ####### TODO: Process response scope? - } else { + if (rootObject.contains("access_token")) { + // Success. - // 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); + setTokensFromJSON(rootObject, rootURL); - emit loginComplete(rootURL); + emit loginComplete(); + + } else { + // Failure. + qCDebug(networking) << "Received a response for password grant that is missing one or more expected values."; + emit loginFailed(); } + } else { - qCDebug(networking) << "Error in response for password grant -" << rootObject["error_description"].toString(); + // Failure. + + // ####### TODO: Error object fields to report. [plugin] + qCDebug(networking) << "Error in response for password grant -" << rootObject["error"].toString(); emit loginFailed(); } } @@ -171,13 +174,14 @@ bool DomainAccountManager::hasValidAccessToken() { } -void DomainAccountManager::setAccessTokenFromJSON(const QJsonObject& jsonObject) { +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? /* + 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)); diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 08b625a2464..6578963bf8e 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -37,7 +37,7 @@ public slots: void requestAccessTokenFinished(); signals: void authRequired(); - void loginComplete(const QUrl& authURL); + void loginComplete(); void loginFailed(); void logoutComplete(); void newTokens(); @@ -47,7 +47,7 @@ private slots: private: bool hasValidAccessToken(); bool accessTokenIsExpired(); - void setAccessTokenFromJSON(const QJsonObject&); + void setTokensFromJSON(const QJsonObject&, const QUrl& url); void sendInterfaceAccessTokenToServer(); QUrl _authURL; From 26e6ce07d7068973d192300e9e8b5f74407fcc44 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 3 Aug 2020 11:26:11 +1200 Subject: [PATCH 34/60] Further networking tidying --- domain-server/src/DomainGatekeeper.cpp | 17 +++++------ .../networking/src/DomainAccountManager.cpp | 29 +++++++------------ 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 16c7383bf9c..25f38d6ebd8 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -1101,11 +1101,11 @@ void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply* requestReply void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) { - // ####### TODO: Get user's domain group memberships (WordPress roles) from domain. + // ####### TODO: Get user's domain group memberships (WordPress roles) from domain. [plugin groups] // This may be able to be provided at the same time as the "authenticate user" call to the domain API, in which case // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. - // ####### TODO: Check how often this method and the WordPress API is called. + // ####### TODO: Check how often this method and the WordPress API is called. [plugin groups] QStringList wordpressGroupsForUser; wordpressGroupsForUser << "silVER" << "gold" << "coal"; @@ -1161,7 +1161,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply* req qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply->error(); } -// ####### TODO: Domain equivalent or addition +// ####### TODO: Domain equivalent or addition [plugin groups] void DomainGatekeeper::refreshGroupsCache() { // if agents are connected to this domain, refresh our cached information about groups and memberships in such. getDomainOwnerFriendsList(); @@ -1251,7 +1251,7 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE; - // ####### TODO: Append a random key to check in response? + // ####### TODO: Append a random key to check in response? [security] QNetworkRequest request; @@ -1265,8 +1265,6 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - // ####### TODO: Handle invalid URL (e.g., set timeout or similar). - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply* requestReply = networkAccessManager.post(request, formData); connect(requestReply, &QNetworkReply::finished, this, &DomainGatekeeper::requestDomainUserFinished); @@ -1280,6 +1278,7 @@ void DomainGatekeeper::requestDomainUserFinished() { const QJsonObject& rootObject = jsonResponse.object(); auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (200 <= httpStatus && httpStatus < 300) { // Success. // ####### TODO: Verify Expected response. [plugin] @@ -1307,11 +1306,9 @@ void DomainGatekeeper::requestDomainUserFinished() { // Failure. // ####### TODO: Error fields to report. [plugin] - qDebug() << "Error in response for user details -" << rootObject["error"].toString(); + qDebug() << "Error in response for user details -" << httpStatus << requestReply->error() + << "-" << rootObject["error"].toString(); - // ####### TODO: Is this the best way to handle _inFlightDomainUserIdentityRequests? - // If there's a brief network glitch will it recover? - // Perhaps clear on a timer? Cancel timer upon subsequent successful responses? _inFlightDomainUserIdentityRequests.clear(); } } diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 5cf707e732c..cb67d30d3ef 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -11,31 +11,23 @@ #include "DomainAccountManager.h" -// ####### TODO: Check that all #includes are still needed. - -#include - #include -#include #include #include #include #include -#include -#include "DomainAccountManager.h" +#include + #include "NetworkingConstants.h" -#include "OAuthAccessToken.h" #include "NetworkLogging.h" -#include "NodeList.h" -#include "udt/PacketHeaders.h" #include "NetworkAccessManager.h" // FIXME: Generalize to other OAuth2 sources for domain login. const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; -const QString DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE = "foo bar"; // ####### TODO: WordPress plugin's required scope. -// ####### TODO: Should scope be configured in domain server settings? +const QString DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE = "foo bar"; // ####### TODO: WordPress plugin's required scope. [plugin] +// ####### TODO: Should scope be configured in domain server settings? [plugin] // ####### TODO: Add storing domain URL and check against it when retrieving values? // ####### TODO: Add storing _authURL and check against it when retrieving values? @@ -79,7 +71,7 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - // ####### TODO: WordPress plugin's authorization requirements. + // ####### TODO: WordPress plugin's authorization requirements. [plugin] request.setRawHeader(QByteArray("Authorization"), QByteArray("Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x")); QByteArray formData; @@ -87,7 +79,7 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt formData.append("username=" + QUrl::toPercentEncoding(username) + "&"); formData.append("password=" + QUrl::toPercentEncoding(password) + "&"); formData.append("scope=" + DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE); - // ####### TODO: Include state? + // ####### TODO: Include state? [plugin] request.setUrl(_authURL); @@ -108,10 +100,10 @@ void DomainAccountManager::requestAccessTokenFinished() { auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (200 <= httpStatus && httpStatus < 300) { - // ####### TODO: Check that token type == "Bearer"? - // ####### TODO: Process response state? - // ####### TODO: Process response scope? + // ####### TODO: Process response state? [plugin] + // ####### TODO: Process response scope? [plugin] + // ####### TODO: Which method are the tokens provided in? [plugin] if (rootObject.contains("access_token")) { // Success. @@ -132,7 +124,8 @@ void DomainAccountManager::requestAccessTokenFinished() { // Failure. // ####### TODO: Error object fields to report. [plugin] - qCDebug(networking) << "Error in response for password grant -" << rootObject["error"].toString(); + qCDebug(networking) << "Error in response for password grant -" << httpStatus << requestReply->error() + << "_" << rootObject["error"].toString(); emit loginFailed(); } } From 0b42ef57489579f05cbcef4a6c2aed6c9ac6f7fc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 3 Aug 2020 13:21:20 +1200 Subject: [PATCH 35/60] Regularize domain username case --- domain-server/src/DomainGatekeeper.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 25f38d6ebd8..4b0eaab6622 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -109,6 +109,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetBytesLeftToRead() > 0) { // Read domain username from packet. packetStream >> domainUsername; + domainUsername = domainUsername.toLower(); // Domain usernames are case-insensitive; internally lower-case. if (message->getBytesLeftToRead() > 0) { // Read domain tokens from packet. @@ -528,8 +529,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect } else if (verifyDomainUserIdentity(domainUsername, domainAccessToken, domainRefreshToken, nodeConnection.senderSockAddr)) { // User's domain identity is confirmed. - getDomainGroupMemberships(domainUsername); - verifiedDomainUsername = domainUsername.toLower(); + verifiedDomainUsername = domainUsername; + getDomainGroupMemberships(verifiedDomainUsername); } else { // User's domain identity didn't check out. From 257eadc99f84b97fe6a6cc4cfc1db0b0011cdec1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 3 Aug 2020 16:12:13 +1200 Subject: [PATCH 36/60] "Unable connect to this domain" message box after login if can't connect --- domain-server/src/DomainGatekeeper.cpp | 4 ++-- interface/src/ConnectionMonitor.cpp | 6 ++++++ libraries/networking/src/AccountManager.cpp | 4 +++- libraries/networking/src/DomainHandler.cpp | 4 +++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 4b0eaab6622..8d0bc17f00e 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -553,10 +553,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (domainAuthURLVariant.canConvert()) { domainAuthURL = domainAuthURLVariant.toString(); } - sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", + sendConnectionDeniedPacket("You lack the required domain permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, domainAuthURL); } else { - sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", + sendConnectionDeniedPacket("You lack the required metaverse permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedMetaverse); } #ifdef WANT_DEBUG diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 33d9fddc1b3..070015f05bb 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -14,8 +14,10 @@ #include "Application.h" #include "ui/DialogsManager.h" +#include #include #include +#include #include #include @@ -34,6 +36,10 @@ void ConnectionMonitor::init() { connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer); connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer); connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState); + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::loginComplete, this, &ConnectionMonitor::startTimer); + auto domainAccountManager = DependencyManager::get(); + connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, &ConnectionMonitor::startTimer); _timer.setSingleShot(true); if (!domainHandler.isConnected()) { diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 83c0fd28dd9..5589defd809 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -508,7 +508,9 @@ bool AccountManager::checkAndSignalForAccessToken() { if (!hasToken) { // emit a signal so somebody can call back to us and request an access token given a username and password - emit authRequired(); + + // Dialog can be hidden immediately after showing if we've just teleported to the domain, unless the signal is delayed. + QTimer::singleShot(500, this, [this] { emit this->authRequired(); }); } return hasToken; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 0a61036b961..7049f2aef62 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -550,7 +550,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer Date: Tue, 4 Aug 2020 09:15:05 +1200 Subject: [PATCH 37/60] Misc. tidying --- domain-server/src/DomainGatekeeper.cpp | 8 +------- domain-server/src/DomainServerSettingsManager.cpp | 2 +- interface/src/Application.cpp | 1 - 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 8d0bc17f00e..9019a530d77 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -1218,7 +1218,7 @@ Node::LocalID DomainGatekeeper::findOrCreateLocalID(const QUuid& uuid) { bool DomainGatekeeper::domainHasLogin() { // The domain may have its own users and groups in a WordPress site. - // ####### TODO: Add checks of any further domain server settings used. + // ####### TODO: Add checks of any further domain server settings used. [plugin, groups] return _server->_settingsManager.valueForKeyPath(AUTHENTICATION_ENAABLED).toBool() && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH).toString().isEmpty() && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString().isEmpty(); @@ -1226,12 +1226,6 @@ bool DomainGatekeeper::domainHasLogin() { void DomainGatekeeper::requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken) { - // ####### TODO: Move this further up the chain such that generates "invalid username or password" condition? - // Don't request identity for the standard psuedo-account-names. - if (NodePermissions::standardNames.contains(username, Qt::CaseInsensitive)) { - return; - } - if (_inFlightDomainUserIdentityRequests.contains(username)) { // Domain identify request for this username is already in progress. return; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 30ca15a51e2..82ac0b265ad 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -2186,7 +2186,7 @@ QList DomainServerSettingsManager::getBlacklistGroupIDs() { } QStringList DomainServerSettingsManager::getDomainGroupNames() { - // Names as configured in domain server; not necessarily mnetaverse groups. + // Names as configured in domain server; not necessarily metaverse groups. QSet result; foreach(NodePermissionsKey groupKey, _groupPermissions.keys()) { result += _groupPermissions[groupKey]->getID(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 72e6af74ec0..2c6702fbfc6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -9444,7 +9444,6 @@ void Application::forceDisplayName(const QString& displayName) { getMyAvatar()->setDisplayName(displayName); } void Application::forceLoginWithTokens(const QString& tokens) { - // ####### TODO DependencyManager::get()->setAccessTokens(tokens); Setting::Handle(KEEP_ME_LOGGED_IN_SETTING_NAME, true).set(true); } From bc56eb5ac75b5f677c01faa72e5f39f325f4f477 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2020 17:12:31 +1200 Subject: [PATCH 38/60] Update to work with WordPress plugin --- .../resources/describe-settings.json | 14 ++++---- domain-server/src/DomainGatekeeper.cpp | 36 +++++++++---------- .../networking/src/DomainAccountManager.cpp | 26 +++++--------- .../networking/src/DomainAccountManager.h | 2 ++ libraries/networking/src/DomainHandler.cpp | 4 ++- 5 files changed, 36 insertions(+), 46 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 5560fdba564..c9017722966 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -77,13 +77,6 @@ "type": "checkbox", "advanced": true }, - { - "name": "domain_access_token", - "label": "Domain API Access Token", - "help": "This is the access token that your domain-server will use to verify users and their roles. This token must grant access to that permission set on your REST API server.", - "advanced": true, - "backup": false - }, { "name": "oauth2_url_path", "label": "Authentication URL", @@ -95,6 +88,13 @@ "label": "WordPress API URL Base", "help": "The URL base that the domain server will use to make WordPress API calls. Typically \"https://oursite.com/wp-json/\". However, if using non-pretty permalinks or otherwise get a 404 error then try \"https://oursite.com/?rest_route=/\".", "advanced": true + }, + { + "name": "plugin_client_id", + "label": "WordPress Plugin Client ID", + "help": "This is the client ID from the WordPress plugin configuration.", + "advanced": true, + "backup": false } ] }, diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 9019a530d77..ad637f3c175 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -453,6 +453,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo const QString AUTHENTICATION_ENAABLED = "authentication.enable_oauth2"; const QString AUTHENTICATION_OAUTH2_URL_PATH = "authentication.oauth2_url_path"; const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base"; +const QString AUTHENTICATION_PLUGIN_CLIENT_ID = "authentication.plugin_client_id"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location"; @@ -553,8 +554,15 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (domainAuthURLVariant.canConvert()) { domainAuthURL = domainAuthURLVariant.toString(); } + QString domainAuthClientID; + auto domainAuthClientIDVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_PLUGIN_CLIENT_ID); + if (domainAuthClientIDVariant.canConvert()) { + domainAuthClientID = domainAuthClientIDVariant.toString(); + } + sendConnectionDeniedPacket("You lack the required domain permissions to connect to this domain.", - nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, domainAuthURL); + nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, + domainAuthURL + "|" + domainAuthClientID); } else { sendConnectionDeniedPacket("You lack the required metaverse permissions to connect to this domain.", nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedMetaverse); @@ -1242,11 +1250,9 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString& } // Get data pertaining to "me", the user who generated the access token. - // ####### TODO: Confirm API route and data w.r.t. OAuth2 plugin's capabilities. [plugin] - const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; - QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE; - - // ####### TODO: Append a random key to check in response? [security] + const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me"; + const QString WORDPRESS_USER_QUERY = "_fields=username,roles"; + QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE + (apiBase.contains("?") ? "&" : "?") + WORDPRESS_USER_QUERY; QNetworkRequest request; @@ -1275,34 +1281,24 @@ void DomainGatekeeper::requestDomainUserFinished() { auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (200 <= httpStatus && httpStatus < 300) { - // Success. - // ####### TODO: Verify Expected response. [plugin] - /* - { - id: 2, - username : 'apiuser', - roles : ['subscriber'] , - } - */ QString username = rootObject["username"].toString().toLower(); if (_inFlightDomainUserIdentityRequests.contains(username)) { // Success! Verified user. _verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username)); _inFlightDomainUserIdentityRequests.remove(username); + + // ####### TODO: Handle roles. + } else { // Failure. qDebug() << "Unexpected username in response for user details -" << username; } - // ####### TODO: Handle roles if available. [plugin] - } else { // Failure. - - // ####### TODO: Error fields to report. [plugin] qDebug() << "Error in response for user details -" << httpStatus << requestReply->error() - << "-" << rootObject["error"].toString(); + << "-" << rootObject["error"].toString() << rootObject["error_description"].toString(); _inFlightDomainUserIdentityRequests.clear(); } diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index cb67d30d3ef..9910252317b 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -26,8 +26,6 @@ // FIXME: Generalize to other OAuth2 sources for domain login. const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; -const QString DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE = "foo bar"; // ####### TODO: WordPress plugin's required scope. [plugin] -// ####### TODO: Should scope be configured in domain server settings? [plugin] // ####### TODO: Add storing domain URL and check against it when retrieving values? // ####### TODO: Add storing _authURL and check against it when retrieving values? @@ -71,15 +69,15 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - // ####### TODO: WordPress plugin's authorization requirements. [plugin] - request.setRawHeader(QByteArray("Authorization"), QByteArray("Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x")); + // miniOrange WordPress API Authentication plugin: + // - Requires "client_id" parameter. + // - Ignores "state" parameter. QByteArray formData; formData.append("grant_type=password&"); formData.append("username=" + QUrl::toPercentEncoding(username) + "&"); formData.append("password=" + QUrl::toPercentEncoding(password) + "&"); - formData.append("scope=" + DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE); - // ####### TODO: Include state? [plugin] + formData.append("client_id=" + _clientID); request.setUrl(_authURL); @@ -100,20 +98,13 @@ void DomainAccountManager::requestAccessTokenFinished() { auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (200 <= httpStatus && httpStatus < 300) { - // ####### TODO: Process response state? [plugin] - // ####### TODO: Process response scope? [plugin] - - // ####### TODO: Which method are the tokens provided in? [plugin] + // miniOrange plugin provides no scope. if (rootObject.contains("access_token")) { // Success. - QUrl rootURL = requestReply->url(); rootURL.setPath(""); - setTokensFromJSON(rootObject, rootURL); - emit loginComplete(); - } else { // Failure. qCDebug(networking) << "Received a response for password grant that is missing one or more expected values."; @@ -122,10 +113,8 @@ void DomainAccountManager::requestAccessTokenFinished() { } else { // Failure. - - // ####### TODO: Error object fields to report. [plugin] - qCDebug(networking) << "Error in response for password grant -" << httpStatus << requestReply->error() - << "_" << rootObject["error"].toString(); + qCDebug(networking) << "Error in response for password grant -" << httpStatus << requestReply->error() + << "-" << rootObject["error"].toString() << rootObject["error_description"].toString(); emit loginFailed(); } } @@ -173,6 +162,7 @@ void DomainAccountManager::setTokensFromJSON(const QJsonObject& jsonObject, cons // ####### 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()); diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 6578963bf8e..213d40a8d85 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -24,6 +24,7 @@ class DomainAccountManager : public QObject, public Dependency { DomainAccountManager(); void setAuthURL(const QUrl& authURL); + void setClientID(const QString& clientID) { _clientID = clientID; } QString getUsername() { return _username; } QString getAccessToken() { return _access_token; } @@ -51,6 +52,7 @@ private slots: void sendInterfaceAccessTokenToServer(); QUrl _authURL; + QString _clientID; QString _username; // ####### TODO: Store elsewhere? QString _access_token; // ####... "" QString _refresh_token; // ####... "" diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 7049f2aef62..c1a24748f46 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -589,7 +589,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); if (!extraInfo.isEmpty()) { - accountManager->setAuthURL(extraInfo); + auto extraInfoComponents = extraInfo.split("|"); + accountManager->setAuthURL(extraInfoComponents.value(0)); + accountManager->setClientID(extraInfoComponents.value(1)); } if (!_hasCheckedForDomainAccessToken) { From 482922d98673a4f73558c14e168321d5222df336 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 4 Aug 2020 01:28:19 -0400 Subject: [PATCH 39/60] First pass at making login dialog more clear. --- .../qml/LoginDialog/LinkAccountBody.qml | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 6f437bb9919..ec3f0d263e9 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -45,6 +45,7 @@ Item { property bool lostFocus: false readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + // If not logging into domain, then we must be logging into the metaverse... readonly property bool isLoggingInToDomain: loginDialog.getDomainLoginRequested() readonly property string domainAuthProvider: loginDialog.getDomainLoginAuthProvider() @@ -105,8 +106,9 @@ Item { loginErrorMessage.wrapMode = Text.WordWrap; errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height; } + loginDialogText.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : "Log In to Domain"; loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; - loginButton.text = (!isLoggingInToDomain) ? "Log In" : "Log In to Domain"; + loginButton.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : "Log In to Domain"; loginButton.color = hifi.buttons.blue; displayNameField.placeholderText = "Display Name (optional)"; var savedDisplayName = Settings.getValue("Avatar/displayName", ""); @@ -140,6 +142,21 @@ Item { visible: false; anchors.fill: parent; } + + Text { + id: loginDialogText + text: qsTr("Log In") + anchors { + left: parent.left + } + lineHeight: 1 + color: "white" + font.family: linkAccountBody.fontFamily + font.pixelSize: linkAccountBody.textFieldFontSize + font.bold: linkAccountBody.fontBold + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } Item { id: loginContainer @@ -626,6 +643,24 @@ Item { root.tryDestroy(); } } + + Text { + id: loginSkipTipText + text: qsTr("Not all domains require you to have a metaverse account. \n Some domains have their own login dialogs.") + visible: !linkAccountBody.isLoggingInToDomain + anchors { + top: dismissButton.bottom + topMargin: hifi.dimensions.contentSpacing.y + left: parent.left + } + lineHeight: 1 + color: "white" + font.family: linkAccountBody.fontFamily + font.pixelSize: linkAccountBody.textFieldFontSize + font.bold: linkAccountBody.fontBold + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } } } From 3a43283e7b1d03545c85b7bf671f1fe9bc64a635 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2020 19:42:02 +1200 Subject: [PATCH 40/60] Provide domain name to domain login dialog --- .../resources/qml/LoginDialog/LinkAccountBody.qml | 1 + interface/src/ui/DialogsManager.cpp | 14 ++++++++++---- interface/src/ui/DialogsManager.h | 5 ++++- interface/src/ui/LoginDialog.cpp | 4 ++++ interface/src/ui/LoginDialog.h | 1 + libraries/networking/src/DomainAccountManager.cpp | 5 ++++- libraries/networking/src/DomainAccountManager.h | 3 ++- 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 3d715d1a397..810a8a37dd7 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -46,6 +46,7 @@ Item { readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() readonly property bool isLoggingInToDomain: loginDialog.getDomainLoginRequested() + readonly property string domainLoginDomain: loginDialog.getDomainLoginDomain() QtObject { id: d diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 848663967f5..6e807c7b9f5 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -110,8 +110,14 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { } } + +void DialogsManager::setDomainLogin(bool isDomainLogin, const QString& domain) { + _isDomainLogin = isDomainLogin; + _domainLoginDomain = domain; +} + void DialogsManager::toggleLoginDialog() { - _isDomainLogin = false; + setDomainLogin(false); LoginDialog::toggleAction(); } @@ -120,7 +126,7 @@ void DialogsManager::showLoginDialog() { // ####### TODO: May be called from script via DialogsManagerScriptingInterface. Need to handle the case that it's already // displayed and may be the domain login version. - _isDomainLogin = false; + setDomainLogin(false); LoginDialog::showWithSelection(); } @@ -129,8 +135,8 @@ void DialogsManager::hideLoginDialog() { } -void DialogsManager::showDomainLoginDialog() { - _isDomainLogin = true; +void DialogsManager::showDomainLoginDialog(const QString& domain) { + setDomainLogin(true, domain); LoginDialog::showWithSelection(); } diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 30127ced688..fa5c589fb41 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -42,6 +42,7 @@ class DialogsManager : public QObject, public Dependency { void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } void setAddressBarVisible(bool addressBarVisible); bool getIsDomainLogin() { return _isDomainLogin; } + QString getDomainLoginDomain() { return _domainLoginDomain; } public slots: void showAddressBar(); @@ -51,7 +52,7 @@ public slots: void toggleLoginDialog(); void showLoginDialog(); void hideLoginDialog(); - void showDomainLoginDialog(); + void showDomainLoginDialog(const QString& domain); void octreeStatsDetails(); void lodTools(); void hmdTools(bool showTools); @@ -86,7 +87,9 @@ private slots: bool _dialogCreatedWhileShown { false }; bool _addressBarVisible { false }; + void setDomainLogin(bool isDomainLogin, const QString& domain = ""); bool _isDomainLogin { false }; + QString _domainLoginDomain; }; #endif // hifi_DialogsManager_h diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 3cc37bcadb1..c7597c5658c 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -427,3 +427,7 @@ void LoginDialog::signupFailed(QNetworkReply* reply) { bool LoginDialog::getDomainLoginRequested() const { return DependencyManager::get()->getIsDomainLogin(); } + +QString LoginDialog::getDomainLoginDomain() const { + return DependencyManager::get()->getDomainLoginDomain(); +} diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 310a5db2551..9f4af5debbe 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -85,6 +85,7 @@ protected slots: Q_INVOKABLE bool getLoginDialogPoppedUp() const; Q_INVOKABLE bool getDomainLoginRequested() const; + Q_INVOKABLE QString getDomainLoginDomain() const; }; diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 9910252317b..6bb868df61d 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -184,7 +184,10 @@ 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. - QTimer::singleShot(500, this, [this] { emit this->authRequired(); }); + auto domain = _authURL.host(); + QTimer::singleShot(500, this, [this, domain] { + emit this->authRequired(domain); + }); } return hasToken; diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 213d40a8d85..0388ba1f5aa 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -36,8 +36,9 @@ public slots: void requestAccessToken(const QString& username, const QString& password); void requestAccessTokenFinished(); + signals: - void authRequired(); + void authRequired(const QString& domain); void loginComplete(); void loginFailed(); void logoutComplete(); From 60048162c086911dce0f09e267d63b61e85f44ca Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2020 21:00:09 +1200 Subject: [PATCH 41/60] Use WordPress user roles as domain groups --- domain-server/src/DomainGatekeeper.cpp | 25 ++++++++----------------- domain-server/src/DomainGatekeeper.h | 1 - 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index ad637f3c175..90a6ee9f5f2 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -531,7 +531,6 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect nodeConnection.senderSockAddr)) { // User's domain identity is confirmed. verifiedDomainUsername = domainUsername; - getDomainGroupMemberships(verifiedDomainUsername); } else { // User's domain identity didn't check out. @@ -1108,20 +1107,6 @@ void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply* requestReply } -void DomainGatekeeper::getDomainGroupMemberships(const QString& domainUserName) { - - // ####### TODO: Get user's domain group memberships (WordPress roles) from domain. [plugin groups] - // This may be able to be provided at the same time as the "authenticate user" call to the domain API, in which case - // a copy of some of the following code can be made there. However, this code is still needed for refreshing groups. - - // ####### TODO: Check how often this method and the WordPress API is called. [plugin groups] - - QStringList wordpressGroupsForUser; - wordpressGroupsForUser << "silVER" << "gold" << "coal"; - _domainGroupMemberships[domainUserName] = wordpressGroupsForUser; -} - - void DomainGatekeeper::getDomainOwnerFriendsList() { JSONCallbackParameters callbackParams; callbackParams.callbackReceiver = this; @@ -1282,13 +1267,19 @@ void DomainGatekeeper::requestDomainUserFinished() { if (200 <= httpStatus && httpStatus < 300) { - QString username = rootObject["username"].toString().toLower(); + QString username = rootObject.value("username").toString().toLower(); if (_inFlightDomainUserIdentityRequests.contains(username)) { // Success! Verified user. _verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username)); _inFlightDomainUserIdentityRequests.remove(username); - // ####### TODO: Handle roles. + // User user's WordPress roles as domain groups. + QStringList domainUserGroups; + auto userRoles = rootObject.value("roles").toArray(); + foreach (auto role, userRoles) { + domainUserGroups.append(role.toString()); + } + _domainGroupMemberships[username] = domainUserGroups; } else { // Failure. diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 0e1da5d6ee7..cf41786e4a5 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -158,7 +158,6 @@ private slots: DomainUserIdentities _inFlightDomainUserIdentityRequests; // Domain user identity requests currently in progress. DomainUserIdentities _verifiedDomainUserIdentities; // Verified domain users. - void getDomainGroupMemberships(const QString& domainUserName); QHash _domainGroupMemberships; // }; From 6f9b47c07d307a34403fa611098eb6be8e48e106 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2020 21:31:13 +1200 Subject: [PATCH 42/60] Distinguish domain groups with a leading "@" --- domain-server/src/DomainGatekeeper.cpp | 7 ++++--- domain-server/src/DomainGatekeeper.h | 2 ++ domain-server/src/DomainServerSettingsManager.cpp | 4 ++++ domain-server/src/DomainServerSettingsManager.h | 6 +++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 90a6ee9f5f2..5b0c526d9f3 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -181,7 +181,7 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; foreach (QString userGroup, userGroups) { // Domain groups may be specified as comma- and/or space-separated lists of group names. - // For example, "silver gold, platinum". + // For example, "@silver @Gold, @platinum". auto domainGroups = _server->_settingsManager.getDomainGroupNames() .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", QRegularExpression::CaseInsensitiveOption)); @@ -302,7 +302,7 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; foreach(QString userGroup, userGroups) { // Domain groups may be specified as comma- and/or space-separated lists of group names. - // For example, "silver gold, platinum". + // For example, "@silver @Gold, @platinum". auto domainGroups = _server->_settingsManager.getDomainBlacklistGroupNames() .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", QRegularExpression::CaseInsensitiveOption)); @@ -1277,7 +1277,8 @@ void DomainGatekeeper::requestDomainUserFinished() { QStringList domainUserGroups; auto userRoles = rootObject.value("roles").toArray(); foreach (auto role, userRoles) { - domainUserGroups.append(role.toString()); + // Distinguish domain groups from metaverse groups by a leading special character. + domainUserGroups.append(DOMAIN_GROUP_CHAR + role.toString().toLower()); } _domainGroupMemberships[username] = domainUserGroups; diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index cf41786e4a5..cb42baa7e39 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -30,6 +30,8 @@ #include "NodeConnectionData.h" #include "PendingAssignedNodeData.h" +const QString DOMAIN_GROUP_CHAR = "@"; + class DomainServer; class DomainGatekeeper : public QObject { diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 82ac0b265ad..c03f26f0a1a 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1966,6 +1966,10 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() { QStringList groupNames = getAllKnownGroupNames(); foreach (QString groupName, groupNames) { QString lowerGroupName = groupName.toLower(); + if (lowerGroupName.contains(DOMAIN_GROUP_CHAR)) { + // Ignore domain groups. + return; + } if (_groupIDs.contains(lowerGroupName)) { // we already know about this one. recall setGroupID in case the group has been // added to another section (the same group is found in both groups and blacklists). diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 8c18c22b32f..73aef07835e 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -19,11 +19,11 @@ #include #include - +#include #include -#include "NodePermissions.h" -#include +#include "DomainGatekeeper.h" +#include "NodePermissions.h" const QString SETTINGS_PATHS_KEY = "paths"; From dfef1fb5bbcb1c5fcc31d3f520f6c16ced524e08 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2020 22:03:08 +1200 Subject: [PATCH 43/60] Remove unused domain server setting --- domain-server/resources/describe-settings.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index c9017722966..8e57a9b6834 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -69,14 +69,6 @@ "type": "checkbox", "advanced": true }, - { - "name": "require_oauth2", - "label": "Require OAuth2 Authentication", - "help": "For any users not explicitly authorized in these settings, notify the Interface to authenticate through this method.", - "default": false, - "type": "checkbox", - "advanced": true - }, { "name": "oauth2_url_path", "label": "Authentication URL", From 09f2153057777e1f5ff2740018cfb70297f6bf2f Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 4 Aug 2020 15:48:10 -0400 Subject: [PATCH 44/60] Update login QML to display domain URL. --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 5 +++-- interface/resources/qml/LoginDialog/LoggingInBody.qml | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 8d869635781..643026c6ff5 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -95,7 +95,7 @@ Item { } bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus, - "displayName":displayNameField.text, "isLoggingInToDomain": linkAccountBody.isLoggingInToDomain }); + "displayName":displayNameField.text, "isLoggingInToDomain": linkAccountBody.isLoggingInToDomain, "domainLoginDomain": linkAccountBody.domainLoginDomain }); } function init() { @@ -106,7 +106,8 @@ Item { loginErrorMessage.wrapMode = Text.WordWrap; errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height; } - loginDialogText.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : "Log In to Domain"; + var domainLoginText = "Log In to Domain: " + domainLoginDomain; + loginDialogText.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : domainLoginText; loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; loginButton.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : "Log In to Domain"; loginButton.color = hifi.buttons.blue; diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index 796798a2de7..757c8b7449e 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -33,6 +33,7 @@ Item { property bool linkOculus: linkOculus property bool createOculus: createOculus property bool isLoggingInToDomain: isLoggingInToDomain + property string domainLoginDomain: domainLoginDomain property string displayName: "" readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -109,7 +110,7 @@ Item { loggingInText.text = "Logging in to Oculus"; loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; } else if (loggingInBody.isLoggingInToDomain) { - loggingInText.text = "Logging in to Domain"; + loggingInText.text = "Logging in to " + domainLoginDomain; loggingInText.anchors.centerIn = loggingInHeader; } else { loggingInText.text = "Logging in"; From ffce0a9d27365537ca7298f91aa75dd2c54b80ca Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 4 Aug 2020 19:12:31 -0400 Subject: [PATCH 45/60] Update QML. --- .../qml/LoginDialog/LinkAccountBody.qml | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 643026c6ff5..6b3fcb17882 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -143,21 +143,6 @@ Item { visible: false; anchors.fill: parent; } - - Text { - id: loginDialogText - text: qsTr("Log In") - anchors { - left: parent.left - } - lineHeight: 1 - color: "white" - font.family: linkAccountBody.fontFamily - font.pixelSize: linkAccountBody.textFieldFontSize - font.bold: linkAccountBody.fontBold - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - } Item { id: loginContainer @@ -176,9 +161,9 @@ Item { width: parent.width height: loginErrorMessageTextMetrics.height anchors { - bottom: displayNameField.top; + bottom: loginDialogText.top; bottomMargin: hifi.dimensions.contentSpacing.y; - left: displayNameField.left; + left: loginDialogText.left; } TextMetrics { id: loginErrorMessageTextMetrics @@ -197,6 +182,23 @@ Item { visible: false } } + + Text { + id: loginDialogText + text: qsTr("Log In") + anchors { + top: parent.top + left: parent.left + topMargin: errorContainer.height + } + lineHeight: 1 + color: "white" + font.family: linkAccountBody.fontFamily + font.pixelSize: linkAccountBody.textFieldFontSize + font.bold: linkAccountBody.fontBold + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } HifiControlsUit.TextField { id: displayNameField @@ -205,8 +207,8 @@ Item { font.pixelSize: linkAccountBody.textFieldFontSize styleRenderType: Text.QtRendering anchors { - top: parent.top - topMargin: errorContainer.height + top: loginDialogText.bottom + topMargin: 1.5 * hifi.dimensions.contentSpacing.y } placeholderText: "Display Name (optional)" activeFocusOnPress: true @@ -370,6 +372,7 @@ Item { labelFontFamily: linkAccountBody.fontFamily labelFontSize: 18; color: hifi.colors.white; + visible: !isLoggingInToDomain anchors { top: passwordField.bottom; topMargin: hifi.dimensions.contentSpacing.y; From 0e8f019b8af0bdcaca6eebd0cc6a1f0978c6c4bc Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 4 Aug 2020 20:00:24 -0400 Subject: [PATCH 46/60] Further things kinda working. --- .../qml/LoginDialog/LinkAccountBody.qml | 1 + interface/src/Application.cpp | 37 +++++++++++++++---- interface/src/ui/LoginDialog.cpp | 4 +- .../networking/src/DomainAccountManager.h | 2 + 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 6b3fcb17882..4f46e392f02 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -651,6 +651,7 @@ Item { Text { id: loginSkipTipText text: qsTr("Not all domains require you to have a metaverse account. \n Some domains have their own login dialogs.") + wrapMode: Text.WordWrap visible: !linkAccountBody.isLoggingInToDomain anchors { top: dismissButton.bottom diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2c6702fbfc6..f2b708793ff 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1355,7 +1355,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo auto domainAccountManager = DependencyManager::get(); connect(domainAccountManager.data(), &DomainAccountManager::authRequired, dialogsManager.data(), &DialogsManager::showDomainLoginDialog); - + + connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, + &Application::updateWindowTitle); // ####### TODO: Connect any other signals from domainAccountManager. @@ -7079,20 +7081,24 @@ void Application::updateWindowTitle() const { auto nodeList = DependencyManager::get(); auto accountManager = DependencyManager::get(); + auto domainAccountManager = DependencyManager::get(); auto isInErrorState = nodeList->getDomainHandler().isInErrorState(); + bool isMetaverseLoggedIn = accountManager->isLoggedIn(); + bool isDomainLoggedIn = domainAccountManager->isLoggedIn(); + qCDebug(interfaceapp) << "Is Logged Into Domain:" << isDomainLoggedIn; QString buildVersion = " - Vircadia - " + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) + " " + applicationVersion(); - // ####### TODO - QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)"; - QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" : nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; - QString username = accountManager->getAccountInfo().getUsername(); - setCrashAnnotation("sentry[user][username]", username.toStdString()); + QString metaverseUsername = accountManager->getAccountInfo().getUsername(); + // ###### TODO + // QString domainUsername = domainAccountManager->getUsername(); + + setCrashAnnotation("sentry[user][metaverseUsername]", metaverseUsername.toStdString()); QString currentPlaceName; if (isServerlessMode()) { @@ -7108,8 +7114,23 @@ void Application::updateWindowTitle() const { } } - QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) - + currentPlaceName + connectionStatus + loginStatus + buildVersion; + QString metaverseDetails; + if (isMetaverseLoggedIn) { + metaverseDetails = "Metaverse: Logged in as " + metaverseUsername; + } else { + metaverseDetails = "Metaverse: Not Logged In"; + } + + QString domainDetails; + if (isDomainLoggedIn) { + // domainDetails = "Domain: Logged in as " + domainUsername; + domainDetails = "Domain: Logged In"; + } else { + domainDetails = "Domain: Not Logged In"; + } + + QString title = QString() + currentPlaceName + connectionStatus + " (" + metaverseDetails + " - " + domainDetails + ")" + + buildVersion; #ifndef WIN32 // crashes with vs2013/win32 diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index c7597c5658c..4f8a3ca2bc5 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -99,11 +99,11 @@ void LoginDialog::toggleAction() { if (accountManager->isLoggedIn()) { // change the menu item to logout - loginAction->setText("Logout " + accountManager->getAccountInfo().getUsername()); + loginAction->setText("Metaverse: Logout " + accountManager->getAccountInfo().getUsername()); connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout); } else { // change the menu item to login - loginAction->setText("Log In / Sign Up"); + loginAction->setText("Metaverse: Log In / Sign Up"); connection = connect(loginAction, &QAction::triggered, [] { // if not in login state, show. if (!qApp->getLoginDialogPoppedUp()) { diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 0388ba1f5aa..1590e4d4673 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -30,6 +30,8 @@ class DomainAccountManager : public QObject, public Dependency { QString getAccessToken() { return _access_token; } QString getRefreshToken() { return _refresh_token; } + bool isLoggedIn() { return hasValidAccessToken(); } + Q_INVOKABLE bool checkAndSignalForAccessToken(); public slots: From 352a3f4ab1250814eb0970b886796d8f50a164ca Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 4 Aug 2020 21:17:57 -0400 Subject: [PATCH 47/60] Fix domain vs metaverse logins slightly. --- interface/src/Application.cpp | 3 ++- interface/src/ui/DialogsManager.cpp | 3 +++ interface/src/ui/DialogsManager.h | 1 + interface/src/ui/LoginDialog.cpp | 2 ++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f2b708793ff..4a7c788d30c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7123,13 +7123,14 @@ void Application::updateWindowTitle() const { QString domainDetails; if (isDomainLoggedIn) { + // ###### TODO // domainDetails = "Domain: Logged in as " + domainUsername; domainDetails = "Domain: Logged In"; } else { domainDetails = "Domain: Not Logged In"; } - QString title = QString() + currentPlaceName + connectionStatus + " (" + metaverseDetails + " - " + domainDetails + ")" + QString title = QString() + currentPlaceName + connectionStatus + " (" + metaverseDetails + ") (" + domainDetails + ")" + buildVersion; #ifndef WIN32 diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 6e807c7b9f5..3aaed2d3ecb 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -110,6 +110,9 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { } } +void DialogsManager::requestMetaverseLogin() { + DialogsManager::setDomainLogin(false); +} void DialogsManager::setDomainLogin(bool isDomainLogin, const QString& domain) { _isDomainLogin = isDomainLogin; diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index fa5c589fb41..07d9de82b09 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -41,6 +41,7 @@ class DialogsManager : public QObject, public Dependency { QPointer getTestingDialog() const { return _testingDialog; } void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } void setAddressBarVisible(bool addressBarVisible); + void requestMetaverseLogin(); bool getIsDomainLogin() { return _isDomainLogin; } QString getDomainLoginDomain() { return _domainLoginDomain; } diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 4f8a3ca2bc5..46e31070b8d 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -107,6 +107,8 @@ void LoginDialog::toggleAction() { connection = connect(loginAction, &QAction::triggered, [] { // if not in login state, show. if (!qApp->getLoginDialogPoppedUp()) { + auto dialogsManager = DependencyManager::get(); + dialogsManager->requestMetaverseLogin(); LoginDialog::showWithSelection(); } }); From 8f55e13aa2faad823b5f8a91b36c15a8cc207754 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 4 Aug 2020 23:14:40 -0400 Subject: [PATCH 48/60] Disable login screen for all first-time users. --- interface/src/Application.cpp | 2 +- interface/src/Application.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4a7c788d30c..761b2ae7173 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1584,7 +1584,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Do not show login dialog if requested not to on the command line QString hifiNoLoginCommandLineKey = QString("--").append(HIFI_NO_LOGIN_COMMAND_LINE_KEY); int index = arguments().indexOf(hifiNoLoginCommandLineKey); - if (index != -1) { + if (index != -1 || _disableLoginScreen) { resumeAfterLoginDialogActionTaken(); return; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 3d4e6873a8b..f42696cda00 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -733,6 +733,7 @@ private slots: GraphicsEngine _graphicsEngine; void updateRenderArgs(float deltaTime); + bool _disableLoginScreen { true }; Overlays _overlays; ApplicationOverlay _applicationOverlay; From d81ec3a4b8256b4160ada2c4dd84d62021e53672 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 4 Aug 2020 23:46:39 -0400 Subject: [PATCH 49/60] Domain login display working correctly. --- .../qml/LoginDialog/LinkAccountBody.qml | 65 +++++++++---------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 4f46e392f02..6ba8c1435ce 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -106,15 +106,15 @@ Item { loginErrorMessage.wrapMode = Text.WordWrap; errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height; } - var domainLoginText = "Log In to Domain: " + domainLoginDomain; - loginDialogText.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : domainLoginText; + var domainLoginText = "Log In to Domain\n" + domainLoginDomain; + loginDialogText.text = (!isLoggingInToDomain) ? "Log In to Metaverse (Not Required)" : domainLoginText; loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; loginButton.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : "Log In to Domain"; loginButton.color = hifi.buttons.blue; displayNameField.placeholderText = "Display Name (optional)"; var savedDisplayName = Settings.getValue("Avatar/displayName", ""); displayNameField.text = savedDisplayName; - emailField.placeholderText = "Username or Email"; + emailField.placeholderText = (!isLoggingInToDomain) ? "Username or Email" : "Username"; if (!isLoggingInToDomain) { var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : ""; @@ -161,9 +161,9 @@ Item { width: parent.width height: loginErrorMessageTextMetrics.height anchors { - bottom: loginDialogText.top; + bottom: loginDialogTextContainer.top; bottomMargin: hifi.dimensions.contentSpacing.y; - left: loginDialogText.left; + left: loginDialogTextContainer.left; } TextMetrics { id: loginErrorMessageTextMetrics @@ -183,21 +183,33 @@ Item { } } - Text { - id: loginDialogText - text: qsTr("Log In") + Item { + id: loginDialogTextContainer + height: 56 anchors { top: parent.top left: parent.left - topMargin: errorContainer.height + right: parent.right; + topMargin: 1.5 * hifi.dimensions.contentSpacing.y + // horizontalCenter: mainContainer.horizontalCenter + } + + Text { + id: loginDialogText + text: qsTr("Log In") + lineHeight: 1 + color: "white" + anchors { + top: parent.top + left: parent.left + right: parent.right + } + font.family: linkAccountBody.fontFamily + font.pixelSize: 24 + font.bold: linkAccountBody.fontBold + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter } - lineHeight: 1 - color: "white" - font.family: linkAccountBody.fontFamily - font.pixelSize: linkAccountBody.textFieldFontSize - font.bold: linkAccountBody.fontBold - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter } HifiControlsUit.TextField { @@ -207,7 +219,7 @@ Item { font.pixelSize: linkAccountBody.textFieldFontSize styleRenderType: Text.QtRendering anchors { - top: loginDialogText.bottom + top: loginDialogTextContainer.bottom topMargin: 1.5 * hifi.dimensions.contentSpacing.y } placeholderText: "Display Name (optional)" @@ -647,25 +659,6 @@ Item { root.tryDestroy(); } } - - Text { - id: loginSkipTipText - text: qsTr("Not all domains require you to have a metaverse account. \n Some domains have their own login dialogs.") - wrapMode: Text.WordWrap - visible: !linkAccountBody.isLoggingInToDomain - anchors { - top: dismissButton.bottom - topMargin: hifi.dimensions.contentSpacing.y - left: parent.left - } - lineHeight: 1 - color: "white" - font.family: linkAccountBody.fontFamily - font.pixelSize: linkAccountBody.textFieldFontSize - font.bold: linkAccountBody.fontBold - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - } } } From 2881f1147fcf77960d5658eb31e2235882de1856 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 5 Aug 2020 00:09:09 -0400 Subject: [PATCH 50/60] Metaverse login display working correctly. --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 6ba8c1435ce..bba46e78c53 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -107,7 +107,7 @@ Item { errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height; } var domainLoginText = "Log In to Domain\n" + domainLoginDomain; - loginDialogText.text = (!isLoggingInToDomain) ? "Log In to Metaverse (Not Required)" : domainLoginText; + loginDialogText.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : domainLoginText; loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; loginButton.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : "Log In to Domain"; loginButton.color = hifi.buttons.blue; @@ -147,7 +147,7 @@ Item { Item { id: loginContainer width: displayNameField.width - height: errorContainer.height + displayNameField.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y + + height: errorContainer.height + loginDialogTextContainer.height + displayNameField.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y + keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height anchors { top: parent.top @@ -469,7 +469,7 @@ Item { font.pixelSize: linkAccountBody.textFieldFontSize font.bold: linkAccountBody.fontBold - text: " Can't access your account?" + text: " Can't access your account?" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter @@ -595,7 +595,7 @@ Item { leftMargin: hifi.dimensions.contentSpacing.x } - text: "Sign Up" + text: "Sign Up" linkColor: hifi.colors.blueAccent onLinkActivated: { From c42adc9e00dbec41338718c0ebd5172e8017eabd Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 5 Aug 2020 00:14:27 -0400 Subject: [PATCH 51/60] Add menu items and triggers. --- interface/src/Menu.cpp | 11 +++++++++++ interface/src/ui/DialogsManager.cpp | 22 +++++++++++++++++++--- interface/src/ui/DialogsManager.h | 7 +++++-- interface/src/ui/LoginDialog.cpp | 2 +- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 0ed5f67ea0a..dbe60c4b7ee 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -9,6 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// For happ(ier) development of QML, use these two things: +// This forces QML files to be pulled from the source as you edit it: HIFI_USE_SOURCE_TREE_RESOURCES=1 +// Use this to live reload: DependencyManager::get()->clearCache(); + #include "Menu.h" #include #include @@ -84,6 +88,13 @@ Menu::Menu() { dialogsManager.data(), &DialogsManager::toggleLoginDialog); } + auto domainLogin = addActionToQMenuAndActionHash(fileMenu, "Domain: Log In"); + connect(domainLogin, &QAction::triggered, [] { + auto dialogsManager = DependencyManager::get(); + dialogsManager->requestDomainLoginState(); + dialogsManager->showDomainLoginDialog(); + }); + // File > Quit addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, qApp, SLOT(quit()), QAction::QuitRole); diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 3aaed2d3ecb..01bf69178f8 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -110,13 +110,29 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { } } -void DialogsManager::requestMetaverseLogin() { - DialogsManager::setDomainLogin(false); +void DialogsManager::requestMetaverseLoginState() { + DialogsManager::setMetaverseLoginState(); +} + +void DialogsManager::requestDomainLoginState() { + DialogsManager::setDomainLoginState(); +} + +void DialogsManager::setMetaverseLoginState() { + // We're only turning off the domain login trigger but the actual domain auth URL is still saved. + // So we can continue the domain login if desired. + _isDomainLogin = false; +} + +void DialogsManager::setDomainLoginState() { + _isDomainLogin = true; } void DialogsManager::setDomainLogin(bool isDomainLogin, const QString& domain) { _isDomainLogin = isDomainLogin; - _domainLoginDomain = domain; + if (!domain.isEmpty()) { + _domainLoginDomain = domain; + } } void DialogsManager::toggleLoginDialog() { diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 07d9de82b09..0f391d97583 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -41,7 +41,8 @@ class DialogsManager : public QObject, public Dependency { QPointer getTestingDialog() const { return _testingDialog; } void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } void setAddressBarVisible(bool addressBarVisible); - void requestMetaverseLogin(); + void requestMetaverseLoginState(); + void requestDomainLoginState(); bool getIsDomainLogin() { return _isDomainLogin; } QString getDomainLoginDomain() { return _domainLoginDomain; } @@ -53,7 +54,7 @@ public slots: void toggleLoginDialog(); void showLoginDialog(); void hideLoginDialog(); - void showDomainLoginDialog(const QString& domain); + void showDomainLoginDialog(const QString& domain = ""); void octreeStatsDetails(); void lodTools(); void hmdTools(bool showTools); @@ -89,6 +90,8 @@ private slots: bool _addressBarVisible { false }; void setDomainLogin(bool isDomainLogin, const QString& domain = ""); + void setMetaverseLoginState(); + void setDomainLoginState(); bool _isDomainLogin { false }; QString _domainLoginDomain; }; diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 46e31070b8d..71ae1eec83b 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -108,7 +108,7 @@ void LoginDialog::toggleAction() { // if not in login state, show. if (!qApp->getLoginDialogPoppedUp()) { auto dialogsManager = DependencyManager::get(); - dialogsManager->requestMetaverseLogin(); + dialogsManager->requestMetaverseLoginState(); LoginDialog::showWithSelection(); } }); From 448cdcb31fd480ae521b92fcbb6791c6b9c79f16 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 5 Aug 2020 00:23:55 -0400 Subject: [PATCH 52/60] Update domain's describe-settings for clarity. --- domain-server/resources/describe-settings.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 8e57a9b6834..d658e5d8cea 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -414,6 +414,7 @@ "name": "group_permissions", "type": "table", "caption": "Permissions for Users in Groups", + "help": "For groups that are supplied through OAuth, you will need to denote them by putting an \"@\" symbol in front of each item. e.g. \"@silver\"", "categorize_by_key": "permissions_id", "can_add_new_categories": true, "can_add_new_rows": false, @@ -542,6 +543,7 @@ "name": "group_forbiddens", "type": "table", "caption": "Permissions Denied to Users in Groups", + "help": "For groups that are supplied through OAuth, you will need to denote them by putting an \"@\" symbol in front of each item. e.g. \"@silver\"", "categorize_by_key": "permissions_id", "can_add_new_categories": true, "can_add_new_rows": false, From 6b0fc8fd66ec03af39fd2945e78df9beab9c0509 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 5 Aug 2020 02:18:01 -0400 Subject: [PATCH 53/60] Titlebar now responds correctly to domain logged in state. --- interface/src/Application.cpp | 9 +++---- .../networking/src/DomainAccountManager.cpp | 27 ++++++++++--------- .../networking/src/DomainAccountManager.h | 4 ++- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 761b2ae7173..eec7d8a5f78 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7085,7 +7085,7 @@ void Application::updateWindowTitle() const { auto isInErrorState = nodeList->getDomainHandler().isInErrorState(); bool isMetaverseLoggedIn = accountManager->isLoggedIn(); bool isDomainLoggedIn = domainAccountManager->isLoggedIn(); - qCDebug(interfaceapp) << "Is Logged Into Domain:" << isDomainLoggedIn; + QString authedDomain = domainAccountManager->getAuthedDomain(); QString buildVersion = " - Vircadia - " + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) @@ -7095,8 +7095,7 @@ void Application::updateWindowTitle() const { nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; QString metaverseUsername = accountManager->getAccountInfo().getUsername(); - // ###### TODO - // QString domainUsername = domainAccountManager->getUsername(); + QString domainUsername = domainAccountManager->getUsername(); setCrashAnnotation("sentry[user][metaverseUsername]", metaverseUsername.toStdString()); @@ -7122,10 +7121,10 @@ void Application::updateWindowTitle() const { } QString domainDetails; - if (isDomainLoggedIn) { + if (currentPlaceName == authedDomain && isDomainLoggedIn) { // ###### TODO // domainDetails = "Domain: Logged in as " + domainUsername; - domainDetails = "Domain: Logged In"; + domainDetails = "Domain: Logged in as " + domainUsername; } else { domainDetails = "Domain: Not Logged In"; } diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 6bb868df61d..19c16178e2e 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -18,7 +18,9 @@ #include #include +#include +#include "NodeList.h" #include "NetworkingConstants.h" #include "NetworkLogging.h" #include "NetworkAccessManager.h" @@ -101,6 +103,8 @@ void DomainAccountManager::requestAccessTokenFinished() { // miniOrange plugin provides no scope. if (rootObject.contains("access_token")) { // Success. + auto nodeList = DependencyManager::get(); + _domain_name = nodeList->getDomainHandler().getHostname(); QUrl rootURL = requestReply->url(); rootURL.setPath(""); setTokensFromJSON(rootObject, rootURL); @@ -133,10 +137,12 @@ bool DomainAccountManager::accessTokenIsExpired() { bool DomainAccountManager::hasValidAccessToken() { - QString currentDomainAccessToken = domainAccessToken.get(); - - if (currentDomainAccessToken.isEmpty() || accessTokenIsExpired()) { + // ###### 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; + // 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()); @@ -153,23 +159,20 @@ bool DomainAccountManager::hasValidAccessToken() { return true; } - } 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()); - */ + + // 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()); } bool DomainAccountManager::checkAndSignalForAccessToken() { diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 1590e4d4673..c8bc5e912c3 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -29,8 +29,9 @@ class DomainAccountManager : public QObject, public Dependency { QString getUsername() { return _username; } QString getAccessToken() { return _access_token; } QString getRefreshToken() { return _refresh_token; } + QString getAuthedDomain() { return _domain_name; } - bool isLoggedIn() { return hasValidAccessToken(); } + bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } Q_INVOKABLE bool checkAndSignalForAccessToken(); @@ -59,6 +60,7 @@ private slots: QString _username; // ####### TODO: Store elsewhere? QString _access_token; // ####... "" QString _refresh_token; // ####... "" + QString _domain_name; // }; #endif // hifi_DomainAccountManager_h From 658e14be25ad41523677ffedef2cf3fb03678732 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 5 Aug 2020 02:30:30 -0400 Subject: [PATCH 54/60] Remove unused aliases. --- interface/src/Menu.cpp | 2 +- interface/src/ui/DialogsManager.cpp | 10 +--------- interface/src/ui/DialogsManager.h | 6 ++---- interface/src/ui/LoginDialog.cpp | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index dbe60c4b7ee..f40082c1e7b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -91,7 +91,7 @@ Menu::Menu() { auto domainLogin = addActionToQMenuAndActionHash(fileMenu, "Domain: Log In"); connect(domainLogin, &QAction::triggered, [] { auto dialogsManager = DependencyManager::get(); - dialogsManager->requestDomainLoginState(); + dialogsManager->setDomainLoginState(); dialogsManager->showDomainLoginDialog(); }); diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 01bf69178f8..ae4f43d6fa5 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -110,16 +110,8 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { } } -void DialogsManager::requestMetaverseLoginState() { - DialogsManager::setMetaverseLoginState(); -} - -void DialogsManager::requestDomainLoginState() { - DialogsManager::setDomainLoginState(); -} - void DialogsManager::setMetaverseLoginState() { - // We're only turning off the domain login trigger but the actual domain auth URL is still saved. + // We're only turning off the domain login trigger but the actual domain auth URL is still saved. // So we can continue the domain login if desired. _isDomainLogin = false; } diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 0f391d97583..864174296e8 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -41,8 +41,8 @@ class DialogsManager : public QObject, public Dependency { QPointer getTestingDialog() const { return _testingDialog; } void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } void setAddressBarVisible(bool addressBarVisible); - void requestMetaverseLoginState(); - void requestDomainLoginState(); + void setMetaverseLoginState(); + void setDomainLoginState(); bool getIsDomainLogin() { return _isDomainLogin; } QString getDomainLoginDomain() { return _domainLoginDomain; } @@ -90,8 +90,6 @@ private slots: bool _addressBarVisible { false }; void setDomainLogin(bool isDomainLogin, const QString& domain = ""); - void setMetaverseLoginState(); - void setDomainLoginState(); bool _isDomainLogin { false }; QString _domainLoginDomain; }; diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 71ae1eec83b..b45a62ae3ae 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -108,7 +108,7 @@ void LoginDialog::toggleAction() { // if not in login state, show. if (!qApp->getLoginDialogPoppedUp()) { auto dialogsManager = DependencyManager::get(); - dialogsManager->requestMetaverseLoginState(); + dialogsManager->setMetaverseLoginState(); LoginDialog::showWithSelection(); } }); From 4d3ca2fdc5b74cb94f521a8ca410204fad1d0acd Mon Sep 17 00:00:00 2001 From: kasenvr <52365539+kasenvr@users.noreply.github.com> Date: Wed, 5 Aug 2020 02:31:10 -0400 Subject: [PATCH 55/60] Apply suggestions from code review Co-authored-by: David Rowe --- domain-server/resources/describe-settings.json | 4 ++-- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 4 ++-- interface/src/Menu.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index d658e5d8cea..9c220b740ee 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -414,7 +414,7 @@ "name": "group_permissions", "type": "table", "caption": "Permissions for Users in Groups", - "help": "For groups that are supplied through OAuth, you will need to denote them by putting an \"@\" symbol in front of each item. e.g. \"@silver\"", + "help": "For groups that are provided from WordPress you need to denote them by putting an \"@\" symbol in front of each item, e.g., \"@silver\"". "categorize_by_key": "permissions_id", "can_add_new_categories": true, "can_add_new_rows": false, @@ -543,7 +543,7 @@ "name": "group_forbiddens", "type": "table", "caption": "Permissions Denied to Users in Groups", - "help": "For groups that are supplied through OAuth, you will need to denote them by putting an \"@\" symbol in front of each item. e.g. \"@silver\"", + "help": "For groups that are provided from WordPress you need to denote them by putting an \"@\" symbol in front of each item, e.g., \"@silver\"". "categorize_by_key": "permissions_id", "can_add_new_categories": true, "can_add_new_rows": false, diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index bba46e78c53..04e980f0bbe 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -189,7 +189,7 @@ Item { anchors { top: parent.top left: parent.left - right: parent.right; + right: parent.right topMargin: 1.5 * hifi.dimensions.contentSpacing.y // horizontalCenter: mainContainer.horizontalCenter } @@ -595,7 +595,7 @@ Item { leftMargin: hifi.dimensions.contentSpacing.x } - text: "Sign Up" + text: "Sign Up" linkColor: hifi.colors.blueAccent onLinkActivated: { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f40082c1e7b..64cdf982393 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -10,7 +10,7 @@ // // For happ(ier) development of QML, use these two things: -// This forces QML files to be pulled from the source as you edit it: HIFI_USE_SOURCE_TREE_RESOURCES=1 +// This forces QML files to be pulled from the source as you edit it: set environment variable HIFI_USE_SOURCE_TREE_RESOURCES=1 // Use this to live reload: DependencyManager::get()->clearCache(); #include "Menu.h" From b948f24a56d288dbcf1ae28e3507199feb7386c9 Mon Sep 17 00:00:00 2001 From: kasenvr <52365539+kasenvr@users.noreply.github.com> Date: Wed, 5 Aug 2020 02:31:26 -0400 Subject: [PATCH 56/60] Apply suggestions from code review Co-authored-by: David Rowe --- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 04e980f0bbe..9ce80c0cb8d 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -469,7 +469,7 @@ Item { font.pixelSize: linkAccountBody.textFieldFontSize font.bold: linkAccountBody.fontBold - text: " Can't access your account?" + text: " Can't access your account?" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter From d89cf867d2a2e63600fdf4c89a89d52f257589d1 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Wed, 5 Aug 2020 02:41:39 -0400 Subject: [PATCH 57/60] Center error text correctly. --- .../resources/qml/LoginDialog/LinkAccountBody.qml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 9ce80c0cb8d..694fd6158f7 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -161,9 +161,10 @@ Item { width: parent.width height: loginErrorMessageTextMetrics.height anchors { - bottom: loginDialogTextContainer.top; - bottomMargin: hifi.dimensions.contentSpacing.y; - left: loginDialogTextContainer.left; + bottom: loginDialogTextContainer.top + bottomMargin: hifi.dimensions.contentSpacing.y + left: loginDialogTextContainer.left + right: loginDialogTextContainer.right } TextMetrics { id: loginErrorMessageTextMetrics @@ -176,6 +177,11 @@ Item { font.family: linkAccountBody.fontFamily font.pixelSize: linkAccountBody.textFieldFontSize font.bold: linkAccountBody.fontBold + anchors { + top: parent.top + left: parent.left + right: parent.right + } verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: "" @@ -191,7 +197,6 @@ Item { left: parent.left right: parent.right topMargin: 1.5 * hifi.dimensions.contentSpacing.y - // horizontalCenter: mainContainer.horizontalCenter } Text { From 13edfde89c3bd3a5b7efb6a7e2a5fda2fbd3ced5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Aug 2020 23:46:15 +1200 Subject: [PATCH 58/60] Fix server settings --- domain-server/resources/describe-settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 9c220b740ee..76924d81654 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -414,7 +414,7 @@ "name": "group_permissions", "type": "table", "caption": "Permissions for Users in Groups", - "help": "For groups that are provided from WordPress you need to denote them by putting an \"@\" symbol in front of each item, e.g., \"@silver\"". + "help": "For groups that are provided from WordPress you need to denote them by putting an \"@\" symbol in front of each item, e.g., \"@silver\".", "categorize_by_key": "permissions_id", "can_add_new_categories": true, "can_add_new_rows": false, @@ -543,7 +543,7 @@ "name": "group_forbiddens", "type": "table", "caption": "Permissions Denied to Users in Groups", - "help": "For groups that are provided from WordPress you need to denote them by putting an \"@\" symbol in front of each item, e.g., \"@silver\"". + "help": "For groups that are provided from WordPress you need to denote them by putting an \"@\" symbol in front of each item, e.g., \"@silver\".", "categorize_by_key": "permissions_id", "can_add_new_categories": true, "can_add_new_rows": false, From 8f0b33b54dd3a90cfaca88814e4ba6dd3ec93cb4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Aug 2020 00:04:22 +1200 Subject: [PATCH 59/60] Miscellaneous tidying --- domain-server/src/DomainGatekeeper.cpp | 10 +++--- interface/src/Application.cpp | 5 --- interface/src/ui/DialogsManager.cpp | 2 +- .../networking/src/DomainAccountManager.cpp | 32 +++++++++++-------- .../networking/src/DomainAccountManager.h | 4 +-- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 5b0c526d9f3..c77d1885958 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -450,7 +450,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo return newNode; } -const QString AUTHENTICATION_ENAABLED = "authentication.enable_oauth2"; +const QString AUTHENTICATION_ENABLE_OAUTH2 = "authentication.enable_oauth2"; const QString AUTHENTICATION_OAUTH2_URL_PATH = "authentication.oauth2_url_path"; const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base"; const QString AUTHENTICATION_PLUGIN_CLIENT_ID = "authentication.plugin_client_id"; @@ -1155,7 +1155,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply* req qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply->error(); } -// ####### TODO: Domain equivalent or addition [plugin groups] +// ####### TODO: Domain equivalent or addition void DomainGatekeeper::refreshGroupsCache() { // if agents are connected to this domain, refresh our cached information about groups and memberships in such. getDomainOwnerFriendsList(); @@ -1211,10 +1211,10 @@ Node::LocalID DomainGatekeeper::findOrCreateLocalID(const QUuid& uuid) { bool DomainGatekeeper::domainHasLogin() { // The domain may have its own users and groups in a WordPress site. - // ####### TODO: Add checks of any further domain server settings used. [plugin, groups] - return _server->_settingsManager.valueForKeyPath(AUTHENTICATION_ENAABLED).toBool() + return _server->_settingsManager.valueForKeyPath(AUTHENTICATION_ENABLE_OAUTH2).toBool() && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH).toString().isEmpty() - && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString().isEmpty(); + && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString().isEmpty() + && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_PLUGIN_CLIENT_ID).toString().isEmpty(); } void DomainGatekeeper::requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index eec7d8a5f78..ed71d1f2fb5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1351,16 +1351,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo #endif 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); // ####### TODO: Connect any other signals from domainAccountManager. - // use our MyAvatar position and quat for address manager path addressManager->setPositionGetter([] { auto avatarManager = DependencyManager::get(); @@ -7122,8 +7119,6 @@ void Application::updateWindowTitle() const { QString domainDetails; if (currentPlaceName == authedDomain && isDomainLoggedIn) { - // ###### TODO - // domainDetails = "Domain: Logged in as " + domainUsername; domainDetails = "Domain: Logged in as " + domainUsername; } else { domainDetails = "Domain: Not Logged In"; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index ae4f43d6fa5..8c8e3bc3c39 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -153,7 +153,7 @@ void DialogsManager::showDomainLoginDialog(const QString& domain) { // #######: TODO: Domain version of toggleLoginDialog()? -// #######: TODO: Domain version of hiadLoginDialog()? +// #######: TODO: Domain version of hideLoginDialog()? void DialogsManager::showUpdateDialog() { diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 19c16178e2e..928b581a5b3 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -17,31 +17,34 @@ #include #include -#include #include +#include -#include "NodeList.h" #include "NetworkingConstants.h" -#include "NetworkLogging.h" #include "NetworkAccessManager.h" +#include "NetworkLogging.h" +#include "NodeList.h" // FIXME: Generalize to other OAuth2 sources for domain login. 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() + _refresh_token(), + _domain_name() { connect(this, &DomainAccountManager::loginComplete, this, &DomainAccountManager::sendInterfaceAccessTokenToServer); } @@ -61,6 +64,10 @@ void DomainAccountManager::setAuthURL(const QUrl& authURL) { } } +bool DomainAccountManager::isLoggedIn() { + return !_authURL.isEmpty() && hasValidAccessToken(); +} + void DomainAccountManager::requestAccessToken(const QString& username, const QString& password) { _username = username; @@ -149,25 +156,24 @@ bool DomainAccountManager::hasValidAccessToken() { } return false; - } else { + } - // ####### TODO:: + // ####### TODO - // if (!_isWaitingForTokenRefresh && needsToRefreshToken()) { - // refreshAccessToken(); - // } + // if (!_isWaitingForTokenRefresh && needsToRefreshToken()) { + // refreshAccessToken(); + // } - return true; - } + return true; } 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()); diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index c8bc5e912c3..31226d69901 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -31,7 +31,7 @@ class DomainAccountManager : public QObject, public Dependency { QString getRefreshToken() { return _refresh_token; } QString getAuthedDomain() { return _domain_name; } - bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } + bool isLoggedIn(); Q_INVOKABLE bool checkAndSignalForAccessToken(); @@ -60,7 +60,7 @@ private slots: QString _username; // ####### TODO: Store elsewhere? QString _access_token; // ####... "" QString _refresh_token; // ####... "" - QString _domain_name; // + QString _domain_name; // ####... "" }; #endif // hifi_DomainAccountManager_h From 81c402ab9c182f99a1c5a0e2451b1d90d59e6966 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Aug 2020 21:40:36 +1200 Subject: [PATCH 60/60] Code review --- domain-server/src/DomainGatekeeper.cpp | 21 ++++++++++--------- .../src/DomainServerSettingsManager.cpp | 12 +++++------ .../src/DomainServerSettingsManager.h | 4 ++-- interface/src/Application.cpp | 2 +- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index c77d1885958..ead4002334a 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -180,10 +180,11 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin if (!verifiedDomainUserName.isEmpty()) { auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; foreach (QString userGroup, userGroups) { - // Domain groups may be specified as comma- and/or space-separated lists of group names. - // For example, "@silver @Gold, @platinum". - auto domainGroups = _server->_settingsManager.getDomainGroupNames() - .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", + // A domain group is signified by a leading special character, "@". + // Multiple domain groups may be specified in one domain server setting as a comma- and/or space-separated lists of + // domain group names. For example, "@silver @Gold, @platinum". + auto domainGroups = _server->_settingsManager.getDomainServerGroupNames() + .filter(QRegularExpression("^(.*[\\s,])?" + QRegularExpression::escape(userGroup) + "([\\s,].*)?$", QRegularExpression::CaseInsensitiveOption)); foreach(QString domainGroup, domainGroups) { userPerms |= _server->_settingsManager.getPermissionsForGroup(domainGroup, QUuid()); // No rank for domain groups. @@ -193,7 +194,6 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin #endif } } - } if (verifiedUsername.isEmpty()) { @@ -301,10 +301,11 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin if (!verifiedDomainUserName.isEmpty()) { auto userGroups = _domainGroupMemberships[verifiedDomainUserName]; foreach(QString userGroup, userGroups) { - // Domain groups may be specified as comma- and/or space-separated lists of group names. - // For example, "@silver @Gold, @platinum". - auto domainGroups = _server->_settingsManager.getDomainBlacklistGroupNames() - .filter(QRegularExpression("^(.*[\\s,])?" + userGroup + "([\\s,].*)?$", + // A domain group is signified by a leading special character, "@". + // Multiple domain groups may be specified in one domain server setting as a comma- and/or space-separated lists of + // domain group names. For example, "@silver @Gold, @platinum". + auto domainGroups = _server->_settingsManager.getDomainServerBlacklistGroupNames() + .filter(QRegularExpression("^(.*[\\s,])?" + QRegularExpression::escape(userGroup) + "([\\s,].*)?$", QRegularExpression::CaseInsensitiveOption)); foreach(QString domainGroup, domainGroups) { userPerms &= ~_server->_settingsManager.getForbiddensForGroup(domainGroup, QUuid()); @@ -1277,7 +1278,7 @@ void DomainGatekeeper::requestDomainUserFinished() { QStringList domainUserGroups; auto userRoles = rootObject.value("roles").toArray(); foreach (auto role, userRoles) { - // Distinguish domain groups from metaverse groups by a leading special character. + // Distinguish domain groups from metaverse groups by adding a leading special character. domainUserGroups.append(DOMAIN_GROUP_CHAR + role.toString().toLower()); } _domainGroupMemberships[username] = domainUserGroups; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index c03f26f0a1a..95bf4adc652 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1966,8 +1966,8 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() { QStringList groupNames = getAllKnownGroupNames(); foreach (QString groupName, groupNames) { QString lowerGroupName = groupName.toLower(); - if (lowerGroupName.contains(DOMAIN_GROUP_CHAR)) { - // Ignore domain groups. + if (lowerGroupName.startsWith(DOMAIN_GROUP_CHAR)) { + // Ignore domain groups. (Assumption: metaverse group names can't start with a "@".) return; } if (_groupIDs.contains(lowerGroupName)) { @@ -2189,8 +2189,8 @@ QList DomainServerSettingsManager::getBlacklistGroupIDs() { return result.toList(); } -QStringList DomainServerSettingsManager::getDomainGroupNames() { - // Names as configured in domain server; not necessarily metaverse groups. +QStringList DomainServerSettingsManager::getDomainServerGroupNames() { + // All names as listed in the domain server settings; both metaverse groups and domain groups QSet result; foreach(NodePermissionsKey groupKey, _groupPermissions.keys()) { result += _groupPermissions[groupKey]->getID(); @@ -2198,8 +2198,8 @@ QStringList DomainServerSettingsManager::getDomainGroupNames() { return result.toList(); } -QStringList DomainServerSettingsManager::getDomainBlacklistGroupNames() { - // Names as configured in domain server; not necessarily mnetaverse groups. +QStringList DomainServerSettingsManager::getDomainServerBlacklistGroupNames() { + // All names as listed in the domain server settings; not necessarily mnetaverse groups. QSet result; foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) { result += _groupForbiddens[groupKey]->getID(); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 73aef07835e..294e441ba4c 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -105,8 +105,8 @@ class DomainServerSettingsManager : public QObject { QList getGroupIDs(); QList getBlacklistGroupIDs(); - QStringList getDomainGroupNames(); - QStringList getDomainBlacklistGroupNames(); + QStringList getDomainServerGroupNames(); + QStringList getDomainServerBlacklistGroupNames(); // these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api void clearGroupMemberships(const QString& name) { _groupMembership[name.toLower()].clear(); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4dddcfb1cc3..cc2aed7f53d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7097,7 +7097,7 @@ void Application::updateWindowTitle() const { QString metaverseUsername = accountManager->getAccountInfo().getUsername(); QString domainUsername = domainAccountManager->getUsername(); - setCrashAnnotation("sentry[user][metaverseUsername]", metaverseUsername.toStdString()); + setCrashAnnotation("sentry[user][username]", metaverseUsername.toStdString()); QString currentPlaceName; if (isServerlessMode()) {