From af181e00b120d0d6e58e44df67bed3ac243092ea Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 19 Feb 2016 14:34:19 -0800 Subject: [PATCH 01/28] allow for creation of domain-server keypair in AccountManager --- domain-server/src/DomainServer.cpp | 2 ++ libraries/networking/src/AccountManager.cpp | 21 +++++++++++++------ libraries/networking/src/AccountManager.h | 8 ++++++- .../networking/src/RSAKeypairGenerator.h | 13 ++++++++++-- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9e13c8e6fa..663b596486 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -526,6 +526,8 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { // we need this DS to know what our public IP is - start trying to figure that out now limitedNodeList->startSTUNPublicSocketUpdate(); + // to send ICE heartbeats we'd better have a private key locally with an uploaded public key + if (!_iceHeartbeatTimer) { // setup a timer to heartbeat with the ice-server every so often _iceHeartbeatTimer = new QTimer { this }; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 4ded2216d0..dc58624534 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -82,7 +82,7 @@ AccountManager::AccountManager() : 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); + connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewUserKeypair); } const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash"; @@ -482,23 +482,32 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) { qCDebug(networking) << "AccountManager requestProfileError - " << error; } -void AccountManager::generateNewKeypair() { +void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) { + if (!isUserKeypair && domainID.isNull()) { + qWarning() << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair."; + return; + } + // 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); + } + connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair); 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 key-pair."; generateThread->start(); } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 719279b0cf..bce5fb512d 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -87,7 +87,9 @@ public slots: void logout(); void updateBalance(); void accountInfoBalanceChanged(qint64 newBalance); - void generateNewKeypair(); + void generateNewUserKeypair() { generateNewKeypair(); } + void generateNewDomainKeypair(const QUuid& domainID) { generateNewKeypair(false, domainID); } + signals: void authRequired(); void authEndpointChanged(); @@ -97,10 +99,12 @@ signals: void loginFailed(); void logoutComplete(); void balanceChanged(qint64 newBalance); + private slots: void processReply(); void handleKeypairGenerationError(); void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey); + private: AccountManager(); AccountManager(AccountManager const& other); // not implemented @@ -111,6 +115,8 @@ private: void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); + void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); + QUrl _authURL; QMap _pendingCallbackMap; diff --git a/libraries/networking/src/RSAKeypairGenerator.h b/libraries/networking/src/RSAKeypairGenerator.h index dd90313625..391065768f 100644 --- a/libraries/networking/src/RSAKeypairGenerator.h +++ b/libraries/networking/src/RSAKeypairGenerator.h @@ -12,17 +12,26 @@ #ifndef hifi_RSAKeypairGenerator_h #define hifi_RSAKeypairGenerator_h -#include +#include +#include class RSAKeypairGenerator : public QObject { Q_OBJECT public: RSAKeypairGenerator(QObject* parent = 0); + + void setDomainID(const QUuid& domainID) { _domainID = domainID; } + const QUuid& getDomainID() const { return _domainID; } + public slots: void generateKeypair(); + signals: void errorGeneratingKeypair(); void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey); + +private: + QUuid _domainID; }; -#endif // hifi_RSAKeypairGenerator_h \ No newline at end of file +#endif // hifi_RSAKeypairGenerator_h From 98b06112bd55bc56b63a9c2bdf2776b2ce116eb6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 19 Feb 2016 15:45:20 -0800 Subject: [PATCH 02/28] handle persistence of AccountManager info to sep file --- domain-server/src/DomainServer.cpp | 1 - libraries/networking/src/AccountManager.cpp | 159 ++++++++++++++------ libraries/networking/src/AccountManager.h | 11 +- 3 files changed, 123 insertions(+), 48 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 663b596486..01f958589f 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -198,7 +198,6 @@ bool DomainServer::optionallySetupOAuth() { } AccountManager& accountManager = AccountManager::getInstance(); - accountManager.disableSettingsFilePersistence(); accountManager.setAuthURL(_oauthProviderURL); _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index dc58624534..d00bd4a8bd 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -12,10 +12,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -60,13 +62,13 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co updateReciever(updateReceiver), updateSlot(updateSlot) { + } AccountManager::AccountManager() : _authURL(), _pendingCallbackMap(), - _accountInfo(), - _shouldPersistToSettingsFile(true) + _accountInfo() { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); @@ -80,9 +82,18 @@ AccountManager::AccountManager() : qRegisterMetaType("QHttpMultiPart*"); 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::generateNewUserKeypair); +} + +void AccountManager::setIsAgent(bool isAgent) { + if (_isAgent != isAgent) { + if (_isAgent) { + // any profile changes in account manager should generate a new keypair + connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewUserKeypair); + } else { + // disconnect the generation of a new keypair during profile changes + disconnect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewUserKeypair); + } + } } const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash"; @@ -93,16 +104,9 @@ void AccountManager::logout() { emit balanceChanged(0); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); - - if (_shouldPersistToSettingsFile) { - QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); - QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString; - Setting::Handle(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."; - } + + // remove this account from the account settings file + removeAccountFromSettings(); emit logoutComplete(); // the username has changed to blank @@ -124,35 +128,59 @@ void AccountManager::accountInfoBalanceChanged(qint64 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 with failure + success = false; + + return QVariantMap(); + } +} + void AccountManager::setAuthURL(const QUrl& authURL) { if (_authURL != authURL) { _authURL = authURL; 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 - Settings settings; - settings.beginGroup(ACCOUNTS_GROUP); + // check if there are existing access tokens to load from settings + bool loadedFile = false; + auto accountsMap = accountMapFromFile(loadedFile); + + if (loadedFile) { + // pull out the stored access token and store it in memory + _accountInfo = accountsMap[_authURL.toString()].value(); - foreach(const QString& key, settings.allKeys()) { - // take a key copy to perform the double slash replacement - QString keyCopy(key); - QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//")); - - if (keyURL == _authURL) { - // pull out the stored access token and store it in memory - _accountInfo = settings.value(key).value(); - qCDebug(networking) << "Found a data-server access token for" << qPrintable(keyURL.toString()); - - // profile info isn't guaranteed to be saved too - if (_accountInfo.hasProfile()) { - emit profileChanged(); - } else { - requestProfile(); - } - } + qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString()); + + // profile info isn't guaranteed to be saved too + if (_accountInfo.hasProfile()) { + emit profileChanged(); + } else { + requestProfile(); } + + } else { + qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded."; } // tell listeners that the auth endpoint has changed @@ -324,15 +352,60 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { } } -void AccountManager::persistAccountToSettings() { - if (_shouldPersistToSettingsFile) { - // store this access token into the local settings - QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); - QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString; - Setting::Handle(path).set(QVariant::fromValue(_accountInfo)); +bool writeAccountMapToFile(const QVariantMap& accountMap) { + // re-open the file and truncate it + QFile accountFile { accountFilePath() }; + if (accountFile.open(QIODevice::WriteOnly)) { + QDataStream writeStream(&accountFile); + + // 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::persistAccountToSettings() { + + 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::removeAccountFromSettings() { + 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() { if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) { @@ -369,6 +442,8 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) qCDebug(networking) << "Setting new account manager access token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2); _accountInfo.setAccessToken(newOAuthToken); + + persistAccountToSettings(); } void AccountManager::requestAccessToken(const QString& login, const QString& password) { diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index bce5fb512d..e7fac134c0 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -62,12 +62,12 @@ public: QHttpMultiPart* dataMultiPart = NULL, const QVariantMap& propertyMap = QVariantMap()); + void setIsAgent(bool isAgent); + const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); bool hasAuthEndpoint() { return !_authURL.isEmpty(); } - void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; } - bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } bool hasValidAccessToken(); Q_INVOKABLE bool checkAndSignalForAccessToken(); @@ -107,10 +107,11 @@ private slots: private: AccountManager(); - AccountManager(AccountManager const& other); // not implemented - void operator=(AccountManager const& other); // not implemented + AccountManager(AccountManager const& other) = delete; + void operator=(AccountManager const& other) = delete; void persistAccountToSettings(); + void removeAccountFromSettings(); void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); @@ -121,7 +122,7 @@ private: QMap _pendingCallbackMap; DataServerAccountInfo _accountInfo; - bool _shouldPersistToSettingsFile; + bool _isAgent { false }; }; #endif // hifi_AccountManager_h From f553becabeb0cd274699ad871fb38437e435c4fe Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 19 Feb 2016 16:04:45 -0800 Subject: [PATCH 03/28] data-server name tweak and isAgent behaviour change --- assignment-client/src/octree/OctreeServer.cpp | 4 ---- interface/src/Application.cpp | 1 + libraries/networking/src/AccountManager.cpp | 20 ++++++++++--------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 31cab68cdf..aedf451924 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -236,10 +236,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) : { _averageLoopTime.updateAverage(0); 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() { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c202331041..ee95c189e9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -590,6 +590,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : 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 + accountManager.setIsAgent(true); accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); UserActivityLogger::getInstance().launch(applicationVersion()); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index d00bd4a8bd..11b2b317a6 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -169,14 +169,16 @@ void AccountManager::setAuthURL(const QUrl& authURL) { if (loadedFile) { // pull out the stored access token and store it in memory _accountInfo = accountsMap[_authURL.toString()].value(); - + qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString()); - // profile info isn't guaranteed to be saved too - if (_accountInfo.hasProfile()) { - emit profileChanged(); - } else { - requestProfile(); + if (_isAgent) { + // profile info isn't guaranteed to be saved too + if (_accountInfo.hasProfile()) { + emit profileChanged(); + } else { + requestProfile(); + } } } else { @@ -327,7 +329,7 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { } else { 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()); } } @@ -345,7 +347,7 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { _pendingCallbackMap.remove(requestReply); } else { 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) << requestReply->readAll(); } @@ -591,7 +593,7 @@ void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const qCDebug(networking) << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key."; - // set the private key on our data-server account info + // set the private key on our metaverse API account info _accountInfo.setPrivateKey(privateKey); persistAccountToSettings(); From fa5f2571d70d25684cc44890673bf957289f50d1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 19 Feb 2016 16:31:31 -0800 Subject: [PATCH 04/28] cleanup migrations from previous account file system --- domain-server/src/DomainServer.cpp | 41 +--------------- domain-server/src/DomainServer.h | 2 - libraries/networking/src/AccountManager.cpp | 54 ++++++++++++++++----- 3 files changed, 43 insertions(+), 54 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 01f958589f..2d7f5ca0cd 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -96,7 +96,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : // make sure we hear about newly connected nodes from our gatekeeper 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 // and completed login or did not need to @@ -376,15 +376,6 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { 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"; bool DomainServer::resetAccountManagerAccessToken() { @@ -428,34 +419,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() { auto nodeList = DependencyManager::get(); @@ -466,7 +429,7 @@ void DomainServer::setupAutomaticNetworking() { setupICEHeartbeatForFullNetworking(); } - if (!didSetupAccountManagerWithAccessToken()) { + if (!resetAccountManagerAccessToken()) { qDebug() << "Cannot send heartbeat to data server without an access token."; qDebug() << "Add an access token to your config file or via the web interface."; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 326ca3e1a8..611385ecde 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -83,11 +83,9 @@ private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); - bool optionallySetupAssignmentPayment(); void optionallyGetTemporaryName(const QStringList& arguments); - bool didSetupAccountManagerWithAccessToken(); bool resetAccountManagerAccessToken(); void setupAutomaticNetworking(); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 11b2b317a6..db9a704404 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -86,6 +86,8 @@ AccountManager::AccountManager() : void AccountManager::setIsAgent(bool isAgent) { if (_isAgent != isAgent) { + _isAgent = isAgent; + if (_isAgent) { // any profile changes in account manager should generate a new keypair connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewUserKeypair); @@ -149,8 +151,9 @@ QVariantMap accountMapFromFile(bool& success) { return accountMap; } else { - // failed to open file, return empty QVariantMap with failure - success = false; + // 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(); } @@ -163,26 +166,51 @@ void AccountManager::setAuthURL(const QUrl& authURL) { qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); // check if there are existing access tokens to load from settings - bool loadedFile = false; - auto accountsMap = accountMapFromFile(loadedFile); + QFile accountsFile { accountFilePath() }; + bool loadedMap = false; + auto accountsMap = accountMapFromFile(loadedMap); - if (loadedFile) { + if (accountsFile.exists() && loadedMap) { // pull out the stored access token and store it in memory _accountInfo = accountsMap[_authURL.toString()].value(); 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 - if (_isAgent) { - // profile info isn't guaranteed to be saved too - if (_accountInfo.hasProfile()) { - emit profileChanged(); - } else { - requestProfile(); + // check if there are existing access tokens to load from settings + Settings settings; + settings.beginGroup(ACCOUNTS_GROUP); + + foreach(const QString& key, settings.allKeys()) { + // take a key copy to perform the double slash replacement + QString keyCopy(key); + QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//")); + + if (keyURL == _authURL) { + // pull out the stored access token and store it in memory + _accountInfo = settings.value(key).value(); + + qCDebug(networking) << "Migrated an access token for" << qPrintable(keyURL.toString()) + << "from previous settings file"; } } - } else { - qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded."; + 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 + persistAccountToSettings(); + } + } + + if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty()) { + // profile info isn't guaranteed to be saved too + if (_accountInfo.hasProfile()) { + emit profileChanged(); + } else { + requestProfile(); + } } // tell listeners that the auth endpoint has changed From 5de8601b4362b63a4c56c196a176a5f38d8e860c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 19 Feb 2016 16:41:56 -0800 Subject: [PATCH 05/28] simpler cleanup of result from RSAKeypairGenerator --- libraries/networking/src/AccountManager.cpp | 18 +++++++++++------- libraries/networking/src/AccountManager.h | 2 +- libraries/networking/src/RSAKeypairGenerator.h | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index db9a704404..009c87804a 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -598,16 +598,24 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI generateThread->setObjectName("Account Manager Generator Thread"); // setup a keypair generator - RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator(); + RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator { this }; if (!isUserKeypair) { keypairGenerator->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); + + // cleanup the keypair generator and the thread once the generation succeeds or fails + connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, keypairGenerator, &RSAKeypairGenerator::deleteLater); + connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, keypairGenerator, &RSAKeypairGenerator::deleteLater); + connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit); connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater); @@ -617,7 +625,7 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI generateThread->start(); } -void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey) { +void AccountManager::processGeneratedKeypair(QByteArray publicKey, QByteArray privateKey) { qCDebug(networking) << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key."; @@ -641,12 +649,8 @@ void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, JSONCallbackParameters(), QByteArray(), requestMultiPart); - - // get rid of the keypair generator now that we don't need it anymore - sender()->deleteLater(); } void AccountManager::handleKeypairGenerationError() { - // for now there isn't anything we do with this except get the worker thread to clean up - sender()->deleteLater(); + qCritical() << "Error generating keypair - this is likely to cause authentication issues."; } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index e7fac134c0..c88d6a8c74 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -103,7 +103,7 @@ signals: private slots: void processReply(); void handleKeypairGenerationError(); - void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey); + void processGeneratedKeypair(QByteArray publicKey, QByteArray privateKey); private: AccountManager(); diff --git a/libraries/networking/src/RSAKeypairGenerator.h b/libraries/networking/src/RSAKeypairGenerator.h index 391065768f..2267bdb91b 100644 --- a/libraries/networking/src/RSAKeypairGenerator.h +++ b/libraries/networking/src/RSAKeypairGenerator.h @@ -28,7 +28,7 @@ public slots: signals: void errorGeneratingKeypair(); - void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey); + void generatedKeypair(QByteArray publicKey, QByteArray privateKey); private: QUuid _domainID; From 6e4ecffb09f81c3b4c915d0d41b2f732a4c853ae Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 19 Feb 2016 16:48:58 -0800 Subject: [PATCH 06/28] expose generated public key and private key from generator --- libraries/networking/src/AccountManager.cpp | 59 +++++++++++-------- libraries/networking/src/AccountManager.h | 2 +- .../networking/src/RSAKeypairGenerator.cpp | 6 +- .../networking/src/RSAKeypairGenerator.h | 7 ++- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 009c87804a..0d0ef1c93c 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -612,10 +612,6 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, this, &AccountManager::handleKeypairGenerationError); - // cleanup the keypair generator and the thread once the generation succeeds or fails - connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, keypairGenerator, &RSAKeypairGenerator::deleteLater); - connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, keypairGenerator, &RSAKeypairGenerator::deleteLater); - connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit); connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater); @@ -625,32 +621,43 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI generateThread->start(); } -void AccountManager::processGeneratedKeypair(QByteArray publicKey, QByteArray privateKey) { +void AccountManager::processGeneratedKeypair() { qCDebug(networking) << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key."; - - // set the private key on our metaverse API account info - _accountInfo.setPrivateKey(privateKey); - persistAccountToSettings(); - - // upload the public key so data-web has an up-to-date key - const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; - - // setup a multipart upload to send up the public key - QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - - QHttpPart keyPart; - keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); - keyPart.setBody(publicKey); - - requestMultiPart->append(keyPart); - - sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QByteArray(), requestMultiPart); + + RSAKeypairGenerator* keypairGenerator = qobject_cast(sender()); + + if (keypairGenerator) { + // set the private key on our metaverse API account info + _accountInfo.setPrivateKey(keypairGenerator->getPrivateKey()); + persistAccountToSettings(); + + // upload the public key so data-web has an up-to-date key + const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; + + // setup a multipart upload to send up the public key + QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + QHttpPart keyPart; + keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); + keyPart.setBody(keypairGenerator->getPublicKey()); + + requestMultiPart->append(keyPart); + + sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), 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::handleKeypairGenerationError() { qCritical() << "Error generating keypair - this is likely to cause authentication issues."; + + sender()->deleteLater(); } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index c88d6a8c74..545624f789 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -103,7 +103,7 @@ signals: private slots: void processReply(); void handleKeypairGenerationError(); - void processGeneratedKeypair(QByteArray publicKey, QByteArray privateKey); + void processGeneratedKeypair(); private: AccountManager(); diff --git a/libraries/networking/src/RSAKeypairGenerator.cpp b/libraries/networking/src/RSAKeypairGenerator.cpp index 53b9b27cc6..a98cf74564 100644 --- a/libraries/networking/src/RSAKeypairGenerator.cpp +++ b/libraries/networking/src/RSAKeypairGenerator.cpp @@ -85,12 +85,12 @@ void RSAKeypairGenerator::generateKeypair() { // we can cleanup the RSA struct before we continue on RSA_free(keyPair); - QByteArray publicKeyArray(reinterpret_cast(publicKeyDER), publicKeyLength); - QByteArray privateKeyArray(reinterpret_cast(privateKeyDER), privateKeyLength); + _publicKey = QByteArray { reinterpret_cast(publicKeyDER), publicKeyLength }; + _privateKey = QByteArray { reinterpret_cast(privateKeyDER), privateKeyLength }; // cleanup the publicKeyDER and publicKeyDER data OPENSSL_free(publicKeyDER); OPENSSL_free(privateKeyDER); - emit generatedKeypair(publicKeyArray, privateKeyArray); + emit generatedKeypair(); } diff --git a/libraries/networking/src/RSAKeypairGenerator.h b/libraries/networking/src/RSAKeypairGenerator.h index 2267bdb91b..36f4a9550b 100644 --- a/libraries/networking/src/RSAKeypairGenerator.h +++ b/libraries/networking/src/RSAKeypairGenerator.h @@ -22,16 +22,21 @@ public: 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: void generateKeypair(); signals: void errorGeneratingKeypair(); - void generatedKeypair(QByteArray publicKey, QByteArray privateKey); + void generatedKeypair(); private: QUuid _domainID; + QByteArray _publicKey; + QByteArray _privateKey; }; #endif // hifi_RSAKeypairGenerator_h From 837c4c0810b22f998e2eb1ed5902073e5928b460 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 19 Feb 2016 16:55:12 -0800 Subject: [PATCH 07/28] handle upload of domain public key to metaverse API --- domain-server/src/DomainServer.cpp | 4 ++++ libraries/networking/src/AccountManager.cpp | 14 +++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2d7f5ca0cd..0f8301c96b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -489,6 +489,10 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { limitedNodeList->startSTUNPublicSocketUpdate(); // to send ICE heartbeats we'd better have a private key locally with an uploaded public key + auto& accountManager = AccountManager::getInstance(); + if (!accountManager.getAccountInfo().hasPrivateKey()) { + accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + } if (!_iceHeartbeatTimer) { // setup a timer to heartbeat with the ice-server every so often diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 0d0ef1c93c..e9b44b2f87 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -589,7 +589,7 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) { void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) { if (!isUserKeypair && domainID.isNull()) { - qWarning() << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair."; + qCWarning(networking) << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair."; return; } @@ -633,7 +633,15 @@ void AccountManager::processGeneratedKeypair() { persistAccountToSettings(); // upload the public key so data-web has an up-to-date key - const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; + const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; + const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key"; + + QString uploadPath; + if (keypairGenerator->getDomainID().isNull()) { + uploadPath = USER_PUBLIC_KEY_UPDATE_PATH; + } else { + uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID())); + } // setup a multipart upload to send up the public key QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); @@ -646,7 +654,7 @@ void AccountManager::processGeneratedKeypair() { requestMultiPart->append(keyPart); - sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, + sendRequest(uploadPath, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, JSONCallbackParameters(), QByteArray(), requestMultiPart); keypairGenerator->deleteLater(); From e30b2b70512de38eb21180e0b975776599a37fdd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 22 Feb 2016 10:28:32 -0800 Subject: [PATCH 08/28] generate new domain keypair on domain ID change --- domain-server/src/DomainServer.cpp | 6 ++++-- libraries/networking/src/DataServerAccountInfo.cpp | 4 ++-- libraries/networking/src/DataServerAccountInfo.h | 12 ++++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0f8301c96b..0f57370aa0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -349,7 +349,8 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // nodes will currently use this to add resources to data-web that relate to our domain const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); if (idValueVariant) { - nodeList->setSessionUUID(idValueVariant->toString()); + QUuid domainID { idValueVariant->toString() }; + nodeList->setSessionUUID(domainID); } connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); @@ -490,7 +491,8 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { // to send ICE heartbeats we'd better have a private key locally with an uploaded public key auto& accountManager = AccountManager::getInstance(); - if (!accountManager.getAccountInfo().hasPrivateKey()) { + auto domainID = accountManager.getAccountInfo().getDomainID(); + if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); } diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 5d633a8df1..ae6d7c4c9b 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -175,12 +175,12 @@ void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) { QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey - << info._walletID << info._privateKey; + << info._walletID << info._privateKey << info._domainID; return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey - >> info._walletID >> info._privateKey; + >> info._walletID >> info._privateKey >> info._domainID; return in; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 9b80de5422..8f6fb83f79 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -42,10 +42,6 @@ public: const QUuid& getWalletID() const { return _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; } float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; } @@ -54,6 +50,13 @@ public: void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; } Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply); + QByteArray getUsernameSignature(const QUuid& connectionToken); + bool hasPrivateKey() const { return !_privateKey.isEmpty(); } + void setPrivateKey(const QByteArray& privateKey); + + void setDomainID(const QUuid& domainID) { _domainID = domainID; } + const QUuid& getDomainID() const { return _domainID; } + bool hasProfile() const; void setProfileInfoFromJSON(const QJsonObject& jsonObject); @@ -72,6 +75,7 @@ private: QUuid _walletID; qint64 _balance; bool _hasBalance; + QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain QByteArray _privateKey; }; From 90e9089e85628a4b3e154ef16c02cc9d2015b979 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 22 Feb 2016 13:23:01 -0800 Subject: [PATCH 09/28] repairs for storage of domain ID in AM accounts file --- domain-server/src/DomainServer.cpp | 1 + libraries/networking/src/AccountManager.cpp | 18 ++-- .../networking/src/DataServerAccountInfo.cpp | 85 ++++++++----------- .../networking/src/DataServerAccountInfo.h | 8 +- 4 files changed, 48 insertions(+), 64 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0f57370aa0..f66a28f063 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -492,6 +492,7 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { // 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 (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index e9b44b2f87..fc1d8e9458 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -67,8 +67,7 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co AccountManager::AccountManager() : _authURL(), - _pendingCallbackMap(), - _accountInfo() + _pendingCallbackMap() { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); @@ -171,7 +170,7 @@ void AccountManager::setAuthURL(const QUrl& authURL) { auto accountsMap = accountMapFromFile(loadedMap); if (accountsFile.exists() && loadedMap) { - // pull out the stored access token and store it in memory + // pull out the stored account info and store it in memory _accountInfo = accountsMap[_authURL.toString()].value(); qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString()); @@ -462,10 +461,7 @@ bool AccountManager::checkAndSignalForAccessToken() { } void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) { - // clear our current DataServerAccountInfo - _accountInfo = DataServerAccountInfo(); - - // start the new account info with a new OAuthAccessToken + // replace the account info access token with a new OAuthAccessToken OAuthAccessToken newOAuthToken; newOAuthToken.token = accessToken; @@ -598,10 +594,12 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI generateThread->setObjectName("Account Manager Generator Thread"); // setup a keypair generator - RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator { this }; + RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator; if (!isUserKeypair) { keypairGenerator->setDomainID(domainID); + _accountInfo.setDomainID(domainID); + qDebug() << "The account info domain ID is now" << _accountInfo.getDomainID(); } // start keypair generation when the thread starts @@ -617,13 +615,13 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI keypairGenerator->moveToThread(generateThread); - qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA key-pair."; + qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA keypair."; generateThread->start(); } 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. Storing private key and uploading public key."; RSAKeypairGenerator* keypairGenerator = qobject_cast(sender()); diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index ae6d7c4c9b..20c7f778c9 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -25,19 +25,6 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif -DataServerAccountInfo::DataServerAccountInfo() : - _accessToken(), - _username(), - _xmppPassword(), - _discourseApiKey(), - _walletID(), - _balance(0), - _hasBalance(false), - _privateKey() -{ - -} - DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() { _accessToken = otherInfo._accessToken; _username = otherInfo._username; @@ -47,6 +34,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _balance = otherInfo._balance; _hasBalance = otherInfo._hasBalance; _privateKey = otherInfo._privateKey; + _domainID = otherInfo._domainID; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -66,6 +54,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_balance, otherInfo._balance); swap(_hasBalance, otherInfo._hasBalance); swap(_privateKey, otherInfo._privateKey); + swap(_domainID, otherInfo._domainID); } void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { @@ -129,53 +118,49 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) { - if (!_privateKey.isEmpty()) { - const char* privateKeyData = _privateKey.constData(); - RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL, - reinterpret_cast(&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(usernameWithToken.constData()), - usernameWithToken.size(), - reinterpret_cast(usernameSignature.data()), - &usernameSignatureSize, - rsaPrivateKey); - - // free the private key RSA struct now that we are done with it - RSA_free(rsaPrivateKey); + if (!_privateKey.isEmpty()) { + const char* privateKeyData = _privateKey.constData(); + RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL, + reinterpret_cast(&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(usernameWithToken.constData()), + usernameWithToken.size(), + reinterpret_cast(usernameSignature.data()), + &usernameSignatureSize, + rsaPrivateKey); + + // free the private key RSA struct now that we are done with it + RSA_free(rsaPrivateKey); - if (encryptReturn == -1) { - qCDebug(networking) << "Error encrypting username signature."; - qCDebug(networking) << "Will re-attempt on next domain-server check in."; - } else { - qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); - return usernameSignature; - } - - } else { - qCDebug(networking) << "Could not create RSA struct from QByteArray private key."; + if (encryptReturn == -1) { + qCDebug(networking) << "Error encrypting username signature."; qCDebug(networking) << "Will re-attempt on next domain-server check in."; + } else { + qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); + return usernameSignature; } + + } else { + qCDebug(networking) << "Could not create RSA struct from QByteArray private key."; + qCDebug(networking) << "Will re-attempt on next domain-server check in."; } + } return QByteArray(); } -void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) { - _privateKey = privateKey; - -} - QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey << info._walletID << info._privateKey << info._domainID; + return out; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 8f6fb83f79..483cd28ff2 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -23,7 +23,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f; class DataServerAccountInfo : public QObject { Q_OBJECT public: - DataServerAccountInfo(); + DataServerAccountInfo() {}; DataServerAccountInfo(const DataServerAccountInfo& otherInfo); DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo); @@ -52,7 +52,7 @@ public: QByteArray getUsernameSignature(const QUuid& connectionToken); bool hasPrivateKey() const { return !_privateKey.isEmpty(); } - void setPrivateKey(const QByteArray& privateKey); + void setPrivateKey(const QByteArray& privateKey) { _privateKey = privateKey; } void setDomainID(const QUuid& domainID) { _domainID = domainID; } const QUuid& getDomainID() const { return _domainID; } @@ -73,8 +73,8 @@ private: QString _xmppPassword; QString _discourseApiKey; QUuid _walletID; - qint64 _balance; - bool _hasBalance; + qint64 _balance { 0 }; + bool _hasBalance { false }; QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain QByteArray _privateKey; From d444d5dd18f0fdc9665931b45cb0eaca367a56d7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 22 Feb 2016 13:56:33 -0800 Subject: [PATCH 10/28] don't offer login reset from CrashHandler --- interface/src/CrashHandler.cpp | 36 +++++++--------------------------- interface/src/CrashHandler.h | 2 +- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index ce5facb580..f9cd7679ae 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -22,11 +22,8 @@ #include #include -#include "DataServerAccountInfo.h" #include "Menu.h" -Q_DECLARE_METATYPE(DataServerAccountInfo) - static const QString RUNNING_MARKER_FILENAME = "Interface.running"; void CrashHandler::checkForAndHandleCrash() { @@ -57,7 +54,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() { layout->addWidget(label); 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"); option3->setChecked(true); layout->addWidget(option1); @@ -79,7 +76,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() { return CrashHandler::DELETE_INTERFACE_INI; } 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) { - 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 return; } @@ -101,18 +98,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { const QString DISPLAY_NAME_KEY = "displayName"; const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; - const QString ACCOUNTS_GROUP = "accounts"; QString displayName; QUrl fullAvatarURL; QString fullAvatarModelName; QUrl address; - QMap accounts; - if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { - // Read login and avatar info - - qRegisterMetaType("DataServerAccountInfo"); - qRegisterMetaTypeStreamOperators("DataServerAccountInfo"); + if (action == CrashHandler::RETAIN_AVATAR_INFO) { + // Read avatar info // Location and orientation settings.beginGroup(ADDRESS_MANAGER_GROUP); @@ -125,13 +117,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); settings.endGroup(); - - // Accounts - settings.beginGroup(ACCOUNTS_GROUP); - foreach(const QString& key, settings.allKeys()) { - accounts.insert(key, settings.value(key).value()); - } - settings.endGroup(); } // Delete Interface.ini @@ -140,8 +125,8 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settingsFile.remove(); } - if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { - // Write login and avatar info + if (action == CrashHandler::RETAIN_AVATAR_INFO) { + // Write avatar info // Location and orientation 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_MODEL_NAME_KEY, fullAvatarModelName); settings.endGroup(); - - // Accounts - settings.beginGroup(ACCOUNTS_GROUP); - foreach(const QString& key, accounts.keys()) { - settings.setValue(key, QVariant::fromValue(accounts.value(key))); - } - settings.endGroup(); } } diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index fc754cf1ac..61361b6107 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -25,7 +25,7 @@ public: private: enum Action { DELETE_INTERFACE_INI, - RETAIN_LOGIN_AND_AVATAR_INFO, + RETAIN_AVATAR_INFO, DO_NOTHING }; From 11a1bc4488036c189ae5fc1b10f95b9b275ec60d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 22 Feb 2016 15:12:02 -0800 Subject: [PATCH 11/28] add signed heartbeat sending to domain-server --- domain-server/src/DomainServer.cpp | 52 +++++++++++++++++-- libraries/networking/src/AccountManager.cpp | 2 +- .../networking/src/DataServerAccountInfo.cpp | 48 +++++++++-------- .../networking/src/DataServerAccountInfo.h | 2 + libraries/networking/src/LimitedNodeList.cpp | 4 -- libraries/networking/src/LimitedNodeList.h | 2 +- .../networking/src/udt/PacketHeaders.cpp | 2 + 7 files changed, 81 insertions(+), 31 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f66a28f063..dec88f8647 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -42,7 +42,7 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923; -const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io"; +const QString ICE_SERVER_DEFAULT_HOSTNAME = "localhost"; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), @@ -1053,11 +1053,55 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { domainUpdateJSON.toUtf8()); } -// TODO: have data-web respond with ice-server hostname to use - void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { - DependencyManager::get()->sendHeartbeatToIceServer(_iceServerSocket); + static auto heartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); + + bool shouldRecreatePacket = false; + + auto limitedNodeList = DependencyManager::get(); + + if (heartbeatPacket->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 + heartbeatPacket->seek(0); + QDataStream heartbeatStream(heartbeatPacket.get()); + + QUuid senderUUID; + HifiSockAddr publicSocket, localSocket; + heartbeatStream >> senderUUID >> publicSocket >> localSocket; + + if (publicSocket != limitedNodeList->getPublicSockAddr() || localSocket != limitedNodeList->getLocalSockAddr()) { + shouldRecreatePacket = true; + } + } else { + shouldRecreatePacket = true; + } + + if (shouldRecreatePacket) { + // either we don't have a heartbeat packet yet or sockets have changed and we need to make a new one + + // reset the position in the packet before writing + heartbeatPacket->reset(); + + // write our plaintext data to the packet + QDataStream heartbeatDataStream(heartbeatPacket.get()); + heartbeatDataStream << limitedNodeList->getSessionUUID() + << limitedNodeList->getPublicSockAddr() << limitedNodeList->getLocalSockAddr(); + + // setup a QByteArray that points to the plaintext data + auto plaintext = QByteArray::fromRawData(heartbeatPacket->getPayload(), heartbeatPacket->getPayloadSize()); + + // generate a signature for the plaintext data in the packet + auto& accountManager = AccountManager::getInstance(); + auto signature = accountManager.getAccountInfo().signPlaintext(plaintext); + + // pack the signature with the data + heartbeatDataStream << plaintext; + } + + // send the heartbeat packet to the ice server now + limitedNodeList->sendUnreliablePacket(*heartbeatPacket, _iceServerSocket); } } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index fc1d8e9458..a24b0b471c 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -33,7 +33,7 @@ #include "AccountManager.h" #include "NetworkLogging.h" -const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; +const bool VERBOSE_HTTP_REQUEST_DEBUGGING = true; AccountManager& AccountManager::getInstance(bool forceReset) { static std::unique_ptr sharedInstance(new AccountManager()); diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 20c7f778c9..706da1ce72 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -117,41 +117,47 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject } QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) { - + QByteArray lowercaseUsername = _username.toLower().toUtf8(); + QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), + QCryptographicHash::Sha256); + + auto signature = signPlaintext(usernameWithToken); + if (!signature.isEmpty()) { + qDebug(networking) << "Returning username" << _username + << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); + } else { + qCDebug(networking) << "Error signing username with connection token"; + qCDebug(networking) << "Will re-attempt on next domain-server check in."; + } + + return signature; +} + +QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) { if (!_privateKey.isEmpty()) { const char* privateKeyData = _privateKey.constData(); RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL, reinterpret_cast(&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; - + QByteArray signature(RSA_size(rsaPrivateKey), 0); + unsigned int signatureBytes = 0; + int encryptReturn = RSA_sign(NID_sha256, - reinterpret_cast(usernameWithToken.constData()), - usernameWithToken.size(), - reinterpret_cast(usernameSignature.data()), - &usernameSignatureSize, + reinterpret_cast(plaintext.constData()), + plaintext.size(), + reinterpret_cast(signature.data()), + &signatureBytes, rsaPrivateKey); - + // free the private key RSA struct now that we are done with it RSA_free(rsaPrivateKey); - if (encryptReturn == -1) { - qCDebug(networking) << "Error encrypting username signature."; - qCDebug(networking) << "Will re-attempt on next domain-server check in."; - } else { - qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); - return usernameSignature; + if (encryptReturn != -1) { + return signature; } - } else { qCDebug(networking) << "Could not create RSA struct from QByteArray private key."; - qCDebug(networking) << "Will re-attempt on next domain-server check in."; } } return QByteArray(); diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 483cd28ff2..6223bc008e 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -54,6 +54,8 @@ public: 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; } diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index a3707d19ba..f236f9d596 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -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, const QUuid& peerID) { sendPacketToIceServer(PacketType::ICEServerQuery, iceServerSockAddr, clientID, peerID); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index fcad23da8f..de110c7e7f 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -143,6 +143,7 @@ public: bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; } + const HifiSockAddr& getPublicSockAddr() const { return _publicSockAddr; } const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; } void processKillNode(ReceivedMessage& message); @@ -161,7 +162,6 @@ public: std::unique_ptr constructICEPingPacket(PingType_t pingType, const QUuid& iceID); std::unique_ptr constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID); - void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr); void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID); SharedNodePointer findNodeWithAddr(const HifiSockAddr& addr); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 4e6418279d..8a4c2ec34f 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -45,6 +45,8 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); + case PacketType::ICEServerHeartbeat: + return 18; // ICE Server Heartbeat signing default: return 17; } From 9f9ef8764d8341149b15f96e0765a8c6febbab2d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 22 Feb 2016 15:32:59 -0800 Subject: [PATCH 12/28] add a note about the QDataStream black box --- domain-server/src/DomainServer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index dec88f8647..5e5a843fae 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1055,6 +1055,8 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { + // 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. static auto heartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); bool shouldRecreatePacket = false; From ddf4f029d969f433eaa111703ad18e604c22bdad Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 22 Feb 2016 16:14:11 -0800 Subject: [PATCH 13/28] request and store domain public key in ice-server memory --- ice-server/src/IceServer.cpp | 125 +++++++++++++++++++++++++++++------ ice-server/src/IceServer.h | 14 +++- 2 files changed, 116 insertions(+), 23 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 2baa7a13a7..31f576fa03 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -9,14 +9,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "IceServer.h" + +#include +#include +#include +#include #include +#include #include #include -#include "IceServer.h" - const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000; const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000; @@ -70,9 +74,10 @@ void IceServer::processPacket(std::unique_ptr packet) { if (nlPacket->getType() == PacketType::ICEServerHeartbeat) { SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket); - - // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now - peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr()); + if (peer) { + // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now + peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr()); + } } else if (nlPacket->getType() == PacketType::ICEServerQuery) { QDataStream heartbeatStream(nlPacket.get()); @@ -114,31 +119,107 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) { // pull the UUID, public and private sock addrs for this peer QUuid senderUUID; HifiSockAddr publicSocket, localSocket; + QByteArray signature; QDataStream heartbeatStream(&packet); - - heartbeatStream >> senderUUID; - heartbeatStream >> publicSocket >> localSocket; + heartbeatStream >> senderUUID >> publicSocket >> localSocket; - // make sure we have this sender in our peer hash - SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID); + auto signedPlaintext = QByteArray::fromRawData(packet.getPayload(), heartbeatStream.device()->pos()); + heartbeatStream >> signature; - if (!matchingPeer) { - // if we don't have this sender we need to create them now - matchingPeer = QSharedPointer::create(senderUUID, publicSocket, localSocket); - _activePeers.insert(senderUUID, matchingPeer); + // make sure this is a verified heartbeat before performing any more processing + if (isVerifiedHeartbeat(senderUUID, signedPlaintext, signature)) { + // make sure we have this sender in our peer hash + 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::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 { - // we already had the peer so just potentially update their sockets - matchingPeer->setPublicSocket(publicSocket); - matchingPeer->setLocalSocket(localSocket); + // not verified, return the empty peer object + return SharedNetworkPeer(); } +} - // update our last heard microstamp for this network peer to now - matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); +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()) { - return matchingPeer; + return true; + } else { + // we don't have a public key for this domain so we can't verify this heartbeat + // ask the metaverse API for the right public key and return false to indicate that this is not verified + requestDomainPublicKey(domainID); + + 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.emplace(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) { diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index f1c2c06b65..81234b2c3c 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -16,13 +16,15 @@ #include #include +#include + #include #include #include #include #include -typedef QHash NetworkPeerHash; +class QNetworkReply; class IceServer : public QCoreApplication, public HTTPRequestHandler { Q_OBJECT @@ -31,6 +33,7 @@ public: bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); private slots: void clearInactivePeers(); + void publicKeyReplyFinished(QNetworkReply* reply); private: bool packetVersionMatch(const udt::Packet& packet); void processPacket(std::unique_ptr packet); @@ -38,10 +41,19 @@ private: SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket); 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; udt::Socket _serverSocket; + + using NetworkPeerHash = QHash; NetworkPeerHash _activePeers; + HTTPManager _httpManager; + + using DomainPublicKeyHash = std::unordered_map; + DomainPublicKeyHash _domainPublicKeys; }; #endif // hifi_IceServer_h From d510ee0e47793a664af4fb0130f2cdd5e1e1ed9b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 22 Feb 2016 18:00:05 -0800 Subject: [PATCH 14/28] complete ice-server signature verification --- domain-server/src/DomainGatekeeper.cpp | 1 - domain-server/src/DomainServer.cpp | 2 +- ice-server/CMakeLists.txt | 14 ++++++ ice-server/src/IceServer.cpp | 44 ++++++++++++++++--- libraries/networking/src/AccountManager.cpp | 2 +- .../networking/src/DataServerAccountInfo.cpp | 13 +++--- 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 3e4ee7b758..f385f5c489 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -331,7 +331,6 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, QCryptographicHash::Sha256); if (rsaPublicKey) { - QByteArray decryptedArray(RSA_size(rsaPublicKey), 0); int decryptResult = RSA_verify(NID_sha256, reinterpret_cast(usernameWithToken.constData()), usernameWithToken.size(), diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 5e5a843fae..46aa9ab2ee 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1099,7 +1099,7 @@ void DomainServer::sendHeartbeatToIceServer() { auto signature = accountManager.getAccountInfo().signPlaintext(plaintext); // pack the signature with the data - heartbeatDataStream << plaintext; + heartbeatDataStream << signature; } // send the heartbeat packet to the ice server now diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt index cfec3c966c..e5bdffe2e2 100644 --- a/ice-server/CMakeLists.txt +++ b/ice-server/CMakeLists.txt @@ -6,3 +6,17 @@ setup_hifi_project(Network) # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared) 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}) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 31f576fa03..d0dad812aa 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -11,6 +11,9 @@ #include "IceServer.h" +#include +#include + #include #include #include @@ -159,14 +162,43 @@ bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& pla auto it = _domainPublicKeys.find(domainID); if (it != _domainPublicKeys.end()) { - return true; - } else { - // we don't have a public key for this domain so we can't verify this heartbeat - // ask the metaverse API for the right public key and return false to indicate that this is not verified - requestDomainPublicKey(domainID); + // attempt to verify the signature for this heartbeat + const unsigned char* publicKeyData = reinterpret_cast(it->second.constData()); - return false; + // 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(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(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"; + } } + + // we could not verify this heartbeat (missing public key, could not load public key, bad actor) + // ask the metaverse API for the right public key and return false to indicate that this is not verified + requestDomainPublicKey(domainID); + + return false; } void IceServer::requestDomainPublicKey(const QUuid& domainID) { diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index a24b0b471c..fc1d8e9458 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -33,7 +33,7 @@ #include "AccountManager.h" #include "NetworkLogging.h" -const bool VERBOSE_HTTP_REQUEST_DEBUGGING = true; +const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; AccountManager& AccountManager::getInstance(bool forceReset) { static std::unique_ptr sharedInstance(new AccountManager()); diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 706da1ce72..9455fb1b88 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -117,11 +117,10 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject } QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) { - QByteArray lowercaseUsername = _username.toLower().toUtf8(); - QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), - QCryptographicHash::Sha256); + auto lowercaseUsername = _username.toLower().toUtf8(); + auto plaintext = lowercaseUsername.append(connectionToken.toRfc4122()); - auto signature = signPlaintext(usernameWithToken); + auto signature = signPlaintext(plaintext); if (!signature.isEmpty()) { qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken); @@ -143,9 +142,11 @@ QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) { 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(plaintext.constData()), - plaintext.size(), + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), reinterpret_cast(signature.data()), &signatureBytes, rsaPrivateKey); From e2f02347dd17b1bdde83ff75adac42157e8fd1a5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 10:57:54 -0800 Subject: [PATCH 15/28] repairs to issues discovered during ID change --- domain-server/src/DomainServer.cpp | 14 ++++++++++++-- ice-server/src/IceServer.cpp | 3 +-- libraries/networking/src/AccountManager.cpp | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 46aa9ab2ee..de34802bb7 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1055,6 +1055,15 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { + + auto& accountManager = AccountManager::getInstance(); + if (!accountManager.getAccountInfo().hasPrivateKey()) { + qWarning() << "Cannot send an ice-server heartbeat without a private key for signature."; + qWarning() << "Please re-launch your domain-server to generate a new keypair."; + + 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. static auto heartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); @@ -1073,7 +1082,9 @@ void DomainServer::sendHeartbeatToIceServer() { HifiSockAddr publicSocket, localSocket; heartbeatStream >> senderUUID >> publicSocket >> localSocket; - if (publicSocket != limitedNodeList->getPublicSockAddr() || localSocket != limitedNodeList->getLocalSockAddr()) { + if (senderUUID != limitedNodeList->getSessionUUID() + || publicSocket != limitedNodeList->getPublicSockAddr() + || localSocket != limitedNodeList->getLocalSockAddr()) { shouldRecreatePacket = true; } } else { @@ -1095,7 +1106,6 @@ void DomainServer::sendHeartbeatToIceServer() { auto plaintext = QByteArray::fromRawData(heartbeatPacket->getPayload(), heartbeatPacket->getPayloadSize()); // generate a signature for the plaintext data in the packet - auto& accountManager = AccountManager::getInstance(); auto signature = accountManager.getAccountInfo().signPlaintext(plaintext); // pack the signature with the data diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index d0dad812aa..1de9a4bb58 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -237,8 +237,7 @@ void IceServer::publicKeyReplyFinished(QNetworkReply* reply) { if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) { auto dataObject = responseObject[DATA_KEY].toObject(); if (dataObject.contains(PUBLIC_KEY_KEY)) { - _domainPublicKeys.emplace(domainID, - QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8())); + _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; } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index fc1d8e9458..d9099aa925 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -589,6 +589,10 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI return; } + // clear the current private key + qDebug() << "Clearing current private key in DataServerAccountInfo"; + _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"); From bfdf74367ed9fc036faa617300f651130b4a73f5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 11:09:28 -0800 Subject: [PATCH 16/28] don't update the AM keypair until uploaded --- libraries/networking/src/AccountManager.cpp | 112 +++++++++++++------- libraries/networking/src/AccountManager.h | 10 +- 2 files changed, 82 insertions(+), 40 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index d9099aa925..8c43f22cad 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -107,7 +107,7 @@ void AccountManager::logout() { connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); // remove this account from the account settings file - removeAccountFromSettings(); + removeAccountFromFile(); emit logoutComplete(); // the username has changed to blank @@ -199,7 +199,7 @@ void AccountManager::setAuthURL(const QUrl& authURL) { qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded."; } else { // persist the migrated settings to file - persistAccountToSettings(); + persistAccountToFile(); } } @@ -399,7 +399,7 @@ bool writeAccountMapToFile(const QVariantMap& accountMap) { } } -void AccountManager::persistAccountToSettings() { +void AccountManager::persistAccountToFile() { qCDebug(networking) << "Persisting AccountManager accounts to" << accountFilePath(); @@ -419,7 +419,7 @@ void AccountManager::persistAccountToSettings() { qCWarning(networking) << "Could not load accounts file - unable to persist account information to file."; } -void AccountManager::removeAccountFromSettings() { +void AccountManager::removeAccountFromFile() { bool wasLoaded = false; auto accountMap = accountMapFromFile(wasLoaded); @@ -469,7 +469,7 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) _accountInfo.setAccessToken(newOAuthToken); - persistAccountToSettings(); + persistAccountToFile(); } void AccountManager::requestAccessToken(const QString& login, const QString& password) { @@ -524,7 +524,7 @@ void AccountManager::requestAccessTokenFinished() { emit loginComplete(rootURL); - persistAccountToSettings(); + persistAccountToFile(); requestProfile(); } @@ -570,7 +570,7 @@ void AccountManager::requestProfileFinished() { emit usernameChanged(_accountInfo.getUsername()); // store the whole profile into the local settings - persistAccountToSettings(); + persistAccountToFile(); } else { // TODO: error handling @@ -589,38 +589,43 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI return; } - // clear the current private key - qDebug() << "Clearing current private key in DataServerAccountInfo"; - _accountInfo.setPrivateKey(QByteArray()); + // make sure we don't already have an outbound keypair generation request + if (!_isWaitingForKeypairResponse) { + _isWaitingForKeypairResponse = true; - // 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"); + // clear the current private key + qDebug() << "Clearing current private key in DataServerAccountInfo"; + _accountInfo.setPrivateKey(QByteArray()); - // setup a keypair generator - RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator; + // 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"); - if (!isUserKeypair) { - keypairGenerator->setDomainID(domainID); - _accountInfo.setDomainID(domainID); - qDebug() << "The account info domain ID is now" << _accountInfo.getDomainID(); + // setup a keypair generator + RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator; + + if (!isUserKeypair) { + keypairGenerator->setDomainID(domainID); + _accountInfo.setDomainID(domainID); + qDebug() << "The account info domain ID is now" << _accountInfo.getDomainID(); + } + + // 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(); } - - // 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() { @@ -631,8 +636,7 @@ void AccountManager::processGeneratedKeypair() { if (keypairGenerator) { // set the private key on our metaverse API account info - _accountInfo.setPrivateKey(keypairGenerator->getPrivateKey()); - persistAccountToSettings(); + _pendingPrivateKey = keypairGenerator->getPrivateKey(); // upload the public key so data-web has an up-to-date key const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; @@ -656,8 +660,15 @@ void AccountManager::processGeneratedKeypair() { 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::Required, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QByteArray(), requestMultiPart); + callbackParameters, QByteArray(), requestMultiPart); keypairGenerator->deleteLater(); } else { @@ -666,8 +677,33 @@ void AccountManager::processGeneratedKeypair() { } } +void AccountManager::publicKeyUploadSuceeded() { + // 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(); +} + +void AccountManager::publicKeyUploadFailed() { + // the public key upload has failed + + // we aren't waiting for a response any longer + _isWaitingForKeypairResponse = false; + + // clear our pending private key + _pendingPrivateKey.clear(); +} + void AccountManager::handleKeypairGenerationError() { qCritical() << "Error generating keypair - this is likely to cause authentication issues."; + // reset our waiting state for keypair response + _isWaitingForKeypairResponse = false; + sender()->deleteLater(); } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 545624f789..3c1bf38fbe 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -99,19 +99,22 @@ signals: void loginFailed(); void logoutComplete(); void balanceChanged(qint64 newBalance); + void newKeypair(); private slots: void processReply(); void handleKeypairGenerationError(); void processGeneratedKeypair(); + void publicKeyUploadSuceeded(); + void publicKeyUploadFailed(); private: AccountManager(); AccountManager(AccountManager const& other) = delete; void operator=(AccountManager const& other) = delete; - void persistAccountToSettings(); - void removeAccountFromSettings(); + void persistAccountToFile(); + void removeAccountFromFile(); void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); @@ -123,6 +126,9 @@ private: DataServerAccountInfo _accountInfo; bool _isAgent { false }; + + bool _isWaitingForKeypairResponse { false }; + QByteArray _pendingPrivateKey; }; #endif // hifi_AccountManager_h From 83e8c248bd009d22ce2b8f233dac48a76824f48e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 11:20:21 -0800 Subject: [PATCH 17/28] repairs to failure case for keypair generation --- domain-server/src/DomainServer.cpp | 14 ++++++++++---- libraries/networking/src/AccountManager.cpp | 20 ++++++++++++++++---- libraries/networking/src/AccountManager.h | 4 ++-- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index de34802bb7..408877072d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1057,9 +1057,17 @@ void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { auto& accountManager = AccountManager::getInstance(); + auto limitedNodeList = DependencyManager::get(); + if (!accountManager.getAccountInfo().hasPrivateKey()) { qWarning() << "Cannot send an ice-server heartbeat without a private key for signature."; - qWarning() << "Please re-launch your domain-server to generate a new keypair."; + 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; } @@ -1068,9 +1076,7 @@ void DomainServer::sendHeartbeatToIceServer() { // QDataStream and the possibility of IPv6 address for the sockets. static auto heartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); - bool shouldRecreatePacket = false; - - auto limitedNodeList = DependencyManager::get(); + bool shouldRecreatePacket = false if (heartbeatPacket->getPayloadSize() > 0) { // if either of our sockets have changed we need to re-sign the heartbeat diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 8c43f22cad..69a97825cd 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -359,6 +359,8 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback."; qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll()); } + + requestReply->deleteLater(); } } @@ -378,6 +380,8 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString(); qCDebug(networking) << requestReply->readAll(); } + + requestReply->deleteLater(); } } @@ -607,7 +611,6 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI if (!isUserKeypair) { keypairGenerator->setDomainID(domainID); _accountInfo.setDomainID(domainID); - qDebug() << "The account info domain ID is now" << _accountInfo.getDomainID(); } // start keypair generation when the thread starts @@ -630,7 +633,7 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI void AccountManager::processGeneratedKeypair() { - qCDebug(networking) << "Generated 2048-bit RSA keypair. Storing private key and uploading public key."; + qCDebug(networking) << "Generated 2048-bit RSA keypair. Uploading public key now."; RSAKeypairGenerator* keypairGenerator = qobject_cast(sender()); @@ -677,7 +680,9 @@ void AccountManager::processGeneratedKeypair() { } } -void AccountManager::publicKeyUploadSuceeded() { +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(); @@ -687,16 +692,23 @@ void AccountManager::publicKeyUploadSuceeded() { _isWaitingForKeypairResponse = false; emit newKeypair(); + + // delete the reply object now that we are done with it + reply.deleteLater(); } -void AccountManager::publicKeyUploadFailed() { +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() { diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 3c1bf38fbe..24a6500d13 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -105,8 +105,8 @@ private slots: void processReply(); void handleKeypairGenerationError(); void processGeneratedKeypair(); - void publicKeyUploadSuceeded(); - void publicKeyUploadFailed(); + void publicKeyUploadSucceeded(QNetworkReply& reply); + void publicKeyUploadFailed(QNetworkReply& reply); private: AccountManager(); From 4b4ea25ed6a8cf6ecc3ce9c8ac6b04bf5839890d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 11:24:26 -0800 Subject: [PATCH 18/28] immediately send ice heartbeat once keypair is ready --- domain-server/src/DomainServer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 408877072d..445c5ae436 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1063,6 +1063,9 @@ void DomainServer::sendHeartbeatToIceServer() { qWarning() << "Cannot send an ice-server heartbeat without a private key for signature."; qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat."; + // hookup this slot to the signal from account manager that tells us when keypair is available + connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::sendHeartbeatToIceServer); + if (!limitedNodeList->getSessionUUID().isNull()) { accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); } else { @@ -1076,7 +1079,7 @@ void DomainServer::sendHeartbeatToIceServer() { // QDataStream and the possibility of IPv6 address for the sockets. static auto heartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); - bool shouldRecreatePacket = false + bool shouldRecreatePacket = false; if (heartbeatPacket->getPayloadSize() > 0) { // if either of our sockets have changed we need to re-sign the heartbeat From 540d1615cd8f402f5602852d2c1dc1253a2d7654 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 12:05:12 -0800 Subject: [PATCH 19/28] re-attempt keypair generation for domain check in if missing --- libraries/networking/src/AccountManager.cpp | 6 +++ libraries/networking/src/AccountManager.h | 3 +- libraries/networking/src/NodeList.cpp | 46 ++++++++++++++------- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 69a97825cd..877f858e97 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -588,6 +588,12 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) { } void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) { + + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "generateNewKeypair", Q_ARG(bool, isUserKeypair), Q_ARG(QUuid, domainID)); + return; + } + if (!isUserKeypair && domainID.isNull()) { qCWarning(networking) << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair."; return; diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 24a6500d13..fc8157e8d8 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -107,6 +107,7 @@ private slots: void processGeneratedKeypair(); void publicKeyUploadSucceeded(QNetworkReply& reply); void publicKeyUploadFailed(QNetworkReply& reply); + void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); private: AccountManager(); @@ -119,8 +120,6 @@ private: void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); - void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); - QUrl _authURL; QMap _pendingCallbackMap; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 677a1ad1e6..37ff79c454 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -265,6 +265,28 @@ 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(); + + connect(&accountManager, &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); + + // don't send the check in packet - wait for the keypair first + return; + } + auto domainPacket = NLPacket::create(domainPacketType); QDataStream packetStream(domainPacket.get()); @@ -289,23 +311,15 @@ void NodeList::sendDomainServerCheckIn() { // pack our data to send to the domain-server 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() ) { - - DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo(); + + if (!_domainHandler.isConnected()) { + DataServerAccountInfo& accountInfo = accountManager.getAccountInfo(); packetStream << accountInfo.getUsername(); - - // get connection token from the domain-server - const QUuid& connectionToken = _domainHandler.getConnectionToken(); - - if (!connectionToken.isNull()) { - - const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature(connectionToken); - - if (!usernameSignature.isEmpty()) { - packetStream << usernameSignature; - } + + // if this is a connect request, and we can present a username signature, send it along + if (requiresUsernameSignature && accountManager.getAccountInfo().hasPrivateKey()) { + const QByteArray& usernameSignature = accountManager.getAccountInfo().getUsernameSignature(connectionToken); + packetStream << usernameSignature; } } From 6cab831ea039e3f050cf8023c0856b6bc809caf8 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 14:48:31 -0800 Subject: [PATCH 20/28] re-gen the interface keypair while being denied from domain --- interface/src/Application.cpp | 23 ------------ interface/src/Application.h | 4 --- .../scripting/WindowScriptingInterface.cpp | 2 +- libraries/networking/src/AccountManager.cpp | 24 ++----------- libraries/networking/src/AccountManager.h | 3 +- libraries/networking/src/DomainHandler.cpp | 35 +++++++++++++++++++ libraries/networking/src/DomainHandler.h | 7 ++++ libraries/networking/src/NodeList.cpp | 11 +++--- 8 files changed, 55 insertions(+), 54 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ee95c189e9..aeec71980a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -926,9 +926,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : SpacemouseManager::getInstance().init(); #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 auto entityScriptingInterface = DependencyManager::get(); connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, @@ -3961,30 +3958,10 @@ void Application::clearDomainOctreeDetails() { void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); clearDomainOctreeDetails(); - _domainConnectionRefusals.clear(); // disable physics until we have enough information about our new location to not cause craziness. _physicsEnabled = false; } -void Application::handleDomainConnectionDeniedPacket(QSharedPointer 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) { AccountManager& accountManager = AccountManager::getInstance(); const QUuid& domainID = DependencyManager::get()->getDomainHandler().getUUID(); diff --git a/interface/src/Application.h b/interface/src/Application.h index d5b677302a..3ac70ea6f7 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -223,7 +223,6 @@ signals: void svoImportRequested(const QString& url); void checkBackgroundDownloads(); - void domainConnectionRefused(const QString& reason); void fullAvatarURLChanged(const QString& newValue, const QString& modelName); @@ -291,8 +290,6 @@ private slots: void activeChanged(Qt::ApplicationState state); void domainSettingsReceived(const QJsonObject& domainSettingsObject); - void handleDomainConnectionDeniedPacket(QSharedPointer message); - void notifyPacketVersionMismatch(); void loadSettings(); @@ -472,7 +469,6 @@ private: typedef bool (Application::* AcceptURLMethod)(const QString &); static const QHash _acceptedExtensions; - QList _domainConnectionRefusals; glm::uvec2 _renderResolution; int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 3e8d0d0360..1726708efb 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -25,7 +25,7 @@ WindowScriptingInterface::WindowScriptingInterface() { const DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); 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) { static const QMetaMethod svoImportRequestedSignal = diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 877f858e97..e8fc5d743b 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -83,20 +83,6 @@ AccountManager::AccountManager() : connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); } -void AccountManager::setIsAgent(bool isAgent) { - if (_isAgent != isAgent) { - _isAgent = isAgent; - - if (_isAgent) { - // any profile changes in account manager should generate a new keypair - connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewUserKeypair); - } else { - // disconnect the generation of a new keypair during profile changes - disconnect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewUserKeypair); - } - } -} - const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash"; void AccountManager::logout() { @@ -203,13 +189,9 @@ void AccountManager::setAuthURL(const QUrl& authURL) { } } - if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty()) { - // profile info isn't guaranteed to be saved too - if (_accountInfo.hasProfile()) { - emit profileChanged(); - } else { - requestProfile(); - } + 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 diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index fc8157e8d8..0ebefafbed 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -62,7 +62,7 @@ public: QHttpMultiPart* dataMultiPart = NULL, const QVariantMap& propertyMap = QVariantMap()); - void setIsAgent(bool isAgent); + void setIsAgent(bool isAgent) { _isAgent = isAgent; } const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); @@ -121,6 +121,7 @@ private: void passErrorToCallback(QNetworkReply* reply); QUrl _authURL; + QMap _pendingCallbackMap; DataServerAccountInfo _accountInfo; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index db775983e1..ff0bbf31e0 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -92,6 +92,8 @@ void DomainHandler::softReset() { disconnect(); clearSettings(); + + _connectionDenialsSinceKeypairRegen = 0; // cancel the failure timeout for any pending requests for settings QMetaObject::invokeMethod(&_settingsTimer, "stop"); @@ -105,6 +107,7 @@ void DomainHandler::hardReset() { _iceServerSockAddr = HifiSockAddr(); _hostname = QString(); _sockAddr.clear(); + _hasCheckedForAccessToken = false; // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear(); @@ -347,3 +350,35 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes emit icePeerSocketsReceived(); } } + +void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer 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; + } +} diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index f60ac2fbe6..b245305a93 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -92,6 +92,7 @@ public slots: void processICEPingReplyPacket(QSharedPointer message); void processDTLSRequirementPacket(QSharedPointer dtlsRequirementPacket); void processICEResponsePacket(QSharedPointer icePacket); + void processDomainServerConnectionDeniedPacket(QSharedPointer message); private slots: void completedHostnameLookup(const QHostInfo& hostInfo); @@ -113,6 +114,8 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); + void domainConnectionRefused(QString reason); + private: void sendDisconnectPacket(); void hardReset(); @@ -130,6 +133,10 @@ private: QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; + + QStringList _domainConnectionRefusals; + bool _hasCheckedForAccessToken { false }; + int _connectionDenialsSinceKeypairRegen { 0 }; }; #endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 37ff79c454..23451897b0 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -80,11 +80,16 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // send a ping punch immediately 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 - connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset); + connect(&accountManager, &AccountManager::loginComplete , this, &NodeList::reset); // 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 connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); @@ -281,8 +286,6 @@ void NodeList::sendDomainServerCheckIn() { << "but no keypair is present. Waiting for keypair generation to complete."; accountManager.generateNewUserKeypair(); - connect(&accountManager, &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); - // don't send the check in packet - wait for the keypair first return; } From 42582e5d0f7578b6d44fbe79c3a2dcbe0f23f460 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 14:53:35 -0800 Subject: [PATCH 21/28] remove old voxel cost model from application --- interface/src/Application.cpp | 28 ---------------------------- interface/src/Application.h | 1 - 2 files changed, 29 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index aeec71980a..3bbaba3920 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -553,7 +553,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); 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 const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000; @@ -4504,33 +4503,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() { auto scriptEngines = DependencyManager::get(); QString fileNameString = OffscreenUi::getOpenFileName( diff --git a/interface/src/Application.h b/interface/src/Application.h index 3ac70ea6f7..81a74d0355 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -289,7 +289,6 @@ private slots: void activeChanged(Qt::ApplicationState state); - void domainSettingsReceived(const QJsonObject& domainSettingsObject); void notifyPacketVersionMismatch(); void loadSettings(); From bb561e98d0692d42e7b99f17038d8a9588aca850 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 15:00:42 -0800 Subject: [PATCH 22/28] actually process the connection denied packets --- libraries/networking/src/DomainHandler.cpp | 6 ++++-- libraries/networking/src/NodeList.cpp | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index ff0bbf31e0..34ca722537 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -94,7 +94,7 @@ void DomainHandler::softReset() { clearSettings(); _connectionDenialsSinceKeypairRegen = 0; - + // cancel the failure timeout for any pending requests for settings QMetaObject::invokeMethod(&_settingsTimer, "stop"); } @@ -107,7 +107,9 @@ void DomainHandler::hardReset() { _iceServerSockAddr = HifiSockAddr(); _hostname = QString(); _sockAddr.clear(); + _hasCheckedForAccessToken = false; + _domainConnectionRefusals.clear(); // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear(); @@ -369,7 +371,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer Date: Tue, 23 Feb 2016 15:12:17 -0800 Subject: [PATCH 23/28] send hearbeat denied packet back from ice-server --- ice-server/src/IceServer.cpp | 5 ++++- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 1de9a4bb58..a452ad8296 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -52,7 +52,6 @@ IceServer::IceServer(int argc, char* argv[]) : QTimer* inactivePeerTimer = new QTimer(this); connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers); inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS); - } bool IceServer::packetVersionMatch(const udt::Packet& packet) { @@ -80,6 +79,10 @@ void IceServer::processPacket(std::unique_ptr packet) { if (peer) { // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now 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, packet->getSenderSockAddr()); } } else if (nlPacket->getType() == PacketType::ICEServerQuery) { QDataStream heartbeatStream(nlPacket.get()); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 8a4c2ec34f..01bccdfdba 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken << PacketType::DomainSettingsRequest << PacketType::DomainSettings << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat - << PacketType::ICEPing << PacketType::ICEPingReply + << PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode << PacketType::DomainServerRemovedNode; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index af3fd49710..ca04b9cae4 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -90,7 +90,8 @@ public: DomainServerRemovedNode, MessagesData, MessagesSubscribe, - MessagesUnsubscribe + MessagesUnsubscribe, + ICEServerHeartbeatDenied }; }; From 96ed19100bf5e2101de7753cc4b4b4368cdae727 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 15:26:13 -0800 Subject: [PATCH 24/28] use ice-server heartbeat denial to trigger keypair re-gen --- domain-server/src/DomainServer.cpp | 18 ++++++++++++++++++ domain-server/src/DomainServer.h | 1 + ice-server/src/IceServer.cpp | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 445c5ae436..672205b8a3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -372,6 +372,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); + packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); @@ -2006,3 +2007,20 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer 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(); + AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + + // reset our number of heartbeat denials + numHeartbeatDenials = 0; + } +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 611385ecde..a95b7e57a3 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -61,6 +61,7 @@ public slots: void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); void processPathQueryPacket(QSharedPointer packet); void processNodeDisconnectRequestPacket(QSharedPointer message); + void processICEServerHeartbeatDenialPacket(QSharedPointer message); private slots: void aboutToQuit(); diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index a452ad8296..f38923b873 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -82,7 +82,7 @@ void IceServer::processPacket(std::unique_ptr packet) { } 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, packet->getSenderSockAddr()); + _serverSocket.writePacket(*deniedPacket, nlPacket->getSenderSockAddr()); } } else if (nlPacket->getType() == PacketType::ICEServerQuery) { QDataStream heartbeatStream(nlPacket.get()); From 3a800350c9868641040f9b4fdc39fc8c998dd01e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 15:42:28 -0800 Subject: [PATCH 25/28] re-set the ice heartbeat packet if keypair changes --- domain-server/src/DomainServer.cpp | 35 ++++++++++++++++++++---------- domain-server/src/DomainServer.h | 3 +++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 672205b8a3..0fe5fd3300 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -498,6 +498,9 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { 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) { // setup a timer to heartbeat with the ice-server every so often _iceHeartbeatTimer = new QTimer { this }; @@ -1064,9 +1067,6 @@ void DomainServer::sendHeartbeatToIceServer() { qWarning() << "Cannot send an ice-server heartbeat without a private key for signature."; qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat."; - // hookup this slot to the signal from account manager that tells us when keypair is available - connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::sendHeartbeatToIceServer); - if (!limitedNodeList->getSessionUUID().isNull()) { accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); } else { @@ -1078,15 +1078,17 @@ void DomainServer::sendHeartbeatToIceServer() { // 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. - static auto heartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); + if (!_iceServerHeartbeatPacket) { + _iceServerHeartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat); + } bool shouldRecreatePacket = false; - if (heartbeatPacket->getPayloadSize() > 0) { + 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 - heartbeatPacket->seek(0); - QDataStream heartbeatStream(heartbeatPacket.get()); + _iceServerHeartbeatPacket->seek(0); + QDataStream heartbeatStream(_iceServerHeartbeatPacket.get()); QUuid senderUUID; HifiSockAddr publicSocket, localSocket; @@ -1105,15 +1107,15 @@ void DomainServer::sendHeartbeatToIceServer() { // either we don't have a heartbeat packet yet or sockets have changed and we need to make a new one // reset the position in the packet before writing - heartbeatPacket->reset(); + _iceServerHeartbeatPacket->reset(); // write our plaintext data to the packet - QDataStream heartbeatDataStream(heartbeatPacket.get()); + QDataStream heartbeatDataStream(_iceServerHeartbeatPacket.get()); heartbeatDataStream << limitedNodeList->getSessionUUID() << limitedNodeList->getPublicSockAddr() << limitedNodeList->getLocalSockAddr(); // setup a QByteArray that points to the plaintext data - auto plaintext = QByteArray::fromRawData(heartbeatPacket->getPayload(), heartbeatPacket->getPayloadSize()); + auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize()); // generate a signature for the plaintext data in the packet auto signature = accountManager.getAccountInfo().signPlaintext(plaintext); @@ -1123,7 +1125,7 @@ void DomainServer::sendHeartbeatToIceServer() { } // send the heartbeat packet to the ice server now - limitedNodeList->sendUnreliablePacket(*heartbeatPacket, _iceServerSocket); + limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket); } } @@ -2024,3 +2026,14 @@ void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointersetPayloadSize(0); + + // send a heartbeat to the ice server immediately + sendHeartbeatToIceServer(); + } +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index a95b7e57a3..3a83e8696b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -79,6 +79,8 @@ private slots: void handleTempDomainError(QNetworkReply& requestReply); void queuedQuit(QString quitMessage, int exitCode); + + void handleKeypairChange(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); @@ -152,6 +154,7 @@ private: DomainServerSettingsManager _settingsManager; HifiSockAddr _iceServerSocket; + std::unique_ptr _iceServerHeartbeatPacket; QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer From 0191d75e865a08f057ab5d369e917d546231c67c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 17:29:15 -0800 Subject: [PATCH 26/28] allow temporary domain to upload public key --- domain-server/resources/web/settings/js/settings.js | 2 +- domain-server/src/DomainServer.cpp | 13 ++++++++----- libraries/networking/src/AccountManager.cpp | 10 +++++++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 7dc94421be..fae07ace45 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -778,7 +778,7 @@ function chooseFromHighFidelityDomains(clickedButton) { function createTemporaryDomain() { swal({ 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.

" + "In order to make your domain reachable, this will also enable full automatic networking.", showCancelButton: true, diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0fe5fd3300..69a2b05812 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -110,7 +110,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : // preload some user public keys so they can connect on first request _gatekeeper.preloadAllowedUserPublicKeys(); - + optionallyGetTemporaryName(args); } } @@ -393,9 +393,10 @@ bool DomainServer::resetAccountManagerAccessToken() { if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) { accessToken = accessTokenVariant->toString(); } else { - 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() << "A domain-server feature that requires authentication is enabled but no access token is present."; + 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"; + AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString()); return false; } } else { @@ -432,8 +433,8 @@ void DomainServer::setupAutomaticNetworking() { } if (!resetAccountManagerAccessToken()) { - qDebug() << "Cannot send heartbeat to data server without an access token."; - qDebug() << "Add an access token to your config file or via the web interface."; + qDebug() << "Will not send heartbeat to Metaverse API without an access token."; + qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface."; return; } @@ -494,6 +495,8 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { 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()); } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index e8fc5d743b..fbd62ba0af 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -450,8 +450,12 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) // replace the account info access token with a new OAuthAccessToken OAuthAccessToken newOAuthToken; 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); @@ -658,7 +662,7 @@ void AccountManager::processGeneratedKeypair() { callbackParameters.errorCallbackReceiver = this; callbackParameters.errorCallbackMethod = "publicKeyUploadFailed"; - sendRequest(uploadPath, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, + sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, QByteArray(), requestMultiPart); keypairGenerator->deleteLater(); From f8392e62a86c55934fe83600e3cad6f15f6f0c24 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 23 Feb 2016 17:43:01 -0800 Subject: [PATCH 27/28] some wording tweaks in comments --- domain-server/src/DomainServer.cpp | 13 ++++++++----- libraries/networking/src/AccountManager.cpp | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 69a2b05812..bd560d8b81 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -42,7 +42,7 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923; -const QString ICE_SERVER_DEFAULT_HOSTNAME = "localhost"; +const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io"; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), @@ -349,8 +349,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // nodes will currently use this to add resources to data-web that relate to our domain const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); if (idValueVariant) { - QUuid domainID { idValueVariant->toString() }; - nodeList->setSessionUUID(domainID); + nodeList->setSessionUUID(idValueVariant->toString()); } connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); @@ -396,7 +395,10 @@ bool DomainServer::resetAccountManagerAccessToken() { qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."; 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"; + + // clear any existing access token from AccountManager AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString()); + return false; } } else { @@ -1075,7 +1077,7 @@ void DomainServer::sendHeartbeatToIceServer() { } else { qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported"; } - + return; } @@ -1107,7 +1109,8 @@ void DomainServer::sendHeartbeatToIceServer() { } if (shouldRecreatePacket) { - // either we don't have a heartbeat packet yet or sockets have changed and we need to make a new one + // 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(); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index fbd62ba0af..d0838c4a8f 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -630,7 +630,7 @@ void AccountManager::processGeneratedKeypair() { RSAKeypairGenerator* keypairGenerator = qobject_cast(sender()); if (keypairGenerator) { - // set the private key on our metaverse API account info + // hold the private key to later set our metaverse API account info if upload succeeds _pendingPrivateKey = keypairGenerator->getPrivateKey(); // upload the public key so data-web has an up-to-date key From f1aca57543894e648142e91c5c99855b68d1f052 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 24 Feb 2016 16:30:34 -0800 Subject: [PATCH 28/28] remove a tab in whitespace only line --- domain-server/src/DomainServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index bd560d8b81..f0df67a6f6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -110,7 +110,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : // preload some user public keys so they can connect on first request _gatekeeper.preloadAllowedUserPublicKeys(); - + optionallyGetTemporaryName(args); } }