diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 08cf56b188..8d1a479407 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -863,6 +863,18 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerreadWithoutCopy(NUM_BYTES_RFC4122_UUID)); + bool hasOptionalBanParameters = false; + bool banByUsername = false; + bool banByFingerprint = false; + bool banByIP = false; + // pull optional ban parameters from the packet + if (message.data()->getSize() > NUM_BYTES_RFC4122_UUID) { + hasOptionalBanParameters = true; + message->readPrimitive(&banByUsername); + message->readPrimitive(&banByFingerprint); + message->readPrimitive(&banByIP); + } + if (!nodeUUID.isNull() && nodeUUID != sendingNode->getUUID()) { // make sure we actually have a node with this UUID auto limitedNodeList = DependencyManager::get(); @@ -881,24 +893,40 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointergetPermissions().getKey()]; - - newPermissions = !hadPermissions || userPermissions->can(NodePermissions::Permission::canConnectToDomain); - - // ensure that the connect permission is clear - userPermissions->clear(NodePermissions::Permission::canConnectToDomain); + // if we have optional ban parameters, we should ban the username based on the parameter + if (hasOptionalBanParameters) { + if (banByUsername) { + // check if there were already permissions + bool hadPermissions = havePermissionsForName(verifiedUsername); + + // grab or create permissions for the given username + auto userPermissions = _agentPermissions[matchingNode->getPermissions().getKey()]; + + newPermissions = !hadPermissions || userPermissions->can(NodePermissions::Permission::canConnectToDomain); + + // ensure that the connect permission is clear + userPermissions->clear(NodePermissions::Permission::canConnectToDomain); + } + } else { + // check if there were already permissions + bool hadPermissions = havePermissionsForName(verifiedUsername); + + // grab or create permissions for the given username + auto userPermissions = _agentPermissions[matchingNode->getPermissions().getKey()]; + + newPermissions = !hadPermissions || userPermissions->can(NodePermissions::Permission::canConnectToDomain); + + // ensure that the connect permission is clear + userPermissions->clear(NodePermissions::Permission::canConnectToDomain); + } } // if we didn't have a username, or this domain-server uses the "multi-kick" setting to // kick logged in users via username AND machine fingerprint (or IP as fallback) // then we remove connect permissions for the machine fingerprint (or IP as fallback) const QString MULTI_KICK_SETTINGS_KEYPATH = "security.multi_kick_logged_in"; - - if (verifiedUsername.isEmpty() || valueOrDefaultValueForKeyPath(MULTI_KICK_SETTINGS_KEYPATH).toBool()) { + qDebug() << "verifiedUsername.isEmpty()" << verifiedUsername.isEmpty(); + if (banByFingerprint || verifiedUsername.isEmpty() || valueOrDefaultValueForKeyPath(MULTI_KICK_SETTINGS_KEYPATH).toBool()) { // remove connect permissions for the machine fingerprint DomainServerNodeData* nodeData = static_cast(matchingNode->getLinkedData()); if (nodeData) { @@ -923,36 +951,39 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerclear(NodePermissions::Permission::canConnectToDomain); } } else { - // if no node data, all we can do is IP address - auto& kickAddress = matchingNode->getActiveSocket() - ? matchingNode->getActiveSocket()->getAddress() - : matchingNode->getPublicSocket().getAddress(); + // if no node data, all we can do is ban by IP address + banByIP = true; + } + } + + if (banByIP) { + auto& kickAddress = matchingNode->getActiveSocket() + ? matchingNode->getActiveSocket()->getAddress() + : matchingNode->getPublicSocket().getAddress(); - // probably isLoopback covers it, as whenever I try to ban an agent on same machine as the domain-server - // it is always 127.0.0.1, but looking at the public and local addresses just to be sure - // TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we - // do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it. - if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() || - kickAddress == limitedNodeList->getLocalSockAddr().getAddress() || - kickAddress.isLoopback() ) { - qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest"; - return; - } + // probably isLoopback covers it, as whenever I try to ban an agent on same machine as the domain-server + // it is always 127.0.0.1, but looking at the public and local addresses just to be sure + // TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we + // do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it. + if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() || + kickAddress == limitedNodeList->getLocalSockAddr().getAddress() || + kickAddress.isLoopback() ) { + qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest"; + return; + } + NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid()); - NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid()); + // check if there were already permissions for the IP + bool hadIPPermissions = hasPermissionsForIP(kickAddress); - // check if there were already permissions for the IP - bool hadIPPermissions = hasPermissionsForIP(kickAddress); + // grab or create permissions for the given IP address + auto ipPermissions = _ipPermissions[ipAddressKey]; - // grab or create permissions for the given IP address - auto ipPermissions = _ipPermissions[ipAddressKey]; + if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) { + newPermissions = true; - if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) { - newPermissions = true; - - ipPermissions->clear(NodePermissions::Permission::canConnectToDomain); - } + ipPermissions->clear(NodePermissions::Permission::canConnectToDomain); } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index eec1efbd99..f2e17c8dc1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2493,7 +2493,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return viewFrustum.getPosition(); }); - DependencyManager::get()->setKickConfirmationOperator([this] (const QUuid& nodeID) { userKickConfirmation(nodeID); }); + DependencyManager::get()->setKickConfirmationOperator([this] (const QUuid& nodeID, bool banByUsername, bool banByFingerprint, bool banByIP) { userKickConfirmation(nodeID, banByUsername, banByFingerprint, banByIP); }); render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([=](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { bool isTablet = url == TabletScriptingInterface::QML; @@ -3575,7 +3575,7 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { _desktopRootItemCreated = true; } -void Application::userKickConfirmation(const QUuid& nodeID) { +void Application::userKickConfirmation(const QUuid& nodeID, bool banByUsername, bool banByFingerprint, bool banByIP) { auto avatarHashMap = DependencyManager::get(); auto avatar = avatarHashMap->getAvatarBySessionID(nodeID); @@ -3600,7 +3600,7 @@ void Application::userKickConfirmation(const QUuid& nodeID) { // ask the NodeList to kick the user with the given session ID if (yes) { - DependencyManager::get()->kickNodeBySessionID(nodeID); + DependencyManager::get()->kickNodeBySessionID(nodeID, banByUsername, banByFingerprint, banByIP); } DependencyManager::get()->setWaitForKickResponse(false); diff --git a/interface/src/Application.h b/interface/src/Application.h index 5cb5fdd5c0..65638b92db 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -608,7 +608,7 @@ private: void toggleTabletUI(bool shouldOpen = false) const; bool shouldCaptureMouse() const; - void userKickConfirmation(const QUuid& nodeID); + void userKickConfirmation(const QUuid& nodeID, bool banByUsername, bool banByFingerprint, bool banByIP); MainWindow* _window; QElapsedTimer& _sessionRunTimer; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index a523a7ff36..6f3f774452 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -1263,17 +1263,21 @@ float NodeList::getInjectorGain() { return _injectorGain; } -void NodeList::kickNodeBySessionID(const QUuid& nodeID) { +void NodeList::kickNodeBySessionID(const QUuid& nodeID, bool banByUsername, bool banByFingerprint, bool banByIP) { // send a request to domain-server to kick the node with the given session ID // the domain-server will handle the persistence of the kick (via username or IP) if (!nodeID.isNull() && getSessionUUID() != nodeID ) { if (getThisNodeCanKick()) { // setup the packet - auto kickPacket = NLPacket::create(PacketType::NodeKickRequest, NUM_BYTES_RFC4122_UUID, true); + auto kickPacket = NLPacket::create(PacketType::NodeKickRequest, NUM_BYTES_RFC4122_UUID + (sizeof(bool) * 3), true); // write the node ID to the packet kickPacket->write(nodeID.toRfc4122()); + // write the ban parameters to the packet + kickPacket->writePrimitive(banByUsername); + kickPacket->writePrimitive(banByFingerprint); + kickPacket->writePrimitive(banByIP); qCDebug(networking) << "Sending packet to kick node" << uuidStringWithoutCurlyBraces(nodeID); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 4954c53c84..3573db1b9f 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -86,7 +86,7 @@ public: void setInjectorGain(float gain); float getInjectorGain(); - void kickNodeBySessionID(const QUuid& nodeID); + void kickNodeBySessionID(const QUuid& nodeID, bool banByUsername = true, bool banByFingerprint = true, bool banByIP = false); void muteNodeBySessionID(const QUuid& nodeID); void requestUsernameFromSessionID(const QUuid& nodeID); bool getRequestsDomainListData() { return _requestsDomainListData; } diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp index 9beb52f20a..c22c1d270e 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.cpp +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -4,6 +4,7 @@ // // Created by Stephen Birarda on 2016-07-11. // Copyright 2016 High Fidelity, Inc. +// Copyright 2021 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,15 +52,15 @@ float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) { return DependencyManager::get()->getAvatarGain(nodeID); } -void UsersScriptingInterface::kick(const QUuid& nodeID) { +void UsersScriptingInterface::kick(const QUuid& nodeID, bool banByUsername, bool banByFingerprint, bool banByIP) { if (_kickConfirmationOperator) { bool waitingForKickResponse = _kickResponseLock.resultWithReadLock([&] { return _waitingForKickResponse; }); if (getCanKick() && !waitingForKickResponse) { - _kickConfirmationOperator(nodeID); + _kickConfirmationOperator(nodeID, banByUsername, banByFingerprint, banByIP); } } else { - DependencyManager::get()->kickNodeBySessionID(nodeID); + DependencyManager::get()->kickNodeBySessionID(nodeID, banByUsername, banByFingerprint, banByIP); } } diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 3b0666481a..ad86189afa 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -4,6 +4,7 @@ // // Created by Stephen Birarda on 2016-07-11. // Copyright 2016 High Fidelity, Inc. +// Copyright 2021 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 @@ -41,7 +42,7 @@ class UsersScriptingInterface : public QObject, public Dependency { public: UsersScriptingInterface(); - void setKickConfirmationOperator(std::function kickConfirmationOperator) { + void setKickConfirmationOperator(std::function kickConfirmationOperator) { _kickConfirmationOperator = kickConfirmationOperator; } @@ -112,12 +113,15 @@ public slots: /**jsdoc * Kicks and bans a user. This removes them from the server and prevents them from returning. The ban is by user name if - * available, or machine fingerprint otherwise. + * available and by machine fingerprint. *

This function only works if you're an administrator of the domain you're in.

* @function Users.kick * @param {Uuid} sessionID - The session ID of the user to kick and ban. + * @param {boolean} [banByUsername=true] - Should ban the user's username. + * @param {boolean} [banByFingerprint=true] - Should ban the user's machine fingerprint. + * @param {boolean} [banByIP=false] - Should ban the user's IP address. */ - void kick(const QUuid& nodeID); + void kick(const QUuid& nodeID, bool banByUsername = true, bool banByFingerprint = true, bool banByIP = false); /**jsdoc * Mutes a user's microphone for everyone. The mute is not permanent: the user can unmute themselves. @@ -237,7 +241,7 @@ private: bool getRequestsDomainListData(); void setRequestsDomainListData(bool requests); - std::function _kickConfirmationOperator; + std::function _kickConfirmationOperator; ReadWriteLockable _kickResponseLock; bool _waitingForKickResponse { false };