diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 257dcaa783..dd0e4ad4a1 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -845,6 +845,78 @@ } ], + "columns": [ + { + "name": "permissions_id", + "label": "" + }, + { + "name": "id_can_connect", + "label": "Connect", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_adjust_locks", + "label": "Lock / Unlock", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez", + "label": "Rez", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp", + "label": "Rez Temporary", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_write_to_asset_server", + "label": "Write Assets", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_connect_past_max_capacity", + "label": "Ignore Max Capacity", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_kick", + "label": "Kick Users", + "type": "checkbox", + "editable": true, + "default": false + } + ] + }, + { + "name": "machine_fingerprint_permissions", + "type": "table", + "caption": "Permissions for Users with Machine Fingerprints", + "can_add_new_rows": true, + "groups": [ + { + "label": "Machine Fingerprint", + "span": 1 + }, + { + "label": "Permissions ?", + "span": 7 + } + ], + "columns": [ { "name": "permissions_id", diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index f55a2073d1..97f3e1a697 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -120,19 +120,21 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointersetPlaceName(nodeConnection.placeName); qDebug() << "Allowed connection from node" << uuidStringWithoutCurlyBraces(node->getUUID()) - << "on" << message->getSenderSockAddr() << "with MAC" << nodeConnection.hardwareAddress; + << "on" << message->getSenderSockAddr() << "with MAC" << nodeConnection.hardwareAddress + << "and machine fingerprint" << nodeConnection.machineFingerprint; // signal that we just connected a node so the DomainServer can get it a list // and broadcast its presence right away emit connectedNode(node); } else { qDebug() << "Refusing connection from node at" << message->getSenderSockAddr() - << "with hardware address" << nodeConnection.hardwareAddress; + << "with hardware address" << nodeConnection.hardwareAddress + << "and machine fingerprint" << nodeConnection.machineFingerprint; } } -NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, - const QHostAddress& senderAddress, const QString& hardwareAddress) { +NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress, + const QString& hardwareAddress, const QUuid& machineFingerprint) { NodePermissions userPerms; userPerms.setAll(false); @@ -155,6 +157,11 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin #ifdef WANT_DEBUG qDebug() << "| user-permissions: specific MAC matches, so:" << userPerms; +#endif + } else if (_server->_settingsManager.hasPermissionsForMachineFingerprint(machineFingerprint)) { + userPerms = _server->_settingsManager.getPermissionsForMachineFingerprint(machineFingerprint); +#ifdef WANT_DEBUG + qDebug(() << "| user-permissions: specific Machine Fingerprint matches, so: " << userPerms; #endif } else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) { // this user comes from an IP we have in our permissions table, apply those permissions @@ -274,13 +281,15 @@ void DomainGatekeeper::updateNodePermissions() { HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket(); QString hardwareAddress; + QUuid machineFingerprint; DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); if (nodeData) { hardwareAddress = nodeData->getHardwareAddress(); + machineFingerprint = nodeData->getMachineFingerprint(); } - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress); + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint); } node->setPermissions(userPerms); @@ -334,6 +343,8 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo nodeData->setWalletUUID(it->second.getWalletUUID()); nodeData->setNodeVersion(it->second.getNodeVersion()); nodeData->setHardwareAddress(nodeConnection.hardwareAddress); + nodeData->setMachineFingerprint(nodeConnection.machineFingerprint); + nodeData->setWasAssigned(true); // cleanup the PendingAssignedNodeData for this assignment now that it's connecting @@ -396,7 +407,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect } userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress(), - nodeConnection.hardwareAddress); + nodeConnection.hardwareAddress, nodeConnection.machineFingerprint); if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", @@ -455,6 +466,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // set the hardware address passed in the connect request nodeData->setHardwareAddress(nodeConnection.hardwareAddress); + // set the machine fingerprint passed in the connect request + nodeData->setMachineFingerprint(nodeConnection.machineFingerprint); + // also add an interpolation to DomainServerNodeData so that servers can get username in stats nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, uuidStringWithoutCurlyBraces(newNode->getUUID()), username); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index b17d0f61a4..163f255411 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -107,8 +107,8 @@ private: QSet _domainOwnerFriends; // keep track of friends of the domain owner QSet _inFlightGroupMembershipsRequests; // keep track of which we've already asked for - NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, - const QHostAddress& senderAddress, const QString& hardwareAddress); + NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress, + const QString& hardwareAddress, const QUuid& machineFingerprint); void getGroupMemberships(const QString& username); // void getIsGroupMember(const QString& username, const QUuid groupID); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 3dd75a19b2..0f7923519b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -532,6 +532,7 @@ void DomainServer::setupNodeListAndAssignments() { // NodeList won't be available to the settings manager when it is created, so call registerListener here packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket"); + packetReceiver.registerListener(PacketType::UsernameFromIDRequest, &_settingsManager, "processUsernameFromIDRequestPacket"); // register the gatekeeper for the packets it needs to receive packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index ff637844f1..db41c77cf2 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -56,7 +56,10 @@ public: void setHardwareAddress(const QString& hardwareAddress) { _hardwareAddress = hardwareAddress; } const QString& getHardwareAddress() { return _hardwareAddress; } - + + void setMachineFingerprint(const QUuid& machineFingerprint) { _machineFingerprint = machineFingerprint; } + const QUuid& getMachineFingerprint() { return _machineFingerprint; } + void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue); void removeOverrideForKey(const QString& key, const QString& value); @@ -85,6 +88,7 @@ private: NodeSet _nodeInterestSet; QString _nodeVersion; QString _hardwareAddress; + QUuid _machineFingerprint; QString _placeName; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 18a46e0658..05cec07f80 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -444,6 +444,9 @@ void DomainServerSettingsManager::packPermissions() { // save settings for MAC addresses packPermissionsForMap("permissions", _macPermissions, MAC_PERMISSIONS_KEYPATH); + // save settings for Machine Fingerprint + packPermissionsForMap("permissions", _machineFingerprintPermissions, MACHINE_FINGERPRINT_PERMISSIONS_KEYPATH); + // save settings for groups packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH); @@ -522,6 +525,18 @@ void DomainServerSettingsManager::unpackPermissions() { } }); + needPack |= unpackPermissionsForKeypath(MACHINE_FINGERPRINT_PERMISSIONS_KEYPATH, &_machineFingerprintPermissions, + [&](NodePermissionsPointer perms){ + // make sure that this permission row has valid machine fingerprint + if (QUuid(perms->getKey().first) == QUuid()) { + _machineFingerprintPermissions.remove(perms->getKey()); + + // we removed a row, so we'll need a re-pack + needPack = true; + } + + }); + needPack |= unpackPermissionsForKeypath(GROUP_PERMISSIONS_KEYPATH, &_groupPermissions, [&](NodePermissionsPointer perms){ @@ -575,7 +590,9 @@ void DomainServerSettingsManager::unpackPermissions() { QList> permissionsSets; permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() << _groupPermissions.get() << _groupForbiddens.get() - << _ipPermissions.get() << _macPermissions.get(); + << _ipPermissions.get() << _macPermissions.get() + << _machineFingerprintPermissions.get(); + foreach (auto permissionSet, permissionsSets) { QHashIterator i(permissionSet); while (i.hasNext()) { @@ -707,9 +724,10 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerclear(NodePermissions::Permission::canConnectToDomain); } - // potentially remove connect permissions for the MAC address + // potentially remove connect permissions for the MAC address and machine fingerprint DomainServerNodeData* nodeData = reinterpret_cast(matchingNode->getLinkedData()); if (nodeData) { + // mac address first NodePermissionsKey macAddressKey(nodeData->getHardwareAddress(), 0); bool hadMACPermissions = hasPermissionsForMAC(nodeData->getHardwareAddress()); @@ -721,6 +739,18 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerclear(NodePermissions::Permission::canConnectToDomain); } + + // now for machine fingerprint + NodePermissionsKey machineFingerprintKey(nodeData->getMachineFingerprint().toString(), 0); + + bool hadFingerprintPermissions = hasPermissionsForMachineFingerprint(nodeData->getMachineFingerprint()); + + auto fingerprintPermissions = _machineFingerprintPermissions[machineFingerprintKey]; + + if (!hadFingerprintPermissions || fingerprintPermissions->can(NodePermissions::Permission::canConnectToDomain)) { + newPermissions = true; + fingerprintPermissions->clear(NodePermissions::Permission::canConnectToDomain); + } } } @@ -748,6 +778,45 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer message, SharedNodePointer sendingNode) { + // Before we do any processing on this packet, make sure it comes from a node that is allowed to kick (is an admin) + if (sendingNode->getCanKick()) { + // From the packet, pull the UUID we're identifying + QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + if (!nodeUUID.isNull()) { + // First, make sure we actually have a node with this UUID + auto limitedNodeList = DependencyManager::get(); + auto matchingNode = limitedNodeList->nodeWithUUID(nodeUUID); + + // If we do have a matching node... + if (matchingNode) { + // It's time to figure out the username + QString verifiedUsername = matchingNode->getPermissions().getVerifiedUserName(); + + // Setup the packet + auto usernameFromIDReplyPacket = NLPacket::create(PacketType::UsernameFromIDReply); + usernameFromIDReplyPacket->write(nodeUUID.toRfc4122()); + usernameFromIDReplyPacket->writeString(verifiedUsername); + + qDebug() << "Sending username" << verifiedUsername << "associated with node" << nodeUUID; + + // Ship it! + limitedNodeList->sendPacket(std::move(usernameFromIDReplyPacket), *sendingNode); + } else { + qWarning() << "Node username request received for unknown node. Refusing to process."; + } + } else { + qWarning() << "Node username request received for invalid node ID. Refusing to process."; + } + + } else { + qWarning() << "Refusing to process a username request packet from node" << uuidStringWithoutCurlyBraces(sendingNode->getUUID()) + << "that does not have kick permissions."; + } +} + QStringList DomainServerSettingsManager::getAllNames() const { QStringList result; foreach (auto key, _agentPermissions.keys()) { @@ -795,6 +864,16 @@ NodePermissions DomainServerSettingsManager::getPermissionsForMAC(const QString& return nullPermissions; } +NodePermissions DomainServerSettingsManager::getPermissionsForMachineFingerprint(const QUuid& machineFingerprint) const { + NodePermissionsKey fingerprintKey = NodePermissionsKey(machineFingerprint.toString(), 0); + if (_machineFingerprintPermissions.contains(fingerprintKey)) { + return *(_machineFingerprintPermissions[fingerprintKey].get()); + } + NodePermissions nullPermissions; + nullPermissions.setAll(false); + return nullPermissions; +} + NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, QUuid rankID) const { NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID); if (_groupPermissions.contains(groupRankKey)) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 06ad7decd1..2b5f9518a0 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -32,6 +32,7 @@ const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permission const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions"; const QString MAC_PERMISSIONS_KEYPATH = "security.mac_permissions"; +const QString MACHINE_FINGERPRINT_PERMISSIONS_KEYPATH = "security.machine_fingerprint_permissions"; const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions"; const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; @@ -70,6 +71,10 @@ public: bool hasPermissionsForMAC(const QString& macAddress) const { return _macPermissions.contains(macAddress, 0); } NodePermissions getPermissionsForMAC(const QString& macAddress) const; + // these give access to permissions for specific machine fingerprints from the domain-server settings page + bool hasPermissionsForMachineFingerprint(const QUuid& machineFingerprint) { return _machineFingerprintPermissions.contains(machineFingerprint.toString(), 0); } + NodePermissions getPermissionsForMachineFingerprint(const QUuid& machineFingerprint) const; + // these give access to permissions for specific groups from the domain-server settings page bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const { return _groupPermissions.contains(groupName, rankID); @@ -113,6 +118,7 @@ public slots: private slots: void processSettingsRequestPacket(QSharedPointer message); void processNodeKickRequestPacket(QSharedPointer message, SharedNodePointer sendingNode); + void processUsernameFromIDRequestPacket(QSharedPointer message, SharedNodePointer sendingNode); private: QStringList _argumentList; @@ -151,6 +157,7 @@ private: NodePermissionsMap _ipPermissions; // permissions granted by node IP address NodePermissionsMap _macPermissions; // permissions granted by node MAC address + NodePermissionsMap _machineFingerprintPermissions; // permissions granted by Machine Fingerprint NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 93d6802d84..0a3782d79b 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -32,6 +32,9 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c // read the hardware address sent by the client dataStream >> newHeader.hardwareAddress; + + // now the machine fingerprint + dataStream >> newHeader.machineFingerprint; } dataStream >> newHeader.nodeType diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index bcbbdf0a40..dd9ca6b650 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -29,6 +29,7 @@ public: QList interestList; QString placeName; QString hardwareAddress; + QUuid machineFingerprint; QByteArray protocolVersion; }; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 382b8fc962..7a8dc4722e 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -72,6 +72,24 @@ Rectangle { table.selection.deselect(userIndex); } break; + // Received an "updateUsername()" request from the JS + case 'updateUsername': + // The User ID (UUID) is the first parameter in the message. + var userId = message.params[0]; + // The text that goes in the userName field is the second parameter in the message. + var userName = message.params[1]; + // If the userId is empty, we're updating "myData". + if (!userId) { + myData.userName = userName; + myCard.userName = userName; // Defensive programming + } else { + // Get the index in userModel and userData associated with the passed UUID + var userIndex = findSessionIndex(userId); + // Set the userName appropriately + userModel.get(userIndex).userName = userName; + userData[userIndex].userName = userName; // Defensive programming + } + break; default: console.log('Unrecognized message:', JSON.stringify(message)); } diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index 8ef67cb2a4..288e98d5a5 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -17,6 +17,7 @@ find_package(TBB REQUIRED) if (APPLE) find_library(FRAMEWORK_IOKIT IOKit) + find_library(CORE_FOUNDATION CoreFoundation) endif () if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include") @@ -32,7 +33,7 @@ target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES} ${TBB_LIBRARIES}) # IOKit is needed for getting machine fingerprint if (APPLE) - target_link_libraries(${TARGET_NAME} ${FRAMEWORK_IOKIT}) + target_link_libraries(${TARGET_NAME} ${FRAMEWORK_IOKIT} ${CORE_FOUNDATION}) endif (APPLE) # libcrypto uses dlopen in libdl diff --git a/libraries/networking/src/FingerprintUtils.cpp b/libraries/networking/src/FingerprintUtils.cpp index 42a617e93d..70a2f8ab2c 100644 --- a/libraries/networking/src/FingerprintUtils.cpp +++ b/libraries/networking/src/FingerprintUtils.cpp @@ -10,8 +10,13 @@ // #include "FingerprintUtils.h" + #include + #include +#include +#include + #ifdef Q_OS_WIN #include #include @@ -44,7 +49,15 @@ QString FingerprintUtils::getMachineFingerprintString() { HRESULT hres; IWbemLocator *pLoc = NULL; - // initialize com + // initialize com. Interface already does, but other + // users of this lib don't necessarily do so. + hres = CoInitializeEx(0, COINIT_MULTITHREADED); + if (FAILED(hres)) { + qDebug() << "Failed to initialize COM library!"; + return uuidString; + } + + // initialize WbemLocator hres = CoCreateInstance( CLSID_WbemLocator, 0, @@ -164,6 +177,11 @@ QUuid FingerprintUtils::getMachineFingerprint() { // any errors in getting the string QUuid uuid(uuidString); if (uuid == QUuid()) { + // if you cannot read a fallback key cuz we aren't saving them, just generate one for + // this session and move on + if (DependencyManager::get().isNull()) { + return QUuid::createUuid(); + } // read fallback key (if any) Settings settings; uuid = QUuid(settings.value(FALLBACK_FINGERPRINT_KEY).toString()); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 29b7ea0385..f76189b13a 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -27,6 +27,7 @@ #include "AddressManager.h" #include "Assignment.h" #include "HifiSockAddr.h" +#include "FingerprintUtils.h" #include "NetworkLogging.h" #include "udt/PacketHeaders.h" @@ -127,6 +128,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) packetReceiver.registerListener(PacketType::ICEPingReply, &_domainHandler, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::DomainServerPathResponse, this, "processDomainServerPathResponse"); packetReceiver.registerListener(PacketType::DomainServerRemovedNode, this, "processDomainServerRemovedNode"); + packetReceiver.registerListener(PacketType::UsernameFromIDReply, this, "processUsernameFromIDReply"); } qint64 NodeList::sendStats(QJsonObject statsObject, HifiSockAddr destination) { @@ -370,6 +372,10 @@ void NodeList::sendDomainServerCheckIn() { } packetStream << hardwareAddress; + + // add in machine fingerprint + QUuid machineFingerprint = FingerprintUtils::getMachineFingerprint(); + packetStream << machineFingerprint; } // pack our data to send to the domain-server including @@ -889,3 +895,36 @@ void NodeList::muteNodeBySessionID(const QUuid& nodeID) { } } + +void NodeList::requestUsernameFromSessionID(const QUuid& nodeID) { + // send a request to domain-server to get the username associated with the given session ID + if (getThisNodeCanKick() || nodeID.isNull()) { + // setup the packet + auto usernameFromIDRequestPacket = NLPacket::create(PacketType::UsernameFromIDRequest, NUM_BYTES_RFC4122_UUID, true); + + // write the node ID to the packet + if (nodeID.isNull()) { + usernameFromIDRequestPacket->write(getSessionUUID().toRfc4122()); + } else { + usernameFromIDRequestPacket->write(nodeID.toRfc4122()); + } + + qDebug() << "Sending packet to get username of node" << uuidStringWithoutCurlyBraces(nodeID); + + sendPacket(std::move(usernameFromIDRequestPacket), _domainHandler.getSockAddr()); + } else { + qWarning() << "You do not have permissions to kick in this domain." + << "Request to get the username of node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent"; + } +} + +void NodeList::processUsernameFromIDReply(QSharedPointer message) { + // read the UUID from the packet + QString nodeUUIDString = (QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID))).toString(); + // read the username from the packet + QString username = message->readString(); + + qDebug() << "Got username" << username << "for node" << nodeUUIDString; + + emit usernameFromIDReply(nodeUUIDString, username); +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 9a9b85b851..d10c790f45 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -81,6 +81,7 @@ public: void kickNodeBySessionID(const QUuid& nodeID); void muteNodeBySessionID(const QUuid& nodeID); + void requestUsernameFromSessionID(const QUuid& nodeID); public slots: void reset(); @@ -99,6 +100,8 @@ public slots: void processICEPingPacket(QSharedPointer message); + void processUsernameFromIDReply(QSharedPointer message); + #if (PR_BUILD || DEV_BUILD) void toggleSendNewerDSConnectVersion(bool shouldSendNewerVersion) { _shouldSendNewerVersion = shouldSendNewerVersion; } #endif @@ -108,6 +111,7 @@ signals: void receivedDomainServerList(); void ignoredNode(const QUuid& nodeID); void ignoreRadiusEnabledChanged(bool isIgnored); + void usernameFromIDReply(const QString& nodeID, const QString& username); private slots: void stopKeepalivePingTimer(); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 79776e2d54..339f41a5e8 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -26,8 +26,8 @@ const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery << PacketType::OctreeDataNack << PacketType::EntityEditNack << PacketType::DomainListRequest << PacketType::StopNode - << PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest - << PacketType::NodeMuteRequest; + << PacketType::DomainDisconnectRequest << PacketType::UsernameFromIDRequest + << PacketType::NodeKickRequest << PacketType::NodeMuteRequest; const QSet NON_SOURCED_PACKETS = QSet() << PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment @@ -39,12 +39,12 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat << PacketType::ICEServerHeartbeatACK << PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode - << PacketType::DomainServerRemovedNode; + << PacketType::DomainServerRemovedNode << PacketType::UsernameFromIDReply; PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { case PacketType::DomainList: - return static_cast(DomainListVersion::PermissionsGrid); + return static_cast(DomainListVersion::GetUsernameFromUUIDSupport); case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: @@ -67,7 +67,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(DomainConnectionDeniedVersion::IncludesExtraInfo); case PacketType::DomainConnectRequest: - return static_cast(DomainConnectRequestVersion::HasMACAddress); + return static_cast(DomainConnectRequestVersion::HasMachineFingerprint); case PacketType::DomainServerAddedNode: return static_cast(DomainServerAddedNodeVersion::PermissionsGrid); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 778cce4c8d..ef0c63c9ce 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -101,7 +101,9 @@ public: NodeKickRequest, NodeMuteRequest, RadiusIgnoreRequest, - LAST_PACKET_TYPE = RadiusIgnoreRequest + UsernameFromIDRequest, + UsernameFromIDReply, + LAST_PACKET_TYPE = UsernameFromIDReply }; }; @@ -211,7 +213,8 @@ enum class DomainConnectRequestVersion : PacketVersion { NoHostname = 17, HasHostname, HasProtocolVersions, - HasMACAddress + HasMACAddress, + HasMachineFingerprint }; enum class DomainConnectionDeniedVersion : PacketVersion { @@ -227,7 +230,8 @@ enum class DomainServerAddedNodeVersion : PacketVersion { enum class DomainListVersion : PacketVersion { PrePermissionsGrid = 18, - PermissionsGrid + PermissionsGrid, + GetUsernameFromUUIDSupport }; enum class AudioVersion : PacketVersion { diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp index 83181f7509..191952e354 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.cpp +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -18,6 +18,7 @@ UsersScriptingInterface::UsersScriptingInterface() { auto nodeList = DependencyManager::get(); connect(nodeList.data(), &LimitedNodeList::canKickChanged, this, &UsersScriptingInterface::canKickChanged); connect(nodeList.data(), &NodeList::ignoreRadiusEnabledChanged, this, &UsersScriptingInterface::ignoreRadiusEnabledChanged); + connect(nodeList.data(), &NodeList::usernameFromIDReply, this, &UsersScriptingInterface::usernameFromIDReply); } void UsersScriptingInterface::ignore(const QUuid& nodeID) { @@ -35,6 +36,11 @@ void UsersScriptingInterface::mute(const QUuid& nodeID) { DependencyManager::get()->muteNodeBySessionID(nodeID); } +void UsersScriptingInterface::requestUsernameFromID(const QUuid& nodeID) { + // ask the Domain Server via the NodeList for the username associated with the given session ID + DependencyManager::get()->requestUsernameFromSessionID(nodeID); +} + bool UsersScriptingInterface::getCanKick() { // ask the NodeList to return our ability to kick return DependencyManager::get()->getThisNodeCanKick(); diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 87e8c7e0d8..1936a2e914 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -51,6 +51,13 @@ public slots: */ void mute(const QUuid& nodeID); + /**jsdoc + * Returns a string containing the username associated with the given Avatar UUID + * @function Users.getUsernameFromID + * @param {nodeID} nodeID The node or session ID of the user whose username you want. + */ + void requestUsernameFromID(const QUuid& nodeID); + /**jsdoc * Returns `true` if the DomainServer will allow this Node/Avatar to make kick * @function Users.getCanKick @@ -92,6 +99,12 @@ signals: * @function Users.enteredIgnoreRadius */ void enteredIgnoreRadius(); + + /**jsdoc + * Notifies scripts of the username associated with a UUID. + * @function Users.enteredIgnoreRadius + */ + void usernameFromIDReply(const QString& nodeID, const QString& username); }; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 61d3bb00e8..4d620f2a85 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -118,9 +118,15 @@ function populateUserList() { var avatar = AvatarList.getAvatar(id); var avatarPalDatum = { displayName: avatar.sessionDisplayName, - userName: "fakeAcct" + (id || "Me"), + userName: '', sessionId: id || '' }; + // If the current user is an admin OR + // they're requesting their own username ("id" is blank)... + if (Users.canKick || !id) { + // Request the username from the given UUID + Users.requestUsernameFromID(id); + } data.push(avatarPalDatum); if (id) { // No overlay for ourself. addAvatarNode(id); @@ -129,6 +135,23 @@ function populateUserList() { }); pal.sendToQml({method: 'users', params: data}); } + +// The function that handles the reply from the server +function usernameFromIDReply(id, username) { + var data; + // If the ID we've received is our ID... + if (AvatarList.getAvatar('').sessionUUID === id) { + // Set the data to contain specific strings. + data = ['', username + ' (hidden)'] + } else { + // Set the data to contain the ID and the username+ID concat string. + data = [id, username + '/' + id]; + } + print('Username Data:', JSON.stringify(data)); + // Ship the data off to QML + pal.sendToQml({ method: 'updateUsername', params: data }); +} + var pingPong = true; function updateOverlays() { var eye = Camera.position; @@ -249,6 +272,7 @@ function onVisibileChanged() { button.clicked.connect(onClicked); pal.visibleChanged.connect(onVisibileChanged); pal.closed.connect(off); +Users.usernameFromIDReply.connect(usernameFromIDReply); // // Cleanup. @@ -258,6 +282,7 @@ Script.scriptEnding.connect(function () { toolBar.removeButton(buttonName); pal.visibleChanged.disconnect(onVisibileChanged); pal.closed.disconnect(off); + Users.usernameFromIDReply.disconnect(usernameFromIDReply); off(); });