mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-22 18:13:52 +02:00
Merge pull request #7179 from birarda/domain-security
verified domain ownership for full automatic networking
This commit is contained in:
commit
2e7c7eace5
26 changed files with 752 additions and 357 deletions
|
@ -236,10 +236,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) :
|
||||||
{
|
{
|
||||||
_averageLoopTime.updateAverage(0);
|
_averageLoopTime.updateAverage(0);
|
||||||
qDebug() << "Octree server starting... [" << this << "]";
|
qDebug() << "Octree server starting... [" << this << "]";
|
||||||
|
|
||||||
// make sure the AccountManager has an Auth URL for payment redemptions
|
|
||||||
|
|
||||||
AccountManager::getInstance().setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OctreeServer::~OctreeServer() {
|
OctreeServer::~OctreeServer() {
|
||||||
|
|
|
@ -778,7 +778,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
||||||
function createTemporaryDomain() {
|
function createTemporaryDomain() {
|
||||||
swal({
|
swal({
|
||||||
title: 'Create temporary place name',
|
title: 'Create temporary place name',
|
||||||
text: "This will create a temporary place name and domain ID (valid for 30 days)"
|
text: "This will create a temporary place name and domain ID"
|
||||||
+ " so other users can easily connect to your domain.</br></br>"
|
+ " so other users can easily connect to your domain.</br></br>"
|
||||||
+ "In order to make your domain reachable, this will also enable full automatic networking.",
|
+ "In order to make your domain reachable, this will also enable full automatic networking.",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
|
|
|
@ -331,7 +331,6 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
QCryptographicHash::Sha256);
|
QCryptographicHash::Sha256);
|
||||||
|
|
||||||
if (rsaPublicKey) {
|
if (rsaPublicKey) {
|
||||||
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
|
|
||||||
int decryptResult = RSA_verify(NID_sha256,
|
int decryptResult = RSA_verify(NID_sha256,
|
||||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
||||||
usernameWithToken.size(),
|
usernameWithToken.size(),
|
||||||
|
|
|
@ -96,7 +96,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
// make sure we hear about newly connected nodes from our gatekeeper
|
// make sure we hear about newly connected nodes from our gatekeeper
|
||||||
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
||||||
|
|
||||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
|
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
||||||
// we either read a certificate and private key or were not passed one
|
// we either read a certificate and private key or were not passed one
|
||||||
// and completed login or did not need to
|
// and completed login or did not need to
|
||||||
|
|
||||||
|
@ -198,7 +198,6 @@ bool DomainServer::optionallySetupOAuth() {
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
AccountManager& accountManager = AccountManager::getInstance();
|
||||||
accountManager.disableSettingsFilePersistence();
|
|
||||||
accountManager.setAuthURL(_oauthProviderURL);
|
accountManager.setAuthURL(_oauthProviderURL);
|
||||||
|
|
||||||
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
|
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
|
||||||
|
@ -372,20 +371,12 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||||
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
|
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
|
||||||
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
|
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
|
||||||
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
|
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
|
||||||
|
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
|
||||||
|
|
||||||
// add whatever static assignments that have been parsed to the queue
|
// add whatever static assignments that have been parsed to the queue
|
||||||
addStaticAssignmentsToQueue();
|
addStaticAssignmentsToQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainServer::didSetupAccountManagerWithAccessToken() {
|
|
||||||
if (AccountManager::getInstance().hasValidAccessToken()) {
|
|
||||||
// we already gave the account manager a valid access token
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resetAccountManagerAccessToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
|
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
|
||||||
|
|
||||||
bool DomainServer::resetAccountManagerAccessToken() {
|
bool DomainServer::resetAccountManagerAccessToken() {
|
||||||
|
@ -401,9 +392,13 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
||||||
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
|
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
|
||||||
accessToken = accessTokenVariant->toString();
|
accessToken = accessTokenVariant->toString();
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."
|
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present.";
|
||||||
<< "Set an access token via the web interface, in your user or master config"
|
qDebug() << "Set an access token via the web interface, in your user or master config"
|
||||||
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
|
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
|
||||||
|
|
||||||
|
// clear any existing access token from AccountManager
|
||||||
|
AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString());
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -429,34 +424,6 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainServer::optionallySetupAssignmentPayment() {
|
|
||||||
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
|
||||||
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
|
||||||
|
|
||||||
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
|
|
||||||
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
|
|
||||||
didSetupAccountManagerWithAccessToken()) {
|
|
||||||
|
|
||||||
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
|
|
||||||
|
|
||||||
// assume that the fact we are authing against HF data server means we will pay for assignments
|
|
||||||
// setup a timer to send transactions to pay assigned nodes every 30 seconds
|
|
||||||
QTimer* creditSetupTimer = new QTimer(this);
|
|
||||||
connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
|
|
||||||
|
|
||||||
const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
|
|
||||||
creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
|
|
||||||
|
|
||||||
QTimer* nodePaymentTimer = new QTimer(this);
|
|
||||||
connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
|
|
||||||
|
|
||||||
const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
|
|
||||||
nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DomainServer::setupAutomaticNetworking() {
|
void DomainServer::setupAutomaticNetworking() {
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
|
@ -467,9 +434,9 @@ void DomainServer::setupAutomaticNetworking() {
|
||||||
setupICEHeartbeatForFullNetworking();
|
setupICEHeartbeatForFullNetworking();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didSetupAccountManagerWithAccessToken()) {
|
if (!resetAccountManagerAccessToken()) {
|
||||||
qDebug() << "Cannot send heartbeat to data server without an access token.";
|
qDebug() << "Will not send heartbeat to Metaverse API without an access token.";
|
||||||
qDebug() << "Add an access token to your config file or via the web interface.";
|
qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface.";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -526,6 +493,19 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
||||||
// we need this DS to know what our public IP is - start trying to figure that out now
|
// we need this DS to know what our public IP is - start trying to figure that out now
|
||||||
limitedNodeList->startSTUNPublicSocketUpdate();
|
limitedNodeList->startSTUNPublicSocketUpdate();
|
||||||
|
|
||||||
|
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
|
||||||
|
auto& accountManager = AccountManager::getInstance();
|
||||||
|
auto domainID = accountManager.getAccountInfo().getDomainID();
|
||||||
|
|
||||||
|
// if we have an access token and we don't have a private key or the current domain ID has changed
|
||||||
|
// we should generate a new keypair
|
||||||
|
if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
|
||||||
|
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// hookup to the signal from account manager that tells us when keypair is available
|
||||||
|
connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
|
||||||
|
|
||||||
if (!_iceHeartbeatTimer) {
|
if (!_iceHeartbeatTimer) {
|
||||||
// setup a timer to heartbeat with the ice-server every so often
|
// setup a timer to heartbeat with the ice-server every so often
|
||||||
_iceHeartbeatTimer = new QTimer { this };
|
_iceHeartbeatTimer = new QTimer { this };
|
||||||
|
@ -1082,11 +1062,76 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
|
||||||
domainUpdateJSON.toUtf8());
|
domainUpdateJSON.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: have data-web respond with ice-server hostname to use
|
|
||||||
|
|
||||||
void DomainServer::sendHeartbeatToIceServer() {
|
void DomainServer::sendHeartbeatToIceServer() {
|
||||||
if (!_iceServerSocket.getAddress().isNull()) {
|
if (!_iceServerSocket.getAddress().isNull()) {
|
||||||
DependencyManager::get<LimitedNodeList>()->sendHeartbeatToIceServer(_iceServerSocket);
|
|
||||||
|
auto& accountManager = AccountManager::getInstance();
|
||||||
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
|
if (!accountManager.getAccountInfo().hasPrivateKey()) {
|
||||||
|
qWarning() << "Cannot send an ice-server heartbeat without a private key for signature.";
|
||||||
|
qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat.";
|
||||||
|
|
||||||
|
if (!limitedNodeList->getSessionUUID().isNull()) {
|
||||||
|
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||||
|
} else {
|
||||||
|
qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: I'd love to specify the correct size for the packet here, but it's a little trickey with
|
||||||
|
// QDataStream and the possibility of IPv6 address for the sockets.
|
||||||
|
if (!_iceServerHeartbeatPacket) {
|
||||||
|
_iceServerHeartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldRecreatePacket = false;
|
||||||
|
|
||||||
|
if (_iceServerHeartbeatPacket->getPayloadSize() > 0) {
|
||||||
|
// if either of our sockets have changed we need to re-sign the heartbeat
|
||||||
|
// first read the sockets out from the current packet
|
||||||
|
_iceServerHeartbeatPacket->seek(0);
|
||||||
|
QDataStream heartbeatStream(_iceServerHeartbeatPacket.get());
|
||||||
|
|
||||||
|
QUuid senderUUID;
|
||||||
|
HifiSockAddr publicSocket, localSocket;
|
||||||
|
heartbeatStream >> senderUUID >> publicSocket >> localSocket;
|
||||||
|
|
||||||
|
if (senderUUID != limitedNodeList->getSessionUUID()
|
||||||
|
|| publicSocket != limitedNodeList->getPublicSockAddr()
|
||||||
|
|| localSocket != limitedNodeList->getLocalSockAddr()) {
|
||||||
|
shouldRecreatePacket = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shouldRecreatePacket = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldRecreatePacket) {
|
||||||
|
// either we don't have a heartbeat packet yet or some combination of sockets, ID and keypair have changed
|
||||||
|
// and we need to make a new one
|
||||||
|
|
||||||
|
// reset the position in the packet before writing
|
||||||
|
_iceServerHeartbeatPacket->reset();
|
||||||
|
|
||||||
|
// write our plaintext data to the packet
|
||||||
|
QDataStream heartbeatDataStream(_iceServerHeartbeatPacket.get());
|
||||||
|
heartbeatDataStream << limitedNodeList->getSessionUUID()
|
||||||
|
<< limitedNodeList->getPublicSockAddr() << limitedNodeList->getLocalSockAddr();
|
||||||
|
|
||||||
|
// setup a QByteArray that points to the plaintext data
|
||||||
|
auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize());
|
||||||
|
|
||||||
|
// generate a signature for the plaintext data in the packet
|
||||||
|
auto signature = accountManager.getAccountInfo().signPlaintext(plaintext);
|
||||||
|
|
||||||
|
// pack the signature with the data
|
||||||
|
heartbeatDataStream << signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the heartbeat packet to the ice server now
|
||||||
|
limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1970,3 +2015,31 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
|
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
||||||
|
|
||||||
|
static int numHeartbeatDenials = 0;
|
||||||
|
if (++numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) {
|
||||||
|
qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server"
|
||||||
|
<< "- re-generating keypair now";
|
||||||
|
|
||||||
|
// we've hit our threshold of heartbeat denials, trigger a keypair re-generation
|
||||||
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||||
|
|
||||||
|
// reset our number of heartbeat denials
|
||||||
|
numHeartbeatDenials = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::handleKeypairChange() {
|
||||||
|
if (_iceServerHeartbeatPacket) {
|
||||||
|
// reset the payload size of the ice-server heartbeat packet - this causes the packet to be re-generated
|
||||||
|
// the next time we go to send an ice-server heartbeat
|
||||||
|
_iceServerHeartbeatPacket->setPayloadSize(0);
|
||||||
|
|
||||||
|
// send a heartbeat to the ice server immediately
|
||||||
|
sendHeartbeatToIceServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ public slots:
|
||||||
void processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode);
|
void processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode);
|
||||||
void processPathQueryPacket(QSharedPointer<ReceivedMessage> packet);
|
void processPathQueryPacket(QSharedPointer<ReceivedMessage> packet);
|
||||||
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
|
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void aboutToQuit();
|
void aboutToQuit();
|
||||||
|
@ -78,16 +79,16 @@ private slots:
|
||||||
void handleTempDomainError(QNetworkReply& requestReply);
|
void handleTempDomainError(QNetworkReply& requestReply);
|
||||||
|
|
||||||
void queuedQuit(QString quitMessage, int exitCode);
|
void queuedQuit(QString quitMessage, int exitCode);
|
||||||
|
|
||||||
|
void handleKeypairChange();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||||
bool optionallySetupOAuth();
|
bool optionallySetupOAuth();
|
||||||
bool optionallyReadX509KeyAndCertificate();
|
bool optionallyReadX509KeyAndCertificate();
|
||||||
bool optionallySetupAssignmentPayment();
|
|
||||||
|
|
||||||
void optionallyGetTemporaryName(const QStringList& arguments);
|
void optionallyGetTemporaryName(const QStringList& arguments);
|
||||||
|
|
||||||
bool didSetupAccountManagerWithAccessToken();
|
|
||||||
bool resetAccountManagerAccessToken();
|
bool resetAccountManagerAccessToken();
|
||||||
|
|
||||||
void setupAutomaticNetworking();
|
void setupAutomaticNetworking();
|
||||||
|
@ -153,6 +154,7 @@ private:
|
||||||
DomainServerSettingsManager _settingsManager;
|
DomainServerSettingsManager _settingsManager;
|
||||||
|
|
||||||
HifiSockAddr _iceServerSocket;
|
HifiSockAddr _iceServerSocket;
|
||||||
|
std::unique_ptr<NLPacket> _iceServerHeartbeatPacket;
|
||||||
|
|
||||||
QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer
|
QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,17 @@ setup_hifi_project(Network)
|
||||||
# link the shared hifi libraries
|
# link the shared hifi libraries
|
||||||
link_hifi_libraries(embedded-webserver networking shared)
|
link_hifi_libraries(embedded-webserver networking shared)
|
||||||
package_libraries_for_deployment()
|
package_libraries_for_deployment()
|
||||||
|
|
||||||
|
# find OpenSSL
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
|
||||||
|
if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include")
|
||||||
|
# this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto
|
||||||
|
message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings."
|
||||||
|
"\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
||||||
|
|
||||||
|
# append OpenSSL to our list of libraries to link
|
||||||
|
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
|
||||||
|
|
|
@ -9,14 +9,21 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <QTimer>
|
#include "IceServer.h"
|
||||||
|
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
|
||||||
#include <LimitedNodeList.h>
|
#include <LimitedNodeList.h>
|
||||||
|
#include <NetworkingConstants.h>
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
#include "IceServer.h"
|
|
||||||
|
|
||||||
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
|
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
|
||||||
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
||||||
|
|
||||||
|
@ -45,7 +52,6 @@ IceServer::IceServer(int argc, char* argv[]) :
|
||||||
QTimer* inactivePeerTimer = new QTimer(this);
|
QTimer* inactivePeerTimer = new QTimer(this);
|
||||||
connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers);
|
connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers);
|
||||||
inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS);
|
inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IceServer::packetVersionMatch(const udt::Packet& packet) {
|
bool IceServer::packetVersionMatch(const udt::Packet& packet) {
|
||||||
|
@ -70,9 +76,14 @@ void IceServer::processPacket(std::unique_ptr<udt::Packet> packet) {
|
||||||
|
|
||||||
if (nlPacket->getType() == PacketType::ICEServerHeartbeat) {
|
if (nlPacket->getType() == PacketType::ICEServerHeartbeat) {
|
||||||
SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket);
|
SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket);
|
||||||
|
if (peer) {
|
||||||
// so that we can send packets to the heartbeating peer when we need, we need to activate a socket now
|
// so that we can send packets to the heartbeating peer when we need, we need to activate a socket now
|
||||||
peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());
|
peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());
|
||||||
|
} else {
|
||||||
|
// we couldn't verify this peer - respond back to them so they know they may need to perform keypair re-generation
|
||||||
|
static auto deniedPacket = NLPacket::create(PacketType::ICEServerHeartbeatDenied);
|
||||||
|
_serverSocket.writePacket(*deniedPacket, nlPacket->getSenderSockAddr());
|
||||||
|
}
|
||||||
} else if (nlPacket->getType() == PacketType::ICEServerQuery) {
|
} else if (nlPacket->getType() == PacketType::ICEServerQuery) {
|
||||||
QDataStream heartbeatStream(nlPacket.get());
|
QDataStream heartbeatStream(nlPacket.get());
|
||||||
|
|
||||||
|
@ -114,31 +125,135 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) {
|
||||||
// pull the UUID, public and private sock addrs for this peer
|
// pull the UUID, public and private sock addrs for this peer
|
||||||
QUuid senderUUID;
|
QUuid senderUUID;
|
||||||
HifiSockAddr publicSocket, localSocket;
|
HifiSockAddr publicSocket, localSocket;
|
||||||
|
QByteArray signature;
|
||||||
|
|
||||||
QDataStream heartbeatStream(&packet);
|
QDataStream heartbeatStream(&packet);
|
||||||
|
heartbeatStream >> senderUUID >> publicSocket >> localSocket;
|
||||||
heartbeatStream >> senderUUID;
|
|
||||||
heartbeatStream >> publicSocket >> localSocket;
|
|
||||||
|
|
||||||
// make sure we have this sender in our peer hash
|
auto signedPlaintext = QByteArray::fromRawData(packet.getPayload(), heartbeatStream.device()->pos());
|
||||||
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
|
heartbeatStream >> signature;
|
||||||
|
|
||||||
if (!matchingPeer) {
|
// make sure this is a verified heartbeat before performing any more processing
|
||||||
// if we don't have this sender we need to create them now
|
if (isVerifiedHeartbeat(senderUUID, signedPlaintext, signature)) {
|
||||||
matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);
|
// make sure we have this sender in our peer hash
|
||||||
_activePeers.insert(senderUUID, matchingPeer);
|
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
|
||||||
|
|
||||||
qDebug() << "Added a new network peer" << *matchingPeer;
|
if (!matchingPeer) {
|
||||||
|
// if we don't have this sender we need to create them now
|
||||||
|
matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);
|
||||||
|
_activePeers.insert(senderUUID, matchingPeer);
|
||||||
|
|
||||||
|
qDebug() << "Added a new network peer" << *matchingPeer;
|
||||||
|
} else {
|
||||||
|
// we already had the peer so just potentially update their sockets
|
||||||
|
matchingPeer->setPublicSocket(publicSocket);
|
||||||
|
matchingPeer->setLocalSocket(localSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update our last heard microstamp for this network peer to now
|
||||||
|
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
|
||||||
|
|
||||||
|
return matchingPeer;
|
||||||
} else {
|
} else {
|
||||||
// we already had the peer so just potentially update their sockets
|
// not verified, return the empty peer object
|
||||||
matchingPeer->setPublicSocket(publicSocket);
|
return SharedNetworkPeer();
|
||||||
matchingPeer->setLocalSocket(localSocket);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) {
|
||||||
|
// check if we have a private key for this domain ID - if we do not then fire off the request for it
|
||||||
|
auto it = _domainPublicKeys.find(domainID);
|
||||||
|
if (it != _domainPublicKeys.end()) {
|
||||||
|
|
||||||
|
// attempt to verify the signature for this heartbeat
|
||||||
|
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(it->second.constData());
|
||||||
|
|
||||||
|
// first load up the public key into an RSA struct
|
||||||
|
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, it->second.size());
|
||||||
|
|
||||||
|
if (rsaPublicKey) {
|
||||||
|
auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);
|
||||||
|
int verificationResult = RSA_verify(NID_sha256,
|
||||||
|
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
|
||||||
|
hashedPlaintext.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(signature.constData()),
|
||||||
|
signature.size(),
|
||||||
|
rsaPublicKey);
|
||||||
|
|
||||||
|
// free up the public key and remove connection token before we return
|
||||||
|
RSA_free(rsaPublicKey);
|
||||||
|
|
||||||
|
if (verificationResult == 1) {
|
||||||
|
// this is the only success case - we return true here to indicate that the heartbeat is verified
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API.";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||||
|
qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key.";
|
||||||
|
qWarning() << "Re-requesting public key from API";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update our last heard microstamp for this network peer to now
|
// we could not verify this heartbeat (missing public key, could not load public key, bad actor)
|
||||||
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
|
// ask the metaverse API for the right public key and return false to indicate that this is not verified
|
||||||
|
requestDomainPublicKey(domainID);
|
||||||
|
|
||||||
return matchingPeer;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IceServer::requestDomainPublicKey(const QUuid& domainID) {
|
||||||
|
// send a request to the metaverse API for the public key for this domain
|
||||||
|
QNetworkAccessManager* manager = new QNetworkAccessManager { this };
|
||||||
|
connect(manager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished);
|
||||||
|
|
||||||
|
QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL };
|
||||||
|
QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID));
|
||||||
|
publicKeyURL.setPath(publicKeyPath);
|
||||||
|
|
||||||
|
QNetworkRequest publicKeyRequest { publicKeyURL };
|
||||||
|
publicKeyRequest.setAttribute(QNetworkRequest::User, domainID);
|
||||||
|
|
||||||
|
qDebug() << "Requesting public key for domain with ID" << domainID;
|
||||||
|
|
||||||
|
manager->get(publicKeyRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IceServer::publicKeyReplyFinished(QNetworkReply* reply) {
|
||||||
|
// get the domain ID from the QNetworkReply attribute
|
||||||
|
QUuid domainID = reply->request().attribute(QNetworkRequest::User).toUuid();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
// pull out the public key and store it for this domain
|
||||||
|
|
||||||
|
// the response should be JSON
|
||||||
|
QJsonDocument responseDocument = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
|
static const QString DATA_KEY = "data";
|
||||||
|
static const QString PUBLIC_KEY_KEY = "public_key";
|
||||||
|
static const QString STATUS_KEY = "status";
|
||||||
|
static const QString SUCCESS_VALUE = "success";
|
||||||
|
|
||||||
|
auto responseObject = responseDocument.object();
|
||||||
|
if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) {
|
||||||
|
auto dataObject = responseObject[DATA_KEY].toObject();
|
||||||
|
if (dataObject.contains(PUBLIC_KEY_KEY)) {
|
||||||
|
_domainPublicKeys[domainID] = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8());
|
||||||
|
} else {
|
||||||
|
qWarning() << "There was no public key present in response for domain with ID" << domainID;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning() << "The metaverse API did not return success for public key request for domain with ID" << domainID;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// there was a problem getting the public key for the domain
|
||||||
|
// log it since it will be re-requested on the next heartbeat
|
||||||
|
|
||||||
|
qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" << reply->errorString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) {
|
void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) {
|
||||||
|
|
|
@ -16,13 +16,15 @@
|
||||||
#include <QtCore/QSharedPointer>
|
#include <QtCore/QSharedPointer>
|
||||||
#include <QUdpSocket>
|
#include <QUdpSocket>
|
||||||
|
|
||||||
|
#include <UUIDHasher.h>
|
||||||
|
|
||||||
#include <NetworkPeer.h>
|
#include <NetworkPeer.h>
|
||||||
#include <HTTPConnection.h>
|
#include <HTTPConnection.h>
|
||||||
#include <HTTPManager.h>
|
#include <HTTPManager.h>
|
||||||
#include <NLPacket.h>
|
#include <NLPacket.h>
|
||||||
#include <udt/Socket.h>
|
#include <udt/Socket.h>
|
||||||
|
|
||||||
typedef QHash<QUuid, SharedNetworkPeer> NetworkPeerHash;
|
class QNetworkReply;
|
||||||
|
|
||||||
class IceServer : public QCoreApplication, public HTTPRequestHandler {
|
class IceServer : public QCoreApplication, public HTTPRequestHandler {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -31,6 +33,7 @@ public:
|
||||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||||
private slots:
|
private slots:
|
||||||
void clearInactivePeers();
|
void clearInactivePeers();
|
||||||
|
void publicKeyReplyFinished(QNetworkReply* reply);
|
||||||
private:
|
private:
|
||||||
bool packetVersionMatch(const udt::Packet& packet);
|
bool packetVersionMatch(const udt::Packet& packet);
|
||||||
void processPacket(std::unique_ptr<udt::Packet> packet);
|
void processPacket(std::unique_ptr<udt::Packet> packet);
|
||||||
|
@ -38,10 +41,19 @@ private:
|
||||||
SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket);
|
SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket);
|
||||||
void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr);
|
void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr);
|
||||||
|
|
||||||
|
bool isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature);
|
||||||
|
void requestDomainPublicKey(const QUuid& domainID);
|
||||||
|
|
||||||
QUuid _id;
|
QUuid _id;
|
||||||
udt::Socket _serverSocket;
|
udt::Socket _serverSocket;
|
||||||
|
|
||||||
|
using NetworkPeerHash = QHash<QUuid, SharedNetworkPeer>;
|
||||||
NetworkPeerHash _activePeers;
|
NetworkPeerHash _activePeers;
|
||||||
|
|
||||||
HTTPManager _httpManager;
|
HTTPManager _httpManager;
|
||||||
|
|
||||||
|
using DomainPublicKeyHash = std::unordered_map<QUuid, QByteArray>;
|
||||||
|
DomainPublicKeyHash _domainPublicKeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_IceServer_h
|
#endif // hifi_IceServer_h
|
||||||
|
|
|
@ -553,7 +553,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
||||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
||||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
||||||
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
|
|
||||||
|
|
||||||
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
|
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
|
||||||
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
||||||
|
@ -590,6 +589,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
|
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
|
||||||
|
|
||||||
// set the account manager's root URL and trigger a login request if we don't have the access token
|
// set the account manager's root URL and trigger a login request if we don't have the access token
|
||||||
|
accountManager.setIsAgent(true);
|
||||||
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
||||||
UserActivityLogger::getInstance().launch(applicationVersion());
|
UserActivityLogger::getInstance().launch(applicationVersion());
|
||||||
|
|
||||||
|
@ -903,9 +903,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
SpacemouseManager::getInstance().init();
|
SpacemouseManager::getInstance().init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
|
||||||
packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket");
|
|
||||||
|
|
||||||
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
|
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
||||||
|
@ -3957,30 +3954,10 @@ void Application::clearDomainOctreeDetails() {
|
||||||
void Application::domainChanged(const QString& domainHostname) {
|
void Application::domainChanged(const QString& domainHostname) {
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
clearDomainOctreeDetails();
|
clearDomainOctreeDetails();
|
||||||
_domainConnectionRefusals.clear();
|
|
||||||
// disable physics until we have enough information about our new location to not cause craziness.
|
// disable physics until we have enough information about our new location to not cause craziness.
|
||||||
_physicsEnabled = false;
|
_physicsEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
|
|
||||||
// Read deny reason from packet
|
|
||||||
quint16 reasonSize;
|
|
||||||
message->readPrimitive(&reasonSize);
|
|
||||||
QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize));
|
|
||||||
|
|
||||||
// output to the log so the user knows they got a denied connection request
|
|
||||||
// and check and signal for an access token so that we can make sure they are logged in
|
|
||||||
qCDebug(interfaceapp) << "The domain-server denied a connection request: " << reason;
|
|
||||||
qCDebug(interfaceapp) << "You may need to re-log to generate a keypair so you can provide a username signature.";
|
|
||||||
|
|
||||||
if (!_domainConnectionRefusals.contains(reason)) {
|
|
||||||
_domainConnectionRefusals.append(reason);
|
|
||||||
emit domainConnectionRefused(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountManager::getInstance().checkAndSignalForAccessToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::connectedToDomain(const QString& hostname) {
|
void Application::connectedToDomain(const QString& hostname) {
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
AccountManager& accountManager = AccountManager::getInstance();
|
||||||
const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID();
|
const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID();
|
||||||
|
@ -4524,33 +4501,6 @@ void Application::openUrl(const QUrl& url) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
|
|
||||||
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
|
|
||||||
const QString VOXEL_SETTINGS_KEY = "voxels";
|
|
||||||
const QString PER_VOXEL_COST_KEY = "per-voxel-credits";
|
|
||||||
const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits";
|
|
||||||
const QString VOXEL_WALLET_UUID = "voxel-wallet";
|
|
||||||
|
|
||||||
const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject();
|
|
||||||
|
|
||||||
qint64 satoshisPerVoxel = 0;
|
|
||||||
qint64 satoshisPerMeterCubed = 0;
|
|
||||||
QUuid voxelWalletUUID;
|
|
||||||
|
|
||||||
if (!domainSettingsObject.isEmpty()) {
|
|
||||||
float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble();
|
|
||||||
float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble();
|
|
||||||
|
|
||||||
satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT);
|
|
||||||
satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT);
|
|
||||||
|
|
||||||
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(interfaceapp) << "Octree edits costs are" << satoshisPerVoxel << "per octree cell and" << satoshisPerMeterCubed << "per meter cubed";
|
|
||||||
qCDebug(interfaceapp) << "Destination wallet UUID for edit payments is" << voxelWalletUUID;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::loadDialog() {
|
void Application::loadDialog() {
|
||||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||||
QString fileNameString = OffscreenUi::getOpenFileName(
|
QString fileNameString = OffscreenUi::getOpenFileName(
|
||||||
|
|
|
@ -224,7 +224,6 @@ signals:
|
||||||
void svoImportRequested(const QString& url);
|
void svoImportRequested(const QString& url);
|
||||||
|
|
||||||
void checkBackgroundDownloads();
|
void checkBackgroundDownloads();
|
||||||
void domainConnectionRefused(const QString& reason);
|
|
||||||
|
|
||||||
void fullAvatarURLChanged(const QString& newValue, const QString& modelName);
|
void fullAvatarURLChanged(const QString& newValue, const QString& modelName);
|
||||||
|
|
||||||
|
@ -292,9 +291,6 @@ private slots:
|
||||||
|
|
||||||
void activeChanged(Qt::ApplicationState state);
|
void activeChanged(Qt::ApplicationState state);
|
||||||
|
|
||||||
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
|
|
||||||
void handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
|
|
||||||
|
|
||||||
void notifyPacketVersionMismatch();
|
void notifyPacketVersionMismatch();
|
||||||
|
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
|
@ -473,7 +469,6 @@ private:
|
||||||
typedef bool (Application::* AcceptURLMethod)(const QString &);
|
typedef bool (Application::* AcceptURLMethod)(const QString &);
|
||||||
static const QHash<QString, AcceptURLMethod> _acceptedExtensions;
|
static const QHash<QString, AcceptURLMethod> _acceptedExtensions;
|
||||||
|
|
||||||
QList<QString> _domainConnectionRefusals;
|
|
||||||
glm::uvec2 _renderResolution;
|
glm::uvec2 _renderResolution;
|
||||||
|
|
||||||
int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS;
|
int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS;
|
||||||
|
|
|
@ -22,11 +22,8 @@
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "DataServerAccountInfo.h"
|
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(DataServerAccountInfo)
|
|
||||||
|
|
||||||
static const QString RUNNING_MARKER_FILENAME = "Interface.running";
|
static const QString RUNNING_MARKER_FILENAME = "Interface.running";
|
||||||
|
|
||||||
void CrashHandler::checkForAndHandleCrash() {
|
void CrashHandler::checkForAndHandleCrash() {
|
||||||
|
@ -57,7 +54,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
||||||
layout->addWidget(label);
|
layout->addWidget(label);
|
||||||
|
|
||||||
QRadioButton* option1 = new QRadioButton("Reset all my settings");
|
QRadioButton* option1 = new QRadioButton("Reset all my settings");
|
||||||
QRadioButton* option2 = new QRadioButton("Reset my settings but retain login and avatar info.");
|
QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info.");
|
||||||
QRadioButton* option3 = new QRadioButton("Continue with my current settings");
|
QRadioButton* option3 = new QRadioButton("Continue with my current settings");
|
||||||
option3->setChecked(true);
|
option3->setChecked(true);
|
||||||
layout->addWidget(option1);
|
layout->addWidget(option1);
|
||||||
|
@ -79,7 +76,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
||||||
return CrashHandler::DELETE_INTERFACE_INI;
|
return CrashHandler::DELETE_INTERFACE_INI;
|
||||||
}
|
}
|
||||||
if (option2->isChecked()) {
|
if (option2->isChecked()) {
|
||||||
return CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO;
|
return CrashHandler::RETAIN_AVATAR_INFO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +85,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrashHandler::handleCrash(CrashHandler::Action action) {
|
void CrashHandler::handleCrash(CrashHandler::Action action) {
|
||||||
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) {
|
||||||
// CrashHandler::DO_NOTHING or unexpected value
|
// CrashHandler::DO_NOTHING or unexpected value
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -101,18 +98,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
||||||
const QString DISPLAY_NAME_KEY = "displayName";
|
const QString DISPLAY_NAME_KEY = "displayName";
|
||||||
const QString FULL_AVATAR_URL_KEY = "fullAvatarURL";
|
const QString FULL_AVATAR_URL_KEY = "fullAvatarURL";
|
||||||
const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName";
|
const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName";
|
||||||
const QString ACCOUNTS_GROUP = "accounts";
|
|
||||||
QString displayName;
|
QString displayName;
|
||||||
QUrl fullAvatarURL;
|
QUrl fullAvatarURL;
|
||||||
QString fullAvatarModelName;
|
QString fullAvatarModelName;
|
||||||
QUrl address;
|
QUrl address;
|
||||||
QMap<QString, DataServerAccountInfo> accounts;
|
|
||||||
|
|
||||||
if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
if (action == CrashHandler::RETAIN_AVATAR_INFO) {
|
||||||
// Read login and avatar info
|
// Read avatar info
|
||||||
|
|
||||||
qRegisterMetaType<DataServerAccountInfo>("DataServerAccountInfo");
|
|
||||||
qRegisterMetaTypeStreamOperators<DataServerAccountInfo>("DataServerAccountInfo");
|
|
||||||
|
|
||||||
// Location and orientation
|
// Location and orientation
|
||||||
settings.beginGroup(ADDRESS_MANAGER_GROUP);
|
settings.beginGroup(ADDRESS_MANAGER_GROUP);
|
||||||
|
@ -125,13 +117,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
||||||
fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
|
fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
|
||||||
fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
|
fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
// Accounts
|
|
||||||
settings.beginGroup(ACCOUNTS_GROUP);
|
|
||||||
foreach(const QString& key, settings.allKeys()) {
|
|
||||||
accounts.insert(key, settings.value(key).value<DataServerAccountInfo>());
|
|
||||||
}
|
|
||||||
settings.endGroup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete Interface.ini
|
// Delete Interface.ini
|
||||||
|
@ -140,8 +125,8 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
||||||
settingsFile.remove();
|
settingsFile.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
if (action == CrashHandler::RETAIN_AVATAR_INFO) {
|
||||||
// Write login and avatar info
|
// Write avatar info
|
||||||
|
|
||||||
// Location and orientation
|
// Location and orientation
|
||||||
settings.beginGroup(ADDRESS_MANAGER_GROUP);
|
settings.beginGroup(ADDRESS_MANAGER_GROUP);
|
||||||
|
@ -154,13 +139,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
||||||
settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
|
settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
|
||||||
settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
|
settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
// Accounts
|
|
||||||
settings.beginGroup(ACCOUNTS_GROUP);
|
|
||||||
foreach(const QString& key, accounts.keys()) {
|
|
||||||
settings.setValue(key, QVariant::fromValue(accounts.value(key)));
|
|
||||||
}
|
|
||||||
settings.endGroup();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ public:
|
||||||
private:
|
private:
|
||||||
enum Action {
|
enum Action {
|
||||||
DELETE_INTERFACE_INI,
|
DELETE_INTERFACE_INI,
|
||||||
RETAIN_LOGIN_AND_AVATAR_INFO,
|
RETAIN_AVATAR_INFO,
|
||||||
DO_NOTHING
|
DO_NOTHING
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
WindowScriptingInterface::WindowScriptingInterface() {
|
WindowScriptingInterface::WindowScriptingInterface() {
|
||||||
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
|
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
|
||||||
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
|
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
|
||||||
|
|
||||||
connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) {
|
connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) {
|
||||||
static const QMetaMethod svoImportRequestedSignal =
|
static const QMetaMethod svoImportRequestedSignal =
|
||||||
|
|
|
@ -12,10 +12,12 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QtCore/QDataStream>
|
#include <QtCore/QDataStream>
|
||||||
|
#include <QtCore/QFile>
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
#include <QtCore/QJsonObject>
|
#include <QtCore/QJsonObject>
|
||||||
#include <QtCore/QMap>
|
#include <QtCore/QMap>
|
||||||
#include <QtCore/QStringList>
|
#include <QtCore/QStringList>
|
||||||
|
#include <QtCore/QStandardPaths>
|
||||||
#include <QtCore/QUrlQuery>
|
#include <QtCore/QUrlQuery>
|
||||||
#include <QtNetwork/QHttpMultiPart>
|
#include <QtNetwork/QHttpMultiPart>
|
||||||
#include <QtNetwork/QNetworkRequest>
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
@ -60,13 +62,12 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co
|
||||||
updateReciever(updateReceiver),
|
updateReciever(updateReceiver),
|
||||||
updateSlot(updateSlot)
|
updateSlot(updateSlot)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountManager::AccountManager() :
|
AccountManager::AccountManager() :
|
||||||
_authURL(),
|
_authURL(),
|
||||||
_pendingCallbackMap(),
|
_pendingCallbackMap()
|
||||||
_accountInfo(),
|
|
||||||
_shouldPersistToSettingsFile(true)
|
|
||||||
{
|
{
|
||||||
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
|
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
|
||||||
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
|
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
|
||||||
|
@ -80,9 +81,6 @@ AccountManager::AccountManager() :
|
||||||
qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*");
|
qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*");
|
||||||
|
|
||||||
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
||||||
|
|
||||||
// once we have a profile in account manager make sure we generate a new keypair
|
|
||||||
connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewKeypair);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
|
const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
|
||||||
|
@ -93,16 +91,9 @@ void AccountManager::logout() {
|
||||||
|
|
||||||
emit balanceChanged(0);
|
emit balanceChanged(0);
|
||||||
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
||||||
|
|
||||||
if (_shouldPersistToSettingsFile) {
|
// remove this account from the account settings file
|
||||||
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
|
removeAccountFromFile();
|
||||||
QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString;
|
|
||||||
Setting::Handle<DataServerAccountInfo>(path).remove();
|
|
||||||
|
|
||||||
qCDebug(networking) << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
|
|
||||||
} else {
|
|
||||||
qCDebug(networking) << "Cleared data server account info in account manager.";
|
|
||||||
}
|
|
||||||
|
|
||||||
emit logoutComplete();
|
emit logoutComplete();
|
||||||
// the username has changed to blank
|
// the username has changed to blank
|
||||||
|
@ -124,35 +115,83 @@ void AccountManager::accountInfoBalanceChanged(qint64 newBalance) {
|
||||||
emit balanceChanged(newBalance);
|
emit balanceChanged(newBalance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString accountFilePath() {
|
||||||
|
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/AccountInfo.bin";
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap accountMapFromFile(bool& success) {
|
||||||
|
QFile accountFile { accountFilePath() };
|
||||||
|
|
||||||
|
if (accountFile.open(QIODevice::ReadOnly)) {
|
||||||
|
// grab the current QVariantMap from the settings file
|
||||||
|
QDataStream readStream(&accountFile);
|
||||||
|
QVariantMap accountMap;
|
||||||
|
|
||||||
|
readStream >> accountMap;
|
||||||
|
|
||||||
|
// close the file now that we have read the data
|
||||||
|
accountFile.close();
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
return accountMap;
|
||||||
|
} else {
|
||||||
|
// failed to open file, return empty QVariantMap
|
||||||
|
// there was only an error if the account file existed when we tried to load it
|
||||||
|
success = !accountFile.exists();
|
||||||
|
|
||||||
|
return QVariantMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AccountManager::setAuthURL(const QUrl& authURL) {
|
void AccountManager::setAuthURL(const QUrl& authURL) {
|
||||||
if (_authURL != authURL) {
|
if (_authURL != authURL) {
|
||||||
_authURL = authURL;
|
_authURL = authURL;
|
||||||
|
|
||||||
qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
|
qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
|
||||||
|
|
||||||
if (_shouldPersistToSettingsFile) {
|
// check if there are existing access tokens to load from settings
|
||||||
|
QFile accountsFile { accountFilePath() };
|
||||||
|
bool loadedMap = false;
|
||||||
|
auto accountsMap = accountMapFromFile(loadedMap);
|
||||||
|
|
||||||
|
if (accountsFile.exists() && loadedMap) {
|
||||||
|
// pull out the stored account info and store it in memory
|
||||||
|
_accountInfo = accountsMap[_authURL.toString()].value<DataServerAccountInfo>();
|
||||||
|
|
||||||
|
qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString());
|
||||||
|
} else {
|
||||||
|
// we didn't have a file - see if we can migrate old settings and store them in the new file
|
||||||
|
|
||||||
// check if there are existing access tokens to load from settings
|
// check if there are existing access tokens to load from settings
|
||||||
Settings settings;
|
Settings settings;
|
||||||
settings.beginGroup(ACCOUNTS_GROUP);
|
settings.beginGroup(ACCOUNTS_GROUP);
|
||||||
|
|
||||||
foreach(const QString& key, settings.allKeys()) {
|
foreach(const QString& key, settings.allKeys()) {
|
||||||
// take a key copy to perform the double slash replacement
|
// take a key copy to perform the double slash replacement
|
||||||
QString keyCopy(key);
|
QString keyCopy(key);
|
||||||
QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//"));
|
QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//"));
|
||||||
|
|
||||||
if (keyURL == _authURL) {
|
if (keyURL == _authURL) {
|
||||||
// pull out the stored access token and store it in memory
|
// pull out the stored access token and store it in memory
|
||||||
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
|
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
|
||||||
qCDebug(networking) << "Found a data-server access token for" << qPrintable(keyURL.toString());
|
|
||||||
|
qCDebug(networking) << "Migrated an access token for" << qPrintable(keyURL.toString())
|
||||||
// profile info isn't guaranteed to be saved too
|
<< "from previous settings file";
|
||||||
if (_accountInfo.hasProfile()) {
|
|
||||||
emit profileChanged();
|
|
||||||
} else {
|
|
||||||
requestProfile();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_accountInfo.getAccessToken().token.isEmpty()) {
|
||||||
|
qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded.";
|
||||||
|
} else {
|
||||||
|
// persist the migrated settings to file
|
||||||
|
persistAccountToFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty() && !_accountInfo.hasProfile()) {
|
||||||
|
// we are missing profile information, request it now
|
||||||
|
requestProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// tell listeners that the auth endpoint has changed
|
// tell listeners that the auth endpoint has changed
|
||||||
|
@ -299,9 +338,11 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||||
qCDebug(networking) << "Received JSON response from data-server that has no matching callback.";
|
qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback.";
|
||||||
qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll());
|
qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestReply->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,22 +358,69 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
|
||||||
_pendingCallbackMap.remove(requestReply);
|
_pendingCallbackMap.remove(requestReply);
|
||||||
} else {
|
} else {
|
||||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||||
qCDebug(networking) << "Received error response from data-server that has no matching callback.";
|
qCDebug(networking) << "Received error response from metaverse API that has no matching callback.";
|
||||||
qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString();
|
qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString();
|
||||||
qCDebug(networking) << requestReply->readAll();
|
qCDebug(networking) << requestReply->readAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestReply->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountManager::persistAccountToSettings() {
|
bool writeAccountMapToFile(const QVariantMap& accountMap) {
|
||||||
if (_shouldPersistToSettingsFile) {
|
// re-open the file and truncate it
|
||||||
// store this access token into the local settings
|
QFile accountFile { accountFilePath() };
|
||||||
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
|
if (accountFile.open(QIODevice::WriteOnly)) {
|
||||||
QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString;
|
QDataStream writeStream(&accountFile);
|
||||||
Setting::Handle<QVariant>(path).set(QVariant::fromValue(_accountInfo));
|
|
||||||
|
// persist the updated account QVariantMap to file
|
||||||
|
writeStream << accountMap;
|
||||||
|
|
||||||
|
// close the file with the newly persisted settings
|
||||||
|
accountFile.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountManager::persistAccountToFile() {
|
||||||
|
|
||||||
|
qCDebug(networking) << "Persisting AccountManager accounts to" << accountFilePath();
|
||||||
|
|
||||||
|
bool wasLoaded = false;
|
||||||
|
auto accountMap = accountMapFromFile(wasLoaded);
|
||||||
|
|
||||||
|
if (wasLoaded) {
|
||||||
|
// replace the current account information for this auth URL in the account map
|
||||||
|
accountMap[_authURL.toString()] = QVariant::fromValue(_accountInfo);
|
||||||
|
|
||||||
|
// re-open the file and truncate it
|
||||||
|
if (writeAccountMapToFile(accountMap)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qCWarning(networking) << "Could not load accounts file - unable to persist account information to file.";
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccountManager::removeAccountFromFile() {
|
||||||
|
bool wasLoaded = false;
|
||||||
|
auto accountMap = accountMapFromFile(wasLoaded);
|
||||||
|
|
||||||
|
if (wasLoaded) {
|
||||||
|
accountMap.remove(_authURL.toString());
|
||||||
|
if (writeAccountMapToFile(accountMap)) {
|
||||||
|
qCDebug(networking) << "Removed account info for" << _authURL << "from settings file.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qCWarning(networking) << "Count not load accounts file - unable to remove account information for" << _authURL
|
||||||
|
<< "from settings file.";
|
||||||
|
}
|
||||||
|
|
||||||
bool AccountManager::hasValidAccessToken() {
|
bool AccountManager::hasValidAccessToken() {
|
||||||
|
|
||||||
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
|
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
|
||||||
|
@ -359,16 +447,19 @@ bool AccountManager::checkAndSignalForAccessToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) {
|
void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) {
|
||||||
// clear our current DataServerAccountInfo
|
// replace the account info access token with a new OAuthAccessToken
|
||||||
_accountInfo = DataServerAccountInfo();
|
|
||||||
|
|
||||||
// start the new account info with a new OAuthAccessToken
|
|
||||||
OAuthAccessToken newOAuthToken;
|
OAuthAccessToken newOAuthToken;
|
||||||
newOAuthToken.token = accessToken;
|
newOAuthToken.token = accessToken;
|
||||||
|
|
||||||
qCDebug(networking) << "Setting new account manager access token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2);
|
if (!accessToken.isEmpty()) {
|
||||||
|
qCDebug(networking) << "Setting new AccountManager OAuth token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2);
|
||||||
|
} else if (!_accountInfo.getAccessToken().token.isEmpty()) {
|
||||||
|
qCDebug(networking) << "Clearing AccountManager OAuth token.";
|
||||||
|
}
|
||||||
|
|
||||||
_accountInfo.setAccessToken(newOAuthToken);
|
_accountInfo.setAccessToken(newOAuthToken);
|
||||||
|
|
||||||
|
persistAccountToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
|
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
|
||||||
|
@ -423,7 +514,7 @@ void AccountManager::requestAccessTokenFinished() {
|
||||||
|
|
||||||
emit loginComplete(rootURL);
|
emit loginComplete(rootURL);
|
||||||
|
|
||||||
persistAccountToSettings();
|
persistAccountToFile();
|
||||||
|
|
||||||
requestProfile();
|
requestProfile();
|
||||||
}
|
}
|
||||||
|
@ -469,7 +560,7 @@ void AccountManager::requestProfileFinished() {
|
||||||
emit usernameChanged(_accountInfo.getUsername());
|
emit usernameChanged(_accountInfo.getUsername());
|
||||||
|
|
||||||
// store the whole profile into the local settings
|
// store the whole profile into the local settings
|
||||||
persistAccountToSettings();
|
persistAccountToFile();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: error handling
|
// TODO: error handling
|
||||||
|
@ -482,57 +573,141 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
|
||||||
qCDebug(networking) << "AccountManager requestProfileError - " << error;
|
qCDebug(networking) << "AccountManager requestProfileError - " << error;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountManager::generateNewKeypair() {
|
void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) {
|
||||||
// setup a new QThread to generate the keypair on, in case it takes a while
|
|
||||||
QThread* generateThread = new QThread(this);
|
if (thread() != QThread::currentThread()) {
|
||||||
generateThread->setObjectName("Account Manager Generator Thread");
|
QMetaObject::invokeMethod(this, "generateNewKeypair", Q_ARG(bool, isUserKeypair), Q_ARG(QUuid, domainID));
|
||||||
|
return;
|
||||||
// setup a keypair generator
|
}
|
||||||
RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator();
|
|
||||||
|
if (!isUserKeypair && domainID.isNull()) {
|
||||||
connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair);
|
qCWarning(networking) << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair.";
|
||||||
connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair);
|
return;
|
||||||
connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair,
|
}
|
||||||
this, &AccountManager::handleKeypairGenerationError);
|
|
||||||
connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit);
|
// make sure we don't already have an outbound keypair generation request
|
||||||
connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater);
|
if (!_isWaitingForKeypairResponse) {
|
||||||
|
_isWaitingForKeypairResponse = true;
|
||||||
keypairGenerator->moveToThread(generateThread);
|
|
||||||
|
// clear the current private key
|
||||||
qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA key-pair.";
|
qDebug() << "Clearing current private key in DataServerAccountInfo";
|
||||||
generateThread->start();
|
_accountInfo.setPrivateKey(QByteArray());
|
||||||
|
|
||||||
|
// setup a new QThread to generate the keypair on, in case it takes a while
|
||||||
|
QThread* generateThread = new QThread(this);
|
||||||
|
generateThread->setObjectName("Account Manager Generator Thread");
|
||||||
|
|
||||||
|
// setup a keypair generator
|
||||||
|
RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator;
|
||||||
|
|
||||||
|
if (!isUserKeypair) {
|
||||||
|
keypairGenerator->setDomainID(domainID);
|
||||||
|
_accountInfo.setDomainID(domainID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start keypair generation when the thread starts
|
||||||
|
connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair);
|
||||||
|
|
||||||
|
// handle success or failure of keypair generation
|
||||||
|
connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair);
|
||||||
|
connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair,
|
||||||
|
this, &AccountManager::handleKeypairGenerationError);
|
||||||
|
|
||||||
|
connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit);
|
||||||
|
connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater);
|
||||||
|
|
||||||
|
keypairGenerator->moveToThread(generateThread);
|
||||||
|
|
||||||
|
qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA keypair.";
|
||||||
|
generateThread->start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey) {
|
void AccountManager::processGeneratedKeypair() {
|
||||||
|
|
||||||
qCDebug(networking) << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key.";
|
qCDebug(networking) << "Generated 2048-bit RSA keypair. Uploading public key now.";
|
||||||
|
|
||||||
// set the private key on our data-server account info
|
RSAKeypairGenerator* keypairGenerator = qobject_cast<RSAKeypairGenerator*>(sender());
|
||||||
_accountInfo.setPrivateKey(privateKey);
|
|
||||||
persistAccountToSettings();
|
if (keypairGenerator) {
|
||||||
|
// hold the private key to later set our metaverse API account info if upload succeeds
|
||||||
// upload the public key so data-web has an up-to-date key
|
_pendingPrivateKey = keypairGenerator->getPrivateKey();
|
||||||
const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key";
|
|
||||||
|
// upload the public key so data-web has an up-to-date key
|
||||||
// setup a multipart upload to send up the public key
|
const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key";
|
||||||
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key";
|
||||||
|
|
||||||
QHttpPart keyPart;
|
QString uploadPath;
|
||||||
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
if (keypairGenerator->getDomainID().isNull()) {
|
||||||
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
uploadPath = USER_PUBLIC_KEY_UPDATE_PATH;
|
||||||
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
} else {
|
||||||
keyPart.setBody(publicKey);
|
uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID()));
|
||||||
|
}
|
||||||
requestMultiPart->append(keyPart);
|
|
||||||
|
// setup a multipart upload to send up the public key
|
||||||
sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation,
|
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||||
JSONCallbackParameters(), QByteArray(), requestMultiPart);
|
|
||||||
|
QHttpPart keyPart;
|
||||||
// get rid of the keypair generator now that we don't need it anymore
|
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||||
sender()->deleteLater();
|
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
||||||
|
keyPart.setBody(keypairGenerator->getPublicKey());
|
||||||
|
|
||||||
|
requestMultiPart->append(keyPart);
|
||||||
|
|
||||||
|
// setup callback parameters so we know once the keypair upload has succeeded or failed
|
||||||
|
JSONCallbackParameters callbackParameters;
|
||||||
|
callbackParameters.jsonCallbackReceiver = this;
|
||||||
|
callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded";
|
||||||
|
callbackParameters.errorCallbackReceiver = this;
|
||||||
|
callbackParameters.errorCallbackMethod = "publicKeyUploadFailed";
|
||||||
|
|
||||||
|
sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation,
|
||||||
|
callbackParameters, QByteArray(), requestMultiPart);
|
||||||
|
|
||||||
|
keypairGenerator->deleteLater();
|
||||||
|
} else {
|
||||||
|
qCWarning(networking) << "Expected processGeneratedKeypair to be called by a live RSAKeypairGenerator"
|
||||||
|
<< "but the casted sender is NULL. Will not process generated keypair.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) {
|
||||||
|
qDebug() << "Uploaded public key to Metaverse API. RSA keypair generation is completed.";
|
||||||
|
|
||||||
|
// public key upload complete - store the matching private key and persist the account to settings
|
||||||
|
_accountInfo.setPrivateKey(_pendingPrivateKey);
|
||||||
|
_pendingPrivateKey.clear();
|
||||||
|
persistAccountToFile();
|
||||||
|
|
||||||
|
// clear our waiting state
|
||||||
|
_isWaitingForKeypairResponse = false;
|
||||||
|
|
||||||
|
emit newKeypair();
|
||||||
|
|
||||||
|
// delete the reply object now that we are done with it
|
||||||
|
reply.deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccountManager::publicKeyUploadFailed(QNetworkReply& reply) {
|
||||||
|
// the public key upload has failed
|
||||||
|
qWarning() << "Public key upload failed from AccountManager" << reply.errorString();
|
||||||
|
|
||||||
|
// we aren't waiting for a response any longer
|
||||||
|
_isWaitingForKeypairResponse = false;
|
||||||
|
|
||||||
|
// clear our pending private key
|
||||||
|
_pendingPrivateKey.clear();
|
||||||
|
|
||||||
|
// delete the reply object now that we are done with it
|
||||||
|
reply.deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountManager::handleKeypairGenerationError() {
|
void AccountManager::handleKeypairGenerationError() {
|
||||||
// for now there isn't anything we do with this except get the worker thread to clean up
|
qCritical() << "Error generating keypair - this is likely to cause authentication issues.";
|
||||||
|
|
||||||
|
// reset our waiting state for keypair response
|
||||||
|
_isWaitingForKeypairResponse = false;
|
||||||
|
|
||||||
sender()->deleteLater();
|
sender()->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,12 +62,12 @@ public:
|
||||||
QHttpMultiPart* dataMultiPart = NULL,
|
QHttpMultiPart* dataMultiPart = NULL,
|
||||||
const QVariantMap& propertyMap = QVariantMap());
|
const QVariantMap& propertyMap = QVariantMap());
|
||||||
|
|
||||||
|
void setIsAgent(bool isAgent) { _isAgent = isAgent; }
|
||||||
|
|
||||||
const QUrl& getAuthURL() const { return _authURL; }
|
const QUrl& getAuthURL() const { return _authURL; }
|
||||||
void setAuthURL(const QUrl& authURL);
|
void setAuthURL(const QUrl& authURL);
|
||||||
bool hasAuthEndpoint() { return !_authURL.isEmpty(); }
|
bool hasAuthEndpoint() { return !_authURL.isEmpty(); }
|
||||||
|
|
||||||
void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; }
|
|
||||||
|
|
||||||
bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
|
bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
|
||||||
bool hasValidAccessToken();
|
bool hasValidAccessToken();
|
||||||
Q_INVOKABLE bool checkAndSignalForAccessToken();
|
Q_INVOKABLE bool checkAndSignalForAccessToken();
|
||||||
|
@ -87,7 +87,9 @@ public slots:
|
||||||
void logout();
|
void logout();
|
||||||
void updateBalance();
|
void updateBalance();
|
||||||
void accountInfoBalanceChanged(qint64 newBalance);
|
void accountInfoBalanceChanged(qint64 newBalance);
|
||||||
void generateNewKeypair();
|
void generateNewUserKeypair() { generateNewKeypair(); }
|
||||||
|
void generateNewDomainKeypair(const QUuid& domainID) { generateNewKeypair(false, domainID); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void authRequired();
|
void authRequired();
|
||||||
void authEndpointChanged();
|
void authEndpointChanged();
|
||||||
|
@ -97,25 +99,36 @@ signals:
|
||||||
void loginFailed();
|
void loginFailed();
|
||||||
void logoutComplete();
|
void logoutComplete();
|
||||||
void balanceChanged(qint64 newBalance);
|
void balanceChanged(qint64 newBalance);
|
||||||
|
void newKeypair();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void processReply();
|
void processReply();
|
||||||
void handleKeypairGenerationError();
|
void handleKeypairGenerationError();
|
||||||
void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
|
void processGeneratedKeypair();
|
||||||
|
void publicKeyUploadSucceeded(QNetworkReply& reply);
|
||||||
|
void publicKeyUploadFailed(QNetworkReply& reply);
|
||||||
|
void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid());
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AccountManager();
|
AccountManager();
|
||||||
AccountManager(AccountManager const& other); // not implemented
|
AccountManager(AccountManager const& other) = delete;
|
||||||
void operator=(AccountManager const& other); // not implemented
|
void operator=(AccountManager const& other) = delete;
|
||||||
|
|
||||||
void persistAccountToSettings();
|
void persistAccountToFile();
|
||||||
|
void removeAccountFromFile();
|
||||||
|
|
||||||
void passSuccessToCallback(QNetworkReply* reply);
|
void passSuccessToCallback(QNetworkReply* reply);
|
||||||
void passErrorToCallback(QNetworkReply* reply);
|
void passErrorToCallback(QNetworkReply* reply);
|
||||||
|
|
||||||
QUrl _authURL;
|
QUrl _authURL;
|
||||||
|
|
||||||
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
|
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
|
||||||
|
|
||||||
DataServerAccountInfo _accountInfo;
|
DataServerAccountInfo _accountInfo;
|
||||||
bool _shouldPersistToSettingsFile;
|
bool _isAgent { false };
|
||||||
|
|
||||||
|
bool _isWaitingForKeypairResponse { false };
|
||||||
|
QByteArray _pendingPrivateKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AccountManager_h
|
#endif // hifi_AccountManager_h
|
||||||
|
|
|
@ -25,19 +25,6 @@
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DataServerAccountInfo::DataServerAccountInfo() :
|
|
||||||
_accessToken(),
|
|
||||||
_username(),
|
|
||||||
_xmppPassword(),
|
|
||||||
_discourseApiKey(),
|
|
||||||
_walletID(),
|
|
||||||
_balance(0),
|
|
||||||
_hasBalance(false),
|
|
||||||
_privateKey()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
|
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
|
||||||
_accessToken = otherInfo._accessToken;
|
_accessToken = otherInfo._accessToken;
|
||||||
_username = otherInfo._username;
|
_username = otherInfo._username;
|
||||||
|
@ -47,6 +34,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
|
||||||
_balance = otherInfo._balance;
|
_balance = otherInfo._balance;
|
||||||
_hasBalance = otherInfo._hasBalance;
|
_hasBalance = otherInfo._hasBalance;
|
||||||
_privateKey = otherInfo._privateKey;
|
_privateKey = otherInfo._privateKey;
|
||||||
|
_domainID = otherInfo._domainID;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
||||||
|
@ -66,6 +54,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
||||||
swap(_balance, otherInfo._balance);
|
swap(_balance, otherInfo._balance);
|
||||||
swap(_hasBalance, otherInfo._hasBalance);
|
swap(_hasBalance, otherInfo._hasBalance);
|
||||||
swap(_privateKey, otherInfo._privateKey);
|
swap(_privateKey, otherInfo._privateKey);
|
||||||
|
swap(_domainID, otherInfo._domainID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
||||||
|
@ -128,59 +117,62 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) {
|
QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) {
|
||||||
|
auto lowercaseUsername = _username.toLower().toUtf8();
|
||||||
if (!_privateKey.isEmpty()) {
|
auto plaintext = lowercaseUsername.append(connectionToken.toRfc4122());
|
||||||
const char* privateKeyData = _privateKey.constData();
|
|
||||||
RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL,
|
|
||||||
reinterpret_cast<const unsigned char**>(&privateKeyData),
|
|
||||||
_privateKey.size());
|
|
||||||
if (rsaPrivateKey) {
|
|
||||||
QByteArray lowercaseUsername = _username.toLower().toUtf8();
|
|
||||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
|
||||||
QCryptographicHash::Sha256);
|
|
||||||
|
|
||||||
QByteArray usernameSignature(RSA_size(rsaPrivateKey), 0);
|
|
||||||
unsigned int usernameSignatureSize = 0;
|
|
||||||
|
|
||||||
int encryptReturn = RSA_sign(NID_sha256,
|
|
||||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
|
||||||
usernameWithToken.size(),
|
|
||||||
reinterpret_cast<unsigned char*>(usernameSignature.data()),
|
|
||||||
&usernameSignatureSize,
|
|
||||||
rsaPrivateKey);
|
|
||||||
|
|
||||||
// free the private key RSA struct now that we are done with it
|
|
||||||
RSA_free(rsaPrivateKey);
|
|
||||||
|
|
||||||
if (encryptReturn == -1) {
|
auto signature = signPlaintext(plaintext);
|
||||||
qCDebug(networking) << "Error encrypting username signature.";
|
if (!signature.isEmpty()) {
|
||||||
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
|
qDebug(networking) << "Returning username" << _username
|
||||||
} else {
|
<< "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken);
|
||||||
qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken);
|
} else {
|
||||||
return usernameSignature;
|
qCDebug(networking) << "Error signing username with connection token";
|
||||||
}
|
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
|
||||||
|
}
|
||||||
} else {
|
|
||||||
qCDebug(networking) << "Could not create RSA struct from QByteArray private key.";
|
return signature;
|
||||||
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QByteArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) {
|
QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) {
|
||||||
_privateKey = privateKey;
|
if (!_privateKey.isEmpty()) {
|
||||||
|
const char* privateKeyData = _privateKey.constData();
|
||||||
|
RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL,
|
||||||
|
reinterpret_cast<const unsigned char**>(&privateKeyData),
|
||||||
|
_privateKey.size());
|
||||||
|
if (rsaPrivateKey) {
|
||||||
|
QByteArray signature(RSA_size(rsaPrivateKey), 0);
|
||||||
|
unsigned int signatureBytes = 0;
|
||||||
|
|
||||||
|
QByteArray hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);
|
||||||
|
|
||||||
|
int encryptReturn = RSA_sign(NID_sha256,
|
||||||
|
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
|
||||||
|
hashedPlaintext.size(),
|
||||||
|
reinterpret_cast<unsigned char*>(signature.data()),
|
||||||
|
&signatureBytes,
|
||||||
|
rsaPrivateKey);
|
||||||
|
|
||||||
|
// free the private key RSA struct now that we are done with it
|
||||||
|
RSA_free(rsaPrivateKey);
|
||||||
|
|
||||||
|
if (encryptReturn != -1) {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCDebug(networking) << "Could not create RSA struct from QByteArray private key.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
||||||
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
|
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
|
||||||
<< info._walletID << info._privateKey;
|
<< info._walletID << info._privateKey << info._domainID;
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
||||||
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
|
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
|
||||||
>> info._walletID >> info._privateKey;
|
>> info._walletID >> info._privateKey >> info._domainID;
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f;
|
||||||
class DataServerAccountInfo : public QObject {
|
class DataServerAccountInfo : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
DataServerAccountInfo();
|
DataServerAccountInfo() {};
|
||||||
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
||||||
DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo);
|
DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo);
|
||||||
|
|
||||||
|
@ -42,10 +42,6 @@ public:
|
||||||
|
|
||||||
const QUuid& getWalletID() const { return _walletID; }
|
const QUuid& getWalletID() const { return _walletID; }
|
||||||
void setWalletID(const QUuid& walletID);
|
void setWalletID(const QUuid& walletID);
|
||||||
|
|
||||||
QByteArray getUsernameSignature(const QUuid& connectionToken);
|
|
||||||
bool hasPrivateKey() const { return !_privateKey.isEmpty(); }
|
|
||||||
void setPrivateKey(const QByteArray& privateKey);
|
|
||||||
|
|
||||||
qint64 getBalance() const { return _balance; }
|
qint64 getBalance() const { return _balance; }
|
||||||
float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; }
|
float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; }
|
||||||
|
@ -54,6 +50,15 @@ public:
|
||||||
void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; }
|
void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; }
|
||||||
Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply);
|
Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply);
|
||||||
|
|
||||||
|
QByteArray getUsernameSignature(const QUuid& connectionToken);
|
||||||
|
bool hasPrivateKey() const { return !_privateKey.isEmpty(); }
|
||||||
|
void setPrivateKey(const QByteArray& privateKey) { _privateKey = privateKey; }
|
||||||
|
|
||||||
|
QByteArray signPlaintext(const QByteArray& plaintext);
|
||||||
|
|
||||||
|
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
|
||||||
|
const QUuid& getDomainID() const { return _domainID; }
|
||||||
|
|
||||||
bool hasProfile() const;
|
bool hasProfile() const;
|
||||||
|
|
||||||
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
||||||
|
@ -70,8 +75,9 @@ private:
|
||||||
QString _xmppPassword;
|
QString _xmppPassword;
|
||||||
QString _discourseApiKey;
|
QString _discourseApiKey;
|
||||||
QUuid _walletID;
|
QUuid _walletID;
|
||||||
qint64 _balance;
|
qint64 _balance { 0 };
|
||||||
bool _hasBalance;
|
bool _hasBalance { false };
|
||||||
|
QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain
|
||||||
QByteArray _privateKey;
|
QByteArray _privateKey;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -92,7 +92,9 @@ void DomainHandler::softReset() {
|
||||||
disconnect();
|
disconnect();
|
||||||
|
|
||||||
clearSettings();
|
clearSettings();
|
||||||
|
|
||||||
|
_connectionDenialsSinceKeypairRegen = 0;
|
||||||
|
|
||||||
// cancel the failure timeout for any pending requests for settings
|
// cancel the failure timeout for any pending requests for settings
|
||||||
QMetaObject::invokeMethod(&_settingsTimer, "stop");
|
QMetaObject::invokeMethod(&_settingsTimer, "stop");
|
||||||
}
|
}
|
||||||
|
@ -106,6 +108,9 @@ void DomainHandler::hardReset() {
|
||||||
_hostname = QString();
|
_hostname = QString();
|
||||||
_sockAddr.clear();
|
_sockAddr.clear();
|
||||||
|
|
||||||
|
_hasCheckedForAccessToken = false;
|
||||||
|
_domainConnectionRefusals.clear();
|
||||||
|
|
||||||
// clear any pending path we may have wanted to ask the previous DS about
|
// clear any pending path we may have wanted to ask the previous DS about
|
||||||
_pendingPath.clear();
|
_pendingPath.clear();
|
||||||
}
|
}
|
||||||
|
@ -347,3 +352,35 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> mes
|
||||||
emit icePeerSocketsReceived();
|
emit icePeerSocketsReceived();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
|
// Read deny reason from packet
|
||||||
|
quint16 reasonSize;
|
||||||
|
message->readPrimitive(&reasonSize);
|
||||||
|
QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize));
|
||||||
|
|
||||||
|
// output to the log so the user knows they got a denied connection request
|
||||||
|
// and check and signal for an access token so that we can make sure they are logged in
|
||||||
|
qCWarning(networking) << "The domain-server denied a connection request: " << reason;
|
||||||
|
qCWarning(networking) << "Make sure you are logged in.";
|
||||||
|
|
||||||
|
if (!_domainConnectionRefusals.contains(reason)) {
|
||||||
|
_domainConnectionRefusals.append(reason);
|
||||||
|
emit domainConnectionRefused(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& accountManager = AccountManager::getInstance();
|
||||||
|
|
||||||
|
if (!_hasCheckedForAccessToken) {
|
||||||
|
accountManager.checkAndSignalForAccessToken();
|
||||||
|
_hasCheckedForAccessToken = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
||||||
|
|
||||||
|
// force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts
|
||||||
|
if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) {
|
||||||
|
accountManager.generateNewUserKeypair();
|
||||||
|
_connectionDenialsSinceKeypairRegen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -92,6 +92,7 @@ public slots:
|
||||||
void processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message);
|
void processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
void processDTLSRequirementPacket(QSharedPointer<ReceivedMessage> dtlsRequirementPacket);
|
void processDTLSRequirementPacket(QSharedPointer<ReceivedMessage> dtlsRequirementPacket);
|
||||||
void processICEResponsePacket(QSharedPointer<ReceivedMessage> icePacket);
|
void processICEResponsePacket(QSharedPointer<ReceivedMessage> icePacket);
|
||||||
|
void processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void completedHostnameLookup(const QHostInfo& hostInfo);
|
void completedHostnameLookup(const QHostInfo& hostInfo);
|
||||||
|
@ -113,6 +114,8 @@ signals:
|
||||||
void settingsReceived(const QJsonObject& domainSettingsObject);
|
void settingsReceived(const QJsonObject& domainSettingsObject);
|
||||||
void settingsReceiveFail();
|
void settingsReceiveFail();
|
||||||
|
|
||||||
|
void domainConnectionRefused(QString reason);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void sendDisconnectPacket();
|
void sendDisconnectPacket();
|
||||||
void hardReset();
|
void hardReset();
|
||||||
|
@ -130,6 +133,10 @@ private:
|
||||||
QJsonObject _settingsObject;
|
QJsonObject _settingsObject;
|
||||||
QString _pendingPath;
|
QString _pendingPath;
|
||||||
QTimer _settingsTimer;
|
QTimer _settingsTimer;
|
||||||
|
|
||||||
|
QStringList _domainConnectionRefusals;
|
||||||
|
bool _hasCheckedForAccessToken { false };
|
||||||
|
int _connectionDenialsSinceKeypairRegen { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_DomainHandler_h
|
#endif // hifi_DomainHandler_h
|
||||||
|
|
|
@ -902,10 +902,6 @@ void LimitedNodeList::updateLocalSockAddr() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr) {
|
|
||||||
sendPacketToIceServer(PacketType::ICEServerHeartbeat, iceServerSockAddr, _sessionUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID,
|
void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID,
|
||||||
const QUuid& peerID) {
|
const QUuid& peerID) {
|
||||||
sendPacketToIceServer(PacketType::ICEServerQuery, iceServerSockAddr, clientID, peerID);
|
sendPacketToIceServer(PacketType::ICEServerQuery, iceServerSockAddr, clientID, peerID);
|
||||||
|
|
|
@ -143,6 +143,7 @@ public:
|
||||||
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
||||||
|
|
||||||
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
|
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
|
||||||
|
const HifiSockAddr& getPublicSockAddr() const { return _publicSockAddr; }
|
||||||
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
|
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
|
||||||
|
|
||||||
void processKillNode(ReceivedMessage& message);
|
void processKillNode(ReceivedMessage& message);
|
||||||
|
@ -161,7 +162,6 @@ public:
|
||||||
std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
|
std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
|
||||||
std::unique_ptr<NLPacket> constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID);
|
std::unique_ptr<NLPacket> constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID);
|
||||||
|
|
||||||
void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr);
|
|
||||||
void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID);
|
void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID);
|
||||||
|
|
||||||
SharedNodePointer findNodeWithAddr(const HifiSockAddr& addr);
|
SharedNodePointer findNodeWithAddr(const HifiSockAddr& addr);
|
||||||
|
|
|
@ -80,11 +80,16 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
||||||
// send a ping punch immediately
|
// send a ping punch immediately
|
||||||
connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer);
|
connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer);
|
||||||
|
|
||||||
|
auto &accountManager = AccountManager::getInstance();
|
||||||
|
|
||||||
|
// assume that we may need to send a new DS check in anytime a new keypair is generated
|
||||||
|
connect(&accountManager, &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn);
|
||||||
|
|
||||||
// clear out NodeList when login is finished
|
// clear out NodeList when login is finished
|
||||||
connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset);
|
connect(&accountManager, &AccountManager::loginComplete , this, &NodeList::reset);
|
||||||
|
|
||||||
// clear our NodeList when logout is requested
|
// clear our NodeList when logout is requested
|
||||||
connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset);
|
connect(&accountManager, &AccountManager::logoutComplete , this, &NodeList::reset);
|
||||||
|
|
||||||
// anytime we get a new node we will want to attempt to punch to it
|
// anytime we get a new node we will want to attempt to punch to it
|
||||||
connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch);
|
connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch);
|
||||||
|
@ -105,6 +110,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
||||||
packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket");
|
packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket");
|
||||||
packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode");
|
packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode");
|
||||||
packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket");
|
packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket");
|
||||||
|
packetReceiver.registerListener(PacketType::DomainConnectionDenied, &_domainHandler, "processDomainServerConnectionDeniedPacket");
|
||||||
packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList");
|
packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList");
|
||||||
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket");
|
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket");
|
||||||
packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket");
|
packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket");
|
||||||
|
@ -265,6 +271,26 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if we're missing a keypair we need to verify ourselves with the domain-server
|
||||||
|
auto& accountManager = AccountManager::getInstance();
|
||||||
|
const QUuid& connectionToken = _domainHandler.getConnectionToken();
|
||||||
|
|
||||||
|
// we assume that we're on the same box as the DS if it has the same local address and
|
||||||
|
// it didn't present us with a connection token to use for username signature
|
||||||
|
bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|
||||||
|
|| (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull());
|
||||||
|
|
||||||
|
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain;
|
||||||
|
|
||||||
|
if (requiresUsernameSignature && !accountManager.getAccountInfo().hasPrivateKey()) {
|
||||||
|
qWarning() << "A keypair is required to present a username signature to the domain-server"
|
||||||
|
<< "but no keypair is present. Waiting for keypair generation to complete.";
|
||||||
|
accountManager.generateNewUserKeypair();
|
||||||
|
|
||||||
|
// don't send the check in packet - wait for the keypair first
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto domainPacket = NLPacket::create(domainPacketType);
|
auto domainPacket = NLPacket::create(domainPacketType);
|
||||||
|
|
||||||
QDataStream packetStream(domainPacket.get());
|
QDataStream packetStream(domainPacket.get());
|
||||||
|
@ -289,23 +315,15 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
|
|
||||||
// pack our data to send to the domain-server
|
// pack our data to send to the domain-server
|
||||||
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
||||||
|
|
||||||
// if this is a connect request, and we can present a username signature, send it along
|
if (!_domainHandler.isConnected()) {
|
||||||
if (!_domainHandler.isConnected() ) {
|
DataServerAccountInfo& accountInfo = accountManager.getAccountInfo();
|
||||||
|
|
||||||
DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo();
|
|
||||||
packetStream << accountInfo.getUsername();
|
packetStream << accountInfo.getUsername();
|
||||||
|
|
||||||
// get connection token from the domain-server
|
// if this is a connect request, and we can present a username signature, send it along
|
||||||
const QUuid& connectionToken = _domainHandler.getConnectionToken();
|
if (requiresUsernameSignature && accountManager.getAccountInfo().hasPrivateKey()) {
|
||||||
|
const QByteArray& usernameSignature = accountManager.getAccountInfo().getUsernameSignature(connectionToken);
|
||||||
if (!connectionToken.isNull()) {
|
packetStream << usernameSignature;
|
||||||
|
|
||||||
const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature(connectionToken);
|
|
||||||
|
|
||||||
if (!usernameSignature.isEmpty()) {
|
|
||||||
packetStream << usernameSignature;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,12 +85,12 @@ void RSAKeypairGenerator::generateKeypair() {
|
||||||
// we can cleanup the RSA struct before we continue on
|
// we can cleanup the RSA struct before we continue on
|
||||||
RSA_free(keyPair);
|
RSA_free(keyPair);
|
||||||
|
|
||||||
QByteArray publicKeyArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
|
_publicKey = QByteArray { reinterpret_cast<char*>(publicKeyDER), publicKeyLength };
|
||||||
QByteArray privateKeyArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
|
_privateKey = QByteArray { reinterpret_cast<char*>(privateKeyDER), privateKeyLength };
|
||||||
|
|
||||||
// cleanup the publicKeyDER and publicKeyDER data
|
// cleanup the publicKeyDER and publicKeyDER data
|
||||||
OPENSSL_free(publicKeyDER);
|
OPENSSL_free(publicKeyDER);
|
||||||
OPENSSL_free(privateKeyDER);
|
OPENSSL_free(privateKeyDER);
|
||||||
|
|
||||||
emit generatedKeypair(publicKeyArray, privateKeyArray);
|
emit generatedKeypair();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,17 +12,31 @@
|
||||||
#ifndef hifi_RSAKeypairGenerator_h
|
#ifndef hifi_RSAKeypairGenerator_h
|
||||||
#define hifi_RSAKeypairGenerator_h
|
#define hifi_RSAKeypairGenerator_h
|
||||||
|
|
||||||
#include <qobject.h>
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QUuid>
|
||||||
|
|
||||||
class RSAKeypairGenerator : public QObject {
|
class RSAKeypairGenerator : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
RSAKeypairGenerator(QObject* parent = 0);
|
RSAKeypairGenerator(QObject* parent = 0);
|
||||||
|
|
||||||
|
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
|
||||||
|
const QUuid& getDomainID() const { return _domainID; }
|
||||||
|
|
||||||
|
const QByteArray& getPublicKey() const { return _publicKey; }
|
||||||
|
const QByteArray& getPrivateKey() const { return _privateKey; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void generateKeypair();
|
void generateKeypair();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorGeneratingKeypair();
|
void errorGeneratingKeypair();
|
||||||
void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
|
void generatedKeypair();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QUuid _domainID;
|
||||||
|
QByteArray _publicKey;
|
||||||
|
QByteArray _privateKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_RSAKeypairGenerator_h
|
#endif // hifi_RSAKeypairGenerator_h
|
||||||
|
|
|
@ -30,7 +30,7 @@ const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
|
||||||
<< PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken
|
<< PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken
|
||||||
<< PacketType::DomainSettingsRequest << PacketType::DomainSettings
|
<< PacketType::DomainSettingsRequest << PacketType::DomainSettings
|
||||||
<< PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat
|
<< PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat
|
||||||
<< PacketType::ICEPing << PacketType::ICEPingReply
|
<< PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied
|
||||||
<< PacketType::AssignmentClientStatus << PacketType::StopNode
|
<< PacketType::AssignmentClientStatus << PacketType::StopNode
|
||||||
<< PacketType::DomainServerRemovedNode;
|
<< PacketType::DomainServerRemovedNode;
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::AvatarData:
|
case PacketType::AvatarData:
|
||||||
case PacketType::BulkAvatarData:
|
case PacketType::BulkAvatarData:
|
||||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
|
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
|
||||||
|
case PacketType::ICEServerHeartbeat:
|
||||||
|
return 18; // ICE Server Heartbeat signing
|
||||||
default:
|
default:
|
||||||
return 17;
|
return 17;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,8 @@ public:
|
||||||
DomainServerRemovedNode,
|
DomainServerRemovedNode,
|
||||||
MessagesData,
|
MessagesData,
|
||||||
MessagesSubscribe,
|
MessagesSubscribe,
|
||||||
MessagesUnsubscribe
|
MessagesUnsubscribe,
|
||||||
|
ICEServerHeartbeatDenied
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue