mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge pull request #5646 from birarda/ds-cleanup
de-bloat the domain-server by adding a gatekeeper
This commit is contained in:
commit
0835b79e33
11 changed files with 846 additions and 571 deletions
611
domain-server/src/DomainGatekeeper.cpp
Normal file
611
domain-server/src/DomainGatekeeper.cpp
Normal file
|
@ -0,0 +1,611 @@
|
|||
//
|
||||
// DomainGatekeeper.cpp
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-24.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "DomainGatekeeper.h"
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <Assignment.h>
|
||||
#include <JSONBreakableMarshal.h>
|
||||
|
||||
#include "DomainServer.h"
|
||||
#include "DomainServerNodeData.h"
|
||||
|
||||
using SharedAssignmentPointer = QSharedPointer<Assignment>;
|
||||
|
||||
DomainGatekeeper::DomainGatekeeper(DomainServer* server) :
|
||||
_server(server)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
|
||||
const QUuid& walletUUID, const QString& nodeVersion) {
|
||||
_pendingAssignedNodes.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(nodeUUID),
|
||||
std::forward_as_tuple(assignmentUUID, walletUUID, nodeVersion));
|
||||
}
|
||||
|
||||
QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID) {
|
||||
auto it = _pendingAssignedNodes.find(tempUUID);
|
||||
|
||||
if (it != _pendingAssignedNodes.end()) {
|
||||
return it->second.getAssignmentUUID();
|
||||
} else {
|
||||
return QUuid();
|
||||
}
|
||||
}
|
||||
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer << NodeType::EntityServer;
|
||||
|
||||
void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<NLPacket> packet) {
|
||||
if (packet->getPayloadSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDataStream packetStream(packet.data());
|
||||
|
||||
// read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it
|
||||
NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr());
|
||||
|
||||
if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) {
|
||||
qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection.";
|
||||
return;
|
||||
}
|
||||
|
||||
// check if this connect request matches an assignment in the queue
|
||||
auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
||||
|
||||
SharedNodePointer node;
|
||||
|
||||
if (pendingAssignment != _pendingAssignedNodes.end()) {
|
||||
node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second);
|
||||
} else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) {
|
||||
QString username;
|
||||
QByteArray usernameSignature;
|
||||
|
||||
if (packet->bytesLeftToRead() > 0) {
|
||||
// read username from packet
|
||||
packetStream >> username;
|
||||
|
||||
if (packet->bytesLeftToRead() > 0) {
|
||||
// read user signature from packet
|
||||
packetStream >> usernameSignature;
|
||||
}
|
||||
}
|
||||
|
||||
node = processAgentConnectRequest(nodeConnection, username, usernameSignature);
|
||||
}
|
||||
|
||||
if (node) {
|
||||
// set the sending sock addr and node interest set on this node
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
nodeData->setSendingSockAddr(packet->getSenderSockAddr());
|
||||
nodeData->setNodeInterestSet(nodeConnection.interestList.toSet());
|
||||
|
||||
// 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" << packet->getSenderSockAddr();
|
||||
}
|
||||
}
|
||||
|
||||
SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const PendingAssignedNodeData& pendingAssignment) {
|
||||
|
||||
// make sure this matches an assignment the DS told us we sent out
|
||||
auto it = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
||||
|
||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||
|
||||
if (it != _pendingAssignedNodes.end()) {
|
||||
// find the matching queued static assignment in DS queue
|
||||
matchingQueuedAssignment = _server->dequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType);
|
||||
|
||||
if (matchingQueuedAssignment) {
|
||||
qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID)
|
||||
<< "matches unfulfilled assignment"
|
||||
<< uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID());
|
||||
} else {
|
||||
// this is a node connecting to fulfill an assignment that doesn't exist
|
||||
// don't reply back to them so they cycle back and re-request an assignment
|
||||
qDebug() << "No match for assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID);
|
||||
return SharedNodePointer();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "No assignment was deployed with UUID" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID);
|
||||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
// add the new node
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
// set assignment related data on the linked data for this node
|
||||
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
||||
nodeData->setWalletUUID(it->second.getWalletUUID());
|
||||
nodeData->setNodeVersion(it->second.getNodeVersion());
|
||||
|
||||
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
||||
_pendingAssignedNodes.erase(it);
|
||||
|
||||
// always allow assignment clients to create and destroy entities
|
||||
newNode->setCanAdjustLocks(true);
|
||||
newNode->setCanRez(true);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
||||
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
|
||||
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
|
||||
|
||||
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
const QByteArray& usernameSignature) {
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
bool isRestrictingAccess =
|
||||
_server->_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
||||
|
||||
// check if this user is on our local machine - if this is true they are always allowed to connect
|
||||
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
||||
bool isLocalUser =
|
||||
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
||||
|
||||
// if we're using restricted access and this user is not local make sure we got a user signature
|
||||
if (isRestrictingAccess && !isLocalUser) {
|
||||
if (!username.isEmpty()) {
|
||||
if (usernameSignature.isEmpty()) {
|
||||
// if user didn't include usernameSignature in connect request, send a connectionToken packet
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool verifiedUsername = false;
|
||||
|
||||
// if we do not have a local user we need to subject them to our verification and capacity checks
|
||||
if (!isLocalUser) {
|
||||
|
||||
// check if we need to look at the username signature
|
||||
if (isRestrictingAccess) {
|
||||
if (isVerifiedAllowedUser(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// we verified the user via their username and signature - set the verifiedUsername
|
||||
// so we don't re-decrypt their sig if we're trying to exempt them from max capacity check (due to
|
||||
// being in the allowed editors list)
|
||||
verifiedUsername = true;
|
||||
} else {
|
||||
// failed to verify user - return a null shared ptr
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
if (!isWithinMaxCapacity(username, usernameSignature, verifiedUsername, nodeConnection.senderSockAddr)) {
|
||||
// we can't allow this user to connect because we are at max capacity (and they either aren't an allowed editor
|
||||
// or couldn't be verified as one)
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
// if this user is in the editors list (or if the editors list is empty) set the user's node's canAdjustLocks to true
|
||||
const QVariant* allowedEditorsVariant =
|
||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
||||
|
||||
// if the allowed editors list is empty then everyone can adjust locks
|
||||
bool canAdjustLocks = allowedEditors.empty();
|
||||
|
||||
if (allowedEditors.contains(username)) {
|
||||
// we have a non-empty allowed editors list - check if this user is verified to be in it
|
||||
if (!verifiedUsername) {
|
||||
if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) {
|
||||
// failed to verify a user that is in the allowed editors list
|
||||
|
||||
// TODO: fix public key refresh in interface/metaverse and force this check
|
||||
qDebug() << "Could not verify user" << username << "as allowed editor. In the interim this user"
|
||||
<< "will be given edit rights to avoid a thrasing of public key requests and connect requests.";
|
||||
}
|
||||
|
||||
canAdjustLocks = true;
|
||||
} else {
|
||||
// already verified this user and they are in the allowed editors list
|
||||
canAdjustLocks = true;
|
||||
}
|
||||
}
|
||||
|
||||
// check if only editors should be able to rez entities
|
||||
const QVariant* editorsAreRezzersVariant =
|
||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH);
|
||||
|
||||
bool onlyEditorsAreRezzers = false;
|
||||
if (editorsAreRezzersVariant) {
|
||||
onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool();
|
||||
}
|
||||
|
||||
bool canRez = true;
|
||||
if (onlyEditorsAreRezzers) {
|
||||
canRez = canAdjustLocks;
|
||||
}
|
||||
|
||||
// add the new node
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||
|
||||
// set the edit rights for this user
|
||||
newNode->setCanAdjustLocks(canAdjustLocks);
|
||||
newNode->setCanRez(canRez);
|
||||
|
||||
// grab the linked data for our new node so we can set the username
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
// if we have a username from the connect request, set it on the DomainServerNodeData
|
||||
nodeData->setUsername(username);
|
||||
|
||||
// also add an interpolation to JSONBreakableMarshal so that servers can get username in stats
|
||||
JSONBreakableMarshal::addInterpolationForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) {
|
||||
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
|
||||
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
|
||||
|
||||
QUuid nodeUUID;
|
||||
|
||||
if (connectedPeer) {
|
||||
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
||||
nodeUUID = nodeConnection.connectUUID;
|
||||
|
||||
if (connectedPeer->getActiveSocket()) {
|
||||
// set their discovered socket to whatever the activated socket on the network peer object was
|
||||
discoveredSocket = *connectedPeer->getActiveSocket();
|
||||
}
|
||||
} else {
|
||||
// we got a connectUUID we didn't recognize, just add the node with a new UUID
|
||||
nodeUUID = QUuid::createUuid();
|
||||
}
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeUUID, nodeConnection.nodeType,
|
||||
nodeConnection.publicSockAddr, nodeConnection.localSockAddr);
|
||||
|
||||
// So that we can send messages to this node at will - we need to activate the correct socket on this node now
|
||||
newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||
const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
||||
|
||||
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
||||
// if we do have a public key for the user, check for a signature match
|
||||
|
||||
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData());
|
||||
|
||||
// first load up the public key into an RSA struct
|
||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
||||
|
||||
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
||||
QCryptographicHash::Sha256);
|
||||
|
||||
if (rsaPublicKey) {
|
||||
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
|
||||
int decryptResult = RSA_verify(NID_sha256,
|
||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
||||
usernameWithToken.size(),
|
||||
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
||||
usernameSignature.size(),
|
||||
rsaPublicKey);
|
||||
|
||||
if (decryptResult == 1) {
|
||||
qDebug() << "Username signature matches for" << username << "- allowing connection.";
|
||||
|
||||
// free up the public key and remove connection token before we return
|
||||
RSA_free(rsaPublicKey);
|
||||
_connectionTokenHash.remove(username);
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if (!senderSockAddr.isNull()) {
|
||||
qDebug() << "Error decrypting username signature for " << username << "- denying connection.";
|
||||
sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr);
|
||||
}
|
||||
|
||||
// free up the public key, we don't need it anymore
|
||||
RSA_free(rsaPublicKey);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||
if (!senderSockAddr.isNull()) {
|
||||
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
||||
sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!senderSockAddr.isNull()) {
|
||||
qDebug() << "Insufficient data to decrypt username signature - denying connection.";
|
||||
sendConnectionDeniedPacket("Insufficient data", senderSockAddr);
|
||||
}
|
||||
}
|
||||
|
||||
requestUserPublicKey(username); // no joy. maybe next time?
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
if (username.isEmpty()) {
|
||||
qDebug() << "Connect request denied - no username provided.";
|
||||
|
||||
sendConnectionDeniedPacket("No username provided", senderSockAddr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList allowedUsers =
|
||||
_server->_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
|
||||
|
||||
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
|
||||
if (!verifyUserSignature(username, usernameSignature, senderSockAddr)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Connect request denied for user" << username << "- not in allowed users list.";
|
||||
sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature,
|
||||
bool& verifiedUsername,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
// find out what our maximum capacity is
|
||||
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
||||
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
||||
|
||||
if (maximumUserCapacity > 0) {
|
||||
unsigned int connectedUsers = _server->countConnectedUsers();
|
||||
|
||||
if (connectedUsers >= maximumUserCapacity) {
|
||||
// too many users, deny the new connection unless this user is an allowed editor
|
||||
|
||||
const QVariant* allowedEditorsVariant =
|
||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
||||
|
||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
||||
if (allowedEditors.contains(username)) {
|
||||
if (verifiedUsername || verifyUserSignature(username, usernameSignature, senderSockAddr)) {
|
||||
verifiedUsername = true;
|
||||
qDebug() << "Above maximum capacity -" << connectedUsers << "/" << maximumUserCapacity <<
|
||||
"but user" << username << "is in allowed editors list so will be allowed to connect.";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// deny connection from this user
|
||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
||||
sendConnectionDeniedPacket("Too many connected users.", senderSockAddr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, allowing new connection.";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void DomainGatekeeper::preloadAllowedUserPublicKeys() {
|
||||
const QVariant* allowedUsersVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH);
|
||||
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
||||
|
||||
if (allowedUsers.size() > 0) {
|
||||
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
|
||||
// going to create > 100 requests
|
||||
foreach(const QString& username, allowedUsers) {
|
||||
requestUserPublicKey(username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
||||
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||
|
||||
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||
|
||||
qDebug() << "Requesting public key for user" << username;
|
||||
|
||||
AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
|
||||
AccountManagerAuth::None,
|
||||
QNetworkAccessManager::GetOperation, callbackParams);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
// figure out which user this is for
|
||||
|
||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
QString username = usernameRegex.cap(1);
|
||||
|
||||
qDebug() << "Storing a public key for user" << username;
|
||||
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
_userPublicKeys[username] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr) {
|
||||
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
||||
QByteArray utfString = reason.toUtf8();
|
||||
quint16 payloadSize = utfString.size();
|
||||
|
||||
// setup the DomainConnectionDenied packet
|
||||
auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize));
|
||||
|
||||
// pack in the reason the connection was denied (the client displays this)
|
||||
if (payloadSize > 0) {
|
||||
connectionDeniedPacket->writePrimitive(payloadSize);
|
||||
connectionDeniedPacket->write(utfString);
|
||||
}
|
||||
|
||||
// send the packet off
|
||||
DependencyManager::get<LimitedNodeList>()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr) {
|
||||
// get the existing connection token or create a new one
|
||||
QUuid& connectionToken = _connectionTokenHash[username.toLower()];
|
||||
|
||||
if (connectionToken.isNull()) {
|
||||
connectionToken = QUuid::createUuid();
|
||||
}
|
||||
|
||||
// setup a static connection token packet
|
||||
static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID);
|
||||
|
||||
// reset the packet before each time we send
|
||||
connectionTokenPacket->reset();
|
||||
|
||||
// write the connection token
|
||||
connectionTokenPacket->write(connectionToken.toRfc4122());
|
||||
|
||||
// send off the packet unreliably
|
||||
DependencyManager::get<LimitedNodeList>()->sendUnreliablePacket(*connectionTokenPacket, senderSockAddr);
|
||||
}
|
||||
|
||||
const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS;
|
||||
|
||||
void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) {
|
||||
|
||||
if (peer->getConnectionAttempts() >= NUM_PEER_PINGS_BEFORE_DELETE) {
|
||||
// we've reached the maximum number of ping attempts
|
||||
qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID();
|
||||
qDebug() << "Removing from list of connecting peers.";
|
||||
|
||||
_icePeers.remove(peer->getUUID());
|
||||
} else {
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// send the ping packet to the local and public sockets for this node
|
||||
auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID());
|
||||
limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket());
|
||||
|
||||
auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID());
|
||||
limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket());
|
||||
|
||||
peer->incrementConnectionAttempts();
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::handlePeerPingTimeout() {
|
||||
NetworkPeer* senderPeer = qobject_cast<NetworkPeer*>(sender());
|
||||
|
||||
if (senderPeer) {
|
||||
SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID());
|
||||
|
||||
if (sharedPeer && !sharedPeer->getActiveSocket()) {
|
||||
pingPunchForConnectingPeer(sharedPeer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<NLPacket> packet) {
|
||||
// loop through the packet and pull out network peers
|
||||
// any peer we don't have we add to the hash, otherwise we update
|
||||
QDataStream iceResponseStream(packet.data());
|
||||
|
||||
NetworkPeer* receivedPeer = new NetworkPeer;
|
||||
iceResponseStream >> *receivedPeer;
|
||||
|
||||
if (!_icePeers.contains(receivedPeer->getUUID())) {
|
||||
qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
|
||||
SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer);
|
||||
_icePeers[receivedPeer->getUUID()] = newPeer;
|
||||
|
||||
// make sure we know when we should ping this peer
|
||||
connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
|
||||
|
||||
// immediately ping the new peer, and start a timer to continue pinging it until we connect to it
|
||||
newPeer->startPingTimer();
|
||||
|
||||
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
|
||||
<< newPeer->getUUID();
|
||||
|
||||
pingPunchForConnectingPeer(newPeer);
|
||||
} else {
|
||||
delete receivedPeer;
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::processICEPingPacket(QSharedPointer<NLPacket> packet) {
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*packet, limitedNodeList->getSessionUUID());
|
||||
|
||||
limitedNodeList->sendPacket(std::move(pingReplyPacket), packet->getSenderSockAddr());
|
||||
}
|
||||
|
||||
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<NLPacket> packet) {
|
||||
QDataStream packetStream(packet.data());
|
||||
|
||||
QUuid nodeUUID;
|
||||
packetStream >> nodeUUID;
|
||||
|
||||
SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID);
|
||||
|
||||
if (sendingPeer) {
|
||||
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
|
||||
sendingPeer->activateMatchingOrNewSymmetricSocket(packet->getSenderSockAddr());
|
||||
}
|
||||
}
|
93
domain-server/src/DomainGatekeeper.h
Normal file
93
domain-server/src/DomainGatekeeper.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// DomainGatekeeper.h
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-24.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_DomainGatekeeper_h
|
||||
#define hifi_DomainGatekeeper_h
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <NLPacket.h>
|
||||
#include <Node.h>
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include "NodeConnectionData.h"
|
||||
#include "PendingAssignedNodeData.h"
|
||||
|
||||
class DomainServer;
|
||||
|
||||
class DomainGatekeeper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainGatekeeper(DomainServer* server);
|
||||
|
||||
void addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
|
||||
const QUuid& walletUUID, const QString& nodeVersion);
|
||||
QUuid assignmentUUIDForPendingAssignment(const QUuid& tempUUID);
|
||||
|
||||
void preloadAllowedUserPublicKeys();
|
||||
|
||||
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
|
||||
public slots:
|
||||
void processConnectRequestPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPingPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPingReplyPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPeerInformationPacket(QSharedPointer<NLPacket> packet);
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
|
||||
signals:
|
||||
void connectedNode(SharedNodePointer node);
|
||||
|
||||
private slots:
|
||||
void handlePeerPingTimeout();
|
||||
private:
|
||||
SharedNodePointer processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const PendingAssignedNodeData& pendingAssignment);
|
||||
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
const QByteArray& usernameSignature);
|
||||
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
|
||||
|
||||
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
bool isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
bool isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature,
|
||||
bool& verifiedUsername,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
|
||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
|
||||
void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr);
|
||||
void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);
|
||||
|
||||
void requestUserPublicKey(const QString& username);
|
||||
|
||||
DomainServer* _server;
|
||||
|
||||
std::unordered_map<QUuid, PendingAssignedNodeData> _pendingAssignedNodes;
|
||||
|
||||
QHash<QUuid, SharedNetworkPeer> _icePeers;
|
||||
|
||||
QHash<QString, QUuid> _connectionTokenHash;
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_DomainGatekeeper_h
|
|
@ -9,9 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include "DomainServer.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
|
@ -38,24 +36,19 @@
|
|||
#include <LogHandler.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
|
||||
#include "DomainServer.h"
|
||||
#include "NodeConnectionData.h"
|
||||
|
||||
int const DomainServer::EXIT_CODE_REBOOT = 234923;
|
||||
|
||||
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
|
||||
|
||||
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
||||
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
|
||||
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
|
||||
|
||||
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_gatekeeper(this),
|
||||
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
||||
_httpsManager(NULL),
|
||||
_allAssignments(),
|
||||
_unfulfilledAssignments(),
|
||||
_pendingAssignedNodes(),
|
||||
_isUsingDTLS(false),
|
||||
_oauthProviderURL(),
|
||||
_oauthClientID(),
|
||||
|
@ -94,6 +87,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||
|
||||
// make sure we hear about newly connected nodes from our gatekeeper
|
||||
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
||||
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
|
||||
// we either read a certificate and private key or were not passed one
|
||||
|
@ -108,7 +104,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
setupAutomaticNetworking();
|
||||
|
||||
// preload some user public keys so they can connect on first request
|
||||
preloadAllowedUserPublicKeys();
|
||||
_gatekeeper.preloadAllowedUserPublicKeys();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,13 +280,13 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
|||
// register as the packet receiver for the types we want
|
||||
PacketReceiver& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainConnectRequest, this, "processConnectRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPingReply, this, "processICEPingReplyPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, this, "processICEPeerInformationPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
|
||||
|
||||
// add whatever static assignments that have been parsed to the queue
|
||||
addStaticAssignmentsToQueue();
|
||||
|
@ -575,217 +571,20 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
}
|
||||
}
|
||||
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer << NodeType::EntityServer;
|
||||
|
||||
void DomainServer::processConnectRequestPacket(QSharedPointer<NLPacket> packet) {
|
||||
NodeType_t nodeType;
|
||||
HifiSockAddr publicSockAddr, localSockAddr;
|
||||
|
||||
if (packet->getPayloadSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDataStream packetStream(packet.data());
|
||||
|
||||
QUuid connectUUID;
|
||||
packetStream >> connectUUID;
|
||||
|
||||
const HifiSockAddr& senderSockAddr = packet->getSenderSockAddr();
|
||||
|
||||
parseNodeData(packetStream, nodeType, publicSockAddr, localSockAddr, senderSockAddr);
|
||||
|
||||
if (localSockAddr.isNull() || senderSockAddr.isNull()) {
|
||||
qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection.";
|
||||
return;
|
||||
}
|
||||
|
||||
// check if this connect request matches an assignment in the queue
|
||||
bool isAssignment = _pendingAssignedNodes.contains(connectUUID);
|
||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||
PendingAssignedNodeData* pendingAssigneeData = NULL;
|
||||
|
||||
if (isAssignment) {
|
||||
pendingAssigneeData = _pendingAssignedNodes.value(connectUUID);
|
||||
|
||||
if (pendingAssigneeData) {
|
||||
matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(pendingAssigneeData->getAssignmentUUID(), nodeType);
|
||||
|
||||
if (matchingQueuedAssignment) {
|
||||
qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(connectUUID)
|
||||
<< "matches unfulfilled assignment"
|
||||
<< uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID());
|
||||
|
||||
// remove this unique assignment deployment from the hash of pending assigned nodes
|
||||
// cleanup of the PendingAssignedNodeData happens below after the node has been added to the LimitedNodeList
|
||||
_pendingAssignedNodes.remove(connectUUID);
|
||||
} else {
|
||||
// this is a node connecting to fulfill an assignment that doesn't exist
|
||||
// don't reply back to them so they cycle back and re-request an assignment
|
||||
qDebug() << "No match for assignment deployed with" << uuidStringWithoutCurlyBraces(connectUUID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<NodeType_t> nodeInterestList;
|
||||
QString username;
|
||||
QByteArray usernameSignature;
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
packetStream >> nodeInterestList;
|
||||
|
||||
if (packet->bytesLeftToRead() > 0) {
|
||||
// try to verify username
|
||||
packetStream >> username;
|
||||
}
|
||||
|
||||
bool isRestrictingAccess =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
||||
|
||||
// we always let in a user who is sending a packet from our local socket or from the localhost address
|
||||
bool isLocalUser = (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress() || senderSockAddr.getAddress() == QHostAddress::LocalHost);
|
||||
|
||||
if (isRestrictingAccess && !isLocalUser) {
|
||||
if (!username.isEmpty()) {
|
||||
// if there's a username, try to unpack username signature
|
||||
packetStream >> usernameSignature;
|
||||
|
||||
if (usernameSignature.isEmpty()) {
|
||||
// if user didn't include usernameSignature in connect request, send a connectionToken packet
|
||||
QUuid& connectionToken = _connectionTokenHash[username.toLower()];
|
||||
|
||||
if (connectionToken.isNull()) {
|
||||
connectionToken = QUuid::createUuid();
|
||||
}
|
||||
|
||||
static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID);
|
||||
connectionTokenPacket->reset();
|
||||
connectionTokenPacket->write(connectionToken.toRfc4122());
|
||||
limitedNodeList->sendUnreliablePacket(*connectionTokenPacket, packet->getSenderSockAddr());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString reason;
|
||||
if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr, reason)) {
|
||||
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
||||
QByteArray utfString = reason.toUtf8();
|
||||
quint16 payloadSize = utfString.size();
|
||||
|
||||
auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize));
|
||||
if (payloadSize > 0) {
|
||||
connectionDeniedPacket->writePrimitive(payloadSize);
|
||||
connectionDeniedPacket->write(utfString);
|
||||
}
|
||||
// tell client it has been refused.
|
||||
limitedNodeList->sendPacket(std::move(connectionDeniedPacket), senderSockAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
||||
|| (isAssignment && matchingQueuedAssignment)) {
|
||||
// this was either not a static assignment or it was and we had a matching one in the queue
|
||||
|
||||
QUuid nodeUUID;
|
||||
|
||||
HifiSockAddr discoveredSocket = senderSockAddr;
|
||||
SharedNetworkPeer connectedPeer = _icePeers.value(connectUUID);
|
||||
|
||||
if (connectedPeer) {
|
||||
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
||||
nodeUUID = connectUUID;
|
||||
|
||||
if (connectedPeer->getActiveSocket()) {
|
||||
// set their discovered socket to whatever the activated socket on the network peer object was
|
||||
discoveredSocket = *connectedPeer->getActiveSocket();
|
||||
}
|
||||
} else {
|
||||
// we got a connectUUID we didn't recognize, just add the node with a new UUID
|
||||
nodeUUID = QUuid::createUuid();
|
||||
}
|
||||
|
||||
// if this user is in the editors list (or if the editors list is empty) set the user's node's canAdjustLocks to true
|
||||
const QVariant* allowedEditorsVariant =
|
||||
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
||||
bool canAdjustLocks = allowedEditors.isEmpty() || allowedEditors.contains(username);
|
||||
|
||||
const QVariant* editorsAreRezzersVariant =
|
||||
valueForKeyPath(_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH);
|
||||
|
||||
bool onlyEditorsAreRezzers = false;
|
||||
if (editorsAreRezzersVariant) {
|
||||
onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool();
|
||||
}
|
||||
|
||||
bool canRez = true;
|
||||
if (onlyEditorsAreRezzers) {
|
||||
canRez = canAdjustLocks;
|
||||
}
|
||||
|
||||
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeUUID, nodeType,
|
||||
publicSockAddr, localSockAddr,
|
||||
canAdjustLocks, canRez);
|
||||
|
||||
// So that we can send messages to this node at will - we need to activate the correct socket on this node now
|
||||
newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket);
|
||||
|
||||
// when the newNode is created the linked data is also created
|
||||
// if this was a static assignment set the UUID, set the sendingSockAddr
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
if (isAssignment) {
|
||||
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
||||
nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID());
|
||||
nodeData->setNodeVersion(pendingAssigneeData->getNodeVersion());
|
||||
|
||||
// always allow assignment clients to create and destroy entities
|
||||
newNode->setCanAdjustLocks(true);
|
||||
newNode->setCanRez(true);
|
||||
|
||||
// now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data
|
||||
delete pendingAssigneeData;
|
||||
}
|
||||
|
||||
if (!username.isEmpty()) {
|
||||
// if we have a username from the connect request, set it on the DomainServerNodeData
|
||||
nodeData->setUsername(username);
|
||||
|
||||
// also add an interpolation to JSONBreakableMarshal so that servers can get username in stats
|
||||
JSONBreakableMarshal::addInterpolationForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||
uuidStringWithoutCurlyBraces(nodeUUID), username);
|
||||
}
|
||||
|
||||
nodeData->setSendingSockAddr(senderSockAddr);
|
||||
|
||||
// reply back to the user with a PacketType::DomainList
|
||||
sendDomainListToNode(newNode, senderSockAddr, nodeInterestList.toSet());
|
||||
|
||||
// send out this node to our other connected nodes
|
||||
broadcastNewNode(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DomainServer::processListRequestPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
||||
|
||||
NodeType_t throwawayNodeType;
|
||||
HifiSockAddr nodePublicAddress, nodeLocalAddress;
|
||||
|
||||
QDataStream packetStream(packet.data());
|
||||
NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr(), false);
|
||||
|
||||
parseNodeData(packetStream, throwawayNodeType, nodePublicAddress, nodeLocalAddress, packet->getSenderSockAddr());
|
||||
// update this node's sockets in case they have changed
|
||||
sendingNode->setPublicSocket(nodeRequestData.publicSockAddr);
|
||||
sendingNode->setLocalSocket(nodeRequestData.localSockAddr);
|
||||
|
||||
// update the NodeInterestSet in case there have been any changes
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
nodeData->setNodeInterestSet(nodeRequestData.interestList.toSet());
|
||||
|
||||
sendingNode->setPublicSocket(nodePublicAddress);
|
||||
sendingNode->setLocalSocket(nodeLocalAddress);
|
||||
|
||||
QList<NodeType_t> nodeInterestList;
|
||||
packetStream >> nodeInterestList;
|
||||
|
||||
sendDomainListToNode(sendingNode, packet->getSenderSockAddr(), nodeInterestList.toSet());
|
||||
sendDomainListToNode(sendingNode, packet->getSenderSockAddr());
|
||||
}
|
||||
|
||||
unsigned int DomainServer::countConnectedUsers() {
|
||||
|
@ -799,156 +598,6 @@ unsigned int DomainServer::countConnectedUsers() {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool DomainServer::verifyUserSignature(const QString& username,
|
||||
const QByteArray& usernameSignature,
|
||||
QString& reasonReturn) {
|
||||
|
||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
||||
|
||||
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
||||
// if we do have a public key for the user, check for a signature match
|
||||
|
||||
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData());
|
||||
|
||||
// first load up the public key into an RSA struct
|
||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
||||
|
||||
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
||||
QCryptographicHash::Sha256);
|
||||
|
||||
if (rsaPublicKey) {
|
||||
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
|
||||
int decryptResult = RSA_verify(NID_sha256,
|
||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
||||
usernameWithToken.size(),
|
||||
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
||||
usernameSignature.size(),
|
||||
rsaPublicKey);
|
||||
|
||||
if (decryptResult == 1) {
|
||||
qDebug() << "Username signature matches for" << username << "- allowing connection.";
|
||||
|
||||
// free up the public key and remove connection token before we return
|
||||
RSA_free(rsaPublicKey);
|
||||
_connectionTokenHash.remove(username);
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
qDebug() << "Error decrypting username signature for " << username << "- denying connection.";
|
||||
reasonReturn = "Error decrypting username signature.";
|
||||
// free up the public key, we don't need it anymore
|
||||
RSA_free(rsaPublicKey);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
||||
reasonReturn = "Couldn't convert data to RSA key.";
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Insufficient data to decrypt username signature - denying connection.";
|
||||
reasonReturn = "Insufficient data";
|
||||
}
|
||||
|
||||
requestUserPublicKey(username); // no joy. maybe next time?
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
||||
const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr,
|
||||
QString& reasonReturn) {
|
||||
|
||||
//TODO: improve flow so these bools aren't declared twice
|
||||
bool isRestrictingAccess =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
||||
bool isLocalUser = (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress() || senderSockAddr.getAddress() == QHostAddress::LocalHost);
|
||||
|
||||
if (isRestrictingAccess && !isLocalUser) {
|
||||
QStringList allowedUsers =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
|
||||
|
||||
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
|
||||
if (username.isEmpty()) {
|
||||
qDebug() << "Connect request denied - no username provided.";
|
||||
reasonReturn = "No username provided";
|
||||
return false;
|
||||
}
|
||||
if (!verifyUserSignature(username, usernameSignature, reasonReturn)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Connect request denied for user" << username << "not in allowed users list.";
|
||||
reasonReturn = "User not on whitelist.";
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// either we aren't restricting users, or this user is in the allowed list
|
||||
// if this user is in the editors list, exempt them from the max-capacity check
|
||||
const QVariant* allowedEditorsVariant =
|
||||
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
||||
if (allowedEditors.contains(username)) {
|
||||
if (verifyUserSignature(username, usernameSignature, reasonReturn)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if we haven't reached max-capacity, let them in.
|
||||
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
||||
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
||||
if (maximumUserCapacity > 0) {
|
||||
unsigned int connectedUsers = countConnectedUsers();
|
||||
if (connectedUsers >= maximumUserCapacity) {
|
||||
// too many users, deny the new connection.
|
||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
||||
reasonReturn = "Too many connected users.";
|
||||
return false;
|
||||
}
|
||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DomainServer::preloadAllowedUserPublicKeys() {
|
||||
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH);
|
||||
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
||||
|
||||
if (allowedUsers.size() > 0) {
|
||||
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
|
||||
// going to create > 100 requests
|
||||
foreach(const QString& username, allowedUsers) {
|
||||
requestUserPublicKey(username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::requestUserPublicKey(const QString& username) {
|
||||
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||
|
||||
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||
|
||||
qDebug() << "Requesting public key for user" << username;
|
||||
|
||||
AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
|
||||
AccountManagerAuth::None,
|
||||
QNetworkAccessManager::GetOperation, callbackParams);
|
||||
}
|
||||
|
||||
QUrl DomainServer::oauthRedirectURL() {
|
||||
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
|
||||
}
|
||||
|
@ -983,30 +632,18 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
|
|||
return authorizationURL;
|
||||
}
|
||||
|
||||
int DomainServer::parseNodeData(QDataStream& packetStream, NodeType_t& nodeType,
|
||||
HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
packetStream >> nodeType;
|
||||
packetStream >> publicSockAddr >> localSockAddr;
|
||||
void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
|
||||
|
||||
if (publicSockAddr.getAddress().isNull()) {
|
||||
// this node wants to use us its STUN server
|
||||
// so set the node public address to whatever we perceive the public address to be
|
||||
|
||||
// if the sender is on our box then leave its public address to 0 so that
|
||||
// other users attempt to reach it on the same address they have for the domain-server
|
||||
if (senderSockAddr.getAddress().isLoopback()) {
|
||||
publicSockAddr.setAddress(QHostAddress());
|
||||
} else {
|
||||
publicSockAddr.setAddress(senderSockAddr.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
return packetStream.device()->pos();
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
// reply back to the user with a PacketType::DomainList
|
||||
sendDomainListToNode(newNode, nodeData->getSendingSockAddr());
|
||||
|
||||
// send out this node to our other connected nodes
|
||||
broadcastNewNode(newNode);
|
||||
}
|
||||
|
||||
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr,
|
||||
const NodeSet& nodeInterestSet) {
|
||||
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) {
|
||||
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NUM_BYTES_RFC4122_UUID + 2;
|
||||
|
||||
// setup the extended header for the domain list packets
|
||||
|
@ -1029,7 +666,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
// store the nodeInterestSet on this DomainServerNodeData, in case it has changed
|
||||
nodeData->setNodeInterestSet(nodeInterestSet);
|
||||
auto& nodeInterestSet = nodeData->getNodeInterestSet();
|
||||
|
||||
if (nodeInterestSet.size() > 0) {
|
||||
|
||||
|
@ -1038,6 +675,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
// if this authenticated node has any interest types, send back those nodes as well
|
||||
limitedNodeList->eachNode([&](const SharedNodePointer& otherNode){
|
||||
if (otherNode->getUUID() != node->getUUID() && nodeInterestSet.contains(otherNode->getType())) {
|
||||
|
||||
// since we're about to add a node to the packet we start a segment
|
||||
domainListPackets.startSegment();
|
||||
|
||||
|
@ -1169,11 +807,10 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<NLPacket> packe
|
|||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->sendUnreliablePacket(*assignmentPacket, packet->getSenderSockAddr());
|
||||
|
||||
// add the information for that deployed assignment to the hash of pending assigned nodes
|
||||
PendingAssignedNodeData* pendingNodeData = new PendingAssignedNodeData(assignmentToDeploy->getUUID(),
|
||||
requestAssignment.getWalletUUID(),
|
||||
requestAssignment.getNodeVersion());
|
||||
_pendingAssignedNodes.insert(uniqueAssignment.getUUID(), pendingNodeData);
|
||||
// give the information for that deployed assignment to the gatekeeper so it knows to that that node
|
||||
// in when it comes back around
|
||||
_gatekeeper.addPendingAssignedNode(uniqueAssignment.getUUID(), assignmentToDeploy->getUUID(),
|
||||
requestAssignment.getWalletUUID(), requestAssignment.getNodeVersion());
|
||||
} else {
|
||||
if (requestAssignment.getType() != Assignment::AgentType
|
||||
|| noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
|
||||
|
@ -1253,30 +890,6 @@ void DomainServer::sendPendingTransactionsToServer() {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServer::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
// figure out which user this is for
|
||||
|
||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
QString username = usernameRegex.cap(1);
|
||||
|
||||
qDebug() << "Storing a public key for user" << username;
|
||||
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
_userPublicKeys[username] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::transactionJSONCallback(const QJsonObject& data) {
|
||||
// check if this was successful - if so we can remove it from our list of pending
|
||||
if (data.value("status").toString() == "success") {
|
||||
|
@ -1373,91 +986,6 @@ void DomainServer::sendHeartbeatToIceServer() {
|
|||
DependencyManager::get<LimitedNodeList>()->sendHeartbeatToIceServer(_iceServerSocket);
|
||||
}
|
||||
|
||||
const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS;
|
||||
|
||||
void DomainServer::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) {
|
||||
|
||||
if (peer->getConnectionAttempts() > 0 && peer->getConnectionAttempts() % NUM_PEER_PINGS_BEFORE_DELETE == 0) {
|
||||
// we've reached the maximum number of ping attempts
|
||||
qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID();
|
||||
qDebug() << "Removing from list of connecting peers.";
|
||||
|
||||
_icePeers.remove(peer->getUUID());
|
||||
} else {
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// send the ping packet to the local and public sockets for this node
|
||||
auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID());
|
||||
limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket());
|
||||
|
||||
auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID());
|
||||
limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket());
|
||||
|
||||
peer->incrementConnectionAttempts();
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::handlePeerPingTimeout() {
|
||||
NetworkPeer* senderPeer = qobject_cast<NetworkPeer*>(sender());
|
||||
|
||||
if (senderPeer) {
|
||||
SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID());
|
||||
|
||||
if (sharedPeer && !sharedPeer->getActiveSocket()) {
|
||||
pingPunchForConnectingPeer(sharedPeer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::processICEPeerInformationPacket(QSharedPointer<NLPacket> packet) {
|
||||
// loop through the packet and pull out network peers
|
||||
// any peer we don't have we add to the hash, otherwise we update
|
||||
QDataStream iceResponseStream(packet.data());
|
||||
|
||||
NetworkPeer* receivedPeer = new NetworkPeer;
|
||||
iceResponseStream >> *receivedPeer;
|
||||
|
||||
if (!_icePeers.contains(receivedPeer->getUUID())) {
|
||||
qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
|
||||
SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer);
|
||||
_icePeers[receivedPeer->getUUID()] = newPeer;
|
||||
|
||||
// make sure we know when we should ping this peer
|
||||
connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainServer::handlePeerPingTimeout);
|
||||
|
||||
// immediately ping the new peer, and start a timer to continue pinging it until we connect to it
|
||||
newPeer->startPingTimer();
|
||||
|
||||
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
|
||||
<< newPeer->getUUID();
|
||||
|
||||
pingPunchForConnectingPeer(newPeer);
|
||||
} else {
|
||||
delete receivedPeer;
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::processICEPingPacket(QSharedPointer<NLPacket> packet) {
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*packet, limitedNodeList->getSessionUUID());
|
||||
|
||||
limitedNodeList->sendPacket(std::move(pingReplyPacket), packet->getSenderSockAddr());
|
||||
}
|
||||
|
||||
void DomainServer::processICEPingReplyPacket(QSharedPointer<NLPacket> packet) {
|
||||
QDataStream packetStream(packet.data());
|
||||
|
||||
QUuid nodeUUID;
|
||||
packetStream >> nodeUUID;
|
||||
|
||||
SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID);
|
||||
|
||||
if (sendingPeer) {
|
||||
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
|
||||
sendingPeer->activateMatchingOrNewSymmetricSocket(packet->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::processNodeJSONStatsPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
||||
auto nodeData = dynamic_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
if (nodeData) {
|
||||
|
@ -1569,9 +1097,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
SharedAssignmentPointer matchingAssignment = _allAssignments.value(matchingUUID);
|
||||
if (!matchingAssignment) {
|
||||
// check if we have a pending assignment that matches this temp UUID, and it is a scripted assignment
|
||||
PendingAssignedNodeData* pendingData = _pendingAssignedNodes.value(matchingUUID);
|
||||
if (pendingData) {
|
||||
matchingAssignment = _allAssignments.value(pendingData->getAssignmentUUID());
|
||||
QUuid assignmentUUID = _gatekeeper.assignmentUUIDForPendingAssignment(matchingUUID);
|
||||
if (!assignmentUUID.isNull()) {
|
||||
matchingAssignment = _allAssignments.value(assignmentUUID);
|
||||
|
||||
if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) {
|
||||
// we have a matching assignment and it is for the right type, have the HTTP manager handle it
|
||||
|
@ -1579,7 +1107,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
QUrl scriptURL = url;
|
||||
scriptURL.setPath(URI_ASSIGNMENT + "/scripts/"
|
||||
+ uuidStringWithoutCurlyBraces(pendingData->getAssignmentUUID()));
|
||||
+ uuidStringWithoutCurlyBraces(assignmentUUID));
|
||||
|
||||
// have the HTTPManager serve the appropriate script file
|
||||
return _httpManager.handleHTTPRequest(connection, scriptURL, true);
|
||||
|
@ -2113,7 +1641,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
|
|||
void DomainServer::nodeKilled(SharedNodePointer node) {
|
||||
|
||||
// if this peer connected via ICE then remove them from our ICE peers hash
|
||||
_icePeers.remove(node->getUUID());
|
||||
_gatekeeper.removeICEPeer(node->getUUID());
|
||||
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
|
@ -2141,7 +1669,7 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
}
|
||||
}
|
||||
|
||||
SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& assignmentUUID, NodeType_t nodeType) {
|
||||
SharedAssignmentPointer DomainServer::dequeueMatchingAssignment(const QUuid& assignmentUUID, NodeType_t nodeType) {
|
||||
QQueue<SharedAssignmentPointer>::iterator i = _unfulfilledAssignments.begin();
|
||||
|
||||
while (i != _unfulfilledAssignments.end()) {
|
||||
|
@ -2192,20 +1720,6 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
|
|||
return SharedAssignmentPointer();
|
||||
}
|
||||
|
||||
void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) {
|
||||
QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _unfulfilledAssignments.begin();
|
||||
while (potentialMatchingAssignment != _unfulfilledAssignments.end()) {
|
||||
if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) {
|
||||
_unfulfilledAssignments.erase(potentialMatchingAssignment);
|
||||
|
||||
// we matched and removed an assignment, bail out
|
||||
break;
|
||||
} else {
|
||||
++potentialMatchingAssignment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::addStaticAssignmentsToQueue() {
|
||||
|
||||
// if the domain-server has just restarted,
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <HTTPSConnection.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DomainGatekeeper.h"
|
||||
#include "DomainServerSettingsManager.h"
|
||||
#include "DomainServerWebSessionData.h"
|
||||
#include "WalletTransaction.h"
|
||||
|
@ -50,19 +51,14 @@ public slots:
|
|||
/// Called by NodeList to inform us a node has been killed
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
void transactionJSONCallback(const QJsonObject& data);
|
||||
|
||||
void restart();
|
||||
|
||||
void processRequestAssignmentPacket(QSharedPointer<NLPacket> packet);
|
||||
void processConnectRequestPacket(QSharedPointer<NLPacket> packet);
|
||||
void processListRequestPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode);
|
||||
void processNodeJSONStatsPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode);
|
||||
void processPathQueryPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPingPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPingReplyPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPeerInformationPacket(QSharedPointer<NLPacket> packet);
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
@ -74,7 +70,9 @@ private slots:
|
|||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); }
|
||||
void sendHeartbeatToIceServer();
|
||||
void handlePeerPingTimeout();
|
||||
|
||||
void handleConnectedNode(SharedNodePointer newNode);
|
||||
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
|
@ -87,20 +85,9 @@ private:
|
|||
void setupAutomaticNetworking();
|
||||
void sendHeartbeatToDataServer(const QString& networkAddress);
|
||||
|
||||
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);
|
||||
|
||||
unsigned int countConnectedUsers();
|
||||
bool verifyUserSignature (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn);
|
||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr, QString& reasonReturn);
|
||||
|
||||
void preloadAllowedUserPublicKeys();
|
||||
void requestUserPublicKey(const QString& username);
|
||||
|
||||
int parseNodeData(QDataStream& packetStream, NodeType_t& nodeType,
|
||||
HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr, const HifiSockAddr& senderSockAddr);
|
||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
|
||||
const NodeSet& nodeInterestSet);
|
||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB);
|
||||
void broadcastNewNode(const SharedNodePointer& node);
|
||||
|
@ -111,12 +98,11 @@ private:
|
|||
void populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes);
|
||||
void populateStaticScriptedAssignmentsFromSettings();
|
||||
|
||||
SharedAssignmentPointer matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
|
||||
SharedAssignmentPointer dequeueMatchingAssignment(const QUuid& checkInUUID, NodeType_t nodeType);
|
||||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
|
@ -131,13 +117,14 @@ private:
|
|||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
DomainGatekeeper _gatekeeper;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
HTTPSManager* _httpsManager;
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
|
||||
TransactionHash _pendingAssignmentCredits;
|
||||
|
||||
bool _isUsingDTLS;
|
||||
|
@ -149,18 +136,14 @@ private:
|
|||
|
||||
QSet<QUuid> _webAuthenticationStateSet;
|
||||
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
|
||||
|
||||
QHash<QString, QUuid> _connectionTokenHash;
|
||||
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
|
||||
QHash<QUuid, SharedNetworkPeer> _icePeers;
|
||||
|
||||
QString _automaticNetworkingSetting;
|
||||
|
||||
DomainServerSettingsManager _settingsManager;
|
||||
|
||||
HifiSockAddr _iceServerSocket;
|
||||
|
||||
friend class DomainGatekeeper;
|
||||
};
|
||||
|
||||
|
||||
|
|
42
domain-server/src/NodeConnectionData.cpp
Normal file
42
domain-server/src/NodeConnectionData.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// NodeConnectionData.cpp
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-24.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "NodeConnectionData.h"
|
||||
|
||||
NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr,
|
||||
bool isConnectRequest) {
|
||||
NodeConnectionData newHeader;
|
||||
|
||||
if (isConnectRequest) {
|
||||
dataStream >> newHeader.connectUUID;
|
||||
}
|
||||
|
||||
dataStream >> newHeader.nodeType
|
||||
>> newHeader.publicSockAddr >> newHeader.localSockAddr
|
||||
>> newHeader.interestList;
|
||||
|
||||
newHeader.senderSockAddr = senderSockAddr;
|
||||
|
||||
if (newHeader.publicSockAddr.getAddress().isNull()) {
|
||||
// this node wants to use us its STUN server
|
||||
// so set the node public address to whatever we perceive the public address to be
|
||||
|
||||
// if the sender is on our box then leave its public address to 0 so that
|
||||
// other users attempt to reach it on the same address they have for the domain-server
|
||||
if (senderSockAddr.getAddress().isLoopback()) {
|
||||
newHeader.publicSockAddr.setAddress(QHostAddress());
|
||||
} else {
|
||||
newHeader.publicSockAddr.setAddress(senderSockAddr.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
return newHeader;
|
||||
}
|
33
domain-server/src/NodeConnectionData.h
Normal file
33
domain-server/src/NodeConnectionData.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// NodeConnectionData.h
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-24.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_NodeConnectionData_h
|
||||
#define hifi_NodeConnectionData_h
|
||||
|
||||
#include <Node.h>
|
||||
|
||||
class NodeConnectionData {
|
||||
public:
|
||||
static NodeConnectionData fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr,
|
||||
bool isConnectRequest = true);
|
||||
|
||||
QUuid connectUUID;
|
||||
NodeType_t nodeType;
|
||||
HifiSockAddr publicSockAddr;
|
||||
HifiSockAddr localSockAddr;
|
||||
HifiSockAddr senderSockAddr;
|
||||
QList<NodeType_t> interestList;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_NodeConnectionData_h
|
|
@ -16,5 +16,5 @@ PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, co
|
|||
_walletUUID(walletUUID),
|
||||
_nodeVersion(nodeVersion)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,4 +34,4 @@ private:
|
|||
QString _nodeVersion;
|
||||
};
|
||||
|
||||
#endif // hifi_PendingAssignedNodeData_h
|
||||
#endif // hifi_PendingAssignedNodeData_h
|
||||
|
|
|
@ -138,7 +138,7 @@ public:
|
|||
|
||||
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
||||
bool canAdjustLocks, bool canRez,
|
||||
bool canAdjustLocks = false, bool canRez = false,
|
||||
const QUuid& connectionSecret = QUuid());
|
||||
|
||||
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
||||
|
|
|
@ -20,10 +20,17 @@
|
|||
class UUIDHasher {
|
||||
public:
|
||||
size_t operator()(const QUuid& uuid) const {
|
||||
return uuid.data1 ^ uuid.data2 ^ (uuid.data3 << 16)
|
||||
^ ((uuid.data4[0] << 24) | (uuid.data4[1] << 16) | (uuid.data4[2] << 8) | uuid.data4[3])
|
||||
^ ((uuid.data4[4] << 24) | (uuid.data4[5] << 16) | (uuid.data4[6] << 8) | uuid.data4[7]);
|
||||
return qHash(uuid);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // hifi_UUIDHasher_h
|
||||
namespace std {
|
||||
template <> struct hash<QUuid> {
|
||||
size_t operator()(const QUuid& uuid) const {
|
||||
return qHash(uuid);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif // hifi_UUIDHasher_h
|
||||
|
|
|
@ -18,14 +18,6 @@
|
|||
#include "JurisdictionMap.h"
|
||||
#include "SentPacketHistory.h"
|
||||
|
||||
namespace std {
|
||||
template <> struct hash<QUuid> {
|
||||
size_t operator()(const QUuid& uuid) const {
|
||||
return qHash(uuid);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Utility for processing, packing, queueing and sending of outbound edit messages.
|
||||
class OctreeEditPacketSender : public PacketSender {
|
||||
Q_OBJECT
|
||||
|
|
Loading…
Reference in a new issue