mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
commit
e74cbee6cb
23 changed files with 961 additions and 77 deletions
|
@ -3,7 +3,7 @@
|
|||
"settings": [
|
||||
{
|
||||
"name": "metaverse",
|
||||
"label": "Metaverse / Networking",
|
||||
"label": "Networking / Metaverse",
|
||||
"settings": [
|
||||
{
|
||||
"name": "access_token",
|
||||
|
@ -57,6 +57,39 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "authentication",
|
||||
"label": "Networking / WordPress OAuth2",
|
||||
"settings": [
|
||||
{
|
||||
"name": "enable_oauth2",
|
||||
"label": "Enable OAuth2 Authentication",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "oauth2_url_path",
|
||||
"label": "Authentication URL",
|
||||
"help": "The URL 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 \"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
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Monitoring",
|
||||
"name": "monitoring",
|
||||
|
@ -381,6 +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\".",
|
||||
"categorize_by_key": "permissions_id",
|
||||
"can_add_new_categories": true,
|
||||
"can_add_new_rows": false,
|
||||
|
@ -509,6 +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\".",
|
||||
"categorize_by_key": "permissions_id",
|
||||
"can_add_new_categories": true,
|
||||
"can_add_new_rows": false,
|
||||
|
|
|
@ -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
|
||||
|
@ -94,6 +95,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
} else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) {
|
||||
QByteArray usernameSignature;
|
||||
|
||||
QString domainUsername;
|
||||
QStringList domainTokens;
|
||||
|
||||
if (message->getBytesLeftToRead() > 0) {
|
||||
// read username from packet
|
||||
packetStream >> username;
|
||||
|
@ -101,10 +105,25 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
if (message->getBytesLeftToRead() > 0) {
|
||||
// read user signature from packet
|
||||
packetStream >> usernameSignature;
|
||||
|
||||
if (message->getBytesLeftToRead() > 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.
|
||||
|
||||
QString domainTokensString;
|
||||
packetStream >> domainTokensString;
|
||||
domainTokens = domainTokensString.split(":");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node = processAgentConnectRequest(nodeConnection, username, usernameSignature);
|
||||
node = processAgentConnectRequest(nodeConnection, username, usernameSignature,
|
||||
domainUsername, domainTokens.value(0), domainTokens.value(1));
|
||||
}
|
||||
|
||||
if (node) {
|
||||
|
@ -142,7 +161,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
}
|
||||
}
|
||||
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername,
|
||||
QString verifiedDomainUserName, const QHostAddress& senderAddress,
|
||||
const QString& hardwareAddress, const QUuid& machineFingerprint) {
|
||||
NodePermissions userPerms;
|
||||
|
||||
|
@ -155,6 +175,27 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
|
|||
#endif
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// 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.
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << domainGroup
|
||||
<< "so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (verifiedUsername.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
#ifdef WANT_DEBUG
|
||||
|
@ -256,6 +297,27 @@ 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) {
|
||||
// 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());
|
||||
#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
|
||||
|
@ -275,6 +337,7 @@ 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 verifiedDomainUserName = node->getPermissions().getVerifiedDomainUserName();
|
||||
NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0));
|
||||
|
||||
if (node->getPermissions().isAssignment) {
|
||||
|
@ -309,7 +372,8 @@ void DomainGatekeeper::updateNodePermissions() {
|
|||
sendingAddress == QHostAddress::LocalHost);
|
||||
}
|
||||
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint);
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUserName,
|
||||
connectingAddr.getAddress(), hardwareAddress, machineFingerprint);
|
||||
}
|
||||
|
||||
node->setPermissions(userPerms);
|
||||
|
@ -387,12 +451,19 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
return newNode;
|
||||
}
|
||||
|
||||
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";
|
||||
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
||||
const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location";
|
||||
|
||||
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
const QByteArray& usernameSignature) {
|
||||
const QByteArray& usernameSignature,
|
||||
const QString& domainUsername,
|
||||
const QString& domainAccessToken,
|
||||
const QString& domainRefreshToken) {
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
|
@ -419,7 +490,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);
|
||||
|
@ -430,16 +503,70 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress(),
|
||||
nodeConnection.hardwareAddress, nodeConnection.machineFingerprint);
|
||||
// The domain may have its own users and groups.
|
||||
QString verifiedDomainUsername;
|
||||
QStringList verifiedDomainUserGroups;
|
||||
if (domainHasLogin() && !domainUsername.isEmpty()) {
|
||||
|
||||
if (domainAccessToken.isEmpty()) {
|
||||
// User is attempting to prove their domain identity.
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "Stalling login because we have no domain OAuth2 tokens:" << domainUsername;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
|
||||
} else if (needToVerifyDomainUserIdentity(domainUsername, domainAccessToken, domainRefreshToken)) {
|
||||
// User's domain identity needs to be confirmed.
|
||||
requestDomainUser(domainUsername, domainAccessToken, domainRefreshToken);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "Stalling login because we haven't authenticated user yet:" << domainUsername;
|
||||
#endif
|
||||
|
||||
} else if (verifyDomainUserIdentity(domainUsername, domainAccessToken, domainRefreshToken,
|
||||
nodeConnection.senderSockAddr)) {
|
||||
// User's domain identity is confirmed.
|
||||
verifiedDomainUsername = domainUsername;
|
||||
|
||||
} else {
|
||||
// User's domain identity didn't check out.
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "Stalling login because domain user verification failed:" << domainUsername;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUsername,
|
||||
nodeConnection.senderSockAddr.getAddress(), nodeConnection.hardwareAddress,
|
||||
nodeConnection.machineFingerprint);
|
||||
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
|
||||
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorized);
|
||||
if (domainHasLogin()) {
|
||||
QString domainAuthURL;
|
||||
auto domainAuthURLVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH);
|
||||
if (domainAuthURLVariant.canConvert<QString>()) {
|
||||
domainAuthURL = domainAuthURLVariant.toString();
|
||||
}
|
||||
QString domainAuthClientID;
|
||||
auto domainAuthClientIDVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_PLUGIN_CLIENT_ID);
|
||||
if (domainAuthClientIDVariant.canConvert<QString>()) {
|
||||
domainAuthClientID = domainAuthClientIDVariant.toString();
|
||||
}
|
||||
|
||||
sendConnectionDeniedPacket("You lack the required domain permissions to connect to this domain.",
|
||||
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);
|
||||
}
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to permissions:" << username;
|
||||
#endif
|
||||
|
@ -600,15 +727,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 +749,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 {
|
||||
|
@ -635,6 +762,25 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool DomainGatekeeper::needToVerifyDomainUserIdentity(const QString& username, const QString& accessToken,
|
||||
const QString& refreshToken) {
|
||||
return !_verifiedDomainUserIdentities.contains(username)
|
||||
|| _verifiedDomainUserIdentities.value(username) != QPair<QString, QString>(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<QString, QString>(accessToken, refreshToken)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
sendConnectionDeniedPacket("Error verifying domain user.", senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason::LoginErrorDomain);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomainGatekeeper::isWithinMaxCapacity() {
|
||||
// find out what our maximum capacity is
|
||||
QVariant maximumUserCapacityVariant =
|
||||
|
@ -907,7 +1053,6 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) {
|
|||
AccountManagerAuth::Required,
|
||||
QNetworkAccessManager::PostOperation, callbackParams,
|
||||
QJsonDocument(json).toJson());
|
||||
|
||||
}
|
||||
|
||||
QString extractUsernameFromGroupMembershipsReply(QNetworkReply* requestReply) {
|
||||
|
@ -962,6 +1107,7 @@ void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply* requestReply
|
|||
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
|
||||
}
|
||||
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsList() {
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.callbackReceiver = this;
|
||||
|
@ -1010,6 +1156,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();
|
||||
|
@ -1029,7 +1176,7 @@ void DomainGatekeeper::refreshGroupsCache() {
|
|||
|
||||
updateNodePermissions();
|
||||
|
||||
#if WANT_DEBUG
|
||||
#ifdef WANT_DEBUG
|
||||
_server->_settingsManager.debugDumpGroupsState();
|
||||
#endif
|
||||
}
|
||||
|
@ -1061,3 +1208,91 @@ Node::LocalID DomainGatekeeper::findOrCreateLocalID(const QUuid& uuid) {
|
|||
_localIDs.insert(newLocalID);
|
||||
return newLocalID;
|
||||
}
|
||||
|
||||
|
||||
bool DomainGatekeeper::domainHasLogin() {
|
||||
// The domain may have its own users and groups in a WordPress site.
|
||||
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_PLUGIN_CLIENT_ID).toString().isEmpty();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken) {
|
||||
|
||||
if (_inFlightDomainUserIdentityRequests.contains(username)) {
|
||||
// Domain identify request for this username is already in progress.
|
||||
return;
|
||||
}
|
||||
_inFlightDomainUserIdentityRequests.insert(username, QPair<QString, QString>(accessToken, refreshToken));
|
||||
|
||||
if (_verifiedDomainUserIdentities.contains(username)) {
|
||||
_verifiedDomainUserIdentities.remove(username);
|
||||
}
|
||||
|
||||
QString apiBase = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString();
|
||||
if (!apiBase.endsWith("/")) {
|
||||
apiBase += "/";
|
||||
}
|
||||
|
||||
// Get data pertaining to "me", the user who generated the access token.
|
||||
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;
|
||||
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
request.setRawHeader(QByteArray("Authorization"), QString("Bearer " + accessToken).toUtf8());
|
||||
|
||||
QByteArray formData; // No data to send.
|
||||
|
||||
request.setUrl(domainUserURL);
|
||||
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkReply* requestReply = networkAccessManager.post(request, formData);
|
||||
connect(requestReply, &QNetworkReply::finished, this, &DomainGatekeeper::requestDomainUserFinished);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::requestDomainUserFinished() {
|
||||
|
||||
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
|
||||
const QJsonObject& rootObject = jsonResponse.object();
|
||||
|
||||
auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (200 <= httpStatus && httpStatus < 300) {
|
||||
|
||||
QString username = rootObject.value("username").toString().toLower();
|
||||
if (_inFlightDomainUserIdentityRequests.contains(username)) {
|
||||
// Success! Verified user.
|
||||
_verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username));
|
||||
_inFlightDomainUserIdentityRequests.remove(username);
|
||||
|
||||
// User user's WordPress roles as domain groups.
|
||||
QStringList domainUserGroups;
|
||||
auto userRoles = rootObject.value("roles").toArray();
|
||||
foreach (auto role, userRoles) {
|
||||
// Distinguish domain groups from metaverse groups by adding a leading special character.
|
||||
domainUserGroups.append(DOMAIN_GROUP_CHAR + role.toString().toLower());
|
||||
}
|
||||
_domainGroupMemberships[username] = domainUserGroups;
|
||||
|
||||
} else {
|
||||
// Failure.
|
||||
qDebug() << "Unexpected username in response for user details -" << username;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Failure.
|
||||
qDebug() << "Error in response for user details -" << httpStatus << requestReply->error()
|
||||
<< "-" << rootObject["error"].toString() << rootObject["error_description"].toString();
|
||||
|
||||
_inFlightDomainUserIdentityRequests.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -29,6 +30,8 @@
|
|||
#include "NodeConnectionData.h"
|
||||
#include "PendingAssignedNodeData.h"
|
||||
|
||||
const QString DOMAIN_GROUP_CHAR = "@";
|
||||
|
||||
class DomainServer;
|
||||
|
||||
class DomainGatekeeper : public QObject {
|
||||
|
@ -71,16 +74,28 @@ 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);
|
||||
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
const QByteArray& usernameSignature);
|
||||
const QByteArray& usernameSignature,
|
||||
const QString& domainUsername,
|
||||
const QString& domainAccessToken,
|
||||
const QString& domainRefreshToken);
|
||||
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
|
||||
|
||||
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
|
||||
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();
|
||||
|
||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||
|
@ -120,8 +135,9 @@ private:
|
|||
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
|
||||
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
|
||||
|
||||
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
|
||||
const QString& hardwareAddress, const QUuid& machineFingerprint);
|
||||
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, QString verifiedDomainUsername,
|
||||
const QHostAddress& senderAddress, const QString& hardwareAddress,
|
||||
const QUuid& machineFingerprint);
|
||||
|
||||
void getGroupMemberships(const QString& username);
|
||||
// void getIsGroupMember(const QString& username, const QUuid groupID);
|
||||
|
@ -133,9 +149,18 @@ private:
|
|||
using LocalIDs = std::unordered_set<Node::LocalID>;
|
||||
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<QString, QPair<QString, QString>> DomainUserIdentities; // <domainUserName, <access_token, refresh_token>>
|
||||
DomainUserIdentities _inFlightDomainUserIdentityRequests; // Domain user identity requests currently in progress.
|
||||
DomainUserIdentities _verifiedDomainUserIdentities; // Verified domain users.
|
||||
|
||||
QHash<QString, QStringList> _domainGroupMemberships; // <domainUserName, [domainGroupName]>
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1966,6 +1966,10 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() {
|
|||
QStringList groupNames = getAllKnownGroupNames();
|
||||
foreach (QString groupName, groupNames) {
|
||||
QString lowerGroupName = groupName.toLower();
|
||||
if (lowerGroupName.startsWith(DOMAIN_GROUP_CHAR)) {
|
||||
// Ignore domain groups. (Assumption: metaverse group names can't start with a "@".)
|
||||
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).
|
||||
|
@ -2185,6 +2189,24 @@ QList<QUuid> DomainServerSettingsManager::getBlacklistGroupIDs() {
|
|||
return result.toList();
|
||||
}
|
||||
|
||||
QStringList DomainServerSettingsManager::getDomainServerGroupNames() {
|
||||
// All names as listed in the domain server settings; both metaverse groups and domain groups
|
||||
QSet<QString> result;
|
||||
foreach(NodePermissionsKey groupKey, _groupPermissions.keys()) {
|
||||
result += _groupPermissions[groupKey]->getID();
|
||||
}
|
||||
return result.toList();
|
||||
}
|
||||
|
||||
QStringList DomainServerSettingsManager::getDomainServerBlacklistGroupNames() {
|
||||
// All names as listed in the domain server settings; not necessarily mnetaverse groups.
|
||||
QSet<QString> result;
|
||||
foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) {
|
||||
result += _groupForbiddens[groupKey]->getID();
|
||||
}
|
||||
return result.toList();
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::debugDumpGroupsState() {
|
||||
qDebug() << "--------- GROUPS ---------";
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
|
||||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPManager.h>
|
||||
|
||||
#include <ReceivedMessage.h>
|
||||
#include "NodePermissions.h"
|
||||
|
||||
#include <Node.h>
|
||||
#include <ReceivedMessage.h>
|
||||
|
||||
#include "DomainGatekeeper.h"
|
||||
#include "NodePermissions.h"
|
||||
|
||||
const QString SETTINGS_PATHS_KEY = "paths";
|
||||
|
||||
|
@ -105,6 +105,9 @@ public:
|
|||
QList<QUuid> getGroupIDs();
|
||||
QList<QUuid> getBlacklistGroupIDs();
|
||||
|
||||
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(); }
|
||||
void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID);
|
||||
|
|
|
@ -45,6 +45,9 @@ 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 domainLoginDomain: loginDialog.getDomainLoginDomain()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
@ -71,7 +74,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);
|
||||
}
|
||||
|
||||
if (linkAccountBody.loginDialogPoppedUp) {
|
||||
var data;
|
||||
if (linkAccountBody.linkSteam) {
|
||||
|
@ -87,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 });
|
||||
"displayName":displayNameField.text, "isLoggingInToDomain": linkAccountBody.isLoggingInToDomain, "domainLoginDomain": linkAccountBody.domainLoginDomain });
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
@ -98,14 +106,22 @@ Item {
|
|||
loginErrorMessage.wrapMode = Text.WordWrap;
|
||||
errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height;
|
||||
}
|
||||
var domainLoginText = "Log In to Domain\n" + 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;
|
||||
displayNameField.placeholderText = "Display Name (optional)";
|
||||
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 : "";
|
||||
emailField.placeholderText = (!isLoggingInToDomain) ? "Username or Email" : "Username";
|
||||
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;
|
||||
|
@ -131,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
|
||||
|
@ -145,9 +161,10 @@ Item {
|
|||
width: parent.width
|
||||
height: loginErrorMessageTextMetrics.height
|
||||
anchors {
|
||||
bottom: displayNameField.top;
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y;
|
||||
left: displayNameField.left;
|
||||
bottom: loginDialogTextContainer.top
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
left: loginDialogTextContainer.left
|
||||
right: loginDialogTextContainer.right
|
||||
}
|
||||
TextMetrics {
|
||||
id: loginErrorMessageTextMetrics
|
||||
|
@ -160,12 +177,45 @@ 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: ""
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: loginDialogTextContainer
|
||||
height: 56
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: 1.5 * hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.TextField {
|
||||
id: displayNameField
|
||||
|
@ -174,8 +224,8 @@ Item {
|
|||
font.pixelSize: linkAccountBody.textFieldFontSize
|
||||
styleRenderType: Text.QtRendering
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: errorContainer.height
|
||||
top: loginDialogTextContainer.bottom
|
||||
topMargin: 1.5 * hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
placeholderText: "Display Name (optional)"
|
||||
activeFocusOnPress: true
|
||||
|
@ -193,7 +243,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;
|
||||
|
@ -232,7 +286,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;
|
||||
|
@ -312,7 +370,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;
|
||||
|
@ -321,12 +383,13 @@ 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
|
||||
labelFontSize: 18;
|
||||
color: hifi.colors.white;
|
||||
visible: !isLoggingInToDomain
|
||||
anchors {
|
||||
top: passwordField.bottom;
|
||||
topMargin: hifi.dimensions.contentSpacing.y;
|
||||
|
@ -334,14 +397,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 {
|
||||
|
@ -393,7 +464,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
|
||||
|
@ -403,7 +474,7 @@ Item {
|
|||
font.pixelSize: linkAccountBody.textFieldFontSize
|
||||
font.bold: linkAccountBody.fontBold
|
||||
|
||||
text: "<a href='metaverse.vircadia.com/users/password/new'> Can't access your account?</a>"
|
||||
text: "<a href='https://metaverse.vircadia.com/users/password/new'> Can't access your account?</a>"
|
||||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
@ -492,7 +563,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
|
||||
|
@ -529,7 +600,7 @@ Item {
|
|||
leftMargin: hifi.dimensions.contentSpacing.x
|
||||
}
|
||||
|
||||
text: "<a href='metaverse.vircadia.com/users/register'>Sign Up</a>"
|
||||
text: "<a href='https://metaverse.vircadia.com/users/register'>Sign Up</a>"
|
||||
|
||||
linkColor: hifi.colors.blueAccent
|
||||
onLinkActivated: {
|
||||
|
|
|
@ -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
|
||||
|
@ -31,6 +32,8 @@ Item {
|
|||
property bool linkSteam: linkSteam
|
||||
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()
|
||||
|
@ -106,6 +109,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 " + domainLoginDomain;
|
||||
loggingInText.anchors.centerIn = loggingInHeader;
|
||||
} else {
|
||||
loggingInText.text = "Logging in";
|
||||
loggingInText.anchors.centerIn = loggingInHeader;
|
||||
|
|
|
@ -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
|
||||
|
@ -65,6 +66,7 @@
|
|||
#include <Trace.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
#include <AccountManager.h>
|
||||
#include <DomainAccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <AnimDebugDraw.h>
|
||||
#include <BuildInfo.h>
|
||||
|
@ -852,6 +854,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
#else
|
||||
DependencyManager::set<AccountManager>(true, std::bind(&Application::getUserAgent, qApp));
|
||||
#endif
|
||||
DependencyManager::set<DomainAccountManager>();
|
||||
DependencyManager::set<StatTracker>();
|
||||
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT, defaultScriptsOverrideOption);
|
||||
DependencyManager::set<Preferences>();
|
||||
|
@ -1348,6 +1351,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
#endif
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
|
||||
|
||||
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
|
||||
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<AvatarManager>();
|
||||
|
@ -1571,7 +1581,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;
|
||||
}
|
||||
|
@ -2801,6 +2811,7 @@ void Application::cleanupBeforeQuit() {
|
|||
if (!keepMeLoggedIn) {
|
||||
DependencyManager::get<AccountManager>()->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<AccountManager>();
|
||||
|
@ -7069,19 +7081,23 @@ void Application::updateWindowTitle() const {
|
|||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
|
||||
auto isInErrorState = nodeList->getDomainHandler().isInErrorState();
|
||||
bool isMetaverseLoggedIn = accountManager->isLoggedIn();
|
||||
bool isDomainLoggedIn = domainAccountManager->isLoggedIn();
|
||||
QString authedDomain = domainAccountManager->getAuthedDomain();
|
||||
|
||||
QString buildVersion = " - Vircadia - "
|
||||
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
|
||||
+ " " + applicationVersion();
|
||||
|
||||
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();
|
||||
QString domainUsername = domainAccountManager->getUsername();
|
||||
|
||||
setCrashAnnotation("sentry[user][username]", metaverseUsername.toStdString());
|
||||
|
||||
QString currentPlaceName;
|
||||
if (isServerlessMode()) {
|
||||
|
@ -7097,8 +7113,22 @@ 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 (currentPlaceName == authedDomain && isDomainLoggedIn) {
|
||||
domainDetails = "Domain: Logged in as " + domainUsername;
|
||||
} else {
|
||||
domainDetails = "Domain: Not Logged In";
|
||||
}
|
||||
|
||||
QString title = QString() + currentPlaceName + connectionStatus + " (" + metaverseDetails + ") (" + domainDetails + ")"
|
||||
+ buildVersion;
|
||||
|
||||
#ifndef WIN32
|
||||
// crashes with vs2013/win32
|
||||
|
|
|
@ -733,6 +733,7 @@ private:
|
|||
GraphicsEngine _graphicsEngine;
|
||||
void updateRenderArgs(float deltaTime);
|
||||
|
||||
bool _disableLoginScreen { true };
|
||||
|
||||
Overlays _overlays;
|
||||
ApplicationOverlay _applicationOverlay;
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
#include "Application.h"
|
||||
#include "ui/DialogsManager.h"
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <DomainHandler.h>
|
||||
#include <DomainAccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
|
@ -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<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::loginComplete, this, &ConnectionMonitor::startTimer);
|
||||
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
|
||||
connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, &ConnectionMonitor::startTimer);
|
||||
|
||||
_timer.setSingleShot(true);
|
||||
if (!domainHandler.isConnected()) {
|
||||
|
|
|
@ -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: set environment variable HIFI_USE_SOURCE_TREE_RESOURCES=1
|
||||
// Use this to live reload: DependencyManager::get<OffscreenUi>()->clearCache();
|
||||
|
||||
#include "Menu.h"
|
||||
#include <QDesktopServices>
|
||||
#include <QFileDialog>
|
||||
|
@ -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>();
|
||||
dialogsManager->setDomainLoginState();
|
||||
dialogsManager->showDomainLoginDialog();
|
||||
});
|
||||
|
||||
// File > Quit
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, qApp, SLOT(quit()), QAction::QuitRole);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -109,11 +110,34 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (!domain.isEmpty()) {
|
||||
_domainLoginDomain = domain;
|
||||
}
|
||||
}
|
||||
|
||||
void DialogsManager::toggleLoginDialog() {
|
||||
setDomainLogin(false);
|
||||
LoginDialog::toggleAction();
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
setDomainLogin(false);
|
||||
LoginDialog::showWithSelection();
|
||||
}
|
||||
|
||||
|
@ -121,10 +145,22 @@ void DialogsManager::hideLoginDialog() {
|
|||
LoginDialog::hide();
|
||||
}
|
||||
|
||||
|
||||
void DialogsManager::showDomainLoginDialog(const QString& domain) {
|
||||
setDomainLogin(true, domain);
|
||||
LoginDialog::showWithSelection();
|
||||
}
|
||||
|
||||
// #######: TODO: Domain version of toggleLoginDialog()?
|
||||
|
||||
// #######: TODO: Domain version of hideLoginDialog()?
|
||||
|
||||
|
||||
void DialogsManager::showUpdateDialog() {
|
||||
UpdateDialog::show();
|
||||
}
|
||||
|
||||
|
||||
void DialogsManager::octreeStatsDetails() {
|
||||
if (!_octreeStatsDialog) {
|
||||
_octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), qApp->getOcteeSceneStats());
|
||||
|
|
|
@ -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
|
||||
|
@ -40,6 +41,10 @@ public:
|
|||
QPointer<TestingDialog> getTestingDialog() const { return _testingDialog; }
|
||||
void emitAddressBarShown(bool visible) { emit addressBarShown(visible); }
|
||||
void setAddressBarVisible(bool addressBarVisible);
|
||||
void setMetaverseLoginState();
|
||||
void setDomainLoginState();
|
||||
bool getIsDomainLogin() { return _isDomainLogin; }
|
||||
QString getDomainLoginDomain() { return _domainLoginDomain; }
|
||||
|
||||
public slots:
|
||||
void showAddressBar();
|
||||
|
@ -49,6 +54,7 @@ public slots:
|
|||
void toggleLoginDialog();
|
||||
void showLoginDialog();
|
||||
void hideLoginDialog();
|
||||
void showDomainLoginDialog(const QString& domain = "");
|
||||
void octreeStatsDetails();
|
||||
void lodTools();
|
||||
void hmdTools(bool showTools);
|
||||
|
@ -82,6 +88,10 @@ private:
|
|||
QPointer<DomainConnectionDialog> _domainConnectionDialog;
|
||||
bool _dialogCreatedWhileShown { false };
|
||||
bool _addressBarVisible { false };
|
||||
|
||||
void setDomainLogin(bool isDomainLogin, const QString& domain = "");
|
||||
bool _isDomainLogin { false };
|
||||
QString _domainLoginDomain;
|
||||
};
|
||||
|
||||
#endif // hifi_DialogsManager_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
|
||||
|
@ -24,7 +25,9 @@
|
|||
#include <UserActivityLogger.h>
|
||||
|
||||
#include "AccountManager.h"
|
||||
#include "DomainAccountManager.h"
|
||||
#include "DependencyManager.h"
|
||||
#include "DialogsManager.h"
|
||||
#include "Menu.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -38,12 +41,17 @@ const QUrl LOGIN_DIALOG = PathUtils::qmlUrl("OverlayLoginDialog.qml");
|
|||
|
||||
LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
|
||||
// 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()));
|
||||
|
@ -91,14 +99,16 @@ 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()) {
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
dialogsManager->setMetaverseLoginState();
|
||||
LoginDialog::showWithSelection();
|
||||
}
|
||||
});
|
||||
|
@ -131,10 +141,15 @@ 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<AccountManager>()->requestAccessToken(username, password);
|
||||
}
|
||||
|
||||
void LoginDialog::loginDomain(const QString& username, const QString& password) const {
|
||||
qDebug() << "Attempting to login" << username << "into a domain";
|
||||
DependencyManager::get<DomainAccountManager>()->requestAccessToken(username, password);
|
||||
}
|
||||
|
||||
void LoginDialog::loginThroughOculus() {
|
||||
qDebug() << "Attempting to login through Oculus";
|
||||
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
|
||||
|
@ -410,3 +425,11 @@ void LoginDialog::signupFailed(QNetworkReply* reply) {
|
|||
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
bool LoginDialog::getDomainLoginRequested() const {
|
||||
return DependencyManager::get<DialogsManager>()->getIsDomainLogin();
|
||||
}
|
||||
|
||||
QString LoginDialog::getDomainLoginDomain() const {
|
||||
return DependencyManager::get<DialogsManager>()->getDomainLoginDomain();
|
||||
}
|
||||
|
|
|
@ -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,6 +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;
|
||||
Q_INVOKABLE void loginThroughSteam();
|
||||
Q_INVOKABLE void linkSteam();
|
||||
Q_INVOKABLE void createAccountFromSteam(QString username = QString());
|
||||
|
@ -81,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 getDomainLoginDomain() const;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_LoginDialog_h
|
||||
|
|
|
@ -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;
|
||||
|
|
203
libraries/networking/src/DomainAccountManager.cpp
Normal file
203
libraries/networking/src/DomainAccountManager.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
//
|
||||
// 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"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#include "NetworkingConstants.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<QString> domainAccessToken {"private/domainAccessToken", "" };
|
||||
Setting::Handle<QString> domainAccessRefreshToken {"private/domainAccessToken", "" };
|
||||
Setting::Handle<int> domainAccessTokenExpiresIn {"private/domainAccessTokenExpiresIn", -1 };
|
||||
Setting::Handle<QString> domainAccessTokenType {"private/domainAccessTokenType", "" };
|
||||
*/
|
||||
|
||||
DomainAccountManager::DomainAccountManager() :
|
||||
_authURL(),
|
||||
_username(),
|
||||
_access_token(),
|
||||
_refresh_token(),
|
||||
_domain_name()
|
||||
{
|
||||
connect(this, &DomainAccountManager::loginComplete, this, &DomainAccountManager::sendInterfaceAccessTokenToServer);
|
||||
}
|
||||
|
||||
void DomainAccountManager::setAuthURL(const QUrl& authURL) {
|
||||
if (_authURL != authURL) {
|
||||
_authURL = authURL;
|
||||
|
||||
qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
|
||||
|
||||
_access_token = "";
|
||||
_refresh_token = "";
|
||||
|
||||
// ####### TODO: Restore and refresh OAuth2 tokens if have them for this domain.
|
||||
|
||||
// ####### TODO: Handle "keep me logged in".
|
||||
}
|
||||
}
|
||||
|
||||
bool DomainAccountManager::isLoggedIn() {
|
||||
return !_authURL.isEmpty() && hasValidAccessToken();
|
||||
}
|
||||
|
||||
void DomainAccountManager::requestAccessToken(const QString& username, const QString& password) {
|
||||
|
||||
_username = username;
|
||||
_access_token = "";
|
||||
_refresh_token = "";
|
||||
|
||||
QNetworkRequest request;
|
||||
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
// 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("client_id=" + _clientID);
|
||||
|
||||
request.setUrl(_authURL);
|
||||
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkReply* requestReply = networkAccessManager.post(request, formData);
|
||||
connect(requestReply, &QNetworkReply::finished, this, &DomainAccountManager::requestAccessTokenFinished);
|
||||
}
|
||||
|
||||
void DomainAccountManager::requestAccessTokenFinished() {
|
||||
|
||||
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
|
||||
const QJsonObject& rootObject = jsonResponse.object();
|
||||
|
||||
auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (200 <= httpStatus && httpStatus < 300) {
|
||||
|
||||
// miniOrange plugin provides no scope.
|
||||
if (rootObject.contains("access_token")) {
|
||||
// Success.
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
_domain_name = nodeList->getDomainHandler().getHostname();
|
||||
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.";
|
||||
emit loginFailed();
|
||||
}
|
||||
|
||||
} else {
|
||||
// Failure.
|
||||
qCDebug(networking) << "Error in response for password grant -" << httpStatus << requestReply->error()
|
||||
<< "-" << rootObject["error"].toString() << rootObject["error_description"].toString();
|
||||
emit loginFailed();
|
||||
}
|
||||
}
|
||||
|
||||
void DomainAccountManager::sendInterfaceAccessTokenToServer() {
|
||||
emit newTokens();
|
||||
}
|
||||
|
||||
bool DomainAccountManager::accessTokenIsExpired() {
|
||||
// ####### TODO: accessTokenIsExpired()
|
||||
return true;
|
||||
/*
|
||||
return domainAccessTokenExpiresIn.get() != -1 && domainAccessTokenExpiresIn.get() <= QDateTime::currentMSecsSinceEpoch();
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
bool DomainAccountManager::hasValidAccessToken() {
|
||||
// ###### TODO: wire this up to actually retrieve a token (based on session or storage) and confirm that it is in fact valid and relevant to the current domain.
|
||||
// QString currentDomainAccessToken = domainAccessToken.get();
|
||||
QString currentDomainAccessToken = _access_token;
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ####### TODO
|
||||
|
||||
// if (!_isWaitingForTokenRefresh && needsToRefreshToken()) {
|
||||
// refreshAccessToken();
|
||||
// }
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
// Dialog can be hidden immediately after showing if we've just teleported to the domain, unless the signal is delayed.
|
||||
auto domain = _authURL.host();
|
||||
QTimer::singleShot(500, this, [this, domain] {
|
||||
emit this->authRequired(domain);
|
||||
});
|
||||
}
|
||||
|
||||
return hasToken;
|
||||
}
|
66
libraries/networking/src/DomainAccountManager.h
Normal file
66
libraries/networking/src/DomainAccountManager.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// 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 <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
|
||||
class DomainAccountManager : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainAccountManager();
|
||||
|
||||
void setAuthURL(const QUrl& authURL);
|
||||
void setClientID(const QString& clientID) { _clientID = clientID; }
|
||||
|
||||
QString getUsername() { return _username; }
|
||||
QString getAccessToken() { return _access_token; }
|
||||
QString getRefreshToken() { return _refresh_token; }
|
||||
QString getAuthedDomain() { return _domain_name; }
|
||||
|
||||
bool isLoggedIn();
|
||||
|
||||
Q_INVOKABLE bool checkAndSignalForAccessToken();
|
||||
|
||||
public slots:
|
||||
void requestAccessToken(const QString& username, const QString& password);
|
||||
|
||||
void requestAccessTokenFinished();
|
||||
|
||||
signals:
|
||||
void authRequired(const QString& domain);
|
||||
void loginComplete();
|
||||
void loginFailed();
|
||||
void logoutComplete();
|
||||
void newTokens();
|
||||
|
||||
private slots:
|
||||
|
||||
private:
|
||||
bool hasValidAccessToken();
|
||||
bool accessTokenIsExpired();
|
||||
void setTokensFromJSON(const QJsonObject&, const QUrl& url);
|
||||
void sendInterfaceAccessTokenToServer();
|
||||
|
||||
QUrl _authURL;
|
||||
QString _clientID;
|
||||
QString _username; // ####### TODO: Store elsewhere?
|
||||
QString _access_token; // ####... ""
|
||||
QString _refresh_token; // ####... ""
|
||||
QString _domain_name; // ####... ""
|
||||
};
|
||||
|
||||
#endif // hifi_DomainAccountManager_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
|
||||
|
@ -24,6 +25,7 @@
|
|||
|
||||
#include "AddressManager.h"
|
||||
#include "Assignment.h"
|
||||
#include "DomainAccountManager.h"
|
||||
#include "HifiSockAddr.h"
|
||||
#include "NodeList.h"
|
||||
#include "udt/Packet.h"
|
||||
|
@ -144,7 +146,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);
|
||||
}
|
||||
|
||||
|
@ -219,6 +222,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();
|
||||
|
||||
|
@ -489,16 +494,33 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> mes
|
|||
}
|
||||
}
|
||||
|
||||
bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) {
|
||||
bool DomainHandler::reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonCode) {
|
||||
switch (reasonCode) {
|
||||
case ConnectionRefusedReason::LoginError:
|
||||
case ConnectionRefusedReason::NotAuthorized:
|
||||
case ConnectionRefusedReason::LoginErrorMetaverse:
|
||||
case ConnectionRefusedReason::NotAuthorizedMetaverse:
|
||||
return true;
|
||||
|
||||
default:
|
||||
case ConnectionRefusedReason::Unknown:
|
||||
case ConnectionRefusedReason::ProtocolMismatch:
|
||||
case ConnectionRefusedReason::TooManyUsers:
|
||||
case ConnectionRefusedReason::NotAuthorizedDomain:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomainHandler::reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode) {
|
||||
switch (reasonCode) {
|
||||
case ConnectionRefusedReason::LoginErrorDomain:
|
||||
case ConnectionRefusedReason::NotAuthorizedDomain:
|
||||
return true;
|
||||
|
||||
default:
|
||||
case ConnectionRefusedReason::Unknown:
|
||||
case ConnectionRefusedReason::ProtocolMismatch:
|
||||
case ConnectionRefusedReason::TooManyUsers:
|
||||
case ConnectionRefusedReason::NotAuthorizedMetaverse:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
@ -528,7 +550,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
|
|||
|
||||
// output to the log so the user knows they got a denied connection request
|
||||
// and check and signal for an access token so that we can make sure they are logged in
|
||||
qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage << " extraInfo:" << extraInfo;
|
||||
QString sanitizedExtraInfo = extraInfo.toLower().startsWith("http") ? "" : extraInfo; // Don't log URLs.
|
||||
qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage
|
||||
<< " extraInfo:" << sanitizedExtraInfo;
|
||||
|
||||
if (!_domainConnectionRefusals.contains(reasonMessage)) {
|
||||
_domainConnectionRefusals.insert(reasonMessage);
|
||||
|
@ -541,11 +565,12 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
|
|||
#endif
|
||||
}
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
||||
// 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.";
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
||||
if (!_hasCheckedForAccessToken) {
|
||||
accountManager->checkAndSignalForAccessToken();
|
||||
|
@ -559,6 +584,23 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
|
|||
accountManager->generateNewUserKeypair();
|
||||
_connectionDenialsSinceKeypairRegen = 0;
|
||||
}
|
||||
} else if (reasonSuggestsDomainLogin(reasonCode)) {
|
||||
qCWarning(networking) << "Make sure you are logged in to the domain.";
|
||||
|
||||
auto accountManager = DependencyManager::get<DomainAccountManager>();
|
||||
if (!extraInfo.isEmpty()) {
|
||||
auto extraInfoComponents = extraInfo.split("|");
|
||||
accountManager->setAuthURL(extraInfoComponents.value(0));
|
||||
accountManager->setClientID(extraInfoComponents.value(1));
|
||||
}
|
||||
|
||||
if (!_hasCheckedForDomainAccessToken) {
|
||||
accountManager->checkAndSignalForAccessToken();
|
||||
_hasCheckedForDomainAccessToken = true;
|
||||
}
|
||||
|
||||
// ####### TODO: regenerate key-pair after several failed connection attempts, similar to metaverse login code?
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -124,6 +125,7 @@ public:
|
|||
|
||||
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);
|
||||
|
@ -172,14 +174,14 @@ public:
|
|||
* <td>The communications protocols of the domain and your Interface are not the same.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>LoginError</strong></td>
|
||||
* <td><strong>LoginErrorMetaverse</strong></td>
|
||||
* <td><code>2</code></td>
|
||||
* <td>You could not be logged into the domain.</td>
|
||||
* <td>You could not be logged into the domain per your metaverse login.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>NotAuthorized</strong></td>
|
||||
* <td><strong>NotAuthorizedMetaverse</strong></td>
|
||||
* <td><code>3</code></td>
|
||||
* <td>You are not authorized to connect to the domain.</td>
|
||||
* <td>You are not authorized to connect to the domain per your metaverse login.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>TooManyUsers</strong></td>
|
||||
|
@ -191,6 +193,16 @@ public:
|
|||
* <td><code>5</code></td>
|
||||
* <td>Connecting to the domain timed out.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>LoginErrorDomain</strong></td>
|
||||
* <td><code>2</code></td>
|
||||
* <td>You could not be logged into the domain per your domain login.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>NotAuthorizedDomain</strong></td>
|
||||
* <td><code>6</code></td>
|
||||
* <td>You are not authorized to connect to the domain per your domain login.</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef {number} Window.ConnectionRefusedReason
|
||||
|
@ -198,10 +210,12 @@ public:
|
|||
enum class ConnectionRefusedReason : uint8_t {
|
||||
Unknown,
|
||||
ProtocolMismatch,
|
||||
LoginError,
|
||||
NotAuthorized,
|
||||
LoginErrorMetaverse,
|
||||
NotAuthorizedMetaverse,
|
||||
TooManyUsers,
|
||||
TimedOut
|
||||
TimedOut,
|
||||
LoginErrorDomain,
|
||||
NotAuthorizedDomain
|
||||
};
|
||||
|
||||
public slots:
|
||||
|
@ -247,7 +261,8 @@ signals:
|
|||
void limitOfSilentDomainCheckInsReached();
|
||||
|
||||
private:
|
||||
bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode);
|
||||
bool reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonCode);
|
||||
bool reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode);
|
||||
void sendDisconnectPacket();
|
||||
void hardReset(QString reason);
|
||||
|
||||
|
@ -278,6 +293,7 @@ private:
|
|||
|
||||
QSet<QString> _domainConnectionRefusals;
|
||||
bool _hasCheckedForAccessToken { false };
|
||||
bool _hasCheckedForDomainAccessToken { false };
|
||||
int _connectionDenialsSinceKeypairRegen { 0 };
|
||||
int _checkInPacketsSinceLastReply { 0 };
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -33,6 +34,7 @@
|
|||
#include "AddressManager.h"
|
||||
#include "Assignment.h"
|
||||
#include "AudioHelpers.h"
|
||||
#include "DomainAccountManager.h"
|
||||
#include "HifiSockAddr.h"
|
||||
#include "FingerprintUtils.h"
|
||||
|
||||
|
@ -103,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<DomainAccountManager>();
|
||||
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);
|
||||
|
@ -379,6 +388,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
if (domainPacketType == PacketType::DomainConnectRequest) {
|
||||
|
||||
#if (PR_BUILD || DEV_BUILD)
|
||||
// #######
|
||||
if (_shouldSendNewerVersion) {
|
||||
domainPacket->setVersion(versionForPacketType(domainPacketType) + 1);
|
||||
}
|
||||
|
@ -466,6 +476,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList();
|
||||
packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
|
||||
|
||||
// ####### TODO: Also send if need to send new domainLogin data?
|
||||
if (!domainIsConnected) {
|
||||
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
|
||||
packetStream << accountInfo.getUsername();
|
||||
|
@ -474,6 +485,23 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) {
|
||||
const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken);
|
||||
packetStream << usernameSignature;
|
||||
} else {
|
||||
// ####### TODO: Only append if are going to send domain username?
|
||||
packetStream << QString(""); // Placeholder in case have domain username.
|
||||
}
|
||||
} else {
|
||||
// ####### TODO: Only append if are going to send domainUsername?
|
||||
packetStream << QString("") << QString(""); // Placeholders in case have domain username.
|
||||
}
|
||||
|
||||
// Send domain domain login data from Interface to domain server.
|
||||
if (_hasDomainAccountManager) {
|
||||
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
|
||||
if (!domainAccountManager->getUsername().isEmpty()) {
|
||||
packetStream << domainAccountManager->getUsername();
|
||||
if (!domainAccountManager->getAccessToken().isEmpty()) {
|
||||
packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -196,6 +196,8 @@ private:
|
|||
#if (PR_BUILD || DEV_BUILD)
|
||||
bool _shouldSendNewerVersion { false };
|
||||
#endif
|
||||
|
||||
bool _hasDomainAccountManager { false };
|
||||
};
|
||||
|
||||
#endif // hifi_NodeList_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
|
||||
|
@ -51,6 +52,9 @@ public:
|
|||
void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); }
|
||||
const QString& getVerifiedUserName() const { return _verifiedUserName; }
|
||||
|
||||
void setVerifiedDomainUserName(QString userName) { _verifiedDomainUserName = userName.toLower(); }
|
||||
const QString& getVerifiedDomainUserName() const { return _verifiedDomainUserName; }
|
||||
|
||||
void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }}
|
||||
QUuid getGroupID() const { return _groupID; }
|
||||
bool isGroup() const { return _groupIDSet; }
|
||||
|
@ -99,6 +103,7 @@ protected:
|
|||
QString _id;
|
||||
QUuid _rankID { QUuid() }; // 0 unless this is for a group
|
||||
QString _verifiedUserName;
|
||||
QString _verifiedDomainUserName;
|
||||
|
||||
bool _groupIDSet { false };
|
||||
QUuid _groupID;
|
||||
|
|
Loading…
Reference in a new issue