mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 21:30:33 +02:00
Merge branch 'vive-ui' of https://github.com/highfidelity/hifi into use-system-pointer
This commit is contained in:
commit
0366c891cc
49 changed files with 1586 additions and 662 deletions
|
@ -3,9 +3,10 @@ Please read the [general build guide](BUILD.md) for information on dependencies
|
||||||
###Homebrew
|
###Homebrew
|
||||||
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all High Fidelity dependencies very simple.
|
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all High Fidelity dependencies very simple.
|
||||||
|
|
||||||
brew install cmake openssl qt5
|
brew tap homebrew/versions
|
||||||
|
brew install cmake openssl qt55
|
||||||
|
|
||||||
We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x and above provide a mechanism to disable the wireless scanning we previously had a custom patch for.
|
We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x provide a mechanism to disable the wireless scanning we previously had a custom patch for.
|
||||||
|
|
||||||
###OpenSSL and Qt
|
###OpenSSL and Qt
|
||||||
|
|
||||||
|
|
|
@ -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.3,
|
"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",
|
||||||
|
@ -157,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",
|
||||||
|
@ -187,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",
|
||||||
"columns": [
|
"can_add_new_rows": false,
|
||||||
|
|
||||||
|
"groups": [
|
||||||
{
|
{
|
||||||
"name": "username",
|
"label": "User / Group",
|
||||||
"label": "Username",
|
"span": 1
|
||||||
"can_set": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "editors_are_rezzers",
|
"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",
|
"type": "checkbox",
|
||||||
"label": "Only Editors Can Create Entities",
|
"editable": true,
|
||||||
"help": "Only users listed in \"Allowed Editors\" can create new entites.",
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_adjust_locks",
|
||||||
|
"label": "Lock / Unlock",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
"default": false
|
"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": "permissions",
|
||||||
|
"type": "table",
|
||||||
|
"caption": "Permissions for Specific Users",
|
||||||
|
"can_add_new_rows": true,
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -218,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",
|
||||||
|
@ -302,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",
|
||||||
|
@ -353,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",
|
||||||
|
@ -380,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",
|
||||||
|
@ -479,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",
|
||||||
|
@ -501,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,10 +1058,16 @@ function makeTableInputs(setting) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_.each(setting.columns, function(col) {
|
_.each(setting.columns, function(col) {
|
||||||
|
if (col.type === "checkbox") {
|
||||||
|
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||||
|
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||||
|
+ "name='" + col.name + "'" + (col.default ? " checked" : "") + "/></td>";
|
||||||
|
} else {
|
||||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||||
</td>"
|
</td>"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (setting.can_order) {
|
if (setting.can_order) {
|
||||||
|
@ -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")
|
||||||
|
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")
|
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
|
||||||
|
|
||||||
|
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 : ""))
|
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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,10 +62,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +120,60 @@ 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) {
|
||||||
|
|
||||||
|
@ -165,15 +216,16 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
||||||
_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,
|
||||||
|
@ -181,92 +233,66 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
||||||
|
|
||||||
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) {
|
||||||
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||||
|
qDebug() << "user-permissions: is local user, so:" << userPerms;
|
||||||
|
}
|
||||||
|
|
||||||
// if we're using restricted access and this user is not local make sure we got a user signature
|
if (!username.isEmpty() && usernameSignature.isEmpty()) {
|
||||||
if (isRestrictingAccess && !isLocalUser) {
|
// user is attempting to prove their identity to us, but we don't have enough information
|
||||||
if (!username.isEmpty()) {
|
|
||||||
if (usernameSignature.isEmpty()) {
|
|
||||||
// if user didn't include usernameSignature in connect request, send a connectionToken packet
|
|
||||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||||
|
|
||||||
// ask for their public key right now to make sure we have it
|
// ask for their public key right now to make sure we have it
|
||||||
requestUserPublicKey(username);
|
requestUserPublicKey(username);
|
||||||
|
if (!userPerms.canConnectToDomain) {
|
||||||
return SharedNodePointer();
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool verifiedUsername = false;
|
if (username.isEmpty()) {
|
||||||
|
// they didn't tell us who they are
|
||||||
// if we do not have a local user we need to subject them to our verification and capacity checks
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||||
if (!isLocalUser) {
|
qDebug() << "user-permissions: no username, so:" << userPerms;
|
||||||
|
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||||
// check if we need to look at the username signature
|
// they are sent us a username and the signature verifies it
|
||||||
if (isRestrictingAccess) {
|
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||||
if (isVerifiedAllowedUser(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
// we have specific permissions for this user.
|
||||||
// we verified the user via their username and signature - set the verifiedUsername
|
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||||
// so we don't re-decrypt their sig if we're trying to exempt them from max capacity check (due to
|
qDebug() << "user-permissions: specific user matches, so:" << userPerms;
|
||||||
// being in the allowed editors list)
|
|
||||||
verifiedUsername = true;
|
|
||||||
} else {
|
} else {
|
||||||
// failed to verify user - return a null shared ptr
|
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||||
return SharedNodePointer();
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||||
|
qDebug() << "user-permissions: user is logged in, so:" << userPerms;
|
||||||
}
|
}
|
||||||
}
|
userPerms.setUserName(username);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this user is in the editors list (or if the editors list is empty) set the user's node's isAllowedEditor to true
|
|
||||||
const QVariant* allowedEditorsVariant =
|
|
||||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
|
||||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
|
||||||
|
|
||||||
// if the allowed editors list is empty then everyone can adjust locks
|
|
||||||
bool isAllowedEditor = allowedEditors.empty();
|
|
||||||
|
|
||||||
if (allowedEditors.contains(username, Qt::CaseInsensitive)) {
|
|
||||||
// we have a non-empty allowed editors list - check if this user is verified to be in it
|
|
||||||
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 sent us a username, but it didn't check out
|
||||||
isAllowedEditor = true;
|
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);
|
|
||||||
|
|
||||||
bool onlyEditorsAreRezzers = false;
|
if (!userPerms.canConnectToDomain) {
|
||||||
if (editorsAreRezzersVariant) {
|
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
|
||||||
onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool();
|
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||||
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -290,8 +316,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
||||||
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());
|
||||||
|
@ -367,7 +392,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
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);
|
||||||
|
@ -407,67 +432,17 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,8 +454,7 @@ bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteA
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -492,6 +466,11 @@ 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;
|
||||||
|
@ -530,6 +509,15 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) {
|
||||||
|
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,
|
void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
||||||
DomainHandler::ConnectionRefusedReason reasonCode) {
|
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
|
||||||
|
|
|
@ -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,17 +72,13 @@ 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);
|
||||||
|
|
|
@ -101,6 +101,13 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
// 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
|
||||||
|
@ -318,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.
|
||||||
|
@ -800,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);
|
||||||
|
|
||||||
|
@ -1093,11 +1094,12 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
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 -
|
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
|
||||||
// for now that will exclude it from listings
|
const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||||
static 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;
|
||||||
|
|
||||||
// Add the metadata to the heartbeat
|
// Add the metadata to the heartbeat
|
||||||
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
||||||
|
@ -2117,7 +2119,15 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
|
||||||
auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID);
|
auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID);
|
||||||
|
|
||||||
if (nodeToKill) {
|
if (nodeToKill) {
|
||||||
|
handleKillNode(nodeToKill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
|
||||||
auto nodeType = nodeToKill->getType();
|
auto nodeType = nodeToKill->getType();
|
||||||
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
const QUuid& nodeUUID = nodeToKill->getUUID();
|
||||||
|
|
||||||
limitedNodeList->killNodeWithUUID(nodeUUID);
|
limitedNodeList->killNodeWithUUID(nodeUUID);
|
||||||
|
|
||||||
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
|
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
|
||||||
|
@ -2134,7 +2144,6 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
|
||||||
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *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;
|
||||||
|
|
|
@ -114,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);
|
||||||
|
|
|
@ -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
|
||||||
|
if (restartRequired) {
|
||||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
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,8 +690,9 @@ 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()) {
|
||||||
|
@ -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
|
||||||
|
|
|
@ -199,7 +199,7 @@ FocusScope {
|
||||||
anchors.leftMargin: hifi.dimensions.textPadding
|
anchors.leftMargin: hifi.dimensions.textPadding
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
id: popupText
|
id: popupText
|
||||||
text: listView.model[index]
|
text: listView.model[index] ? listView.model[index] : ""
|
||||||
size: hifi.fontSizes.textFieldInput
|
size: hifi.fontSizes.textFieldInput
|
||||||
color: hifi.colors.baseGray
|
color: hifi.colors.baseGray
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,7 +186,12 @@ ModalWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
|
if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
|
||||||
|
if (root.selectDirectory) {
|
||||||
|
currentSelection.text = currentText !== "This PC" ? currentText : "";
|
||||||
|
d.currentSelectionUrl = helper.pathToUrl(currentText);
|
||||||
|
}
|
||||||
fileTableModel.folder = folder;
|
fileTableModel.folder = folder;
|
||||||
|
fileTableView.forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,9 +217,11 @@ ModalWindow {
|
||||||
function update() {
|
function update() {
|
||||||
var row = fileTableView.currentRow;
|
var row = fileTableView.currentRow;
|
||||||
|
|
||||||
openButton.text = root.selectDirectory && row === -1 ? "Choose" : "Open"
|
|
||||||
|
|
||||||
if (row === -1) {
|
if (row === -1) {
|
||||||
|
if (!root.selectDirectory) {
|
||||||
|
currentSelection.text = "";
|
||||||
|
currentSelectionIsFolder = false;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,12 +452,6 @@ ModalWindow {
|
||||||
|
|
||||||
onSortIndicatorOrderChanged: { updateSort(); }
|
onSortIndicatorOrderChanged: { updateSort(); }
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
|
||||||
if (activeFocus && currentRow == -1) {
|
|
||||||
fileTableView.selection.select(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
itemDelegate: Item {
|
itemDelegate: Item {
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
@ -607,6 +608,12 @@ ModalWindow {
|
||||||
readOnly: !root.saveDialog
|
readOnly: !root.saveDialog
|
||||||
activeFocusOnTab: !readOnly
|
activeFocusOnTab: !readOnly
|
||||||
onActiveFocusChanged: if (activeFocus) { selectAll(); }
|
onActiveFocusChanged: if (activeFocus) { selectAll(); }
|
||||||
|
onTextChanged: {
|
||||||
|
if (root.saveDialog && text !== "") {
|
||||||
|
fileTableView.selection.clear();
|
||||||
|
fileTableView.currentRow = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
onAccepted: okAction.trigger();
|
onAccepted: okAction.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,7 +659,7 @@ ModalWindow {
|
||||||
|
|
||||||
Action {
|
Action {
|
||||||
id: okAction
|
id: okAction
|
||||||
text: root.saveDialog ? "Save" : (root.selectDirectory ? "Choose" : "Open")
|
text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open"
|
||||||
enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
|
enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!root.selectDirectory && !d.currentSelectionIsFolder
|
if (!root.selectDirectory && !d.currentSelectionIsFolder
|
||||||
|
@ -676,7 +683,6 @@ ModalWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Handle the ambiguity between different cases
|
// Handle the ambiguity between different cases
|
||||||
// * typed name (with or without extension)
|
// * typed name (with or without extension)
|
||||||
// * full path vs relative vs filename only
|
// * full path vs relative vs filename only
|
||||||
|
|
|
@ -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";
|
||||||
|
@ -4209,6 +4208,7 @@ void Application::resetSensors(bool andReload) {
|
||||||
DependencyManager::get<DdeFaceTracker>()->reset();
|
DependencyManager::get<DdeFaceTracker>()->reset();
|
||||||
DependencyManager::get<EyeTracker>()->reset();
|
DependencyManager::get<EyeTracker>()->reset();
|
||||||
getActiveDisplayPlugin()->resetSensors();
|
getActiveDisplayPlugin()->resetSensors();
|
||||||
|
_overlayConductor.centerUI();
|
||||||
getMyAvatar()->reset(andReload);
|
getMyAvatar()->reset(andReload);
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
@ -4294,7 +4294,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 {
|
||||||
|
@ -4620,17 +4620,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()) {
|
||||||
|
@ -4801,7 +4790,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;
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -256,8 +256,7 @@ Menu::Menu() {
|
||||||
UNSPECIFIED_POSITION, "Advanced");
|
UNSPECIFIED_POSITION, "Advanced");
|
||||||
|
|
||||||
// View > Overlays
|
// View > Overlays
|
||||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true,
|
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true);
|
||||||
qApp, SLOT(setOverlaysVisible(bool)));
|
|
||||||
|
|
||||||
// Navigate menu ----------------------------------
|
// Navigate menu ----------------------------------
|
||||||
MenuWrapper* navigateMenu = addMenu("Navigate");
|
MenuWrapper* navigateMenu = addMenu("Navigate");
|
||||||
|
@ -542,6 +541,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";
|
||||||
|
|
|
@ -1246,8 +1246,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
||||||
|
|
||||||
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
|
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
|
||||||
if (qApp->isHMDMode()) {
|
if (qApp->isHMDMode()) {
|
||||||
bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
|
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput());
|
||||||
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput);
|
|
||||||
} else {
|
} else {
|
||||||
_follow.deactivate();
|
_follow.deactivate();
|
||||||
}
|
}
|
||||||
|
@ -2132,3 +2131,7 @@ bool MyAvatar::didTeleport() {
|
||||||
lastPosition = pos;
|
lastPosition = pos;
|
||||||
return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME);
|
return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MyAvatar::hasDriveInput() const {
|
||||||
|
return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
|
||||||
|
}
|
||||||
|
|
|
@ -266,6 +266,8 @@ public:
|
||||||
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
|
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
|
||||||
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
|
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
|
||||||
|
|
||||||
|
bool hasDriveInput() const;
|
||||||
|
|
||||||
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
|
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
|
||||||
Q_INVOKABLE bool getCharacterControllerEnabled();
|
Q_INVOKABLE bool getCharacterControllerEnabled();
|
||||||
|
|
||||||
|
|
|
@ -17,122 +17,164 @@
|
||||||
#include "OverlayConductor.h"
|
#include "OverlayConductor.h"
|
||||||
|
|
||||||
OverlayConductor::OverlayConductor() {
|
OverlayConductor::OverlayConductor() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OverlayConductor::~OverlayConductor() {
|
OverlayConductor::~OverlayConductor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OverlayConductor::headOutsideOverlay() const {
|
||||||
|
glm::mat4 hmdMat = qApp->getHMDSensorPose();
|
||||||
|
glm::vec3 hmdPos = extractTranslation(hmdMat);
|
||||||
|
glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f));
|
||||||
|
|
||||||
|
Transform uiTransform = qApp->getApplicationCompositor().getModelTransform();
|
||||||
|
glm::vec3 uiPos = uiTransform.getTranslation();
|
||||||
|
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
|
||||||
|
|
||||||
|
const float MAX_COMPOSITOR_DISTANCE = 0.6f;
|
||||||
|
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
|
||||||
|
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
|
||||||
|
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OverlayConductor::updateAvatarIsAtRest() {
|
||||||
|
|
||||||
|
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
|
|
||||||
|
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
|
||||||
|
const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
|
||||||
|
|
||||||
|
const float AT_REST_THRESHOLD = 0.01f;
|
||||||
|
bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD;
|
||||||
|
if (desiredAtRest != _desiredAtRest) {
|
||||||
|
// start timer
|
||||||
|
_desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS);
|
||||||
|
}
|
||||||
|
|
||||||
|
_desiredAtRest = desiredAtRest;
|
||||||
|
|
||||||
|
if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) {
|
||||||
|
// timer expired
|
||||||
|
// change state!
|
||||||
|
_currentAtRest = _desiredAtRest;
|
||||||
|
// disable timer
|
||||||
|
_desiredAtRestTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _currentAtRest;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OverlayConductor::updateAvatarHasDriveInput() {
|
||||||
|
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
|
|
||||||
|
const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms
|
||||||
|
const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s
|
||||||
|
|
||||||
|
bool desiredDriving = myAvatar->hasDriveInput();
|
||||||
|
if (desiredDriving != _desiredDriving) {
|
||||||
|
// start timer
|
||||||
|
_desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS);
|
||||||
|
}
|
||||||
|
|
||||||
|
_desiredDriving = desiredDriving;
|
||||||
|
|
||||||
|
if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) {
|
||||||
|
// timer expired
|
||||||
|
// change state!
|
||||||
|
_currentDriving = _desiredDriving;
|
||||||
|
// disable timer
|
||||||
|
_desiredDrivingTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _currentDriving;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverlayConductor::centerUI() {
|
||||||
|
// place the overlay at the current hmd position in sensor space
|
||||||
|
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
|
||||||
|
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OverlayConductor::userWishesToHide() const {
|
||||||
|
// user pressed toggle button.
|
||||||
|
return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OverlayConductor::userWishesToShow() const {
|
||||||
|
// user pressed toggle button.
|
||||||
|
return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && !Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverlayConductor::setState(State state) {
|
||||||
|
#ifdef WANT_DEBUG
|
||||||
|
static QString stateToString[NumStates] = { "Enabled", "DisabledByDrive", "DisabledByHead", "DisabledByToggle" };
|
||||||
|
qDebug() << "OverlayConductor " << stateToString[state] << "<--" << stateToString[_state];
|
||||||
|
#endif
|
||||||
|
_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
OverlayConductor::State OverlayConductor::getState() const {
|
||||||
|
return _state;
|
||||||
|
}
|
||||||
|
|
||||||
void OverlayConductor::update(float dt) {
|
void OverlayConductor::update(float dt) {
|
||||||
|
|
||||||
updateMode();
|
|
||||||
|
|
||||||
switch (_mode) {
|
|
||||||
case SITTING: {
|
|
||||||
// when sitting, the overlay is at the origin, facing down the -z axis.
|
|
||||||
// the camera is taken directly from the HMD.
|
|
||||||
Transform identity;
|
|
||||||
qApp->getApplicationCompositor().setModelTransform(identity);
|
|
||||||
qApp->getApplicationCompositor().setCameraBaseTransform(identity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case STANDING: {
|
|
||||||
// when standing, the overlay is at a reference position, which is set when the overlay is
|
|
||||||
// enabled. The camera is taken directly from the HMD, but in world space.
|
|
||||||
// So the sensorToWorldMatrix must be applied.
|
|
||||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
Transform t;
|
|
||||||
t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix());
|
|
||||||
qApp->getApplicationCompositor().setCameraBaseTransform(t);
|
|
||||||
|
|
||||||
// detect when head moves out side of sweet spot, or looks away.
|
// centerUI when hmd mode is first enabled
|
||||||
mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose();
|
if (qApp->isHMDMode() && !_hmdMode) {
|
||||||
vec3 headWorldPos = extractTranslation(headMat);
|
centerUI();
|
||||||
vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f);
|
}
|
||||||
Transform modelXform = qApp->getApplicationCompositor().getModelTransform();
|
_hmdMode = qApp->isHMDMode();
|
||||||
vec3 compositorWorldPos = modelXform.getTranslation();
|
|
||||||
vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
|
bool prevDriving = _currentDriving;
|
||||||
const float MAX_COMPOSITOR_DISTANCE = 0.6f;
|
bool isDriving = updateAvatarHasDriveInput();
|
||||||
const float MAX_COMPOSITOR_ANGLE = 110.0f;
|
bool drivingChanged = prevDriving != isDriving;
|
||||||
if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE ||
|
|
||||||
glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) {
|
bool isAtRest = updateAvatarIsAtRest();
|
||||||
// fade out the overlay
|
|
||||||
|
switch (getState()) {
|
||||||
|
case Enabled:
|
||||||
|
if (myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode() && headOutsideOverlay()) {
|
||||||
|
setState(DisabledByHead);
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
if (userWishesToHide()) {
|
||||||
|
setState(DisabledByToggle);
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) {
|
||||||
|
setState(DisabledByDrive);
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case DisabledByDrive:
|
||||||
|
if (!isDriving || userWishesToShow()) {
|
||||||
|
setState(Enabled);
|
||||||
|
setEnabled(true);
|
||||||
}
|
}
|
||||||
case FLAT:
|
|
||||||
// do nothing
|
|
||||||
break;
|
break;
|
||||||
|
case DisabledByHead:
|
||||||
|
if (isAtRest || userWishesToShow()) {
|
||||||
|
setState(Enabled);
|
||||||
|
setEnabled(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void OverlayConductor::updateMode() {
|
|
||||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
|
||||||
if (myAvatar->getClearOverlayWhenDriving()) {
|
|
||||||
float speed = glm::length(myAvatar->getVelocity());
|
|
||||||
const float MIN_DRIVING = 0.2f;
|
|
||||||
const float MAX_NOT_DRIVING = 0.01f;
|
|
||||||
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000;
|
|
||||||
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000;
|
|
||||||
bool nowDriving = _driving; // Assume current _driving mode unless...
|
|
||||||
if (speed > MIN_DRIVING) { // ... we're definitely moving...
|
|
||||||
nowDriving = true;
|
|
||||||
} else if (speed < MAX_NOT_DRIVING) { // ... or definitely not.
|
|
||||||
nowDriving = false;
|
|
||||||
}
|
|
||||||
// Check that we're in this new mode for long enough to really trigger a transition.
|
|
||||||
if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer.
|
|
||||||
_timeInPotentialMode = 0;
|
|
||||||
} else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now.
|
|
||||||
_timeInPotentialMode = usecTimestampNow();
|
|
||||||
} else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) {
|
|
||||||
_timeInPotentialMode = 0; // a real transition
|
|
||||||
if (nowDriving) {
|
|
||||||
_wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
|
|
||||||
} else { // reset when coming out of driving
|
|
||||||
_mode = FLAT; // Seems appropriate to let things reset, below, after the following.
|
|
||||||
// All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments.
|
|
||||||
qApp->getActiveDisplayPlugin()->resetSensors();
|
|
||||||
myAvatar->reset(true, false, false);
|
|
||||||
}
|
|
||||||
if (_wantsOverlays) {
|
|
||||||
setEnabled(!nowDriving);
|
|
||||||
}
|
|
||||||
_driving = nowDriving;
|
|
||||||
} // Else haven't accumulated enough time in new mode, but keep timing.
|
|
||||||
}
|
|
||||||
|
|
||||||
Mode newMode;
|
|
||||||
if (qApp->isHMDMode()) {
|
|
||||||
newMode = SITTING;
|
|
||||||
} else {
|
|
||||||
newMode = FLAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newMode != _mode) {
|
|
||||||
switch (newMode) {
|
|
||||||
case SITTING: {
|
|
||||||
// enter the SITTING state
|
|
||||||
// place the overlay at origin
|
|
||||||
qApp->getApplicationCompositor().setModelTransform(Transform());
|
|
||||||
break;
|
break;
|
||||||
|
case DisabledByToggle:
|
||||||
|
if (userWishesToShow()) {
|
||||||
|
setState(Enabled);
|
||||||
|
setEnabled(true);
|
||||||
}
|
}
|
||||||
case STANDING: { // STANDING mode is not currently used.
|
break;
|
||||||
// enter the STANDING state
|
default:
|
||||||
// place the overlay at the current hmd position in world space
|
|
||||||
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
|
|
||||||
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case FLAT:
|
_prevOverlayMenuChecked = Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
|
||||||
// do nothing
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_mode = newMode;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayConductor::setEnabled(bool enabled) {
|
void OverlayConductor::setEnabled(bool enabled) {
|
||||||
|
@ -143,12 +185,14 @@ void OverlayConductor::setEnabled(bool enabled) {
|
||||||
_enabled = enabled; // set the new value
|
_enabled = enabled; // set the new value
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
offscreenUi->setPinned(!_enabled);
|
offscreenUi->setPinned(!_enabled);
|
||||||
|
|
||||||
|
// ensure that the the state of the menu item reflects the state of the overlay.
|
||||||
|
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, _enabled);
|
||||||
|
_prevOverlayMenuChecked = _enabled;
|
||||||
|
|
||||||
// if the new state is visible/enabled...
|
// if the new state is visible/enabled...
|
||||||
if (_enabled && _mode == STANDING) {
|
if (_enabled && qApp->isHMDMode()) {
|
||||||
// place the overlay at the current hmd position in world space
|
centerUI();
|
||||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
|
||||||
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
|
|
||||||
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,20 +20,41 @@ public:
|
||||||
void setEnabled(bool enable);
|
void setEnabled(bool enable);
|
||||||
bool getEnabled() const;
|
bool getEnabled() const;
|
||||||
|
|
||||||
private:
|
void centerUI();
|
||||||
void updateMode();
|
|
||||||
|
|
||||||
enum Mode {
|
private:
|
||||||
FLAT,
|
bool headOutsideOverlay() const;
|
||||||
SITTING,
|
bool updateAvatarHasDriveInput();
|
||||||
STANDING
|
bool updateAvatarIsAtRest();
|
||||||
|
bool userWishesToHide() const;
|
||||||
|
bool userWishesToShow() const;
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Enabled = 0,
|
||||||
|
DisabledByDrive,
|
||||||
|
DisabledByHead,
|
||||||
|
DisabledByToggle,
|
||||||
|
NumStates
|
||||||
};
|
};
|
||||||
|
|
||||||
Mode _mode { FLAT };
|
void setState(State state);
|
||||||
|
State getState() const;
|
||||||
|
|
||||||
|
State _state { DisabledByDrive };
|
||||||
|
|
||||||
|
bool _prevOverlayMenuChecked { true };
|
||||||
bool _enabled { false };
|
bool _enabled { false };
|
||||||
bool _driving { false };
|
bool _hmdMode { false };
|
||||||
quint64 _timeInPotentialMode { 0 };
|
|
||||||
bool _wantsOverlays { true };
|
// used by updateAvatarHasDriveInput
|
||||||
|
quint64 _desiredDrivingTimer { 0 };
|
||||||
|
bool _desiredDriving { false };
|
||||||
|
bool _currentDriving { false };
|
||||||
|
|
||||||
|
// used by updateAvatarIsAtRest
|
||||||
|
quint64 _desiredAtRestTimer { 0 };
|
||||||
|
bool _desiredAtRest { true };
|
||||||
|
bool _currentAtRest { true };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3&
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 CompositorHelper::getUiTransform() const {
|
glm::mat4 CompositorHelper::getUiTransform() const {
|
||||||
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose());
|
glm::mat4 modelMat;
|
||||||
|
_modelTransform.getMatrix(modelMat);
|
||||||
|
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Finds the collision point of a world space ray
|
//Finds the collision point of a world space ray
|
||||||
|
|
|
@ -253,12 +253,13 @@ void HmdDisplayPlugin::compositeScene() {
|
||||||
void HmdDisplayPlugin::compositeOverlay() {
|
void HmdDisplayPlugin::compositeOverlay() {
|
||||||
using namespace oglplus;
|
using namespace oglplus;
|
||||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||||
|
glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix();
|
||||||
|
|
||||||
useProgram(_program);
|
useProgram(_program);
|
||||||
_sphereSection->Use();
|
_sphereSection->Use();
|
||||||
for_each_eye([&](Eye eye) {
|
for_each_eye([&](Eye eye) {
|
||||||
eyeViewport(eye);
|
eyeViewport(eye);
|
||||||
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye));
|
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat;
|
||||||
auto mvp = _eyeProjections[eye] * modelView;
|
auto mvp = _eyeProjections[eye] * modelView;
|
||||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
|
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
|
||||||
_sphereSection->Draw();
|
_sphereSection->Draw();
|
||||||
|
|
|
@ -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,13 +144,22 @@ 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) {
|
||||||
trigger = Internal;
|
trigger = Internal;
|
||||||
|
@ -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()) {
|
||||||
|
// 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);
|
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;
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ 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; }
|
||||||
|
|
||||||
|
@ -76,7 +76,6 @@ public:
|
||||||
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; }
|
||||||
void clearPendingPath() { _pendingPath.clear(); }
|
void clearPendingPath() { _pendingPath.clear(); }
|
||||||
|
@ -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,11 +104,11 @@ 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 getThisNodeCanRez() const { return _thisNodeCanRez; }
|
bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||||
void setThisNodeCanRez(bool canRez);
|
bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; }
|
||||||
|
|
||||||
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
|
||||||
|
@ -543,13 +542,10 @@ 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()) {
|
||||||
|
@ -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) {
|
||||||
|
|
|
@ -213,11 +213,13 @@ 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)) {
|
}
|
||||||
|
if (!variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
|
||||||
|
variantMap[firstKey] = QVariantMap();
|
||||||
|
}
|
||||||
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
|
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
|
||||||
shouldCreateIfMissing);
|
shouldCreateIfMissing);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -894,22 +894,14 @@ function MyController(hand) {
|
||||||
var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined);
|
var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined);
|
||||||
var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES);
|
var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES);
|
||||||
if (grabData) {
|
if (grabData) {
|
||||||
|
|
||||||
var hotspotPos = grabProps.position;
|
|
||||||
|
|
||||||
// does this entity have an attach point?
|
// does this entity have an attach point?
|
||||||
var wearableData = getEntityCustomData("wearable", entities[i], undefined);
|
var wearableData = getEntityCustomData("wearable", entities[i], undefined);
|
||||||
if (wearableData) {
|
if (wearableData && wearableData.joints) {
|
||||||
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand";
|
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand";
|
||||||
if (wearableData[handJointName]) {
|
if (wearableData.joints[handJointName]) {
|
||||||
// draw the hotspot around the attach point.
|
// draw the hotspot
|
||||||
hotspotPos = wearableData[handJointName][0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw a hotspot!
|
|
||||||
this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", {
|
this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", {
|
||||||
position: hotspotPos,
|
position: grabProps.position,
|
||||||
size: 0.2,
|
size: 0.2,
|
||||||
color: { red: 90, green: 255, blue: 90 },
|
color: { red: 90, green: 255, blue: 90 },
|
||||||
alpha: 0.7,
|
alpha: 0.7,
|
||||||
|
@ -920,6 +912,8 @@ function MyController(hand) {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.searchExit = function() {
|
this.searchExit = function() {
|
||||||
|
|
|
@ -1222,7 +1222,7 @@ function handeMenuEvent(menuItem) {
|
||||||
Window.alert("No entities have been selected.");
|
Window.alert("No entities have been selected.");
|
||||||
} else {
|
} else {
|
||||||
var filename = "entities__" + Window.location.hostname + ".svo.json";
|
var filename = "entities__" + Window.location.hostname + ".svo.json";
|
||||||
filename = Window.save("Select where to save", filename, "*.json")
|
filename = Window.save("Select Where to Save", filename, "*.json")
|
||||||
if (filename) {
|
if (filename) {
|
||||||
var success = Clipboard.exportEntities(filename, selectionManager.selections);
|
var success = Clipboard.exportEntities(filename, selectionManager.selections);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
@ -1234,7 +1234,7 @@ function handeMenuEvent(menuItem) {
|
||||||
|
|
||||||
var importURL = null;
|
var importURL = null;
|
||||||
if (menuItem == "Import Entities") {
|
if (menuItem == "Import Entities") {
|
||||||
var fullPath = Window.browse("Select models to import", "", "*.json");
|
var fullPath = Window.browse("Select Model to Import", "", "*.json");
|
||||||
if (fullPath) {
|
if (fullPath) {
|
||||||
importURL = "file:///" + fullPath;
|
importURL = "file:///" + fullPath;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue