mirror of
https://github.com/lubosz/overte.git
synced 2025-08-08 03:08:00 +02:00
Merge branch 'master' into 20957
This commit is contained in:
commit
098432152d
60 changed files with 1783 additions and 745 deletions
|
@ -286,8 +286,8 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<ReceivedMes
|
||||||
|
|
||||||
if (!senderID.isNull()) {
|
if (!senderID.isNull()) {
|
||||||
// We don't have this node yet - we should add it
|
// We don't have this node yet - we should add it
|
||||||
matchingNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode
|
matchingNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode(senderID, NodeType::Unassigned,
|
||||||
(senderID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false, false);
|
senderSockAddr, senderSockAddr);
|
||||||
|
|
||||||
auto childData = std::unique_ptr<AssignmentClientChildData>
|
auto childData = std::unique_ptr<AssignmentClientChildData>
|
||||||
{ new AssignmentClientChildData(Assignment::Type::AllTypes) };
|
{ new AssignmentClientChildData(Assignment::Type::AllTypes) };
|
||||||
|
|
|
@ -235,7 +235,7 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||||
if (senderNode->getCanRez()) {
|
if (senderNode->getCanWriteToAssetServer()) {
|
||||||
QString assetPath = message.readString();
|
QString assetPath = message.readString();
|
||||||
|
|
||||||
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
|
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
|
||||||
|
@ -251,7 +251,7 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||||
if (senderNode->getCanRez()) {
|
if (senderNode->getCanWriteToAssetServer()) {
|
||||||
int numberOfDeletedMappings { 0 };
|
int numberOfDeletedMappings { 0 };
|
||||||
message.readPrimitive(&numberOfDeletedMappings);
|
message.readPrimitive(&numberOfDeletedMappings);
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||||
if (senderNode->getCanRez()) {
|
if (senderNode->getCanWriteToAssetServer()) {
|
||||||
QString oldPath = message.readString();
|
QString oldPath = message.readString();
|
||||||
QString newPath = message.readString();
|
QString newPath = message.readString();
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, Shared
|
||||||
|
|
||||||
void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||||
|
|
||||||
if (senderNode->getCanRez()) {
|
if (senderNode->getCanWriteToAssetServer()) {
|
||||||
qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
|
qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
|
||||||
|
|
||||||
auto task = new UploadAssetTask(message, senderNode, _filesDirectory);
|
auto task = new UploadAssetTask(message, senderNode, _filesDirectory);
|
||||||
|
|
|
@ -268,6 +268,14 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
||||||
qDebug("wantTerseEditLogging=%s", debug::valueOf(wantTerseEditLogging));
|
qDebug("wantTerseEditLogging=%s", debug::valueOf(wantTerseEditLogging));
|
||||||
|
|
||||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||||
|
|
||||||
|
int maxTmpEntityLifetime;
|
||||||
|
if (readOptionInt("maxTmpLifetime", settingsSectionObject, maxTmpEntityLifetime)) {
|
||||||
|
tree->setEntityMaxTmpLifetime(maxTmpEntityLifetime);
|
||||||
|
} else {
|
||||||
|
tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME);
|
||||||
|
}
|
||||||
|
|
||||||
tree->setWantEditLogging(wantEditLogging);
|
tree->setWantEditLogging(wantEditLogging);
|
||||||
tree->setWantTerseEditLogging(wantTerseEditLogging);
|
tree->setWantTerseEditLogging(wantTerseEditLogging);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": 1.2,
|
"version": 1.4,
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"name": "metaverse",
|
"name": "metaverse",
|
||||||
|
@ -56,6 +56,7 @@
|
||||||
"label": "Paths",
|
"label": "Paths",
|
||||||
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
|
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
|
||||||
"type": "table",
|
"type": "table",
|
||||||
|
"can_add_new_rows": true,
|
||||||
"key": {
|
"key": {
|
||||||
"name": "path",
|
"name": "path",
|
||||||
"label": "Path",
|
"label": "Path",
|
||||||
|
@ -71,6 +72,76 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "descriptors",
|
||||||
|
"label": "Description",
|
||||||
|
"help": "This data will be queryable from your server. It may be collected by High Fidelity and used to share your domain with others.",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"label": "Description",
|
||||||
|
"help": "A description of your domain (256 character limit)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maturity",
|
||||||
|
"label": "Maturity",
|
||||||
|
"help": "A maturity rating, available as a guideline for content on your domain.",
|
||||||
|
"default": "unrated",
|
||||||
|
"type": "select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "unrated",
|
||||||
|
"label": "Unrated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "everyone",
|
||||||
|
"label": "Everyone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "teen",
|
||||||
|
"label": "Teen (13+)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "mature",
|
||||||
|
"label": "Mature (17+)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "adult",
|
||||||
|
"label": "Adult (18+)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hosts",
|
||||||
|
"label": "Hosts",
|
||||||
|
"type": "table",
|
||||||
|
"help": "Usernames of hosts who can reliably show your domain to new visitors.",
|
||||||
|
"numbered": false,
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "host",
|
||||||
|
"label": "Username",
|
||||||
|
"can_set": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tags",
|
||||||
|
"label": "Tags",
|
||||||
|
"type": "table",
|
||||||
|
"help": "Common categories under which your domain falls.",
|
||||||
|
"numbered": false,
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "tag",
|
||||||
|
"label": "Tag",
|
||||||
|
"can_set": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "security",
|
"name": "security",
|
||||||
"label": "Security",
|
"label": "Security",
|
||||||
|
@ -87,27 +158,6 @@
|
||||||
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
|
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
|
||||||
"value-hidden": true
|
"value-hidden": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "restricted_access",
|
|
||||||
"type": "checkbox",
|
|
||||||
"label": "Restricted Access",
|
|
||||||
"default": false,
|
|
||||||
"help": "Only users listed in \"Allowed Users\" can enter your domain."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "allowed_users",
|
|
||||||
"type": "table",
|
|
||||||
"label": "Allowed Users",
|
|
||||||
"help": "You can always connect from the domain-server machine.",
|
|
||||||
"numbered": false,
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"label": "Username",
|
|
||||||
"can_set": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "maximum_user_capacity",
|
"name": "maximum_user_capacity",
|
||||||
"label": "Maximum User Capacity",
|
"label": "Maximum User Capacity",
|
||||||
|
@ -117,25 +167,141 @@
|
||||||
"advanced": false
|
"advanced": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "allowed_editors",
|
"name": "standard_permissions",
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"label": "Allowed Editors",
|
"label": "Domain-Wide User Permissions",
|
||||||
"help": "List the High Fidelity names for people you want to be able lock or unlock entities in this domain.<br/>An empty list means everyone.",
|
"help": "Indicate which users or groups 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 the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” 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’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></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’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>.",
|
||||||
"numbered": false,
|
"caption": "Standard Permissions",
|
||||||
|
"can_add_new_rows": false,
|
||||||
|
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"label": "User / Group",
|
||||||
|
"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 the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” 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’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></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’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": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "username",
|
"name": "permissions_id",
|
||||||
"label": "Username",
|
"label": ""
|
||||||
"can_set": true
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_connect",
|
||||||
|
"label": "Connect",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_adjust_locks",
|
||||||
|
"label": "Lock / Unlock",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_rez",
|
||||||
|
"label": "Rez",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_rez_tmp",
|
||||||
|
"label": "Rez Temporary",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_write_to_asset_server",
|
||||||
|
"label": "Write Assets",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_connect_past_max_capacity",
|
||||||
|
"label": "Ignore Max Capacity",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
|
||||||
|
"non-deletable-row-key": "permissions_id",
|
||||||
|
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "editors_are_rezzers",
|
"name": "permissions",
|
||||||
"type": "checkbox",
|
"type": "table",
|
||||||
"label": "Only Editors Can Create Entities",
|
"caption": "Permissions for Specific Users",
|
||||||
"help": "Only users listed in \"Allowed Editors\" can create new entites.",
|
"can_add_new_rows": true,
|
||||||
"default": false
|
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"label": "User / Group",
|
||||||
|
"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 the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” 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’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></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’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": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "permissions_id",
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_connect",
|
||||||
|
"label": "Connect",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_adjust_locks",
|
||||||
|
"label": "Lock / Unlock",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_rez",
|
||||||
|
"label": "Rez",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_rez_tmp",
|
||||||
|
"label": "Rez Temporary",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_write_to_asset_server",
|
||||||
|
"label": "Write Assets",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_connect_past_max_capacity",
|
||||||
|
"label": "Ignore Max Capacity",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -148,6 +314,8 @@
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"label": "Persistent Scripts",
|
"label": "Persistent Scripts",
|
||||||
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
|
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
|
||||||
|
"can_add_new_rows": true,
|
||||||
|
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "url",
|
"name": "url",
|
||||||
|
@ -232,6 +400,8 @@
|
||||||
"label": "Zones",
|
"label": "Zones",
|
||||||
"help": "In this table you can define a set of zones in which you can specify various audio properties.",
|
"help": "In this table you can define a set of zones in which you can specify various audio properties.",
|
||||||
"numbered": false,
|
"numbered": false,
|
||||||
|
"can_add_new_rows": true,
|
||||||
|
|
||||||
"key": {
|
"key": {
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"label": "Name",
|
"label": "Name",
|
||||||
|
@ -283,6 +453,8 @@
|
||||||
"help": "In this table you can set custom attenuation coefficients between audio zones",
|
"help": "In this table you can set custom attenuation coefficients between audio zones",
|
||||||
"numbered": true,
|
"numbered": true,
|
||||||
"can_order": true,
|
"can_order": true,
|
||||||
|
"can_add_new_rows": true,
|
||||||
|
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "source",
|
"name": "source",
|
||||||
|
@ -310,6 +482,8 @@
|
||||||
"label": "Reverb Settings",
|
"label": "Reverb Settings",
|
||||||
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
|
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
|
||||||
"numbered": true,
|
"numbered": true,
|
||||||
|
"can_add_new_rows": true,
|
||||||
|
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "zone",
|
"name": "zone",
|
||||||
|
@ -409,6 +583,14 @@
|
||||||
"label": "Entity Server Settings",
|
"label": "Entity Server Settings",
|
||||||
"assignment-types": [6],
|
"assignment-types": [6],
|
||||||
"settings": [
|
"settings": [
|
||||||
|
{
|
||||||
|
"name": "maxTmpLifetime",
|
||||||
|
"label": "Maximum Lifetime of Temporary Entities",
|
||||||
|
"help": "The maximum number of seconds for the lifetime of an entity which will be considered \"temporary\".",
|
||||||
|
"placeholder": "3600",
|
||||||
|
"default": "3600",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "persistFilePath",
|
"name": "persistFilePath",
|
||||||
"label": "Entities File Path",
|
"label": "Entities File Path",
|
||||||
|
@ -431,6 +613,8 @@
|
||||||
"label": "Backup Rules",
|
"label": "Backup Rules",
|
||||||
"help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.",
|
"help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.",
|
||||||
"numbered": false,
|
"numbered": false,
|
||||||
|
"can_add_new_rows": true,
|
||||||
|
|
||||||
"default": [
|
"default": [
|
||||||
{"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5},
|
{"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5},
|
||||||
{"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7},
|
{"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7},
|
||||||
|
|
|
@ -20,6 +20,17 @@ body {
|
||||||
top: 40px;
|
top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table .value-row td, .table .inputs td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table .table-checkbox {
|
||||||
|
/* Fix IE sizing checkboxes to fill table cell */
|
||||||
|
width: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.glyphicon-remove {
|
.glyphicon-remove {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
@ -107,6 +118,58 @@ table {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 700;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table > tbody > .headers > td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table .headers + .headers td {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.top .tooltip-arrow {
|
||||||
|
border-top-color: #fff;
|
||||||
|
border-width: 10px 10px 0;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-inner {
|
||||||
|
padding: 20px 20px 10px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
color: #333;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 3px 8px 8px #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.in {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-inner ul {
|
||||||
|
padding-left: 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-inner li {
|
||||||
|
list-style-type: none;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#security .tooltip-inner {
|
||||||
|
max-width: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
#xs-advanced-container {
|
#xs-advanced-container {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,6 +232,17 @@ $(document).ready(function(){
|
||||||
badgeSidebarForDifferences($(this));
|
badgeSidebarForDifferences($(this));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bootstrap switch in table
|
||||||
|
$('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () {
|
||||||
|
// Bootstrap switches in table: set the changed data attribute for all rows in table.
|
||||||
|
var row = $(this).closest('tr');
|
||||||
|
if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table.
|
||||||
|
row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true);
|
||||||
|
updateDataChangedForSiblingRows(row, true);
|
||||||
|
badgeSidebarForDifferences($(this));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('.advanced-toggle').click(function(){
|
$('.advanced-toggle').click(function(){
|
||||||
Settings.showAdvanced = !Settings.showAdvanced
|
Settings.showAdvanced = !Settings.showAdvanced
|
||||||
var advancedSelector = $('.' + Settings.ADVANCED_CLASS)
|
var advancedSelector = $('.' + Settings.ADVANCED_CLASS)
|
||||||
|
@ -841,6 +852,8 @@ function reloadSettings(callback) {
|
||||||
// setup any bootstrap switches
|
// setup any bootstrap switches
|
||||||
$('.toggle-checkbox').bootstrapSwitch();
|
$('.toggle-checkbox').bootstrapSwitch();
|
||||||
|
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
|
||||||
// add tooltip to locked settings
|
// add tooltip to locked settings
|
||||||
$('label.locked').tooltip({
|
$('label.locked').tooltip({
|
||||||
placement: 'right',
|
placement: 'right',
|
||||||
|
@ -875,6 +888,7 @@ function saveSettings() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("----- SAVING ------");
|
||||||
console.log(formJSON);
|
console.log(formJSON);
|
||||||
|
|
||||||
// re-enable all inputs
|
// re-enable all inputs
|
||||||
|
@ -908,10 +922,33 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
||||||
html += "<span class='help-block'>" + setting.help + "</span>"
|
html += "<span class='help-block'>" + setting.help + "</span>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nonDeletableRowKey = setting["non-deletable-row-key"];
|
||||||
|
var nonDeletableRowValues = setting["non-deletable-row-values"];
|
||||||
|
|
||||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name
|
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name
|
||||||
+ "' name='" + keypath + "' id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath)
|
+ "' name='" + keypath + "' id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath)
|
||||||
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||||
|
|
||||||
|
if (setting.caption) {
|
||||||
|
html += "<caption>" + setting.caption + "</caption>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column groups
|
||||||
|
if (setting.groups) {
|
||||||
|
html += "<tr class='headers'>"
|
||||||
|
_.each(setting.groups, function (group) {
|
||||||
|
html += "<td colspan='" + group.span + "'><strong>" + group.label + "</strong></td>"
|
||||||
|
})
|
||||||
|
if (!isLocked && !setting.read_only) {
|
||||||
|
if (setting.can_order) {
|
||||||
|
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
|
||||||
|
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
|
||||||
|
}
|
||||||
|
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>"
|
||||||
|
}
|
||||||
|
html += "</tr>"
|
||||||
|
}
|
||||||
|
|
||||||
// Column names
|
// Column names
|
||||||
html += "<tr class='headers'>"
|
html += "<tr class='headers'>"
|
||||||
|
|
||||||
|
@ -950,6 +987,8 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
||||||
html += "<td class='key'>" + rowIndexOrName + "</td>"
|
html += "<td class='key'>" + rowIndexOrName + "</td>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isNonDeletableRow = false;
|
||||||
|
|
||||||
_.each(setting.columns, function(col) {
|
_.each(setting.columns, function(col) {
|
||||||
|
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
|
@ -961,16 +1000,19 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
||||||
colName = keypath + "." + rowIndexOrName + "." + col.name;
|
colName = keypath + "." + rowIndexOrName + "." + col.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup the td for this column
|
isNonDeletableRow = isNonDeletableRow
|
||||||
html += "<td class='" + Settings.DATA_COL_CLASS + "' name='" + colName + "'>";
|
|| (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1);
|
||||||
|
|
||||||
// add the actual value to the td so it is displayed
|
if (isArray && col.type === "checkbox" && col.editable) {
|
||||||
html += colValue;
|
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||||
|
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||||
|
+ "name='" + colName + "'" + (colValue ? " checked" : "") + " /></td>";
|
||||||
|
} else {
|
||||||
|
// Use a hidden input so that the values are posted.
|
||||||
|
html += "<td class='" + Settings.DATA_COL_CLASS + "' name='" + colName + "'>"
|
||||||
|
+ colValue + "<input type='hidden' name='" + colName + "' value='" + colValue + "'/></td>";
|
||||||
|
}
|
||||||
|
|
||||||
// for values to be posted properly we add a hidden input to this td
|
|
||||||
html += "<input type='hidden' name='" + colName + "' value='" + colValue + "'/>";
|
|
||||||
|
|
||||||
html += "</td>";
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isLocked && !setting.read_only) {
|
if (!isLocked && !setting.read_only) {
|
||||||
|
@ -979,8 +1021,12 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
||||||
"'><a href='javascript:void(0);' class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a>"
|
"'><a href='javascript:void(0);' class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a>"
|
||||||
+ "<a href='javascript:void(0);' class='" + Settings.MOVE_DOWN_SPAN_CLASSES + "'></a></td>"
|
+ "<a href='javascript:void(0);' class='" + Settings.MOVE_DOWN_SPAN_CLASSES + "'></a></td>"
|
||||||
}
|
}
|
||||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
if (isNonDeletableRow) {
|
||||||
"'><a href='javascript:void(0);' class='" + Settings.DEL_ROW_SPAN_CLASSES + "'></a></td>"
|
html += "<td></td>";
|
||||||
|
} else {
|
||||||
|
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES
|
||||||
|
+ "'><a href='javascript:void(0);' class='" + Settings.DEL_ROW_SPAN_CLASSES + "'></a></td>";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
|
@ -990,7 +1036,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate inputs in the table for new values
|
// populate inputs in the table for new values
|
||||||
if (!isLocked && !setting.read_only) {
|
if (!isLocked && !setting.read_only && setting.can_add_new_rows) {
|
||||||
html += makeTableInputs(setting)
|
html += makeTableInputs(setting)
|
||||||
}
|
}
|
||||||
html += "</table>"
|
html += "</table>"
|
||||||
|
@ -1012,17 +1058,23 @@ function makeTableInputs(setting) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_.each(setting.columns, function(col) {
|
_.each(setting.columns, function(col) {
|
||||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
if (col.type === "checkbox") {
|
||||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||||
</td>"
|
+ "name='" + col.name + "'" + (col.default ? " checked" : "") + "/></td>";
|
||||||
|
} else {
|
||||||
|
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||||
|
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||||
|
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||||
|
</td>"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (setting.can_order) {
|
if (setting.can_order) {
|
||||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
||||||
}
|
}
|
||||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></a></td>"
|
"'><a href='javascript:void(0);' class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></a></td>"
|
||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
|
|
||||||
return html
|
return html
|
||||||
|
@ -1127,11 +1179,11 @@ function addTableRow(add_glyphicon) {
|
||||||
} else {
|
} else {
|
||||||
$(element).html(1)
|
$(element).html(1)
|
||||||
}
|
}
|
||||||
} else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) {
|
} else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) {
|
||||||
$(element).html("<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><a href='javascript:void(0);'"
|
$(element).html("<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><a href='javascript:void(0);'"
|
||||||
+ " class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a><a href='javascript:void(0);' class='"
|
+ " class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a><a href='javascript:void(0);' class='"
|
||||||
+ Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>")
|
+ Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>")
|
||||||
} else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
} else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
||||||
// Change buttons
|
// Change buttons
|
||||||
var anchor = $(element).children("a")
|
var anchor = $(element).children("a")
|
||||||
anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES)
|
anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES)
|
||||||
|
@ -1142,8 +1194,20 @@ function addTableRow(add_glyphicon) {
|
||||||
input.remove()
|
input.remove()
|
||||||
} else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
|
} else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
|
||||||
// Hide inputs
|
// Hide inputs
|
||||||
var input = $(element).children("input")
|
var input = $(element).find("input")
|
||||||
input.attr("type", "hidden")
|
var isCheckbox = false;
|
||||||
|
if (input.hasClass("table-checkbox")) {
|
||||||
|
input = $(input).parent();
|
||||||
|
isCheckbox = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = input.val();
|
||||||
|
if (isCheckbox) {
|
||||||
|
val = $(input).find("input").is(':checked');
|
||||||
|
// don't hide the checkbox
|
||||||
|
} else {
|
||||||
|
input.attr("type", "hidden")
|
||||||
|
}
|
||||||
|
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length
|
var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length
|
||||||
|
@ -1152,14 +1216,22 @@ function addTableRow(add_glyphicon) {
|
||||||
// are there multiple columns or just one?
|
// are there multiple columns or just one?
|
||||||
// with multiple we have an array of Objects, with one we have an array of whatever the value type is
|
// with multiple we have an array of Objects, with one we have an array of whatever the value type is
|
||||||
var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length
|
var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length
|
||||||
input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
|
||||||
|
if (isCheckbox) {
|
||||||
|
$(input).find("input").attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
||||||
|
} else {
|
||||||
|
input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
input.attr("name", full_name + "." + $(element).attr("name"))
|
input.attr("name", full_name + "." + $(element).attr("name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
input.attr("data-changed", "true")
|
if (isCheckbox) {
|
||||||
|
$(input).find("input").attr("data-changed", "true");
|
||||||
$(element).append(input.val())
|
} else {
|
||||||
|
input.attr("data-changed", "true");
|
||||||
|
$(element).append(val);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Unknown table element")
|
console.log("Unknown table element")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ using SharedAssignmentPointer = QSharedPointer<Assignment>;
|
||||||
DomainGatekeeper::DomainGatekeeper(DomainServer* server) :
|
DomainGatekeeper::DomainGatekeeper(DomainServer* server) :
|
||||||
_server(server)
|
_server(server)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
|
void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
|
||||||
|
@ -38,7 +38,7 @@ void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid
|
||||||
|
|
||||||
QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID) {
|
QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID) {
|
||||||
auto it = _pendingAssignedNodes.find(tempUUID);
|
auto it = _pendingAssignedNodes.find(tempUUID);
|
||||||
|
|
||||||
if (it != _pendingAssignedNodes.end()) {
|
if (it != _pendingAssignedNodes.end()) {
|
||||||
return it->second.getAssignmentUUID();
|
return it->second.getAssignmentUUID();
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,59 +62,56 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
||||||
|
|
||||||
QByteArray myProtocolVersion = protocolVersionsSignature();
|
QByteArray myProtocolVersion = protocolVersionsSignature();
|
||||||
if (nodeConnection.protocolVersion != myProtocolVersion) {
|
if (nodeConnection.protocolVersion != myProtocolVersion) {
|
||||||
QString protocolVersionError = "Protocol version mismatch - Domain version:" + QCoreApplication::applicationVersion();
|
sendProtocolMismatchConnectionDenial(message->getSenderSockAddr());
|
||||||
qDebug() << "Protocol Version mismatch - denying connection.";
|
|
||||||
sendConnectionDeniedPacket(protocolVersionError, message->getSenderSockAddr(),
|
|
||||||
DomainHandler::ConnectionRefusedReason::ProtocolMismatch);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) {
|
if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) {
|
||||||
qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection.";
|
qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const NodeSet VALID_NODE_TYPES {
|
static const NodeSet VALID_NODE_TYPES {
|
||||||
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer
|
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) {
|
if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) {
|
||||||
qDebug() << "Received an invalid node type with connect request. Will not allow connection from"
|
qDebug() << "Received an invalid node type with connect request. Will not allow connection from"
|
||||||
<< nodeConnection.senderSockAddr << ": " << nodeConnection.nodeType;
|
<< nodeConnection.senderSockAddr << ": " << nodeConnection.nodeType;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if this connect request matches an assignment in the queue
|
// check if this connect request matches an assignment in the queue
|
||||||
auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
||||||
|
|
||||||
SharedNodePointer node;
|
SharedNodePointer node;
|
||||||
|
|
||||||
if (pendingAssignment != _pendingAssignedNodes.end()) {
|
if (pendingAssignment != _pendingAssignedNodes.end()) {
|
||||||
node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second);
|
node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second);
|
||||||
} else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) {
|
} else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) {
|
||||||
QString username;
|
QString username;
|
||||||
QByteArray usernameSignature;
|
QByteArray usernameSignature;
|
||||||
|
|
||||||
if (message->getBytesLeftToRead() > 0) {
|
if (message->getBytesLeftToRead() > 0) {
|
||||||
// read username from packet
|
// read username from packet
|
||||||
packetStream >> username;
|
packetStream >> username;
|
||||||
|
|
||||||
if (message->getBytesLeftToRead() > 0) {
|
if (message->getBytesLeftToRead() > 0) {
|
||||||
// read user signature from packet
|
// read user signature from packet
|
||||||
packetStream >> usernameSignature;
|
packetStream >> usernameSignature;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node = processAgentConnectRequest(nodeConnection, username, usernameSignature);
|
node = processAgentConnectRequest(nodeConnection, username, usernameSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
// set the sending sock addr and node interest set on this node
|
// set the sending sock addr and node interest set on this node
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||||
nodeData->setSendingSockAddr(message->getSenderSockAddr());
|
nodeData->setSendingSockAddr(message->getSenderSockAddr());
|
||||||
nodeData->setNodeInterestSet(nodeConnection.interestList.toSet());
|
nodeData->setNodeInterestSet(nodeConnection.interestList.toSet());
|
||||||
nodeData->setPlaceName(nodeConnection.placeName);
|
nodeData->setPlaceName(nodeConnection.placeName);
|
||||||
|
|
||||||
// signal that we just connected a node so the DomainServer can get it a list
|
// signal that we just connected a node so the DomainServer can get it a list
|
||||||
// and broadcast its presence right away
|
// and broadcast its presence right away
|
||||||
emit connectedNode(node);
|
emit connectedNode(node);
|
||||||
|
@ -123,18 +120,72 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainGatekeeper::updateNodePermissions() {
|
||||||
|
// If the permissions were changed on the domain-server webpage (and nothing else was), a restart isn't required --
|
||||||
|
// we reprocess the permissions map and update the nodes here. The node list is frequently sent out to all
|
||||||
|
// the connected nodes, so these changes are propagated to other nodes.
|
||||||
|
|
||||||
|
QList<SharedNodePointer> nodesToKill;
|
||||||
|
|
||||||
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
|
||||||
|
QString username = node->getPermissions().getUserName();
|
||||||
|
NodePermissions userPerms(username);
|
||||||
|
|
||||||
|
if (node->getPermissions().isAssignment) {
|
||||||
|
// this node is an assignment-client
|
||||||
|
userPerms.isAssignment = true;
|
||||||
|
userPerms.canAdjustLocks = true;
|
||||||
|
userPerms.canRezPermanentEntities = true;
|
||||||
|
userPerms.canRezTemporaryEntities = true;
|
||||||
|
} else {
|
||||||
|
// this node is an agent
|
||||||
|
userPerms.setAll(false);
|
||||||
|
|
||||||
|
const QHostAddress& addr = node->getLocalSocket().getAddress();
|
||||||
|
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||||
|
addr == QHostAddress::LocalHost);
|
||||||
|
if (isLocalUser) {
|
||||||
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username.isEmpty()) {
|
||||||
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||||
|
} else {
|
||||||
|
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||||
|
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||||
|
} else {
|
||||||
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node->setPermissions(userPerms);
|
||||||
|
|
||||||
|
if (!userPerms.canConnectToDomain) {
|
||||||
|
qDebug() << "node" << node->getUUID() << "no longer has permission to connect.";
|
||||||
|
// hang up on this node
|
||||||
|
nodesToKill << node;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (auto node, nodesToKill) {
|
||||||
|
emit killNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
|
SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||||
const PendingAssignedNodeData& pendingAssignment) {
|
const PendingAssignedNodeData& pendingAssignment) {
|
||||||
|
|
||||||
// make sure this matches an assignment the DS told us we sent out
|
// make sure this matches an assignment the DS told us we sent out
|
||||||
auto it = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
auto it = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
||||||
|
|
||||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||||
|
|
||||||
if (it != _pendingAssignedNodes.end()) {
|
if (it != _pendingAssignedNodes.end()) {
|
||||||
// find the matching queued static assignment in DS queue
|
// find the matching queued static assignment in DS queue
|
||||||
matchingQueuedAssignment = _server->dequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType);
|
matchingQueuedAssignment = _server->dequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType);
|
||||||
|
|
||||||
if (matchingQueuedAssignment) {
|
if (matchingQueuedAssignment) {
|
||||||
qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID)
|
qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID)
|
||||||
<< "matches unfulfilled assignment"
|
<< "matches unfulfilled assignment"
|
||||||
|
@ -149,124 +200,99 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
||||||
qDebug() << "No assignment was deployed with UUID" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID);
|
qDebug() << "No assignment was deployed with UUID" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID);
|
||||||
return SharedNodePointer();
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the new node
|
// add the new node
|
||||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||||
|
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||||
|
|
||||||
// set assignment related data on the linked data for this node
|
// set assignment related data on the linked data for this node
|
||||||
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
||||||
nodeData->setWalletUUID(it->second.getWalletUUID());
|
nodeData->setWalletUUID(it->second.getWalletUUID());
|
||||||
nodeData->setNodeVersion(it->second.getNodeVersion());
|
nodeData->setNodeVersion(it->second.getNodeVersion());
|
||||||
nodeData->setWasAssigned(true);
|
nodeData->setWasAssigned(true);
|
||||||
|
|
||||||
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
||||||
_pendingAssignedNodes.erase(it);
|
_pendingAssignedNodes.erase(it);
|
||||||
|
|
||||||
// always allow assignment clients to create and destroy entities
|
// always allow assignment clients to create and destroy entities
|
||||||
newNode->setIsAllowedEditor(true);
|
NodePermissions userPerms;
|
||||||
newNode->setCanRez(true);
|
userPerms.isAssignment = true;
|
||||||
|
userPerms.canAdjustLocks = true;
|
||||||
|
userPerms.canRezPermanentEntities = true;
|
||||||
|
userPerms.canRezTemporaryEntities = true;
|
||||||
|
newNode->setPermissions(userPerms);
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
||||||
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
|
|
||||||
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
|
|
||||||
|
|
||||||
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||||
const QString& username,
|
const QString& username,
|
||||||
const QByteArray& usernameSignature) {
|
const QByteArray& usernameSignature) {
|
||||||
|
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
bool isRestrictingAccess =
|
// start with empty permissions
|
||||||
_server->_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
NodePermissions userPerms(username);
|
||||||
|
userPerms.setAll(false);
|
||||||
// check if this user is on our local machine - if this is true they are always allowed to connect
|
|
||||||
|
// check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
|
||||||
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
||||||
bool isLocalUser =
|
bool isLocalUser =
|
||||||
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
||||||
|
if (isLocalUser) {
|
||||||
// if we're using restricted access and this user is not local make sure we got a user signature
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||||
if (isRestrictingAccess && !isLocalUser) {
|
qDebug() << "user-permissions: is local user, so:" << userPerms;
|
||||||
if (!username.isEmpty()) {
|
|
||||||
if (usernameSignature.isEmpty()) {
|
|
||||||
// if user didn't include usernameSignature in connect request, send a connectionToken packet
|
|
||||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
|
||||||
|
|
||||||
// ask for their public key right now to make sure we have it
|
|
||||||
requestUserPublicKey(username);
|
|
||||||
|
|
||||||
return SharedNodePointer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool verifiedUsername = false;
|
if (!username.isEmpty() && usernameSignature.isEmpty()) {
|
||||||
|
// user is attempting to prove their identity to us, but we don't have enough information
|
||||||
// if we do not have a local user we need to subject them to our verification and capacity checks
|
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||||
if (!isLocalUser) {
|
// ask for their public key right now to make sure we have it
|
||||||
|
requestUserPublicKey(username);
|
||||||
// check if we need to look at the username signature
|
if (!userPerms.canConnectToDomain) {
|
||||||
if (isRestrictingAccess) {
|
|
||||||
if (isVerifiedAllowedUser(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
|
||||||
// we verified the user via their username and signature - set the verifiedUsername
|
|
||||||
// so we don't re-decrypt their sig if we're trying to exempt them from max capacity check (due to
|
|
||||||
// being in the allowed editors list)
|
|
||||||
verifiedUsername = true;
|
|
||||||
} else {
|
|
||||||
// failed to verify user - return a null shared ptr
|
|
||||||
return SharedNodePointer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isWithinMaxCapacity(username, usernameSignature, verifiedUsername, nodeConnection.senderSockAddr)) {
|
|
||||||
// we can't allow this user to connect because we are at max capacity (and they either aren't an allowed editor
|
|
||||||
// or couldn't be verified as one)
|
|
||||||
return SharedNodePointer();
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this user is in the editors list (or if the editors list is empty) set the user's node's isAllowedEditor to true
|
if (username.isEmpty()) {
|
||||||
const QVariant* allowedEditorsVariant =
|
// they didn't tell us who they are
|
||||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
qDebug() << "user-permissions: no username, so:" << userPerms;
|
||||||
|
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||||
// if the allowed editors list is empty then everyone can adjust locks
|
// they are sent us a username and the signature verifies it
|
||||||
bool isAllowedEditor = allowedEditors.empty();
|
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||||
|
// we have specific permissions for this user.
|
||||||
if (allowedEditors.contains(username, Qt::CaseInsensitive)) {
|
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||||
// we have a non-empty allowed editors list - check if this user is verified to be in it
|
qDebug() << "user-permissions: specific user matches, so:" << userPerms;
|
||||||
if (!verifiedUsername) {
|
|
||||||
if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) {
|
|
||||||
// failed to verify a user that is in the allowed editors list
|
|
||||||
|
|
||||||
// TODO: fix public key refresh in interface/metaverse and force this check
|
|
||||||
qDebug() << "Could not verify user" << username << "as allowed editor. In the interim this user"
|
|
||||||
<< "will be given edit rights to avoid a thrasing of public key requests and connect requests.";
|
|
||||||
}
|
|
||||||
|
|
||||||
isAllowedEditor = true;
|
|
||||||
} else {
|
} else {
|
||||||
// already verified this user and they are in the allowed editors list
|
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||||
isAllowedEditor = true;
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||||
|
qDebug() << "user-permissions: user is logged in, so:" << userPerms;
|
||||||
|
}
|
||||||
|
userPerms.setUserName(username);
|
||||||
|
} else {
|
||||||
|
// they sent us a username, but it didn't check out
|
||||||
|
requestUserPublicKey(username);
|
||||||
|
if (!userPerms.canConnectToDomain) {
|
||||||
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if only editors should be able to rez entities
|
qDebug() << "user-permissions: final:" << userPerms;
|
||||||
const QVariant* editorsAreRezzersVariant =
|
|
||||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH);
|
if (!userPerms.canConnectToDomain) {
|
||||||
|
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
|
||||||
bool onlyEditorsAreRezzers = false;
|
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||||
if (editorsAreRezzersVariant) {
|
return SharedNodePointer();
|
||||||
onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool canRez = true;
|
if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) {
|
||||||
if (onlyEditorsAreRezzers) {
|
// we can't allow this user to connect because we are at max capacity
|
||||||
canRez = isAllowedEditor;
|
sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
|
||||||
|
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||||
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid hintNodeID;
|
QUuid hintNodeID;
|
||||||
|
@ -285,24 +311,23 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
|
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
|
||||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
|
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
|
||||||
|
|
||||||
// set the edit rights for this user
|
// set the edit rights for this user
|
||||||
newNode->setIsAllowedEditor(isAllowedEditor);
|
newNode->setPermissions(userPerms);
|
||||||
newNode->setCanRez(canRez);
|
|
||||||
|
|
||||||
// grab the linked data for our new node so we can set the username
|
// grab the linked data for our new node so we can set the username
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||||
|
|
||||||
// if we have a username from the connect request, set it on the DomainServerNodeData
|
// if we have a username from the connect request, set it on the DomainServerNodeData
|
||||||
nodeData->setUsername(username);
|
nodeData->setUsername(username);
|
||||||
|
|
||||||
// also add an interpolation to DomainServerNodeData so that servers can get username in stats
|
// also add an interpolation to DomainServerNodeData so that servers can get username in stats
|
||||||
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||||
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,11 +335,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
||||||
QUuid nodeID) {
|
QUuid nodeID) {
|
||||||
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
|
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
|
||||||
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
|
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
|
||||||
|
|
||||||
if (connectedPeer) {
|
if (connectedPeer) {
|
||||||
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
||||||
nodeID = nodeConnection.connectUUID;
|
nodeID = nodeConnection.connectUUID;
|
||||||
|
|
||||||
if (connectedPeer->getActiveSocket()) {
|
if (connectedPeer->getActiveSocket()) {
|
||||||
// set their discovered socket to whatever the activated socket on the network peer object was
|
// set their discovered socket to whatever the activated socket on the network peer object was
|
||||||
discoveredSocket = *connectedPeer->getActiveSocket();
|
discoveredSocket = *connectedPeer->getActiveSocket();
|
||||||
|
@ -325,15 +350,15 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
||||||
nodeID = QUuid::createUuid();
|
nodeID = QUuid::createUuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType,
|
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType,
|
||||||
nodeConnection.publicSockAddr, nodeConnection.localSockAddr);
|
nodeConnection.publicSockAddr, nodeConnection.localSockAddr);
|
||||||
|
|
||||||
// So that we can send messages to this node at will - we need to activate the correct socket on this node now
|
// So that we can send messages to this node at will - we need to activate the correct socket on this node now
|
||||||
newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket);
|
newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket);
|
||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,21 +368,21 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
|
|
||||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||||
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||||
|
|
||||||
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
||||||
|
|
||||||
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
||||||
// if we do have a public key for the user, check for a signature match
|
// if we do have a public key for the user, check for a signature match
|
||||||
|
|
||||||
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData());
|
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData());
|
||||||
|
|
||||||
// first load up the public key into an RSA struct
|
// first load up the public key into an RSA struct
|
||||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
||||||
|
|
||||||
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
||||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
||||||
QCryptographicHash::Sha256);
|
QCryptographicHash::Sha256);
|
||||||
|
|
||||||
if (rsaPublicKey) {
|
if (rsaPublicKey) {
|
||||||
int decryptResult = RSA_verify(NID_sha256,
|
int decryptResult = RSA_verify(NID_sha256,
|
||||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
||||||
|
@ -365,29 +390,29 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
||||||
usernameSignature.size(),
|
usernameSignature.size(),
|
||||||
rsaPublicKey);
|
rsaPublicKey);
|
||||||
|
|
||||||
if (decryptResult == 1) {
|
if (decryptResult == 1) {
|
||||||
qDebug() << "Username signature matches for" << username << "- allowing connection.";
|
qDebug() << "Username signature matches for" << username;
|
||||||
|
|
||||||
// free up the public key and remove connection token before we return
|
// free up the public key and remove connection token before we return
|
||||||
RSA_free(rsaPublicKey);
|
RSA_free(rsaPublicKey);
|
||||||
_connectionTokenHash.remove(username);
|
_connectionTokenHash.remove(username);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (!senderSockAddr.isNull()) {
|
if (!senderSockAddr.isNull()) {
|
||||||
qDebug() << "Error decrypting username signature for " << username << "- denying connection.";
|
qDebug() << "Error decrypting username signature for " << username << "- denying connection.";
|
||||||
sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr,
|
sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr,
|
||||||
DomainHandler::ConnectionRefusedReason::LoginError);
|
DomainHandler::ConnectionRefusedReason::LoginError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// free up the public key, we don't need it anymore
|
// free up the public key, we don't need it anymore
|
||||||
RSA_free(rsaPublicKey);
|
RSA_free(rsaPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||||
if (!senderSockAddr.isNull()) {
|
if (!senderSockAddr.isNull()) {
|
||||||
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
||||||
|
@ -402,86 +427,35 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
DomainHandler::ConnectionRefusedReason::LoginError);
|
DomainHandler::ConnectionRefusedReason::LoginError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUserPublicKey(username); // no joy. maybe next time?
|
requestUserPublicKey(username); // no joy. maybe next time?
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature,
|
bool DomainGatekeeper::isWithinMaxCapacity() {
|
||||||
const HifiSockAddr& senderSockAddr) {
|
|
||||||
|
|
||||||
if (username.isEmpty()) {
|
|
||||||
qDebug() << "Connect request denied - no username provided.";
|
|
||||||
|
|
||||||
sendConnectionDeniedPacket("No username provided", senderSockAddr,
|
|
||||||
DomainHandler::ConnectionRefusedReason::LoginError);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList allowedUsers =
|
|
||||||
_server->_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
|
|
||||||
|
|
||||||
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
|
|
||||||
if (!verifyUserSignature(username, usernameSignature, senderSockAddr)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qDebug() << "Connect request denied for user" << username << "- not in allowed users list.";
|
|
||||||
sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr,
|
|
||||||
DomainHandler::ConnectionRefusedReason::NotAuthorized);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature,
|
|
||||||
bool& verifiedUsername,
|
|
||||||
const HifiSockAddr& senderSockAddr) {
|
|
||||||
// find out what our maximum capacity is
|
// find out what our maximum capacity is
|
||||||
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
const QVariant* maximumUserCapacityVariant =
|
||||||
|
valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
||||||
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
||||||
|
|
||||||
if (maximumUserCapacity > 0) {
|
if (maximumUserCapacity > 0) {
|
||||||
unsigned int connectedUsers = _server->countConnectedUsers();
|
unsigned int connectedUsers = _server->countConnectedUsers();
|
||||||
|
|
||||||
if (connectedUsers >= maximumUserCapacity) {
|
if (connectedUsers >= maximumUserCapacity) {
|
||||||
// too many users, deny the new connection unless this user is an allowed editor
|
|
||||||
|
|
||||||
const QVariant* allowedEditorsVariant =
|
|
||||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
|
||||||
|
|
||||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
|
||||||
if (allowedEditors.contains(username)) {
|
|
||||||
if (verifiedUsername || verifyUserSignature(username, usernameSignature, senderSockAddr)) {
|
|
||||||
verifiedUsername = true;
|
|
||||||
qDebug() << "Above maximum capacity -" << connectedUsers << "/" << maximumUserCapacity <<
|
|
||||||
"but user" << username << "is in allowed editors list so will be allowed to connect.";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deny connection from this user
|
|
||||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
||||||
sendConnectionDeniedPacket("Too many connected users.", senderSockAddr,
|
|
||||||
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, allowing new connection.";
|
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, allowing new connection.";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DomainGatekeeper::preloadAllowedUserPublicKeys() {
|
void DomainGatekeeper::preloadAllowedUserPublicKeys() {
|
||||||
const QVariant* allowedUsersVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH);
|
QStringList allowedUsers = _server->_settingsManager.getAllNames();
|
||||||
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
|
||||||
|
|
||||||
if (allowedUsers.size() > 0) {
|
if (allowedUsers.size() > 0) {
|
||||||
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
|
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
|
||||||
// going to create > 100 requests
|
// going to create > 100 requests
|
||||||
|
@ -492,15 +466,20 @@ void DomainGatekeeper::preloadAllowedUserPublicKeys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
||||||
|
// don't request public keys for the standard psuedo-account-names
|
||||||
|
if (NodePermissions::standardNames.contains(username, Qt::CaseInsensitive)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// even if we have a public key for them right now, request a new one in case it has just changed
|
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||||
JSONCallbackParameters callbackParams;
|
JSONCallbackParameters callbackParams;
|
||||||
callbackParams.jsonCallbackReceiver = this;
|
callbackParams.jsonCallbackReceiver = this;
|
||||||
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||||
|
|
||||||
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||||
|
|
||||||
qDebug() << "Requesting public key for user" << username;
|
qDebug() << "Requesting public key for user" << username;
|
||||||
|
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
|
DependencyManager::get<AccountManager>()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
|
||||||
AccountManagerAuth::None,
|
AccountManagerAuth::None,
|
||||||
QNetworkAccessManager::GetOperation, callbackParams);
|
QNetworkAccessManager::GetOperation, callbackParams);
|
||||||
|
@ -508,38 +487,47 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
||||||
|
|
||||||
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
|
|
||||||
if (jsonObject["status"].toString() == "success") {
|
if (jsonObject["status"].toString() == "success") {
|
||||||
// figure out which user this is for
|
// figure out which user this is for
|
||||||
|
|
||||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||||
|
|
||||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||||
QString username = usernameRegex.cap(1);
|
QString username = usernameRegex.cap(1);
|
||||||
|
|
||||||
qDebug() << "Storing a public key for user" << username;
|
qDebug() << "Storing a public key for user" << username;
|
||||||
|
|
||||||
// pull the public key as a QByteArray from this response
|
// pull the public key as a QByteArray from this response
|
||||||
const QString JSON_DATA_KEY = "data";
|
const QString JSON_DATA_KEY = "data";
|
||||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||||
|
|
||||||
_userPublicKeys[username] =
|
_userPublicKeys[username] =
|
||||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) {
|
||||||
DomainHandler::ConnectionRefusedReason reasonCode) {
|
QString protocolVersionError = "Protocol version mismatch - Domain version: " + QCoreApplication::applicationVersion();
|
||||||
|
|
||||||
|
qDebug() << "Protocol Version mismatch - denying connection.";
|
||||||
|
|
||||||
|
sendConnectionDeniedPacket(protocolVersionError, senderSockAddr,
|
||||||
|
DomainHandler::ConnectionRefusedReason::ProtocolMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
||||||
|
DomainHandler::ConnectionRefusedReason reasonCode) {
|
||||||
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
||||||
QByteArray utfString = reason.toUtf8();
|
QByteArray utfString = reason.toUtf8();
|
||||||
quint16 payloadSize = utfString.size();
|
quint16 payloadSize = utfString.size();
|
||||||
|
|
||||||
// setup the DomainConnectionDenied packet
|
// setup the DomainConnectionDenied packet
|
||||||
auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied,
|
auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied,
|
||||||
payloadSize + sizeof(payloadSize) + sizeof(uint8_t));
|
payloadSize + sizeof(payloadSize) + sizeof(uint8_t));
|
||||||
|
|
||||||
// pack in the reason the connection was denied (the client displays this)
|
// pack in the reason the connection was denied (the client displays this)
|
||||||
if (payloadSize > 0) {
|
if (payloadSize > 0) {
|
||||||
uint8_t reasonCodeWire = (uint8_t)reasonCode;
|
uint8_t reasonCodeWire = (uint8_t)reasonCode;
|
||||||
|
@ -547,7 +535,7 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H
|
||||||
connectionDeniedPacket->writePrimitive(payloadSize);
|
connectionDeniedPacket->writePrimitive(payloadSize);
|
||||||
connectionDeniedPacket->write(utfString);
|
connectionDeniedPacket->write(utfString);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send the packet off
|
// send the packet off
|
||||||
DependencyManager::get<LimitedNodeList>()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr);
|
DependencyManager::get<LimitedNodeList>()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr);
|
||||||
}
|
}
|
||||||
|
@ -555,20 +543,20 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H
|
||||||
void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr) {
|
void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr) {
|
||||||
// get the existing connection token or create a new one
|
// get the existing connection token or create a new one
|
||||||
QUuid& connectionToken = _connectionTokenHash[username.toLower()];
|
QUuid& connectionToken = _connectionTokenHash[username.toLower()];
|
||||||
|
|
||||||
if (connectionToken.isNull()) {
|
if (connectionToken.isNull()) {
|
||||||
connectionToken = QUuid::createUuid();
|
connectionToken = QUuid::createUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup a static connection token packet
|
// setup a static connection token packet
|
||||||
static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID);
|
static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID);
|
||||||
|
|
||||||
// reset the packet before each time we send
|
// reset the packet before each time we send
|
||||||
connectionTokenPacket->reset();
|
connectionTokenPacket->reset();
|
||||||
|
|
||||||
// write the connection token
|
// write the connection token
|
||||||
connectionTokenPacket->write(connectionToken.toRfc4122());
|
connectionTokenPacket->write(connectionToken.toRfc4122());
|
||||||
|
|
||||||
// send off the packet unreliably
|
// send off the packet unreliably
|
||||||
DependencyManager::get<LimitedNodeList>()->sendUnreliablePacket(*connectionTokenPacket, senderSockAddr);
|
DependencyManager::get<LimitedNodeList>()->sendUnreliablePacket(*connectionTokenPacket, senderSockAddr);
|
||||||
}
|
}
|
||||||
|
@ -576,33 +564,33 @@ void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const
|
||||||
const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS;
|
const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS;
|
||||||
|
|
||||||
void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) {
|
void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) {
|
||||||
|
|
||||||
if (peer->getConnectionAttempts() >= NUM_PEER_PINGS_BEFORE_DELETE) {
|
if (peer->getConnectionAttempts() >= NUM_PEER_PINGS_BEFORE_DELETE) {
|
||||||
// we've reached the maximum number of ping attempts
|
// we've reached the maximum number of ping attempts
|
||||||
qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID();
|
qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID();
|
||||||
qDebug() << "Removing from list of connecting peers.";
|
qDebug() << "Removing from list of connecting peers.";
|
||||||
|
|
||||||
_icePeers.remove(peer->getUUID());
|
_icePeers.remove(peer->getUUID());
|
||||||
} else {
|
} else {
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
// send the ping packet to the local and public sockets for this node
|
// send the ping packet to the local and public sockets for this node
|
||||||
auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID());
|
auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID());
|
||||||
limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket());
|
limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket());
|
||||||
|
|
||||||
auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID());
|
auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID());
|
||||||
limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket());
|
limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket());
|
||||||
|
|
||||||
peer->incrementConnectionAttempts();
|
peer->incrementConnectionAttempts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::handlePeerPingTimeout() {
|
void DomainGatekeeper::handlePeerPingTimeout() {
|
||||||
NetworkPeer* senderPeer = qobject_cast<NetworkPeer*>(sender());
|
NetworkPeer* senderPeer = qobject_cast<NetworkPeer*>(sender());
|
||||||
|
|
||||||
if (senderPeer) {
|
if (senderPeer) {
|
||||||
SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID());
|
SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID());
|
||||||
|
|
||||||
if (sharedPeer && !sharedPeer->getActiveSocket()) {
|
if (sharedPeer && !sharedPeer->getActiveSocket()) {
|
||||||
pingPunchForConnectingPeer(sharedPeer);
|
pingPunchForConnectingPeer(sharedPeer);
|
||||||
}
|
}
|
||||||
|
@ -613,24 +601,24 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMe
|
||||||
// loop through the packet and pull out network peers
|
// loop through the packet and pull out network peers
|
||||||
// any peer we don't have we add to the hash, otherwise we update
|
// any peer we don't have we add to the hash, otherwise we update
|
||||||
QDataStream iceResponseStream(message->getMessage());
|
QDataStream iceResponseStream(message->getMessage());
|
||||||
|
|
||||||
NetworkPeer* receivedPeer = new NetworkPeer;
|
NetworkPeer* receivedPeer = new NetworkPeer;
|
||||||
iceResponseStream >> *receivedPeer;
|
iceResponseStream >> *receivedPeer;
|
||||||
|
|
||||||
if (!_icePeers.contains(receivedPeer->getUUID())) {
|
if (!_icePeers.contains(receivedPeer->getUUID())) {
|
||||||
qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
|
qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
|
||||||
SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer);
|
SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer);
|
||||||
_icePeers[receivedPeer->getUUID()] = newPeer;
|
_icePeers[receivedPeer->getUUID()] = newPeer;
|
||||||
|
|
||||||
// make sure we know when we should ping this peer
|
// make sure we know when we should ping this peer
|
||||||
connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
|
connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
|
||||||
|
|
||||||
// immediately ping the new peer, and start a timer to continue pinging it until we connect to it
|
// immediately ping the new peer, and start a timer to continue pinging it until we connect to it
|
||||||
newPeer->startPingTimer();
|
newPeer->startPingTimer();
|
||||||
|
|
||||||
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
|
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
|
||||||
<< newPeer->getUUID();
|
<< newPeer->getUUID();
|
||||||
|
|
||||||
pingPunchForConnectingPeer(newPeer);
|
pingPunchForConnectingPeer(newPeer);
|
||||||
} else {
|
} else {
|
||||||
delete receivedPeer;
|
delete receivedPeer;
|
||||||
|
@ -640,18 +628,18 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMe
|
||||||
void DomainGatekeeper::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainGatekeeper::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
|
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
|
||||||
|
|
||||||
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
|
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
QDataStream packetStream(message->getMessage());
|
QDataStream packetStream(message->getMessage());
|
||||||
|
|
||||||
QUuid nodeUUID;
|
QUuid nodeUUID;
|
||||||
packetStream >> nodeUUID;
|
packetStream >> nodeUUID;
|
||||||
|
|
||||||
SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID);
|
SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID);
|
||||||
|
|
||||||
if (sendingPeer) {
|
if (sendingPeer) {
|
||||||
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
|
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
|
||||||
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
|
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
|
||||||
|
|
|
@ -42,6 +42,8 @@ public:
|
||||||
void preloadAllowedUserPublicKeys();
|
void preloadAllowedUserPublicKeys();
|
||||||
|
|
||||||
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
|
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
|
||||||
|
|
||||||
|
static void sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr);
|
||||||
public slots:
|
public slots:
|
||||||
void processConnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
void processConnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
void processICEPingPacket(QSharedPointer<ReceivedMessage> message);
|
void processICEPingPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
|
@ -51,8 +53,12 @@ public slots:
|
||||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void killNode(SharedNodePointer node);
|
||||||
void connectedNode(SharedNodePointer node);
|
void connectedNode(SharedNodePointer node);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateNodePermissions();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handlePeerPingTimeout();
|
void handlePeerPingTimeout();
|
||||||
private:
|
private:
|
||||||
|
@ -66,18 +72,14 @@ private:
|
||||||
|
|
||||||
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
|
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
|
||||||
const HifiSockAddr& senderSockAddr);
|
const HifiSockAddr& senderSockAddr);
|
||||||
bool isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature,
|
bool isWithinMaxCapacity();
|
||||||
const HifiSockAddr& senderSockAddr);
|
|
||||||
bool isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature,
|
|
||||||
bool& verifiedUsername,
|
|
||||||
const HifiSockAddr& senderSockAddr);
|
|
||||||
|
|
||||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||||
const HifiSockAddr& senderSockAddr);
|
const HifiSockAddr& senderSockAddr);
|
||||||
|
|
||||||
void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr);
|
void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr);
|
||||||
void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
static void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
||||||
DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown);
|
DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown);
|
||||||
|
|
||||||
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);
|
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);
|
||||||
|
|
||||||
|
|
132
domain-server/src/DomainMetadata.cpp
Normal file
132
domain-server/src/DomainMetadata.cpp
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
//
|
||||||
|
// DomainMetadata.cpp
|
||||||
|
// domain-server/src
|
||||||
|
//
|
||||||
|
// Created by Zach Pomerantz on 5/25/2016.
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
#include "DomainMetadata.h"
|
||||||
|
|
||||||
|
#include <HifiConfigVariantMap.h>
|
||||||
|
#include <DependencyManager.h>
|
||||||
|
#include <LimitedNodeList.h>
|
||||||
|
|
||||||
|
#include "DomainServerNodeData.h"
|
||||||
|
|
||||||
|
const QString DomainMetadata::USERS = "users";
|
||||||
|
const QString DomainMetadata::USERS_NUM_TOTAL = "num_users";
|
||||||
|
const QString DomainMetadata::USERS_NUM_ANON = "num_anon_users";
|
||||||
|
const QString DomainMetadata::USERS_HOSTNAMES = "user_hostnames";
|
||||||
|
// users metadata will appear as (JSON):
|
||||||
|
// { "num_users": Number,
|
||||||
|
// "num_anon_users": Number,
|
||||||
|
// "user_hostnames": { <HOSTNAME>: Number }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const QString DomainMetadata::DESCRIPTORS = "descriptors";
|
||||||
|
const QString DomainMetadata::DESCRIPTORS_DESCRIPTION = "description";
|
||||||
|
const QString DomainMetadata::DESCRIPTORS_CAPACITY = "capacity"; // parsed from security
|
||||||
|
const QString DomainMetadata::DESCRIPTORS_RESTRICTION = "restriction"; // parsed from ACL
|
||||||
|
const QString DomainMetadata::DESCRIPTORS_MATURITY = "maturity";
|
||||||
|
const QString DomainMetadata::DESCRIPTORS_HOSTS = "hosts";
|
||||||
|
const QString DomainMetadata::DESCRIPTORS_TAGS = "tags";
|
||||||
|
// descriptors metadata will appear as (JSON):
|
||||||
|
// { "capacity": Number,
|
||||||
|
// TODO: "hours": String, // UTF-8 representation of the week, split into 15" segments
|
||||||
|
// "restriction": String, // enum of either open, hifi, or acl
|
||||||
|
// "maturity": String, // enum corresponding to ESRB ratings
|
||||||
|
// "hosts": [ String ], // capped list of usernames
|
||||||
|
// "description": String, // capped description
|
||||||
|
// TODO: "img": {
|
||||||
|
// "src": String,
|
||||||
|
// "type": String,
|
||||||
|
// "size": Number,
|
||||||
|
// "updated_at": Number,
|
||||||
|
// },
|
||||||
|
// "tags": [ String ], // capped list of tags
|
||||||
|
// }
|
||||||
|
|
||||||
|
// metadata will appear as (JSON):
|
||||||
|
// { users: <USERS>, descriptors: <DESCRIPTORS> }
|
||||||
|
//
|
||||||
|
// it is meant to be sent to and consumed by an external API
|
||||||
|
|
||||||
|
DomainMetadata::DomainMetadata() {
|
||||||
|
_metadata[USERS] = {};
|
||||||
|
_metadata[DESCRIPTORS] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainMetadata::setDescriptors(QVariantMap& settings) {
|
||||||
|
const QString CAPACITY = "security.maximum_user_capacity";
|
||||||
|
const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY);
|
||||||
|
unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0;
|
||||||
|
|
||||||
|
// TODO: Keep parity with ACL development.
|
||||||
|
const QString RESTRICTION = "security.restricted_access";
|
||||||
|
const QString RESTRICTION_OPEN = "open";
|
||||||
|
// const QString RESTRICTION_HIFI = "hifi";
|
||||||
|
const QString RESTRICTION_ACL = "acl";
|
||||||
|
const QVariant* isRestrictedVariant = valueForKeyPath(settings, RESTRICTION);
|
||||||
|
bool isRestricted = isRestrictedVariant ? isRestrictedVariant->toBool() : false;
|
||||||
|
QString restriction = isRestricted ? RESTRICTION_ACL : RESTRICTION_OPEN;
|
||||||
|
|
||||||
|
QVariantMap descriptors = settings[DESCRIPTORS].toMap();
|
||||||
|
descriptors[DESCRIPTORS_CAPACITY] = capacity;
|
||||||
|
descriptors[DESCRIPTORS_RESTRICTION] = restriction;
|
||||||
|
_metadata[DESCRIPTORS] = descriptors;
|
||||||
|
|
||||||
|
#if DEV_BUILD || PR_BUILD
|
||||||
|
qDebug() << "Domain metadata descriptors set:" << descriptors;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainMetadata::updateUsers() {
|
||||||
|
static const QString DEFAULT_HOSTNAME = "*";
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
int numConnected = 0;
|
||||||
|
int numConnectedAnonymously = 0;
|
||||||
|
QVariantMap userHostnames;
|
||||||
|
|
||||||
|
// figure out the breakdown of currently connected interface clients
|
||||||
|
nodeList->eachNode([&numConnected, &numConnectedAnonymously, &userHostnames](const SharedNodePointer& node) {
|
||||||
|
auto linkedData = node->getLinkedData();
|
||||||
|
if (linkedData) {
|
||||||
|
auto nodeData = static_cast<DomainServerNodeData*>(linkedData);
|
||||||
|
|
||||||
|
if (!nodeData->wasAssigned()) {
|
||||||
|
++numConnected;
|
||||||
|
|
||||||
|
if (nodeData->getUsername().isEmpty()) {
|
||||||
|
++numConnectedAnonymously;
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment the count for this hostname (or the default if we don't have one)
|
||||||
|
auto placeName = nodeData->getPlaceName();
|
||||||
|
auto hostname = placeName.isEmpty() ? DEFAULT_HOSTNAME : placeName;
|
||||||
|
userHostnames[hostname] = userHostnames[hostname].toInt() + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QVariantMap users = {
|
||||||
|
{ USERS_NUM_TOTAL, numConnected },
|
||||||
|
{ USERS_NUM_ANON, numConnectedAnonymously },
|
||||||
|
{ USERS_HOSTNAMES, userHostnames }};
|
||||||
|
_metadata[USERS] = users;
|
||||||
|
|
||||||
|
#if DEV_BUILD || PR_BUILD
|
||||||
|
qDebug() << "Domain metadata users updated:" << users;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainMetadata::usersChanged() {
|
||||||
|
++_tic;
|
||||||
|
|
||||||
|
#if DEV_BUILD || PR_BUILD
|
||||||
|
qDebug() << "Domain metadata users change detected";
|
||||||
|
#endif
|
||||||
|
}
|
65
domain-server/src/DomainMetadata.h
Normal file
65
domain-server/src/DomainMetadata.h
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// DomainMetadata.h
|
||||||
|
// domain-server/src
|
||||||
|
//
|
||||||
|
// Created by Zach Pomerantz on 5/25/2016.
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
#ifndef hifi_DomainMetadata_h
|
||||||
|
#define hifi_DomainMetadata_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <QVariantMap>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
class DomainMetadata : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
static const QString USERS;
|
||||||
|
static const QString USERS_NUM_TOTAL;
|
||||||
|
static const QString USERS_NUM_ANON;
|
||||||
|
static const QString USERS_HOSTNAMES;
|
||||||
|
|
||||||
|
static const QString DESCRIPTORS;
|
||||||
|
static const QString DESCRIPTORS_DESCRIPTION;
|
||||||
|
static const QString DESCRIPTORS_CAPACITY;
|
||||||
|
static const QString DESCRIPTORS_HOURS;
|
||||||
|
static const QString DESCRIPTORS_RESTRICTION;
|
||||||
|
static const QString DESCRIPTORS_MATURITY;
|
||||||
|
static const QString DESCRIPTORS_HOSTS;
|
||||||
|
static const QString DESCRIPTORS_TAGS;
|
||||||
|
static const QString DESCRIPTORS_IMG;
|
||||||
|
static const QString DESCRIPTORS_IMG_SRC;
|
||||||
|
static const QString DESCRIPTORS_IMG_TYPE;
|
||||||
|
static const QString DESCRIPTORS_IMG_SIZE;
|
||||||
|
static const QString DESCRIPTORS_IMG_UPDATED_AT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DomainMetadata();
|
||||||
|
|
||||||
|
// Returns the last set metadata
|
||||||
|
// If connected users have changed, metadata may need to be updated
|
||||||
|
// this should be checked by storing tic = getTic() between calls
|
||||||
|
// and testing it for equality before the next get (tic == getTic())
|
||||||
|
QJsonObject get() { return QJsonObject::fromVariantMap(_metadata); }
|
||||||
|
QJsonObject getUsers() { return QJsonObject::fromVariantMap(_metadata[USERS].toMap()); }
|
||||||
|
QJsonObject getDescriptors() { return QJsonObject::fromVariantMap(_metadata[DESCRIPTORS].toMap()); }
|
||||||
|
|
||||||
|
uint32_t getTic() { return _tic; }
|
||||||
|
|
||||||
|
void setDescriptors(QVariantMap& settings);
|
||||||
|
void updateUsers();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void usersChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QVariantMap _metadata;
|
||||||
|
uint32_t _tic{ 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_DomainMetadata_h
|
|
@ -94,9 +94,20 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
|
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||||
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
|
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||||
|
|
||||||
|
// update the metadata when a user (dis)connects
|
||||||
|
connect(this, &DomainServer::userConnected, &_metadata, &DomainMetadata::usersChanged);
|
||||||
|
connect(this, &DomainServer::userDisconnected, &_metadata, &DomainMetadata::usersChanged);
|
||||||
|
|
||||||
// make sure we hear about newly connected nodes from our gatekeeper
|
// make sure we hear about newly connected nodes from our gatekeeper
|
||||||
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
||||||
|
|
||||||
|
// if a connected node loses connection privileges, hang up on it
|
||||||
|
connect(&_gatekeeper, &DomainGatekeeper::killNode, this, &DomainServer::handleKillNode);
|
||||||
|
|
||||||
|
// if permissions are updated, relay the changes to the Node datastructures
|
||||||
|
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
||||||
|
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
||||||
|
|
||||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
||||||
// we either read a certificate and private key or were not passed one
|
// we either read a certificate and private key or were not passed one
|
||||||
// and completed login or did not need to
|
// and completed login or did not need to
|
||||||
|
@ -112,6 +123,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
|
|
||||||
optionallyGetTemporaryName(args);
|
optionallyGetTemporaryName(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update the metadata with current descriptors
|
||||||
|
_metadata.setDescriptors(_settingsManager.getSettingsMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainServer::~DomainServer() {
|
DomainServer::~DomainServer() {
|
||||||
|
@ -311,16 +325,11 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) {
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
// This implements a special case that handles OLD clients which don't know how to negotiate matching
|
// if this is a mismatching connect packet, we can't simply drop it on the floor
|
||||||
// protocol versions. We know these clients will sent DomainConnectRequest with older versions. We also
|
// send back a packet to the interface that tells them we refuse connection for a mismatch
|
||||||
// know these clients will show a warning dialog if they get an EntityData with a protocol version they
|
if (headerType == PacketType::DomainConnectRequest
|
||||||
// don't understand, so we can send them an empty EntityData with our latest version and they will
|
&& headerVersion != versionForPacketType(PacketType::DomainConnectRequest)) {
|
||||||
// warn the user that the protocol is not compatible
|
DomainGatekeeper::sendProtocolMismatchConnectionDenial(packet.getSenderSockAddr());
|
||||||
if (headerType == PacketType::DomainConnectRequest &&
|
|
||||||
headerVersion < static_cast<PacketVersion>(DomainConnectRequestVersion::HasProtocolVersions)) {
|
|
||||||
auto packetWithBadVersion = NLPacket::create(PacketType::EntityData);
|
|
||||||
nodeList->sendPacket(std::move(packetWithBadVersion), packet.getSenderSockAddr());
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// let the normal nodeList implementation handle all other packets.
|
// let the normal nodeList implementation handle all other packets.
|
||||||
|
@ -767,12 +776,16 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
|
void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
|
||||||
|
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
|
||||||
|
|
||||||
// reply back to the user with a PacketType::DomainList
|
// reply back to the user with a PacketType::DomainList
|
||||||
sendDomainListToNode(newNode, nodeData->getSendingSockAddr());
|
sendDomainListToNode(newNode, nodeData->getSendingSockAddr());
|
||||||
|
|
||||||
|
// if this node is a user (unassigned Agent), signal
|
||||||
|
if (newNode->getType() == NodeType::Agent && !nodeData->wasAssigned()) {
|
||||||
|
emit userConnected();
|
||||||
|
}
|
||||||
|
|
||||||
// send out this node to our other connected nodes
|
// send out this node to our other connected nodes
|
||||||
broadcastNewNode(newNode);
|
broadcastNewNode(newNode);
|
||||||
}
|
}
|
||||||
|
@ -789,8 +802,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
||||||
|
|
||||||
extendedHeaderStream << limitedNodeList->getSessionUUID();
|
extendedHeaderStream << limitedNodeList->getSessionUUID();
|
||||||
extendedHeaderStream << node->getUUID();
|
extendedHeaderStream << node->getUUID();
|
||||||
extendedHeaderStream << (quint8) node->isAllowedEditor();
|
extendedHeaderStream << node->getPermissions();
|
||||||
extendedHeaderStream << (quint8) node->getCanRez();
|
|
||||||
|
|
||||||
auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader);
|
auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader);
|
||||||
|
|
||||||
|
@ -1067,62 +1079,40 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr)
|
||||||
sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString());
|
sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
const QUuid& domainID = nodeList->getSessionUUID();
|
const QUuid& domainID = nodeList->getSessionUUID();
|
||||||
|
|
||||||
// setup the domain object to send to the data server
|
// Setup the domain object to send to the data server
|
||||||
const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
|
||||||
const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
|
||||||
|
|
||||||
QJsonObject domainObject;
|
QJsonObject domainObject;
|
||||||
|
|
||||||
if (!networkAddress.isEmpty()) {
|
if (!networkAddress.isEmpty()) {
|
||||||
|
static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
||||||
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
|
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||||
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
||||||
|
|
||||||
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
|
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
|
||||||
const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||||
|
|
||||||
domainObject[RESTRICTED_ACCESS_FLAG] =
|
// consider the domain to have restricted access if "anonymous" connections can't connect to the domain.
|
||||||
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||||
|
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
|
||||||
// figure out the breakdown of currently connected interface clients
|
|
||||||
int numConnectedUnassigned = 0;
|
|
||||||
QJsonObject userHostnames;
|
|
||||||
|
|
||||||
static const QString DEFAULT_HOSTNAME = "*";
|
|
||||||
|
|
||||||
nodeList->eachNode([&numConnectedUnassigned, &userHostnames](const SharedNodePointer& node) {
|
|
||||||
if (node->getLinkedData()) {
|
|
||||||
auto nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
|
||||||
|
|
||||||
if (!nodeData->wasAssigned()) {
|
|
||||||
++numConnectedUnassigned;
|
|
||||||
|
|
||||||
// increment the count for this hostname (or the default if we don't have one)
|
|
||||||
auto hostname = nodeData->getPlaceName().isEmpty() ? DEFAULT_HOSTNAME : nodeData->getPlaceName();
|
|
||||||
userHostnames[hostname] = userHostnames[hostname].toInt() + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Add the metadata to the heartbeat
|
||||||
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
||||||
static const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
|
auto tic = _metadata.getTic();
|
||||||
static const QString HEARTBEAT_USER_HOSTNAMES_KEY = "user_hostnames";
|
if (_metadataTic != tic) {
|
||||||
|
_metadataTic = tic;
|
||||||
|
_metadata.updateUsers();
|
||||||
|
}
|
||||||
|
domainObject[DOMAIN_HEARTBEAT_KEY] = _metadata.getUsers();
|
||||||
|
|
||||||
QJsonObject heartbeatObject;
|
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
||||||
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedUnassigned;
|
|
||||||
heartbeatObject[HEARTBEAT_USER_HOSTNAMES_KEY] = userHostnames;
|
|
||||||
|
|
||||||
domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject;
|
|
||||||
|
|
||||||
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
|
||||||
|
|
||||||
|
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
||||||
AccountManagerAuth::Required,
|
AccountManagerAuth::Required,
|
||||||
QNetworkAccessManager::PutOperation,
|
QNetworkAccessManager::PutOperation,
|
||||||
|
@ -1918,11 +1908,10 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::nodeKilled(SharedNodePointer node) {
|
void DomainServer::nodeKilled(SharedNodePointer node) {
|
||||||
|
|
||||||
// if this peer connected via ICE then remove them from our ICE peers hash
|
// if this peer connected via ICE then remove them from our ICE peers hash
|
||||||
_gatekeeper.removeICEPeer(node->getUUID());
|
_gatekeeper.removeICEPeer(node->getUUID());
|
||||||
|
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||||
|
|
||||||
if (nodeData) {
|
if (nodeData) {
|
||||||
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
|
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
|
||||||
|
@ -1934,15 +1923,22 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this node was an Agent ask DomainServerNodeData to potentially remove the interpolation we stored
|
|
||||||
nodeData->removeOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
|
||||||
uuidStringWithoutCurlyBraces(node->getUUID()));
|
|
||||||
|
|
||||||
// cleanup the connection secrets that we set up for this node (on the other nodes)
|
// cleanup the connection secrets that we set up for this node (on the other nodes)
|
||||||
foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) {
|
foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) {
|
||||||
SharedNodePointer otherNode = DependencyManager::get<LimitedNodeList>()->nodeWithUUID(otherNodeSessionUUID);
|
SharedNodePointer otherNode = DependencyManager::get<LimitedNodeList>()->nodeWithUUID(otherNodeSessionUUID);
|
||||||
if (otherNode) {
|
if (otherNode) {
|
||||||
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID());
|
static_cast<DomainServerNodeData*>(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->getType() == NodeType::Agent) {
|
||||||
|
// if this node was an Agent ask DomainServerNodeData to remove the interpolation we potentially stored
|
||||||
|
nodeData->removeOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||||
|
uuidStringWithoutCurlyBraces(node->getUUID()));
|
||||||
|
|
||||||
|
// if this node is a user (unassigned Agent), signal
|
||||||
|
if (!nodeData->wasAssigned()) {
|
||||||
|
emit userDisconnected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2113,35 +2109,42 @@ void DomainServer::processPathQueryPacket(QSharedPointer<ReceivedMessage> messag
|
||||||
void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
// This packet has been matched to a source node and they're asking not to be in the domain anymore
|
// This packet has been matched to a source node and they're asking not to be in the domain anymore
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
const QUuid& nodeUUID = message->getSourceID();
|
const QUuid& nodeUUID = message->getSourceID();
|
||||||
|
|
||||||
qDebug() << "Received a disconnect request from node with UUID" << nodeUUID;
|
qDebug() << "Received a disconnect request from node with UUID" << nodeUUID;
|
||||||
|
|
||||||
// we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode
|
// we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode
|
||||||
// packet to nodes that don't care about this type
|
// packet to nodes that don't care about this type
|
||||||
auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID);
|
auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID);
|
||||||
|
|
||||||
if (nodeToKill) {
|
if (nodeToKill) {
|
||||||
auto nodeType = nodeToKill->getType();
|
handleKillNode(nodeToKill);
|
||||||
limitedNodeList->killNodeWithUUID(nodeUUID);
|
|
||||||
|
|
||||||
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
|
|
||||||
|
|
||||||
removedNodePacket->reset();
|
|
||||||
removedNodePacket->write(nodeUUID.toRfc4122());
|
|
||||||
|
|
||||||
// broadcast out the DomainServerRemovedNode message
|
|
||||||
limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool {
|
|
||||||
// only send the removed node packet to nodes that care about the type of node this was
|
|
||||||
auto nodeLinkedData = dynamic_cast<DomainServerNodeData*>(otherNode->getLinkedData());
|
|
||||||
return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType);
|
|
||||||
}, [&limitedNodeList](const SharedNodePointer& otherNode){
|
|
||||||
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
|
||||||
|
auto nodeType = nodeToKill->getType();
|
||||||
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
const QUuid& nodeUUID = nodeToKill->getUUID();
|
||||||
|
|
||||||
|
limitedNodeList->killNodeWithUUID(nodeUUID);
|
||||||
|
|
||||||
|
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
|
||||||
|
|
||||||
|
removedNodePacket->reset();
|
||||||
|
removedNodePacket->write(nodeUUID.toRfc4122());
|
||||||
|
|
||||||
|
// broadcast out the DomainServerRemovedNode message
|
||||||
|
limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool {
|
||||||
|
// only send the removed node packet to nodes that care about the type of node this was
|
||||||
|
auto nodeLinkedData = dynamic_cast<DomainServerNodeData*>(otherNode->getLinkedData());
|
||||||
|
return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType);
|
||||||
|
}, [&limitedNodeList](const SharedNodePointer& otherNode){
|
||||||
|
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <LimitedNodeList.h>
|
#include <LimitedNodeList.h>
|
||||||
|
|
||||||
#include "DomainGatekeeper.h"
|
#include "DomainGatekeeper.h"
|
||||||
|
#include "DomainMetadata.h"
|
||||||
#include "DomainServerSettingsManager.h"
|
#include "DomainServerSettingsManager.h"
|
||||||
#include "DomainServerWebSessionData.h"
|
#include "DomainServerWebSessionData.h"
|
||||||
#include "WalletTransaction.h"
|
#include "WalletTransaction.h"
|
||||||
|
@ -91,6 +92,8 @@ private slots:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void iceServerChanged();
|
void iceServerChanged();
|
||||||
|
void userConnected();
|
||||||
|
void userDisconnected();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||||
|
@ -111,6 +114,8 @@ private:
|
||||||
|
|
||||||
unsigned int countConnectedUsers();
|
unsigned int countConnectedUsers();
|
||||||
|
|
||||||
|
void handleKillNode(SharedNodePointer nodeToKill);
|
||||||
|
|
||||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr);
|
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr);
|
||||||
|
|
||||||
QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB);
|
QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB);
|
||||||
|
@ -167,6 +172,9 @@ private:
|
||||||
|
|
||||||
DomainServerSettingsManager _settingsManager;
|
DomainServerSettingsManager _settingsManager;
|
||||||
|
|
||||||
|
DomainMetadata _metadata;
|
||||||
|
uint32_t _metadataTic{ 0 };
|
||||||
|
|
||||||
HifiSockAddr _iceServerSocket;
|
HifiSockAddr _iceServerSocket;
|
||||||
std::unique_ptr<NLPacket> _iceServerHeartbeatPacket;
|
std::unique_ptr<NLPacket> _iceServerHeartbeatPacket;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtCore/QDir>
|
#include <QtCore/QDir>
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
|
@ -26,6 +28,9 @@
|
||||||
|
|
||||||
#include "DomainServerSettingsManager.h"
|
#include "DomainServerSettingsManager.h"
|
||||||
|
|
||||||
|
#define WANT_DEBUG 1
|
||||||
|
|
||||||
|
|
||||||
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
|
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
|
||||||
|
|
||||||
const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
||||||
|
@ -44,7 +49,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
|
||||||
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
||||||
descriptionFile.open(QIODevice::ReadOnly);
|
descriptionFile.open(QIODevice::ReadOnly);
|
||||||
|
|
||||||
QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll());
|
QJsonParseError parseError;
|
||||||
|
QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll(), &parseError);
|
||||||
|
|
||||||
if (descriptionDocument.isObject()) {
|
if (descriptionDocument.isObject()) {
|
||||||
QJsonObject descriptionObject = descriptionDocument.object();
|
QJsonObject descriptionObject = descriptionDocument.object();
|
||||||
|
@ -63,8 +69,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
|
||||||
}
|
}
|
||||||
|
|
||||||
static const QString MISSING_SETTINGS_DESC_MSG =
|
static const QString MISSING_SETTINGS_DESC_MSG =
|
||||||
QString("Did not find settings decription in JSON at %1 - Unable to continue. domain-server will quit.")
|
QString("Did not find settings decription in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3")
|
||||||
.arg(SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
.arg(SETTINGS_DESCRIPTION_RELATIVE_PATH).arg(parseError.errorString()).arg(parseError.offset);
|
||||||
static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6;
|
static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6;
|
||||||
|
|
||||||
QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection,
|
||||||
|
@ -88,7 +94,8 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||||
_configMap.loadMasterAndUserConfig(argumentList);
|
_argumentList = argumentList;
|
||||||
|
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||||
|
|
||||||
// What settings version were we before and what are we using now?
|
// What settings version were we before and what are we using now?
|
||||||
// Do we need to do any re-mapping?
|
// Do we need to do any re-mapping?
|
||||||
|
@ -97,6 +104,11 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
double oldVersion = appSettings.value(JSON_SETTINGS_VERSION_KEY, 0.0).toDouble();
|
double oldVersion = appSettings.value(JSON_SETTINGS_VERSION_KEY, 0.0).toDouble();
|
||||||
|
|
||||||
if (oldVersion != _descriptionVersion) {
|
if (oldVersion != _descriptionVersion) {
|
||||||
|
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
||||||
|
const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access";
|
||||||
|
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
|
||||||
|
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
|
||||||
|
|
||||||
qDebug() << "Previous domain-server settings version was"
|
qDebug() << "Previous domain-server settings version was"
|
||||||
<< QString::number(oldVersion, 'g', 8) << "and the new version is"
|
<< QString::number(oldVersion, 'g', 8) << "and the new version is"
|
||||||
<< QString::number(_descriptionVersion, 'g', 8) << "- checking if any re-mapping is required";
|
<< QString::number(_descriptionVersion, 'g', 8) << "- checking if any re-mapping is required";
|
||||||
|
@ -127,7 +139,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
persistToFile();
|
persistToFile();
|
||||||
|
|
||||||
// reload the master and user config so that the merged config is right
|
// reload the master and user config so that the merged config is right
|
||||||
_configMap.loadMasterAndUserConfig(argumentList);
|
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +175,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
persistToFile();
|
persistToFile();
|
||||||
|
|
||||||
// reload the master and user config so that the merged config is right
|
// reload the master and user config so that the merged config is right
|
||||||
_configMap.loadMasterAndUserConfig(argumentList);
|
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -186,15 +198,216 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
persistToFile();
|
persistToFile();
|
||||||
|
|
||||||
// reload the master and user config so the merged config is correct
|
// reload the master and user config so the merged config is correct
|
||||||
_configMap.loadMasterAndUserConfig(argumentList);
|
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 1.4) {
|
||||||
|
// This was prior to the permissions-grid in the domain-server settings page
|
||||||
|
bool isRestrictedAccess = valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
||||||
|
QStringList allowedUsers = valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
|
||||||
|
QStringList allowedEditors = valueOrDefaultValueForKeyPath(ALLOWED_EDITORS_SETTINGS_KEYPATH).toStringList();
|
||||||
|
bool onlyEditorsAreRezzers = valueOrDefaultValueForKeyPath(EDITORS_ARE_REZZERS_KEYPATH).toBool();
|
||||||
|
|
||||||
|
_standardAgentPermissions[NodePermissions::standardNameLocalhost].reset(
|
||||||
|
new NodePermissions(NodePermissions::standardNameLocalhost));
|
||||||
|
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->setAll(true);
|
||||||
|
_standardAgentPermissions[NodePermissions::standardNameAnonymous].reset(
|
||||||
|
new NodePermissions(NodePermissions::standardNameAnonymous));
|
||||||
|
_standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset(
|
||||||
|
new NodePermissions(NodePermissions::standardNameLoggedIn));
|
||||||
|
|
||||||
|
if (isRestrictedAccess) {
|
||||||
|
// only users in allow-users list can connect
|
||||||
|
_standardAgentPermissions[NodePermissions::standardNameAnonymous]->canConnectToDomain = false;
|
||||||
|
_standardAgentPermissions[NodePermissions::standardNameLoggedIn]->canConnectToDomain = false;
|
||||||
|
} // else anonymous and logged-in retain default of canConnectToDomain = true
|
||||||
|
|
||||||
|
foreach (QString allowedUser, allowedUsers) {
|
||||||
|
// even if isRestrictedAccess is false, we have to add explicit rows for these users.
|
||||||
|
// defaults to canConnectToDomain = true
|
||||||
|
_agentPermissions[allowedUser].reset(new NodePermissions(allowedUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (QString allowedEditor, allowedEditors) {
|
||||||
|
if (!_agentPermissions.contains(allowedEditor)) {
|
||||||
|
_agentPermissions[allowedEditor].reset(new NodePermissions(allowedEditor));
|
||||||
|
if (isRestrictedAccess) {
|
||||||
|
// they can change locks, but can't connect.
|
||||||
|
_agentPermissions[allowedEditor]->canConnectToDomain = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_agentPermissions[allowedEditor]->canAdjustLocks = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QHash<QString, NodePermissionsPointer>> permissionsSets;
|
||||||
|
permissionsSets << _standardAgentPermissions << _agentPermissions;
|
||||||
|
foreach (auto permissionsSet, permissionsSets) {
|
||||||
|
foreach (QString userName, permissionsSet.keys()) {
|
||||||
|
if (onlyEditorsAreRezzers) {
|
||||||
|
permissionsSet[userName]->canRezPermanentEntities = permissionsSet[userName]->canAdjustLocks;
|
||||||
|
permissionsSet[userName]->canRezTemporaryEntities = permissionsSet[userName]->canAdjustLocks;
|
||||||
|
} else {
|
||||||
|
permissionsSet[userName]->canRezPermanentEntities = true;
|
||||||
|
permissionsSet[userName]->canRezTemporaryEntities = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packPermissions();
|
||||||
|
_standardAgentPermissions.clear();
|
||||||
|
_agentPermissions.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unpackPermissions();
|
||||||
|
|
||||||
// write the current description version to our settings
|
// write the current description version to our settings
|
||||||
appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion);
|
appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServerSettingsManager::packPermissionsForMap(QString mapName,
|
||||||
|
QHash<QString, NodePermissionsPointer> agentPermissions,
|
||||||
|
QString keyPath) {
|
||||||
|
QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security");
|
||||||
|
if (!security || !security->canConvert(QMetaType::QVariantMap)) {
|
||||||
|
security = valueForKeyPath(_configMap.getUserConfig(), "security", true);
|
||||||
|
(*security) = QVariantMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// save settings for anonymous / logged-in / localhost
|
||||||
|
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath);
|
||||||
|
if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) {
|
||||||
|
permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true);
|
||||||
|
(*permissions) = QVariantList();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList* permissionsList = reinterpret_cast<QVariantList*>(permissions);
|
||||||
|
(*permissionsList).clear();
|
||||||
|
foreach (QString userName, agentPermissions.keys()) {
|
||||||
|
*permissionsList += agentPermissions[userName]->toVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServerSettingsManager::packPermissions() {
|
||||||
|
// transfer details from _agentPermissions to _configMap
|
||||||
|
packPermissionsForMap("standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH);
|
||||||
|
|
||||||
|
// save settings for specific users
|
||||||
|
packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH);
|
||||||
|
|
||||||
|
persistToFile();
|
||||||
|
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServerSettingsManager::unpackPermissions() {
|
||||||
|
// transfer details from _configMap to _agentPermissions;
|
||||||
|
|
||||||
|
_standardAgentPermissions.clear();
|
||||||
|
_agentPermissions.clear();
|
||||||
|
|
||||||
|
bool foundLocalhost = false;
|
||||||
|
bool foundAnonymous = false;
|
||||||
|
bool foundLoggedIn = false;
|
||||||
|
bool needPack = false;
|
||||||
|
|
||||||
|
QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH);
|
||||||
|
if (!standardPermissions || !standardPermissions->canConvert(QMetaType::QVariantList)) {
|
||||||
|
qDebug() << "failed to extract standard permissions from settings.";
|
||||||
|
standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH, true);
|
||||||
|
(*standardPermissions) = QVariantList();
|
||||||
|
}
|
||||||
|
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH);
|
||||||
|
if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) {
|
||||||
|
qDebug() << "failed to extract permissions from settings.";
|
||||||
|
permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH, true);
|
||||||
|
(*permissions) = QVariantList();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QVariant> standardPermissionsList = standardPermissions->toList();
|
||||||
|
foreach (QVariant permsHash, standardPermissionsList) {
|
||||||
|
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
|
||||||
|
QString id = perms->getID();
|
||||||
|
foundLocalhost |= (id == NodePermissions::standardNameLocalhost);
|
||||||
|
foundAnonymous |= (id == NodePermissions::standardNameAnonymous);
|
||||||
|
foundLoggedIn |= (id == NodePermissions::standardNameLoggedIn);
|
||||||
|
if (_standardAgentPermissions.contains(id)) {
|
||||||
|
qDebug() << "duplicate name in standard permissions table: " << id;
|
||||||
|
_standardAgentPermissions[id] |= perms;
|
||||||
|
needPack = true;
|
||||||
|
} else {
|
||||||
|
_standardAgentPermissions[id] = perms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QVariant> permissionsList = permissions->toList();
|
||||||
|
foreach (QVariant permsHash, permissionsList) {
|
||||||
|
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
|
||||||
|
QString id = perms->getID();
|
||||||
|
if (_agentPermissions.contains(id)) {
|
||||||
|
qDebug() << "duplicate name in permissions table: " << id;
|
||||||
|
_agentPermissions[id] |= perms;
|
||||||
|
needPack = true;
|
||||||
|
} else {
|
||||||
|
_agentPermissions[id] = perms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any of the standard names are missing, add them
|
||||||
|
if (!foundLocalhost) {
|
||||||
|
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) };
|
||||||
|
perms->setAll(true);
|
||||||
|
_standardAgentPermissions[perms->getID()] = perms;
|
||||||
|
needPack = true;
|
||||||
|
}
|
||||||
|
if (!foundAnonymous) {
|
||||||
|
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) };
|
||||||
|
_standardAgentPermissions[perms->getID()] = perms;
|
||||||
|
needPack = true;
|
||||||
|
}
|
||||||
|
if (!foundLoggedIn) {
|
||||||
|
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) };
|
||||||
|
_standardAgentPermissions[perms->getID()] = perms;
|
||||||
|
needPack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needPack) {
|
||||||
|
packPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WANT_DEBUG
|
||||||
|
qDebug() << "--------------- permissions ---------------------";
|
||||||
|
QList<QHash<QString, NodePermissionsPointer>> permissionsSets;
|
||||||
|
permissionsSets << _standardAgentPermissions << _agentPermissions;
|
||||||
|
foreach (auto permissionSet, permissionsSets) {
|
||||||
|
QHashIterator<QString, NodePermissionsPointer> i(permissionSet);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
NodePermissionsPointer perms = i.value();
|
||||||
|
qDebug() << i.key() << perms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const QString& name) const {
|
||||||
|
if (_standardAgentPermissions.contains(name)) {
|
||||||
|
return *(_standardAgentPermissions[name].get());
|
||||||
|
}
|
||||||
|
NodePermissions nullPermissions;
|
||||||
|
nullPermissions.setAll(false);
|
||||||
|
return nullPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const {
|
||||||
|
if (_agentPermissions.contains(name)) {
|
||||||
|
return *(_agentPermissions[name].get());
|
||||||
|
}
|
||||||
|
NodePermissions nullPermissions;
|
||||||
|
nullPermissions.setAll(false);
|
||||||
|
return nullPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) {
|
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) {
|
||||||
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
|
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
|
||||||
|
|
||||||
|
@ -257,7 +470,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
||||||
qDebug() << "DomainServerSettingsManager postedObject -" << postedObject;
|
qDebug() << "DomainServerSettingsManager postedObject -" << postedObject;
|
||||||
|
|
||||||
// we recurse one level deep below each group for the appropriate setting
|
// we recurse one level deep below each group for the appropriate setting
|
||||||
recurseJSONObjectAndOverwriteSettings(postedObject);
|
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject);
|
||||||
|
|
||||||
// store whatever the current _settingsMap is to file
|
// store whatever the current _settingsMap is to file
|
||||||
persistToFile();
|
persistToFile();
|
||||||
|
@ -267,8 +480,13 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
||||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||||
|
|
||||||
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
if (restartRequired) {
|
||||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||||
|
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||||
|
} else {
|
||||||
|
unpackPermissions();
|
||||||
|
emit updateNodePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||||
|
@ -282,7 +500,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
||||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
||||||
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
|
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
|
||||||
|
|
||||||
|
|
||||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,6 +675,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
||||||
// TODO: we still need to recurse here with the description in case values in the array have special types
|
// TODO: we still need to recurse here with the description in case values in the array have special types
|
||||||
settingMap[key] = newValue.toArray().toVariantList();
|
settingMap[key] = newValue.toArray().toVariantList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) {
|
QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) {
|
||||||
|
@ -471,9 +690,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
|
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
|
||||||
auto& settingsVariant = _configMap.getUserConfig();
|
auto& settingsVariant = _configMap.getUserConfig();
|
||||||
|
bool needRestart = false;
|
||||||
|
|
||||||
// Iterate on the setting groups
|
// Iterate on the setting groups
|
||||||
foreach(const QString& rootKey, postedObject.keys()) {
|
foreach(const QString& rootKey, postedObject.keys()) {
|
||||||
QJsonValue rootValue = postedObject[rootKey];
|
QJsonValue rootValue = postedObject[rootKey];
|
||||||
|
@ -521,6 +741,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
|
|
||||||
if (!matchingDescriptionObject.isEmpty()) {
|
if (!matchingDescriptionObject.isEmpty()) {
|
||||||
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
||||||
|
if (rootKey != "security") {
|
||||||
|
needRestart = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
|
qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
|
||||||
}
|
}
|
||||||
|
@ -534,6 +757,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
if (!matchingDescriptionObject.isEmpty()) {
|
if (!matchingDescriptionObject.isEmpty()) {
|
||||||
QJsonValue settingValue = rootValue.toObject()[settingKey];
|
QJsonValue settingValue = rootValue.toObject()[settingKey];
|
||||||
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
||||||
|
if (rootKey != "security") {
|
||||||
|
needRestart = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
|
qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
|
||||||
"- cannot update setting.";
|
"- cannot update setting.";
|
||||||
|
@ -549,9 +775,42 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
|
|
||||||
// re-merge the user and master configs after a settings change
|
// re-merge the user and master configs after a settings change
|
||||||
_configMap.mergeMasterAndUserConfigs();
|
_configMap.mergeMasterAndUserConfigs();
|
||||||
|
|
||||||
|
return needRestart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare two members of a permissions list
|
||||||
|
bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
|
||||||
|
if (!v1.canConvert(QMetaType::QVariantMap) ||
|
||||||
|
!v2.canConvert(QMetaType::QVariantMap)) {
|
||||||
|
return v1.toString() < v2.toString();
|
||||||
|
}
|
||||||
|
QVariantMap m1 = v1.toMap();
|
||||||
|
QVariantMap m2 = v2.toMap();
|
||||||
|
|
||||||
|
if (!m1.contains("permissions_id") ||
|
||||||
|
!m2.contains("permissions_id")) {
|
||||||
|
return v1.toString() < v2.toString();
|
||||||
|
}
|
||||||
|
return m1["permissions_id"].toString() < m2["permissions_id"].toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServerSettingsManager::sortPermissions() {
|
||||||
|
// sort the permission-names
|
||||||
|
QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH);
|
||||||
|
if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) {
|
||||||
|
QList<QVariant>* standardPermissionsList = reinterpret_cast<QVariantList*>(standardPermissions);
|
||||||
|
std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan);
|
||||||
|
}
|
||||||
|
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH);
|
||||||
|
if (permissions && permissions->canConvert(QMetaType::QVariantList)) {
|
||||||
|
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions);
|
||||||
|
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServerSettingsManager::persistToFile() {
|
void DomainServerSettingsManager::persistToFile() {
|
||||||
|
sortPermissions();
|
||||||
|
|
||||||
// make sure we have the dir the settings file is supposed to live in
|
// make sure we have the dir the settings file is supposed to live in
|
||||||
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
|
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
|
||||||
|
|
|
@ -19,14 +19,14 @@
|
||||||
#include <HTTPManager.h>
|
#include <HTTPManager.h>
|
||||||
|
|
||||||
#include <ReceivedMessage.h>
|
#include <ReceivedMessage.h>
|
||||||
|
#include "NodePermissions.h"
|
||||||
|
|
||||||
const QString SETTINGS_PATHS_KEY = "paths";
|
const QString SETTINGS_PATHS_KEY = "paths";
|
||||||
|
|
||||||
const QString SETTINGS_PATH = "/settings";
|
const QString SETTINGS_PATH = "/settings";
|
||||||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||||
|
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
|
||||||
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
|
||||||
const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access";
|
|
||||||
|
|
||||||
class DomainServerSettingsManager : public QObject {
|
class DomainServerSettingsManager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -41,16 +41,29 @@ public:
|
||||||
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
||||||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||||
|
|
||||||
|
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); }
|
||||||
|
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); }
|
||||||
|
NodePermissions getStandardPermissionsForName(const QString& name) const;
|
||||||
|
NodePermissions getPermissionsForName(const QString& name) const;
|
||||||
|
QStringList getAllNames() { return _agentPermissions.keys(); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void updateNodePermissions();
|
||||||
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QStringList _argumentList;
|
||||||
|
|
||||||
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
||||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject);
|
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject);
|
||||||
|
|
||||||
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||||
const QJsonObject& settingDescription);
|
const QJsonObject& settingDescription);
|
||||||
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
|
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
|
||||||
|
void sortPermissions();
|
||||||
void persistToFile();
|
void persistToFile();
|
||||||
|
|
||||||
double _descriptionVersion;
|
double _descriptionVersion;
|
||||||
|
@ -58,6 +71,12 @@ private:
|
||||||
HifiConfigVariantMap _configMap;
|
HifiConfigVariantMap _configMap;
|
||||||
|
|
||||||
friend class DomainServer;
|
friend class DomainServer;
|
||||||
|
|
||||||
|
void packPermissionsForMap(QString mapName, QHash<QString, NodePermissionsPointer> agentPermissions, QString keyPath);
|
||||||
|
void packPermissions();
|
||||||
|
void unpackPermissions();
|
||||||
|
QHash<QString, NodePermissionsPointer> _standardAgentPermissions; // anonymous, logged-in, localhost
|
||||||
|
QHash<QString, NodePermissionsPointer> _agentPermissions; // specific account-names
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_DomainServerSettingsManager_h
|
#endif // hifi_DomainServerSettingsManager_h
|
||||||
|
|
|
@ -261,6 +261,7 @@ Window {
|
||||||
HifiControls.Button {
|
HifiControls.Button {
|
||||||
text: "Load Defaults"
|
text: "Load Defaults"
|
||||||
color: hifi.buttons.black
|
color: hifi.buttons.black
|
||||||
|
height: 26
|
||||||
onClicked: loadDefaults()
|
onClicked: loadDefaults()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -630,7 +630,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
||||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
||||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
||||||
connect(&domainHandler, &DomainHandler::resetting, nodeList.data(), &NodeList::resetDomainServerCheckInVersion);
|
|
||||||
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused);
|
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused);
|
||||||
|
|
||||||
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
|
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
|
||||||
|
@ -654,9 +653,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated);
|
connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated);
|
||||||
connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID);
|
connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID);
|
||||||
connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID);
|
connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID);
|
||||||
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, this, &Application::limitOfSilentDomainCheckInsReached);
|
|
||||||
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
|
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
|
||||||
|
|
||||||
|
// you might think we could just do this in NodeList but we only want this connection for Interface
|
||||||
|
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset);
|
||||||
|
|
||||||
// connect to appropriate slots on AccountManager
|
// connect to appropriate slots on AccountManager
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
|
||||||
|
@ -1074,8 +1075,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) {
|
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) {
|
||||||
switch (static_cast<DomainHandler::ConnectionRefusedReason>(reasonCode)) {
|
switch (static_cast<DomainHandler::ConnectionRefusedReason>(reasonCode)) {
|
||||||
case DomainHandler::ConnectionRefusedReason::ProtocolMismatch:
|
case DomainHandler::ConnectionRefusedReason::ProtocolMismatch:
|
||||||
notifyPacketVersionMismatch();
|
|
||||||
break;
|
|
||||||
case DomainHandler::ConnectionRefusedReason::TooManyUsers:
|
case DomainHandler::ConnectionRefusedReason::TooManyUsers:
|
||||||
case DomainHandler::ConnectionRefusedReason::Unknown: {
|
case DomainHandler::ConnectionRefusedReason::Unknown: {
|
||||||
QString message = "Unable to connect to the location you are visiting.\n";
|
QString message = "Unable to connect to the location you are visiting.\n";
|
||||||
|
@ -4293,7 +4292,7 @@ void Application::nodeActivated(SharedNodePointer node) {
|
||||||
if (assetDialog) {
|
if (assetDialog) {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
if (nodeList->getThisNodeCanRez()) {
|
if (nodeList->getThisNodeCanWriteAssets()) {
|
||||||
// call reload on the shown asset browser dialog to get the mappings (if permissions allow)
|
// call reload on the shown asset browser dialog to get the mappings (if permissions allow)
|
||||||
QMetaObject::invokeMethod(assetDialog, "reload");
|
QMetaObject::invokeMethod(assetDialog, "reload");
|
||||||
} else {
|
} else {
|
||||||
|
@ -4619,17 +4618,6 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const {
|
||||||
Physics::setSessionUUID(sessionUUID);
|
Physics::setSessionUUID(sessionUUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// If we're not getting anything back from the domain server checkin, it might be that the domain speaks an
|
|
||||||
// older version of the DomainConnectRequest protocol. We will attempt to send and older version of DomainConnectRequest.
|
|
||||||
// We won't actually complete the connection, but if the server responds, we know that it needs to be upgraded (or we
|
|
||||||
// need to be downgraded to talk to it).
|
|
||||||
void Application::limitOfSilentDomainCheckInsReached() {
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
nodeList->downgradeDomainServerCheckInVersion(); // attempt to use an older domain checkin version
|
|
||||||
nodeList->reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Application::askToSetAvatarUrl(const QString& url) {
|
bool Application::askToSetAvatarUrl(const QString& url) {
|
||||||
QUrl realUrl(url);
|
QUrl realUrl(url);
|
||||||
if (realUrl.isLocalFile()) {
|
if (realUrl.isLocalFile()) {
|
||||||
|
@ -4800,7 +4788,7 @@ void Application::toggleRunningScriptsWidget() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::toggleAssetServerWidget(QString filePath) {
|
void Application::toggleAssetServerWidget(QString filePath) {
|
||||||
if (!DependencyManager::get<NodeList>()->getThisNodeCanRez()) {
|
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -318,7 +318,6 @@ private slots:
|
||||||
bool displayAvatarAttachmentConfirmationDialog(const QString& name) const;
|
bool displayAvatarAttachmentConfirmationDialog(const QString& name) const;
|
||||||
|
|
||||||
void setSessionUUID(const QUuid& sessionUUID) const;
|
void setSessionUUID(const QUuid& sessionUUID) const;
|
||||||
void limitOfSilentDomainCheckInsReached();
|
|
||||||
|
|
||||||
void domainChanged(const QString& domainHostname);
|
void domainChanged(const QString& domainHostname);
|
||||||
void updateWindowTitle() const;
|
void updateWindowTitle() const;
|
||||||
|
|
|
@ -10,16 +10,20 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
#ifdef HAS_BUGSPLAT
|
#include "Application.h"
|
||||||
#include "CrashReporter.h"
|
#include "CrashReporter.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
#include <new.h>
|
#include <new.h>
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#include <DbgHelp.h>
|
||||||
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
|
||||||
|
#pragma comment(lib, "Dbghelp.lib")
|
||||||
|
|
||||||
// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information
|
// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information
|
||||||
// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li
|
// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li
|
||||||
// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so
|
// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so
|
||||||
|
@ -77,13 +81,37 @@ BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn)
|
||||||
return bRet;
|
return bRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printStackTrace(ULONG framesToSkip = 1) {
|
||||||
|
HANDLE process = GetCurrentProcess();
|
||||||
|
SymInitialize(process, NULL, TRUE);
|
||||||
|
void* stack[100];
|
||||||
|
uint16_t frames = CaptureStackBackTrace(framesToSkip, 100, stack, NULL);
|
||||||
|
SYMBOL_INFO* symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
|
||||||
|
symbol->MaxNameLen = 255;
|
||||||
|
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < frames; ++i) {
|
||||||
|
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
|
||||||
|
qWarning() << QString("%1: %2 - 0x%0X").arg(QString::number(frames - i - 1), QString(symbol->Name), QString::number(symbol->Address, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
free(symbol);
|
||||||
|
|
||||||
|
// Try to force the log to sync to the filesystem
|
||||||
|
auto app = qApp;
|
||||||
|
if (app && app->getLogger()) {
|
||||||
|
app->getLogger()->sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void handleSignal(int signal) {
|
void handleSignal(int signal) {
|
||||||
// Throw so BugSplat can handle
|
// Throw so BugSplat can handle
|
||||||
throw(signal);
|
throw(signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlePureVirtualCall() {
|
void __cdecl handlePureVirtualCall() {
|
||||||
|
qWarning() << "Pure virtual function call detected";
|
||||||
|
printStackTrace(2);
|
||||||
// Throw so BugSplat can handle
|
// Throw so BugSplat can handle
|
||||||
throw("ERROR: Pure virtual call");
|
throw("ERROR: Pure virtual call");
|
||||||
}
|
}
|
||||||
|
@ -107,6 +135,8 @@ _purecall_handler __cdecl noop_set_purecall_handler(_purecall_handler pNew) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAS_BUGSPLAT
|
||||||
|
|
||||||
static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY;
|
static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY;
|
||||||
|
|
||||||
CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version)
|
CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version)
|
||||||
|
@ -133,3 +163,4 @@ CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicati
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
|
@ -115,3 +115,7 @@ QString FileLogger::getLogData() {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FileLogger::sync() {
|
||||||
|
_persistThreadInstance->waitIdle();
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ public:
|
||||||
virtual void addMessage(const QString&) override;
|
virtual void addMessage(const QString&) override;
|
||||||
virtual QString getLogData() override;
|
virtual QString getLogData() override;
|
||||||
virtual void locateLog() override;
|
virtual void locateLog() override;
|
||||||
|
void sync();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void rollingLogFile(QString newFilename);
|
void rollingLogFile(QString newFilename);
|
||||||
|
|
|
@ -136,8 +136,8 @@ Menu::Menu() {
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_A,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_A,
|
||||||
qApp, SLOT(toggleAssetServerWidget()));
|
qApp, SLOT(toggleAssetServerWidget()));
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
QObject::connect(nodeList.data(), &NodeList::canRezChanged, assetServerAction, &QAction::setEnabled);
|
QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled);
|
||||||
assetServerAction->setEnabled(nodeList->getThisNodeCanRez());
|
assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets());
|
||||||
|
|
||||||
// Edit > Package Model... [advanced]
|
// Edit > Package Model... [advanced]
|
||||||
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
|
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
|
||||||
|
@ -542,6 +542,9 @@ Menu::Menu() {
|
||||||
#if (PR_BUILD || DEV_BUILD)
|
#if (PR_BUILD || DEV_BUILD)
|
||||||
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false,
|
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false,
|
||||||
qApp, SLOT(sendWrongProtocolVersionsSignature(bool)));
|
qApp, SLOT(sendWrongProtocolVersionsSignature(bool)));
|
||||||
|
|
||||||
|
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false,
|
||||||
|
nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool)));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,7 @@ namespace MenuOption {
|
||||||
const QString RunTimingTests = "Run Timing Tests";
|
const QString RunTimingTests = "Run Timing Tests";
|
||||||
const QString ScriptEditor = "Script Editor...";
|
const QString ScriptEditor = "Script Editor...";
|
||||||
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
||||||
|
const QString SendWrongDSConnectVersion = "Send wrong DS connect version";
|
||||||
const QString SendWrongProtocolVersion = "Send wrong protocol version";
|
const QString SendWrongProtocolVersion = "Send wrong protocol version";
|
||||||
const QString SetHomeLocation = "Set Home Location";
|
const QString SetHomeLocation = "Set Home Location";
|
||||||
const QString ShowDSConnectTable = "Show Domain Connection Timing";
|
const QString ShowDSConnectTable = "Show Domain Connection Timing";
|
||||||
|
|
|
@ -84,7 +84,6 @@ Avatar::Avatar(RigPointer rig) :
|
||||||
_acceleration(0.0f),
|
_acceleration(0.0f),
|
||||||
_lastAngularVelocity(0.0f),
|
_lastAngularVelocity(0.0f),
|
||||||
_lastOrientation(),
|
_lastOrientation(),
|
||||||
_leanScale(0.5f),
|
|
||||||
_worldUpDirection(DEFAULT_UP_DIRECTION),
|
_worldUpDirection(DEFAULT_UP_DIRECTION),
|
||||||
_moving(false),
|
_moving(false),
|
||||||
_initialized(false),
|
_initialized(false),
|
||||||
|
|
|
@ -210,7 +210,6 @@ protected:
|
||||||
glm::vec3 _angularAcceleration;
|
glm::vec3 _angularAcceleration;
|
||||||
glm::quat _lastOrientation;
|
glm::quat _lastOrientation;
|
||||||
|
|
||||||
float _leanScale;
|
|
||||||
glm::vec3 _worldUpDirection;
|
glm::vec3 _worldUpDirection;
|
||||||
float _stringLength;
|
float _stringLength;
|
||||||
bool _moving; ///< set when position is changing
|
bool _moving; ///< set when position is changing
|
||||||
|
|
|
@ -54,8 +54,6 @@ Head::Head(Avatar* owningAvatar) :
|
||||||
_deltaPitch(0.0f),
|
_deltaPitch(0.0f),
|
||||||
_deltaYaw(0.0f),
|
_deltaYaw(0.0f),
|
||||||
_deltaRoll(0.0f),
|
_deltaRoll(0.0f),
|
||||||
_deltaLeanSideways(0.0f),
|
|
||||||
_deltaLeanForward(0.0f),
|
|
||||||
_isCameraMoving(false),
|
_isCameraMoving(false),
|
||||||
_isLookingAtMe(false),
|
_isLookingAtMe(false),
|
||||||
_lookingAtMeStarted(0),
|
_lookingAtMeStarted(0),
|
||||||
|
@ -70,7 +68,6 @@ void Head::init() {
|
||||||
|
|
||||||
void Head::reset() {
|
void Head::reset() {
|
||||||
_baseYaw = _basePitch = _baseRoll = 0.0f;
|
_baseYaw = _basePitch = _baseRoll = 0.0f;
|
||||||
_leanForward = _leanSideways = 0.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||||
|
@ -118,13 +115,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
||||||
auto eyeTracker = DependencyManager::get<EyeTracker>();
|
auto eyeTracker = DependencyManager::get<EyeTracker>();
|
||||||
_isEyeTrackerConnected = eyeTracker->isTracking();
|
_isEyeTrackerConnected = eyeTracker->isTracking();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Twist the upper body to follow the rotation of the head, but only do this with my avatar,
|
|
||||||
// since everyone else will see the full joint rotations for other people.
|
|
||||||
const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f;
|
|
||||||
const float BODY_FOLLOW_HEAD_FACTOR = 0.66f;
|
|
||||||
float currentTwist = getTorsoTwist();
|
|
||||||
setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(_isFaceTrackerConnected || billboard)) {
|
if (!(_isFaceTrackerConnected || billboard)) {
|
||||||
|
@ -301,17 +291,13 @@ void Head::applyEyelidOffset(glm::quat headOrientation) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Head::relaxLean(float deltaTime) {
|
void Head::relax(float deltaTime) {
|
||||||
// restore rotation, lean to neutral positions
|
// restore rotation, lean to neutral positions
|
||||||
const float LEAN_RELAXATION_PERIOD = 0.25f; // seconds
|
const float LEAN_RELAXATION_PERIOD = 0.25f; // seconds
|
||||||
float relaxationFactor = 1.0f - glm::min(deltaTime / LEAN_RELAXATION_PERIOD, 1.0f);
|
float relaxationFactor = 1.0f - glm::min(deltaTime / LEAN_RELAXATION_PERIOD, 1.0f);
|
||||||
_deltaYaw *= relaxationFactor;
|
_deltaYaw *= relaxationFactor;
|
||||||
_deltaPitch *= relaxationFactor;
|
_deltaPitch *= relaxationFactor;
|
||||||
_deltaRoll *= relaxationFactor;
|
_deltaRoll *= relaxationFactor;
|
||||||
_leanSideways *= relaxationFactor;
|
|
||||||
_leanForward *= relaxationFactor;
|
|
||||||
_deltaLeanSideways *= relaxationFactor;
|
|
||||||
_deltaLeanForward *= relaxationFactor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Head::setScale (float scale) {
|
void Head::setScale (float scale) {
|
||||||
|
@ -419,8 +405,3 @@ float Head::getFinalPitch() const {
|
||||||
float Head::getFinalRoll() const {
|
float Head::getFinalRoll() const {
|
||||||
return glm::clamp(_baseRoll + _deltaRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
|
return glm::clamp(_baseRoll + _deltaRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Head::addLeanDeltas(float sideways, float forward) {
|
|
||||||
_deltaLeanSideways += sideways;
|
|
||||||
_deltaLeanForward += forward;
|
|
||||||
}
|
|
||||||
|
|
|
@ -59,8 +59,6 @@ public:
|
||||||
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
||||||
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||||
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
||||||
float getFinalLeanSideways() const { return _leanSideways + _deltaLeanSideways; }
|
|
||||||
float getFinalLeanForward() const { return _leanForward + _deltaLeanForward; }
|
|
||||||
|
|
||||||
glm::quat getEyeRotation(const glm::vec3& eyePosition) const;
|
glm::quat getEyeRotation(const glm::vec3& eyePosition) const;
|
||||||
|
|
||||||
|
@ -91,8 +89,7 @@ public:
|
||||||
virtual float getFinalYaw() const;
|
virtual float getFinalYaw() const;
|
||||||
virtual float getFinalRoll() const;
|
virtual float getFinalRoll() const;
|
||||||
|
|
||||||
void relaxLean(float deltaTime);
|
void relax(float deltaTime);
|
||||||
void addLeanDeltas(float sideways, float forward);
|
|
||||||
|
|
||||||
float getTimeWithoutTalking() const { return _timeWithoutTalking; }
|
float getTimeWithoutTalking() const { return _timeWithoutTalking; }
|
||||||
|
|
||||||
|
@ -132,10 +129,6 @@ private:
|
||||||
float _deltaYaw;
|
float _deltaYaw;
|
||||||
float _deltaRoll;
|
float _deltaRoll;
|
||||||
|
|
||||||
// delta lean angles for lean perturbations (driven by collisions)
|
|
||||||
float _deltaLeanSideways;
|
|
||||||
float _deltaLeanForward;
|
|
||||||
|
|
||||||
bool _isCameraMoving;
|
bool _isCameraMoving;
|
||||||
bool _isLookingAtMe;
|
bool _isLookingAtMe;
|
||||||
quint64 _lookingAtMeStarted;
|
quint64 _lookingAtMeStarted;
|
||||||
|
|
|
@ -190,9 +190,6 @@ MyAvatar::MyAvatar(RigPointer rig) :
|
||||||
if (!headData->getBlendshapeCoefficients().isEmpty()) {
|
if (!headData->getBlendshapeCoefficients().isEmpty()) {
|
||||||
_headData->setBlendshapeCoefficients(headData->getBlendshapeCoefficients());
|
_headData->setBlendshapeCoefficients(headData->getBlendshapeCoefficients());
|
||||||
}
|
}
|
||||||
// head lean
|
|
||||||
_headData->setLeanForward(headData->getLeanForward());
|
|
||||||
_headData->setLeanSideways(headData->getLeanSideways());
|
|
||||||
// head orientation
|
// head orientation
|
||||||
_headData->setLookAtPosition(headData->getLookAtPosition());
|
_headData->setLookAtPosition(headData->getLookAtPosition());
|
||||||
}
|
}
|
||||||
|
@ -237,7 +234,7 @@ QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
|
||||||
void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
|
void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
|
||||||
|
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter));
|
QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter), Q_ARG(bool, andReload), Q_ARG(bool, andHead));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +303,7 @@ void MyAvatar::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Head* head = getHead();
|
Head* head = getHead();
|
||||||
head->relaxLean(deltaTime);
|
head->relax(deltaTime);
|
||||||
updateFromTrackers(deltaTime);
|
updateFromTrackers(deltaTime);
|
||||||
|
|
||||||
// Get audio loudness data from audio input device
|
// Get audio loudness data from audio input device
|
||||||
|
@ -574,16 +571,6 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
|
||||||
head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView);
|
head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView);
|
||||||
head->setDeltaRoll(estimatedRotation.z);
|
head->setDeltaRoll(estimatedRotation.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update torso lean distance based on accelerometer data
|
|
||||||
const float TORSO_LENGTH = 0.5f;
|
|
||||||
glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f);
|
|
||||||
|
|
||||||
const float MAX_LEAN = 45.0f;
|
|
||||||
head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
|
|
||||||
-MAX_LEAN, MAX_LEAN));
|
|
||||||
head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
|
|
||||||
-MAX_LEAN, MAX_LEAN));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 MyAvatar::getLeftHandPosition() const {
|
glm::vec3 MyAvatar::getLeftHandPosition() const {
|
||||||
|
@ -692,7 +679,6 @@ void MyAvatar::saveData() {
|
||||||
|
|
||||||
settings.setValue("headPitch", getHead()->getBasePitch());
|
settings.setValue("headPitch", getHead()->getBasePitch());
|
||||||
|
|
||||||
settings.setValue("leanScale", _leanScale);
|
|
||||||
settings.setValue("scale", _targetScale);
|
settings.setValue("scale", _targetScale);
|
||||||
|
|
||||||
settings.setValue("fullAvatarURL",
|
settings.setValue("fullAvatarURL",
|
||||||
|
@ -809,7 +795,6 @@ void MyAvatar::loadData() {
|
||||||
|
|
||||||
getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f));
|
getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f));
|
||||||
|
|
||||||
_leanScale = loadSetting(settings, "leanScale", 0.05f);
|
|
||||||
_targetScale = loadSetting(settings, "scale", 1.0f);
|
_targetScale = loadSetting(settings, "scale", 1.0f);
|
||||||
setScale(glm::vec3(_targetScale));
|
setScale(glm::vec3(_targetScale));
|
||||||
|
|
||||||
|
@ -1271,13 +1256,13 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
||||||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||||
glm::vec3 position = getPosition();
|
glm::vec3 position = getPosition();
|
||||||
glm::quat orientation = getOrientation();
|
glm::quat orientation = getOrientation();
|
||||||
if (_characterController.isEnabled()) {
|
if (_characterController.isEnabledAndReady()) {
|
||||||
_characterController.getPositionAndOrientation(position, orientation);
|
_characterController.getPositionAndOrientation(position, orientation);
|
||||||
}
|
}
|
||||||
nextAttitude(position, orientation);
|
nextAttitude(position, orientation);
|
||||||
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
|
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
|
||||||
|
|
||||||
if (_characterController.isEnabled()) {
|
if (_characterController.isEnabledAndReady()) {
|
||||||
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
|
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
|
||||||
} else {
|
} else {
|
||||||
setVelocity(getVelocity() + _characterController.getFollowVelocity());
|
setVelocity(getVelocity() + _characterController.getFollowVelocity());
|
||||||
|
@ -1660,7 +1645,7 @@ void MyAvatar::updatePosition(float deltaTime) {
|
||||||
|
|
||||||
vec3 velocity = getVelocity();
|
vec3 velocity = getVelocity();
|
||||||
const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s
|
const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s
|
||||||
if (!_characterController.isEnabled()) {
|
if (!_characterController.isEnabledAndReady()) {
|
||||||
// _characterController is not in physics simulation but it can still compute its target velocity
|
// _characterController is not in physics simulation but it can still compute its target velocity
|
||||||
updateMotors();
|
updateMotors();
|
||||||
_characterController.computeNewVelocity(deltaTime, velocity);
|
_characterController.computeNewVelocity(deltaTime, velocity);
|
||||||
|
@ -1834,6 +1819,16 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
|
||||||
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
|
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCharacterControllerEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::setCharacterControllerEnabled(bool enabled) {
|
||||||
|
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "setCharacterControllerEnabled", Q_ARG(bool, enabled));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool ghostingAllowed = true;
|
bool ghostingAllowed = true;
|
||||||
EntityTreeRenderer* entityTreeRenderer = qApp->getEntities();
|
EntityTreeRenderer* entityTreeRenderer = qApp->getEntities();
|
||||||
if (entityTreeRenderer) {
|
if (entityTreeRenderer) {
|
||||||
|
@ -1842,12 +1837,11 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
|
||||||
ghostingAllowed = zone->getGhostingAllowed();
|
ghostingAllowed = zone->getGhostingAllowed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool checked = menu->isOptionChecked(MenuOption::EnableCharacterController);
|
_characterController.setEnabled(ghostingAllowed ? enabled : true);
|
||||||
if (!ghostingAllowed) {
|
}
|
||||||
checked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_characterController.setEnabled(checked);
|
bool MyAvatar::getCharacterControllerEnabled() {
|
||||||
|
return _characterController.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::clearDriveKeys() {
|
void MyAvatar::clearDriveKeys() {
|
||||||
|
@ -2055,14 +2049,17 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
|
||||||
|
|
||||||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
||||||
_desiredBodyMatrix = desiredBodyMatrix;
|
_desiredBodyMatrix = desiredBodyMatrix;
|
||||||
if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
|
||||||
activate(Rotation);
|
if (myAvatar.getHMDLeanRecenterEnabled()) {
|
||||||
}
|
if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||||
if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
activate(Rotation);
|
||||||
activate(Horizontal);
|
}
|
||||||
}
|
if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||||
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
activate(Horizontal);
|
||||||
activate(Vertical);
|
}
|
||||||
|
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||||
|
activate(Vertical);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix;
|
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix;
|
||||||
|
|
|
@ -69,7 +69,6 @@ class MyAvatar : public Avatar {
|
||||||
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
|
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
|
||||||
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
|
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
|
||||||
|
|
||||||
|
|
||||||
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
|
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
|
||||||
Q_PROPERTY(glm::vec3 rightHandPosition READ getRightHandPosition)
|
Q_PROPERTY(glm::vec3 rightHandPosition READ getRightHandPosition)
|
||||||
Q_PROPERTY(glm::vec3 leftHandTipPosition READ getLeftHandTipPosition)
|
Q_PROPERTY(glm::vec3 leftHandTipPosition READ getLeftHandTipPosition)
|
||||||
|
@ -84,6 +83,9 @@ class MyAvatar : public Avatar {
|
||||||
|
|
||||||
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
|
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
|
||||||
|
|
||||||
|
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||||
|
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MyAvatar(RigPointer rig);
|
explicit MyAvatar(RigPointer rig);
|
||||||
~MyAvatar();
|
~MyAvatar();
|
||||||
|
@ -123,9 +125,6 @@ public:
|
||||||
|
|
||||||
void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); }
|
void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); }
|
||||||
|
|
||||||
void setLeanScale(float scale) { _leanScale = scale; }
|
|
||||||
float getLeanScale() const { return _leanScale; }
|
|
||||||
|
|
||||||
Q_INVOKABLE glm::vec3 getDefaultEyePosition() const;
|
Q_INVOKABLE glm::vec3 getDefaultEyePosition() const;
|
||||||
|
|
||||||
float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); }
|
float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); }
|
||||||
|
@ -163,6 +162,9 @@ public:
|
||||||
Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; }
|
Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; }
|
||||||
Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; }
|
Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; }
|
||||||
|
|
||||||
|
Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; }
|
||||||
|
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
|
||||||
|
|
||||||
// get/set avatar data
|
// get/set avatar data
|
||||||
void saveData();
|
void saveData();
|
||||||
void loadData();
|
void loadData();
|
||||||
|
@ -264,6 +266,9 @@ public:
|
||||||
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
|
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
|
||||||
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
|
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
|
||||||
|
Q_INVOKABLE bool getCharacterControllerEnabled();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void increaseSize();
|
void increaseSize();
|
||||||
void decreaseSize();
|
void decreaseSize();
|
||||||
|
@ -470,6 +475,8 @@ private:
|
||||||
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInSensorFrameCache { controller::Pose() };
|
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInSensorFrameCache { controller::Pose() };
|
||||||
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
|
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
|
||||||
|
|
||||||
|
bool _hmdLeanRecenterEnabled = true;
|
||||||
|
|
||||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||||
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
||||||
|
|
|
@ -106,10 +106,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||||
|
|
||||||
Rig::HeadParameters headParams;
|
Rig::HeadParameters headParams;
|
||||||
headParams.enableLean = qApp->isHMDMode();
|
|
||||||
headParams.leanSideways = head->getFinalLeanSideways();
|
|
||||||
headParams.leanForward = head->getFinalLeanForward();
|
|
||||||
headParams.torsoTwist = head->getTorsoTwist();
|
|
||||||
|
|
||||||
if (qApp->isHMDMode()) {
|
if (qApp->isHMDMode()) {
|
||||||
headParams.isInHMD = true;
|
headParams.isInHMD = true;
|
||||||
|
@ -131,7 +127,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
|
headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
headParams.leanJointIndex = geometry.leanJointIndex;
|
|
||||||
headParams.neckJointIndex = geometry.neckJointIndex;
|
headParams.neckJointIndex = geometry.neckJointIndex;
|
||||||
headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||||
|
|
||||||
|
|
|
@ -129,16 +129,6 @@ void setupPreferences() {
|
||||||
preference->setStep(1);
|
preference->setStep(1);
|
||||||
preferences->addPreference(preference);
|
preferences->addPreference(preference);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
auto getter = [=]()->float { return myAvatar->getLeanScale(); };
|
|
||||||
auto setter = [=](float value) { myAvatar->setLeanScale(value); };
|
|
||||||
auto preference = new SpinnerPreference(AVATAR_TUNING, "Lean scale (applies to Faceshift users)", getter, setter);
|
|
||||||
preference->setMin(0);
|
|
||||||
preference->setMax(99.9f);
|
|
||||||
preference->setDecimals(2);
|
|
||||||
preference->setStep(1);
|
|
||||||
preferences->addPreference(preference);
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
auto getter = [=]()->float { return myAvatar->getUniformScale(); };
|
auto getter = [=]()->float { return myAvatar->getUniformScale(); };
|
||||||
auto setter = [=](float value) { myAvatar->setTargetScaleVerbose(value); }; // The hell?
|
auto setter = [=](float value) { myAvatar->setTargetScaleVerbose(value); }; // The hell?
|
||||||
|
|
|
@ -931,11 +931,6 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
||||||
if (params.enableLean) {
|
|
||||||
updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist);
|
|
||||||
} else {
|
|
||||||
_animVars.unset("lean");
|
|
||||||
}
|
|
||||||
updateNeckJoint(params.neckJointIndex, params);
|
updateNeckJoint(params.neckJointIndex, params);
|
||||||
|
|
||||||
_animVars.set("isTalking", params.isTalking);
|
_animVars.set("isTalking", params.isTalking);
|
||||||
|
@ -953,15 +948,6 @@ static const glm::vec3 X_AXIS(1.0f, 0.0f, 0.0f);
|
||||||
static const glm::vec3 Y_AXIS(0.0f, 1.0f, 0.0f);
|
static const glm::vec3 Y_AXIS(0.0f, 1.0f, 0.0f);
|
||||||
static const glm::vec3 Z_AXIS(0.0f, 0.0f, 1.0f);
|
static const glm::vec3 Z_AXIS(0.0f, 0.0f, 1.0f);
|
||||||
|
|
||||||
void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) {
|
|
||||||
if (isIndexValid(index)) {
|
|
||||||
glm::quat absRot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, Z_AXIS) *
|
|
||||||
glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, X_AXIS) *
|
|
||||||
glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, Y_AXIS));
|
|
||||||
_animVars.set("lean", absRot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Rig::computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut,
|
void Rig::computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut,
|
||||||
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const {
|
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const {
|
||||||
|
|
||||||
|
|
|
@ -42,15 +42,10 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HeadParameters {
|
struct HeadParameters {
|
||||||
float leanSideways = 0.0f; // degrees
|
|
||||||
float leanForward = 0.0f; // degrees
|
|
||||||
float torsoTwist = 0.0f; // degrees
|
|
||||||
bool enableLean = false;
|
|
||||||
glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward)
|
glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward)
|
||||||
glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward)
|
glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward)
|
||||||
glm::vec3 rigHeadPosition = glm::vec3(); // rig space
|
glm::vec3 rigHeadPosition = glm::vec3(); // rig space
|
||||||
bool isInHMD = false;
|
bool isInHMD = false;
|
||||||
int leanJointIndex = -1;
|
|
||||||
int neckJointIndex = -1;
|
int neckJointIndex = -1;
|
||||||
bool isTalking = false;
|
bool isTalking = false;
|
||||||
};
|
};
|
||||||
|
@ -222,7 +217,6 @@ protected:
|
||||||
void applyOverridePoses();
|
void applyOverridePoses();
|
||||||
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut);
|
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut);
|
||||||
|
|
||||||
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
|
|
||||||
void updateNeckJoint(int index, const HeadParameters& params);
|
void updateNeckJoint(int index, const HeadParameters& params);
|
||||||
void computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut,
|
void computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut,
|
||||||
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const;
|
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const;
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
@ -119,68 +118,18 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Detect AVX/AVX2 support
|
|
||||||
//
|
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
|
|
||||||
#include <intrin.h>
|
|
||||||
|
|
||||||
static bool cpuSupportsAVX() {
|
|
||||||
int info[4];
|
|
||||||
int mask = (1 << 27) | (1 << 28); // OSXSAVE and AVX
|
|
||||||
|
|
||||||
__cpuidex(info, 0x1, 0);
|
|
||||||
|
|
||||||
bool result = false;
|
|
||||||
if ((info[2] & mask) == mask) {
|
|
||||||
|
|
||||||
if ((_xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(__GNUC__)
|
|
||||||
|
|
||||||
#include <cpuid.h>
|
|
||||||
|
|
||||||
static bool cpuSupportsAVX() {
|
|
||||||
unsigned int eax, ebx, ecx, edx;
|
|
||||||
unsigned int mask = (1 << 27) | (1 << 28); // OSXSAVE and AVX
|
|
||||||
|
|
||||||
bool result = false;
|
|
||||||
if (__get_cpuid(0x1, &eax, &ebx, &ecx, &edx) && ((ecx & mask) == mask)) {
|
|
||||||
|
|
||||||
__asm__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0));
|
|
||||||
if ((eax & 0x6) == 0x6) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
static bool cpuSupportsAVX() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Runtime CPU dispatch
|
// Runtime CPU dispatch
|
||||||
//
|
//
|
||||||
|
|
||||||
typedef void FIR_1x4_t(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames);
|
#include "CPUDetect.h"
|
||||||
FIR_1x4_t FIR_1x4_AVX; // separate compilation with VEX-encoding enabled
|
|
||||||
|
void FIR_1x4_AVX(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames);
|
||||||
|
|
||||||
static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) {
|
static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) {
|
||||||
|
|
||||||
static FIR_1x4_t* f = cpuSupportsAVX() ? FIR_1x4_AVX : FIR_1x4_SSE; // init on first call
|
static auto f = cpuSupportsAVX() ? FIR_1x4_AVX : FIR_1x4_SSE;
|
||||||
(*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch
|
(*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4 channel planar to interleaved
|
// 4 channel planar to interleaved
|
||||||
|
|
|
@ -32,7 +32,7 @@ public:
|
||||||
// input: mono source
|
// input: mono source
|
||||||
// output: interleaved stereo mix buffer (accumulates into existing output)
|
// output: interleaved stereo mix buffer (accumulates into existing output)
|
||||||
// index: HRTF subject index
|
// index: HRTF subject index
|
||||||
// azimuth: clockwise panning angle [0, 360] in degrees
|
// azimuth: clockwise panning angle in radians
|
||||||
// gain: gain factor for distance attenuation
|
// gain: gain factor for distance attenuation
|
||||||
// numFrames: must be HRTF_BLOCK in this version
|
// numFrames: must be HRTF_BLOCK in this version
|
||||||
//
|
//
|
||||||
|
|
|
@ -31,9 +31,6 @@ HeadData::HeadData(AvatarData* owningAvatar) :
|
||||||
_baseYaw(0.0f),
|
_baseYaw(0.0f),
|
||||||
_basePitch(0.0f),
|
_basePitch(0.0f),
|
||||||
_baseRoll(0.0f),
|
_baseRoll(0.0f),
|
||||||
_leanSideways(0.0f),
|
|
||||||
_leanForward(0.0f),
|
|
||||||
_torsoTwist(0.0f),
|
|
||||||
_lookAtPosition(0.0f, 0.0f, 0.0f),
|
_lookAtPosition(0.0f, 0.0f, 0.0f),
|
||||||
_audioLoudness(0.0f),
|
_audioLoudness(0.0f),
|
||||||
_isFaceTrackerConnected(false),
|
_isFaceTrackerConnected(false),
|
||||||
|
@ -132,12 +129,6 @@ QJsonObject HeadData::toJson() const {
|
||||||
if (getRawOrientation() != quat()) {
|
if (getRawOrientation() != quat()) {
|
||||||
headJson[JSON_AVATAR_HEAD_ROTATION] = toJsonValue(getRawOrientation());
|
headJson[JSON_AVATAR_HEAD_ROTATION] = toJsonValue(getRawOrientation());
|
||||||
}
|
}
|
||||||
if (getLeanForward() != 0.0f) {
|
|
||||||
headJson[JSON_AVATAR_HEAD_LEAN_FORWARD] = getLeanForward();
|
|
||||||
}
|
|
||||||
if (getLeanSideways() != 0.0f) {
|
|
||||||
headJson[JSON_AVATAR_HEAD_LEAN_SIDEWAYS] = getLeanSideways();
|
|
||||||
}
|
|
||||||
auto lookat = getLookAtPosition();
|
auto lookat = getLookAtPosition();
|
||||||
if (lookat != vec3()) {
|
if (lookat != vec3()) {
|
||||||
vec3 relativeLookAt = glm::inverse(_owningAvatar->getOrientation()) *
|
vec3 relativeLookAt = glm::inverse(_owningAvatar->getOrientation()) *
|
||||||
|
@ -171,12 +162,6 @@ void HeadData::fromJson(const QJsonObject& json) {
|
||||||
if (json.contains(JSON_AVATAR_HEAD_ROTATION)) {
|
if (json.contains(JSON_AVATAR_HEAD_ROTATION)) {
|
||||||
setOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION]));
|
setOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION]));
|
||||||
}
|
}
|
||||||
if (json.contains(JSON_AVATAR_HEAD_LEAN_FORWARD)) {
|
|
||||||
setLeanForward((float)json[JSON_AVATAR_HEAD_LEAN_FORWARD].toDouble());
|
|
||||||
}
|
|
||||||
if (json.contains(JSON_AVATAR_HEAD_LEAN_SIDEWAYS)) {
|
|
||||||
setLeanSideways((float)json[JSON_AVATAR_HEAD_LEAN_SIDEWAYS].toDouble());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.contains(JSON_AVATAR_HEAD_LOOKAT)) {
|
if (json.contains(JSON_AVATAR_HEAD_LOOKAT)) {
|
||||||
auto relativeLookAt = vec3FromJsonValue(json[JSON_AVATAR_HEAD_LOOKAT]);
|
auto relativeLookAt = vec3FromJsonValue(json[JSON_AVATAR_HEAD_LOOKAT]);
|
||||||
|
|
|
@ -68,17 +68,6 @@ public:
|
||||||
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
||||||
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
|
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
|
||||||
|
|
||||||
|
|
||||||
float getLeanSideways() const { return _leanSideways; }
|
|
||||||
float getLeanForward() const { return _leanForward; }
|
|
||||||
float getTorsoTwist() const { return _torsoTwist; }
|
|
||||||
virtual float getFinalLeanSideways() const { return _leanSideways; }
|
|
||||||
virtual float getFinalLeanForward() const { return _leanForward; }
|
|
||||||
|
|
||||||
void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; }
|
|
||||||
void setLeanForward(float leanForward) { _leanForward = leanForward; }
|
|
||||||
void setTorsoTwist(float torsoTwist) { _torsoTwist = torsoTwist; }
|
|
||||||
|
|
||||||
friend class AvatarData;
|
friend class AvatarData;
|
||||||
|
|
||||||
QJsonObject toJson() const;
|
QJsonObject toJson() const;
|
||||||
|
@ -89,9 +78,6 @@ protected:
|
||||||
float _baseYaw;
|
float _baseYaw;
|
||||||
float _basePitch;
|
float _basePitch;
|
||||||
float _baseRoll;
|
float _baseRoll;
|
||||||
float _leanSideways;
|
|
||||||
float _leanForward;
|
|
||||||
float _torsoTwist;
|
|
||||||
|
|
||||||
glm::vec3 _lookAtPosition;
|
glm::vec3 _lookAtPosition;
|
||||||
float _audioLoudness;
|
float _audioLoudness;
|
||||||
|
|
|
@ -32,6 +32,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged);
|
connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged);
|
||||||
connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged);
|
connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged);
|
||||||
|
connect(nodeList.data(), &NodeList::canRezTmpChanged, this, &EntityScriptingInterface::canRezTmpChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
|
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
|
||||||
|
@ -49,6 +50,11 @@ bool EntityScriptingInterface::canRez() {
|
||||||
return nodeList->getThisNodeCanRez();
|
return nodeList->getThisNodeCanRez();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EntityScriptingInterface::canRezTmp() {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
return nodeList->getThisNodeCanRezTmp();
|
||||||
|
}
|
||||||
|
|
||||||
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
|
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
|
||||||
if (_entityTree) {
|
if (_entityTree) {
|
||||||
disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
|
disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
|
||||||
|
|
|
@ -80,6 +80,7 @@ public slots:
|
||||||
|
|
||||||
// returns true if the DomainServer will allow this Node/Avatar to rez new entities
|
// returns true if the DomainServer will allow this Node/Avatar to rez new entities
|
||||||
Q_INVOKABLE bool canRez();
|
Q_INVOKABLE bool canRez();
|
||||||
|
Q_INVOKABLE bool canRezTmp();
|
||||||
|
|
||||||
/// adds a model with the specific properties
|
/// adds a model with the specific properties
|
||||||
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false);
|
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false);
|
||||||
|
@ -179,6 +180,7 @@ signals:
|
||||||
|
|
||||||
void canAdjustLocksChanged(bool canAdjustLocks);
|
void canAdjustLocksChanged(bool canAdjustLocks);
|
||||||
void canRezChanged(bool canRez);
|
void canRezChanged(bool canRez);
|
||||||
|
void canRezTmpChanged(bool canRez);
|
||||||
|
|
||||||
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
||||||
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
#include "LogHandler.h"
|
#include "LogHandler.h"
|
||||||
|
|
||||||
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;
|
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;
|
||||||
|
const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour
|
||||||
|
|
||||||
|
|
||||||
EntityTree::EntityTree(bool shouldReaverage) :
|
EntityTree::EntityTree(bool shouldReaverage) :
|
||||||
Octree(shouldReaverage),
|
Octree(shouldReaverage),
|
||||||
|
@ -128,13 +130,16 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
||||||
EntityItemProperties properties = origProperties;
|
EntityItemProperties properties = origProperties;
|
||||||
|
|
||||||
bool allowLockChange;
|
bool allowLockChange;
|
||||||
|
bool canRezPermanentEntities;
|
||||||
QUuid senderID;
|
QUuid senderID;
|
||||||
if (senderNode.isNull()) {
|
if (senderNode.isNull()) {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
allowLockChange = nodeList->isAllowedEditor();
|
allowLockChange = nodeList->isAllowedEditor();
|
||||||
|
canRezPermanentEntities = nodeList->getThisNodeCanRez();
|
||||||
senderID = nodeList->getSessionUUID();
|
senderID = nodeList->getSessionUUID();
|
||||||
} else {
|
} else {
|
||||||
allowLockChange = senderNode->isAllowedEditor();
|
allowLockChange = senderNode->isAllowedEditor();
|
||||||
|
canRezPermanentEntities = senderNode->getCanRez();
|
||||||
senderID = senderNode->getUUID();
|
senderID = senderNode->getUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +148,12 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!canRezPermanentEntities && (entity->getLifetime() != properties.getLifetime())) {
|
||||||
|
// we don't allow a Node that can't create permanent entities to adjust lifetimes on existing ones
|
||||||
|
qCDebug(entities) << "Refusing disallowed entity lifetime adjustment.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// enforce support for locked entities. If an entity is currently locked, then the only
|
// enforce support for locked entities. If an entity is currently locked, then the only
|
||||||
// property we allow you to change is the locked property.
|
// property we allow you to change is the locked property.
|
||||||
if (entity->getLocked()) {
|
if (entity->getLocked()) {
|
||||||
|
@ -308,17 +319,39 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EntityTree::permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) {
|
||||||
|
float lifeTime = properties.getLifetime();
|
||||||
|
|
||||||
|
if (lifeTime == 0.0f || lifeTime > _maxTmpEntityLifetime) {
|
||||||
|
// this is an attempt to rez a permanent entity.
|
||||||
|
if (!canRez) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this is an attempt to rez a temporary entity.
|
||||||
|
if (!canRezTmp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||||
EntityItemPointer result = NULL;
|
EntityItemPointer result = NULL;
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
if (!nodeList) {
|
||||||
|
qDebug() << "EntityTree::addEntity -- can't get NodeList";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool clientOnly = properties.getClientOnly();
|
bool clientOnly = properties.getClientOnly();
|
||||||
|
|
||||||
if (!clientOnly && getIsClient()) {
|
if (!clientOnly && getIsClient() &&
|
||||||
|
!permissionsAllowRez(properties, nodeList->getThisNodeCanRez(), nodeList->getThisNodeCanRezTmp())) {
|
||||||
// if our Node isn't allowed to create entities in this domain, don't try.
|
// if our Node isn't allowed to create entities in this domain, don't try.
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
return nullptr;
|
||||||
if (nodeList && !nodeList->getThisNodeCanRez()) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool recordCreationTime = false;
|
bool recordCreationTime = false;
|
||||||
|
@ -920,7 +953,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
endUpdate = usecTimestampNow();
|
endUpdate = usecTimestampNow();
|
||||||
_totalUpdates++;
|
_totalUpdates++;
|
||||||
} else if (message.getType() == PacketType::EntityAdd) {
|
} else if (message.getType() == PacketType::EntityAdd) {
|
||||||
if (senderNode->getCanRez()) {
|
if (permissionsAllowRez(properties, senderNode->getCanRez(), senderNode->getCanRezTmp())) {
|
||||||
// this is a new entity... assign a new entityID
|
// this is a new entity... assign a new entityID
|
||||||
properties.setCreated(properties.getLastEdited());
|
properties.setCreated(properties.getLastEdited());
|
||||||
startCreate = usecTimestampNow();
|
startCreate = usecTimestampNow();
|
||||||
|
@ -1430,6 +1463,12 @@ bool EntityTree::readFromMap(QVariantMap& map) {
|
||||||
QVariantList entitiesQList = map["Entities"].toList();
|
QVariantList entitiesQList = map["Entities"].toList();
|
||||||
QScriptEngine scriptEngine;
|
QScriptEngine scriptEngine;
|
||||||
|
|
||||||
|
if (entitiesQList.length() == 0) {
|
||||||
|
// Empty map or invalidly formed file.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
foreach (QVariant entityVariant, entitiesQList) {
|
foreach (QVariant entityVariant, entitiesQList) {
|
||||||
// QVariantMap --> QScriptValue --> EntityItemProperties --> Entity
|
// QVariantMap --> QScriptValue --> EntityItemProperties --> Entity
|
||||||
QVariantMap entityMap = entityVariant.toMap();
|
QVariantMap entityMap = entityVariant.toMap();
|
||||||
|
@ -1447,9 +1486,10 @@ bool EntityTree::readFromMap(QVariantMap& map) {
|
||||||
EntityItemPointer entity = addEntity(entityItemID, properties);
|
EntityItemPointer entity = addEntity(entityItemID, properties);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
|
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
|
||||||
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTree::resetClientEditStats() {
|
void EntityTree::resetClientEditStats() {
|
||||||
|
|
|
@ -62,6 +62,10 @@ public:
|
||||||
|
|
||||||
void createRootElement();
|
void createRootElement();
|
||||||
|
|
||||||
|
|
||||||
|
void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; }
|
||||||
|
bool permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp);
|
||||||
|
|
||||||
/// Implements our type specific root element factory
|
/// Implements our type specific root element factory
|
||||||
virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override;
|
virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override;
|
||||||
|
|
||||||
|
@ -252,6 +256,8 @@ public:
|
||||||
|
|
||||||
void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID);
|
void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID);
|
||||||
|
|
||||||
|
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void callLoader(EntityItemID entityID);
|
void callLoader(EntityItemID entityID);
|
||||||
|
|
||||||
|
@ -331,6 +337,8 @@ protected:
|
||||||
// we maintain a list of avatarIDs to notice when an entity is a child of one.
|
// we maintain a list of avatarIDs to notice when an entity is a child of one.
|
||||||
QSet<QUuid> _avatarIDs; // IDs of avatars connected to entity server
|
QSet<QUuid> _avatarIDs; // IDs of avatars connected to entity server
|
||||||
QHash<QUuid, QSet<EntityItemID>> _childrenOfAvatars; // which entities are children of which avatars
|
QHash<QUuid, QSet<EntityItemID>> _childrenOfAvatars; // which entities are children of which avatars
|
||||||
|
|
||||||
|
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_EntityTree_h
|
#endif // hifi_EntityTree_h
|
||||||
|
|
|
@ -144,12 +144,21 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
||||||
// 4. domain network address (IP or dns resolvable hostname)
|
// 4. domain network address (IP or dns resolvable hostname)
|
||||||
|
|
||||||
// use our regex'ed helpers to figure out what we're supposed to do with this
|
// use our regex'ed helpers to figure out what we're supposed to do with this
|
||||||
if (!handleUsername(lookupUrl.authority())) {
|
if (handleUsername(lookupUrl.authority())) {
|
||||||
|
// handled a username for lookup
|
||||||
|
|
||||||
|
// in case we're failing to connect to where we thought this user was
|
||||||
|
// store their username as previous lookup so we can refresh their location via API
|
||||||
|
_previousLookup = lookupUrl;
|
||||||
|
} else {
|
||||||
// we're assuming this is either a network address or global place name
|
// we're assuming this is either a network address or global place name
|
||||||
// check if it is a network address first
|
// check if it is a network address first
|
||||||
bool hostChanged;
|
bool hostChanged;
|
||||||
if (handleNetworkAddress(lookupUrl.host()
|
if (handleNetworkAddress(lookupUrl.host()
|
||||||
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) {
|
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) {
|
||||||
|
|
||||||
|
// a network address lookup clears the previous lookup since we don't expect to re-attempt it
|
||||||
|
_previousLookup.clear();
|
||||||
|
|
||||||
// If the host changed then we have already saved to history
|
// If the host changed then we have already saved to history
|
||||||
if (hostChanged) {
|
if (hostChanged) {
|
||||||
|
@ -165,10 +174,16 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
||||||
// we may have a path that defines a relative viewpoint - if so we should jump to that now
|
// we may have a path that defines a relative viewpoint - if so we should jump to that now
|
||||||
handlePath(path, trigger);
|
handlePath(path, trigger);
|
||||||
} else if (handleDomainID(lookupUrl.host())){
|
} else if (handleDomainID(lookupUrl.host())){
|
||||||
|
// store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info
|
||||||
|
_previousLookup = lookupUrl;
|
||||||
|
|
||||||
// no place name - this is probably a domain ID
|
// no place name - this is probably a domain ID
|
||||||
// try to look up the domain ID on the metaverse API
|
// try to look up the domain ID on the metaverse API
|
||||||
attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger);
|
attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger);
|
||||||
} else {
|
} else {
|
||||||
|
// store this place name as the previous lookup in case we fail to connect and want to refresh API info
|
||||||
|
_previousLookup = lookupUrl;
|
||||||
|
|
||||||
// wasn't an address - lookup the place name
|
// wasn't an address - lookup the place name
|
||||||
// we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after
|
// we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after
|
||||||
attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger);
|
attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger);
|
||||||
|
@ -180,9 +195,13 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
||||||
} else if (lookupUrl.toString().startsWith('/')) {
|
} else if (lookupUrl.toString().startsWith('/')) {
|
||||||
qCDebug(networking) << "Going to relative path" << lookupUrl.path();
|
qCDebug(networking) << "Going to relative path" << lookupUrl.path();
|
||||||
|
|
||||||
|
// a path lookup clears the previous lookup since we don't expect to re-attempt it
|
||||||
|
_previousLookup.clear();
|
||||||
|
|
||||||
// if this is a relative path then handle it as a relative viewpoint
|
// if this is a relative path then handle it as a relative viewpoint
|
||||||
handlePath(lookupUrl.path(), trigger, true);
|
handlePath(lookupUrl.path(), trigger, true);
|
||||||
emit lookupResultsFinished();
|
emit lookupResultsFinished();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +295,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
|
||||||
|
|
||||||
qCDebug(networking) << "Possible domain change required to connect to" << domainHostname
|
qCDebug(networking) << "Possible domain change required to connect to" << domainHostname
|
||||||
<< "on" << domainPort;
|
<< "on" << domainPort;
|
||||||
emit possibleDomainChangeRequired(domainHostname, domainPort);
|
emit possibleDomainChangeRequired(domainHostname, domainPort, domainID);
|
||||||
} else {
|
} else {
|
||||||
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
|
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
|
||||||
|
|
||||||
|
@ -315,7 +334,10 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
|
||||||
QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString();
|
QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString();
|
||||||
|
|
||||||
if (!overridePath.isEmpty()) {
|
if (!overridePath.isEmpty()) {
|
||||||
handlePath(overridePath, trigger);
|
// make sure we don't re-handle an overriden path if this was a refresh of info from API
|
||||||
|
if (trigger != LookupTrigger::AttemptedRefresh) {
|
||||||
|
handlePath(overridePath, trigger);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// take the path that came back
|
// take the path that came back
|
||||||
const QString PLACE_PATH_KEY = "path";
|
const QString PLACE_PATH_KEY = "path";
|
||||||
|
@ -362,7 +384,7 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) {
|
||||||
|
|
||||||
if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
|
if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
|
||||||
// if this is a lookup that has no result, don't keep re-trying it
|
// if this is a lookup that has no result, don't keep re-trying it
|
||||||
//_previousLookup.clear();
|
_previousLookup.clear();
|
||||||
|
|
||||||
emit lookupResultIsNotFound();
|
emit lookupResultIsNotFound();
|
||||||
}
|
}
|
||||||
|
@ -598,7 +620,7 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup
|
||||||
|
|
||||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress);
|
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress);
|
||||||
|
|
||||||
emit possibleDomainChangeRequired(hostname, port);
|
emit possibleDomainChangeRequired(hostname, port, QUuid());
|
||||||
|
|
||||||
return hostChanged;
|
return hostChanged;
|
||||||
}
|
}
|
||||||
|
@ -618,6 +640,13 @@ void AddressManager::goToUser(const QString& username) {
|
||||||
QByteArray(), nullptr, requestParams);
|
QByteArray(), nullptr, requestParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddressManager::refreshPreviousLookup() {
|
||||||
|
// if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history)
|
||||||
|
if (!_previousLookup.isEmpty()) {
|
||||||
|
handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AddressManager::copyAddress() {
|
void AddressManager::copyAddress() {
|
||||||
QApplication::clipboard()->setText(currentAddress().toString());
|
QApplication::clipboard()->setText(currentAddress().toString());
|
||||||
}
|
}
|
||||||
|
@ -629,7 +658,10 @@ void AddressManager::copyPath() {
|
||||||
void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
|
void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
|
||||||
|
|
||||||
// if we're cold starting and this is called for the first address (from settings) we don't do anything
|
// if we're cold starting and this is called for the first address (from settings) we don't do anything
|
||||||
if (trigger != LookupTrigger::StartupFromSettings && trigger != LookupTrigger::DomainPathResponse) {
|
if (trigger != LookupTrigger::StartupFromSettings
|
||||||
|
&& trigger != LookupTrigger::DomainPathResponse
|
||||||
|
&& trigger != LookupTrigger::AttemptedRefresh) {
|
||||||
|
|
||||||
if (trigger == LookupTrigger::Back) {
|
if (trigger == LookupTrigger::Back) {
|
||||||
// we're about to push to the forward stack
|
// we're about to push to the forward stack
|
||||||
// if it's currently empty emit our signal to say that going forward is now possible
|
// if it's currently empty emit our signal to say that going forward is now possible
|
||||||
|
|
|
@ -48,7 +48,8 @@ public:
|
||||||
Forward,
|
Forward,
|
||||||
StartupFromSettings,
|
StartupFromSettings,
|
||||||
DomainPathResponse,
|
DomainPathResponse,
|
||||||
Internal
|
Internal,
|
||||||
|
AttemptedRefresh
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isConnected();
|
bool isConnected();
|
||||||
|
@ -89,6 +90,8 @@ public slots:
|
||||||
|
|
||||||
void goToUser(const QString& username);
|
void goToUser(const QString& username);
|
||||||
|
|
||||||
|
void refreshPreviousLookup();
|
||||||
|
|
||||||
void storeCurrentAddress();
|
void storeCurrentAddress();
|
||||||
|
|
||||||
void copyAddress();
|
void copyAddress();
|
||||||
|
@ -99,7 +102,7 @@ signals:
|
||||||
void lookupResultIsOffline();
|
void lookupResultIsOffline();
|
||||||
void lookupResultIsNotFound();
|
void lookupResultIsNotFound();
|
||||||
|
|
||||||
void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort);
|
void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort, const QUuid& domainID);
|
||||||
void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID);
|
void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID);
|
||||||
|
|
||||||
void locationChangeRequired(const glm::vec3& newPosition,
|
void locationChangeRequired(const glm::vec3& newPosition,
|
||||||
|
@ -152,6 +155,8 @@ private:
|
||||||
quint64 _lastBackPush = 0;
|
quint64 _lastBackPush = 0;
|
||||||
|
|
||||||
QString _newHostLookupPath;
|
QString _newHostLookupPath;
|
||||||
|
|
||||||
|
QUrl _previousLookup;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AddressManager_h
|
#endif // hifi_AddressManager_h
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
#include <QtCore/QDataStream>
|
#include <QtCore/QDataStream>
|
||||||
|
|
||||||
|
#include "AddressManager.h"
|
||||||
#include "Assignment.h"
|
#include "Assignment.h"
|
||||||
#include "HifiSockAddr.h"
|
#include "HifiSockAddr.h"
|
||||||
#include "NodeList.h"
|
#include "NodeList.h"
|
||||||
|
@ -28,17 +29,10 @@
|
||||||
|
|
||||||
DomainHandler::DomainHandler(QObject* parent) :
|
DomainHandler::DomainHandler(QObject* parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
_uuid(),
|
|
||||||
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
|
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
|
||||||
_assignmentUUID(),
|
|
||||||
_connectionToken(),
|
|
||||||
_iceDomainID(),
|
|
||||||
_iceClientID(),
|
|
||||||
_iceServerSockAddr(),
|
|
||||||
_icePeer(this),
|
_icePeer(this),
|
||||||
_isConnected(false),
|
_settingsTimer(this),
|
||||||
_settingsObject(),
|
_apiRefreshTimer(this)
|
||||||
_settingsTimer(this)
|
|
||||||
{
|
{
|
||||||
_sockAddr.setObjectName("DomainServer");
|
_sockAddr.setObjectName("DomainServer");
|
||||||
|
|
||||||
|
@ -49,6 +43,16 @@ DomainHandler::DomainHandler(QObject* parent) :
|
||||||
static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000;
|
static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000;
|
||||||
_settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS);
|
_settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS);
|
||||||
connect(&_settingsTimer, &QTimer::timeout, this, &DomainHandler::settingsReceiveFail);
|
connect(&_settingsTimer, &QTimer::timeout, this, &DomainHandler::settingsReceiveFail);
|
||||||
|
|
||||||
|
// setup the API refresh timer for auto connection information refresh from API when failing to connect
|
||||||
|
const int API_REFRESH_TIMEOUT_MSEC = 2500;
|
||||||
|
_apiRefreshTimer.setInterval(API_REFRESH_TIMEOUT_MSEC);
|
||||||
|
|
||||||
|
auto addressManager = DependencyManager::get<AddressManager>();
|
||||||
|
connect(&_apiRefreshTimer, &QTimer::timeout, addressManager.data(), &AddressManager::refreshPreviousLookup);
|
||||||
|
|
||||||
|
// stop the refresh timer if we connect to a domain
|
||||||
|
connect(this, &DomainHandler::connectedToDomain, &_apiRefreshTimer, &QTimer::stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainHandler::disconnect() {
|
void DomainHandler::disconnect() {
|
||||||
|
@ -93,10 +97,14 @@ void DomainHandler::softReset() {
|
||||||
|
|
||||||
clearSettings();
|
clearSettings();
|
||||||
|
|
||||||
|
_domainConnectionRefusals.clear();
|
||||||
_connectionDenialsSinceKeypairRegen = 0;
|
_connectionDenialsSinceKeypairRegen = 0;
|
||||||
|
|
||||||
// cancel the failure timeout for any pending requests for settings
|
// cancel the failure timeout for any pending requests for settings
|
||||||
QMetaObject::invokeMethod(&_settingsTimer, "stop");
|
QMetaObject::invokeMethod(&_settingsTimer, "stop");
|
||||||
|
|
||||||
|
// restart the API refresh timer in case we fail to connect and need to refresh information
|
||||||
|
QMetaObject::invokeMethod(&_apiRefreshTimer, "start");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainHandler::hardReset() {
|
void DomainHandler::hardReset() {
|
||||||
|
@ -105,7 +113,7 @@ void DomainHandler::hardReset() {
|
||||||
softReset();
|
softReset();
|
||||||
|
|
||||||
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
|
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
|
||||||
_iceDomainID = QUuid();
|
_pendingDomainID = QUuid();
|
||||||
_iceServerSockAddr = HifiSockAddr();
|
_iceServerSockAddr = HifiSockAddr();
|
||||||
_hostname = QString();
|
_hostname = QString();
|
||||||
_sockAddr.clear();
|
_sockAddr.clear();
|
||||||
|
@ -139,7 +147,9 @@ void DomainHandler::setUUID(const QUuid& uuid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
|
void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const QUuid& domainID) {
|
||||||
|
|
||||||
|
_pendingDomainID = domainID;
|
||||||
|
|
||||||
if (hostname != _hostname || _sockAddr.getPort() != port) {
|
if (hostname != _hostname || _sockAddr.getPort() != port) {
|
||||||
// re-set the domain info so that auth information is reloaded
|
// re-set the domain info so that auth information is reloaded
|
||||||
|
@ -149,9 +159,6 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
|
||||||
// set the new hostname
|
// set the new hostname
|
||||||
_hostname = hostname;
|
_hostname = hostname;
|
||||||
|
|
||||||
// FIXME - is this the right place???
|
|
||||||
_domainConnectionRefusals.clear();
|
|
||||||
|
|
||||||
qCDebug(networking) << "Updated domain hostname to" << _hostname;
|
qCDebug(networking) << "Updated domain hostname to" << _hostname;
|
||||||
|
|
||||||
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
|
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
|
||||||
|
@ -174,14 +181,15 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
|
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
|
||||||
if (id != _uuid) {
|
|
||||||
|
if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) {
|
||||||
// re-set the domain info to connect to new domain
|
// re-set the domain info to connect to new domain
|
||||||
hardReset();
|
hardReset();
|
||||||
|
|
||||||
// refresh our ICE client UUID to something new
|
// refresh our ICE client UUID to something new
|
||||||
_iceClientID = QUuid::createUuid();
|
_iceClientID = QUuid::createUuid();
|
||||||
|
|
||||||
_iceDomainID = id;
|
_pendingDomainID = id;
|
||||||
|
|
||||||
HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr;
|
HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr;
|
||||||
replaceableSockAddr->~HifiSockAddr();
|
replaceableSockAddr->~HifiSockAddr();
|
||||||
|
@ -255,6 +263,7 @@ void DomainHandler::setIsConnected(bool isConnected) {
|
||||||
|
|
||||||
// we've connected to new domain - time to ask it for global settings
|
// we've connected to new domain - time to ask it for global settings
|
||||||
requestDomainSettings();
|
requestDomainSettings();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
emit disconnectedFromDomain();
|
emit disconnectedFromDomain();
|
||||||
}
|
}
|
||||||
|
@ -305,6 +314,9 @@ void DomainHandler::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> me
|
||||||
qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr;
|
qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr;
|
||||||
|
|
||||||
if (getIP().isNull()) {
|
if (getIP().isNull()) {
|
||||||
|
// we're hearing back from this domain-server, no need to refresh API information
|
||||||
|
_apiRefreshTimer.stop();
|
||||||
|
|
||||||
// for now we're unsafely assuming this came back from the domain
|
// for now we're unsafely assuming this came back from the domain
|
||||||
if (senderSockAddr == _icePeer.getLocalSocket()) {
|
if (senderSockAddr == _icePeer.getLocalSocket()) {
|
||||||
qCDebug(networking) << "Connecting to domain using local socket";
|
qCDebug(networking) << "Connecting to domain using local socket";
|
||||||
|
@ -333,17 +345,20 @@ void DomainHandler::processDTLSRequirementPacket(QSharedPointer<ReceivedMessage>
|
||||||
void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
if (_icePeer.hasSockets()) {
|
if (_icePeer.hasSockets()) {
|
||||||
qDebug() << "Received an ICE peer packet for domain-server but we already have sockets. Not processing.";
|
qDebug() << "Received an ICE peer packet for domain-server but we already have sockets. Not processing.";
|
||||||
// bail on processing this packet if our ice peer doesn't have sockets
|
// bail on processing this packet if our ice peer already has sockets
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start or restart the API refresh timer now that we have new information
|
||||||
|
_apiRefreshTimer.start();
|
||||||
|
|
||||||
QDataStream iceResponseStream(message->getMessage());
|
QDataStream iceResponseStream(message->getMessage());
|
||||||
|
|
||||||
iceResponseStream >> _icePeer;
|
iceResponseStream >> _icePeer;
|
||||||
|
|
||||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation);
|
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation);
|
||||||
|
|
||||||
if (_icePeer.getUUID() != _iceDomainID) {
|
if (_icePeer.getUUID() != _pendingDomainID) {
|
||||||
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
|
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
|
||||||
_icePeer.reset();
|
_icePeer.reset();
|
||||||
} else {
|
} else {
|
||||||
|
@ -373,6 +388,9 @@ bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
|
// we're hearing from this domain-server, don't need to refresh API info
|
||||||
|
_apiRefreshTimer.stop();
|
||||||
|
|
||||||
// Read deny reason from packet
|
// Read deny reason from packet
|
||||||
uint8_t reasonCodeWire;
|
uint8_t reasonCodeWire;
|
||||||
|
|
||||||
|
|
|
@ -58,8 +58,8 @@ public:
|
||||||
|
|
||||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||||
|
|
||||||
const QUuid& getICEDomainID() const { return _iceDomainID; }
|
const QUuid& getPendingDomainID() const { return _pendingDomainID; }
|
||||||
|
|
||||||
const QUuid& getICEClientID() const { return _iceClientID; }
|
const QUuid& getICEClientID() const { return _iceClientID; }
|
||||||
|
|
||||||
|
@ -75,7 +75,6 @@ public:
|
||||||
bool hasSettings() const { return !_settingsObject.isEmpty(); }
|
bool hasSettings() const { return !_settingsObject.isEmpty(); }
|
||||||
void requestDomainSettings();
|
void requestDomainSettings();
|
||||||
const QJsonObject& getSettingsObject() const { return _settingsObject; }
|
const QJsonObject& getSettingsObject() const { return _settingsObject; }
|
||||||
|
|
||||||
|
|
||||||
void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; }
|
void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; }
|
||||||
const QString& getPendingPath() { return _pendingPath; }
|
const QString& getPendingPath() { return _pendingPath; }
|
||||||
|
@ -94,7 +93,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT);
|
void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid());
|
||||||
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
|
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
|
||||||
|
|
||||||
void processSettingsPacketList(QSharedPointer<ReceivedMessage> packetList);
|
void processSettingsPacketList(QSharedPointer<ReceivedMessage> packetList);
|
||||||
|
@ -136,11 +135,11 @@ private:
|
||||||
HifiSockAddr _sockAddr;
|
HifiSockAddr _sockAddr;
|
||||||
QUuid _assignmentUUID;
|
QUuid _assignmentUUID;
|
||||||
QUuid _connectionToken;
|
QUuid _connectionToken;
|
||||||
QUuid _iceDomainID;
|
QUuid _pendingDomainID; // ID of domain being connected to, via ICE or direct connection
|
||||||
QUuid _iceClientID;
|
QUuid _iceClientID;
|
||||||
HifiSockAddr _iceServerSockAddr;
|
HifiSockAddr _iceServerSockAddr;
|
||||||
NetworkPeer _icePeer;
|
NetworkPeer _icePeer;
|
||||||
bool _isConnected;
|
bool _isConnected { false };
|
||||||
QJsonObject _settingsObject;
|
QJsonObject _settingsObject;
|
||||||
QString _pendingPath;
|
QString _pendingPath;
|
||||||
QTimer _settingsTimer;
|
QTimer _settingsTimer;
|
||||||
|
@ -148,6 +147,8 @@ private:
|
||||||
QStringList _domainConnectionRefusals;
|
QStringList _domainConnectionRefusals;
|
||||||
bool _hasCheckedForAccessToken { false };
|
bool _hasCheckedForAccessToken { false };
|
||||||
int _connectionDenialsSinceKeypairRegen { 0 };
|
int _connectionDenialsSinceKeypairRegen { 0 };
|
||||||
|
|
||||||
|
QTimer _apiRefreshTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_DomainHandler_h
|
#endif // hifi_DomainHandler_h
|
||||||
|
|
|
@ -52,7 +52,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
|
||||||
_numCollectedPackets(0),
|
_numCollectedPackets(0),
|
||||||
_numCollectedBytes(0),
|
_numCollectedBytes(0),
|
||||||
_packetStatTimer(),
|
_packetStatTimer(),
|
||||||
_thisNodeCanRez(true)
|
_permissions(NodePermissions())
|
||||||
{
|
{
|
||||||
static bool firstCall = true;
|
static bool firstCall = true;
|
||||||
if (firstCall) {
|
if (firstCall) {
|
||||||
|
@ -130,17 +130,22 @@ void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LimitedNodeList::setIsAllowedEditor(bool isAllowedEditor) {
|
void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
|
||||||
if (_isAllowedEditor != isAllowedEditor) {
|
NodePermissions originalPermissions = _permissions;
|
||||||
_isAllowedEditor = isAllowedEditor;
|
|
||||||
emit isAllowedEditorChanged(isAllowedEditor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LimitedNodeList::setThisNodeCanRez(bool canRez) {
|
_permissions = newPermissions;
|
||||||
if (_thisNodeCanRez != canRez) {
|
|
||||||
_thisNodeCanRez = canRez;
|
if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) {
|
||||||
emit canRezChanged(canRez);
|
emit isAllowedEditorChanged(_permissions.canAdjustLocks);
|
||||||
|
}
|
||||||
|
if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) {
|
||||||
|
emit canRezChanged(_permissions.canRezPermanentEntities);
|
||||||
|
}
|
||||||
|
if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) {
|
||||||
|
emit canRezTmpChanged(_permissions.canRezTemporaryEntities);
|
||||||
|
}
|
||||||
|
if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) {
|
||||||
|
emit canWriteAssetsChanged(_permissions.canWriteToAssetServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,7 +520,7 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
|
||||||
|
|
||||||
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
||||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
||||||
bool isAllowedEditor, bool canRez,
|
const NodePermissions& permissions,
|
||||||
const QUuid& connectionSecret) {
|
const QUuid& connectionSecret) {
|
||||||
NodeHash::const_iterator it = _nodeHash.find(uuid);
|
NodeHash::const_iterator it = _nodeHash.find(uuid);
|
||||||
|
|
||||||
|
@ -524,14 +529,13 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
||||||
|
|
||||||
matchingNode->setPublicSocket(publicSocket);
|
matchingNode->setPublicSocket(publicSocket);
|
||||||
matchingNode->setLocalSocket(localSocket);
|
matchingNode->setLocalSocket(localSocket);
|
||||||
matchingNode->setIsAllowedEditor(isAllowedEditor);
|
matchingNode->setPermissions(permissions);
|
||||||
matchingNode->setCanRez(canRez);
|
|
||||||
matchingNode->setConnectionSecret(connectionSecret);
|
matchingNode->setConnectionSecret(connectionSecret);
|
||||||
|
|
||||||
return matchingNode;
|
return matchingNode;
|
||||||
} else {
|
} else {
|
||||||
// we didn't have this node, so add them
|
// we didn't have this node, so add them
|
||||||
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, isAllowedEditor, canRez, connectionSecret, this);
|
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, permissions, connectionSecret, this);
|
||||||
|
|
||||||
if (nodeType == NodeType::AudioMixer) {
|
if (nodeType == NodeType::AudioMixer) {
|
||||||
LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer);
|
LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer);
|
||||||
|
|
|
@ -104,12 +104,12 @@ public:
|
||||||
const QUuid& getSessionUUID() const { return _sessionUUID; }
|
const QUuid& getSessionUUID() const { return _sessionUUID; }
|
||||||
void setSessionUUID(const QUuid& sessionUUID);
|
void setSessionUUID(const QUuid& sessionUUID);
|
||||||
|
|
||||||
bool isAllowedEditor() const { return _isAllowedEditor; }
|
void setPermissions(const NodePermissions& newPermissions);
|
||||||
void setIsAllowedEditor(bool isAllowedEditor);
|
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||||
|
bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||||
|
bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||||
|
bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; }
|
||||||
|
|
||||||
bool getThisNodeCanRez() const { return _thisNodeCanRez; }
|
|
||||||
void setThisNodeCanRez(bool canRez);
|
|
||||||
|
|
||||||
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
|
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
|
||||||
QUdpSocket& getDTLSSocket();
|
QUdpSocket& getDTLSSocket();
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ public:
|
||||||
|
|
||||||
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
||||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
||||||
bool isAllowedEditor = false, bool canRez = false,
|
const NodePermissions& permissions = DEFAULT_AGENT_PERMISSIONS,
|
||||||
const QUuid& connectionSecret = QUuid());
|
const QUuid& connectionSecret = QUuid());
|
||||||
|
|
||||||
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
||||||
|
@ -254,6 +254,8 @@ signals:
|
||||||
|
|
||||||
void isAllowedEditorChanged(bool isAllowedEditor);
|
void isAllowedEditorChanged(bool isAllowedEditor);
|
||||||
void canRezChanged(bool canRez);
|
void canRezChanged(bool canRez);
|
||||||
|
void canRezTmpChanged(bool canRezTmp);
|
||||||
|
void canWriteAssetsChanged(bool canWriteAssets);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void connectedForLocalSocketTest();
|
void connectedForLocalSocketTest();
|
||||||
|
@ -300,8 +302,7 @@ protected:
|
||||||
int _numCollectedBytes;
|
int _numCollectedBytes;
|
||||||
|
|
||||||
QElapsedTimer _packetStatTimer;
|
QElapsedTimer _packetStatTimer;
|
||||||
bool _isAllowedEditor { false };
|
NodePermissions _permissions;
|
||||||
bool _thisNodeCanRez;
|
|
||||||
|
|
||||||
QPointer<QTimer> _initialSTUNTimer;
|
QPointer<QTimer> _initialSTUNTimer;
|
||||||
|
|
||||||
|
|
|
@ -184,6 +184,11 @@ void NLPacket::setType(PacketType type) {
|
||||||
writeTypeAndVersion();
|
writeTypeAndVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NLPacket::setVersion(PacketVersion version) {
|
||||||
|
_version = version;
|
||||||
|
writeTypeAndVersion();
|
||||||
|
}
|
||||||
|
|
||||||
void NLPacket::readType() {
|
void NLPacket::readType() {
|
||||||
_type = NLPacket::typeInHeader(*this);
|
_type = NLPacket::typeInHeader(*this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ public:
|
||||||
void setType(PacketType type);
|
void setType(PacketType type);
|
||||||
|
|
||||||
PacketVersion getVersion() const { return _version; }
|
PacketVersion getVersion() const { return _version; }
|
||||||
|
void setVersion(PacketVersion version);
|
||||||
|
|
||||||
const QUuid& getSourceID() const { return _sourceID; }
|
const QUuid& getSourceID() const { return _sourceID; }
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include "Node.h"
|
#include "Node.h"
|
||||||
#include "SharedUtil.h"
|
#include "SharedUtil.h"
|
||||||
|
#include "NodePermissions.h"
|
||||||
|
|
||||||
#include <QtCore/QDataStream>
|
#include <QtCore/QDataStream>
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
|
@ -47,7 +48,7 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
|
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
|
||||||
const HifiSockAddr& localSocket, bool isAllowedEditor, bool canRez, const QUuid& connectionSecret,
|
const HifiSockAddr& localSocket, const NodePermissions& permissions, const QUuid& connectionSecret,
|
||||||
QObject* parent) :
|
QObject* parent) :
|
||||||
NetworkPeer(uuid, publicSocket, localSocket, parent),
|
NetworkPeer(uuid, publicSocket, localSocket, parent),
|
||||||
_type(type),
|
_type(type),
|
||||||
|
@ -57,8 +58,7 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
|
||||||
_clockSkewUsec(0),
|
_clockSkewUsec(0),
|
||||||
_mutex(),
|
_mutex(),
|
||||||
_clockSkewMovingPercentile(30, 0.8f), // moving 80th percentile of 30 samples
|
_clockSkewMovingPercentile(30, 0.8f), // moving 80th percentile of 30 samples
|
||||||
_isAllowedEditor(isAllowedEditor),
|
_permissions(permissions)
|
||||||
_canRez(canRez)
|
|
||||||
{
|
{
|
||||||
// Update socket's object name
|
// Update socket's object name
|
||||||
setType(_type);
|
setType(_type);
|
||||||
|
@ -78,15 +78,12 @@ void Node::updateClockSkewUsec(qint64 clockSkewSample) {
|
||||||
_clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile();
|
_clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& out, const Node& node) {
|
QDataStream& operator<<(QDataStream& out, const Node& node) {
|
||||||
out << node._type;
|
out << node._type;
|
||||||
out << node._uuid;
|
out << node._uuid;
|
||||||
out << node._publicSocket;
|
out << node._publicSocket;
|
||||||
out << node._localSocket;
|
out << node._localSocket;
|
||||||
out << node._isAllowedEditor;
|
out << node._permissions;
|
||||||
out << node._canRez;
|
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,9 +92,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) {
|
||||||
in >> node._uuid;
|
in >> node._uuid;
|
||||||
in >> node._publicSocket;
|
in >> node._publicSocket;
|
||||||
in >> node._localSocket;
|
in >> node._localSocket;
|
||||||
in >> node._isAllowedEditor;
|
in >> node._permissions;
|
||||||
in >> node._canRez;
|
|
||||||
|
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,14 @@
|
||||||
#include "NodeType.h"
|
#include "NodeType.h"
|
||||||
#include "SimpleMovingAverage.h"
|
#include "SimpleMovingAverage.h"
|
||||||
#include "MovingPercentile.h"
|
#include "MovingPercentile.h"
|
||||||
|
#include "NodePermissions.h"
|
||||||
|
|
||||||
class Node : public NetworkPeer {
|
class Node : public NetworkPeer {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Node(const QUuid& uuid, NodeType_t type,
|
Node(const QUuid& uuid, NodeType_t type,
|
||||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
||||||
bool isAllowedEditor, bool canRez, const QUuid& connectionSecret = QUuid(),
|
const NodePermissions& permissions, const QUuid& connectionSecret = QUuid(),
|
||||||
QObject* parent = 0);
|
QObject* parent = 0);
|
||||||
|
|
||||||
bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; }
|
bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; }
|
||||||
|
@ -58,11 +59,12 @@ public:
|
||||||
void updateClockSkewUsec(qint64 clockSkewSample);
|
void updateClockSkewUsec(qint64 clockSkewSample);
|
||||||
QMutex& getMutex() { return _mutex; }
|
QMutex& getMutex() { return _mutex; }
|
||||||
|
|
||||||
void setIsAllowedEditor(bool isAllowedEditor) { _isAllowedEditor = isAllowedEditor; }
|
void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; }
|
||||||
bool isAllowedEditor() { return _isAllowedEditor; }
|
NodePermissions getPermissions() const { return _permissions; }
|
||||||
|
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||||
void setCanRez(bool canRez) { _canRez = canRez; }
|
bool getCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||||
bool getCanRez() { return _canRez; }
|
bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||||
|
bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; }
|
||||||
|
|
||||||
friend QDataStream& operator<<(QDataStream& out, const Node& node);
|
friend QDataStream& operator<<(QDataStream& out, const Node& node);
|
||||||
friend QDataStream& operator>>(QDataStream& in, Node& node);
|
friend QDataStream& operator>>(QDataStream& in, Node& node);
|
||||||
|
@ -81,8 +83,7 @@ private:
|
||||||
qint64 _clockSkewUsec;
|
qint64 _clockSkewUsec;
|
||||||
QMutex _mutex;
|
QMutex _mutex;
|
||||||
MovingPercentile _clockSkewMovingPercentile;
|
MovingPercentile _clockSkewMovingPercentile;
|
||||||
bool _isAllowedEditor;
|
NodePermissions _permissions;
|
||||||
bool _canRez;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Node*)
|
Q_DECLARE_METATYPE(Node*)
|
||||||
|
|
|
@ -50,7 +50,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
||||||
|
|
||||||
// handle domain change signals from AddressManager
|
// handle domain change signals from AddressManager
|
||||||
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired,
|
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired,
|
||||||
&_domainHandler, &DomainHandler::setHostnameAndPort);
|
&_domainHandler, &DomainHandler::setSocketAndID);
|
||||||
|
|
||||||
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID,
|
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID,
|
||||||
&_domainHandler, &DomainHandler::setIceServerHostnameAndID);
|
&_domainHandler, &DomainHandler::setIceServerHostnameAndID);
|
||||||
|
@ -250,7 +250,6 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
|
qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
|
||||||
handleICEConnectionToDomainServer();
|
handleICEConnectionToDomainServer();
|
||||||
} else if (!_domainHandler.getIP().isNull()) {
|
} else if (!_domainHandler.getIP().isNull()) {
|
||||||
bool isUsingDTLS = false;
|
|
||||||
|
|
||||||
PacketType domainPacketType = !_domainHandler.isConnected()
|
PacketType domainPacketType = !_domainHandler.isConnected()
|
||||||
? PacketType::DomainConnectRequest : PacketType::DomainListRequest;
|
? PacketType::DomainConnectRequest : PacketType::DomainListRequest;
|
||||||
|
@ -292,12 +291,18 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto packetVersion = (domainPacketType == PacketType::DomainConnectRequest) ? _domainConnectRequestVersion : 0;
|
auto domainPacket = NLPacket::create(domainPacketType);
|
||||||
auto domainPacket = NLPacket::create(domainPacketType, -1, false, false, packetVersion);
|
|
||||||
|
|
||||||
QDataStream packetStream(domainPacket.get());
|
QDataStream packetStream(domainPacket.get());
|
||||||
|
|
||||||
if (domainPacketType == PacketType::DomainConnectRequest) {
|
if (domainPacketType == PacketType::DomainConnectRequest) {
|
||||||
|
|
||||||
|
#if (PR_BUILD || DEV_BUILD)
|
||||||
|
if (_shouldSendNewerVersion) {
|
||||||
|
domainPacket->setVersion(versionForPacketType(domainPacketType) + 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
QUuid connectUUID;
|
QUuid connectUUID;
|
||||||
|
|
||||||
if (!_domainHandler.getAssignmentUUID().isNull()) {
|
if (!_domainHandler.getAssignmentUUID().isNull()) {
|
||||||
|
@ -315,18 +320,14 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
packetStream << connectUUID;
|
packetStream << connectUUID;
|
||||||
|
|
||||||
// include the protocol version signature in our connect request
|
// include the protocol version signature in our connect request
|
||||||
if (_domainConnectRequestVersion >= static_cast<PacketVersion>(DomainConnectRequestVersion::HasProtocolVersions)) {
|
QByteArray protocolVersionSig = protocolVersionsSignature();
|
||||||
QByteArray protocolVersionSig = protocolVersionsSignature();
|
packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size());
|
||||||
packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pack our data to send to the domain-server including
|
// pack our data to send to the domain-server including
|
||||||
// the hostname information (so the domain-server can see which place name we came in on)
|
// the hostname information (so the domain-server can see which place name we came in on)
|
||||||
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
||||||
if (_domainConnectRequestVersion >= static_cast<PacketVersion>(DomainConnectRequestVersion::HasHostname)) {
|
packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
|
||||||
packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_domainHandler.isConnected()) {
|
if (!_domainHandler.isConnected()) {
|
||||||
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
|
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
|
||||||
|
@ -341,9 +342,7 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
|
|
||||||
flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn);
|
flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn);
|
||||||
|
|
||||||
if (!isUsingDTLS) {
|
sendPacket(std::move(domainPacket), _domainHandler.getSockAddr());
|
||||||
sendPacket(std::move(domainPacket), _domainHandler.getSockAddr());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
|
if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
|
||||||
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
|
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
|
||||||
|
@ -462,7 +461,7 @@ void NodeList::handleICEConnectionToDomainServer() {
|
||||||
|
|
||||||
LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(),
|
LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(),
|
||||||
_domainHandler.getICEClientID(),
|
_domainHandler.getICEClientID(),
|
||||||
_domainHandler.getICEDomainID());
|
_domainHandler.getPendingDomainID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,7 +474,7 @@ void NodeList::pingPunchForDomainServer() {
|
||||||
|
|
||||||
if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) {
|
if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) {
|
||||||
qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID"
|
qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID"
|
||||||
<< uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID());
|
<< uuidStringWithoutCurlyBraces(_domainHandler.getPendingDomainID());
|
||||||
} else {
|
} else {
|
||||||
if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) {
|
if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) {
|
||||||
// if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat
|
// if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat
|
||||||
|
@ -527,7 +526,7 @@ void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message)
|
||||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList);
|
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList);
|
||||||
|
|
||||||
QDataStream packetStream(message->getMessage());
|
QDataStream packetStream(message->getMessage());
|
||||||
|
|
||||||
// grab the domain's ID from the beginning of the packet
|
// grab the domain's ID from the beginning of the packet
|
||||||
QUuid domainUUID;
|
QUuid domainUUID;
|
||||||
packetStream >> domainUUID;
|
packetStream >> domainUUID;
|
||||||
|
@ -543,14 +542,11 @@ void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message)
|
||||||
packetStream >> newUUID;
|
packetStream >> newUUID;
|
||||||
setSessionUUID(newUUID);
|
setSessionUUID(newUUID);
|
||||||
|
|
||||||
quint8 isAllowedEditor;
|
// pull the permissions/right/privileges for this node out of the stream
|
||||||
packetStream >> isAllowedEditor;
|
NodePermissions newPermissions;
|
||||||
setIsAllowedEditor((bool) isAllowedEditor);
|
packetStream >> newPermissions;
|
||||||
|
setPermissions(newPermissions);
|
||||||
|
|
||||||
quint8 thisNodeCanRez;
|
|
||||||
packetStream >> thisNodeCanRez;
|
|
||||||
setThisNodeCanRez((bool) thisNodeCanRez);
|
|
||||||
|
|
||||||
// pull each node in the packet
|
// pull each node in the packet
|
||||||
while (packetStream.device()->pos() < message->getSize()) {
|
while (packetStream.device()->pos() < message->getSize()) {
|
||||||
parseNodeFromPacketStream(packetStream);
|
parseNodeFromPacketStream(packetStream);
|
||||||
|
@ -577,10 +573,9 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
|
||||||
qint8 nodeType;
|
qint8 nodeType;
|
||||||
QUuid nodeUUID, connectionUUID;
|
QUuid nodeUUID, connectionUUID;
|
||||||
HifiSockAddr nodePublicSocket, nodeLocalSocket;
|
HifiSockAddr nodePublicSocket, nodeLocalSocket;
|
||||||
bool isAllowedEditor;
|
NodePermissions permissions;
|
||||||
bool canRez;
|
|
||||||
|
|
||||||
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> isAllowedEditor >> canRez;
|
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> permissions;
|
||||||
|
|
||||||
// if the public socket address is 0 then it's reachable at the same IP
|
// if the public socket address is 0 then it's reachable at the same IP
|
||||||
// as the domain server
|
// as the domain server
|
||||||
|
@ -591,8 +586,7 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
|
||||||
packetStream >> connectionUUID;
|
packetStream >> connectionUUID;
|
||||||
|
|
||||||
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket,
|
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket,
|
||||||
nodeLocalSocket, isAllowedEditor, canRez,
|
nodeLocalSocket, permissions, connectionUUID);
|
||||||
connectionUUID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::sendAssignment(Assignment& assignment) {
|
void NodeList::sendAssignment(Assignment& assignment) {
|
||||||
|
|
|
@ -68,9 +68,6 @@ public:
|
||||||
|
|
||||||
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
|
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
|
||||||
|
|
||||||
/// downgrades the DomainConnnectRequest PacketVersion to attempt to probe for older domain servers
|
|
||||||
void downgradeDomainServerCheckInVersion() { _domainConnectRequestVersion--; }
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void reset();
|
void reset();
|
||||||
void sendDomainServerCheckIn();
|
void sendDomainServerCheckIn();
|
||||||
|
@ -88,8 +85,9 @@ public slots:
|
||||||
|
|
||||||
void processICEPingPacket(QSharedPointer<ReceivedMessage> message);
|
void processICEPingPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
|
|
||||||
void resetDomainServerCheckInVersion()
|
#if (PR_BUILD || DEV_BUILD)
|
||||||
{ _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); }
|
void toggleSendNewerDSConnectVersion(bool shouldSendNewerVersion) { _shouldSendNewerVersion = shouldSendNewerVersion; }
|
||||||
|
#endif
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void limitOfSilentDomainCheckInsReached();
|
void limitOfSilentDomainCheckInsReached();
|
||||||
|
@ -105,6 +103,7 @@ private slots:
|
||||||
void pingPunchForDomainServer();
|
void pingPunchForDomainServer();
|
||||||
|
|
||||||
void sendKeepAlivePings();
|
void sendKeepAlivePings();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile
|
NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile
|
||||||
NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
|
NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
|
||||||
|
@ -130,7 +129,9 @@ private:
|
||||||
bool _isShuttingDown { false };
|
bool _isShuttingDown { false };
|
||||||
QTimer _keepAlivePingTimer;
|
QTimer _keepAlivePingTimer;
|
||||||
|
|
||||||
PacketVersion _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest);
|
#if (PR_BUILD || DEV_BUILD)
|
||||||
|
bool _shouldSendNewerVersion { false };
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_NodeList_h
|
#endif // hifi_NodeList_h
|
||||||
|
|
97
libraries/networking/src/NodePermissions.cpp
Normal file
97
libraries/networking/src/NodePermissions.cpp
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
//
|
||||||
|
// NodePermissions.cpp
|
||||||
|
// libraries/networking/src/
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2016-6-1.
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <QDataStream>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include "NodePermissions.h"
|
||||||
|
|
||||||
|
QString NodePermissions::standardNameLocalhost = QString("localhost");
|
||||||
|
QString NodePermissions::standardNameLoggedIn = QString("logged-in");
|
||||||
|
QString NodePermissions::standardNameAnonymous = QString("anonymous");
|
||||||
|
|
||||||
|
QStringList NodePermissions::standardNames = QList<QString>()
|
||||||
|
<< NodePermissions::standardNameLocalhost
|
||||||
|
<< NodePermissions::standardNameLoggedIn
|
||||||
|
<< NodePermissions::standardNameAnonymous;
|
||||||
|
|
||||||
|
NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) {
|
||||||
|
this->canConnectToDomain |= rhs.canConnectToDomain;
|
||||||
|
this->canAdjustLocks |= rhs.canAdjustLocks;
|
||||||
|
this->canRezPermanentEntities |= rhs.canRezPermanentEntities;
|
||||||
|
this->canRezTemporaryEntities |= rhs.canRezTemporaryEntities;
|
||||||
|
this->canWriteToAssetServer |= rhs.canWriteToAssetServer;
|
||||||
|
this->canConnectPastMaxCapacity |= rhs.canConnectPastMaxCapacity;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
NodePermissions& NodePermissions::operator|=(const NodePermissionsPointer& rhs) {
|
||||||
|
if (rhs) {
|
||||||
|
*this |= *rhs.get();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs) {
|
||||||
|
if (lhs && rhs) {
|
||||||
|
*lhs.get() |= rhs;
|
||||||
|
}
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QDataStream& operator<<(QDataStream& out, const NodePermissions& perms) {
|
||||||
|
out << perms.canConnectToDomain;
|
||||||
|
out << perms.canAdjustLocks;
|
||||||
|
out << perms.canRezPermanentEntities;
|
||||||
|
out << perms.canRezTemporaryEntities;
|
||||||
|
out << perms.canWriteToAssetServer;
|
||||||
|
out << perms.canConnectPastMaxCapacity;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream& operator>>(QDataStream& in, NodePermissions& perms) {
|
||||||
|
in >> perms.canConnectToDomain;
|
||||||
|
in >> perms.canAdjustLocks;
|
||||||
|
in >> perms.canRezPermanentEntities;
|
||||||
|
in >> perms.canRezTemporaryEntities;
|
||||||
|
in >> perms.canWriteToAssetServer;
|
||||||
|
in >> perms.canConnectPastMaxCapacity;
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, const NodePermissions& perms) {
|
||||||
|
debug.nospace() << "[permissions: " << perms.getID() << " --";
|
||||||
|
if (perms.canConnectToDomain) {
|
||||||
|
debug << " connect";
|
||||||
|
}
|
||||||
|
if (perms.canAdjustLocks) {
|
||||||
|
debug << " locks";
|
||||||
|
}
|
||||||
|
if (perms.canRezPermanentEntities) {
|
||||||
|
debug << " rez";
|
||||||
|
}
|
||||||
|
if (perms.canRezTemporaryEntities) {
|
||||||
|
debug << " rez-tmp";
|
||||||
|
}
|
||||||
|
if (perms.canWriteToAssetServer) {
|
||||||
|
debug << " asset-server";
|
||||||
|
}
|
||||||
|
if (perms.canConnectPastMaxCapacity) {
|
||||||
|
debug << " ignore-max-cap";
|
||||||
|
}
|
||||||
|
debug.nospace() << "]";
|
||||||
|
return debug.nospace();
|
||||||
|
}
|
||||||
|
QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms) {
|
||||||
|
if (perms) {
|
||||||
|
return operator<<(debug, *perms.get());
|
||||||
|
}
|
||||||
|
debug.nospace() << "[permissions: null]";
|
||||||
|
return debug.nospace();
|
||||||
|
}
|
97
libraries/networking/src/NodePermissions.h
Normal file
97
libraries/networking/src/NodePermissions.h
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
//
|
||||||
|
// NodePermissions.h
|
||||||
|
// libraries/networking/src/
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2016-6-1.
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_NodePermissions_h
|
||||||
|
#define hifi_NodePermissions_h
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <QString>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
class NodePermissions;
|
||||||
|
using NodePermissionsPointer = std::shared_ptr<NodePermissions>;
|
||||||
|
|
||||||
|
class NodePermissions {
|
||||||
|
public:
|
||||||
|
NodePermissions() { _id = QUuid::createUuid().toString(); }
|
||||||
|
NodePermissions(const QString& name) { _id = name; }
|
||||||
|
NodePermissions(QMap<QString, QVariant> perms) {
|
||||||
|
_id = perms["permissions_id"].toString();
|
||||||
|
canConnectToDomain = perms["id_can_connect"].toBool();
|
||||||
|
canAdjustLocks = perms["id_can_adjust_locks"].toBool();
|
||||||
|
canRezPermanentEntities = perms["id_can_rez"].toBool();
|
||||||
|
canRezTemporaryEntities = perms["id_can_rez_tmp"].toBool();
|
||||||
|
canWriteToAssetServer = perms["id_can_write_to_asset_server"].toBool();
|
||||||
|
canConnectPastMaxCapacity = perms["id_can_connect_past_max_capacity"].toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getID() const { return _id; }
|
||||||
|
|
||||||
|
// the _id member isn't authenticated and _username is.
|
||||||
|
void setUserName(QString userName) { _userName = userName; }
|
||||||
|
QString getUserName() { return _userName; }
|
||||||
|
|
||||||
|
bool isAssignment { false };
|
||||||
|
|
||||||
|
// these 3 names have special meaning.
|
||||||
|
static QString standardNameLocalhost;
|
||||||
|
static QString standardNameLoggedIn;
|
||||||
|
static QString standardNameAnonymous;
|
||||||
|
static QStringList standardNames;
|
||||||
|
|
||||||
|
// the initializations here should match the defaults in describe-settings.json
|
||||||
|
bool canConnectToDomain { true };
|
||||||
|
bool canAdjustLocks { false };
|
||||||
|
bool canRezPermanentEntities { false };
|
||||||
|
bool canRezTemporaryEntities { false };
|
||||||
|
bool canWriteToAssetServer { false };
|
||||||
|
bool canConnectPastMaxCapacity { false };
|
||||||
|
|
||||||
|
void setAll(bool value) {
|
||||||
|
canConnectToDomain = value;
|
||||||
|
canAdjustLocks = value;
|
||||||
|
canRezPermanentEntities = value;
|
||||||
|
canRezTemporaryEntities = value;
|
||||||
|
canWriteToAssetServer = value;
|
||||||
|
canConnectPastMaxCapacity = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant toVariant() {
|
||||||
|
QMap<QString, QVariant> values;
|
||||||
|
values["permissions_id"] = _id;
|
||||||
|
values["id_can_connect"] = canConnectToDomain;
|
||||||
|
values["id_can_adjust_locks"] = canAdjustLocks;
|
||||||
|
values["id_can_rez"] = canRezPermanentEntities;
|
||||||
|
values["id_can_rez_tmp"] = canRezTemporaryEntities;
|
||||||
|
values["id_can_write_to_asset_server"] = canWriteToAssetServer;
|
||||||
|
values["id_can_connect_past_max_capacity"] = canConnectPastMaxCapacity;
|
||||||
|
return QVariant(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
NodePermissions& operator|=(const NodePermissions& rhs);
|
||||||
|
NodePermissions& operator|=(const NodePermissionsPointer& rhs);
|
||||||
|
friend QDataStream& operator<<(QDataStream& out, const NodePermissions& perms);
|
||||||
|
friend QDataStream& operator>>(QDataStream& in, NodePermissions& perms);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString _id;
|
||||||
|
QString _userName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NodePermissions DEFAULT_AGENT_PERMISSIONS;
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, const NodePermissions& perms);
|
||||||
|
QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms);
|
||||||
|
NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs);
|
||||||
|
|
||||||
|
#endif // hifi_NodePermissions_h
|
|
@ -45,7 +45,7 @@ const QSet<PacketType> RELIABLE_PACKETS = QSet<PacketType>();
|
||||||
PacketVersion versionForPacketType(PacketType packetType) {
|
PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
switch (packetType) {
|
switch (packetType) {
|
||||||
case PacketType::DomainList:
|
case PacketType::DomainList:
|
||||||
return 18;
|
return static_cast<PacketVersion>(DomainListVersion::PermissionsGrid);
|
||||||
case PacketType::EntityAdd:
|
case PacketType::EntityAdd:
|
||||||
case PacketType::EntityEdit:
|
case PacketType::EntityEdit:
|
||||||
case PacketType::EntityData:
|
case PacketType::EntityData:
|
||||||
|
@ -69,6 +69,9 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::DomainConnectRequest:
|
case PacketType::DomainConnectRequest:
|
||||||
return static_cast<PacketVersion>(DomainConnectRequestVersion::HasProtocolVersions);
|
return static_cast<PacketVersion>(DomainConnectRequestVersion::HasProtocolVersions);
|
||||||
|
|
||||||
|
case PacketType::DomainServerAddedNode:
|
||||||
|
return static_cast<PacketVersion>(DomainServerAddedNodeVersion::PermissionsGrid);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 17;
|
return 17;
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,4 +199,14 @@ enum class DomainConnectionDeniedVersion : PacketVersion {
|
||||||
IncludesReasonCode
|
IncludesReasonCode
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class DomainServerAddedNodeVersion : PacketVersion {
|
||||||
|
PrePermissionsGrid = 17,
|
||||||
|
PermissionsGrid
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DomainListVersion : PacketVersion {
|
||||||
|
PrePermissionsGrid = 18,
|
||||||
|
PermissionsGrid
|
||||||
|
};
|
||||||
|
|
||||||
#endif // hifi_PacketHeaders_h
|
#endif // hifi_PacketHeaders_h
|
||||||
|
|
|
@ -1863,9 +1863,9 @@ bool Octree::readJSONFromStream(unsigned long streamLength, QDataStream& inputSt
|
||||||
QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer);
|
QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer);
|
||||||
QVariant asVariant = asDocument.toVariant();
|
QVariant asVariant = asDocument.toVariant();
|
||||||
QVariantMap asMap = asVariant.toMap();
|
QVariantMap asMap = asVariant.toMap();
|
||||||
readFromMap(asMap);
|
bool success = readFromMap(asMap);
|
||||||
delete[] rawData;
|
delete[] rawData;
|
||||||
return true;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Octree::writeToFile(const char* fileName, OctreeElementPointer element, QString persistAsFileType) {
|
void Octree::writeToFile(const char* fileName, OctreeElementPointer element, QString persistAsFileType) {
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <atomic>
|
||||||
#include <btBulletDynamicsCommon.h>
|
#include <btBulletDynamicsCommon.h>
|
||||||
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
||||||
|
|
||||||
|
@ -105,8 +106,9 @@ public:
|
||||||
|
|
||||||
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
|
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||||
|
|
||||||
|
bool isEnabled() const { return _enabled; } // thread-safe
|
||||||
void setEnabled(bool enabled);
|
void setEnabled(bool enabled);
|
||||||
bool isEnabled() const { return _enabled && _dynamicsWorld; }
|
bool isEnabledAndReady() const { return _enabled && _dynamicsWorld; }
|
||||||
|
|
||||||
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
|
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
|
||||||
|
|
||||||
|
@ -167,7 +169,7 @@ protected:
|
||||||
btQuaternion _followAngularDisplacement;
|
btQuaternion _followAngularDisplacement;
|
||||||
btVector3 _linearAcceleration;
|
btVector3 _linearAcceleration;
|
||||||
|
|
||||||
bool _enabled;
|
std::atomic_bool _enabled;
|
||||||
State _state;
|
State _state;
|
||||||
bool _isPushingUp;
|
bool _isPushingUp;
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,10 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <QQueue>
|
#include <QtCore/QElapsedTimer>
|
||||||
#include <QMutex>
|
#include <QtCore/QMutex>
|
||||||
#include <QWaitCondition>
|
#include <QtCore/QQueue>
|
||||||
|
#include <QtCore/QWaitCondition>
|
||||||
|
|
||||||
#include "GenericThread.h"
|
#include "GenericThread.h"
|
||||||
#include "NumericalConstants.h"
|
#include "NumericalConstants.h"
|
||||||
|
@ -35,6 +36,25 @@ public:
|
||||||
_hasItems.wakeAll();
|
_hasItems.wakeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void waitIdle(uint32_t maxWaitMs = UINT32_MAX) {
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
// FIXME this will work as long as the thread doing the wait
|
||||||
|
// is the only thread which can add work to the queue.
|
||||||
|
// It would be better if instead we had a queue empty condition to wait on
|
||||||
|
// that would ensure that we get woken as soon as we're idle the very
|
||||||
|
// first time the queue was empty.
|
||||||
|
while (timer.elapsed() < maxWaitMs) {
|
||||||
|
lock();
|
||||||
|
if (!_items.size()) {
|
||||||
|
unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void queueItemInternal(const T& t) {
|
virtual void queueItemInternal(const T& t) {
|
||||||
_items.push_back(t);
|
_items.push_back(t);
|
||||||
|
@ -44,6 +64,7 @@ protected:
|
||||||
return MSECS_PER_SECOND;
|
return MSECS_PER_SECOND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
virtual bool process() {
|
virtual bool process() {
|
||||||
lock();
|
lock();
|
||||||
if (!_items.size()) {
|
if (!_items.size()) {
|
||||||
|
|
|
@ -213,10 +213,12 @@ QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool
|
||||||
if (shouldCreateIfMissing || variantMap.contains(firstKey)) {
|
if (shouldCreateIfMissing || variantMap.contains(firstKey)) {
|
||||||
if (dotIndex == -1) {
|
if (dotIndex == -1) {
|
||||||
return &variantMap[firstKey];
|
return &variantMap[firstKey];
|
||||||
} else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
|
|
||||||
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
|
|
||||||
shouldCreateIfMissing);
|
|
||||||
}
|
}
|
||||||
|
if (!variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
|
||||||
|
variantMap[firstKey] = QVariantMap();
|
||||||
|
}
|
||||||
|
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
|
||||||
|
shouldCreateIfMissing);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
Loading…
Reference in a new issue