diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index e7d86c824e..ca0f222e0c 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -50,9 +50,9 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000; const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; -static const QStringList BAKEABLE_MODEL_EXTENSIONS = {"fbx"}; +static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" }; static QStringList BAKEABLE_TEXTURE_EXTENSIONS; -static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {"js"}; +static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {}; static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx"; static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx"; static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js"; diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index fa9c73b12d..995a5bad27 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -41,8 +41,15 @@ EntityServer::EntityServer(ReceivedMessage& message) : DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, PacketType::ChallengeOwnership }, - this, "handleEntityPacket"); + packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, + PacketType::EntityEdit, + PacketType::EntityErase, + PacketType::EntityPhysics, + PacketType::ChallengeOwnership, + PacketType::ChallengeOwnershipRequest, + PacketType::ChallengeOwnershipReply }, + this, + "handleEntityPacket"); connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); _dynamicDomainVerificationTimer.setSingleShot(true); @@ -459,7 +466,7 @@ void EntityServer::startDynamicDomainVerification() { EntityItemPointer entity = tree->findEntityByEntityItemID(i.value()); if (entity) { - if (!entity->verifyStaticCertificateProperties()) { + if (!entity->getProperties().verifyStaticCertificateProperties()) { qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed" << "static certificate verification."; // Delete the entity if it doesn't pass static certificate verification diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 3f835678ac..bce6e7fe44 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -96,6 +96,14 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer _myServer->getOctree()->withWriteLock([&] { _myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode); }); + } else if (packetType == PacketType::ChallengeOwnershipRequest) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode); + }); + } else if (packetType == PacketType::ChallengeOwnershipReply) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode); + }); } else if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); _receivedPacketCount++; diff --git a/cmake/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake index 338dee7bc8..0619c4d587 100644 --- a/cmake/modules/FindOpenSSL.cmake +++ b/cmake/modules/FindOpenSSL.cmake @@ -60,7 +60,7 @@ if (WIN32 AND NOT CYGWIN) select_library_configurations(LIB_EAY) select_library_configurations(SSL_EAY) set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY}) - find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS}) + find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH) endif() else() diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index a5dd522a53..66159209ea 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -146,187 +146,256 @@ function sendUpdatePlaceRequest(id, path, domainID, clearDomainID, onSuccess, on }); } +var pendingDomainRequest = null; +function getDomainFromAPI(callback) { + if (pendingDomainRequest !== null) { + pendingDomainRequest.success(callback); + pendingDomainRequest.error(function() { callback({ status: 'fail' }) }); + return pendingDomainRequest; + } + + if (callback === undefined) { + callback = function() {}; + } + + var domainID = Settings.data.values.metaverse.id; + if (domainID === null || domainID === undefined || domainID === '') { + callback({ status: 'fail' }); + return null; + } + + pendingDomainRequest = $.ajax({ + url: "/api/domains/" + domainID, + dataType: 'json', + success: function(data) { + pendingDomainRequest = null; + + if (data.status === 'success') { + DomainInfo = data.domain; + } else { + DomainInfo = null; + } + callback(data); + }, + error: function() { + pendingDomainRequest = null; + + DomainInfo = null; + callback({ status: 'fail' }); + } + }); + + return pendingDomainRequest; +} + function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) { if (accessToken) { var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG); - $.ajax("/api/places", { - dataType: 'json', - jsonp: false, - success: function(data) { - if (data.status == 'success') { - var modal_buttons = { - cancel: { - label: Strings.ADD_PLACE_CANCEL_BUTTON, - className: 'add-place-cancel-button btn-default' - } - }; - - var dialog; - var modal_body; - - if (data.data.places.length) { - var places_by_id = {}; - - modal_body = $('
'); - - modal_body.append($("

Choose a place name that you own or register a new place name

")); - - var currentDomainIDType = getCurrentDomainIDType(); - if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) { - var warning = "
"; - warning += "If you choose a place name it will replace your current temporary place name."; - warning += "
"; - modal_body.append(warning); - } - - // setup a select box for the returned places - modal_body.append($("")); - place_select = $(""); - _.each(data.data.places, function(place) { - places_by_id[place.id] = place; - place_select.append(""); - }) - modal_body.append(place_select); - modal_body.append($("")); - - if (forcePathTo === undefined || forcePathTo === null) { - var path = "
"; - path += ""; - path += ""; - path += "
"; - modal_body.append($(path)); - } - - var place_select = modal_body.find("#place-name-select") - place_select.change(function(ev) { - var warning = modal_body.find("#place-name-warning"); - var place = places_by_id[$(this).val()]; - if (place === undefined || place.pointee === null) { - warning.hide(); - } else { - warning.show(); + function loadPlaces() { + $.ajax("/api/places", { + dataType: 'json', + jsonp: false, + success: function(data) { + if (data.status == 'success') { + var modal_buttons = { + cancel: { + label: Strings.ADD_PLACE_CANCEL_BUTTON, + className: 'add-place-cancel-button btn-default' } - }); - place_select.trigger('change'); + }; - modal_buttons["success"] = { - label: Strings.ADD_PLACE_CONFIRM_BUTTON, - className: 'add-place-confirm-button btn btn-primary', - callback: function() { - var placeID = $('#place-name-select').val(); - // set the place ID on the form - $(Settings.place_ID_SELECTOR).val(placeID).change(); + var dialog; + var modal_body; - if (forcePathTo === undefined || forcePathTo === null) { - var placePath = $('#place-path-input').val(); + if (data.data.places.length) { + var places_by_id = {}; + + modal_body = $('
'); + + modal_body.append($("

Choose a place name that you own or register a new place name

")); + + var currentDomainIDType = getCurrentDomainIDType(); + if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) { + var warning = "
"; + warning += "If you choose a place name it will replace your current temporary place name."; + warning += "
"; + modal_body.append(warning); + } + + // setup a select box for the returned places + modal_body.append($("")); + place_select = $(""); + _.each(data.data.places, function(place) { + places_by_id[place.id] = place; + place_select.append(""); + }) + modal_body.append(place_select); + modal_body.append($("")); + + if (forcePathTo === undefined || forcePathTo === null) { + var path = "
"; + path += ""; + path += ""; + path += "
"; + modal_body.append($(path)); + } + + var place_select = modal_body.find("#place-name-select") + place_select.change(function(ev) { + var warning = modal_body.find("#place-name-warning"); + var place = places_by_id[$(this).val()]; + if (place === undefined || place.pointee === null) { + warning.hide(); } else { - var placePath = forcePathTo; + warning.show(); } + }); + place_select.trigger('change'); - $('.add-place-confirm-button').attr('disabled', 'disabled'); - $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING); - $('.add-place-cancel-button').attr('disabled', 'disabled'); + modal_buttons["success"] = { + label: Strings.ADD_PLACE_CONFIRM_BUTTON, + className: 'add-place-confirm-button btn btn-primary', + callback: function() { + var placeID = $('#place-name-select').val(); + // set the place ID on the form + $(Settings.place_ID_SELECTOR).val(placeID).change(); - function finalizeSaveDomainID(domainID) { - var jsonSettings = { - metaverse: { - id: domainID + if (forcePathTo === undefined || forcePathTo === null) { + var placePath = $('#place-path-input').val(); + } else { + var placePath = forcePathTo; + } + + $('.add-place-confirm-button').attr('disabled', 'disabled'); + $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING); + $('.add-place-cancel-button').attr('disabled', 'disabled'); + + function finalizeSaveDomainID(domainID) { + var jsonSettings = { + metaverse: { + id: domainID + } + } + var dialog = showLoadingDialog("Waiting for Domain Server to restart..."); + $.ajax('/settings.json', { + data: JSON.stringify(jsonSettings), + contentType: 'application/json', + type: 'POST' + }).done(function(data) { + if (data.status == "success") { + waitForDomainServerRestart(function() { + dialog.modal('hide'); + if (onSuccessfullyAdded) { + onSuccessfullyAdded(places_by_id[placeID].name, domainID); + } + }); + } else { + bootbox.alert("Failed to add place"); + } + }).fail(function() { + bootbox.alert("Failed to add place"); + }); + } + + // If domainID is not specified, the current domain id will be used. + function finishSettingUpPlace(domainID) { + sendUpdatePlaceRequest( + placeID, + placePath, + domainID, + false, + function(data) { + dialog.modal('hide') + if (domainID) { + $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); + finalizeSaveDomainID(domainID); + } else { + if (onSuccessfullyAdded) { + onSuccessfullyAdded(places_by_id[placeID].name); + } + } + }, + function(data) { + $('.add-place-confirm-button').removeAttr('disabled'); + $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); + $('.add-place-cancel-button').removeAttr('disabled'); + bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); + } + ); + } + + function maybeCreateNewDomainID() { + console.log("Maybe creating domain id", currentDomainIDType) + if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) { + finishSettingUpPlace(); + } else { + sendCreateDomainRequest(function(domainID) { + console.log("Created domain", domainID); + finishSettingUpPlace(domainID); + }, function() { + $('.add-place-confirm-button').removeAttr('disabled'); + $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); + $('.add-place-cancel-button').removeAttr('disabled'); + bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); + bootbox.alert("FAIL"); + }); } } - var dialog = showLoadingDialog("Waiting for Domain Server to restart..."); - $.ajax('/settings.json', { - data: JSON.stringify(jsonSettings), - contentType: 'application/json', - type: 'POST' - }).done(function(data) { - if (data.status == "success") { - waitForDomainServerRestart(function() { - dialog.modal('hide'); - if (onSuccessfullyAdded) { - onSuccessfullyAdded(places_by_id[placeID].name, domainID); - } - }); - } else { - bootbox.alert("Failed to add place"); - } - }).fail(function() { - bootbox.alert("Failed to add place"); - }); - } - function finishSettingUpPlace(domainID) { - sendUpdatePlaceRequest( - placeID, - placePath, - domainID, - false, - function(data) { - $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); - dialog.modal('hide') - if (domainID) { - finalizeSaveDomainID(domainID); - } else { - if (onSuccessfullyAdded) { - onSuccessfullyAdded(places_by_id[placeID].name); - } - } - }, - function(data) { - $('.add-place-confirm-button').removeAttr('disabled'); - $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); - $('.add-place-cancel-button').removeAttr('disabled'); - bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); - } - ); - } + maybeCreateNewDomainID(); - if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) { - finishSettingUpPlace(); - } else { - sendCreateDomainRequest(function(domainID) { - console.log("Created domain", domainID); - finishSettingUpPlace(domainID); - }, function() { - $('.add-place-confirm-button').removeAttr('disabled'); - $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); - $('.add-place-cancel-button').removeAttr('disabled'); - bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); - bootbox.alert("FAIL"); - }); + return false; } - - return false; } + } else { + modal_buttons["success"] = { + label: Strings.ADD_PLACE_NO_PLACES_BUTTON, + callback: function() { + window.open(URLs.METAVERSE_URL + "/user/places", '_blank'); + } + } + modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE; } + + dialog = bootbox.dialog({ + title: Strings.ADD_PLACE_TITLE, + message: modal_body, + closeButton: false, + buttons: modal_buttons + }); } else { - modal_buttons["success"] = { - label: Strings.ADD_PLACE_NO_PLACES_BUTTON, - callback: function() { - window.open(URLs.METAVERSE_URL + "/user/places", '_blank'); - } - } - modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE; + bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); } - - dialog = bootbox.dialog({ - title: Strings.ADD_PLACE_TITLE, - message: modal_body, - closeButton: false, - buttons: modal_buttons - }); - } else { + }, + error: function() { bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); + }, + complete: function() { + loadingDialog.modal('hide'); } - }, - error: function() { - bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); - }, - complete: function() { - loadingDialog.modal('hide'); - } - }); + }); + } + + var domainType = getCurrentDomainIDType(); + if (domainType !== DOMAIN_ID_TYPE_UNKNOWN) { + loadPlaces(); + } else { + getDomainFromAPI(function(data) { + if (data.status === 'success') { + var domainType = getCurrentDomainIDType(); + loadPlaces(); + } else { + loadingDialog.modal('hide'); + bootbox.confirm("We were not able to load your domain information from the Metaverse. Would you like to retry?", function(response) { + if (response) { + chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded); + } + }); + } + }) + } } else { bootbox.alert({ diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 1224b724da..7f99b367a3 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -980,20 +980,6 @@ function placeTableRowForPlaceObject(place) { return placeTableRow(place.name, placePathOrIndex, false, place.id); } -function getDomainFromAPI(callback) { - var domainID = Settings.data.values.metaverse.id; - $.ajax({ - url: "/api/domains/" + domainID, - dataType: 'json', - success: function(data) { - callback(data); - }, - error: function() { - callback({ status: 'fail' }); - } - }); -} - function reloadDomainInfo() { $('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove(); @@ -1010,7 +996,6 @@ function reloadDomainInfo() { // check if we have owner_places (for a real domain) or a name (for a temporary domain) if (data.status == "success") { $('.domain-loading-hide').show(); - DomainInfo = data.domain; if (data.domain.owner_places) { // add a table row for each of these names _.each(data.domain.owner_places, function(place){ @@ -1043,7 +1028,6 @@ function reloadDomainInfo() { appendAddButtonToPlacesTable(); } else { - DomainInfo = null; $('.domain-loading-error').show(); } }) diff --git a/domain-server/resources/web/wizard/js/wizard.js b/domain-server/resources/web/wizard/js/wizard.js index 24a82402a6..1af3f305b7 100644 --- a/domain-server/resources/web/wizard/js/wizard.js +++ b/domain-server/resources/web/wizard/js/wizard.js @@ -58,6 +58,7 @@ $(document).ready(function(){ reloadSettings(function(success) { if (success) { + getDomainFromAPI(); setupWizardSteps(); updatePlaceNameDisplay(); updateUsernameDisplay(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7b0b709bf7..c2fe3af7c1 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -830,26 +830,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { void DomainServer::updateICEServerAddresses() { if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) { _iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo))); - - // there seems to be a 5.9 bug where lookupHost never calls our slot - // so we add a single shot manual "timeout" to fire it off again if it hasn't called back yet - static const int ICE_ADDRESS_LOOKUP_TIMEOUT_MS = 5000; - QTimer::singleShot(ICE_ADDRESS_LOOKUP_TIMEOUT_MS, this, &DomainServer::timeoutICEAddressLookup); - } -} - -void DomainServer::timeoutICEAddressLookup() { - if (_iceAddressLookupID != INVALID_ICE_LOOKUP_ID) { - // we waited 5s and didn't hear back for our ICE DNS lookup - // so time that one out and kick off another - - qDebug() << "IP address lookup timed out for" << _iceServerAddr << "- retrying"; - - QHostInfo::abortHostLookup(_iceAddressLookupID); - - _iceAddressLookupID = INVALID_ICE_LOOKUP_ID; - - updateICEServerAddresses(); } } @@ -3007,9 +2987,20 @@ void DomainServer::handleKeypairChange() { void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) { // clear the ICE address lookup ID so that it can fire again - _iceAddressLookupID = -1; + _iceAddressLookupID = INVALID_ICE_LOOKUP_ID; - if (hostInfo.error() != QHostInfo::NoError) { + // enumerate the returned addresses and collect only valid IPv4 addresses + QList sanitizedAddresses = hostInfo.addresses(); + auto it = sanitizedAddresses.begin(); + while (it != sanitizedAddresses.end()) { + if (!it->isNull() && it->protocol() == QAbstractSocket::IPv4Protocol) { + ++it; + } else { + it = sanitizedAddresses.erase(it); + } + } + + if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) { qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString(); // if we don't have an ICE server to use yet, trigger a retry @@ -3022,7 +3013,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) { } else { int countBefore = _iceServerAddresses.count(); - _iceServerAddresses = hostInfo.addresses(); + _iceServerAddresses = sanitizedAddresses; if (countBefore == 0) { qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 3fc87bbd59..b45b8a4816 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -116,8 +116,6 @@ private slots: void tokenGrantFinished(); void profileRequestFinished(); - void timeoutICEAddressLookup(); - signals: void iceServerChanged(); void userConnected(); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 43262a1a81..6c50e5245d 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include //for KillAvatarReason #include #include "DomainServerNodeData.h" @@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns"; const QString SETTINGS_VIEWPOINT_KEY = "viewpoint"; -static Setting::Handle JSON_SETTING_VERSION("json-settings/version", 0.0); - -DomainServerSettingsManager::DomainServerSettingsManager() : - _descriptionArray(), - _configMap() -{ +DomainServerSettingsManager::DomainServerSettingsManager() { // load the description object from the settings description QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); descriptionFile.open(QIODevice::ReadOnly); @@ -100,12 +96,34 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer JSON_SETTING_VERSION("json-settings/version", 0.0); + if (JSON_SETTING_VERSION.isSet()) { + auto version = JSON_SETTING_VERSION.get(); + *versionVariant = version; + persistToFile(); + QFile::remove(settingsFilename()); + } + } // What settings version were we before and what are we using now? // Do we need to do any re-mapping? - double oldVersion = JSON_SETTING_VERSION.get(); + double oldVersion = versionVariant->toDouble(); if (oldVersion != _descriptionVersion) { const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; @@ -137,12 +155,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true); *restrictedAccess = QVariant(true); - - // write the new settings to the json file - persistToFile(); - - // reload the master and user config so that the merged config is right - _configMap.loadMasterAndUserConfig(_argumentList); } } @@ -172,12 +184,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList *entityServerVariant = entityServerMap; } - - // write the new settings to the json file - persistToFile(); - - // reload the master and user config so that the merged config is right - _configMap.loadMasterAndUserConfig(_argumentList); } } @@ -195,12 +201,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings."; *passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); - - // write the new settings to file - persistToFile(); - - // reload the master and user config so the merged config is correct - _configMap.loadMasterAndUserConfig(_argumentList); } } @@ -283,19 +283,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList packPermissions(); } - if (oldVersion < 1.7) { - // This was prior to the removal of the master config file - // So we write the merged config to the user config file, and stop reading from the user config file - - qDebug() << "Migrating merged config to user config file. The master config file is deprecated."; - - // replace the user config by the merged config - _configMap.getConfig() = _configMap.getMergedConfig(); - - // persist the new config so the user config file has the correctly merged config - persistToFile(); - } - if (oldVersion < 1.8) { unpackPermissions(); // This was prior to addition of domain content replacement, add that to localhost permissions by default @@ -316,16 +303,16 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true); *wizardCompletedOnce = QVariant(true); - - // write the new settings to the json file - persistToFile(); } + + // write the current description version to our settings + *versionVariant = _descriptionVersion; + + // write the new settings to the json file + persistToFile(); } unpackPermissions(); - - // write the current description version to our settings - JSON_SETTING_VERSION.set(_descriptionVersion); } QVariantMap& DomainServerSettingsManager::getDescriptorsMap() { @@ -1289,9 +1276,6 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ } } - // re-merge the user and master configs after a settings change - _configMap.mergeMasterAndUserConfigs(); - return needRestart; } diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index fcfff02b72..775bae7969 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -432,7 +432,8 @@ Item { anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter x: 240 onClicked: { - AddressManager.goToUser(thisNameCard.userName); + console.log("Vist user button clicked."); // Remove after debugging. + AddressManager.goToUser(thisNameCard.userName, false); UserActivityLogger.palAction("go_to_user", thisNameCard.userName); } } @@ -594,7 +595,10 @@ Item { // the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now. // FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script. // Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target. + // Position avatar 2 metres from the target in the direction that target avatar was facing. MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2})); - MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1}); + + // Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch. + MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1})); } } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 89f18e5ce2..efcf6ccfcf 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -827,7 +827,7 @@ Rectangle { hoverEnabled: enabled enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab" onClicked: { - AddressManager.goToUser(model.userName); + AddressManager.goToUser(model.userName, false); UserActivityLogger.palAction("go_to_user", model.userName); } onEntered: connectionsLocationData.color = hifi.colors.blueHighlight; diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index aa1372494f..b6c29a1fad 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -33,6 +33,7 @@ Rectangle { property string dateOfPurchase: "--"; property bool isLightbox: false; property bool isMyCert: false; + property bool isCertificateInvalid: false; // Style color: hifi.colors.faintGray; Hifi.QmlCommerce { @@ -44,10 +45,11 @@ Rectangle { } else { root.marketplaceUrl = result.data.marketplace_item_url; root.isMyCert = result.isMyCert ? result.isMyCert : false; - root.itemOwner = root.isMyCert ? Account.username : - "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; - root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); - root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000); + root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username : + "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"); + root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" : + (result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run)); + root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000); root.itemName = result.data.marketplace_item_name; if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") { @@ -65,6 +67,44 @@ Rectangle { } } } + + onUpdateCertificateStatus: { + if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + // NOP + } else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT + root.isCertificateInvalid = true; + errorText.text = "Verification of this certificate timed out."; + errorText.color = hifi.colors.redHighlight; + } else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + root.isCertificateInvalid = true; + titleBarText.text = "Invalid Certificate"; + titleBarText.color = hifi.colors.redHighlight; + + popText.text = ""; + root.itemOwner = ""; + dateOfPurchaseHeader.text = ""; + root.dateOfPurchase = ""; + root.itemEdition = "Uncertified Copy"; + + errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; + errorText.color = hifi.colors.baseGray; + } else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + root.isCertificateInvalid = true; + titleBarText.text = "Invalid Certificate"; + titleBarText.color = hifi.colors.redHighlight; + + popText.text = ""; + root.itemOwner = ""; + dateOfPurchaseHeader.text = ""; + root.dateOfPurchase = ""; + root.itemEdition = "Uncertified Copy"; + + errorText.text = "The avatar who rezzed this item doesn't own it."; + errorText.color = hifi.colors.baseGray; + } else { + console.log("Unknown certificate status received from ledger signal!"); + } + } } onCertificateIdChanged: { @@ -216,7 +256,7 @@ Rectangle { } AnonymousProRegular { id: isMyCertText; - visible: root.isMyCert; + visible: root.isMyCert && !root.isCertificateInvalid; text: "(Private)"; size: 18; // Anchors diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e8b800db69..4cf8ba6d4e 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const AvatarPriority& sortData = sortedAvatars.top(); const auto& avatar = std::static_pointer_cast(sortData.avatar); + bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); + if (ignoring) { + sortedAvatars.pop(); + continue; + } + // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index ae001010f0..42eb0ffc49 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -35,6 +35,14 @@ public: void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); void certificateInfo(const QString& certificateId); + enum CertificateStatus { + CERTIFICATE_STATUS_UNKNOWN = 0, + CERTIFICATE_STATUS_VERIFICATION_SUCCESS, + CERTIFICATE_STATUS_VERIFICATION_TIMEOUT, + CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED, + CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED, + }; + signals: void buyResult(QJsonObject result); void receiveAtResult(QJsonObject result); @@ -45,6 +53,8 @@ signals: void locationUpdateResult(QJsonObject result); void certificateInfoResult(QJsonObject result); + void updateCertificateStatus(const QString& certID, uint certStatus); + public slots: void buySuccess(QNetworkReply& reply); void buyFailure(QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 803264fa9f..9d07ddb4ab 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -30,6 +30,12 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); + connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); + + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, [&]() { + setPassphrase(""); + }); } void QmlCommerce::getWalletStatus() { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index ae63133425..d4f4aa35d2 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -45,6 +45,8 @@ signals: void accountResult(QJsonObject result); void certificateInfoResult(QJsonObject result); + void updateCertificateStatus(const QString& certID, uint certStatus); + protected: Q_INVOKABLE void getWalletStatus(); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index c7c09d8b03..85632ff8f1 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -319,6 +319,7 @@ Wallet::Wallet() { auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); + packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); connect(ledger.data(), &Ledger::accountResult, this, [&]() { auto wallet = DependencyManager::get(); @@ -717,50 +718,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) { } void Wallet::handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto nodeList = DependencyManager::get(); + + bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest; unsigned char decryptedText[64]; int certIDByteArraySize; int encryptedTextByteArraySize; + int challengingNodeUUIDByteArraySize; packet->readPrimitive(&certIDByteArraySize); packet->readPrimitive(&encryptedTextByteArraySize); + if (challengeOriginatedFromClient) { + packet->readPrimitive(&challengingNodeUUIDByteArraySize); + } QByteArray certID = packet->read(certIDByteArraySize); QByteArray encryptedText = packet->read(encryptedTextByteArraySize); + QByteArray challengingNodeUUID; + if (challengeOriginatedFromClient) { + challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize); + } RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); + int decryptionStatus = -1; if (rsa) { - const int decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, + ERR_clear_error(); + decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, reinterpret_cast(encryptedText.constData()), decryptedText, rsa, RSA_PKCS1_OAEP_PADDING); RSA_free(rsa); - - if (decryptionStatus != -1) { - auto nodeList = DependencyManager::get(); - - QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast(decryptedText), decryptionStatus); - int decryptedTextByteArraySize = decryptedTextByteArray.size(); - int certIDSize = certID.size(); - // setup the packet - auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); - - decryptedTextPacket->writePrimitive(certIDSize); - decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); - decryptedTextPacket->write(certID); - decryptedTextPacket->write(decryptedTextByteArray); - - qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; - - nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); - } else { - qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; - } } else { qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed."; } + + QByteArray decryptedTextByteArray; + if (decryptionStatus > -1) { + decryptedTextByteArray = QByteArray(reinterpret_cast(decryptedText), decryptionStatus); + } + int decryptedTextByteArraySize = decryptedTextByteArray.size(); + int certIDSize = certID.size(); + // setup the packet + if (challengeOriginatedFromClient) { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, + certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), + true); + + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + decryptedTextPacket->write(challengingNodeUUID); + + qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } else { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); + + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + + qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } + + if (decryptionStatus == -1) { + qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; + long error = ERR_get_error(); + if (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "RSA error:" << error_str; + } + } } void Wallet::account() { diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 70b75a0b17..b5af529f2b 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -14,6 +14,10 @@ #include #include +#include +#include +#include +#include #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) @@ -42,6 +46,8 @@ ContextOverlayInterface::ContextOverlayInterface() { _entityPropertyFlags += PROP_DIMENSIONS; _entityPropertyFlags += PROP_REGISTRATION_POINT; _entityPropertyFlags += PROP_CERTIFICATE_ID; + _entityPropertyFlags += PROP_CLIENT_ONLY; + _entityPropertyFlags += PROP_OWNING_AVATAR_ID; auto entityScriptingInterface = DependencyManager::get().data(); connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay); @@ -66,6 +72,11 @@ ContextOverlayInterface::ContextOverlayInterface() { connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged); + + auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); + packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket"); + _challengeOwnershipTimeoutTimer.setSingleShot(true); } static const uint32_t MOUSE_HW_ID = 0; @@ -260,6 +271,89 @@ void ContextOverlayInterface::openInspectionCertificate() { auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); _hmdScriptingInterface->openTablet(); + + setLastInspectedEntity(_currentEntityWithContextOverlay); + + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + + auto nodeList = DependencyManager::get(); + + if (entityProperties.getClientOnly()) { + if (entityProperties.verifyStaticCertificateProperties()) { + SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); + + if (entityServer) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); + QJsonObject request; + request["certificate_id"] = entityProperties.getCertificateID(); + networkRequest.setUrl(requestURL); + + QNetworkReply* networkReply = NULL; + networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + + connect(networkReply, &QNetworkReply::finished, [=]() { + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); + + if (networkReply->error() == QNetworkReply::NoError) { + if (!jsonObject["invalid_reason"].toString().isEmpty()) { + qCDebug(entities) << "invalid_reason not empty"; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { + qCDebug(entities) << "'transfer_status' is 'failed'"; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { + qCDebug(entities) << "'transfer_status' is 'pending'"; + } else { + QString ownerKey = jsonObject["transfer_recipient_key"].toString(); + + QByteArray certID = entityProperties.getCertificateID().toUtf8(); + QByteArray encryptedText = DependencyManager::get()->getTree()->computeEncryptedNonce(certID, ownerKey); + QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int encryptedTextByteArraySize = encryptedText.length(); + int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + encryptedTextByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); + challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(encryptedText); + challengeOwnershipPacket->write(nodeToChallengeByteArray); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); + + // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); + return; + } else { + startChallengeOwnershipTimer(); + } + } + } else { + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << + "More info:" << networkReply->readAll(); + } + + networkReply->deleteLater(); + }); + } else { + qCWarning(context_overlay) << "Couldn't get Entity Server!"; + } + } else { + auto ledger = DependencyManager::get(); + _challengeOwnershipTimeoutTimer.stop(); + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); + qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; + } + } } } @@ -293,3 +387,39 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent()); } } + +void ContextOverlayInterface::startChallengeOwnershipTimer() { + auto ledger = DependencyManager::get(); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + + connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { + qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity; + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); + }); + + _challengeOwnershipTimeoutTimer.start(5000); +} + +void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto ledger = DependencyManager::get(); + + _challengeOwnershipTimeoutTimer.stop(); + + int certIDByteArraySize; + int decryptedTextByteArraySize; + + packet->readPrimitive(&certIDByteArraySize); + packet->readPrimitive(&decryptedTextByteArraySize); + + QString certID(packet->read(certIDByteArraySize)); + QString decryptedText(packet->read(decryptedTextByteArraySize)); + + EntityItemID id; + bool verificationSuccess = DependencyManager::get()->getTree()->verifyDecryptedNonce(certID, decryptedText, id); + + if (verificationSuccess) { + emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + } else { + emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); + } +} diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index b4d3ddc0c2..8f0a40ef8e 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -51,6 +51,7 @@ public: Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } + void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } void setEnabled(bool enabled); bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } @@ -70,10 +71,14 @@ public slots: void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event); bool contextOverlayFilterPassed(const EntityItemID& entityItemID); +private slots: + void handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode); + private: bool _verboseLogging { true }; bool _enabled { true }; - QUuid _currentEntityWithContextOverlay{}; + EntityItemID _currentEntityWithContextOverlay{}; + EntityItemID _lastInspectedEntity{}; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; @@ -87,6 +92,9 @@ private: void deletingEntity(const EntityItemID& entityItemID); SelectionToSceneHandler _selectionToSceneHandler; + + Q_INVOKABLE void startChallengeOwnershipTimer(); + QTimer _challengeOwnershipTimeoutTimer; }; #endif // hifi_ContextOverlayInterface_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7db19704b4..03380ad321 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -215,6 +215,7 @@ void RenderableModelEntityItem::updateModelBounds() { model->setScaleToFit(true, getDimensions()); model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); updateRenderItems = true; + model->scaleToFit(); } bool success; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index b87a1a994f..469e1c4b6f 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -320,11 +320,12 @@ void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { } } -void WebEntityRenderer::handlePointerEvent(PointerEvent& event) { - event.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); +void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { // Ignore mouse interaction if we're locked if (!_lastLocked && _webSurface) { - _webSurface->handlePointerEvent(event, _touchDevice); + PointerEvent webEvent = event; + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); + _webSurface->handlePointerEvent(webEvent, _touchDevice); } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 84f2a6ccb7..28c024cdbb 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -26,7 +26,7 @@ public: WebEntityRenderer(const EntityItemPointer& entity); Q_INVOKABLE void hoverLeaveEntity(const PointerEvent& event); - Q_INVOKABLE void handlePointerEvent(PointerEvent& event); + Q_INVOKABLE void handlePointerEvent(const PointerEvent& event); protected: virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index cf20179250..f6f4e48a73 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -14,10 +14,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -1575,116 +1571,6 @@ float EntityItem::getRadius() const { return 0.5f * glm::length(getDimensions()); } -// Checking Certifiable Properties -#define ADD_STRING_PROPERTY(n, N) if (!propertySet.get##N().isEmpty()) json[#n] = propertySet.get##N() -#define ADD_ENUM_PROPERTY(n, N) json[#n] = propertySet.get##N##AsString() -#define ADD_INT_PROPERTY(n, N) if (propertySet.get##N() != 0) json[#n] = (propertySet.get##N() == (quint32) -1) ? -1.0 : ((double) propertySet.get##N()) -QByteArray EntityItem::getStaticCertificateJSON() const { - // Produce a compact json of every non-default static certificate property, with the property names in alphabetical order. - // The static certificate properties include all an only those properties that cannot be changed without altering the identity - // of the entity as reviewed during the certification submission. - - QJsonObject json; - EntityItemProperties propertySet = getProperties(); // Note: neither EntityItem nor EntityitemProperties "properties" are QObject "properties"! - // It is important that this be reproducible in the same order each time. Since we also generate these on the server, we do it alphabetically - // to help maintainence in two different code bases. - if (!propertySet.getAnimation().getURL().isEmpty()) { - json["animationURL"] = propertySet.getAnimation().getURL(); - } - ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); - ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); - ADD_INT_PROPERTY(editionNumber, EditionNumber); - ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); - ADD_STRING_PROPERTY(itemArtist, ItemArtist); - ADD_STRING_PROPERTY(itemCategories, ItemCategories); - ADD_STRING_PROPERTY(itemDescription, ItemDescription); - ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense); - ADD_STRING_PROPERTY(itemName, ItemName); - ADD_INT_PROPERTY(limitedRun, LimitedRun); - ADD_STRING_PROPERTY(marketplaceID, MarketplaceID); - ADD_STRING_PROPERTY(modelURL, ModelURL); - ADD_STRING_PROPERTY(script, Script); - ADD_ENUM_PROPERTY(shapeType, ShapeType); - json["type"] = EntityTypes::getEntityTypeName(propertySet.getType()); - - return QJsonDocument(json).toJson(QJsonDocument::Compact); -} -QByteArray EntityItem::getStaticCertificateHash() const { - return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256); -} - -bool EntityItem::verifyStaticCertificateProperties() { - // True IIF a non-empty certificateID matches the static certificate json. - // I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash. - - if (getCertificateID().isEmpty()) { - return false; - } - - const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8(); - const unsigned char* marketplacePublicKey = reinterpret_cast(marketplacePublicKeyByteArray.constData()); - int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length(); - - BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength); - EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); - if (evp_key) { - RSA* rsa = EVP_PKEY_get1_RSA(evp_key); - if (rsa) { - const QByteArray digestByteArray = getStaticCertificateHash(); - const unsigned char* digest = reinterpret_cast(digestByteArray.constData()); - int digestLength = digestByteArray.length(); - - const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8()); - const unsigned char* signature = reinterpret_cast(signatureByteArray.constData()); - int signatureLength = signatureByteArray.length(); - - ERR_clear_error(); - bool answer = RSA_verify(NID_sha256, - digest, - digestLength, - signature, - signatureLength, - rsa); - long error = ERR_get_error(); - if (error != 0) { - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str - << "\nStatic Cert JSON:" << getStaticCertificateJSON() - << "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength - << "\nDigest:" << digest << "\nDigest Length:" << digestLength - << "\nSignature:" << signature << "\nSignature Length:" << signatureLength; - } - RSA_free(rsa); - if (bio) { - BIO_free(bio); - } - if (evp_key) { - EVP_PKEY_free(evp_key); - } - return answer; - } else { - if (bio) { - BIO_free(bio); - } - if (evp_key) { - EVP_PKEY_free(evp_key); - } - long error = ERR_get_error(); - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; - return false; - } - } else { - if (bio) { - BIO_free(bio); - } - long error = ERR_get_error(); - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; - return false; - } -} - void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { glm::mat4 scale = glm::scale(getDimensions()); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 79862b9bd2..76a5205960 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -328,9 +328,6 @@ public: void setEntityInstanceNumber(const quint32&); QString getCertificateID() const; void setCertificateID(const QString& value); - QByteArray getStaticCertificateJSON() const; - QByteArray getStaticCertificateHash() const; - bool verifyStaticCertificateProperties(); // TODO: get rid of users of getRadius()... float getRadius() const; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3bbd6ce8e6..f774b208c4 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -14,6 +14,15 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -2471,3 +2480,110 @@ bool EntityItemProperties::parentRelatedPropertyChanged() const { bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const { return parentRelatedPropertyChanged() || dimensionsChanged(); } + +// Checking Certifiable Properties +#define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N() +#define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString() +#define ADD_INT_PROPERTY(n, N) if (get##N() != 0) json[#n] = (get##N() == (quint32) -1) ? -1.0 : ((double) get##N()) +QByteArray EntityItemProperties::getStaticCertificateJSON() const { + // Produce a compact json of every non-default static certificate property, with the property names in alphabetical order. + // The static certificate properties include all an only those properties that cannot be changed without altering the identity + // of the entity as reviewed during the certification submission. + + QJsonObject json; + if (!getAnimation().getURL().isEmpty()) { + json["animationURL"] = getAnimation().getURL(); + } + ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); + ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); + ADD_INT_PROPERTY(editionNumber, EditionNumber); + ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); + ADD_STRING_PROPERTY(itemArtist, ItemArtist); + ADD_STRING_PROPERTY(itemCategories, ItemCategories); + ADD_STRING_PROPERTY(itemDescription, ItemDescription); + ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense); + ADD_STRING_PROPERTY(itemName, ItemName); + ADD_INT_PROPERTY(limitedRun, LimitedRun); + ADD_STRING_PROPERTY(marketplaceID, MarketplaceID); + ADD_STRING_PROPERTY(modelURL, ModelURL); + ADD_STRING_PROPERTY(script, Script); + ADD_ENUM_PROPERTY(shapeType, ShapeType); + json["type"] = EntityTypes::getEntityTypeName(getType()); + + return QJsonDocument(json).toJson(QJsonDocument::Compact); +} +QByteArray EntityItemProperties::getStaticCertificateHash() const { + return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256); +} + +bool EntityItemProperties::verifyStaticCertificateProperties() { + // True IIF a non-empty certificateID matches the static certificate json. + // I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash. + + if (getCertificateID().isEmpty()) { + return false; + } + + const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8(); + const unsigned char* marketplacePublicKey = reinterpret_cast(marketplacePublicKeyByteArray.constData()); + int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length(); + + BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength); + EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (evp_key) { + RSA* rsa = EVP_PKEY_get1_RSA(evp_key); + if (rsa) { + const QByteArray digestByteArray = getStaticCertificateHash(); + const unsigned char* digest = reinterpret_cast(digestByteArray.constData()); + int digestLength = digestByteArray.length(); + + const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8()); + const unsigned char* signature = reinterpret_cast(signatureByteArray.constData()); + int signatureLength = signatureByteArray.length(); + + ERR_clear_error(); + bool answer = RSA_verify(NID_sha256, + digest, + digestLength, + signature, + signatureLength, + rsa); + long error = ERR_get_error(); + if (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str + << "\nStatic Cert JSON:" << getStaticCertificateJSON() + << "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength + << "\nDigest:" << digest << "\nDigest Length:" << digestLength + << "\nSignature:" << signature << "\nSignature Length:" << signatureLength; + } + RSA_free(rsa); + if (bio) { + BIO_free(bio); + } + if (evp_key) { + EVP_PKEY_free(evp_key); + } + return answer; + } else { + if (bio) { + BIO_free(bio); + } + if (evp_key) { + EVP_PKEY_free(evp_key); + } + long error = ERR_get_error(); + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; + return false; + } + } else { + if (bio) { + BIO_free(bio); + } + long error = ERR_get_error(); + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; + return false; + } +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6547026e5c..a8bb063934 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -336,6 +336,10 @@ public: QByteArray getPackedStrokeColors() const; QByteArray packStrokeColors(const QVector& strokeColors) const; + QByteArray getStaticCertificateJSON() const; + QByteArray getStaticCertificateHash() const; + bool verifyStaticCertificateProperties(); + protected: QString getCollisionMaskAsString() const; void setCollisionMaskFromString(const QString& maskString); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index c83a5f60a1..998a2d4dfe 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1827,7 +1827,7 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { - result = entity->verifyStaticCertificateProperties(); + result = entity->getProperties().verifyStaticCertificateProperties(); } }); } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 9397f38cdd..333a514377 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1195,7 +1195,6 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin QWriteLocker locker(&_certNonceMapLock); _certNonceMap.insert(certID, nonce); - qCDebug(entities) << "Challenging ownership of Cert ID" << certID << "by encrypting and sending nonce" << nonce << "to owner."; return encryptedText; } else { @@ -1206,9 +1205,7 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin } } -bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce) { - - EntityItemID id; +bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id) { { QReadLocker certIdMapLocker(&_entityCertificateIDMapLock); id = _entityCertificateIDMap.value(certID); @@ -1221,19 +1218,116 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr } bool verificationSuccess = (actualNonce == decryptedNonce); - if (!verificationSuccess) { - if (!id.isNull()) { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed; deleting entity" << id - << "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce; - deleteEntity(id, true); - } + + if (verificationSuccess) { + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded."; } else { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded; keeping entity" << id; + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed." + << "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce; } return verificationSuccess; } +void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + int certIDByteArraySize; + int encryptedTextByteArraySize; + int nodeToChallengeByteArraySize; + + message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&encryptedTextByteArraySize); + message.readPrimitive(&nodeToChallengeByteArraySize); + + QByteArray certID(message.read(certIDByteArraySize)); + QByteArray encryptedText(message.read(encryptedTextByteArraySize)); + QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize)); + + sendChallengeOwnershipRequestPacket(certID, encryptedText, nodeToChallenge, sourceNode); +} + +void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + auto nodeList = DependencyManager::get(); + + int certIDByteArraySize; + int decryptedTextByteArraySize; + int challengingNodeUUIDByteArraySize; + + message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&decryptedTextByteArraySize); + message.readPrimitive(&challengingNodeUUIDByteArraySize); + + QByteArray certID(message.read(certIDByteArraySize)); + QByteArray decryptedText(message.read(decryptedTextByteArraySize)); + QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize)); + + auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, + certIDByteArraySize + decryptedText.length() + 2 * sizeof(int), + true); + challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipReplyPacket->writePrimitive(decryptedText.length()); + challengeOwnershipReplyPacket->write(certID); + challengeOwnershipReplyPacket->write(decryptedText); + + nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode))); +} + +void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { + // 1. Encrypt a nonce with the owner's public key + auto nodeList = DependencyManager::get(); + + QByteArray encryptedText = computeEncryptedNonce(certID, ownerKey); + + if (encryptedText == "") { + qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity..."; + deleteEntity(entityItemID, true); + } else { + qCDebug(entities) << "Challenging ownership of Cert ID" << certID; + // 2. Send the encrypted text to the rezzing avatar's node + QByteArray certIDByteArray = certID.toUtf8(); + int certIDByteArraySize = certIDByteArray.size(); + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, + certIDByteArraySize + encryptedText.length() + 2 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedText.length()); + challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(encryptedText); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); + + // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); + return; + } else { + startChallengeOwnershipTimer(entityItemID); + } + } +} + +void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) { + auto nodeList = DependencyManager::get(); + + // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants + // to make sure belongs to Avatar B. + QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int encryptedTextByteArraySize = encryptedText.length(); + int senderNodeUUIDSize = senderNodeUUID.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + encryptedTextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); + challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(encryptedText); + challengeOwnershipPacket->write(senderNodeUUID); + + nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge)))); +} + void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { // Start owner verification. auto nodeList = DependencyManager::get(); @@ -1279,33 +1373,11 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt } } else { // Second, challenge ownership of the PoP cert - // 1. Encrypt a nonce with the owner's public key - QByteArray encryptedText = computeEncryptedNonce(certID, jsonObject["transfer_recipient_key"].toString()); + sendChallengeOwnershipPacket(certID, + jsonObject["transfer_recipient_key"].toString(), + entityItemID, + senderNode); - if (encryptedText == "") { - qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity..."; - deleteEntity(entityItemID, true); - } else { - // 2. Send the encrypted text to the rezzing avatar's node - QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, - certIDByteArraySize + encryptedText.length() + 2 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(encryptedText.length()); - challengeOwnershipPacket->write(certIDByteArray); - challengeOwnershipPacket->write(encryptedText); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); - - // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); - return; - } else { - startChallengeOwnershipTimer(entityItemID); - } - } } } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID @@ -1329,7 +1401,12 @@ void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const emit killChallengeOwnershipTimeoutTimer(certID); - verifyDecryptedNonce(certID, decryptedText); + EntityItemID id; + if (!verifyDecryptedNonce(certID, decryptedText, id)) { + if (!id.isNull()) { + deleteEntity(id, true); + } + } } int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, @@ -1528,7 +1605,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c _totalCreates++; if (newEntity && isCertified && getIsServer()) { - if (!newEntity->verifyStaticCertificateProperties()) { + if (!properties.verifyStaticCertificateProperties()) { qCDebug(entities) << "User" << senderNode->getUUID() << "attempted to add a certified entity with ID" << entityItemID << "which failed" << "static certificate verification."; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 518cde9a59..86bfe984f6 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -93,6 +93,8 @@ public: void fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties); virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) override; + virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; + virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -273,6 +275,9 @@ public: static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; + QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); + bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id); + signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); @@ -375,8 +380,8 @@ protected: Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); private: - QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); - bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce); + void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); + void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); }; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 1a51440e91..b884dcba17 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -667,8 +667,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change."; } } - - emit locationChangeRequired(newPosition, orientationChanged, newOrientation, shouldFace); + + emit locationChangeRequired(newPosition, orientationChanged, + LookupTrigger::VisitUserFromPAL ? cancelOutRollAndPitch(newOrientation): newOrientation, + shouldFace + ); } else { qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value."; @@ -732,13 +735,14 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup return hostChanged; } -void AddressManager::goToUser(const QString& username) { +void AddressManager::goToUser(const QString& username, bool shouldMatchOrientation) { QString formattedUsername = QUrl::toPercentEncoding(username); - // for history storage handling we remember how this lookup was trigged - for a username it's always user input + // for history storage handling we remember how this lookup was triggered - for a username it's always user input QVariantMap requestParams; - requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(LookupTrigger::UserInput)); - + requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast( + shouldMatchOrientation ? LookupTrigger::UserInput : LookupTrigger::VisitUserFromPAL + )); // this is a username - pull the captured name and lookup that user's location DependencyManager::get()->sendRequest(GET_USER_LOCATION.arg(formattedUsername), AccountManagerAuth::Optional, @@ -840,8 +844,8 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // and do not but it into the back stack _forwardStack.push(currentAddress()); } else { - if (trigger == LookupTrigger::UserInput) { - // anyime the user has manually looked up an address we know we should clear the forward stack + if (trigger == LookupTrigger::UserInput || trigger == LookupTrigger::VisitUserFromPAL) { + // anyime the user has actively triggered an address we know we should clear the forward stack _forwardStack.clear(); emit goForwardPossible(false); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 366fc5dfab..2f3d896509 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -54,7 +54,8 @@ public: DomainPathResponse, Internal, AttemptedRefresh, - Suggestions + Suggestions, + VisitUserFromPAL }; bool isConnected(); @@ -93,7 +94,7 @@ public slots: void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); } - void goToUser(const QString& username); + void goToUser(const QString& username, bool shouldMatchOrientation = true); void refreshPreviousLookup(); diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index b582198139..3c753f0434 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -34,7 +34,7 @@ public: HifiSockAddr(const sockaddr* sockaddr); bool isNull() const { return _address.isNull() && _port == 0; } - void clear() { _address = QHostAddress::Null; _port = 0;} + void clear() { _address.clear(); _port = 0;} HifiSockAddr& operator=(const HifiSockAddr& rhsSockAddr); void swap(HifiSockAddr& otherSockAddr); diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 0bb0cee5d2..0c210e4360 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -16,8 +16,8 @@ namespace NetworkingConstants { // If you want to use STAGING instead of STABLE, - // don't forget to ALSO change the Domain Server Metaverse Server URL, which is at the top of: - // \domain-server\resources\web\settings\js\settings.js + // don't forget to ALSO change the Domain Server Metaverse Server URL inside of: + // \domain-server\resources\web\js\shared.js const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com"); const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com"); const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e00b4139c3..21b4ae8878 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -124,6 +124,8 @@ public: OctreeFileReplacementFromUrl, ChallengeOwnership, EntityScriptCallMethod, + ChallengeOwnershipRequest, + ChallengeOwnershipReply, NUM_PACKET_TYPE }; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 2761dffb1b..ec6a0e810d 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -212,6 +212,8 @@ public: virtual bool handlesEditPacketType(PacketType packetType) const { return false; } virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } + virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } + virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual bool recurseChildrenWithData() const { return true; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 8bce976b4e..3abf7e2758 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -262,6 +262,8 @@ public: Q_INVOKABLE MeshProxyList getMeshes() const; + void scaleToFit(); + public slots: void loadURLFinished(bool success); @@ -320,7 +322,6 @@ protected: virtual void initJointStates(); void setScaleInternal(const glm::vec3& scale); - void scaleToFit(); void snapToRegistrationPoint(); void computeMeshPartLocalBounds(); diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index d0fb14e104..59f660feee 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -91,32 +91,6 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL return mergedMap; } -HifiConfigVariantMap::HifiConfigVariantMap() : - _userConfigFilename(), - _masterConfig(), - _userConfig(), - _mergedConfig() -{ - -} - -void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentList) { - // check if there is a master config file - const QString MASTER_CONFIG_FILE_OPTION = "--master-config"; - - int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION); - if (masterConfigIndex != -1) { - QString masterConfigFilepath = argumentList[masterConfigIndex + 1]; - - loadMapFromJSONFile(_masterConfig, masterConfigFilepath); - } - - // load the user config - that method replace loadMasterAndUserConfig after the 1.7 migration - loadConfig(argumentList); - - mergeMasterAndUserConfigs(); -} - void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) { // load the user config const QString USER_CONFIG_FILE_OPTION = "--user-config"; @@ -172,14 +146,6 @@ void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) { loadMapFromJSONFile(_userConfig, _userConfigFilename); } -void HifiConfigVariantMap::mergeMasterAndUserConfigs() { - // the merged config is initially matched to the master config - _mergedConfig = _masterConfig; - - // then we merge in anything missing from the user config - addMissingValuesToExistingMap(_mergedConfig, _userConfig); -} - void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) { QFile configFile(filename); diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h index cb6e92df96..ee248ec3d2 100644 --- a/libraries/shared/src/HifiConfigVariantMap.h +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -21,26 +21,19 @@ class HifiConfigVariantMap { public: static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); - HifiConfigVariantMap(); - void loadMasterAndUserConfig(const QStringList& argumentList); void loadConfig(const QStringList& argumentList); const QVariant value(const QString& key) const { return _userConfig.value(key); } QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false) { return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); } - QVariantMap& getMergedConfig() { return _mergedConfig; } QVariantMap& getConfig() { return _userConfig; } - void mergeMasterAndUserConfigs(); - const QString& getUserConfigFilename() const { return _userConfigFilename; } private: QString _userConfigFilename; - QVariantMap _masterConfig; QVariantMap _userConfig; - QVariantMap _mergedConfig; void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename); void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap); diff --git a/libraries/shared/src/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp index dd301aa5aa..c6b8ac8f83 100644 --- a/libraries/shared/src/SettingHelpers.cpp +++ b/libraries/shared/src/SettingHelpers.cpp @@ -23,9 +23,15 @@ #include "SharedLogging.h" +const QSettings::Format JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile); + QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document); QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map); +QString settingsFilename() { + return QSettings().fileName(); +} + bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) { QJsonParseError jsonParseError; diff --git a/libraries/shared/src/SettingHelpers.h b/libraries/shared/src/SettingHelpers.h index 122e56957c..ad61897c65 100644 --- a/libraries/shared/src/SettingHelpers.h +++ b/libraries/shared/src/SettingHelpers.h @@ -14,12 +14,13 @@ #include +extern const QSettings::Format JSON_FORMAT; + +QString settingsFilename(); + bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map); bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map); -static const auto JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile); - void loadOldINIFile(QSettings& settings); - #endif // hifi_SettingHelpers_h diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 8deb5c0bbd..4b127baef8 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -290,11 +290,17 @@ } }); - // change pricing to GET on button hover + // change pricing to GET/BUY on button hover $('body').on('mouseenter', '#price-or-edit .price', function () { var $this = $(this); $this.data('initialHtml', $this.html()); - $this.text('GET'); + + var cost = $(this).parent().siblings().text(); + if (parseInt(cost) > 0) { + $this.text('BUY'); + } else { + $this.text('GET'); + } }); $('body').on('mouseleave', '#price-or-edit .price', function () {