Merge pull request #11658 from zfox23/pop_dynamicTestsMaster

Commerce: Owner Verification for Certified Entities
This commit is contained in:
Zach Fox 2017-10-27 12:28:04 -07:00 committed by GitHub
commit 5ab737f66d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 730 additions and 157 deletions

View file

@ -16,6 +16,10 @@
#include <ResourceCache.h>
#include <ScriptCache.h>
#include <EntityEditFilters.h>
#include <NetworkingConstants.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <AddressManager.h>
#include "AssignmentParentFinder.h"
#include "EntityNodeData.h"
@ -29,15 +33,19 @@ const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
EntityServer::EntityServer(ReceivedMessage& message) :
OctreeServer(message),
_entitySimulation(NULL)
_entitySimulation(NULL),
_dynamicDomainVerificationTimer(this)
{
DependencyManager::set<ResourceManager>();
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<ScriptCache>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics },
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, PacketType::ChallengeOwnership },
this, "handleEntityPacket");
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
_dynamicDomainVerificationTimer.setSingleShot(true);
}
EntityServer::~EntityServer() {
@ -93,6 +101,9 @@ void EntityServer::beforeRun() {
connect(_pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities()));
const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second
_pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &EntityServer::domainSettingsRequestFailed);
}
void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) {
@ -296,6 +307,18 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME);
}
int minTime;
if (readOptionInt("dynamicDomainVerificationTimeMin", settingsSectionObject, minTime)) {
_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = minTime * 1000;
}
int maxTime;
if (readOptionInt("dynamicDomainVerificationTimeMax", settingsSectionObject, maxTime)) {
_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = maxTime * 1000;
}
startDynamicDomainVerification();
tree->setWantEditLogging(wantEditLogging);
tree->setWantTerseEditLogging(wantTerseEditLogging);
@ -410,3 +433,79 @@ QString EntityServer::serverSubclassStats() {
return statsString;
}
void EntityServer::domainSettingsRequestFailed() {
auto nodeList = DependencyManager::get<NodeList>();
qCDebug(entities) << "The EntityServer couldn't get the Domain Settings. Starting dynamic domain verification with default values...";
_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
startDynamicDomainVerification();
}
void EntityServer::startDynamicDomainVerification() {
qCDebug(entities) << "Starting Dynamic Domain Verification...";
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainId().remove(QRegExp("\\{|\\}"));
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
QHashIterator<QString, EntityItemID> i(localMap);
qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap";
while (i.hasNext()) {
i.next();
EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
if (entity) {
if (!entity->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
tree->deleteEntity(i.value(), true);
} else {
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/location");
QJsonObject request;
request["certificate_id"] = i.key();
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["domain_id"].toString() != thisDomainID) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value();
tree->deleteEntity(i.value(), true);
} else {
qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value();
}
} else {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << i.value()
<< "More info:" << jsonObject;
tree->deleteEntity(i.value(), true);
}
networkReply->deleteLater();
});
}
} else {
qCWarning(entities) << "During DDV, an entity with ID" << i.value() << "was NOT found in the Entity Tree!";
}
}
int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds";
_dynamicDomainVerificationTimer.start(nextInterval);
}

View file

@ -73,6 +73,7 @@ protected:
private slots:
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void domainSettingsRequestFailed();
private:
SimpleEntitySimulationPointer _entitySimulation;
@ -80,6 +81,13 @@ private:
QReadWriteLock _viewerSendingStatsLock;
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
static const int DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 45 * 60 * 1000; // 45m
static const int DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 60 * 60 * 1000; // 1h
int _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 45m
int _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 1h
QTimer _dynamicDomainVerificationTimer;
void startDynamicDomainVerification();
};
#endif // hifi_EntityServer_h

View file

@ -92,7 +92,11 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
// Ask our tree subclass if it can handle the incoming packet...
PacketType packetType = message->getType();
if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
if (packetType == PacketType::ChallengeOwnership) {
_myServer->getOctree()->withWriteLock([&] {
_myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode);
});
} else if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket);
_receivedPacketCount++;

View file

@ -1,5 +1,5 @@
{
"version": 1.8,
"version": 1.9,
"settings": [
{
"name": "metaverse",
@ -207,7 +207,7 @@
"name": "standard_permissions",
"type": "table",
"label": "Domain-Wide User Permissions",
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"caption": "Standard Permissions",
"can_add_new_rows": false,
"groups": [
@ -216,8 +216,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -253,6 +253,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -283,7 +297,7 @@
}
],
"non-deletable-row-key": "permissions_id",
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
"non-deletable-row-values": [ "localhost", "anonymous", "logged-in" ]
},
{
"name": "group_permissions",
@ -300,8 +314,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -362,6 +376,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -383,7 +411,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -407,8 +435,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -466,6 +494,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -487,7 +529,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -507,8 +549,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -544,6 +586,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -565,7 +621,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -585,8 +641,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -622,6 +678,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -643,7 +713,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -663,8 +733,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -700,6 +770,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -721,7 +805,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -741,8 +825,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -778,6 +862,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -799,7 +897,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -841,7 +939,7 @@
{
"name": "asset_server",
"label": "Asset Server (ATP)",
"assignment-types": [3],
"assignment-types": [ 3 ],
"settings": [
{
"name": "enabled",
@ -872,7 +970,7 @@
{
"name": "entity_script_server",
"label": "Entity Script Server (ESS)",
"assignment-types": [5],
"assignment-types": [ 5 ],
"settings": [
{
"name": "entity_pps_per_script",
@ -895,7 +993,7 @@
{
"name": "avatars",
"label": "Avatars",
"assignment-types": [1, 2],
"assignment-types": [ 1, 2 ],
"settings": [
{
"name": "min_avatar_scale",
@ -934,7 +1032,7 @@
{
"name": "audio_threading",
"label": "Audio Threading",
"assignment-types": [0],
"assignment-types": [ 0 ],
"settings": [
{
"name": "auto_threads",
@ -957,7 +1055,7 @@
{
"name": "audio_env",
"label": "Audio Environment",
"assignment-types": [0],
"assignment-types": [ 0 ],
"settings": [
{
"name": "attenuation_per_doubling_in_distance",
@ -1164,6 +1262,22 @@
"default": "3600",
"advanced": true
},
{
"name": "dynamicDomainVerificationTimeMin",
"label": "Dynamic Domain Verification Time (seconds) - Minimum",
"help": "The lower limit on the amount of time that passes before Dynamic Domain Verification on entities occurs. Units are seconds.",
"placeholder": "2700",
"default": "2700",
"advanced": true
},
{
"name": "dynamicDomainVerificationTimeMax",
"label": "Dynamic Domain Verification Time (seconds) - Maximum",
"help": "The upper limit on the amount of time that passes before Dynamic Domain Verification on entities occurs. Units are seconds.",
"placeholder": "3600",
"default": "3600",
"advanced": true
},
{
"name": "entityScriptSourceWhitelist",
"label": "Entity Scripts Allowed from:",

View file

@ -585,7 +585,7 @@ Rectangle {
// "Rez" button
HifiControlsUit.Button {
id: rezNowButton;
enabled: root.canRezCertifiedItems;
enabled: root.canRezCertifiedItems || root.isWearable;
buttonGlyph: hifi.glyphs.lightning;
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.light;
@ -634,7 +634,7 @@ Rectangle {
}
RalewaySemiBold {
id: explainRezText;
//visible: !root.isWearable;
visible: !root.isWearable;
text: '<font color="' + hifi.colors.redAccent + '"><a href="#">What does "Rez" mean?</a></font>'
// Text size
size: 16;

View file

@ -1018,6 +1018,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// connect to the packet sent signal of the _entityEditSender
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
connect(&_entityEditSender, &EntityEditPacketSender::addingEntityWithCertificate, this, &Application::addingEntityWithCertificate);
const char** constArgv = const_cast<const char**>(argv);
QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
@ -5739,6 +5740,11 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer
void Application::packetSent(quint64 length) {
}
void Application::addingEntityWithCertificate(const QString& certificateID, const QString& placeName) {
auto ledger = DependencyManager::get<Ledger>();
ledger->updateLocation(certificateID, placeName);
}
void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointer scriptEngine) {
scriptEngine->setEmitScriptUpdatesFunction([this]() {

View file

@ -428,6 +428,7 @@ private slots:
void nodeActivated(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
static void packetSent(quint64 length);
static void addingEntityWithCertificate(const QString& certificateID, const QString& placeName);
void updateDisplayMode();
void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo);

View file

@ -220,18 +220,26 @@ void Ledger::account() {
}
// The api/failResponse is called just for the side effect of logging.
void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("reset", reply); }
void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("reset", reply); }
void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("updateLocation", reply); }
void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("updateLocation", reply); }
void Ledger::updateLocation(const QString& asset_id, const QString location, const bool controlledFailure) {
auto wallet = DependencyManager::get<Wallet>();
QStringList keys = wallet->listPublicKeys();
QString key = keys[0];
QJsonObject transaction;
transaction["asset_id"] = asset_id;
transaction["location"] = location;
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure);
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
uint walletStatus = walletScriptingInterface->getWalletStatus();
if (walletStatus != (uint)wallet->WALLET_STATUS_READY) {
emit walletScriptingInterface->walletNotSetup();
qDebug(commerce) << "User attempted to update the location of a certificate, but their wallet wasn't ready. Status:" << walletStatus;
} else {
QStringList keys = wallet->listPublicKeys();
QString key = keys[0];
QJsonObject transaction;
transaction["certificate_id"] = asset_id;
transaction["place_name"] = location;
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure);
}
}
void Ledger::certificateInfoSuccess(QNetworkReply& reply) {

View file

@ -717,32 +717,52 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
}
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
QString decryptedText;
quint64 encryptedTextSize;
quint64 publicKeySize;
unsigned char decryptedText[64];
int certIDByteArraySize;
int encryptedTextByteArraySize;
if (verifyOwnerChallenge(packet->read(packet->readPrimitive(&encryptedTextSize)), packet->read(packet->readPrimitive(&publicKeySize)), decryptedText)) {
auto nodeList = DependencyManager::get<NodeList>();
// setup the packet
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, NUM_BYTES_RFC4122_UUID + decryptedText.size(), true);
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&encryptedTextByteArraySize);
// write the decrypted text to the packet
decryptedTextPacket->write(decryptedText.toUtf8());
QByteArray certID = packet->read(certIDByteArraySize);
QByteArray encryptedText = packet->read(encryptedTextByteArraySize);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text";
RSA* rsa = readKeys(keyFilePath().toStdString().c_str());
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
if (rsa) {
const int 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) << "verifyOwnerChallenge() returned false";
qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed.";
}
}
bool Wallet::verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText) {
// I have no idea how to do this yet, so here's some dummy code that may not even work.
decryptedText = QString("hello");
return true;
}
void Wallet::account() {
auto ledger = DependencyManager::get<Ledger>();
ledger->account();

View file

@ -82,8 +82,6 @@ private:
bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
bool writeBackupInstructions();
bool verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText);
void account();
};

View file

@ -18,6 +18,7 @@
#include "EntitiesLogging.h"
#include "EntityItem.h"
#include "EntityItemProperties.h"
#include <AddressManager.h>
EntityEditPacketSender::EntityEditPacketSender() {
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
@ -93,9 +94,9 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
bool success;
auto nodeList = DependencyManager::get<NodeList>();
if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) {
EntityItemProperties propertiesCopy = properties;
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
propertiesCopy.setParentID(myNodeID);
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut);
@ -110,6 +111,9 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
qCDebug(entities) << " properties:" << properties;
#endif
queueOctreeEditMessage(type, bufferOut);
if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());
}
}
}

View file

@ -43,6 +43,9 @@ public:
virtual char getMyNodeType() const override { return NodeType::EntityServer; }
virtual void adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) override;
signals:
void addingEntityWithCertificate(const QString& certificateID, const QString& placeName);
public slots:
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);

View file

@ -14,9 +14,14 @@
#include <QtCore/QObject>
#include <QtEndian>
#include <QJsonDocument>
#include <openssl/rsa.h> // see comments for DEBUG_CERT
#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 <glm/gtx/transform.hpp>
@ -41,6 +46,7 @@ int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
int EntityItem::_maxActionsDataSize = 800;
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
QString EntityItem::_marketplacePublicKey;
EntityItem::EntityItem(const EntityItemID& entityItemID) :
SpatiallyNestable(NestableType::Entity, entityItemID)
@ -1583,16 +1589,16 @@ QByteArray EntityItem::getStaticCertificateJSON() const {
// 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["animation.url"] = propertySet.getAnimation().getURL();
json["animationURL"] = propertySet.getAnimation().getURL();
}
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
ADD_INT_PROPERTY(editionNumber, EditionNumber);
ADD_INT_PROPERTY(entityInstanceNumber, EntityInstanceNumber);
ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber);
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
ADD_STRING_PROPERTY(itemLicense, ItemLicense);
ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense);
ADD_STRING_PROPERTY(itemName, ItemName);
ADD_INT_PROPERTY(limitedRun, LimitedRun);
ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
@ -1607,39 +1613,6 @@ QByteArray EntityItem::getStaticCertificateHash() const {
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
}
#ifdef DEBUG_CERT
QString EntityItem::computeCertificateID() {
// Until the marketplace generates it, compute and answer the certificateID here.
// Does not set it, as that will have to be done from script engine in order to update server, etc.
const auto hash = getStaticCertificateHash();
const auto text = reinterpret_cast<const unsigned char*>(hash.constData());
const unsigned int textLength = hash.length();
const char privateKey[] = "-----BEGIN RSA PRIVATE KEY-----\n\
MIIBOQIBAAJBALCoBiDAZOClO26tC5pd7JikBL61WIgpAqbcNnrV/TcG6LPI7Zbi\n\
MjdUixmTNvYMRZH3Wlqtl2IKG1W68y3stKECAwEAAQJABvOlwhYwIhL+gr12jm2R\n\
yPPzZ9nVEQ6kFxLlZfIT09119fd6OU1X5d4sHWfMfSIEgjwQIDS3ZU1kY3XKo87X\n\
zQIhAOPHlYa1OC7BLhaTouy68qIU2vCKLP8mt4S31/TT0UOnAiEAxor6gU6yupTQ\n\
yuyV3yHvr5LkZKBGqhjmOTmDfgtX7ncCIChGbgX3nQuHVOLhD/nTxHssPNozVGl5\n\
KxHof+LmYSYZAiB4U+yEh9SsXdq40W/3fpLMPuNq1PRezJ5jGidGMcvF+wIgUNec\n\
3Kg2U+CVZr8/bDT/vXRrsKj1zfobYuvbfVH02QY=\n\
-----END RSA PRIVATE KEY-----";
BIO* bio = BIO_new_mem_buf((void*)privateKey, sizeof(privateKey));
RSA* rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
QByteArray signature(RSA_size(rsa), 0);
unsigned int signatureLength = 0;
const int signOK = RSA_sign(NID_sha256, text, textLength, reinterpret_cast<unsigned char*>(signature.data()), &signatureLength, rsa);
BIO_free(bio);
RSA_free(rsa);
if (!signOK) {
qCWarning(entities) << "Unable to compute signature for" << getName() << getEntityItemID();
return "";
}
return signature.toBase64();
#endif
}
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.
@ -1647,27 +1620,69 @@ bool EntityItem::verifyStaticCertificateProperties() {
if (getCertificateID().isEmpty()) {
return false;
}
const auto signatureBytes = QByteArray::fromBase64(getCertificateID().toLatin1());
const auto signature = reinterpret_cast<const unsigned char*>(signatureBytes.constData());
const unsigned int signatureLength = signatureBytes.length();
const auto hash = getStaticCertificateHash();
const auto text = reinterpret_cast<const unsigned char*>(hash.constData());
const unsigned int textLength = hash.length();
const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8();
const unsigned char* marketplacePublicKey = reinterpret_cast<const unsigned char*>(marketplacePublicKeyByteArray.constData());
int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length();
// After DEBUG_CERT ends, we will get/cache this once from the marketplace when needed, and it likely won't be RSA.
const char publicKey[] = "-----BEGIN PUBLIC KEY-----\n\
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALCoBiDAZOClO26tC5pd7JikBL61WIgp\n\
AqbcNnrV/TcG6LPI7ZbiMjdUixmTNvYMRZH3Wlqtl2IKG1W68y3stKECAwEAAQ==\n\
-----END PUBLIC KEY-----";
BIO *bio = BIO_new_mem_buf((void*)publicKey, sizeof(publicKey));
BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength);
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
bool answer = RSA_verify(NID_sha256, text, textLength, signature, signatureLength, rsa);
BIO_free(bio);
RSA_free(rsa);
EVP_PKEY_free(evp_key);
return answer;
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 {
@ -2986,3 +3001,34 @@ void EntityItem::somethingChangedNotification() {
}
});
}
void EntityItem::retrieveMarketplacePublicKey() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
requestURL.setPath("/api/v1/commerce/marketplace_key");
QJsonObject request;
networkRequest.setUrl(requestURL);
QNetworkReply* networkReply = NULL;
networkReply = networkAccessManager.get(networkRequest);
connect(networkReply, &QNetworkReply::finished, [=]() {
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
if (networkReply->error() == QNetworkReply::NoError) {
if (!jsonObject["public_key"].toString().isEmpty()) {
EntityItem::_marketplacePublicKey = jsonObject["public_key"].toString();
qCWarning(entities) << "Marketplace public key has been set to" << _marketplacePublicKey;
} else {
qCWarning(entities) << "Marketplace public key is empty!";
}
} else {
qCWarning(entities) << "Call to" << networkRequest.url() << "failed! Error:" << networkReply->error();
}
networkReply->deleteLater();
});
}

View file

@ -36,9 +36,6 @@
#include "SimulationFlags.h"
#include "EntityDynamicInterface.h"
// FIXME: The server-side marketplace will soon create the certificateID. At that point, all of the DEBUG_CERT stuff will go away.
#define DEBUG_CERT 1
class EntitySimulation;
class EntityTreeElement;
class EntityTreeElementExtraEncodeData;
@ -334,9 +331,6 @@ public:
QByteArray getStaticCertificateJSON() const;
QByteArray getStaticCertificateHash() const;
bool verifyStaticCertificateProperties();
#ifdef DEBUG_CERT
QString computeCertificateID();
#endif
// TODO: get rid of users of getRadius()...
float getRadius() const;
@ -483,6 +477,9 @@ public:
ChangeHandlerId registerChangeHandler(const ChangeHandlerCallback& handler);
void deregisterChangeHandler(const ChangeHandlerId& changeHandlerId);
static QString _marketplacePublicKey;
static void retrieveMarketplacePublicKey();
protected:
QHash<ChangeHandlerId, ChangeHandlerCallback> _changeHandlers;

View file

@ -1833,18 +1833,3 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en
}
return result;
}
#ifdef DEBUG_CERT
QString EntityScriptingInterface::computeCertificateID(const QUuid& entityID) {
QString result { "" };
if (_entityTree) {
_entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
if (entity) {
result = entity->computeCertificateID();
}
});
}
return result;
}
#endif

View file

@ -424,9 +424,6 @@ public slots:
Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID);
Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID);
#ifdef DEBUG_CERT
Q_INVOKABLE QString computeCertificateID(const QUuid& entityID);
#endif
signals:
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);

View file

@ -13,6 +13,16 @@
#include <QtCore/QDateTime>
#include <QtCore/QQueue>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <NetworkingConstants.h>
#include "AccountManager.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QtScript/QScriptEngine>
#include <Extents.h>
@ -62,6 +72,8 @@ EntityTree::EntityTree(bool shouldReaverage) :
Octree(shouldReaverage)
{
resetClientEditStats();
EntityItem::retrieveMarketplacePublicKey();
}
EntityTree::~EntityTree() {
@ -228,6 +240,31 @@ bool EntityTree::handlesEditPacketType(PacketType packetType) const {
/// Adds a new entity item to the tree
void EntityTree::postAddEntity(EntityItemPointer entity) {
assert(entity);
if (getIsServer()) {
QString certID(entity->getCertificateID());
EntityItemID entityItemID = entity->getEntityItemID();
EntityItemID existingEntityItemID;
{
QWriteLocker locker(&_entityCertificateIDMapLock);
existingEntityItemID = _entityCertificateIDMap.value(certID);
if (!certID.isEmpty()) {
_entityCertificateIDMap.insert(certID, entityItemID);
qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID;
}
}
// Delete an already-existing entity from the tree if it has the same
// CertificateID as the entity we're trying to add.
if (!existingEntityItemID.isNull()) {
qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID"
<< existingEntityItemID << ". Deleting existing entity.";
deleteEntity(existingEntityItemID, true);
return;
}
}
// check to see if we need to simulate this entity..
if (_simulation) {
_simulation->addEntity(entity);
@ -643,8 +680,16 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
theEntity->die();
if (getIsServer()) {
{
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
QString certID = theEntity->getCertificateID();
if (theEntity->getEntityItemID() == _entityCertificateIDMap.value(certID)) {
_entityCertificateIDMap.remove(certID);
}
}
// set up the deleted entities ID
QWriteLocker locker(&_recentlyDeletedEntitiesLock);
QWriteLocker recentlyDeletedEntitiesLocker(&_recentlyDeletedEntitiesLock);
_recentlyDeletedEntityItemIDs.insert(deletedAt, theEntity->getEntityItemID());
} else {
// on the client side, we also remember that we deleted this entity, we don't care about the time
@ -1090,6 +1135,203 @@ bool EntityTree::isScriptInWhitelist(const QString& scriptProperty) {
return false;
}
void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) {
QTimer* _challengeOwnershipTimeoutTimer = new QTimer(this);
connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const QString& certID) {
QReadLocker locker(&_entityCertificateIDMapLock);
EntityItemID id = _entityCertificateIDMap.value(certID);
if (entityItemID == id && _challengeOwnershipTimeoutTimer) {
_challengeOwnershipTimeoutTimer->stop();
_challengeOwnershipTimeoutTimer->deleteLater();
}
});
connect(_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
qCDebug(entities) << "Ownership challenge timed out, deleting entity" << entityItemID;
deleteEntity(entityItemID, true);
if (_challengeOwnershipTimeoutTimer) {
_challengeOwnershipTimeoutTimer->stop();
_challengeOwnershipTimeoutTimer->deleteLater();
}
});
_challengeOwnershipTimeoutTimer->setSingleShot(true);
_challengeOwnershipTimeoutTimer->start(5000);
}
void EntityTree::startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) {
qCDebug(entities) << "'transfer_status' is 'pending', checking again in 90 seconds..." << entityItemID;
QTimer* transferStatusRetryTimer = new QTimer(this);
connect(transferStatusRetryTimer, &QTimer::timeout, this, [=]() {
validatePop(certID, entityItemID, senderNode, true);
});
transferStatusRetryTimer->setSingleShot(true);
transferStatusRetryTimer->start(90000);
}
QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QString ownerKey) {
QString ownerKeyWithHeaders = ("-----BEGIN RSA PUBLIC KEY-----\n" + ownerKey + "\n-----END RSA PUBLIC KEY-----");
BIO* bio = BIO_new_mem_buf((void*)ownerKeyWithHeaders.toUtf8().constData(), -1);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // NO NEWLINE
RSA* rsa = PEM_read_bio_RSAPublicKey(bio, NULL, NULL, NULL);
if (rsa) {
QUuid nonce = QUuid::createUuid();
const unsigned int textLength = nonce.toString().length();
QByteArray encryptedText(RSA_size(rsa), 0);
const int encryptStatus = RSA_public_encrypt(textLength,
reinterpret_cast<const unsigned char*>(qPrintable(nonce.toString())),
reinterpret_cast<unsigned char*>(encryptedText.data()),
rsa,
RSA_PKCS1_OAEP_PADDING);
if (bio) {
BIO_free(bio);
}
RSA_free(rsa);
if (encryptStatus == -1) {
long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "Unable to compute encrypted nonce for" << certID << "\nRSA error:" << error_str;
return "";
}
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 {
if (bio) {
BIO_free(bio);
}
return "";
}
}
bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce) {
EntityItemID id;
{
QReadLocker certIdMapLocker(&_entityCertificateIDMapLock);
id = _entityCertificateIDMap.value(certID);
}
QString actualNonce;
{
QWriteLocker locker(&_certNonceMapLock);
actualNonce = _certNonceMap.take(certID).toString();
}
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);
}
} else {
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded; keeping entity" << id;
}
return verificationSuccess;
}
void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) {
// Start owner verification.
auto nodeList = DependencyManager::get<NodeList>();
// First, asynchronously hit "proof_of_purchase_status?transaction_type=transfer" endpoint.
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"] = certID;
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, deleting entity" << entityItemID;
deleteEntity(entityItemID, true);
} else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") {
qCDebug(entities) << "'transfer_status' is 'failed', deleting entity" << entityItemID;
deleteEntity(entityItemID, true);
} else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") {
if (isRetryingValidation) {
qCDebug(entities) << "'transfer_status' is 'pending' after retry, deleting entity" << entityItemID;
deleteEntity(entityItemID, true);
} else {
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "startPendingTransferStatusTimer",
Q_ARG(const QString&, certID),
Q_ARG(const EntityItemID&, entityItemID),
Q_ARG(const SharedNodePointer&, senderNode));
return;
} else {
startPendingTransferStatusTimer(certID, entityItemID, senderNode);
}
}
} 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());
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
<< "More info:" << jsonObject;
deleteEntity(entityItemID, true);
}
networkReply->deleteLater();
});
}
void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
int certIDByteArraySize;
int decryptedTextByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&decryptedTextByteArraySize);
QString certID(message.read(certIDByteArraySize));
QString decryptedText(message.read(decryptedTextByteArraySize));
emit killChallengeOwnershipTimeoutTimer(certID);
verifyDecryptedNonce(certID, decryptedText);
}
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
const SharedNodePointer& senderNode) {
@ -1265,13 +1507,17 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
_totalUpdates++;
} else if (isAdd) {
bool failedAdd = !allowed;
bool isCertified = !properties.getCertificateID().isEmpty();
if (!allowed) {
qCDebug(entities) << "Filtered entity add. ID:" << entityItemID;
} else if (!senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
} else if (!isCertified && !senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
failedAdd = true;
qCDebug(entities) << "User without 'rez rights' [" << senderNode->getUUID()
<< "] attempted to add an entity ID:" << entityItemID;
qCDebug(entities) << "User without 'uncertified rez rights' [" << senderNode->getUUID()
<< "] attempted to add an uncertified entity with ID:" << entityItemID;
} else if (isCertified && !senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
failedAdd = true;
qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID()
<< "] attempted to add a certified entity with ID:" << entityItemID;
} else {
// this is a new entity... assign a new entityID
properties.setCreated(properties.getLastEdited());
@ -1280,6 +1526,19 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
EntityItemPointer newEntity = addEntity(entityItemID, properties);
endCreate = usecTimestampNow();
_totalCreates++;
if (newEntity && isCertified && getIsServer()) {
if (!newEntity->verifyStaticCertificateProperties()) {
qCDebug(entities) << "User" << senderNode->getUUID()
<< "attempted to add a certified entity with ID" << entityItemID << "which failed"
<< "static certificate verification.";
// Delete the entity we just added if it doesn't pass static certificate verification
deleteEntity(entityItemID, true);
} else {
validatePop(properties.getCertificateID(), entityItemID, senderNode, false);
}
}
if (newEntity) {
newEntity->markAsChangedOnServer();
notifyNewlyCreatedEntity(*newEntity, senderNode);

View file

@ -93,6 +93,7 @@ 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 processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
@ -181,6 +182,11 @@ public:
return _recentlyDeletedEntityItemIDs;
}
QHash<QString, EntityItemID> getEntityCertificateIDMap() const {
QReadLocker locker(&_entityCertificateIDMapLock);
return _entityCertificateIDMap;
}
void forgetEntitiesDeletedBefore(quint64 sinceTime);
int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode);
@ -276,6 +282,7 @@ signals:
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID);
void clearingEntities();
void killChallengeOwnershipTimeoutTimer(const QString& certID);
protected:
@ -316,6 +323,12 @@ protected:
mutable QReadWriteLock _entityMapLock;
QHash<EntityItemID, EntityItemPointer> _entityMap;
mutable QReadWriteLock _entityCertificateIDMapLock;
QHash<QString, EntityItemID> _entityCertificateIDMap;
mutable QReadWriteLock _certNonceMapLock;
QHash<QString, QUuid> _certNonceMap;
EntitySimulationPointer _simulation;
bool _wantEditLogging = false;
@ -357,6 +370,14 @@ protected:
MovingEntitiesOperator _entityMover;
QHash<EntityItemID, EntityItemPointer> _entitiesToAdd;
Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID);
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 validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation);
};
#endif // hifi_EntityTree_h

View file

@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
return static_cast<PacketVersion>(EntityVersion::StrokeColorProperty);
return static_cast<PacketVersion>(EntityVersion::HasDynamicOwnershipTests);
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::JSONFilterWithFamilyTree);

View file

@ -195,7 +195,8 @@ uint qHash(const PacketType& key, uint seed);
QDebug operator<<(QDebug debug, const PacketType& type);
enum class EntityVersion : PacketVersion {
StrokeColorProperty = 77
StrokeColorProperty = 77,
HasDynamicOwnershipTests
};
enum class EntityScriptCallMethodVersion : PacketVersion {

View file

@ -212,6 +212,7 @@ 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 processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
virtual bool recurseChildrenWithData() const { return true; }
virtual bool rootElementHasData() const { return false; }

View file

@ -415,7 +415,7 @@ var toolBar = (function () {
}
});
var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp());
var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified());
var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON);
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
activeButton = tablet.addButton({
@ -434,7 +434,7 @@ var toolBar = (function () {
tablet.fromQml.connect(fromQml);
createButton.clicked.connect(function() {
if ( ! (Entities.canRez() || Entities.canRezTmp()) ) {
if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) {
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
return;
}
@ -634,7 +634,7 @@ var toolBar = (function () {
if (active === isActive) {
return;
}
if (active && !Entities.canRez() && !Entities.canRezTmp()) {
if (active && !Entities.canRez() && !Entities.canRezTmp() && !Entities.canRezCertified() && !Entities.canRezTmpCertified()) {
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
return;
}
@ -789,7 +789,7 @@ function handleDomainChange() {
return;
}
var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp());
var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified());
createButton.editProperties({
icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON),
captionColorOverride: (hasRezPermissions ? "" : "#888888"),
@ -1491,7 +1491,7 @@ function onFileOpenChanged(filename) {
}
}
if (importURL) {
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) {
toolBar.toggle();
}
importSVO(importURL);
@ -1501,7 +1501,7 @@ function onFileOpenChanged(filename) {
function onPromptTextChanged(prompt) {
Window.promptTextChanged.disconnect(onPromptTextChanged);
if (prompt !== "") {
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) {
toolBar.toggle();
}
importSVO(prompt);
@ -1570,7 +1570,8 @@ function getPositionToCreateEntity(extra) {
}
function importSVO(importURL) {
if (!Entities.canRez() && !Entities.canRezTmp()) {
if (!Entities.canRez() && !Entities.canRezTmp() &&
!Entities.canRezCertified() && !Entities.canRezTmpCertified()) {
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
return;
}