mirror of
https://github.com/lubosz/overte.git
synced 2025-04-13 00:43:06 +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) {
|
||||
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else if (packetType == PacketTypeSignedTransactionPayment) {
|
||||
handleSignedTransactionPayment(packetType, receivedPacket);
|
||||
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
|
||||
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else {
|
||||
|
@ -1245,51 +1243,6 @@ QString OctreeServer::getStatusLink() {
|
|||
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() {
|
||||
// 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
|
||||
|
|
|
@ -127,8 +127,6 @@ public slots:
|
|||
void nodeKilled(SharedNodePointer node);
|
||||
void sendStatsPacket();
|
||||
|
||||
void handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject);
|
||||
|
||||
void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle
|
||||
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
@ -141,7 +139,6 @@ protected:
|
|||
QString getConfiguration();
|
||||
QString getStatusLink();
|
||||
|
||||
void handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram);
|
||||
void setupDatagramProcessingThread();
|
||||
|
||||
int _argc;
|
||||
|
|
|
@ -38,4 +38,18 @@ endif ()
|
|||
# link the shared hifi libraries
|
||||
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()
|
|
@ -59,6 +59,20 @@
|
|||
"type": "password",
|
||||
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
|
||||
"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='buttons'><strong>+/-</strong></td></tr>"
|
||||
html += "<td class='buttons'></td></tr>"
|
||||
|
||||
// populate rows in the table from existing values
|
||||
var row_num = 1
|
||||
|
@ -279,13 +279,13 @@ function makeTable(setting, setting_name, setting_value) {
|
|||
html += "<td class='" + Settings.DATA_COL_CLASS + "'>"
|
||||
|
||||
if (isArray) {
|
||||
colIsArray = _.isArray(row)
|
||||
colValue = colIsArray ? row : row[col.name]
|
||||
rowIsObject = setting.columns.length > 1
|
||||
colValue = rowIsObject ? row[col.name] : row
|
||||
html += colValue
|
||||
|
||||
// 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 + "]"
|
||||
+ (colIsArray ? "" : "." + col.name) + "' value='" + colValue + "'/>"
|
||||
+ (rowIsObject ? "." + col.name : "") + "' value='" + colValue + "'/>"
|
||||
} else if (row.hasOwnProperty(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
|
||||
//
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
|
@ -44,8 +47,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_oauthProviderURL(),
|
||||
_oauthClientID(),
|
||||
_hostname(),
|
||||
_networkReplyUUIDMap(),
|
||||
_sessionAuthenticationHash(),
|
||||
_webAuthenticationStateSet(),
|
||||
_cookieSessionHash(),
|
||||
_settingsManager()
|
||||
|
@ -80,6 +81,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
// setup automatic networking settings with data server
|
||||
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
|
||||
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer
|
||||
<< NodeType::MetavoxelServer;
|
||||
|
@ -517,8 +519,11 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
|||
|
||||
NodeType_t nodeType;
|
||||
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);
|
||||
|
||||
|
@ -551,33 +556,20 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
|||
|
||||
}
|
||||
|
||||
QString connectedUsername;
|
||||
|
||||
if (!isAssignment && !_oauthProviderURL.isEmpty() && _settingsManager.getSettingsMap().contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
||||
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
|
||||
if (_sessionAuthenticationHash.contains(packetUUID)) {
|
||||
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
|
||||
if (connectedUsername.isEmpty()) {
|
||||
// we've decided this is a user that isn't allowed in, return out
|
||||
// TODO: provide information to the user so they know why they can't connect
|
||||
return;
|
||||
} else {
|
||||
// we're letting this user in, don't return and remove their UUID from the hash
|
||||
_sessionAuthenticationHash.remove(packetUUID);
|
||||
}
|
||||
} else {
|
||||
// we don't know anything about this client
|
||||
// we have an OAuth provider, ask this interface client to auth against it
|
||||
QByteArray oauthRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainOAuthRequest);
|
||||
QDataStream oauthRequestStream(&oauthRequestByteArray, QIODevice::Append);
|
||||
QUrl authorizationURL = packetUUID.isNull() ? oauthAuthorizationURL() : oauthAuthorizationURL(packetUUID);
|
||||
oauthRequestStream << authorizationURL;
|
||||
|
||||
// send this oauth request datagram back to the client
|
||||
LimitedNodeList::getInstance()->writeUnverifiedDatagram(oauthRequestByteArray, senderSockAddr);
|
||||
|
||||
return;
|
||||
}
|
||||
QList<NodeType_t> nodeInterestList;
|
||||
QString username;
|
||||
QByteArray usernameSignature;
|
||||
|
||||
packetStream >> nodeInterestList >> username >> usernameSignature;
|
||||
|
||||
if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr)) {
|
||||
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
||||
QByteArray usernameRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied);
|
||||
|
||||
// send this oauth request datagram back to the client
|
||||
LimitedNodeList::getInstance()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
nodeData->setUsername(connectedUsername);
|
||||
|
||||
nodeData->setUsername(username);
|
||||
nodeData->setSendingSockAddr(senderSockAddr);
|
||||
|
||||
// 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() {
|
||||
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
|
||||
}
|
||||
|
@ -653,12 +739,9 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
|
|||
return authorizationURL;
|
||||
}
|
||||
|
||||
int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
|
||||
HifiSockAddr& localSockAddr, const QByteArray& packet,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
QDataStream packetStream(packet);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||
|
||||
int DomainServer::parseNodeDataFromByteArray(QDataStream& packetStream, NodeType_t& nodeType,
|
||||
HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
packetStream >> nodeType;
|
||||
packetStream >> publicSockAddr >> localSockAddr;
|
||||
|
||||
|
@ -925,7 +1008,30 @@ void DomainServer::sendPendingTransactionsToServer() {
|
|||
++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) {
|
||||
|
@ -1095,9 +1201,13 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
|
|||
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
|
||||
NodeType_t throwawayNodeType;
|
||||
HifiSockAddr nodePublicAddress, nodeLocalAddress;
|
||||
|
||||
QDataStream packetStream(receivedPacket);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
|
||||
|
||||
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
|
||||
receivedPacket, senderSockAddr);
|
||||
int numNodeInfoBytes = parseNodeDataFromByteArray(packetStream, throwawayNodeType,
|
||||
nodePublicAddress, nodeLocalAddress,
|
||||
senderSockAddr);
|
||||
|
||||
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
|
||||
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";
|
||||
|
||||
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) {
|
||||
// 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
|
||||
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));
|
||||
|
||||
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";
|
||||
|
||||
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {
|
||||
|
|
|
@ -52,6 +52,7 @@ public slots:
|
|||
/// Called by NodeList to inform us a node has been killed
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
void transactionJSONCallback(const QJsonObject& data);
|
||||
|
||||
void restart();
|
||||
|
@ -82,8 +83,17 @@ private:
|
|||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
|
||||
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||
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);
|
||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
|
||||
const NodeSet& nodeInterestList);
|
||||
|
@ -131,13 +141,11 @@ private:
|
|||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
QString _hostname;
|
||||
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
|
||||
QHash<QUuid, QString> _sessionAuthenticationHash;
|
||||
|
||||
QSet<QUuid> _webAuthenticationStateSet;
|
||||
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
|
||||
|
||||
HifiSockAddr _localSockAddr;
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
|
||||
QHash<QUuid, NetworkPeer> _connectingICEPeers;
|
||||
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_package(ZLIB REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
# perform standard include and linking for found externals
|
||||
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
||||
|
@ -169,10 +168,9 @@ endif ()
|
|||
|
||||
# include headers for interface and InterfaceConfig.
|
||||
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")
|
||||
include_directories("${OPENSSL_INCLUDE_DIR}")
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
|
|
@ -72,7 +72,6 @@
|
|||
#include "InterfaceVersion.h"
|
||||
#include "Menu.h"
|
||||
#include "ModelUploader.h"
|
||||
#include "PaymentManager.h"
|
||||
#include "Util.h"
|
||||
#include "devices/MIDIManager.h"
|
||||
#include "devices/OculusManager.h"
|
||||
|
@ -90,7 +89,6 @@
|
|||
#include "scripting/WindowScriptingInterface.h"
|
||||
|
||||
#include "ui/InfoView.h"
|
||||
#include "ui/OAuthWebViewHandler.h"
|
||||
#include "ui/Snapshot.h"
|
||||
#include "ui/Stats.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
|
@ -220,10 +218,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
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
|
||||
_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::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
|
||||
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
||||
|
||||
|
@ -3483,7 +3472,7 @@ void Application::updateLocationInServer() {
|
|||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -3526,20 +3515,18 @@ void Application::domainChanged(const QString& domainHostname) {
|
|||
|
||||
// reset the voxels renderer
|
||||
_voxels.killLocalVoxels();
|
||||
|
||||
// reset the auth URL for OAuth web view handler
|
||||
OAuthWebViewHandler::getInstance().clearLastAuthorizationURL();
|
||||
}
|
||||
|
||||
void Application::connectedToDomain(const QString& hostname) {
|
||||
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()) {
|
||||
// update our domain-server with the data-server we're logged in with
|
||||
QString domainPutJsonString = "{\"location\":{\"domain_id\":\"" + uuidStringWithoutCurlyBraces(domainID) + "\"}}";
|
||||
|
||||
QString domainPutJsonString = "{\"address\":{\"domain\":\"" + hostname + "\"}}";
|
||||
|
||||
accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation,
|
||||
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
|
||||
JSONCallbackParameters(), domainPutJsonString.toUtf8());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
#include <QtCore/QWeakPointer>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
#include "ui/OAuthWebViewHandler.h"
|
||||
|
||||
#include "DatagramProcessor.h"
|
||||
|
||||
|
@ -136,16 +136,11 @@ void DatagramProcessor::processDatagrams() {
|
|||
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
|
||||
break;
|
||||
}
|
||||
case PacketTypeDomainOAuthRequest: {
|
||||
QDataStream readStream(incomingPacket);
|
||||
readStream.skipRawData(numBytesForPacketHeader(incomingPacket));
|
||||
|
||||
QUrl authorizationURL;
|
||||
readStream >> authorizationURL;
|
||||
|
||||
QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL",
|
||||
Q_ARG(const QUrl&, authorizationURL));
|
||||
|
||||
case PacketTypeDomainConnectionDenied: {
|
||||
// output to the log so the user knows they got a denied connection request
|
||||
// and check and signal for an access token so that we can make sure they are logged in
|
||||
qDebug() << "The domain-server denied a connection request.";
|
||||
AccountManager::getInstance().checkAndSignalForAccessToken();
|
||||
break;
|
||||
}
|
||||
case PacketTypeMuteEnvironment: {
|
||||
|
|
|
@ -364,7 +364,9 @@ void ModelUploader::send() {
|
|||
_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") {
|
||||
qDebug() << "status : success";
|
||||
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) {
|
||||
_progressDialog->accept();
|
||||
}
|
||||
|
|
|
@ -40,9 +40,9 @@ public slots:
|
|||
void send();
|
||||
|
||||
private slots:
|
||||
void checkJSON(const QJsonObject& jsonResponse);
|
||||
void checkJSON(QNetworkReply& requestReply);
|
||||
void uploadUpdate(qint64 bytesSent, qint64 bytesTotal);
|
||||
void uploadSuccess(const QJsonObject& jsonResponse);
|
||||
void uploadSuccess(QNetworkReply& requestReply);
|
||||
void uploadFailed(QNetworkReply& errorReply);
|
||||
void checkS3();
|
||||
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;
|
||||
|
||||
Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) :
|
||||
_instanceId(instanceId),
|
||||
_name(name),
|
||||
_sdlGameController(sdlGameController),
|
||||
_sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)),
|
||||
_instanceId(instanceId),
|
||||
_name(name),
|
||||
_axes(QVector<float>(SDL_JoystickNumAxes(_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)
|
||||
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
|
||||
link_shared_dependencies()
|
|
@ -19,9 +19,11 @@
|
|||
#include <QtCore/QUrlQuery>
|
||||
#include <QtNetwork/QHttpMultiPart>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <qthread.h>
|
||||
|
||||
#include "NodeList.h"
|
||||
#include "PacketHeaders.h"
|
||||
#include "RSAKeypairGenerator.h"
|
||||
|
||||
#include "AccountManager.h"
|
||||
|
||||
|
@ -144,6 +146,12 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
|
|||
} else {
|
||||
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) {
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
|
||||
|
||||
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
|
||||
|
||||
if (callbackParams.jsonCallbackReceiver) {
|
||||
// invoke the right method on the callback receiver
|
||||
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
|
||||
_pendingCallbackMap.remove(requestReply);
|
||||
|
@ -295,7 +301,7 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
|||
} else {
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
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() {
|
||||
|
||||
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
|
||||
|
@ -408,15 +424,10 @@ void AccountManager::requestAccessTokenFinished() {
|
|||
|
||||
emit loginComplete(rootURL);
|
||||
|
||||
if (_shouldPersistToSettingsFile) {
|
||||
// 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));
|
||||
}
|
||||
persistAccountToSettings();
|
||||
|
||||
requestProfile();
|
||||
generateNewKeypair();
|
||||
}
|
||||
} else {
|
||||
// TODO: error handling
|
||||
|
@ -434,7 +445,7 @@ void AccountManager::requestProfile() {
|
|||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QUrl profileURL = _authURL;
|
||||
profileURL.setPath("/api/v1/users/profile");
|
||||
profileURL.setPath("/api/v1/user/profile");
|
||||
|
||||
QNetworkRequest profileRequest(profileURL);
|
||||
profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue());
|
||||
|
@ -458,15 +469,8 @@ void AccountManager::requestProfileFinished() {
|
|||
// the username has changed to whatever came back
|
||||
emit usernameChanged(_accountInfo.getUsername());
|
||||
|
||||
if (_shouldPersistToSettingsFile) {
|
||||
// store the whole profile into the local settings
|
||||
QUrl rootURL = profileReply->url();
|
||||
rootURL.setPath("");
|
||||
QSettings localSettings;
|
||||
localSettings.beginGroup(ACCOUNTS_GROUP);
|
||||
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
|
||||
QVariant::fromValue(_accountInfo));
|
||||
}
|
||||
// store the whole profile into the local settings
|
||||
persistAccountToSettings();
|
||||
|
||||
} else {
|
||||
// TODO: error handling
|
||||
|
@ -478,3 +482,57 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
|
|||
// TODO: error handling
|
||||
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 requestProfile();
|
||||
|
||||
const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; }
|
||||
DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
|
||||
|
||||
public slots:
|
||||
void requestAccessTokenFinished();
|
||||
|
@ -91,13 +91,19 @@ signals:
|
|||
void balanceChanged(qint64 newBalance);
|
||||
private slots:
|
||||
void processReply();
|
||||
void handleKeypairGenerationError();
|
||||
void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
|
||||
private:
|
||||
AccountManager();
|
||||
AccountManager(AccountManager const& other); // not implemented
|
||||
void operator=(AccountManager const& other); // not implemented
|
||||
|
||||
void persistAccountToSettings();
|
||||
|
||||
void passSuccessToCallback(QNetworkReply* reply);
|
||||
void passErrorToCallback(QNetworkReply* reply);
|
||||
|
||||
void generateNewKeypair();
|
||||
|
||||
Q_INVOKABLE void invokedRequest(const QString& path,
|
||||
bool requiresAuthentication,
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qregexp.h>
|
||||
#include <qstringlist.h>
|
||||
|
||||
|
@ -134,8 +135,9 @@ void AddressManager::handleLookupString(const QString& lookupString) {
|
|||
}
|
||||
}
|
||||
|
||||
void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
|
||||
QJsonObject dataObject = jsonObject["data"].toObject();
|
||||
void AddressManager::handleAPIResponse(QNetworkReply& requestReply) {
|
||||
QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
QJsonObject dataObject = responseObject["data"].toObject();
|
||||
|
||||
const QString ADDRESS_API_DOMAIN_KEY = "domain";
|
||||
const QString ADDRESS_API_ONLINE_KEY = "online";
|
||||
|
@ -190,7 +192,7 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
|
|||
|
||||
} else {
|
||||
qDebug() << "Received an address manager API response with no domain key. Cannot parse.";
|
||||
qDebug() << jsonObject;
|
||||
qDebug() << responseObject;
|
||||
}
|
||||
} else {
|
||||
// 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:
|
||||
void handleLookupString(const QString& lookupString);
|
||||
|
||||
void handleAPIResponse(const QJsonObject& jsonObject);
|
||||
void handleAPIResponse(QNetworkReply& requestReply);
|
||||
void handleAPIError(QNetworkReply& errorReply);
|
||||
void goToUser(const QString& username);
|
||||
signals:
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
// 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 "DataServerAccountInfo.h"
|
||||
|
@ -20,7 +23,9 @@ DataServerAccountInfo::DataServerAccountInfo() :
|
|||
_discourseApiKey(),
|
||||
_walletID(),
|
||||
_balance(0),
|
||||
_hasBalance(false)
|
||||
_hasBalance(false),
|
||||
_privateKey(),
|
||||
_usernameSignature()
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -33,6 +38,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
|
|||
_walletID = otherInfo._walletID;
|
||||
_balance = otherInfo._balance;
|
||||
_hasBalance = otherInfo._hasBalance;
|
||||
_privateKey = otherInfo._privateKey;
|
||||
}
|
||||
|
||||
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
||||
|
@ -51,6 +57,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
|||
swap(_walletID, otherInfo._walletID);
|
||||
swap(_balance, otherInfo._balance);
|
||||
swap(_hasBalance, otherInfo._hasBalance);
|
||||
swap(_privateKey, otherInfo._privateKey);
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
||||
|
@ -61,6 +68,9 @@ void DataServerAccountInfo::setUsername(const QString& username) {
|
|||
if (_username != username) {
|
||||
_username = username;
|
||||
|
||||
// clear our username signature so it has to be re-created
|
||||
_usernameSignature = QByteArray();
|
||||
|
||||
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") {
|
||||
qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toDouble();
|
||||
setBalance(balanceInSatoshis);
|
||||
|
@ -111,12 +122,58 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject
|
|||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_DataServerAccountInfo_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <qnetworkreply.h>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include "OAuthAccessToken.h"
|
||||
|
@ -41,13 +42,17 @@ public:
|
|||
|
||||
const QUuid& getWalletID() const { return _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; }
|
||||
float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; }
|
||||
void setBalance(qint64 balance);
|
||||
bool hasBalance() const { return _hasBalance; }
|
||||
void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; }
|
||||
Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject);
|
||||
Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply);
|
||||
|
||||
bool hasProfile() const;
|
||||
|
||||
|
@ -67,6 +72,8 @@ private:
|
|||
QUuid _walletID;
|
||||
qint64 _balance;
|
||||
bool _hasBalance;
|
||||
QByteArray _privateKey;
|
||||
QByteArray _usernameSignature;
|
||||
};
|
||||
|
||||
#endif // hifi_DataServerAccountInfo_h
|
||||
|
|
|
@ -679,7 +679,6 @@ void LimitedNodeList::updateLocalSockAddr() {
|
|||
qDebug() << "Local socket has changed from" << _localSockAddr << "to" << newSockAddr;
|
||||
}
|
||||
|
||||
|
||||
_localSockAddr = newSockAddr;
|
||||
|
||||
emit localSockAddrChanged(_localSockAddr);
|
||||
|
|
|
@ -101,6 +101,8 @@ public:
|
|||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
|
||||
SharedNodePointer updateSocketsForNode(const QUuid& uuid,
|
||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
|
||||
|
||||
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
|
||||
|
||||
void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
|
||||
void processKillNode(const QByteArray& datagram);
|
||||
|
|
|
@ -302,11 +302,22 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
|
||||
|
||||
// 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) {
|
||||
packetStream << nodeTypeOfInterest;
|
||||
|
||||
// if this is a connect request, and we can present a username signature, send it along
|
||||
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) {
|
||||
|
|
|
@ -114,7 +114,7 @@ QString nameForPacketType(PacketType type) {
|
|||
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainListRequest);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeRequestAssignment);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeCreateAssignment);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainOAuthRequest);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainConnectionDenied);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeMuteEnvironment);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeAudioStreamStats);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeDataServerConfirm);
|
||||
|
|
|
@ -38,7 +38,7 @@ enum PacketType {
|
|||
PacketTypeDomainListRequest,
|
||||
PacketTypeRequestAssignment,
|
||||
PacketTypeCreateAssignment,
|
||||
PacketTypeDomainOAuthRequest,
|
||||
PacketTypeDomainConnectionDenied,
|
||||
PacketTypeMuteEnvironment,
|
||||
PacketTypeAudioStreamStats,
|
||||
PacketTypeDataServerConfirm,
|
||||
|
@ -81,7 +81,7 @@ typedef char PacketVersion;
|
|||
|
||||
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
||||
<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest
|
||||
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest
|
||||
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainConnectionDenied
|
||||
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
|
||||
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery
|
||||
<< 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);
|
||||
}
|
||||
|
||||
void UserActivityLogger::requestFinished(const QJsonObject& object) {
|
||||
void UserActivityLogger::requestFinished(QNetworkReply& requestReply) {
|
||||
// qDebug() << object;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public slots:
|
|||
void wentTo(QString destinationType, QString destinationName);
|
||||
|
||||
private slots:
|
||||
void requestFinished(const QJsonObject& object);
|
||||
void requestFinished(QNetworkReply& requestReply);
|
||||
void requestError(QNetworkReply& errorReply);
|
||||
|
||||
private:
|
||||
|
|
Loading…
Reference in a new issue