mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 03:13:51 +02:00
Merge pull request #571 from ctrlaltdavid/feature/oauth2-login-required
Domain login groups and "not authorized on domain"
This commit is contained in:
commit
bcb469a29d
6 changed files with 177 additions and 26 deletions
|
@ -89,10 +89,12 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
|
||||
SharedNodePointer node;
|
||||
QString username;
|
||||
QString domainUsername;
|
||||
if (pendingAssignment != _pendingAssignedNodes.end()) {
|
||||
node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second);
|
||||
} 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(QSharedPointer<ReceivedMessag
|
|||
if (message->getBytesLeftToRead() > 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) {
|
||||
|
@ -142,8 +154,11 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
}
|
||||
}
|
||||
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
|
||||
const QString& hardwareAddress, const QUuid& machineFingerprint) {
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername,
|
||||
QString verifiedDomainUserName,
|
||||
QStringList verifiedDomainUserGroups,
|
||||
const QHostAddress& senderAddress, const QString& hardwareAddress,
|
||||
const QUuid& machineFingerprint) {
|
||||
NodePermissions userPerms;
|
||||
|
||||
userPerms.setAll(false);
|
||||
|
@ -155,6 +170,23 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
|
|||
#endif
|
||||
}
|
||||
|
||||
// If this user is a known member of an externally-hosted 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() && !verifiedDomainUserGroups.isEmpty()) {
|
||||
foreach (QString group, verifiedDomainUserGroups) {
|
||||
if (_server->_settingsManager.getAllKnownGroupNames().contains(group)) {
|
||||
userPerms |= _server->_settingsManager.getPermissionsForGroup(group, QUuid());
|
||||
//#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << group << "so:" << userPerms;
|
||||
//#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
userPerms.setVerifiedDomainUserName(verifiedDomainUserName);
|
||||
userPerms.setVerifiedDomainUserGroups(verifiedDomainUserGroups);
|
||||
}
|
||||
|
||||
if (verifiedUsername.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
#ifdef WANT_DEBUG
|
||||
|
@ -275,6 +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 verifiedDomainUserName = node->getPermissions().getVerifiedDomainUserName();
|
||||
QStringList verifiedDomainUserGroups = node->getPermissions().getVerifiedDomainUserGroups();
|
||||
NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0));
|
||||
|
||||
if (node->getPermissions().isAssignment) {
|
||||
|
@ -309,7 +343,9 @@ void DomainGatekeeper::updateNodePermissions() {
|
|||
sendingAddress == QHostAddress::LocalHost);
|
||||
}
|
||||
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint);
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUserName,
|
||||
verifiedDomainUserGroups, connectingAddr.getAddress(),
|
||||
hardwareAddress, machineFingerprint);
|
||||
}
|
||||
|
||||
node->setPermissions(userPerms);
|
||||
|
@ -392,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<LimitedNodeList>();
|
||||
|
||||
|
@ -419,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);
|
||||
|
@ -427,6 +467,41 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
} else {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because signature verification failed:" << username;
|
||||
#endif
|
||||
if (!domainHasLogin() || domainUsername.isEmpty()) {
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
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: OAuth2 corollary of metaverse code, above.
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because signature verification failed:" << username;
|
||||
#endif
|
||||
|
@ -434,12 +509,18 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
}
|
||||
}
|
||||
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress(),
|
||||
nodeConnection.hardwareAddress, nodeConnection.machineFingerprint);
|
||||
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
|
||||
|
@ -600,15 +681,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 +703,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 +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 =
|
||||
|
@ -1029,11 +1125,22 @@ void DomainGatekeeper::refreshGroupsCache() {
|
|||
|
||||
updateNodePermissions();
|
||||
|
||||
#if WANT_DEBUG
|
||||
#ifdef WANT_DEBUG
|
||||
_server->_settingsManager.debugDumpGroupsState();
|
||||
#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<quint16> sixteenBitRand;
|
||||
std::random_device randomDevice;
|
||||
|
|
|
@ -76,11 +76,17 @@ private:
|
|||
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,
|
||||
|
@ -120,13 +126,16 @@ 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,
|
||||
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, QString verifiedDomainUsername,
|
||||
QStringList verifiedDomainUserGroups, const QHostAddress& senderAddress,
|
||||
const QString& hardwareAddress, const QUuid& machineFingerprint);
|
||||
|
||||
void getGroupMemberships(const QString& username);
|
||||
// void getIsGroupMember(const QString& username, const QUuid groupID);
|
||||
void getDomainOwnerFriendsList();
|
||||
|
||||
bool domainHasLogin();
|
||||
|
||||
// Local ID management.
|
||||
void initLocalIDManagement();
|
||||
using UUIDToLocalID = std::unordered_map<QUuid, Node::LocalID> ;
|
||||
|
|
|
@ -492,7 +492,7 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> 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;
|
||||
|
||||
|
|
|
@ -172,14 +172,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 +191,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,11 +208,12 @@ public:
|
|||
enum class ConnectionRefusedReason : uint8_t {
|
||||
Unknown,
|
||||
ProtocolMismatch,
|
||||
LoginError,
|
||||
LoginErrorMetaverse,
|
||||
NotAuthorizedMetaverse,
|
||||
NotAuthorizedDomain,
|
||||
TooManyUsers,
|
||||
TimedOut
|
||||
TimedOut,
|
||||
LoginErrorDomain,
|
||||
NotAuthorizedDomain
|
||||
};
|
||||
|
||||
public slots:
|
||||
|
|
|
@ -379,6 +379,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
if (domainPacketType == PacketType::DomainConnectRequest) {
|
||||
|
||||
#if (PR_BUILD || DEV_BUILD)
|
||||
// #######
|
||||
if (_shouldSendNewerVersion) {
|
||||
domainPacket->setVersion(versionForPacketType(domainPacketType) + 1);
|
||||
}
|
||||
|
@ -474,6 +475,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,11 @@ 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 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; }
|
||||
bool isGroup() const { return _groupIDSet; }
|
||||
|
@ -99,6 +104,8 @@ protected:
|
|||
QString _id;
|
||||
QUuid _rankID { QUuid() }; // 0 unless this is for a group
|
||||
QString _verifiedUserName;
|
||||
QString _verifiedDomainUserName;
|
||||
QStringList _verifiedDomainUserGroups;
|
||||
|
||||
bool _groupIDSet { false };
|
||||
QUuid _groupID;
|
||||
|
|
Loading…
Reference in a new issue