mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #2771 from birarda/oauth-ds
restrict access to domains using user roles retreived from data-web
This commit is contained in:
commit
9aac74035c
29 changed files with 895 additions and 311 deletions
|
@ -191,9 +191,10 @@ void Agent::run() {
|
|||
// figure out the URL for the script for this agent assignment
|
||||
QUrl scriptURL;
|
||||
if (_payload.isEmpty()) {
|
||||
scriptURL = QUrl(QString("http://%1:8080/assignment/%2")
|
||||
.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(),
|
||||
uuidStringWithoutCurlyBraces(_uuid)));
|
||||
scriptURL = QUrl(QString("http://%1:%2/assignment/%3")
|
||||
.arg(NodeList::getInstance()->getDomainHandler().getIP().toString())
|
||||
.arg(DOMAIN_SERVER_HTTP_PORT)
|
||||
.arg(uuidStringWithoutCurlyBraces(_uuid)));
|
||||
} else {
|
||||
scriptURL = QUrl(_payload);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtCore/QProcess>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QUrlQuery>
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
|
@ -31,18 +32,22 @@
|
|||
|
||||
#include "DomainServer.h"
|
||||
|
||||
const quint16 DOMAIN_SERVER_HTTP_PORT = 8080;
|
||||
|
||||
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
||||
_staticAssignmentHash(),
|
||||
_assignmentQueue(),
|
||||
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
||||
_httpsManager(NULL),
|
||||
_allAssignments(),
|
||||
_unfulfilledAssignments(),
|
||||
_isUsingDTLS(false),
|
||||
_x509Credentials(NULL),
|
||||
_dhParams(NULL),
|
||||
_priorityCache(NULL),
|
||||
_dtlsSessions()
|
||||
_dtlsSessions(),
|
||||
_oauthProviderURL(),
|
||||
_oauthClientID(),
|
||||
_hostname(),
|
||||
_networkReplyUUIDMap(),
|
||||
_sessionAuthenticationHash()
|
||||
{
|
||||
gnutls_global_init();
|
||||
|
||||
|
@ -53,22 +58,20 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
|
||||
|
||||
if (optionallySetupDTLS()) {
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
||||
// we either read a certificate and private key or were not passed one, good to load assignments
|
||||
// and set up the node list
|
||||
qDebug() << "Setting up LimitedNodeList and assignments.";
|
||||
setupNodeListAndAssignments();
|
||||
|
||||
if (_isUsingDTLS) {
|
||||
// we're using DTLS and our NodeList socket is good to go, so make the required DTLS changes
|
||||
// DTLS requires that IP_DONTFRAG be set
|
||||
// This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it
|
||||
|
||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||
|
||||
// connect our socket to read datagrams received on the DTLS socket
|
||||
connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams);
|
||||
}
|
||||
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,44 +89,7 @@ DomainServer::~DomainServer() {
|
|||
gnutls_global_deinit();
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupDTLS() {
|
||||
if (readX509KeyAndCertificate()) {
|
||||
if (_x509Credentials) {
|
||||
qDebug() << "Generating Diffie-Hellman parameters.";
|
||||
|
||||
// generate Diffie-Hellman parameters
|
||||
// When short bit length is used, it might be wise to regenerate parameters often.
|
||||
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
|
||||
|
||||
_dhParams = new gnutls_dh_params_t;
|
||||
gnutls_dh_params_init(_dhParams);
|
||||
gnutls_dh_params_generate2(*_dhParams, dhBits);
|
||||
|
||||
qDebug() << "Successfully generated Diffie-Hellman parameters.";
|
||||
|
||||
// set the D-H paramters on the X509 credentials
|
||||
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
|
||||
|
||||
// setup the key used for cookie verification
|
||||
_cookieKey = new gnutls_datum_t;
|
||||
gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE);
|
||||
|
||||
_priorityCache = new gnutls_priority_t;
|
||||
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
|
||||
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
|
||||
|
||||
_isUsingDTLS = true;
|
||||
|
||||
qDebug() << "Initial DTLS setup complete.";
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DomainServer::readX509KeyAndCertificate() {
|
||||
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
|
||||
|
@ -157,6 +123,22 @@ bool DomainServer::readX509KeyAndCertificate() {
|
|||
|
||||
qDebug() << "Successfully read certificate and private key.";
|
||||
|
||||
// we need to also pass this certificate and private key to the HTTPS manager
|
||||
// this is used for Oauth callbacks when authorizing users against a data server
|
||||
|
||||
QFile certFile(certPath);
|
||||
certFile.open(QIODevice::ReadOnly);
|
||||
|
||||
QFile keyFile(keyPath);
|
||||
keyFile.open(QIODevice::ReadOnly);
|
||||
|
||||
QSslCertificate sslCertificate(&certFile);
|
||||
QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
|
||||
|
||||
_httpsManager = new HTTPSManager(DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this);
|
||||
|
||||
qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT;
|
||||
|
||||
} else if (!certPath.isEmpty() || !keyPath.isEmpty()) {
|
||||
qDebug() << "Missing certificate or private key. domain-server will now quit.";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
|
@ -166,6 +148,67 @@ bool DomainServer::readX509KeyAndCertificate() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupOAuth() {
|
||||
const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider";
|
||||
const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id";
|
||||
const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
|
||||
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
|
||||
|
||||
_oauthProviderURL = QUrl(_argumentVariantMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
|
||||
_oauthClientID = _argumentVariantMap.value(OAUTH_CLIENT_ID_OPTION).toString();
|
||||
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
|
||||
_hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
|
||||
|
||||
if (!_oauthProviderURL.isEmpty() || !_hostname.isEmpty() || !_oauthClientID.isEmpty()) {
|
||||
if (_oauthProviderURL.isEmpty()
|
||||
|| _hostname.isEmpty()
|
||||
|| _oauthClientID.isEmpty()
|
||||
|| _oauthClientSecret.isEmpty()) {
|
||||
qDebug() << "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit.";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString();
|
||||
qDebug() << "OAuth Client ID is" << _oauthClientID;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupDTLS() {
|
||||
if (_x509Credentials) {
|
||||
qDebug() << "Generating Diffie-Hellman parameters.";
|
||||
|
||||
// generate Diffie-Hellman parameters
|
||||
// When short bit length is used, it might be wise to regenerate parameters often.
|
||||
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
|
||||
|
||||
_dhParams = new gnutls_dh_params_t;
|
||||
gnutls_dh_params_init(_dhParams);
|
||||
gnutls_dh_params_generate2(*_dhParams, dhBits);
|
||||
|
||||
qDebug() << "Successfully generated Diffie-Hellman parameters.";
|
||||
|
||||
// set the D-H paramters on the X509 credentials
|
||||
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
|
||||
|
||||
// setup the key used for cookie verification
|
||||
_cookieKey = new gnutls_datum_t;
|
||||
gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE);
|
||||
|
||||
_priorityCache = new gnutls_priority_t;
|
||||
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
|
||||
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
|
||||
|
||||
_isUsingDTLS = true;
|
||||
|
||||
qDebug() << "Initial DTLS setup complete.";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||
|
||||
const QString CUSTOM_PORT_OPTION = "port";
|
||||
|
@ -246,7 +289,8 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
|
|||
|
||||
void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) {
|
||||
qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash.";
|
||||
_staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
|
||||
newAssignment->setIsStatic(true);
|
||||
_allAssignments.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
|
||||
}
|
||||
|
||||
void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configArray) {
|
||||
|
@ -279,7 +323,9 @@ void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configAr
|
|||
qDebug() << "URL for script is" << assignmentURL;
|
||||
|
||||
// scripts passed on CL or via JSON are static - so they are added back to the queue if the node dies
|
||||
_assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment));
|
||||
SharedAssignmentPointer sharedScriptAssignment(scriptAssignment);
|
||||
_unfulfilledAssignments.enqueue(sharedScriptAssignment);
|
||||
_allAssignments.insert(sharedScriptAssignment->getUUID(), sharedScriptAssignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -340,47 +386,71 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
}
|
||||
}
|
||||
|
||||
const QString ALLOWED_ROLES_CONFIG_KEY = "allowed-roles";
|
||||
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::MetavoxelServer;
|
||||
|
||||
void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
|
||||
void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
NodeType_t nodeType;
|
||||
HifiSockAddr publicSockAddr, localSockAddr;
|
||||
|
||||
int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr);
|
||||
|
||||
QUuid assignmentUUID = uuidFromPacketHeader(packet);
|
||||
bool isStaticAssignment = _staticAssignmentHash.contains(assignmentUUID);
|
||||
SharedAssignmentPointer matchingAssignment = SharedAssignmentPointer();
|
||||
|
||||
if (isStaticAssignment) {
|
||||
// this is a static assignment, make sure the UUID sent is for an assignment we're actually trying to give out
|
||||
matchingAssignment = matchingQueuedAssignmentForCheckIn(assignmentUUID, nodeType);
|
||||
|
||||
if (matchingAssignment) {
|
||||
// remove the matching assignment from the assignment queue so we don't take the next check in
|
||||
// (if it exists)
|
||||
removeMatchingAssignmentFromQueue(matchingAssignment);
|
||||
}
|
||||
} else {
|
||||
assignmentUUID = QUuid();
|
||||
QUuid packetUUID = uuidFromPacketHeader(packet);
|
||||
|
||||
// check if this connect request matches an assignment in the queue
|
||||
bool isFulfilledOrUnfulfilledAssignment = _allAssignments.contains(packetUUID);
|
||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||
if (isFulfilledOrUnfulfilledAssignment) {
|
||||
matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType);
|
||||
}
|
||||
|
||||
// make sure this was either not a static assignment or it was and we had a matching one in teh queue
|
||||
if ((!isStaticAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) || (isStaticAssignment && matchingAssignment)) {
|
||||
if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
||||
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
|
||||
if (_sessionAuthenticationHash.contains(packetUUID)) {
|
||||
if (!_sessionAuthenticationHash.value(packetUUID)) {
|
||||
// we've decided this is a user that isn't allowed in, return out
|
||||
// TODO: provide information to the user so they know why they can't connect
|
||||
return;
|
||||
} else {
|
||||
// we're letting this user in, don't return and remove their UUID from the hash
|
||||
_sessionAuthenticationHash.remove(packetUUID);
|
||||
}
|
||||
} else {
|
||||
// we don't know anything about this client
|
||||
// we have an OAuth provider, ask this interface client to auth against it
|
||||
QByteArray oauthRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainOAuthRequest);
|
||||
QDataStream oauthRequestStream(&oauthRequestByteArray, QIODevice::Append);
|
||||
QUrl authorizationURL = packetUUID.isNull() ? oauthAuthorizationURL() : oauthAuthorizationURL(packetUUID);
|
||||
oauthRequestStream << authorizationURL;
|
||||
|
||||
// send this oauth request datagram back to the client
|
||||
LimitedNodeList::getInstance()->writeUnverifiedDatagram(oauthRequestByteArray, senderSockAddr);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!isFulfilledOrUnfulfilledAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
||||
|| (isFulfilledOrUnfulfilledAssignment && matchingQueuedAssignment)) {
|
||||
// this was either not a static assignment or it was and we had a matching one in the queue
|
||||
|
||||
// create a new session UUID for this node
|
||||
QUuid nodeUUID = QUuid::createUuid();
|
||||
|
||||
SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType,
|
||||
publicSockAddr, localSockAddr);
|
||||
|
||||
// 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());
|
||||
|
||||
nodeData->setStaticAssignmentUUID(assignmentUUID);
|
||||
if (isFulfilledOrUnfulfilledAssignment) {
|
||||
nodeData->setAssignmentUUID(packetUUID);
|
||||
}
|
||||
|
||||
nodeData->setSendingSockAddr(senderSockAddr);
|
||||
|
||||
// reply back to the user with a PacketTypeDomainList
|
||||
|
@ -388,6 +458,40 @@ void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packe
|
|||
}
|
||||
}
|
||||
|
||||
QUrl DomainServer::oauthRedirectURL() {
|
||||
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
|
||||
}
|
||||
|
||||
const QString OAUTH_CLIENT_ID_QUERY_KEY = "client_id";
|
||||
const QString OAUTH_REDIRECT_URI_QUERY_KEY = "redirect_uri";
|
||||
|
||||
QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
|
||||
// for now these are all interface clients that have a GUI
|
||||
// so just send them back the full authorization URL
|
||||
QUrl authorizationURL = _oauthProviderURL;
|
||||
|
||||
const QString OAUTH_AUTHORIZATION_PATH = "/oauth/authorize";
|
||||
authorizationURL.setPath(OAUTH_AUTHORIZATION_PATH);
|
||||
|
||||
QUrlQuery authorizationQuery;
|
||||
|
||||
authorizationQuery.addQueryItem(OAUTH_CLIENT_ID_QUERY_KEY, _oauthClientID);
|
||||
|
||||
const QString OAUTH_RESPONSE_TYPE_QUERY_KEY = "response_type";
|
||||
const QString OAUTH_REPSONSE_TYPE_QUERY_VALUE = "code";
|
||||
authorizationQuery.addQueryItem(OAUTH_RESPONSE_TYPE_QUERY_KEY, OAUTH_REPSONSE_TYPE_QUERY_VALUE);
|
||||
|
||||
const QString OAUTH_STATE_QUERY_KEY = "state";
|
||||
// create a new UUID that will be the state parameter for oauth authorization AND the new session UUID for that node
|
||||
authorizationQuery.addQueryItem(OAUTH_STATE_QUERY_KEY, uuidStringWithoutCurlyBraces(stateUUID));
|
||||
|
||||
authorizationQuery.addQueryItem(OAUTH_REDIRECT_URI_QUERY_KEY, oauthRedirectURL().toString());
|
||||
|
||||
authorizationURL.setQuery(authorizationQuery);
|
||||
|
||||
return authorizationURL;
|
||||
}
|
||||
|
||||
int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
|
||||
HifiSockAddr& localSockAddr, const QByteArray& packet,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
@ -451,52 +555,54 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
|
||||
int dataMTU = dtlsSession ? (int)gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE;
|
||||
|
||||
// if the node has any interest types, send back those nodes as well
|
||||
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
|
||||
|
||||
// reset our nodeByteArray and nodeDataStream
|
||||
QByteArray nodeByteArray;
|
||||
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
|
||||
|
||||
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
|
||||
if (nodeData->isAuthenticated()) {
|
||||
// if this authenticated node has any interest types, send back those nodes as well
|
||||
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
|
||||
|
||||
// don't send avatar nodes to other avatars, that will come from avatar mixer
|
||||
nodeDataStream << *otherNode.data();
|
||||
// reset our nodeByteArray and nodeDataStream
|
||||
QByteArray nodeByteArray;
|
||||
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
|
||||
|
||||
// pack the secret that these two nodes will use to communicate with each other
|
||||
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
|
||||
if (secretUUID.isNull()) {
|
||||
// generate a new secret UUID these two nodes can use
|
||||
secretUUID = QUuid::createUuid();
|
||||
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
|
||||
|
||||
// set that on the current Node's sessionSecretHash
|
||||
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
|
||||
// don't send avatar nodes to other avatars, that will come from avatar mixer
|
||||
nodeDataStream << *otherNode.data();
|
||||
|
||||
// set it on the other Node's sessionSecretHash
|
||||
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
|
||||
// pack the secret that these two nodes will use to communicate with each other
|
||||
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
|
||||
if (secretUUID.isNull()) {
|
||||
// generate a new secret UUID these two nodes can use
|
||||
secretUUID = QUuid::createUuid();
|
||||
|
||||
// set that on the current Node's sessionSecretHash
|
||||
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
|
||||
|
||||
// set it on the other Node's sessionSecretHash
|
||||
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
|
||||
->getSessionSecretHash().insert(node->getUUID(), secretUUID);
|
||||
|
||||
}
|
||||
|
||||
nodeDataStream << secretUUID;
|
||||
|
||||
if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) {
|
||||
// we need to break here and start a new packet
|
||||
// so send the current one
|
||||
|
||||
if (!dtlsSession) {
|
||||
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
|
||||
} else {
|
||||
dtlsSession->writeDatagram(broadcastPacket);
|
||||
|
||||
}
|
||||
|
||||
// reset the broadcastPacket structure
|
||||
broadcastPacket.resize(numBroadcastPacketLeadBytes);
|
||||
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes);
|
||||
nodeDataStream << secretUUID;
|
||||
|
||||
if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) {
|
||||
// we need to break here and start a new packet
|
||||
// so send the current one
|
||||
|
||||
if (!dtlsSession) {
|
||||
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
|
||||
} else {
|
||||
dtlsSession->writeDatagram(broadcastPacket);
|
||||
}
|
||||
|
||||
// reset the broadcastPacket structure
|
||||
broadcastPacket.resize(numBroadcastPacketLeadBytes);
|
||||
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes);
|
||||
}
|
||||
|
||||
// append the nodeByteArray to the current state of broadcastDataStream
|
||||
broadcastPacket.append(nodeByteArray);
|
||||
}
|
||||
|
||||
// append the nodeByteArray to the current state of broadcastDataStream
|
||||
broadcastPacket.append(nodeByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -670,9 +776,7 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
|
|||
PacketType requestType = packetTypeForPacket(receivedPacket);
|
||||
|
||||
if (requestType == PacketTypeDomainConnectRequest) {
|
||||
// add this node to our NodeList
|
||||
// and send back session UUID right away
|
||||
addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr);
|
||||
handleConnectRequest(receivedPacket, senderSockAddr);
|
||||
} else if (requestType == PacketTypeDomainListRequest) {
|
||||
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
|
||||
|
||||
|
@ -738,7 +842,8 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
|||
nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp());
|
||||
|
||||
// if the node has pool information, add it
|
||||
SharedAssignmentPointer matchingAssignment = _staticAssignmentHash.value(node->getUUID());
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
|
||||
if (matchingAssignment) {
|
||||
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
|
||||
}
|
||||
|
@ -774,9 +879,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
// enumerate the NodeList to find the assigned nodes
|
||||
foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
|
||||
if (_staticAssignmentHash.value(node->getUUID())) {
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
if (!nodeData->getAssignmentUUID().isNull()) {
|
||||
// add the node using the UUID as the key
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(nodeData->getAssignmentUUID());
|
||||
assignedNodesJSON[uuidString] = jsonObjectForNode(node);
|
||||
}
|
||||
}
|
||||
|
@ -786,7 +893,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
QJsonObject queuedAssignmentsJSON;
|
||||
|
||||
// add the queued but unfilled assignments to the json
|
||||
foreach(const SharedAssignmentPointer& assignment, _assignmentQueue) {
|
||||
foreach(const SharedAssignmentPointer& assignment, _unfulfilledAssignments) {
|
||||
QJsonObject queuedAssignmentJSON;
|
||||
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(assignment->getUUID());
|
||||
|
@ -903,7 +1010,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
.arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool));
|
||||
|
||||
// add the script assigment to the assignment queue
|
||||
_assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment));
|
||||
SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment);
|
||||
_unfulfilledAssignments.enqueue(sharedScriptedAssignment);
|
||||
_allAssignments.insert(sharedScriptedAssignment->getUUID(), sharedScriptedAssignment);
|
||||
}
|
||||
|
||||
// respond with a 200 code for successful upload
|
||||
|
@ -950,6 +1059,114 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
return false;
|
||||
}
|
||||
|
||||
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url) {
|
||||
const QString URI_OAUTH = "/oauth";
|
||||
if (url.path() == URI_OAUTH) {
|
||||
|
||||
QUrlQuery codeURLQuery(url);
|
||||
|
||||
const QString CODE_QUERY_KEY = "code";
|
||||
QString authorizationCode = codeURLQuery.queryItemValue(CODE_QUERY_KEY);
|
||||
|
||||
const QString STATE_QUERY_KEY = "state";
|
||||
QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY));
|
||||
|
||||
|
||||
if (!authorizationCode.isEmpty() && !stateUUID.isNull()) {
|
||||
// fire off a request with this code and state to get an access token for the user
|
||||
|
||||
const QString OAUTH_TOKEN_REQUEST_PATH = "/oauth/token";
|
||||
QUrl tokenRequestUrl = _oauthProviderURL;
|
||||
tokenRequestUrl.setPath(OAUTH_TOKEN_REQUEST_PATH);
|
||||
|
||||
const QString OAUTH_GRANT_TYPE_POST_STRING = "grant_type=authorization_code";
|
||||
QString tokenPostBody = OAUTH_GRANT_TYPE_POST_STRING;
|
||||
tokenPostBody += QString("&code=%1&redirect_uri=%2&client_id=%3&client_secret=%4")
|
||||
.arg(authorizationCode, oauthRedirectURL().toString(), _oauthClientID, _oauthClientSecret);
|
||||
|
||||
QNetworkRequest tokenRequest(tokenRequestUrl);
|
||||
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply* tokenReply = _networkAccessManager->post(tokenRequest, tokenPostBody.toLocal8Bit());
|
||||
|
||||
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
|
||||
|
||||
// insert this to our pending token replies so we can associate the returned access token with the right UUID
|
||||
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
|
||||
|
||||
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
|
||||
}
|
||||
|
||||
// respond with a 200 code indicating that login is complete
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
|
||||
|
||||
void DomainServer::handleTokenRequestFinished() {
|
||||
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
|
||||
|
||||
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
|
||||
// pull the access token from the returned JSON and store it with the matching session UUID
|
||||
QJsonDocument returnedJSON = QJsonDocument::fromJson(networkReply->readAll());
|
||||
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
|
||||
|
||||
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
|
||||
|
||||
// fire off a request to get this user's identity so we can see if we will let them in
|
||||
QUrl profileURL = _oauthProviderURL;
|
||||
profileURL.setPath("/api/v1/users/profile");
|
||||
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
|
||||
|
||||
QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL));
|
||||
|
||||
qDebug() << "Requesting access token for user with session UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
|
||||
|
||||
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished);
|
||||
|
||||
_networkReplyUUIDMap.insert(profileReply, matchingSessionUUID);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::handleProfileRequestFinished() {
|
||||
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
|
||||
|
||||
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
|
||||
QJsonDocument profileJSON = QJsonDocument::fromJson(networkReply->readAll());
|
||||
|
||||
if (profileJSON.object()["status"].toString() == "success") {
|
||||
// pull the user roles from the response
|
||||
QJsonArray userRolesArray = profileJSON.object()["data"].toObject()["user"].toObject()["roles"].toArray();
|
||||
|
||||
QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
|
||||
|
||||
bool shouldAllowUserToConnect = false;
|
||||
|
||||
foreach(const QJsonValue& roleValue, userRolesArray) {
|
||||
if (allowedRolesArray.contains(roleValue)) {
|
||||
// the user has a role that lets them in
|
||||
// set the bool to true and break
|
||||
shouldAllowUserToConnect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Confirmed authentication state for user" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
||||
<< "-" << shouldAllowUserToConnect;
|
||||
|
||||
// insert this UUID and a flag that indicates if they are allowed to connect
|
||||
_sessionAuthenticationHash.insert(matchingSessionUUID, shouldAllowUserToConnect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) {
|
||||
QUuid oldUUID = assignment->getUUID();
|
||||
assignment->resetUUID();
|
||||
|
@ -963,13 +1180,8 @@ void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer&
|
|||
}
|
||||
|
||||
// add the static assignment back under the right UUID, and to the queue
|
||||
_staticAssignmentHash.insert(assignment->getUUID(), assignment);
|
||||
|
||||
_assignmentQueue.enqueue(assignment);
|
||||
|
||||
// remove the old assignment from the _staticAssignmentHash
|
||||
// this must be done last so copies are created before the assignment passed by reference is killed
|
||||
_staticAssignmentHash.remove(oldUUID);
|
||||
_allAssignments.insert(assignment->getUUID(), assignment);
|
||||
_unfulfilledAssignments.enqueue(assignment);
|
||||
}
|
||||
|
||||
void DomainServer::nodeAdded(SharedNodePointer node) {
|
||||
|
@ -983,10 +1195,10 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
|
||||
if (nodeData) {
|
||||
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
|
||||
if (!nodeData->getStaticAssignmentUUID().isNull()) {
|
||||
SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(nodeData->getStaticAssignmentUUID());
|
||||
if (!nodeData->getAssignmentUUID().isNull()) {
|
||||
SharedAssignmentPointer matchedAssignment = _allAssignments.take(nodeData->getAssignmentUUID());
|
||||
|
||||
if (matchedAssignment) {
|
||||
if (matchedAssignment && matchedAssignment->isStatic()) {
|
||||
refreshStaticAssignmentAndAddToQueue(matchedAssignment);
|
||||
}
|
||||
}
|
||||
|
@ -1010,11 +1222,11 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
}
|
||||
|
||||
SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
|
||||
QQueue<SharedAssignmentPointer>::iterator i = _assignmentQueue.begin();
|
||||
QQueue<SharedAssignmentPointer>::iterator i = _unfulfilledAssignments.begin();
|
||||
|
||||
while (i != _assignmentQueue.end()) {
|
||||
while (i != _unfulfilledAssignments.end()) {
|
||||
if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
|
||||
return _assignmentQueue.takeAt(i - _assignmentQueue.begin());
|
||||
return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin());
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
|
@ -1026,9 +1238,9 @@ SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const Q
|
|||
SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assignment& requestAssignment) {
|
||||
// this is an unassigned client talking to us directly for an assignment
|
||||
// go through our queue and see if there are any assignments to give out
|
||||
QQueue<SharedAssignmentPointer>::iterator sharedAssignment = _assignmentQueue.begin();
|
||||
QQueue<SharedAssignmentPointer>::iterator sharedAssignment = _unfulfilledAssignments.begin();
|
||||
|
||||
while (sharedAssignment != _assignmentQueue.end()) {
|
||||
while (sharedAssignment != _unfulfilledAssignments.end()) {
|
||||
Assignment* assignment = sharedAssignment->data();
|
||||
bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes;
|
||||
bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType();
|
||||
|
@ -1038,16 +1250,12 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
|
|||
if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) {
|
||||
|
||||
// remove the assignment from the queue
|
||||
SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment
|
||||
- _assignmentQueue.begin());
|
||||
SharedAssignmentPointer deployableAssignment = _unfulfilledAssignments.takeAt(sharedAssignment
|
||||
- _unfulfilledAssignments.begin());
|
||||
|
||||
if (deployableAssignment->getType() != Assignment::AgentType
|
||||
|| _staticAssignmentHash.contains(deployableAssignment->getUUID())) {
|
||||
// this is a static assignment
|
||||
// until we get a check-in from that GUID
|
||||
// put assignment back in queue but stick it at the back so the others have a chance to go out
|
||||
_assignmentQueue.enqueue(deployableAssignment);
|
||||
}
|
||||
// until we get a connection for this assignment
|
||||
// put assignment back in queue but stick it at the back so the others have a chance to go out
|
||||
_unfulfilledAssignments.enqueue(deployableAssignment);
|
||||
|
||||
// stop looping, we've handed out an assignment
|
||||
return deployableAssignment;
|
||||
|
@ -1061,10 +1269,10 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
|
|||
}
|
||||
|
||||
void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) {
|
||||
QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _assignmentQueue.begin();
|
||||
while (potentialMatchingAssignment != _assignmentQueue.end()) {
|
||||
QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _unfulfilledAssignments.begin();
|
||||
while (potentialMatchingAssignment != _unfulfilledAssignments.end()) {
|
||||
if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) {
|
||||
_assignmentQueue.erase(potentialMatchingAssignment);
|
||||
_unfulfilledAssignments.erase(potentialMatchingAssignment);
|
||||
|
||||
// we matched and removed an assignment, bail out
|
||||
break;
|
||||
|
@ -1078,7 +1286,7 @@ void DomainServer::addStaticAssignmentsToQueue() {
|
|||
|
||||
// if the domain-server has just restarted,
|
||||
// check if there are static assignments that we need to throw into the assignment queue
|
||||
QHash<QUuid, SharedAssignmentPointer> staticHashCopy = _staticAssignmentHash;
|
||||
QHash<QUuid, SharedAssignmentPointer> staticHashCopy = _allAssignments;
|
||||
QHash<QUuid, SharedAssignmentPointer>::iterator staticAssignment = staticHashCopy.begin();
|
||||
while (staticAssignment != staticHashCopy.end()) {
|
||||
// add any of the un-matched static assignments to the queue
|
||||
|
|
|
@ -23,20 +23,21 @@
|
|||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include <Assignment.h>
|
||||
#include <HTTPManager.h>
|
||||
#include <HTTPSConnection.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DTLSServerSession.h"
|
||||
|
||||
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
||||
|
||||
class DomainServer : public QCoreApplication, public HTTPRequestHandler {
|
||||
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
|
||||
|
||||
void exit(int retCode = 0);
|
||||
|
||||
|
@ -52,12 +53,13 @@ private slots:
|
|||
void readAvailableDTLSDatagrams();
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallySetupDTLS();
|
||||
bool readX509KeyAndCertificate();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
|
||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
|
||||
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
|
||||
|
@ -76,13 +78,20 @@ private:
|
|||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
void handleTokenRequestFinished();
|
||||
void handleProfileRequestFinished();
|
||||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
HTTPManager _HTTPManager;
|
||||
HTTPManager _httpManager;
|
||||
HTTPSManager* _httpsManager;
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash;
|
||||
QQueue<SharedAssignmentPointer> _assignmentQueue;
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
|
||||
QVariantMap _argumentVariantMap;
|
||||
|
||||
|
@ -93,6 +102,15 @@ private:
|
|||
gnutls_priority_t* _priorityCache;
|
||||
|
||||
QHash<HifiSockAddr, DTLSServerSession*> _dtlsSessions;
|
||||
|
||||
QNetworkAccessManager* _networkAccessManager;
|
||||
|
||||
QUrl _oauthProviderURL;
|
||||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
QString _hostname;
|
||||
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
|
||||
QHash<QUuid, bool> _sessionAuthenticationHash;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServer_h
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
DomainServerNodeData::DomainServerNodeData() :
|
||||
_sessionSecretHash(),
|
||||
_staticAssignmentUUID(),
|
||||
_assignmentUUID(),
|
||||
_statsJSONObject(),
|
||||
_sendingSockAddr()
|
||||
_sendingSockAddr(),
|
||||
_isAuthenticated(true)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -27,20 +27,24 @@ public:
|
|||
|
||||
void parseJSONStatsPacket(const QByteArray& statsPacket);
|
||||
|
||||
void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; }
|
||||
const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; }
|
||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
|
||||
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
|
||||
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
|
||||
|
||||
void setIsAuthenticated(bool isAuthenticated) { _isAuthenticated = isAuthenticated; }
|
||||
bool isAuthenticated() const { return _isAuthenticated; }
|
||||
|
||||
QHash<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; }
|
||||
private:
|
||||
QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject);
|
||||
|
||||
QHash<QUuid, QUuid> _sessionSecretHash;
|
||||
QUuid _staticAssignmentUUID;
|
||||
QUuid _assignmentUUID;
|
||||
QJsonObject _statsJSONObject;
|
||||
HifiSockAddr _sendingSockAddr;
|
||||
bool _isAuthenticated;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerNodeData_h
|
||||
|
|
|
@ -81,9 +81,10 @@
|
|||
#include "scripting/LocationScriptingInterface.h"
|
||||
|
||||
#include "ui/InfoView.h"
|
||||
#include "ui/OAuthWebViewHandler.h"
|
||||
#include "ui/Snapshot.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
#include "ui/Stats.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -361,12 +362,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
QMutexLocker locker(&_settingsMutex);
|
||||
_previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();
|
||||
}
|
||||
|
||||
|
||||
connect(_window, &MainWindow::windowGeometryChanged,
|
||||
_runningScriptsWidget, &RunningScriptsWidget::setBoundary);
|
||||
|
||||
//When -url in command line, teleport to location
|
||||
urlGoTo(argc, constArgv);
|
||||
|
||||
//When -url in command line, teleport to location
|
||||
urlGoTo(argc, constArgv);
|
||||
|
||||
// call the OAuthWebviewHandler static getter so that its instance lives in our thread
|
||||
OAuthWebViewHandler::getInstance();
|
||||
// make sure the High Fidelity root CA is in our list of trusted certs
|
||||
OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig();
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
|
@ -3125,7 +3131,7 @@ void Application::updateWindowTitle(){
|
|||
QString buildVersion = " (build " + applicationVersion() + ")";
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
QString username = AccountManager::getInstance().getUsername();
|
||||
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
|
||||
QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString()
|
||||
+ " @ " + nodeList->getDomainHandler().getHostname() + buildVersion;
|
||||
qDebug("Application title set to: %s", title.toStdString().c_str());
|
||||
|
@ -3151,6 +3157,9 @@ void Application::domainChanged(const QString& domainHostname) {
|
|||
|
||||
// reset the voxels renderer
|
||||
_voxels.killLocalVoxels();
|
||||
|
||||
// reset the auth URL for OAuth web view handler
|
||||
OAuthWebViewHandler::getInstance().clearLastAuthorizationURL();
|
||||
}
|
||||
|
||||
void Application::connectedToDomain(const QString& hostname) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
#include "ui/OAuthWebViewHandler.h"
|
||||
|
||||
#include "DatagramProcessor.h"
|
||||
|
||||
|
@ -56,13 +57,11 @@ void DatagramProcessor::processDatagrams() {
|
|||
Particle::handleAddParticleResponse(incomingPacket);
|
||||
application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket);
|
||||
break;
|
||||
|
||||
case PacketTypeModelAddResponse:
|
||||
// this will keep creatorTokenIDs to IDs mapped correctly
|
||||
ModelItem::handleAddModelResponse(incomingPacket);
|
||||
application->getModels()->getTree()->handleAddModelResponse(incomingPacket);
|
||||
break;
|
||||
|
||||
case PacketTypeParticleData:
|
||||
case PacketTypeParticleErase:
|
||||
case PacketTypeModelData:
|
||||
|
@ -120,6 +119,18 @@ void DatagramProcessor::processDatagrams() {
|
|||
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
|
||||
break;
|
||||
}
|
||||
case PacketTypeDomainOAuthRequest: {
|
||||
QDataStream readStream(incomingPacket);
|
||||
readStream.skipRawData(numBytesForPacketHeader(incomingPacket));
|
||||
|
||||
QUrl authorizationURL;
|
||||
readStream >> authorizationURL;
|
||||
|
||||
QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL",
|
||||
Q_ARG(const QUrl&, authorizationURL));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
nodeList->processNodeData(senderSockAddr, incomingPacket);
|
||||
break;
|
||||
|
|
|
@ -1100,7 +1100,7 @@ void Menu::toggleLoginMenuItem() {
|
|||
|
||||
if (accountManager.isLoggedIn()) {
|
||||
// change the menu item to logout
|
||||
_loginAction->setText("Logout " + accountManager.getUsername());
|
||||
_loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername());
|
||||
connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout);
|
||||
} else {
|
||||
// change the menu item to login
|
||||
|
|
|
@ -34,12 +34,13 @@ XmppClient& XmppClient::getInstance() {
|
|||
|
||||
void XmppClient::xmppConnected() {
|
||||
_publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM);
|
||||
_publicChatRoom->setNickName(AccountManager::getInstance().getUsername());
|
||||
_publicChatRoom->setNickName(AccountManager::getInstance().getAccountInfo().getUsername());
|
||||
_publicChatRoom->join();
|
||||
}
|
||||
|
||||
void XmppClient::xmppError(QXmppClient::Error error) {
|
||||
qDebug() << "Error connnecting to XMPP for user " << AccountManager::getInstance().getUsername() << ": " << error;
|
||||
qDebug() << "Error connnecting to XMPP for user "
|
||||
<< AccountManager::getInstance().getAccountInfo().getUsername() << ": " << error;
|
||||
}
|
||||
|
||||
void XmppClient::connectToServer() {
|
||||
|
@ -50,8 +51,8 @@ void XmppClient::connectToServer() {
|
|||
connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(xmppError(QXmppClient::Error)));
|
||||
}
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
QString user = accountManager.getUsername();
|
||||
const QString& password = accountManager.getXMPPPassword();
|
||||
QString user = accountManager.getAccountInfo().getUsername();
|
||||
const QString& password = accountManager.getAccountInfo().getXMPPPassword();
|
||||
_xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
|
|||
}
|
||||
|
||||
// Update background if this is a message from the current user
|
||||
bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername();
|
||||
bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getAccountInfo().getUsername();
|
||||
|
||||
// Create message area
|
||||
ChatMessageArea* messageArea = new ChatMessageArea(true);
|
||||
|
|
116
interface/src/ui/OAuthWebViewHandler.cpp
Normal file
116
interface/src/ui/OAuthWebViewHandler.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
//
|
||||
// OAuthWebViewHandler.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-05-01.
|
||||
// Copyright 2014 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 <QtWebKitWidgets/QWebView>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "OAuthWebViewHandler.h"
|
||||
|
||||
OAuthWebViewHandler& OAuthWebViewHandler::getInstance() {
|
||||
static OAuthWebViewHandler sharedInstance;
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
OAuthWebViewHandler::OAuthWebViewHandler() :
|
||||
_activeWebView(NULL),
|
||||
_webViewRedisplayTimer(),
|
||||
_lastAuthorizationURL()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig() {
|
||||
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
|
||||
|
||||
// add the High Fidelity root CA to the list of trusted CA certificates
|
||||
QByteArray highFidelityCACertificate(reinterpret_cast<char*>(DTLSSession::highFidelityCADatum()->data),
|
||||
DTLSSession::highFidelityCADatum()->size);
|
||||
sslConfig.setCaCertificates(sslConfig.caCertificates() + QSslCertificate::fromData(highFidelityCACertificate));
|
||||
|
||||
// set the modified configuration
|
||||
QSslConfiguration::setDefaultConfiguration(sslConfig);
|
||||
}
|
||||
|
||||
const int WEB_VIEW_REDISPLAY_ELAPSED_MSECS = 5 * 1000;
|
||||
|
||||
void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authorizationURL) {
|
||||
if (!_activeWebView) {
|
||||
|
||||
if (!_lastAuthorizationURL.isEmpty()) {
|
||||
if (_lastAuthorizationURL.host() == authorizationURL.host()
|
||||
&& _webViewRedisplayTimer.elapsed() < WEB_VIEW_REDISPLAY_ELAPSED_MSECS) {
|
||||
// this would be re-displaying an OAuth dialog for the same auth URL inside of the redisplay ms
|
||||
// so return instead
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_lastAuthorizationURL = authorizationURL;
|
||||
|
||||
_activeWebView = new QWebView;
|
||||
|
||||
// keep the window on top and delete it when it closes
|
||||
_activeWebView->setWindowFlags(Qt::WindowStaysOnTopHint);
|
||||
_activeWebView->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
qDebug() << "Displaying QWebView for OAuth authorization at" << authorizationURL.toString();
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
QUrl codedAuthorizationURL = authorizationURL;
|
||||
|
||||
// check if we have an access token for this host - if so we can bypass login by adding it to the URL
|
||||
if (accountManager.getAuthURL().host() == authorizationURL.host()
|
||||
&& accountManager.hasValidAccessToken()) {
|
||||
|
||||
const QString ACCESS_TOKEN_QUERY_STRING_KEY = "access_token";
|
||||
|
||||
QUrlQuery authQuery(codedAuthorizationURL);
|
||||
authQuery.addQueryItem(ACCESS_TOKEN_QUERY_STRING_KEY, accountManager.getAccountInfo().getAccessToken().token);
|
||||
|
||||
codedAuthorizationURL.setQuery(authQuery);
|
||||
}
|
||||
|
||||
_activeWebView->load(codedAuthorizationURL);
|
||||
_activeWebView->show();
|
||||
|
||||
connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::sslErrors,
|
||||
this, &OAuthWebViewHandler::handleSSLErrors);
|
||||
connect(_activeWebView.data(), &QWebView::loadFinished, this, &OAuthWebViewHandler::handleLoadFinished);
|
||||
|
||||
// connect to the destroyed signal so after the web view closes we can start a timer
|
||||
connect(_activeWebView.data(), &QWebView::destroyed, this, &OAuthWebViewHandler::handleWebViewDestroyed);
|
||||
}
|
||||
}
|
||||
|
||||
void OAuthWebViewHandler::handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList) {
|
||||
qDebug() << "SSL Errors:" << errorList;
|
||||
}
|
||||
|
||||
void OAuthWebViewHandler::handleLoadFinished(bool success) {
|
||||
if (success && _activeWebView->url().host() == NodeList::getInstance()->getDomainHandler().getHostname()) {
|
||||
qDebug() << "OAuth authorization code passed successfully to domain-server.";
|
||||
|
||||
// grab the UUID that is set as the state parameter in the auth URL
|
||||
// since that is our new session UUID
|
||||
QUrlQuery authQuery(_activeWebView->url());
|
||||
|
||||
const QString AUTH_STATE_QUERY_KEY = "state";
|
||||
NodeList::getInstance()->setSessionUUID(QUuid(authQuery.queryItemValue(AUTH_STATE_QUERY_KEY)));
|
||||
|
||||
_activeWebView->close();
|
||||
}
|
||||
}
|
||||
|
||||
void OAuthWebViewHandler::handleWebViewDestroyed(QObject* destroyedObject) {
|
||||
_webViewRedisplayTimer.restart();
|
||||
}
|
41
interface/src/ui/OAuthWebViewHandler.h
Normal file
41
interface/src/ui/OAuthWebViewHandler.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// OAuthWebviewHandler.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-05-01.
|
||||
// Copyright 2014 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
|
||||
//
|
||||
|
||||
#ifndef hifi_OAuthWebviewHandler_h
|
||||
#define hifi_OAuthWebviewHandler_h
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
class QWebView;
|
||||
|
||||
class OAuthWebViewHandler : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OAuthWebViewHandler();
|
||||
static OAuthWebViewHandler& getInstance();
|
||||
static void addHighFidelityRootCAToSSLConfig();
|
||||
|
||||
void clearLastAuthorizationURL() { _lastAuthorizationURL = QUrl(); }
|
||||
|
||||
public slots:
|
||||
void displayWebviewForAuthorizationURL(const QUrl& authorizationURL);
|
||||
|
||||
private slots:
|
||||
void handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList);
|
||||
void handleLoadFinished(bool success);
|
||||
void handleWebViewDestroyed(QObject* destroyedObject);
|
||||
private:
|
||||
QPointer<QWebView> _activeWebView;
|
||||
QElapsedTimer _webViewRedisplayTimer;
|
||||
QUrl _lastAuthorizationURL;
|
||||
};
|
||||
|
||||
#endif // hifi_OAuthWebviewHandler_h
|
|
@ -86,7 +86,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
|||
// replace decimal . with '-'
|
||||
formattedLocation.replace('.', '-');
|
||||
|
||||
QString username = AccountManager::getInstance().getUsername();
|
||||
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
|
||||
// normalize username, replace all non alphanumeric with '-'
|
||||
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
|
|||
_parentManager(parentManager),
|
||||
_socket(socket),
|
||||
_stream(socket),
|
||||
_address(socket->peerAddress()) {
|
||||
|
||||
_address(socket->peerAddress())
|
||||
{
|
||||
// take over ownership of the socket
|
||||
_socket->setParent(this);
|
||||
|
||||
|
@ -42,7 +42,7 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
|
|||
HTTPConnection::~HTTPConnection() {
|
||||
// log the destruction
|
||||
if (_socket->error() != QAbstractSocket::UnknownSocketError) {
|
||||
qDebug() << _socket->errorString();
|
||||
qDebug() << _socket->errorString() << "-" << _socket->error();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
|
||||
#include <QDataStream>
|
||||
#include <QHash>
|
||||
#include <QHostAddress>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
#include <QIODevice>
|
||||
#include <QList>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QUrl>
|
||||
|
|
|
@ -18,116 +18,6 @@
|
|||
#include "HTTPConnection.h"
|
||||
#include "HTTPManager.h"
|
||||
|
||||
bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) {
|
||||
if (_requestHandler && _requestHandler->handleHTTPRequest(connection, url)) {
|
||||
// this request was handled by our _requestHandler object
|
||||
// so we don't need to attempt to do so in the document root
|
||||
return true;
|
||||
}
|
||||
|
||||
// check to see if there is a file to serve from the document root for this path
|
||||
QString subPath = url.path();
|
||||
|
||||
// remove any slash at the beginning of the path
|
||||
if (subPath.startsWith('/')) {
|
||||
subPath.remove(0, 1);
|
||||
}
|
||||
|
||||
QString filePath;
|
||||
|
||||
if (QFileInfo(_documentRoot + subPath).isFile()) {
|
||||
filePath = _documentRoot + subPath;
|
||||
} else if (subPath.size() > 0 && !subPath.endsWith('/')) {
|
||||
// this could be a directory with a trailing slash
|
||||
// send a redirect to the path with a slash so we can
|
||||
QString redirectLocation = '/' + subPath + '/';
|
||||
|
||||
if (!url.query().isEmpty()) {
|
||||
redirectLocation += "?" + url.query();
|
||||
}
|
||||
|
||||
QHash<QByteArray, QByteArray> redirectHeader;
|
||||
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||
}
|
||||
|
||||
// if the last thing is a trailing slash then we want to look for index file
|
||||
if (subPath.endsWith('/') || subPath.size() == 0) {
|
||||
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
|
||||
|
||||
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
|
||||
if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
|
||||
filePath = _documentRoot + subPath + possibleIndexFilename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
// file exists, serve it
|
||||
static QMimeDatabase mimeDatabase;
|
||||
|
||||
QFile localFile(filePath);
|
||||
localFile.open(QIODevice::ReadOnly);
|
||||
QByteArray localFileData = localFile.readAll();
|
||||
|
||||
QFileInfo localFileInfo(filePath);
|
||||
|
||||
if (localFileInfo.completeSuffix() == "shtml") {
|
||||
// this is a file that may have some SSI statements
|
||||
// the only thing we support is the include directive, but check the contents for that
|
||||
|
||||
// setup our static QRegExp that will catch <!--#include virtual ... --> and <!--#include file .. --> directives
|
||||
const QString includeRegExpString = "<!--\\s*#include\\s+(virtual|file)\\s?=\\s?\"(\\S+)\"\\s*-->";
|
||||
QRegExp includeRegExp(includeRegExpString);
|
||||
|
||||
int matchPosition = 0;
|
||||
|
||||
QString localFileString(localFileData);
|
||||
|
||||
while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) {
|
||||
// check if this is a file or vitual include
|
||||
bool isFileInclude = includeRegExp.cap(1) == "file";
|
||||
|
||||
// setup the correct file path for the included file
|
||||
QString includeFilePath = isFileInclude
|
||||
? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2)
|
||||
: _documentRoot + includeRegExp.cap(2);
|
||||
|
||||
QString replacementString;
|
||||
|
||||
if (QFileInfo(includeFilePath).isFile()) {
|
||||
|
||||
QFile includedFile(includeFilePath);
|
||||
includedFile.open(QIODevice::ReadOnly);
|
||||
|
||||
replacementString = QString(includedFile.readAll());
|
||||
} else {
|
||||
qDebug() << "SSI include directive referenced a missing file:" << includeFilePath;
|
||||
}
|
||||
|
||||
// replace the match with the contents of the file, or an empty string if the file was not found
|
||||
localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString);
|
||||
|
||||
// push the match position forward so we can check the next match
|
||||
matchPosition += includeRegExp.matchedLength();
|
||||
}
|
||||
|
||||
localFileData = localFileString.toLocal8Bit();
|
||||
}
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, localFileData,
|
||||
qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
|
||||
} else {
|
||||
|
||||
// respond with a 404
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) :
|
||||
QTcpServer(parent),
|
||||
_documentRoot(documentRoot),
|
||||
|
@ -138,14 +28,131 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH
|
|||
qDebug() << "Failed to open HTTP server socket:" << errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
// connect the connection signal
|
||||
connect(this, SIGNAL(newConnection()), SLOT(acceptConnections()));
|
||||
}
|
||||
|
||||
void HTTPManager::acceptConnections() {
|
||||
QTcpSocket* socket;
|
||||
while ((socket = nextPendingConnection()) != 0) {
|
||||
void HTTPManager::incomingConnection(qintptr socketDescriptor) {
|
||||
QTcpSocket* socket = new QTcpSocket(this);
|
||||
|
||||
if (socket->setSocketDescriptor(socketDescriptor)) {
|
||||
new HTTPConnection(socket, this);
|
||||
} else {
|
||||
delete socket;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) {
|
||||
if (requestHandledByRequestHandler(connection, url)) {
|
||||
// this request was handled by our request handler object
|
||||
// so we don't need to attempt to do so in the document root
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_documentRoot.isEmpty()) {
|
||||
// check to see if there is a file to serve from the document root for this path
|
||||
QString subPath = url.path();
|
||||
|
||||
// remove any slash at the beginning of the path
|
||||
if (subPath.startsWith('/')) {
|
||||
subPath.remove(0, 1);
|
||||
}
|
||||
|
||||
QString filePath;
|
||||
|
||||
if (QFileInfo(_documentRoot + subPath).isFile()) {
|
||||
filePath = _documentRoot + subPath;
|
||||
} else if (subPath.size() > 0 && !subPath.endsWith('/')) {
|
||||
// this could be a directory with a trailing slash
|
||||
// send a redirect to the path with a slash so we can
|
||||
QString redirectLocation = '/' + subPath + '/';
|
||||
|
||||
if (!url.query().isEmpty()) {
|
||||
redirectLocation += "?" + url.query();
|
||||
}
|
||||
|
||||
QHash<QByteArray, QByteArray> redirectHeader;
|
||||
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||
}
|
||||
|
||||
// if the last thing is a trailing slash then we want to look for index file
|
||||
if (subPath.endsWith('/') || subPath.size() == 0) {
|
||||
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
|
||||
|
||||
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
|
||||
if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
|
||||
filePath = _documentRoot + subPath + possibleIndexFilename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
// file exists, serve it
|
||||
static QMimeDatabase mimeDatabase;
|
||||
|
||||
QFile localFile(filePath);
|
||||
localFile.open(QIODevice::ReadOnly);
|
||||
QByteArray localFileData = localFile.readAll();
|
||||
|
||||
QFileInfo localFileInfo(filePath);
|
||||
|
||||
if (localFileInfo.completeSuffix() == "shtml") {
|
||||
// this is a file that may have some SSI statements
|
||||
// the only thing we support is the include directive, but check the contents for that
|
||||
|
||||
// setup our static QRegExp that will catch <!--#include virtual ... --> and <!--#include file .. --> directives
|
||||
const QString includeRegExpString = "<!--\\s*#include\\s+(virtual|file)\\s?=\\s?\"(\\S+)\"\\s*-->";
|
||||
QRegExp includeRegExp(includeRegExpString);
|
||||
|
||||
int matchPosition = 0;
|
||||
|
||||
QString localFileString(localFileData);
|
||||
|
||||
while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) {
|
||||
// check if this is a file or vitual include
|
||||
bool isFileInclude = includeRegExp.cap(1) == "file";
|
||||
|
||||
// setup the correct file path for the included file
|
||||
QString includeFilePath = isFileInclude
|
||||
? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2)
|
||||
: _documentRoot + includeRegExp.cap(2);
|
||||
|
||||
QString replacementString;
|
||||
|
||||
if (QFileInfo(includeFilePath).isFile()) {
|
||||
|
||||
QFile includedFile(includeFilePath);
|
||||
includedFile.open(QIODevice::ReadOnly);
|
||||
|
||||
replacementString = QString(includedFile.readAll());
|
||||
} else {
|
||||
qDebug() << "SSI include directive referenced a missing file:" << includeFilePath;
|
||||
}
|
||||
|
||||
// replace the match with the contents of the file, or an empty string if the file was not found
|
||||
localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString);
|
||||
|
||||
// push the match position forward so we can check the next match
|
||||
matchPosition += includeRegExp.matchedLength();
|
||||
}
|
||||
|
||||
localFileData = localFileString.toLocal8Bit();
|
||||
}
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, localFileData,
|
||||
qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// respond with a 404
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) {
|
||||
return _requestHandler && _requestHandler->handleHTTPRequest(connection, url);
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
#include <QtNetwork/QTcpServer>
|
||||
|
||||
class HTTPConnection;
|
||||
class HTTPSConnection;
|
||||
|
||||
class HTTPRequestHandler {
|
||||
public:
|
||||
|
@ -35,9 +36,10 @@ public:
|
|||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
protected slots:
|
||||
protected:
|
||||
/// Accepts all pending connections
|
||||
void acceptConnections();
|
||||
virtual void incomingConnection(qintptr socketDescriptor);
|
||||
virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url);
|
||||
protected:
|
||||
QString _documentRoot;
|
||||
HTTPRequestHandler* _requestHandler;
|
||||
|
|
23
libraries/embedded-webserver/src/HTTPSConnection.cpp
Normal file
23
libraries/embedded-webserver/src/HTTPSConnection.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// HTTPSConnection.cpp
|
||||
// libraries/embedded-webserver/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-24.
|
||||
// Copyright 2014 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 "HTTPSConnection.h"
|
||||
|
||||
HTTPSConnection::HTTPSConnection(QSslSocket* sslSocket, HTTPSManager* parentManager) :
|
||||
HTTPConnection(sslSocket, parentManager)
|
||||
{
|
||||
connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError>&)), this, SLOT(handleSSLErrors(const QList<QSslError>&)));
|
||||
sslSocket->startServerEncryption();
|
||||
}
|
||||
|
||||
void HTTPSConnection::handleSSLErrors(const QList<QSslError>& errors) {
|
||||
qDebug() << "SSL errors:" << errors;
|
||||
}
|
26
libraries/embedded-webserver/src/HTTPSConnection.h
Normal file
26
libraries/embedded-webserver/src/HTTPSConnection.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// HTTPSConnection.h
|
||||
// libraries/embedded-webserver/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-24.
|
||||
// Copyright 2014 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
|
||||
//
|
||||
|
||||
#ifndef hifi_HTTPSConnection_h
|
||||
#define hifi_HTTPSConnection_h
|
||||
|
||||
#include "HTTPConnection.h"
|
||||
#include "HTTPSManager.h"
|
||||
|
||||
class HTTPSConnection : public HTTPConnection {
|
||||
Q_OBJECT
|
||||
public:
|
||||
HTTPSConnection(QSslSocket* sslSocket, HTTPSManager* parentManager);
|
||||
protected slots:
|
||||
void handleSSLErrors(const QList<QSslError>& errors);
|
||||
};
|
||||
|
||||
#endif // hifi_HTTPSConnection_h
|
51
libraries/embedded-webserver/src/HTTPSManager.cpp
Normal file
51
libraries/embedded-webserver/src/HTTPSManager.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// HTTPSManager.cpp
|
||||
// libraries/embedded-webserver/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-24.
|
||||
// Copyright 2014 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 <QtNetwork/QSslSocket>
|
||||
|
||||
#include "HTTPSConnection.h"
|
||||
|
||||
#include "HTTPSManager.h"
|
||||
|
||||
HTTPSManager::HTTPSManager(quint16 port, const QSslCertificate& certificate, const QSslKey& privateKey,
|
||||
const QString& documentRoot, HTTPSRequestHandler* requestHandler, QObject* parent) :
|
||||
HTTPManager(port, documentRoot, requestHandler, parent),
|
||||
_certificate(certificate),
|
||||
_privateKey(privateKey),
|
||||
_sslRequestHandler(requestHandler)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HTTPSManager::incomingConnection(qintptr socketDescriptor) {
|
||||
QSslSocket* sslSocket = new QSslSocket(this);
|
||||
|
||||
sslSocket->setLocalCertificate(_certificate);
|
||||
sslSocket->setPrivateKey(_privateKey);
|
||||
|
||||
if (sslSocket->setSocketDescriptor(socketDescriptor)) {
|
||||
new HTTPSConnection(sslSocket, this);
|
||||
} else {
|
||||
delete sslSocket;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPSManager::handleHTTPRequest(HTTPConnection* connection, const QUrl &url) {
|
||||
return handleHTTPSRequest(reinterpret_cast<HTTPSConnection*>(connection), url);
|
||||
}
|
||||
|
||||
bool HTTPSManager::handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url) {
|
||||
return HTTPManager::handleHTTPRequest(connection, url);
|
||||
}
|
||||
|
||||
bool HTTPSManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) {
|
||||
return _sslRequestHandler && _sslRequestHandler->handleHTTPSRequest(reinterpret_cast<HTTPSConnection*>(connection), url);
|
||||
}
|
50
libraries/embedded-webserver/src/HTTPSManager.h
Normal file
50
libraries/embedded-webserver/src/HTTPSManager.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// HTTPSManager.h
|
||||
// libraries/embedded-webserver/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-24.
|
||||
// Copyright 2014 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
|
||||
//
|
||||
|
||||
#ifndef hifi_HTTPSManager_h
|
||||
#define hifi_HTTPSManager_h
|
||||
|
||||
#include <QtNetwork/QSslKey>
|
||||
#include <QtNetwork/QSslCertificate>
|
||||
|
||||
#include "HTTPManager.h"
|
||||
|
||||
class HTTPSRequestHandler : public HTTPRequestHandler {
|
||||
public:
|
||||
/// Handles an HTTPS request
|
||||
virtual bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url) = 0;
|
||||
};
|
||||
|
||||
class HTTPSManager : public HTTPManager, public HTTPSRequestHandler {
|
||||
Q_OBJECT
|
||||
public:
|
||||
HTTPSManager(quint16 port,
|
||||
const QSslCertificate& certificate,
|
||||
const QSslKey& privateKey,
|
||||
const QString& documentRoot,
|
||||
HTTPSRequestHandler* requestHandler = NULL, QObject* parent = 0);
|
||||
|
||||
void setCertificate(const QSslCertificate& certificate) { _certificate = certificate; }
|
||||
void setPrivateKey(const QSslKey& privateKey) { _privateKey = privateKey; }
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
|
||||
|
||||
protected:
|
||||
void incomingConnection(qintptr socketDescriptor);
|
||||
bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url);
|
||||
private:
|
||||
QSslCertificate _certificate;
|
||||
QSslKey _privateKey;
|
||||
HTTPSRequestHandler* _sslRequestHandler;
|
||||
};
|
||||
|
||||
#endif // hifi_HTTPSManager_h
|
|
@ -55,9 +55,7 @@ public:
|
|||
|
||||
void requestAccessToken(const QString& login, const QString& password);
|
||||
|
||||
QString getUsername() const { return _accountInfo.getUsername(); }
|
||||
|
||||
const QString& getXMPPPassword() const { return _accountInfo.getXMPPPassword(); }
|
||||
const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; }
|
||||
|
||||
void destroy() { delete _networkAccessManager; }
|
||||
|
||||
|
|
|
@ -50,7 +50,8 @@ Assignment::Assignment() :
|
|||
_type(Assignment::AllTypes),
|
||||
_pool(),
|
||||
_location(Assignment::LocalLocation),
|
||||
_payload()
|
||||
_payload(),
|
||||
_isStatic(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -61,7 +62,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
|
|||
_type(type),
|
||||
_pool(pool),
|
||||
_location(location),
|
||||
_payload()
|
||||
_payload(),
|
||||
_isStatic(false)
|
||||
{
|
||||
if (_command == Assignment::CreateCommand) {
|
||||
// this is a newly created assignment, generate a random UUID
|
||||
|
|
|
@ -78,6 +78,9 @@ public:
|
|||
void setPool(const QString& pool) { _pool = pool; };
|
||||
const QString& getPool() const { return _pool; }
|
||||
|
||||
void setIsStatic(bool isStatic) { _isStatic = isStatic; }
|
||||
bool isStatic() const { return _isStatic; }
|
||||
|
||||
const char* getTypeName() const;
|
||||
|
||||
// implement parseData to return 0 so we can be a subclass of NodeData
|
||||
|
@ -94,6 +97,7 @@ protected:
|
|||
QString _pool; /// the destination pool for this assignment
|
||||
Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs
|
||||
QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed
|
||||
bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled
|
||||
};
|
||||
|
||||
#endif // hifi_Assignment_h
|
||||
|
|
|
@ -78,7 +78,7 @@ gnutls_datum_t* DTLSSession::highFidelityCADatum() {
|
|||
static bool datumInitialized = false;
|
||||
|
||||
static unsigned char HIGHFIDELITY_ROOT_CA_CERT[] =
|
||||
"-----BEGIN CERTIFICATE-----"
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n"
|
||||
"VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n"
|
||||
"aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n"
|
||||
|
@ -100,7 +100,7 @@ gnutls_datum_t* DTLSSession::highFidelityCADatum() {
|
|||
"SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n"
|
||||
"FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n"
|
||||
"Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n"
|
||||
"-----END CERTIFICATE-----";
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
if (!datumInitialized) {
|
||||
hifiCADatum.data = HIGHFIDELITY_ROOT_CA_CERT;
|
||||
|
|
|
@ -22,8 +22,11 @@
|
|||
#include "HifiSockAddr.h"
|
||||
|
||||
const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io";
|
||||
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102;
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103;
|
||||
const quint16 DOMAIN_SERVER_HTTP_PORT = 40100;
|
||||
const quint16 DOMAIN_SERVER_HTTPS_PORT = 40101;
|
||||
|
||||
class DomainHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -106,6 +106,10 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() {
|
|||
|
||||
_dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress);
|
||||
|
||||
// we're using DTLS and our socket is good to go, so make the required DTLS changes
|
||||
// DTLS requires that IP_DONTFRAG be set
|
||||
// This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it
|
||||
|
||||
#if defined(IP_DONTFRAG) || defined(IP_MTU_DISCOVER)
|
||||
qDebug() << "Making required DTLS changes to LimitedNodeList DTLS socket.";
|
||||
|
||||
|
|
|
@ -378,12 +378,17 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
}
|
||||
}
|
||||
|
||||
PacketType domainPacketType = _sessionUUID.isNull()
|
||||
PacketType domainPacketType = !_domainHandler.isConnected()
|
||||
? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest;
|
||||
|
||||
// construct the DS check in packet
|
||||
QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest
|
||||
? _sessionUUID : _domainHandler.getAssignmentUUID());
|
||||
QUuid packetUUID = _sessionUUID;
|
||||
|
||||
if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) {
|
||||
// this is a connect request and we're an assigned node
|
||||
// so set our packetUUID as the assignment UUID
|
||||
packetUUID = _domainHandler.getAssignmentUUID();
|
||||
}
|
||||
|
||||
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID);
|
||||
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
|
||||
|
@ -404,7 +409,6 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
dtlsSession->writeDatagram(domainServerPacket);
|
||||
}
|
||||
|
||||
|
||||
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
|
||||
static unsigned int numDomainCheckins = 0;
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ enum PacketType {
|
|||
PacketTypeDomainListRequest,
|
||||
PacketTypeRequestAssignment,
|
||||
PacketTypeCreateAssignment,
|
||||
PacketTypeDataServerPut, // reusable
|
||||
PacketTypeDomainOAuthRequest,
|
||||
PacketTypeDataServerGet, // reusable
|
||||
PacketTypeDataServerSend, // reusable
|
||||
PacketTypeDataServerConfirm,
|
||||
|
@ -72,7 +72,7 @@ typedef char PacketVersion;
|
|||
|
||||
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
||||
<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest
|
||||
<< PacketTypeDomainList << PacketTypeDomainListRequest
|
||||
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest
|
||||
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
|
||||
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeModelQuery;
|
||||
|
||||
|
|
Loading…
Reference in a new issue