mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 18:50:00 +02:00
Merge pull request #3598 from birarda/domain-tunnel
initial pass at using keypairs for username verification for basic domain-server ACL
This commit is contained in:
commit
4fbc589606
35 changed files with 537 additions and 667 deletions
|
@ -861,8 +861,6 @@ void OctreeServer::readPendingDatagram(const QByteArray& receivedPacket, const H
|
||||||
}
|
}
|
||||||
} else if (packetType == PacketTypeJurisdictionRequest) {
|
} else if (packetType == PacketTypeJurisdictionRequest) {
|
||||||
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
|
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
|
||||||
} else if (packetType == PacketTypeSignedTransactionPayment) {
|
|
||||||
handleSignedTransactionPayment(packetType, receivedPacket);
|
|
||||||
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
|
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
|
||||||
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
|
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1245,51 +1243,6 @@ QString OctreeServer::getStatusLink() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeServer::handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram) {
|
|
||||||
// for now we're not verifying that this is actual payment for any octree edits
|
|
||||||
// just use the AccountManager to send it up to the data server and have it redeemed
|
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
|
||||||
|
|
||||||
const int NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE = 72;
|
|
||||||
const int NUM_BYTES_SIGNED_TRANSACTION_BINARY_SIGNATURE = 256;
|
|
||||||
|
|
||||||
int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(packetType);
|
|
||||||
|
|
||||||
// pull out the transaction message in binary
|
|
||||||
QByteArray messageHex = datagram.mid(numBytesPacketHeader, NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE).toHex();
|
|
||||||
// pull out the binary signed message digest
|
|
||||||
QByteArray signatureHex = datagram.mid(numBytesPacketHeader + NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE,
|
|
||||||
NUM_BYTES_SIGNED_TRANSACTION_BINARY_SIGNATURE).toHex();
|
|
||||||
|
|
||||||
// setup the QJSONObject we are posting
|
|
||||||
QJsonObject postObject;
|
|
||||||
|
|
||||||
const QString TRANSACTION_OBJECT_MESSAGE_KEY = "message";
|
|
||||||
const QString TRANSACTION_OBJECT_SIGNATURE_KEY = "signature";
|
|
||||||
const QString POST_OBJECT_TRANSACTION_KEY = "transaction";
|
|
||||||
|
|
||||||
QJsonObject transactionObject;
|
|
||||||
transactionObject.insert(TRANSACTION_OBJECT_MESSAGE_KEY, QString(messageHex));
|
|
||||||
transactionObject.insert(TRANSACTION_OBJECT_SIGNATURE_KEY, QString(signatureHex));
|
|
||||||
|
|
||||||
postObject.insert(POST_OBJECT_TRANSACTION_KEY, transactionObject);
|
|
||||||
|
|
||||||
// setup our callback params
|
|
||||||
JSONCallbackParameters callbackParameters;
|
|
||||||
callbackParameters.jsonCallbackReceiver = this;
|
|
||||||
callbackParameters.jsonCallbackMethod = "handleSignedTransactionPaymentResponse";
|
|
||||||
|
|
||||||
accountManager.unauthenticatedRequest("/api/v1/transactions/redeem", QNetworkAccessManager::PostOperation,
|
|
||||||
callbackParameters, QJsonDocument(postObject).toJson());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void OctreeServer::handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject) {
|
|
||||||
// pull the ID to debug the transaction
|
|
||||||
QString transactionIDString = jsonObject["data"].toObject()["transaction"].toObject()["id"].toString();
|
|
||||||
qDebug() << "Redeemed transaction with ID" << transactionIDString << "successfully.";
|
|
||||||
}
|
|
||||||
|
|
||||||
void OctreeServer::sendStatsPacket() {
|
void OctreeServer::sendStatsPacket() {
|
||||||
// TODO: we have too many stats to fit in a single MTU... so for now, we break it into multiple JSON objects and
|
// TODO: we have too many stats to fit in a single MTU... so for now, we break it into multiple JSON objects and
|
||||||
// send them separately. What we really should do is change the NodeList::sendStatsToDomainServer() to handle the
|
// send them separately. What we really should do is change the NodeList::sendStatsToDomainServer() to handle the
|
||||||
|
|
|
@ -127,8 +127,6 @@ public slots:
|
||||||
void nodeKilled(SharedNodePointer node);
|
void nodeKilled(SharedNodePointer node);
|
||||||
void sendStatsPacket();
|
void sendStatsPacket();
|
||||||
|
|
||||||
void handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject);
|
|
||||||
|
|
||||||
void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle
|
void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle
|
||||||
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||||
|
|
||||||
|
@ -141,7 +139,6 @@ protected:
|
||||||
QString getConfiguration();
|
QString getConfiguration();
|
||||||
QString getStatusLink();
|
QString getStatusLink();
|
||||||
|
|
||||||
void handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram);
|
|
||||||
void setupDatagramProcessingThread();
|
void setupDatagramProcessingThread();
|
||||||
|
|
||||||
int _argc;
|
int _argc;
|
||||||
|
|
|
@ -38,4 +38,18 @@ endif ()
|
||||||
# link the shared hifi libraries
|
# link the shared hifi libraries
|
||||||
link_hifi_libraries(embedded-webserver networking shared)
|
link_hifi_libraries(embedded-webserver networking shared)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${OPENSSL_LIBRARIES}")
|
||||||
|
|
||||||
link_shared_dependencies()
|
link_shared_dependencies()
|
|
@ -59,6 +59,20 @@
|
||||||
"type": "password",
|
"type": "password",
|
||||||
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
|
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
|
||||||
"value-hidden": true
|
"value-hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "allowed_users",
|
||||||
|
"type": "table",
|
||||||
|
"label": "Allowed Users",
|
||||||
|
"help": "List the High Fidelity names for people you want to be able to connect to this domain.<br/>An empty list means everyone.<br/>You can always connect from this machine.",
|
||||||
|
"numbered": false,
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"label": "Username",
|
||||||
|
"can_set": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -259,7 +259,7 @@ function makeTable(setting, setting_name, setting_value) {
|
||||||
html += "<td class='data'><strong>" + col.label + "</strong></td>" // Data
|
html += "<td class='data'><strong>" + col.label + "</strong></td>" // Data
|
||||||
})
|
})
|
||||||
|
|
||||||
html += "<td class='buttons'><strong>+/-</strong></td></tr>"
|
html += "<td class='buttons'></td></tr>"
|
||||||
|
|
||||||
// populate rows in the table from existing values
|
// populate rows in the table from existing values
|
||||||
var row_num = 1
|
var row_num = 1
|
||||||
|
@ -279,13 +279,13 @@ function makeTable(setting, setting_name, setting_value) {
|
||||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'>"
|
html += "<td class='" + Settings.DATA_COL_CLASS + "'>"
|
||||||
|
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
colIsArray = _.isArray(row)
|
rowIsObject = setting.columns.length > 1
|
||||||
colValue = colIsArray ? row : row[col.name]
|
colValue = rowIsObject ? row[col.name] : row
|
||||||
html += colValue
|
html += colValue
|
||||||
|
|
||||||
// for arrays we add a hidden input to this td so that values can be posted appropriately
|
// for arrays we add a hidden input to this td so that values can be posted appropriately
|
||||||
html += "<input type='hidden' name='" + setting_name + "[" + indexOrName + "]"
|
html += "<input type='hidden' name='" + setting_name + "[" + indexOrName + "]"
|
||||||
+ (colIsArray ? "" : "." + col.name) + "' value='" + colValue + "'/>"
|
+ (rowIsObject ? "." + col.name : "") + "' value='" + colValue + "'/>"
|
||||||
} else if (row.hasOwnProperty(col.name)) {
|
} else if (row.hasOwnProperty(col.name)) {
|
||||||
html += row[col.name]
|
html += row[col.name]
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
|
||||||
#include <QtCore/QDir>
|
#include <QtCore/QDir>
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
#include <QtCore/QJsonObject>
|
#include <QtCore/QJsonObject>
|
||||||
|
@ -44,8 +47,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
_oauthProviderURL(),
|
_oauthProviderURL(),
|
||||||
_oauthClientID(),
|
_oauthClientID(),
|
||||||
_hostname(),
|
_hostname(),
|
||||||
_networkReplyUUIDMap(),
|
|
||||||
_sessionAuthenticationHash(),
|
|
||||||
_webAuthenticationStateSet(),
|
_webAuthenticationStateSet(),
|
||||||
_cookieSessionHash(),
|
_cookieSessionHash(),
|
||||||
_settingsManager()
|
_settingsManager()
|
||||||
|
@ -80,6 +81,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
|
|
||||||
// setup automatic networking settings with data server
|
// setup automatic networking settings with data server
|
||||||
setupAutomaticNetworking();
|
setupAutomaticNetworking();
|
||||||
|
|
||||||
|
// preload some user public keys so they can connect on first request
|
||||||
|
preloadAllowedUserPublicKeys();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,8 +511,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString ALLOWED_ROLES_CONFIG_KEY = "allowed-roles";
|
|
||||||
|
|
||||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||||
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer
|
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer
|
||||||
<< NodeType::MetavoxelServer;
|
<< NodeType::MetavoxelServer;
|
||||||
|
@ -517,8 +519,11 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
||||||
|
|
||||||
NodeType_t nodeType;
|
NodeType_t nodeType;
|
||||||
HifiSockAddr publicSockAddr, localSockAddr;
|
HifiSockAddr publicSockAddr, localSockAddr;
|
||||||
|
|
||||||
|
QDataStream packetStream(packet);
|
||||||
|
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||||
|
|
||||||
int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr);
|
parseNodeDataFromByteArray(packetStream, nodeType, publicSockAddr, localSockAddr, senderSockAddr);
|
||||||
|
|
||||||
QUuid packetUUID = uuidFromPacketHeader(packet);
|
QUuid packetUUID = uuidFromPacketHeader(packet);
|
||||||
|
|
||||||
|
@ -551,33 +556,20 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString connectedUsername;
|
QList<NodeType_t> nodeInterestList;
|
||||||
|
QString username;
|
||||||
if (!isAssignment && !_oauthProviderURL.isEmpty() && _settingsManager.getSettingsMap().contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
QByteArray usernameSignature;
|
||||||
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
|
|
||||||
if (_sessionAuthenticationHash.contains(packetUUID)) {
|
packetStream >> nodeInterestList >> username >> usernameSignature;
|
||||||
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
|
|
||||||
if (connectedUsername.isEmpty()) {
|
if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr)) {
|
||||||
// we've decided this is a user that isn't allowed in, return out
|
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
||||||
// TODO: provide information to the user so they know why they can't connect
|
QByteArray usernameRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied);
|
||||||
return;
|
|
||||||
} else {
|
// send this oauth request datagram back to the client
|
||||||
// we're letting this user in, don't return and remove their UUID from the hash
|
LimitedNodeList::getInstance()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr);
|
||||||
_sessionAuthenticationHash.remove(packetUUID);
|
|
||||||
}
|
return;
|
||||||
} else {
|
|
||||||
// we don't know anything about this client
|
|
||||||
// we have an OAuth provider, ask this interface client to auth against it
|
|
||||||
QByteArray oauthRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainOAuthRequest);
|
|
||||||
QDataStream oauthRequestStream(&oauthRequestByteArray, QIODevice::Append);
|
|
||||||
QUrl authorizationURL = packetUUID.isNull() ? oauthAuthorizationURL() : oauthAuthorizationURL(packetUUID);
|
|
||||||
oauthRequestStream << authorizationURL;
|
|
||||||
|
|
||||||
// send this oauth request datagram back to the client
|
|
||||||
LimitedNodeList::getInstance()->writeUnverifiedDatagram(oauthRequestByteArray, senderSockAddr);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
||||||
|
@ -610,15 +602,109 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have a username from an OAuth connect request, set it on the DomainServerNodeData
|
// if we have a username from an OAuth connect request, set it on the DomainServerNodeData
|
||||||
nodeData->setUsername(connectedUsername);
|
nodeData->setUsername(username);
|
||||||
|
|
||||||
nodeData->setSendingSockAddr(senderSockAddr);
|
nodeData->setSendingSockAddr(senderSockAddr);
|
||||||
|
|
||||||
// reply back to the user with a PacketTypeDomainList
|
// reply back to the user with a PacketTypeDomainList
|
||||||
sendDomainListToNode(newNode, senderSockAddr, nodeInterestListFromPacket(packet, numPreInterestBytes));
|
sendDomainListToNode(newNode, senderSockAddr, nodeInterestList.toSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
||||||
|
|
||||||
|
bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
||||||
|
const QByteArray& usernameSignature,
|
||||||
|
const HifiSockAddr& senderSockAddr) {
|
||||||
|
static const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
|
||||||
|
ALLOWED_USERS_SETTINGS_KEYPATH);
|
||||||
|
static QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
||||||
|
|
||||||
|
if (allowedUsers.count() > 0) {
|
||||||
|
// this is an agent, we need to ask them to provide us with their signed username to see if they are allowed in
|
||||||
|
// we always let in a user who is sending a packet from our local socket or from the localhost address
|
||||||
|
|
||||||
|
if (senderSockAddr.getAddress() != LimitedNodeList::getInstance()->getLocalSockAddr().getAddress()
|
||||||
|
&& senderSockAddr.getAddress() != QHostAddress::LocalHost) {
|
||||||
|
if (allowedUsers.contains(username)) {
|
||||||
|
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||||
|
|
||||||
|
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||||
|
if (!publicKeyArray.isEmpty()) {
|
||||||
|
// if we do have a public key for the user, check for a signature match
|
||||||
|
|
||||||
|
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData());
|
||||||
|
|
||||||
|
// first load up the public key into an RSA struct
|
||||||
|
RSA* rsaPublicKey = d2i_RSAPublicKey(NULL, &publicKeyData, publicKeyArray.size());
|
||||||
|
|
||||||
|
if (rsaPublicKey) {
|
||||||
|
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
|
||||||
|
int decryptResult = RSA_public_decrypt(usernameSignature.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
||||||
|
reinterpret_cast<unsigned char*>(decryptedArray.data()),
|
||||||
|
rsaPublicKey, RSA_PKCS1_PADDING);
|
||||||
|
|
||||||
|
if (decryptResult != -1) {
|
||||||
|
if (username == decryptedArray) {
|
||||||
|
qDebug() << "Username signature matches for" << username << "- allowing connection.";
|
||||||
|
|
||||||
|
// free up the public key before we return
|
||||||
|
RSA_free(rsaPublicKey);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Username signature did not match for" << username << "- denying connection.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Couldn't decrypt user signature for" << username << "- denying connection.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// free up the public key, we don't need it anymore
|
||||||
|
RSA_free(rsaPublicKey);
|
||||||
|
} else {
|
||||||
|
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||||
|
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestUserPublicKey(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// since we have no allowed user list, let them all in
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::preloadAllowedUserPublicKeys() {
|
||||||
|
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH);
|
||||||
|
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
||||||
|
|
||||||
|
if (allowedUsers.size() > 0) {
|
||||||
|
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
|
||||||
|
// going to create > 100 requests
|
||||||
|
foreach(const QString& username, allowedUsers) {
|
||||||
|
requestUserPublicKey(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::requestUserPublicKey(const QString& username) {
|
||||||
|
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||||
|
JSONCallbackParameters callbackParams;
|
||||||
|
callbackParams.jsonCallbackReceiver = this;
|
||||||
|
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||||
|
|
||||||
|
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||||
|
|
||||||
|
qDebug() << "Requesting public key for user" << username;
|
||||||
|
|
||||||
|
AccountManager::getInstance().unauthenticatedRequest(USER_PUBLIC_KEY_PATH.arg(username),
|
||||||
|
QNetworkAccessManager::GetOperation, callbackParams);
|
||||||
|
}
|
||||||
|
|
||||||
QUrl DomainServer::oauthRedirectURL() {
|
QUrl DomainServer::oauthRedirectURL() {
|
||||||
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
|
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
|
||||||
}
|
}
|
||||||
|
@ -653,12 +739,9 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
|
||||||
return authorizationURL;
|
return authorizationURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
|
int DomainServer::parseNodeDataFromByteArray(QDataStream& packetStream, NodeType_t& nodeType,
|
||||||
HifiSockAddr& localSockAddr, const QByteArray& packet,
|
HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr,
|
||||||
const HifiSockAddr& senderSockAddr) {
|
const HifiSockAddr& senderSockAddr) {
|
||||||
QDataStream packetStream(packet);
|
|
||||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
|
||||||
|
|
||||||
packetStream >> nodeType;
|
packetStream >> nodeType;
|
||||||
packetStream >> publicSockAddr >> localSockAddr;
|
packetStream >> publicSockAddr >> localSockAddr;
|
||||||
|
|
||||||
|
@ -925,7 +1008,30 @@ void DomainServer::sendPendingTransactionsToServer() {
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||||
|
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
|
|
||||||
|
if (jsonObject["status"].toString() == "success") {
|
||||||
|
// figure out which user this is for
|
||||||
|
|
||||||
|
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||||
|
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||||
|
|
||||||
|
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||||
|
QString username = usernameRegex.cap(1);
|
||||||
|
|
||||||
|
qDebug() << "Storing a public key for user" << username;
|
||||||
|
|
||||||
|
// pull the public key as a QByteArray from this response
|
||||||
|
const QString JSON_DATA_KEY = "data";
|
||||||
|
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||||
|
|
||||||
|
_userPublicKeys[username] =
|
||||||
|
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::transactionJSONCallback(const QJsonObject& data) {
|
void DomainServer::transactionJSONCallback(const QJsonObject& data) {
|
||||||
|
@ -1095,9 +1201,13 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
|
||||||
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
|
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
|
||||||
NodeType_t throwawayNodeType;
|
NodeType_t throwawayNodeType;
|
||||||
HifiSockAddr nodePublicAddress, nodeLocalAddress;
|
HifiSockAddr nodePublicAddress, nodeLocalAddress;
|
||||||
|
|
||||||
|
QDataStream packetStream(receivedPacket);
|
||||||
|
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
|
||||||
|
|
||||||
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
|
int numNodeInfoBytes = parseNodeDataFromByteArray(packetStream, throwawayNodeType,
|
||||||
receivedPacket, senderSockAddr);
|
nodePublicAddress, nodeLocalAddress,
|
||||||
|
senderSockAddr);
|
||||||
|
|
||||||
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
|
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
|
||||||
|
|
||||||
|
@ -1545,13 +1655,6 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
|
||||||
// we've redirected the user back to our homepage
|
// we've redirected the user back to our homepage
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else {
|
|
||||||
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
|
|
||||||
|
|
||||||
// insert this to our pending token replies so we can associate the returned access token with the right UUID
|
|
||||||
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
|
|
||||||
|
|
||||||
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1695,22 +1798,6 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
||||||
|
|
||||||
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
|
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
|
||||||
|
|
||||||
void DomainServer::handleTokenRequestFinished() {
|
|
||||||
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
|
|
||||||
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
|
|
||||||
|
|
||||||
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
|
|
||||||
|
|
||||||
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
|
||||||
<< "-" << "requesting profile.";
|
|
||||||
|
|
||||||
QNetworkReply* profileReply = profileRequestGivenTokenReply(networkReply);
|
|
||||||
|
|
||||||
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished);
|
|
||||||
|
|
||||||
_networkReplyUUIDMap.insert(profileReply, matchingSessionUUID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) {
|
QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) {
|
||||||
// pull the access token from the returned JSON and store it with the matching session UUID
|
// pull the access token from the returned JSON and store it with the matching session UUID
|
||||||
|
@ -1719,54 +1806,12 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR
|
||||||
|
|
||||||
// fire off a request to get this user's identity so we can see if we will let them in
|
// fire off a request to get this user's identity so we can see if we will let them in
|
||||||
QUrl profileURL = _oauthProviderURL;
|
QUrl profileURL = _oauthProviderURL;
|
||||||
profileURL.setPath("/api/v1/users/profile");
|
profileURL.setPath("/api/v1/user/profile");
|
||||||
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
|
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
|
||||||
|
|
||||||
return NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
|
return NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::handleProfileRequestFinished() {
|
|
||||||
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
|
|
||||||
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
|
|
||||||
|
|
||||||
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
|
|
||||||
QJsonDocument profileJSON = QJsonDocument::fromJson(networkReply->readAll());
|
|
||||||
|
|
||||||
if (profileJSON.object()["status"].toString() == "success") {
|
|
||||||
// pull the user roles from the response
|
|
||||||
QJsonArray userRolesArray = profileJSON.object()["data"].toObject()["user"].toObject()["roles"].toArray();
|
|
||||||
|
|
||||||
QStringList allowedRolesArray = _settingsManager.getSettingsMap().value(ALLOWED_ROLES_CONFIG_KEY).toStringList();
|
|
||||||
|
|
||||||
QString connectableUsername;
|
|
||||||
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();
|
|
||||||
|
|
||||||
foreach(const QJsonValue& roleValue, userRolesArray) {
|
|
||||||
if (allowedRolesArray.contains(roleValue.toString())) {
|
|
||||||
// the user has a role that lets them in
|
|
||||||
// set the bool to true and break
|
|
||||||
connectableUsername = profileUsername;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectableUsername.isEmpty()) {
|
|
||||||
qDebug() << "User" << profileUsername << "with session UUID"
|
|
||||||
<< uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
|
||||||
<< "does not have an allowable role. Refusing connection.";
|
|
||||||
} else {
|
|
||||||
qDebug() << "User" << profileUsername << "with session UUID"
|
|
||||||
<< uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
|
||||||
<< "has an allowable role. Can connect.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert this UUID and a flag that indicates if they are allowed to connect
|
|
||||||
_sessionAuthenticationHash.insert(matchingSessionUUID, connectableUsername);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
|
const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
|
||||||
|
|
||||||
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {
|
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {
|
||||||
|
|
|
@ -52,6 +52,7 @@ public slots:
|
||||||
/// Called by NodeList to inform us a node has been killed
|
/// Called by NodeList to inform us a node has been killed
|
||||||
void nodeKilled(SharedNodePointer node);
|
void nodeKilled(SharedNodePointer node);
|
||||||
|
|
||||||
|
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||||
void transactionJSONCallback(const QJsonObject& data);
|
void transactionJSONCallback(const QJsonObject& data);
|
||||||
|
|
||||||
void restart();
|
void restart();
|
||||||
|
@ -82,8 +83,17 @@ private:
|
||||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||||
|
|
||||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||||
int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
|
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||||
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
const HifiSockAddr& senderSockAddr);
|
||||||
|
|
||||||
|
void preloadAllowedUserPublicKeys();
|
||||||
|
void requestUserPublicKey(const QString& username);
|
||||||
|
|
||||||
|
int parseNodeDataFromByteArray(QDataStream& packetStream,
|
||||||
|
NodeType_t& nodeType,
|
||||||
|
HifiSockAddr& publicSockAddr,
|
||||||
|
HifiSockAddr& localSockAddr,
|
||||||
|
const HifiSockAddr& senderSockAddr);
|
||||||
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
|
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
|
||||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
|
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
|
||||||
const NodeSet& nodeInterestList);
|
const NodeSet& nodeInterestList);
|
||||||
|
@ -131,13 +141,11 @@ private:
|
||||||
QString _oauthClientID;
|
QString _oauthClientID;
|
||||||
QString _oauthClientSecret;
|
QString _oauthClientSecret;
|
||||||
QString _hostname;
|
QString _hostname;
|
||||||
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
|
|
||||||
QHash<QUuid, QString> _sessionAuthenticationHash;
|
|
||||||
|
|
||||||
QSet<QUuid> _webAuthenticationStateSet;
|
QSet<QUuid> _webAuthenticationStateSet;
|
||||||
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
|
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
|
||||||
|
|
||||||
HifiSockAddr _localSockAddr;
|
QHash<QString, QByteArray> _userPublicKeys;
|
||||||
|
|
||||||
QHash<QUuid, NetworkPeer> _connectingICEPeers;
|
QHash<QUuid, NetworkPeer> _connectingICEPeers;
|
||||||
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
|
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
|
||||||
|
|
|
@ -105,7 +105,6 @@ link_hifi_libraries(shared octree voxels fbx metavoxels networking particles ent
|
||||||
|
|
||||||
# find any optional and required libraries
|
# find any optional and required libraries
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_package(OpenSSL REQUIRED)
|
|
||||||
|
|
||||||
# perform standard include and linking for found externals
|
# perform standard include and linking for found externals
|
||||||
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
||||||
|
@ -169,10 +168,9 @@ endif ()
|
||||||
|
|
||||||
# include headers for interface and InterfaceConfig.
|
# include headers for interface and InterfaceConfig.
|
||||||
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")
|
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")
|
||||||
include_directories("${OPENSSL_INCLUDE_DIR}")
|
|
||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
${TARGET_NAME} ${ZLIB_LIBRARIES} ${OPENSSL_LIBRARIES}
|
${TARGET_NAME} ${ZLIB_LIBRARIES}
|
||||||
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKitWidgets
|
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKitWidgets
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,6 @@
|
||||||
#include "InterfaceVersion.h"
|
#include "InterfaceVersion.h"
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
#include "ModelUploader.h"
|
#include "ModelUploader.h"
|
||||||
#include "PaymentManager.h"
|
|
||||||
#include "Util.h"
|
#include "Util.h"
|
||||||
#include "devices/MIDIManager.h"
|
#include "devices/MIDIManager.h"
|
||||||
#include "devices/OculusManager.h"
|
#include "devices/OculusManager.h"
|
||||||
|
@ -90,7 +89,6 @@
|
||||||
#include "scripting/WindowScriptingInterface.h"
|
#include "scripting/WindowScriptingInterface.h"
|
||||||
|
|
||||||
#include "ui/InfoView.h"
|
#include "ui/InfoView.h"
|
||||||
#include "ui/OAuthWebViewHandler.h"
|
|
||||||
#include "ui/Snapshot.h"
|
#include "ui/Snapshot.h"
|
||||||
#include "ui/Stats.h"
|
#include "ui/Stats.h"
|
||||||
#include "ui/TextRenderer.h"
|
#include "ui/TextRenderer.h"
|
||||||
|
@ -220,10 +218,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
listenPort = atoi(portStr);
|
listenPort = atoi(portStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// call the OAuthWebviewHandler static getter so that its instance lives in our thread
|
|
||||||
// make sure it is ready before the NodeList might need it
|
|
||||||
OAuthWebViewHandler::getInstance();
|
|
||||||
|
|
||||||
// start the nodeThread so its event loop is running
|
// start the nodeThread so its event loop is running
|
||||||
_nodeThread->start();
|
_nodeThread->start();
|
||||||
|
|
||||||
|
@ -256,11 +250,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
|
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
|
||||||
connect(&domainHandler, &DomainHandler::hostnameChanged, Menu::getInstance(), &Menu::clearLoginDialogDisplayedFlag);
|
connect(&domainHandler, &DomainHandler::hostnameChanged, Menu::getInstance(), &Menu::clearLoginDialogDisplayedFlag);
|
||||||
|
|
||||||
// hookup VoxelEditSender to PaymentManager so we can pay for octree edits
|
|
||||||
const PaymentManager& paymentManager = PaymentManager::getInstance();
|
|
||||||
connect(&_voxelEditSender, &VoxelEditPacketSender::octreePaymentRequired,
|
|
||||||
&paymentManager, &PaymentManager::sendSignedPayment);
|
|
||||||
|
|
||||||
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
|
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
|
||||||
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
||||||
|
|
||||||
|
@ -3483,7 +3472,7 @@ void Application::updateLocationInServer() {
|
||||||
|
|
||||||
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
|
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
|
||||||
|
|
||||||
accountManager.authenticatedRequest("/api/v1/users/location", QNetworkAccessManager::PutOperation,
|
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
|
||||||
JSONCallbackParameters(), QJsonDocument(rootObject).toJson());
|
JSONCallbackParameters(), QJsonDocument(rootObject).toJson());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3526,20 +3515,18 @@ void Application::domainChanged(const QString& domainHostname) {
|
||||||
|
|
||||||
// reset the voxels renderer
|
// reset the voxels renderer
|
||||||
_voxels.killLocalVoxels();
|
_voxels.killLocalVoxels();
|
||||||
|
|
||||||
// reset the auth URL for OAuth web view handler
|
|
||||||
OAuthWebViewHandler::getInstance().clearLastAuthorizationURL();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::connectedToDomain(const QString& hostname) {
|
void Application::connectedToDomain(const QString& hostname) {
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
AccountManager& accountManager = AccountManager::getInstance();
|
||||||
|
const QUuid& domainID = NodeList::getInstance()->getDomainHandler().getUUID();
|
||||||
|
|
||||||
|
if (accountManager.isLoggedIn() && !domainID.isNull()) {
|
||||||
|
// update our data-server with the domain-server we're logged in with
|
||||||
|
|
||||||
if (accountManager.isLoggedIn()) {
|
QString domainPutJsonString = "{\"location\":{\"domain_id\":\"" + uuidStringWithoutCurlyBraces(domainID) + "\"}}";
|
||||||
// update our domain-server with the data-server we're logged in with
|
|
||||||
|
|
||||||
QString domainPutJsonString = "{\"address\":{\"domain\":\"" + hostname + "\"}}";
|
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
|
||||||
|
|
||||||
accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation,
|
|
||||||
JSONCallbackParameters(), domainPutJsonString.toUtf8());
|
JSONCallbackParameters(), domainPutJsonString.toUtf8());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
|
|
||||||
#include <QtCore/QWeakPointer>
|
#include <QtCore/QWeakPointer>
|
||||||
|
|
||||||
|
#include <AccountManager.h>
|
||||||
#include <PerfStat.h>
|
#include <PerfStat.h>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
#include "ui/OAuthWebViewHandler.h"
|
|
||||||
|
|
||||||
#include "DatagramProcessor.h"
|
#include "DatagramProcessor.h"
|
||||||
|
|
||||||
|
@ -136,16 +136,11 @@ void DatagramProcessor::processDatagrams() {
|
||||||
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
|
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PacketTypeDomainOAuthRequest: {
|
case PacketTypeDomainConnectionDenied: {
|
||||||
QDataStream readStream(incomingPacket);
|
// output to the log so the user knows they got a denied connection request
|
||||||
readStream.skipRawData(numBytesForPacketHeader(incomingPacket));
|
// and check and signal for an access token so that we can make sure they are logged in
|
||||||
|
qDebug() << "The domain-server denied a connection request.";
|
||||||
QUrl authorizationURL;
|
AccountManager::getInstance().checkAndSignalForAccessToken();
|
||||||
readStream >> authorizationURL;
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL",
|
|
||||||
Q_ARG(const QUrl&, authorizationURL));
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PacketTypeMuteEnvironment: {
|
case PacketTypeMuteEnvironment: {
|
||||||
|
|
|
@ -364,7 +364,9 @@ void ModelUploader::send() {
|
||||||
_progressBar = NULL;
|
_progressBar = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelUploader::checkJSON(const QJsonObject& jsonResponse) {
|
void ModelUploader::checkJSON(QNetworkReply& requestReply) {
|
||||||
|
QJsonObject jsonResponse = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
|
|
||||||
if (jsonResponse.contains("status") && jsonResponse.value("status").toString() == "success") {
|
if (jsonResponse.contains("status") && jsonResponse.value("status").toString() == "success") {
|
||||||
qDebug() << "status : success";
|
qDebug() << "status : success";
|
||||||
JSONCallbackParameters callbackParams;
|
JSONCallbackParameters callbackParams;
|
||||||
|
@ -426,7 +428,7 @@ void ModelUploader::uploadUpdate(qint64 bytesSent, qint64 bytesTotal) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelUploader::uploadSuccess(const QJsonObject& jsonResponse) {
|
void ModelUploader::uploadSuccess(QNetworkReply& requestReply) {
|
||||||
if (_progressDialog) {
|
if (_progressDialog) {
|
||||||
_progressDialog->accept();
|
_progressDialog->accept();
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,9 +40,9 @@ public slots:
|
||||||
void send();
|
void send();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void checkJSON(const QJsonObject& jsonResponse);
|
void checkJSON(QNetworkReply& requestReply);
|
||||||
void uploadUpdate(qint64 bytesSent, qint64 bytesTotal);
|
void uploadUpdate(qint64 bytesSent, qint64 bytesTotal);
|
||||||
void uploadSuccess(const QJsonObject& jsonResponse);
|
void uploadSuccess(QNetworkReply& requestReply);
|
||||||
void uploadFailed(QNetworkReply& errorReply);
|
void uploadFailed(QNetworkReply& errorReply);
|
||||||
void checkS3();
|
void checkS3();
|
||||||
void processCheck();
|
void processCheck();
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
//
|
|
||||||
// PaymentManager.cpp
|
|
||||||
// interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 2014-07-30.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <QtCore/QDateTime>
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
#include <QtCore/QUuid>
|
|
||||||
|
|
||||||
#include <Menu.h>
|
|
||||||
#include <NodeList.h>
|
|
||||||
#include <PacketHeaders.h>
|
|
||||||
|
|
||||||
#include "SignedWalletTransaction.h"
|
|
||||||
|
|
||||||
#include "PaymentManager.h"
|
|
||||||
|
|
||||||
PaymentManager& PaymentManager::getInstance() {
|
|
||||||
static PaymentManager sharedInstance;
|
|
||||||
return sharedInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaymentManager::sendSignedPayment(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID) {
|
|
||||||
|
|
||||||
QByteArray walletPrivateKeyByteArray = Menu::getInstance()->getWalletPrivateKey();
|
|
||||||
|
|
||||||
if (!walletPrivateKeyByteArray.isEmpty()) {
|
|
||||||
// setup a signed wallet transaction
|
|
||||||
const qint64 DEFAULT_TRANSACTION_EXPIRY_SECONDS = 60;
|
|
||||||
qint64 currentTimestamp = QDateTime::currentDateTimeUtc().toTime_t();
|
|
||||||
SignedWalletTransaction newTransaction(destinationWalletUUID, satoshiAmount,
|
|
||||||
currentTimestamp, DEFAULT_TRANSACTION_EXPIRY_SECONDS);
|
|
||||||
|
|
||||||
// send the signed transaction to the redeeming node
|
|
||||||
QByteArray transactionByteArray = byteArrayWithPopulatedHeader(PacketTypeSignedTransactionPayment);
|
|
||||||
|
|
||||||
// append the binary message and the signed message digest
|
|
||||||
transactionByteArray.append(newTransaction.binaryMessage());
|
|
||||||
QByteArray signedMessageDigest = newTransaction.signedMessageDigest();
|
|
||||||
|
|
||||||
if (!signedMessageDigest.isEmpty()) {
|
|
||||||
transactionByteArray.append(signedMessageDigest);
|
|
||||||
|
|
||||||
qDebug() << "Paying" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID;
|
|
||||||
|
|
||||||
// use the NodeList to send that to the right node
|
|
||||||
NodeList* nodeList = NodeList::getInstance();
|
|
||||||
nodeList->writeDatagram(transactionByteArray, nodeList->nodeWithUUID(nodeUUID));
|
|
||||||
} else {
|
|
||||||
qDebug() << "Payment of" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID <<
|
|
||||||
"cannot be sent because there was an error signing the transaction.";
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qDebug() << "Payment of" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID <<
|
|
||||||
"cannot be sent because there is no stored wallet private key.";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
//
|
|
||||||
// PaymentManager.h
|
|
||||||
// interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 2014-07-30.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_PaymentManager_h
|
|
||||||
#define hifi_PaymentManager_h
|
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
|
||||||
|
|
||||||
class PaymentManager : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
static PaymentManager& getInstance();
|
|
||||||
public slots:
|
|
||||||
void sendSignedPayment(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_PaymentManager_h
|
|
|
@ -1,86 +0,0 @@
|
||||||
//
|
|
||||||
// SignedWalletTransaction.cpp
|
|
||||||
// interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 2014-07-11.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <QtCore/QCryptographicHash>
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
#include <QtCore/QFile>
|
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
|
||||||
#include <openssl/rsa.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
|
|
||||||
#include <AccountManager.h>
|
|
||||||
|
|
||||||
#include "Menu.h"
|
|
||||||
|
|
||||||
#include "SignedWalletTransaction.h"
|
|
||||||
|
|
||||||
SignedWalletTransaction::SignedWalletTransaction(const QUuid& destinationUUID, qint64 amount,
|
|
||||||
qint64 messageTimestamp, qint64 expiryDelta) :
|
|
||||||
WalletTransaction(destinationUUID, amount),
|
|
||||||
_messageTimestamp(messageTimestamp),
|
|
||||||
_expiryDelta(expiryDelta)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray SignedWalletTransaction::binaryMessage() {
|
|
||||||
// build the message using the components of this transaction
|
|
||||||
|
|
||||||
// UUID, source UUID, destination UUID, message timestamp, expiry delta, amount
|
|
||||||
QByteArray messageBinary;
|
|
||||||
|
|
||||||
messageBinary.append(_uuid.toRfc4122());
|
|
||||||
|
|
||||||
messageBinary.append(reinterpret_cast<const char*>(&_messageTimestamp), sizeof(_messageTimestamp));
|
|
||||||
messageBinary.append(reinterpret_cast<const char*>(&_expiryDelta), sizeof(_expiryDelta));
|
|
||||||
|
|
||||||
messageBinary.append(AccountManager::getInstance().getAccountInfo().getWalletID().toRfc4122());
|
|
||||||
|
|
||||||
messageBinary.append(_destinationUUID.toRfc4122());
|
|
||||||
|
|
||||||
messageBinary.append(reinterpret_cast<const char*>(&_amount), sizeof(_amount));
|
|
||||||
|
|
||||||
return messageBinary;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray SignedWalletTransaction::hexMessage() {
|
|
||||||
return binaryMessage().toHex();
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray SignedWalletTransaction::messageDigest() {
|
|
||||||
return QCryptographicHash::hash(hexMessage(), QCryptographicHash::Sha256).toHex();
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray SignedWalletTransaction::signedMessageDigest() {
|
|
||||||
// pull the current private key from menu into RSA structure in memory
|
|
||||||
QByteArray privateKeyByteArray = Menu::getInstance()->getWalletPrivateKey();
|
|
||||||
|
|
||||||
BIO* privateKeyBIO = NULL;
|
|
||||||
RSA* rsaPrivateKey = NULL;
|
|
||||||
|
|
||||||
privateKeyBIO = BIO_new_mem_buf(privateKeyByteArray.data(), privateKeyByteArray.size());
|
|
||||||
PEM_read_bio_RSAPrivateKey(privateKeyBIO, &rsaPrivateKey, NULL, NULL);
|
|
||||||
|
|
||||||
QByteArray digestToEncrypt = messageDigest();
|
|
||||||
QByteArray encryptedDigest(RSA_size(rsaPrivateKey), 0);
|
|
||||||
|
|
||||||
int encryptReturn = RSA_private_encrypt(digestToEncrypt.size(),
|
|
||||||
reinterpret_cast<const unsigned char*>(digestToEncrypt.constData()),
|
|
||||||
reinterpret_cast<unsigned char*>(encryptedDigest.data()),
|
|
||||||
rsaPrivateKey, RSA_PKCS1_PADDING);
|
|
||||||
|
|
||||||
// free the two structures used
|
|
||||||
BIO_free(privateKeyBIO);
|
|
||||||
RSA_free(rsaPrivateKey);
|
|
||||||
|
|
||||||
return (encryptReturn != -1) ? encryptedDigest : QByteArray();
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
//
|
|
||||||
// SignedWalletTransaction.h
|
|
||||||
// interfac/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 2014-07-11.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_SignedWalletTransaction_h
|
|
||||||
#define hifi_SignedWalletTransaction_h
|
|
||||||
|
|
||||||
#include <WalletTransaction.h>
|
|
||||||
|
|
||||||
class SignedWalletTransaction : public WalletTransaction {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
SignedWalletTransaction(const QUuid& destinationUUID, qint64 amount, qint64 messageTimestamp, qint64 expiryDelta);
|
|
||||||
|
|
||||||
QByteArray binaryMessage();
|
|
||||||
QByteArray hexMessage();
|
|
||||||
QByteArray messageDigest();
|
|
||||||
QByteArray signedMessageDigest();
|
|
||||||
|
|
||||||
private:
|
|
||||||
qint64 _messageTimestamp;
|
|
||||||
qint64 _expiryDelta;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_SignedWalletTransaction_h
|
|
|
@ -20,10 +20,10 @@
|
||||||
const float MAX_AXIS = 32768.0f;
|
const float MAX_AXIS = 32768.0f;
|
||||||
|
|
||||||
Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) :
|
Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) :
|
||||||
_instanceId(instanceId),
|
|
||||||
_name(name),
|
|
||||||
_sdlGameController(sdlGameController),
|
_sdlGameController(sdlGameController),
|
||||||
_sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)),
|
_sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)),
|
||||||
|
_instanceId(instanceId),
|
||||||
|
_name(name),
|
||||||
_axes(QVector<float>(SDL_JoystickNumAxes(_sdlJoystick))),
|
_axes(QVector<float>(SDL_JoystickNumAxes(_sdlJoystick))),
|
||||||
_buttons(QVector<bool>(SDL_JoystickNumButtons(_sdlJoystick)))
|
_buttons(QVector<bool>(SDL_JoystickNumButtons(_sdlJoystick)))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
//
|
|
||||||
// OAuthWebViewHandler.cpp
|
|
||||||
// interface/src/ui
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 2014-05-01.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <qwebview.h>
|
|
||||||
#include <qurlquery.h>
|
|
||||||
|
|
||||||
#include <AccountManager.h>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
|
|
||||||
#include "OAuthWebViewHandler.h"
|
|
||||||
|
|
||||||
OAuthWebViewHandler& OAuthWebViewHandler::getInstance() {
|
|
||||||
static OAuthWebViewHandler sharedInstance;
|
|
||||||
return sharedInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuthWebViewHandler::OAuthWebViewHandler() :
|
|
||||||
_activeWebView(NULL),
|
|
||||||
_webViewRedisplayTimer(),
|
|
||||||
_lastAuthorizationURL()
|
|
||||||
{
|
|
||||||
addHighFidelityRootCAToSSLConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char HIGH_FIDELITY_CA[] = "-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n"
|
|
||||||
"VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n"
|
|
||||||
"aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n"
|
|
||||||
"YXRpb25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEW\n"
|
|
||||||
"E29wc0BoaWdoZmlkZWxpdHkuaW8wHhcNMTQwMzI4MjIzMzM1WhcNMjQwMzI1MjIz\n"
|
|
||||||
"MzM1WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV\n"
|
|
||||||
"BAcTDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoTEkhpZ2ggRmlkZWxpdHksIEluYzET\n"
|
|
||||||
"MBEGA1UECxMKT3BlcmF0aW9uczEYMBYGA1UEAxMPaGlnaGZpZGVsaXR5LmlvMSIw\n"
|
|
||||||
"IAYJKoZIhvcNAQkBFhNvcHNAaGlnaGZpZGVsaXR5LmlvMIGfMA0GCSqGSIb3DQEB\n"
|
|
||||||
"AQUAA4GNADCBiQKBgQDyo1euYiPPEdnvDZnIjWrrP230qUKMSj8SWoIkbTJF2hE8\n"
|
|
||||||
"2eP3YOgbgSGBzZ8EJBxIOuNmj9g9Eg6691hIKFqy5W0BXO38P04Gg+pVBvpHFGBi\n"
|
|
||||||
"wpqGbfsjaUDuYmBeJRcMO0XYkLCRQG+lAQNHoFDdItWAJfC3FwtP3OCDnz8cNwID\n"
|
|
||||||
"AQABo4IBEzCCAQ8wHQYDVR0OBBYEFCSv2kmiGg6VFMnxXzLDNP304cPAMIHfBgNV\n"
|
|
||||||
"HSMEgdcwgdSAFCSv2kmiGg6VFMnxXzLDNP304cPAoYGwpIGtMIGqMQswCQYDVQQG\n"
|
|
||||||
"EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\n"
|
|
||||||
"bzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVyYXRp\n"
|
|
||||||
"b25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEWE29w\n"
|
|
||||||
"c0BoaWdoZmlkZWxpdHkuaW+CCQDZX0ZEQ/QPGzAMBgNVHRMEBTADAQH/MA0GCSqG\n"
|
|
||||||
"SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n"
|
|
||||||
"FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n"
|
|
||||||
"Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n"
|
|
||||||
"-----END CERTIFICATE-----\n";
|
|
||||||
|
|
||||||
void OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig() {
|
|
||||||
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
|
|
||||||
|
|
||||||
// add the High Fidelity root CA to the list of trusted CA certificates
|
|
||||||
QByteArray highFidelityCACertificate(HIGH_FIDELITY_CA, sizeof(HIGH_FIDELITY_CA));
|
|
||||||
sslConfig.setCaCertificates(sslConfig.caCertificates() + QSslCertificate::fromData(highFidelityCACertificate));
|
|
||||||
|
|
||||||
// set the modified configuration
|
|
||||||
QSslConfiguration::setDefaultConfiguration(sslConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int WEB_VIEW_REDISPLAY_ELAPSED_MSECS = 5 * 1000;
|
|
||||||
|
|
||||||
void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authorizationURL) {
|
|
||||||
if (!_activeWebView) {
|
|
||||||
|
|
||||||
if (!_lastAuthorizationURL.isEmpty()) {
|
|
||||||
if (_lastAuthorizationURL.host() == authorizationURL.host()
|
|
||||||
&& _webViewRedisplayTimer.elapsed() < WEB_VIEW_REDISPLAY_ELAPSED_MSECS) {
|
|
||||||
// this would be re-displaying an OAuth dialog for the same auth URL inside of the redisplay ms
|
|
||||||
// so return instead
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastAuthorizationURL = authorizationURL;
|
|
||||||
|
|
||||||
_activeWebView = new QWebView;
|
|
||||||
|
|
||||||
// keep the window on top and delete it when it closes
|
|
||||||
_activeWebView->setWindowFlags(Qt::Sheet);
|
|
||||||
_activeWebView->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
|
|
||||||
qDebug() << "Displaying QWebView for OAuth authorization at" << authorizationURL.toString();
|
|
||||||
|
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
|
||||||
|
|
||||||
QUrl codedAuthorizationURL = authorizationURL;
|
|
||||||
|
|
||||||
// check if we have an access token for this host - if so we can bypass login by adding it to the URL
|
|
||||||
if (accountManager.getAuthURL().host() == authorizationURL.host()
|
|
||||||
&& accountManager.hasValidAccessToken()) {
|
|
||||||
|
|
||||||
const QString ACCESS_TOKEN_QUERY_STRING_KEY = "access_token";
|
|
||||||
|
|
||||||
QUrlQuery authQuery(codedAuthorizationURL);
|
|
||||||
authQuery.addQueryItem(ACCESS_TOKEN_QUERY_STRING_KEY, accountManager.getAccountInfo().getAccessToken().token);
|
|
||||||
|
|
||||||
codedAuthorizationURL.setQuery(authQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(_activeWebView.data(), &QWebView::urlChanged, this, &OAuthWebViewHandler::handleURLChanged);
|
|
||||||
|
|
||||||
_activeWebView->load(codedAuthorizationURL);
|
|
||||||
|
|
||||||
connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::sslErrors,
|
|
||||||
this, &OAuthWebViewHandler::handleSSLErrors);
|
|
||||||
connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::finished,
|
|
||||||
this, &OAuthWebViewHandler::handleReplyFinished);
|
|
||||||
connect(_activeWebView.data(), &QWebView::loadFinished, this, &OAuthWebViewHandler::handleLoadFinished);
|
|
||||||
|
|
||||||
// connect to the destroyed signal so after the web view closes we can start a timer
|
|
||||||
connect(_activeWebView.data(), &QWebView::destroyed, this, &OAuthWebViewHandler::handleWebViewDestroyed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OAuthWebViewHandler::handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList) {
|
|
||||||
qDebug() << "SSL Errors:" << errorList;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OAuthWebViewHandler::handleLoadFinished(bool success) {
|
|
||||||
if (success && _activeWebView->url().host() == NodeList::getInstance()->getDomainHandler().getHostname()) {
|
|
||||||
qDebug() << "OAuth authorization code passed successfully to domain-server.";
|
|
||||||
|
|
||||||
// grab the UUID that is set as the state parameter in the auth URL
|
|
||||||
// since that is our new session UUID
|
|
||||||
QUrlQuery authQuery(_activeWebView->url());
|
|
||||||
|
|
||||||
const QString AUTH_STATE_QUERY_KEY = "state";
|
|
||||||
NodeList::getInstance()->setSessionUUID(QUuid(authQuery.queryItemValue(AUTH_STATE_QUERY_KEY)));
|
|
||||||
|
|
||||||
_activeWebView->close();
|
|
||||||
_activeWebView = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OAuthWebViewHandler::handleReplyFinished(QNetworkReply* reply) {
|
|
||||||
if (_activeWebView && reply->error() != QNetworkReply::NoError) {
|
|
||||||
qDebug() << "Error loading" << reply->url() << "-" << reply->errorString();
|
|
||||||
_activeWebView->close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OAuthWebViewHandler::handleWebViewDestroyed(QObject* destroyedObject) {
|
|
||||||
_webViewRedisplayTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OAuthWebViewHandler::handleURLChanged(const QUrl& url) {
|
|
||||||
// check if this is the authorization screen - if it is then we need to show the OAuthWebViewHandler
|
|
||||||
const QString ACCESS_TOKEN_URL_REGEX_STRING = "redirect_uri=[\\w:\\/\\.]+&access_token=";
|
|
||||||
QRegExp accessTokenRegex(ACCESS_TOKEN_URL_REGEX_STRING);
|
|
||||||
|
|
||||||
if (accessTokenRegex.indexIn(url.toString()) != -1) {
|
|
||||||
_activeWebView->show();
|
|
||||||
} else if (url.toString() == DEFAULT_NODE_AUTH_URL.toString() + "/login") {
|
|
||||||
// this is a login request - we're going to close the webview and signal the AccountManager that we need a login
|
|
||||||
qDebug() << "data-server replied with login request. Signalling that login is required to proceed with OAuth.";
|
|
||||||
_activeWebView->close();
|
|
||||||
AccountManager::getInstance().checkAndSignalForAccessToken();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
//
|
|
||||||
// OAuthWebviewHandler.h
|
|
||||||
// interface/src/ui
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 2014-05-01.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_OAuthWebviewHandler_h
|
|
||||||
#define hifi_OAuthWebviewHandler_h
|
|
||||||
|
|
||||||
#include <QtCore/QUrl>
|
|
||||||
|
|
||||||
class QWebView;
|
|
||||||
|
|
||||||
class OAuthWebViewHandler : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
OAuthWebViewHandler();
|
|
||||||
static OAuthWebViewHandler& getInstance();
|
|
||||||
static void addHighFidelityRootCAToSSLConfig();
|
|
||||||
|
|
||||||
void clearLastAuthorizationURL() { _lastAuthorizationURL = QUrl(); }
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void displayWebviewForAuthorizationURL(const QUrl& authorizationURL);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList);
|
|
||||||
void handleLoadFinished(bool success);
|
|
||||||
void handleReplyFinished(QNetworkReply* reply);
|
|
||||||
void handleWebViewDestroyed(QObject* destroyedObject);
|
|
||||||
void handleURLChanged(const QUrl& url);
|
|
||||||
private:
|
|
||||||
QPointer<QWebView> _activeWebView;
|
|
||||||
QElapsedTimer _webViewRedisplayTimer;
|
|
||||||
QUrl _lastAuthorizationURL;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_OAuthWebviewHandler_h
|
|
|
@ -10,5 +10,19 @@ if (WIN32)
|
||||||
target_link_libraries(${TARGET_NAME} ws2_32.lib)
|
target_link_libraries(${TARGET_NAME} ws2_32.lib)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
# 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
|
||||||
|
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${OPENSSL_LIBRARIES}")
|
||||||
|
|
||||||
# call macro to link our dependencies and bubble them up via a property on our target
|
# call macro to link our dependencies and bubble them up via a property on our target
|
||||||
link_shared_dependencies()
|
link_shared_dependencies()
|
|
@ -19,9 +19,11 @@
|
||||||
#include <QtCore/QUrlQuery>
|
#include <QtCore/QUrlQuery>
|
||||||
#include <QtNetwork/QHttpMultiPart>
|
#include <QtNetwork/QHttpMultiPart>
|
||||||
#include <QtNetwork/QNetworkRequest>
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
#include <qthread.h>
|
||||||
|
|
||||||
#include "NodeList.h"
|
#include "NodeList.h"
|
||||||
#include "PacketHeaders.h"
|
#include "PacketHeaders.h"
|
||||||
|
#include "RSAKeypairGenerator.h"
|
||||||
|
|
||||||
#include "AccountManager.h"
|
#include "AccountManager.h"
|
||||||
|
|
||||||
|
@ -144,6 +146,12 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
|
||||||
} else {
|
} else {
|
||||||
requestProfile();
|
requestProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we don't have a private key in settings we should generate a new keypair
|
||||||
|
if (!_accountInfo.hasPrivateKey()) {
|
||||||
|
qDebug() << "No private key present - generating a new key-pair.";
|
||||||
|
generateNewKeypair();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,14 +288,12 @@ void AccountManager::processReply() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
||||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
|
|
||||||
|
|
||||||
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
|
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
|
||||||
|
|
||||||
if (callbackParams.jsonCallbackReceiver) {
|
if (callbackParams.jsonCallbackReceiver) {
|
||||||
// invoke the right method on the callback receiver
|
// invoke the right method on the callback receiver
|
||||||
QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod),
|
QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod),
|
||||||
Q_ARG(const QJsonObject&, jsonResponse.object()));
|
Q_ARG(QNetworkReply&, *requestReply));
|
||||||
|
|
||||||
// remove the related reply-callback group from the map
|
// remove the related reply-callback group from the map
|
||||||
_pendingCallbackMap.remove(requestReply);
|
_pendingCallbackMap.remove(requestReply);
|
||||||
|
@ -295,7 +301,7 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
||||||
} else {
|
} else {
|
||||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||||
qDebug() << "Received JSON response from data-server that has no matching callback.";
|
qDebug() << "Received JSON response from data-server that has no matching callback.";
|
||||||
qDebug() << jsonResponse;
|
qDebug() << QJsonDocument::fromJson(requestReply->readAll());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,6 +325,16 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountManager::persistAccountToSettings() {
|
||||||
|
if (_shouldPersistToSettingsFile) {
|
||||||
|
// store this access token into the local settings
|
||||||
|
QSettings localSettings;
|
||||||
|
localSettings.beginGroup(ACCOUNTS_GROUP);
|
||||||
|
localSettings.setValue(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
|
||||||
|
QVariant::fromValue(_accountInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AccountManager::hasValidAccessToken() {
|
bool AccountManager::hasValidAccessToken() {
|
||||||
|
|
||||||
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
|
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
|
||||||
|
@ -408,15 +424,10 @@ void AccountManager::requestAccessTokenFinished() {
|
||||||
|
|
||||||
emit loginComplete(rootURL);
|
emit loginComplete(rootURL);
|
||||||
|
|
||||||
if (_shouldPersistToSettingsFile) {
|
persistAccountToSettings();
|
||||||
// store this access token into the local settings
|
|
||||||
QSettings localSettings;
|
|
||||||
localSettings.beginGroup(ACCOUNTS_GROUP);
|
|
||||||
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
|
|
||||||
QVariant::fromValue(_accountInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
requestProfile();
|
requestProfile();
|
||||||
|
generateNewKeypair();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: error handling
|
// TODO: error handling
|
||||||
|
@ -434,7 +445,7 @@ void AccountManager::requestProfile() {
|
||||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
|
|
||||||
QUrl profileURL = _authURL;
|
QUrl profileURL = _authURL;
|
||||||
profileURL.setPath("/api/v1/users/profile");
|
profileURL.setPath("/api/v1/user/profile");
|
||||||
|
|
||||||
QNetworkRequest profileRequest(profileURL);
|
QNetworkRequest profileRequest(profileURL);
|
||||||
profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue());
|
profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue());
|
||||||
|
@ -458,15 +469,8 @@ void AccountManager::requestProfileFinished() {
|
||||||
// the username has changed to whatever came back
|
// the username has changed to whatever came back
|
||||||
emit usernameChanged(_accountInfo.getUsername());
|
emit usernameChanged(_accountInfo.getUsername());
|
||||||
|
|
||||||
if (_shouldPersistToSettingsFile) {
|
// store the whole profile into the local settings
|
||||||
// store the whole profile into the local settings
|
persistAccountToSettings();
|
||||||
QUrl rootURL = profileReply->url();
|
|
||||||
rootURL.setPath("");
|
|
||||||
QSettings localSettings;
|
|
||||||
localSettings.beginGroup(ACCOUNTS_GROUP);
|
|
||||||
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
|
|
||||||
QVariant::fromValue(_accountInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: error handling
|
// TODO: error handling
|
||||||
|
@ -478,3 +482,57 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
|
||||||
// TODO: error handling
|
// TODO: error handling
|
||||||
qDebug() << "AccountManager requestProfileError - " << error;
|
qDebug() << "AccountManager requestProfileError - " << error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountManager::generateNewKeypair() {
|
||||||
|
// setup a new QThread to generate the keypair on, in case it takes a while
|
||||||
|
QThread* generateThread = new QThread(this);
|
||||||
|
|
||||||
|
// setup a keypair generator
|
||||||
|
RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
qDebug() << "Starting worker thread to generate 2048-bit RSA key-pair.";
|
||||||
|
generateThread->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey) {
|
||||||
|
|
||||||
|
qDebug() << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key.";
|
||||||
|
|
||||||
|
// set the private key on our data-server 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);
|
||||||
|
|
||||||
|
authenticatedRequest(PUBLIC_KEY_UPDATE_PATH, 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();
|
||||||
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ public:
|
||||||
void requestAccessToken(const QString& login, const QString& password);
|
void requestAccessToken(const QString& login, const QString& password);
|
||||||
void requestProfile();
|
void requestProfile();
|
||||||
|
|
||||||
const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; }
|
DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void requestAccessTokenFinished();
|
void requestAccessTokenFinished();
|
||||||
|
@ -91,13 +91,19 @@ signals:
|
||||||
void balanceChanged(qint64 newBalance);
|
void balanceChanged(qint64 newBalance);
|
||||||
private slots:
|
private slots:
|
||||||
void processReply();
|
void processReply();
|
||||||
|
void handleKeypairGenerationError();
|
||||||
|
void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
|
||||||
private:
|
private:
|
||||||
AccountManager();
|
AccountManager();
|
||||||
AccountManager(AccountManager const& other); // not implemented
|
AccountManager(AccountManager const& other); // not implemented
|
||||||
void operator=(AccountManager const& other); // not implemented
|
void operator=(AccountManager const& other); // not implemented
|
||||||
|
|
||||||
|
void persistAccountToSettings();
|
||||||
|
|
||||||
void passSuccessToCallback(QNetworkReply* reply);
|
void passSuccessToCallback(QNetworkReply* reply);
|
||||||
void passErrorToCallback(QNetworkReply* reply);
|
void passErrorToCallback(QNetworkReply* reply);
|
||||||
|
|
||||||
|
void generateNewKeypair();
|
||||||
|
|
||||||
Q_INVOKABLE void invokedRequest(const QString& path,
|
Q_INVOKABLE void invokedRequest(const QString& path,
|
||||||
bool requiresAuthentication,
|
bool requiresAuthentication,
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
|
#include <qjsondocument.h>
|
||||||
#include <qregexp.h>
|
#include <qregexp.h>
|
||||||
#include <qstringlist.h>
|
#include <qstringlist.h>
|
||||||
|
|
||||||
|
@ -134,8 +135,9 @@ void AddressManager::handleLookupString(const QString& lookupString) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
|
void AddressManager::handleAPIResponse(QNetworkReply& requestReply) {
|
||||||
QJsonObject dataObject = jsonObject["data"].toObject();
|
QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
|
QJsonObject dataObject = responseObject["data"].toObject();
|
||||||
|
|
||||||
const QString ADDRESS_API_DOMAIN_KEY = "domain";
|
const QString ADDRESS_API_DOMAIN_KEY = "domain";
|
||||||
const QString ADDRESS_API_ONLINE_KEY = "online";
|
const QString ADDRESS_API_ONLINE_KEY = "online";
|
||||||
|
@ -190,7 +192,7 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Received an address manager API response with no domain key. Cannot parse.";
|
qDebug() << "Received an address manager API response with no domain key. Cannot parse.";
|
||||||
qDebug() << jsonObject;
|
qDebug() << responseObject;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we've been told that this result exists but is offline, emit our signal so the application can handle
|
// we've been told that this result exists but is offline, emit our signal so the application can handle
|
||||||
|
|
|
@ -42,7 +42,7 @@ public:
|
||||||
public slots:
|
public slots:
|
||||||
void handleLookupString(const QString& lookupString);
|
void handleLookupString(const QString& lookupString);
|
||||||
|
|
||||||
void handleAPIResponse(const QJsonObject& jsonObject);
|
void handleAPIResponse(QNetworkReply& requestReply);
|
||||||
void handleAPIError(QNetworkReply& errorReply);
|
void handleAPIError(QNetworkReply& errorReply);
|
||||||
void goToUser(const QString& username);
|
void goToUser(const QString& username);
|
||||||
signals:
|
signals:
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
|
||||||
|
#include <qjsondocument.h>
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
|
|
||||||
#include "DataServerAccountInfo.h"
|
#include "DataServerAccountInfo.h"
|
||||||
|
@ -20,7 +23,9 @@ DataServerAccountInfo::DataServerAccountInfo() :
|
||||||
_discourseApiKey(),
|
_discourseApiKey(),
|
||||||
_walletID(),
|
_walletID(),
|
||||||
_balance(0),
|
_balance(0),
|
||||||
_hasBalance(false)
|
_hasBalance(false),
|
||||||
|
_privateKey(),
|
||||||
|
_usernameSignature()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +38,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
|
||||||
_walletID = otherInfo._walletID;
|
_walletID = otherInfo._walletID;
|
||||||
_balance = otherInfo._balance;
|
_balance = otherInfo._balance;
|
||||||
_hasBalance = otherInfo._hasBalance;
|
_hasBalance = otherInfo._hasBalance;
|
||||||
|
_privateKey = otherInfo._privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
||||||
|
@ -51,6 +57,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
||||||
swap(_walletID, otherInfo._walletID);
|
swap(_walletID, otherInfo._walletID);
|
||||||
swap(_balance, otherInfo._balance);
|
swap(_balance, otherInfo._balance);
|
||||||
swap(_hasBalance, otherInfo._hasBalance);
|
swap(_hasBalance, otherInfo._hasBalance);
|
||||||
|
swap(_privateKey, otherInfo._privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
||||||
|
@ -61,6 +68,9 @@ void DataServerAccountInfo::setUsername(const QString& username) {
|
||||||
if (_username != username) {
|
if (_username != username) {
|
||||||
_username = username;
|
_username = username;
|
||||||
|
|
||||||
|
// clear our username signature so it has to be re-created
|
||||||
|
_usernameSignature = QByteArray();
|
||||||
|
|
||||||
qDebug() << "Username changed to" << username;
|
qDebug() << "Username changed to" << username;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +102,8 @@ void DataServerAccountInfo::setBalance(qint64 balance) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataServerAccountInfo::setBalanceFromJSON(const QJsonObject& jsonObject) {
|
void DataServerAccountInfo::setBalanceFromJSON(QNetworkReply& requestReply) {
|
||||||
|
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
if (jsonObject["status"].toString() == "success") {
|
if (jsonObject["status"].toString() == "success") {
|
||||||
qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toDouble();
|
qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toDouble();
|
||||||
setBalance(balanceInSatoshis);
|
setBalance(balanceInSatoshis);
|
||||||
|
@ -111,12 +122,58 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject
|
||||||
setWalletID(QUuid(user["wallet_id"].toString()));
|
setWalletID(QUuid(user["wallet_id"].toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QByteArray& DataServerAccountInfo::getUsernameSignature() {
|
||||||
|
if (_usernameSignature.isEmpty()) {
|
||||||
|
if (!_privateKey.isEmpty()) {
|
||||||
|
const char* privateKeyData = _privateKey.constData();
|
||||||
|
RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL,
|
||||||
|
reinterpret_cast<const unsigned char**>(&privateKeyData),
|
||||||
|
_privateKey.size());
|
||||||
|
if (rsaPrivateKey) {
|
||||||
|
QByteArray usernameByteArray = _username.toUtf8();
|
||||||
|
_usernameSignature.resize(RSA_size(rsaPrivateKey));
|
||||||
|
|
||||||
|
int encryptReturn = RSA_private_encrypt(usernameByteArray.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(usernameByteArray.constData()),
|
||||||
|
reinterpret_cast<unsigned char*>(_usernameSignature.data()),
|
||||||
|
rsaPrivateKey, RSA_PKCS1_PADDING);
|
||||||
|
|
||||||
|
if (encryptReturn == -1) {
|
||||||
|
qDebug() << "Error encrypting username signature.";
|
||||||
|
qDebug() << "Will re-attempt on next domain-server check in.";
|
||||||
|
_usernameSignature = QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// free the private key RSA struct now that we are done with it
|
||||||
|
RSA_free(rsaPrivateKey);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Could not create RSA struct from QByteArray private key.";
|
||||||
|
qDebug() << "Will re-attempt on next domain-server check in.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "No private key present in DataServerAccountInfo. Re-log to generate new key.";
|
||||||
|
qDebug() << "Returning empty username signature.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _usernameSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) {
|
||||||
|
_privateKey = privateKey;
|
||||||
|
|
||||||
|
// clear our username signature so it has to be re-created
|
||||||
|
_usernameSignature = QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
||||||
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey << info._walletID;
|
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
|
||||||
|
<< info._walletID << info._privateKey;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
||||||
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey >> info._walletID;
|
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
|
||||||
|
>> info._walletID >> info._privateKey;
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#define hifi_DataServerAccountInfo_h
|
#define hifi_DataServerAccountInfo_h
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
|
#include <qnetworkreply.h>
|
||||||
#include <QtCore/QUuid>
|
#include <QtCore/QUuid>
|
||||||
|
|
||||||
#include "OAuthAccessToken.h"
|
#include "OAuthAccessToken.h"
|
||||||
|
@ -41,13 +42,17 @@ public:
|
||||||
|
|
||||||
const QUuid& getWalletID() const { return _walletID; }
|
const QUuid& getWalletID() const { return _walletID; }
|
||||||
void setWalletID(const QUuid& walletID);
|
void setWalletID(const QUuid& walletID);
|
||||||
|
|
||||||
|
const QByteArray& getUsernameSignature();
|
||||||
|
bool hasPrivateKey() const { return !_privateKey.isEmpty(); }
|
||||||
|
void setPrivateKey(const QByteArray& privateKey);
|
||||||
|
|
||||||
qint64 getBalance() const { return _balance; }
|
qint64 getBalance() const { return _balance; }
|
||||||
float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; }
|
float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; }
|
||||||
void setBalance(qint64 balance);
|
void setBalance(qint64 balance);
|
||||||
bool hasBalance() const { return _hasBalance; }
|
bool hasBalance() const { return _hasBalance; }
|
||||||
void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; }
|
void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; }
|
||||||
Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject);
|
Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply);
|
||||||
|
|
||||||
bool hasProfile() const;
|
bool hasProfile() const;
|
||||||
|
|
||||||
|
@ -67,6 +72,8 @@ private:
|
||||||
QUuid _walletID;
|
QUuid _walletID;
|
||||||
qint64 _balance;
|
qint64 _balance;
|
||||||
bool _hasBalance;
|
bool _hasBalance;
|
||||||
|
QByteArray _privateKey;
|
||||||
|
QByteArray _usernameSignature;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_DataServerAccountInfo_h
|
#endif // hifi_DataServerAccountInfo_h
|
||||||
|
|
|
@ -679,7 +679,6 @@ void LimitedNodeList::updateLocalSockAddr() {
|
||||||
qDebug() << "Local socket has changed from" << _localSockAddr << "to" << newSockAddr;
|
qDebug() << "Local socket has changed from" << _localSockAddr << "to" << newSockAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_localSockAddr = newSockAddr;
|
_localSockAddr = newSockAddr;
|
||||||
|
|
||||||
emit localSockAddrChanged(_localSockAddr);
|
emit localSockAddrChanged(_localSockAddr);
|
||||||
|
|
|
@ -101,6 +101,8 @@ public:
|
||||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
|
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
|
||||||
SharedNodePointer updateSocketsForNode(const QUuid& uuid,
|
SharedNodePointer updateSocketsForNode(const QUuid& uuid,
|
||||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
|
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
|
||||||
|
|
||||||
|
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
|
||||||
|
|
||||||
void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
|
void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
|
||||||
void processKillNode(const QByteArray& datagram);
|
void processKillNode(const QByteArray& datagram);
|
||||||
|
|
|
@ -302,11 +302,22 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
|
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
|
||||||
|
|
||||||
// pack our data to send to the domain-server
|
// pack our data to send to the domain-server
|
||||||
packetStream << _ownerType << _publicSockAddr << _localSockAddr << (quint8) _nodeTypesOfInterest.size();
|
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
||||||
|
|
||||||
// copy over the bytes for node types of interest, if required
|
|
||||||
foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) {
|
// if this is a connect request, and we can present a username signature, send it along
|
||||||
packetStream << nodeTypeOfInterest;
|
if (!_domainHandler.isConnected()) {
|
||||||
|
DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo();
|
||||||
|
packetStream << accountInfo.getUsername();
|
||||||
|
|
||||||
|
const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature();
|
||||||
|
|
||||||
|
if (!usernameSignature.isEmpty()) {
|
||||||
|
qDebug() << "Including username signature in domain connect request.";
|
||||||
|
packetStream << usernameSignature;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Private key not present - domain connect request cannot include username signature";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUsingDTLS) {
|
if (!isUsingDTLS) {
|
||||||
|
|
|
@ -114,7 +114,7 @@ QString nameForPacketType(PacketType type) {
|
||||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainListRequest);
|
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainListRequest);
|
||||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeRequestAssignment);
|
PACKET_TYPE_NAME_LOOKUP(PacketTypeRequestAssignment);
|
||||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeCreateAssignment);
|
PACKET_TYPE_NAME_LOOKUP(PacketTypeCreateAssignment);
|
||||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainOAuthRequest);
|
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainConnectionDenied);
|
||||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeMuteEnvironment);
|
PACKET_TYPE_NAME_LOOKUP(PacketTypeMuteEnvironment);
|
||||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeAudioStreamStats);
|
PACKET_TYPE_NAME_LOOKUP(PacketTypeAudioStreamStats);
|
||||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeDataServerConfirm);
|
PACKET_TYPE_NAME_LOOKUP(PacketTypeDataServerConfirm);
|
||||||
|
|
|
@ -38,7 +38,7 @@ enum PacketType {
|
||||||
PacketTypeDomainListRequest,
|
PacketTypeDomainListRequest,
|
||||||
PacketTypeRequestAssignment,
|
PacketTypeRequestAssignment,
|
||||||
PacketTypeCreateAssignment,
|
PacketTypeCreateAssignment,
|
||||||
PacketTypeDomainOAuthRequest,
|
PacketTypeDomainConnectionDenied,
|
||||||
PacketTypeMuteEnvironment,
|
PacketTypeMuteEnvironment,
|
||||||
PacketTypeAudioStreamStats,
|
PacketTypeAudioStreamStats,
|
||||||
PacketTypeDataServerConfirm,
|
PacketTypeDataServerConfirm,
|
||||||
|
@ -81,7 +81,7 @@ typedef char PacketVersion;
|
||||||
|
|
||||||
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
||||||
<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest
|
<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest
|
||||||
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest
|
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainConnectionDenied
|
||||||
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
|
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
|
||||||
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery
|
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery
|
||||||
<< PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack
|
<< PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack
|
||||||
|
|
91
libraries/networking/src/RSAKeypairGenerator.cpp
Normal file
91
libraries/networking/src/RSAKeypairGenerator.cpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
//
|
||||||
|
// RSAKeypairGenerator.cpp
|
||||||
|
// libraries/networking/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 2014-10-14.
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
|
#include <qdebug.h>
|
||||||
|
|
||||||
|
#include "RSAKeypairGenerator.h"
|
||||||
|
|
||||||
|
RSAKeypairGenerator::RSAKeypairGenerator(QObject* parent) :
|
||||||
|
QObject(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSAKeypairGenerator::generateKeypair() {
|
||||||
|
|
||||||
|
RSA* keyPair = RSA_new();
|
||||||
|
BIGNUM* exponent = BN_new();
|
||||||
|
|
||||||
|
const unsigned long RSA_KEY_EXPONENT = 65537;
|
||||||
|
BN_set_word(exponent, RSA_KEY_EXPONENT);
|
||||||
|
|
||||||
|
// seed the random number generator before we call RSA_generate_key_ex
|
||||||
|
srand(time(NULL));
|
||||||
|
|
||||||
|
const int RSA_KEY_BITS = 2048;
|
||||||
|
|
||||||
|
if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) {
|
||||||
|
qDebug() << "Error generating 2048-bit RSA Keypair -" << ERR_get_error();
|
||||||
|
|
||||||
|
emit errorGeneratingKeypair();
|
||||||
|
|
||||||
|
// we're going to bust out of here but first we cleanup the BIGNUM
|
||||||
|
BN_free(exponent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't need the BIGNUM anymore so clean that up
|
||||||
|
BN_free(exponent);
|
||||||
|
|
||||||
|
// grab the public key and private key from the file
|
||||||
|
unsigned char* publicKeyDER = NULL;
|
||||||
|
int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER);
|
||||||
|
|
||||||
|
unsigned char* privateKeyDER = NULL;
|
||||||
|
int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER);
|
||||||
|
|
||||||
|
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
|
||||||
|
qDebug() << "Error getting DER public or private key from RSA struct -" << ERR_get_error();
|
||||||
|
|
||||||
|
emit errorGeneratingKeypair();
|
||||||
|
|
||||||
|
// cleanup the RSA struct
|
||||||
|
RSA_free(keyPair);
|
||||||
|
|
||||||
|
// cleanup the public and private key DER data, if required
|
||||||
|
if (publicKeyLength > 0) {
|
||||||
|
delete publicKeyDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (privateKeyLength > 0) {
|
||||||
|
delete privateKeyDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have the public key and private key in memory
|
||||||
|
// we can cleanup the RSA struct before we continue on
|
||||||
|
RSA_free(keyPair);
|
||||||
|
|
||||||
|
QByteArray publicKeyArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
|
||||||
|
QByteArray privateKeyArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
|
||||||
|
|
||||||
|
// cleanup the publicKeyDER and publicKeyDER data
|
||||||
|
delete publicKeyDER;
|
||||||
|
delete privateKeyDER;
|
||||||
|
|
||||||
|
emit generatedKeypair(publicKeyArray, privateKeyArray);
|
||||||
|
}
|
28
libraries/networking/src/RSAKeypairGenerator.h
Normal file
28
libraries/networking/src/RSAKeypairGenerator.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// RSAKeypairGenerator.h
|
||||||
|
// libraries/networking/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 2014-10-14.
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_RSAKeypairGenerator_h
|
||||||
|
#define hifi_RSAKeypairGenerator_h
|
||||||
|
|
||||||
|
#include <qobject.h>
|
||||||
|
|
||||||
|
class RSAKeypairGenerator : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
RSAKeypairGenerator(QObject* parent = 0);
|
||||||
|
public slots:
|
||||||
|
void generateKeypair();
|
||||||
|
signals:
|
||||||
|
void errorGeneratingKeypair();
|
||||||
|
void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_RSAKeypairGenerator_h
|
|
@ -69,7 +69,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
|
||||||
multipart);
|
multipart);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserActivityLogger::requestFinished(const QJsonObject& object) {
|
void UserActivityLogger::requestFinished(QNetworkReply& requestReply) {
|
||||||
// qDebug() << object;
|
// qDebug() << object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ public slots:
|
||||||
void wentTo(QString destinationType, QString destinationName);
|
void wentTo(QString destinationType, QString destinationName);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void requestFinished(const QJsonObject& object);
|
void requestFinished(QNetworkReply& requestReply);
|
||||||
void requestError(QNetworkReply& errorReply);
|
void requestError(QNetworkReply& errorReply);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Reference in a new issue