mirror of
https://github.com/overte-org/overte.git
synced 2025-04-23 00:33:37 +02:00
Merge remote-tracking branch 'upstream/master' into ui
This commit is contained in:
commit
55ac585cd9
42 changed files with 862 additions and 508 deletions
assignment-client/src
cmake/modules
domain-server
resources/web
src
interface
resources/qml/hifi
src
libraries
entities-renderer/src
entities/src
EntityItem.cppEntityItem.hEntityItemProperties.cppEntityItemProperties.hEntityScriptingInterface.cppEntityTree.cppEntityTree.h
networking/src
octree/src
render-utils/src
shared/src
scripts/system/html/js
|
@ -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";
|
||||
|
|
|
@ -41,8 +41,15 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
|||
DependencyManager::set<ScriptCache>();
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->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
|
||||
|
|
|
@ -96,6 +96,14 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
|
|||
_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++;
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 = $('<div>');
|
||||
|
||||
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
|
||||
|
||||
var currentDomainIDType = getCurrentDomainIDType();
|
||||
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
|
||||
var warning = "<div class='domain-loading-error alert alert-warning'>";
|
||||
warning += "If you choose a place name it will replace your current temporary place name.";
|
||||
warning += "</div>";
|
||||
modal_body.append(warning);
|
||||
}
|
||||
|
||||
// setup a select box for the returned places
|
||||
modal_body.append($("<label for='place-name-select'>Places</label>"));
|
||||
place_select = $("<select id='place-name-select' class='form-control'></select>");
|
||||
_.each(data.data.places, function(place) {
|
||||
places_by_id[place.id] = place;
|
||||
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
|
||||
})
|
||||
modal_body.append(place_select);
|
||||
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
|
||||
|
||||
if (forcePathTo === undefined || forcePathTo === null) {
|
||||
var path = "<div class='form-group'>";
|
||||
path += "<label for='place-path-input' class='control-label'>Path</label>";
|
||||
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
|
||||
path += "</div>";
|
||||
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 = $('<div>');
|
||||
|
||||
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
|
||||
|
||||
var currentDomainIDType = getCurrentDomainIDType();
|
||||
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
|
||||
var warning = "<div class='domain-loading-error alert alert-warning'>";
|
||||
warning += "If you choose a place name it will replace your current temporary place name.";
|
||||
warning += "</div>";
|
||||
modal_body.append(warning);
|
||||
}
|
||||
|
||||
// setup a select box for the returned places
|
||||
modal_body.append($("<label for='place-name-select'>Places</label>"));
|
||||
place_select = $("<select id='place-name-select' class='form-control'></select>");
|
||||
_.each(data.data.places, function(place) {
|
||||
places_by_id[place.id] = place;
|
||||
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
|
||||
})
|
||||
modal_body.append(place_select);
|
||||
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
|
||||
|
||||
if (forcePathTo === undefined || forcePathTo === null) {
|
||||
var path = "<div class='form-group'>";
|
||||
path += "<label for='place-path-input' class='control-label'>Path</label>";
|
||||
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
|
||||
path += "</div>";
|
||||
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({
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
})
|
||||
|
|
|
@ -58,6 +58,7 @@ $(document).ready(function(){
|
|||
|
||||
reloadSettings(function(success) {
|
||||
if (success) {
|
||||
getDomainFromAPI();
|
||||
setupWizardSteps();
|
||||
updatePlaceNameDisplay();
|
||||
updateUsernameDisplay();
|
||||
|
|
|
@ -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<QHostAddress> 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;
|
||||
|
|
|
@ -116,8 +116,6 @@ private slots:
|
|||
void tokenGrantFinished();
|
||||
void profileRequestFinished();
|
||||
|
||||
void timeoutICEAddressLookup();
|
||||
|
||||
signals:
|
||||
void iceServerChanged();
|
||||
void userConnected();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <NLPacketList.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SettingHelpers.h>
|
||||
#include <AvatarData.h> //for KillAvatarReason
|
||||
#include <FingerprintUtils.h>
|
||||
#include "DomainServerNodeData.h"
|
||||
|
@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
|||
|
||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||
|
||||
static Setting::Handle<double> 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<Re
|
|||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||
_argumentList = argumentList;
|
||||
|
||||
// after 1.7 we no longer use the master or merged configs - this is kept in place for migration
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
_configMap.loadConfig(_argumentList);
|
||||
|
||||
static const auto VERSION_SETTINGS_KEYPATH = "version";
|
||||
QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
|
||||
|
||||
if (!versionVariant) {
|
||||
versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true);
|
||||
*versionVariant = _descriptionVersion;
|
||||
persistToFile();
|
||||
qDebug() << "No version in config file, setting to current version" << _descriptionVersion;
|
||||
}
|
||||
|
||||
{
|
||||
// Backward compatibility migration code
|
||||
// The config version used to be stored in a different file
|
||||
// This moves it to the actual config file.
|
||||
Setting::Handle<double> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
const AvatarPriority& sortData = sortedAvatars.top();
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
|
||||
|
||||
bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
|
||||
if (ignoring) {
|
||||
sortedAvatars.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// for ALL avatars...
|
||||
if (_shouldRender) {
|
||||
avatar->ensureInScene(avatar, qApp->getMain3DScene());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, [&]() {
|
||||
setPassphrase("");
|
||||
});
|
||||
}
|
||||
|
||||
void QmlCommerce::getWalletStatus() {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<Wallet>();
|
||||
|
@ -717,50 +718,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
|
|||
}
|
||||
|
||||
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
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<const unsigned char*>(encryptedText.constData()),
|
||||
decryptedText,
|
||||
rsa,
|
||||
RSA_PKCS1_OAEP_PADDING);
|
||||
|
||||
RSA_free(rsa);
|
||||
|
||||
if (decryptionStatus != -1) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(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<const char*>(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() {
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
|
||||
#include <EntityTreeRenderer.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <commerce/Ledger.h>
|
||||
|
||||
#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<EntityScriptingInterface>().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<NodeList>();
|
||||
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<TabletProxy*>(_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<NodeList>();
|
||||
|
||||
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<EntityTreeRenderer>()->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<Ledger>();
|
||||
_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<Ledger>();
|
||||
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<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
|
||||
_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<EntityTreeRenderer>()->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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ReceivedMessage> 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
|
||||
|
|
|
@ -215,6 +215,7 @@ void RenderableModelEntityItem::updateModelBounds() {
|
|||
model->setScaleToFit(true, getDimensions());
|
||||
model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
|
||||
updateRenderItems = true;
|
||||
model->scaleToFit();
|
||||
}
|
||||
|
||||
bool success;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -14,10 +14,6 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtEndian>
|
||||
#include <QJsonDocument>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
@ -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<const unsigned char*>(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<const unsigned char*>(digestByteArray.constData());
|
||||
int digestLength = digestByteArray.length();
|
||||
|
||||
const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8());
|
||||
const unsigned char* signature = reinterpret_cast<const unsigned char*>(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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -14,6 +14,15 @@
|
|||
#include <QObject>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
@ -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<const unsigned char*>(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<const unsigned char*>(digestByteArray.constData());
|
||||
int digestLength = digestByteArray.length();
|
||||
|
||||
const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8());
|
||||
const unsigned char* signature = reinterpret_cast<const unsigned char*>(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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -336,6 +336,10 @@ public:
|
|||
QByteArray getPackedStrokeColors() const;
|
||||
QByteArray packStrokeColors(const QVector<glm::vec3>& strokeColors) const;
|
||||
|
||||
QByteArray getStaticCertificateJSON() const;
|
||||
QByteArray getStaticCertificateHash() const;
|
||||
bool verifyStaticCertificateProperties();
|
||||
|
||||
protected:
|
||||
QString getCollisionMaskAsString() const;
|
||||
void setCollisionMaskFromString(const QString& maskString);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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<NodeList>();
|
||||
|
||||
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<NodeList>();
|
||||
|
||||
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<NodeList>();
|
||||
|
||||
// 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<NodeList>();
|
||||
|
@ -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.";
|
||||
|
|
|
@ -93,6 +93,8 @@ public:
|
|||
void fixupTerseEditLogging(EntityItemProperties& properties, QList<QString>& 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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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<int>(LookupTrigger::UserInput));
|
||||
|
||||
requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast<int>(
|
||||
shouldMatchOrientation ? LookupTrigger::UserInput : LookupTrigger::VisitUserFromPAL
|
||||
));
|
||||
// this is a username - pull the captured name and lookup that user's location
|
||||
DependencyManager::get<AccountManager>()->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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
// <hifi repo>\domain-server\resources\web\settings\js\settings.js
|
||||
// don't forget to ALSO change the Domain Server Metaverse Server URL inside of:
|
||||
// <hifi repo>\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;
|
||||
|
|
|
@ -124,6 +124,8 @@ public:
|
|||
OctreeFileReplacementFromUrl,
|
||||
ChallengeOwnership,
|
||||
EntityScriptCallMethod,
|
||||
ChallengeOwnershipRequest,
|
||||
ChallengeOwnershipReply,
|
||||
NUM_PACKET_TYPE
|
||||
};
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
|
||||
#include <QSettings>
|
||||
|
||||
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
|
||||
|
|
|
@ -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 () {
|
||||
|
|
Loading…
Reference in a new issue