mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 04:57:58 +02:00
Merge pull request #8009 from sethalves/permissions-grid
Permissions grid
This commit is contained in:
commit
27c6df660c
28 changed files with 1200 additions and 413 deletions
|
@ -286,8 +286,8 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<ReceivedMes
|
||||||
|
|
||||||
if (!senderID.isNull()) {
|
if (!senderID.isNull()) {
|
||||||
// We don't have this node yet - we should add it
|
// We don't have this node yet - we should add it
|
||||||
matchingNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode
|
matchingNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode(senderID, NodeType::Unassigned,
|
||||||
(senderID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false, false);
|
senderSockAddr, senderSockAddr);
|
||||||
|
|
||||||
auto childData = std::unique_ptr<AssignmentClientChildData>
|
auto childData = std::unique_ptr<AssignmentClientChildData>
|
||||||
{ new AssignmentClientChildData(Assignment::Type::AllTypes) };
|
{ new AssignmentClientChildData(Assignment::Type::AllTypes) };
|
||||||
|
|
|
@ -235,7 +235,7 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||||
if (senderNode->getCanRez()) {
|
if (senderNode->getCanWriteToAssetServer()) {
|
||||||
QString assetPath = message.readString();
|
QString assetPath = message.readString();
|
||||||
|
|
||||||
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
|
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
|
||||||
|
@ -251,7 +251,7 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||||
if (senderNode->getCanRez()) {
|
if (senderNode->getCanWriteToAssetServer()) {
|
||||||
int numberOfDeletedMappings { 0 };
|
int numberOfDeletedMappings { 0 };
|
||||||
message.readPrimitive(&numberOfDeletedMappings);
|
message.readPrimitive(&numberOfDeletedMappings);
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||||
if (senderNode->getCanRez()) {
|
if (senderNode->getCanWriteToAssetServer()) {
|
||||||
QString oldPath = message.readString();
|
QString oldPath = message.readString();
|
||||||
QString newPath = message.readString();
|
QString newPath = message.readString();
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, Shared
|
||||||
|
|
||||||
void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||||
|
|
||||||
if (senderNode->getCanRez()) {
|
if (senderNode->getCanWriteToAssetServer()) {
|
||||||
qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
|
qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
|
||||||
|
|
||||||
auto task = new UploadAssetTask(message, senderNode, _filesDirectory);
|
auto task = new UploadAssetTask(message, senderNode, _filesDirectory);
|
||||||
|
|
|
@ -268,6 +268,14 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
||||||
qDebug("wantTerseEditLogging=%s", debug::valueOf(wantTerseEditLogging));
|
qDebug("wantTerseEditLogging=%s", debug::valueOf(wantTerseEditLogging));
|
||||||
|
|
||||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||||
|
|
||||||
|
int maxTmpEntityLifetime;
|
||||||
|
if (readOptionInt("maxTmpLifetime", settingsSectionObject, maxTmpEntityLifetime)) {
|
||||||
|
tree->setEntityMaxTmpLifetime(maxTmpEntityLifetime);
|
||||||
|
} else {
|
||||||
|
tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME);
|
||||||
|
}
|
||||||
|
|
||||||
tree->setWantEditLogging(wantEditLogging);
|
tree->setWantEditLogging(wantEditLogging);
|
||||||
tree->setWantTerseEditLogging(wantTerseEditLogging);
|
tree->setWantTerseEditLogging(wantTerseEditLogging);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": 1.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",
|
||||||
|
"can_add_new_rows": false,
|
||||||
|
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"label": "User / Group",
|
||||||
|
"span": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||||
|
"span": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "username",
|
"name": "permissions_id",
|
||||||
"label": "Username",
|
"label": ""
|
||||||
"can_set": true
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_connect",
|
||||||
|
"label": "Connect",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_adjust_locks",
|
||||||
|
"label": "Lock / Unlock",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_rez",
|
||||||
|
"label": "Rez",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_rez_tmp",
|
||||||
|
"label": "Rez Temporary",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_write_to_asset_server",
|
||||||
|
"label": "Write Assets",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_connect_past_max_capacity",
|
||||||
|
"label": "Ignore Max Capacity",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
|
||||||
|
"non-deletable-row-key": "permissions_id",
|
||||||
|
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "editors_are_rezzers",
|
"name": "permissions",
|
||||||
"type": "checkbox",
|
"type": "table",
|
||||||
"label": "Only Editors Can Create Entities",
|
"caption": "Permissions for Specific Users",
|
||||||
"help": "Only users listed in \"Allowed Editors\" can create new entites.",
|
"can_add_new_rows": true,
|
||||||
"default": false
|
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"label": "User / Group",
|
||||||
|
"span": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||||
|
"span": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "permissions_id",
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_connect",
|
||||||
|
"label": "Connect",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_adjust_locks",
|
||||||
|
"label": "Lock / Unlock",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_rez",
|
||||||
|
"label": "Rez",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_rez_tmp",
|
||||||
|
"label": "Rez Temporary",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_write_to_asset_server",
|
||||||
|
"label": "Write Assets",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id_can_connect_past_max_capacity",
|
||||||
|
"label": "Ignore Max Capacity",
|
||||||
|
"type": "checkbox",
|
||||||
|
"editable": true,
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -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,17 +1058,23 @@ function makeTableInputs(setting) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_.each(setting.columns, function(col) {
|
_.each(setting.columns, function(col) {
|
||||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
if (col.type === "checkbox") {
|
||||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||||
</td>"
|
+ "name='" + col.name + "'" + (col.default ? " checked" : "") + "/></td>";
|
||||||
|
} else {
|
||||||
|
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||||
|
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||||
|
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||||
|
</td>"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (setting.can_order) {
|
if (setting.can_order) {
|
||||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
||||||
}
|
}
|
||||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></a></td>"
|
"'><a href='javascript:void(0);' class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></a></td>"
|
||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
|
|
||||||
return html
|
return html
|
||||||
|
@ -1127,11 +1179,11 @@ function addTableRow(add_glyphicon) {
|
||||||
} else {
|
} else {
|
||||||
$(element).html(1)
|
$(element).html(1)
|
||||||
}
|
}
|
||||||
} else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) {
|
} else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) {
|
||||||
$(element).html("<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><a href='javascript:void(0);'"
|
$(element).html("<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><a href='javascript:void(0);'"
|
||||||
+ " class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a><a href='javascript:void(0);' class='"
|
+ " class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a><a href='javascript:void(0);' class='"
|
||||||
+ Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>")
|
+ Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>")
|
||||||
} else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
} else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
||||||
// Change buttons
|
// Change buttons
|
||||||
var anchor = $(element).children("a")
|
var anchor = $(element).children("a")
|
||||||
anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES)
|
anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES)
|
||||||
|
@ -1142,8 +1194,20 @@ function addTableRow(add_glyphicon) {
|
||||||
input.remove()
|
input.remove()
|
||||||
} else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
|
} else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
|
||||||
// Hide inputs
|
// Hide inputs
|
||||||
var input = $(element).children("input")
|
var input = $(element).find("input")
|
||||||
input.attr("type", "hidden")
|
var isCheckbox = false;
|
||||||
|
if (input.hasClass("table-checkbox")) {
|
||||||
|
input = $(input).parent();
|
||||||
|
isCheckbox = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var val = input.val();
|
||||||
|
if (isCheckbox) {
|
||||||
|
val = $(input).find("input").is(':checked');
|
||||||
|
// don't hide the checkbox
|
||||||
|
} else {
|
||||||
|
input.attr("type", "hidden")
|
||||||
|
}
|
||||||
|
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length
|
var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length
|
||||||
|
@ -1152,14 +1216,22 @@ function addTableRow(add_glyphicon) {
|
||||||
// are there multiple columns or just one?
|
// are there multiple columns or just one?
|
||||||
// with multiple we have an array of Objects, with one we have an array of whatever the value type is
|
// with multiple we have an array of Objects, with one we have an array of whatever the value type is
|
||||||
var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length
|
var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length
|
||||||
input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
|
||||||
|
if (isCheckbox) {
|
||||||
|
$(input).find("input").attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
||||||
|
} else {
|
||||||
|
input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
input.attr("name", full_name + "." + $(element).attr("name"))
|
input.attr("name", full_name + "." + $(element).attr("name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
input.attr("data-changed", "true")
|
if (isCheckbox) {
|
||||||
|
$(input).find("input").attr("data-changed", "true");
|
||||||
$(element).append(input.val())
|
} else {
|
||||||
|
input.attr("data-changed", "true");
|
||||||
|
$(element).append(val);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Unknown table element")
|
console.log("Unknown table element")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ using SharedAssignmentPointer = QSharedPointer<Assignment>;
|
||||||
DomainGatekeeper::DomainGatekeeper(DomainServer* server) :
|
DomainGatekeeper::DomainGatekeeper(DomainServer* server) :
|
||||||
_server(server)
|
_server(server)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
|
void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
|
||||||
|
@ -38,7 +38,7 @@ void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid
|
||||||
|
|
||||||
QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID) {
|
QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID) {
|
||||||
auto it = _pendingAssignedNodes.find(tempUUID);
|
auto it = _pendingAssignedNodes.find(tempUUID);
|
||||||
|
|
||||||
if (it != _pendingAssignedNodes.end()) {
|
if (it != _pendingAssignedNodes.end()) {
|
||||||
return it->second.getAssignmentUUID();
|
return it->second.getAssignmentUUID();
|
||||||
} else {
|
} else {
|
||||||
|
@ -65,53 +65,53 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
||||||
sendProtocolMismatchConnectionDenial(message->getSenderSockAddr());
|
sendProtocolMismatchConnectionDenial(message->getSenderSockAddr());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) {
|
if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) {
|
||||||
qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection.";
|
qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const NodeSet VALID_NODE_TYPES {
|
static const NodeSet VALID_NODE_TYPES {
|
||||||
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer
|
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) {
|
if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) {
|
||||||
qDebug() << "Received an invalid node type with connect request. Will not allow connection from"
|
qDebug() << "Received an invalid node type with connect request. Will not allow connection from"
|
||||||
<< nodeConnection.senderSockAddr << ": " << nodeConnection.nodeType;
|
<< nodeConnection.senderSockAddr << ": " << nodeConnection.nodeType;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if this connect request matches an assignment in the queue
|
// check if this connect request matches an assignment in the queue
|
||||||
auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
||||||
|
|
||||||
SharedNodePointer node;
|
SharedNodePointer node;
|
||||||
|
|
||||||
if (pendingAssignment != _pendingAssignedNodes.end()) {
|
if (pendingAssignment != _pendingAssignedNodes.end()) {
|
||||||
node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second);
|
node = processAssignmentConnectRequest(nodeConnection, pendingAssignment->second);
|
||||||
} else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) {
|
} else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) {
|
||||||
QString username;
|
QString username;
|
||||||
QByteArray usernameSignature;
|
QByteArray usernameSignature;
|
||||||
|
|
||||||
if (message->getBytesLeftToRead() > 0) {
|
if (message->getBytesLeftToRead() > 0) {
|
||||||
// read username from packet
|
// read username from packet
|
||||||
packetStream >> username;
|
packetStream >> username;
|
||||||
|
|
||||||
if (message->getBytesLeftToRead() > 0) {
|
if (message->getBytesLeftToRead() > 0) {
|
||||||
// read user signature from packet
|
// read user signature from packet
|
||||||
packetStream >> usernameSignature;
|
packetStream >> usernameSignature;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node = processAgentConnectRequest(nodeConnection, username, usernameSignature);
|
node = processAgentConnectRequest(nodeConnection, username, usernameSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
// set the sending sock addr and node interest set on this node
|
// set the sending sock addr and node interest set on this node
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||||
nodeData->setSendingSockAddr(message->getSenderSockAddr());
|
nodeData->setSendingSockAddr(message->getSenderSockAddr());
|
||||||
nodeData->setNodeInterestSet(nodeConnection.interestList.toSet());
|
nodeData->setNodeInterestSet(nodeConnection.interestList.toSet());
|
||||||
nodeData->setPlaceName(nodeConnection.placeName);
|
nodeData->setPlaceName(nodeConnection.placeName);
|
||||||
|
|
||||||
// signal that we just connected a node so the DomainServer can get it a list
|
// signal that we just connected a node so the DomainServer can get it a list
|
||||||
// and broadcast its presence right away
|
// and broadcast its presence right away
|
||||||
emit connectedNode(node);
|
emit connectedNode(node);
|
||||||
|
@ -120,18 +120,74 @@ 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->eachNodeBreakable([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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (auto node, nodesToKill) {
|
||||||
|
emit killNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
|
SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||||
const PendingAssignedNodeData& pendingAssignment) {
|
const PendingAssignedNodeData& pendingAssignment) {
|
||||||
|
|
||||||
// make sure this matches an assignment the DS told us we sent out
|
// make sure this matches an assignment the DS told us we sent out
|
||||||
auto it = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
auto it = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
||||||
|
|
||||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||||
|
|
||||||
if (it != _pendingAssignedNodes.end()) {
|
if (it != _pendingAssignedNodes.end()) {
|
||||||
// find the matching queued static assignment in DS queue
|
// find the matching queued static assignment in DS queue
|
||||||
matchingQueuedAssignment = _server->dequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType);
|
matchingQueuedAssignment = _server->dequeueMatchingAssignment(it->second.getAssignmentUUID(), nodeConnection.nodeType);
|
||||||
|
|
||||||
if (matchingQueuedAssignment) {
|
if (matchingQueuedAssignment) {
|
||||||
qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID)
|
qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID)
|
||||||
<< "matches unfulfilled assignment"
|
<< "matches unfulfilled assignment"
|
||||||
|
@ -146,124 +202,99 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
||||||
qDebug() << "No assignment was deployed with UUID" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID);
|
qDebug() << "No assignment was deployed with UUID" << uuidStringWithoutCurlyBraces(nodeConnection.connectUUID);
|
||||||
return SharedNodePointer();
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the new node
|
// add the new node
|
||||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||||
|
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||||
|
|
||||||
// set assignment related data on the linked data for this node
|
// set assignment related data on the linked data for this node
|
||||||
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
||||||
nodeData->setWalletUUID(it->second.getWalletUUID());
|
nodeData->setWalletUUID(it->second.getWalletUUID());
|
||||||
nodeData->setNodeVersion(it->second.getNodeVersion());
|
nodeData->setNodeVersion(it->second.getNodeVersion());
|
||||||
nodeData->setWasAssigned(true);
|
nodeData->setWasAssigned(true);
|
||||||
|
|
||||||
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
||||||
_pendingAssignedNodes.erase(it);
|
_pendingAssignedNodes.erase(it);
|
||||||
|
|
||||||
// always allow assignment clients to create and destroy entities
|
// always allow assignment clients to create and destroy entities
|
||||||
newNode->setIsAllowedEditor(true);
|
NodePermissions userPerms;
|
||||||
newNode->setCanRez(true);
|
userPerms.isAssignment = true;
|
||||||
|
userPerms.canAdjustLocks = true;
|
||||||
|
userPerms.canRezPermanentEntities = true;
|
||||||
|
userPerms.canRezTemporaryEntities = true;
|
||||||
|
newNode->setPermissions(userPerms);
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
||||||
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
|
|
||||||
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
|
|
||||||
|
|
||||||
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||||
const QString& username,
|
const QString& username,
|
||||||
const QByteArray& usernameSignature) {
|
const QByteArray& usernameSignature) {
|
||||||
|
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
bool isRestrictingAccess =
|
// start with empty permissions
|
||||||
_server->_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
NodePermissions userPerms(username);
|
||||||
|
userPerms.setAll(false);
|
||||||
// check if this user is on our local machine - if this is true they are always allowed to connect
|
|
||||||
|
// check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
|
||||||
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
||||||
bool isLocalUser =
|
bool isLocalUser =
|
||||||
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
||||||
|
if (isLocalUser) {
|
||||||
// if we're using restricted access and this user is not local make sure we got a user signature
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||||
if (isRestrictingAccess && !isLocalUser) {
|
qDebug() << "user-permissions: is local user, so:" << userPerms;
|
||||||
if (!username.isEmpty()) {
|
|
||||||
if (usernameSignature.isEmpty()) {
|
|
||||||
// if user didn't include usernameSignature in connect request, send a connectionToken packet
|
|
||||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
|
||||||
|
|
||||||
// ask for their public key right now to make sure we have it
|
|
||||||
requestUserPublicKey(username);
|
|
||||||
|
|
||||||
return SharedNodePointer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool verifiedUsername = false;
|
if (!username.isEmpty() && usernameSignature.isEmpty()) {
|
||||||
|
// user is attempting to prove their identity to us, but we don't have enough information
|
||||||
// if we do not have a local user we need to subject them to our verification and capacity checks
|
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||||
if (!isLocalUser) {
|
// ask for their public key right now to make sure we have it
|
||||||
|
requestUserPublicKey(username);
|
||||||
// check if we need to look at the username signature
|
if (!userPerms.canConnectToDomain) {
|
||||||
if (isRestrictingAccess) {
|
|
||||||
if (isVerifiedAllowedUser(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
|
||||||
// we verified the user via their username and signature - set the verifiedUsername
|
|
||||||
// so we don't re-decrypt their sig if we're trying to exempt them from max capacity check (due to
|
|
||||||
// being in the allowed editors list)
|
|
||||||
verifiedUsername = true;
|
|
||||||
} else {
|
|
||||||
// failed to verify user - return a null shared ptr
|
|
||||||
return SharedNodePointer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isWithinMaxCapacity(username, usernameSignature, verifiedUsername, nodeConnection.senderSockAddr)) {
|
|
||||||
// we can't allow this user to connect because we are at max capacity (and they either aren't an allowed editor
|
|
||||||
// or couldn't be verified as one)
|
|
||||||
return SharedNodePointer();
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this user is in the editors list (or if the editors list is empty) set the user's node's isAllowedEditor to true
|
if (username.isEmpty()) {
|
||||||
const QVariant* allowedEditorsVariant =
|
// they didn't tell us who they are
|
||||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
qDebug() << "user-permissions: no username, so:" << userPerms;
|
||||||
|
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||||
// if the allowed editors list is empty then everyone can adjust locks
|
// they are sent us a username and the signature verifies it
|
||||||
bool isAllowedEditor = allowedEditors.empty();
|
userPerms.setUserName(username);
|
||||||
|
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||||
if (allowedEditors.contains(username, Qt::CaseInsensitive)) {
|
// we have specific permissions for this user.
|
||||||
// we have a non-empty allowed editors list - check if this user is verified to be in it
|
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||||
if (!verifiedUsername) {
|
qDebug() << "user-permissions: specific user matches, so:" << userPerms;
|
||||||
if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) {
|
|
||||||
// failed to verify a user that is in the allowed editors list
|
|
||||||
|
|
||||||
// TODO: fix public key refresh in interface/metaverse and force this check
|
|
||||||
qDebug() << "Could not verify user" << username << "as allowed editor. In the interim this user"
|
|
||||||
<< "will be given edit rights to avoid a thrasing of public key requests and connect requests.";
|
|
||||||
}
|
|
||||||
|
|
||||||
isAllowedEditor = true;
|
|
||||||
} else {
|
} else {
|
||||||
// already verified this user and they are in the allowed editors list
|
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||||
isAllowedEditor = true;
|
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||||
|
qDebug() << "user-permissions: user is logged in, so:" << userPerms;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// they sent us a username, but it didn't check out
|
||||||
|
requestUserPublicKey(username);
|
||||||
|
if (!userPerms.canConnectToDomain) {
|
||||||
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if only editors should be able to rez entities
|
qDebug() << "user-permissions: final:" << userPerms;
|
||||||
const QVariant* editorsAreRezzersVariant =
|
|
||||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH);
|
if (!userPerms.canConnectToDomain) {
|
||||||
|
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
|
||||||
bool onlyEditorsAreRezzers = false;
|
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||||
if (editorsAreRezzersVariant) {
|
return SharedNodePointer();
|
||||||
onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool canRez = true;
|
if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) {
|
||||||
if (onlyEditorsAreRezzers) {
|
// we can't allow this user to connect because we are at max capacity
|
||||||
canRez = isAllowedEditor;
|
sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
|
||||||
|
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||||
|
return SharedNodePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid hintNodeID;
|
QUuid hintNodeID;
|
||||||
|
@ -282,24 +313,23 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
|
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
|
||||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
|
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
|
||||||
|
|
||||||
// set the edit rights for this user
|
// set the edit rights for this user
|
||||||
newNode->setIsAllowedEditor(isAllowedEditor);
|
newNode->setPermissions(userPerms);
|
||||||
newNode->setCanRez(canRez);
|
|
||||||
|
|
||||||
// grab the linked data for our new node so we can set the username
|
// grab the linked data for our new node so we can set the username
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||||
|
|
||||||
// if we have a username from the connect request, set it on the DomainServerNodeData
|
// if we have a username from the connect request, set it on the DomainServerNodeData
|
||||||
nodeData->setUsername(username);
|
nodeData->setUsername(username);
|
||||||
|
|
||||||
// also add an interpolation to DomainServerNodeData so that servers can get username in stats
|
// also add an interpolation to DomainServerNodeData so that servers can get username in stats
|
||||||
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||||
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,11 +337,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
||||||
QUuid nodeID) {
|
QUuid nodeID) {
|
||||||
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
|
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
|
||||||
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
|
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
|
||||||
|
|
||||||
if (connectedPeer) {
|
if (connectedPeer) {
|
||||||
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
||||||
nodeID = nodeConnection.connectUUID;
|
nodeID = nodeConnection.connectUUID;
|
||||||
|
|
||||||
if (connectedPeer->getActiveSocket()) {
|
if (connectedPeer->getActiveSocket()) {
|
||||||
// set their discovered socket to whatever the activated socket on the network peer object was
|
// set their discovered socket to whatever the activated socket on the network peer object was
|
||||||
discoveredSocket = *connectedPeer->getActiveSocket();
|
discoveredSocket = *connectedPeer->getActiveSocket();
|
||||||
|
@ -322,15 +352,15 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
||||||
nodeID = QUuid::createUuid();
|
nodeID = QUuid::createUuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType,
|
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType,
|
||||||
nodeConnection.publicSockAddr, nodeConnection.localSockAddr);
|
nodeConnection.publicSockAddr, nodeConnection.localSockAddr);
|
||||||
|
|
||||||
// So that we can send messages to this node at will - we need to activate the correct socket on this node now
|
// So that we can send messages to this node at will - we need to activate the correct socket on this node now
|
||||||
newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket);
|
newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket);
|
||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,21 +370,21 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
|
|
||||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||||
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||||
|
|
||||||
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
||||||
|
|
||||||
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
||||||
// if we do have a public key for the user, check for a signature match
|
// if we do have a public key for the user, check for a signature match
|
||||||
|
|
||||||
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData());
|
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData());
|
||||||
|
|
||||||
// first load up the public key into an RSA struct
|
// first load up the public key into an RSA struct
|
||||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
||||||
|
|
||||||
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
||||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
||||||
QCryptographicHash::Sha256);
|
QCryptographicHash::Sha256);
|
||||||
|
|
||||||
if (rsaPublicKey) {
|
if (rsaPublicKey) {
|
||||||
int decryptResult = RSA_verify(NID_sha256,
|
int decryptResult = RSA_verify(NID_sha256,
|
||||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
||||||
|
@ -362,29 +392,29 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
||||||
usernameSignature.size(),
|
usernameSignature.size(),
|
||||||
rsaPublicKey);
|
rsaPublicKey);
|
||||||
|
|
||||||
if (decryptResult == 1) {
|
if (decryptResult == 1) {
|
||||||
qDebug() << "Username signature matches for" << username << "- allowing connection.";
|
qDebug() << "Username signature matches for" << username;
|
||||||
|
|
||||||
// free up the public key and remove connection token before we return
|
// free up the public key and remove connection token before we return
|
||||||
RSA_free(rsaPublicKey);
|
RSA_free(rsaPublicKey);
|
||||||
_connectionTokenHash.remove(username);
|
_connectionTokenHash.remove(username);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (!senderSockAddr.isNull()) {
|
if (!senderSockAddr.isNull()) {
|
||||||
qDebug() << "Error decrypting username signature for " << username << "- denying connection.";
|
qDebug() << "Error decrypting username signature for " << username << "- denying connection.";
|
||||||
sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr,
|
sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr,
|
||||||
DomainHandler::ConnectionRefusedReason::LoginError);
|
DomainHandler::ConnectionRefusedReason::LoginError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// free up the public key, we don't need it anymore
|
// free up the public key, we don't need it anymore
|
||||||
RSA_free(rsaPublicKey);
|
RSA_free(rsaPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||||
if (!senderSockAddr.isNull()) {
|
if (!senderSockAddr.isNull()) {
|
||||||
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
||||||
|
@ -399,86 +429,35 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||||
DomainHandler::ConnectionRefusedReason::LoginError);
|
DomainHandler::ConnectionRefusedReason::LoginError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUserPublicKey(username); // no joy. maybe next time?
|
requestUserPublicKey(username); // no joy. maybe next time?
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature,
|
bool DomainGatekeeper::isWithinMaxCapacity() {
|
||||||
const HifiSockAddr& senderSockAddr) {
|
|
||||||
|
|
||||||
if (username.isEmpty()) {
|
|
||||||
qDebug() << "Connect request denied - no username provided.";
|
|
||||||
|
|
||||||
sendConnectionDeniedPacket("No username provided", senderSockAddr,
|
|
||||||
DomainHandler::ConnectionRefusedReason::LoginError);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList allowedUsers =
|
|
||||||
_server->_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
|
|
||||||
|
|
||||||
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
|
|
||||||
if (!verifyUserSignature(username, usernameSignature, senderSockAddr)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qDebug() << "Connect request denied for user" << username << "- not in allowed users list.";
|
|
||||||
sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr,
|
|
||||||
DomainHandler::ConnectionRefusedReason::NotAuthorized);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature,
|
|
||||||
bool& verifiedUsername,
|
|
||||||
const HifiSockAddr& senderSockAddr) {
|
|
||||||
// find out what our maximum capacity is
|
// find out what our maximum capacity is
|
||||||
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
const QVariant* maximumUserCapacityVariant =
|
||||||
|
valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
||||||
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
||||||
|
|
||||||
if (maximumUserCapacity > 0) {
|
if (maximumUserCapacity > 0) {
|
||||||
unsigned int connectedUsers = _server->countConnectedUsers();
|
unsigned int connectedUsers = _server->countConnectedUsers();
|
||||||
|
|
||||||
if (connectedUsers >= maximumUserCapacity) {
|
if (connectedUsers >= maximumUserCapacity) {
|
||||||
// too many users, deny the new connection unless this user is an allowed editor
|
|
||||||
|
|
||||||
const QVariant* allowedEditorsVariant =
|
|
||||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
|
||||||
|
|
||||||
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
|
||||||
if (allowedEditors.contains(username)) {
|
|
||||||
if (verifiedUsername || verifyUserSignature(username, usernameSignature, senderSockAddr)) {
|
|
||||||
verifiedUsername = true;
|
|
||||||
qDebug() << "Above maximum capacity -" << connectedUsers << "/" << maximumUserCapacity <<
|
|
||||||
"but user" << username << "is in allowed editors list so will be allowed to connect.";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deny connection from this user
|
|
||||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
||||||
sendConnectionDeniedPacket("Too many connected users.", senderSockAddr,
|
|
||||||
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, allowing new connection.";
|
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, allowing new connection.";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DomainGatekeeper::preloadAllowedUserPublicKeys() {
|
void DomainGatekeeper::preloadAllowedUserPublicKeys() {
|
||||||
const QVariant* allowedUsersVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH);
|
QStringList allowedUsers = _server->_settingsManager.getAllNames();
|
||||||
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
|
||||||
|
|
||||||
if (allowedUsers.size() > 0) {
|
if (allowedUsers.size() > 0) {
|
||||||
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
|
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
|
||||||
// going to create > 100 requests
|
// going to create > 100 requests
|
||||||
|
@ -489,15 +468,20 @@ void DomainGatekeeper::preloadAllowedUserPublicKeys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
||||||
|
// don't request public keys for the standard psuedo-account-names
|
||||||
|
if (NodePermissions::standardNames.contains(username, Qt::CaseInsensitive)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// even if we have a public key for them right now, request a new one in case it has just changed
|
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||||
JSONCallbackParameters callbackParams;
|
JSONCallbackParameters callbackParams;
|
||||||
callbackParams.jsonCallbackReceiver = this;
|
callbackParams.jsonCallbackReceiver = this;
|
||||||
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||||
|
|
||||||
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||||
|
|
||||||
qDebug() << "Requesting public key for user" << username;
|
qDebug() << "Requesting public key for user" << username;
|
||||||
|
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
|
DependencyManager::get<AccountManager>()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
|
||||||
AccountManagerAuth::None,
|
AccountManagerAuth::None,
|
||||||
QNetworkAccessManager::GetOperation, callbackParams);
|
QNetworkAccessManager::GetOperation, callbackParams);
|
||||||
|
@ -505,22 +489,22 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
||||||
|
|
||||||
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
|
|
||||||
if (jsonObject["status"].toString() == "success") {
|
if (jsonObject["status"].toString() == "success") {
|
||||||
// figure out which user this is for
|
// figure out which user this is for
|
||||||
|
|
||||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||||
|
|
||||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||||
QString username = usernameRegex.cap(1);
|
QString username = usernameRegex.cap(1);
|
||||||
|
|
||||||
qDebug() << "Storing a public key for user" << username;
|
qDebug() << "Storing a public key for user" << username;
|
||||||
|
|
||||||
// pull the public key as a QByteArray from this response
|
// pull the public key as a QByteArray from this response
|
||||||
const QString JSON_DATA_KEY = "data";
|
const QString JSON_DATA_KEY = "data";
|
||||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||||
|
|
||||||
_userPublicKeys[username] =
|
_userPublicKeys[username] =
|
||||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||||
}
|
}
|
||||||
|
@ -536,16 +520,16 @@ void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr&
|
||||||
DomainHandler::ConnectionRefusedReason::ProtocolMismatch);
|
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
|
||||||
QByteArray utfString = reason.toUtf8();
|
QByteArray utfString = reason.toUtf8();
|
||||||
quint16 payloadSize = utfString.size();
|
quint16 payloadSize = utfString.size();
|
||||||
|
|
||||||
// setup the DomainConnectionDenied packet
|
// setup the DomainConnectionDenied packet
|
||||||
auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied,
|
auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied,
|
||||||
payloadSize + sizeof(payloadSize) + sizeof(uint8_t));
|
payloadSize + sizeof(payloadSize) + sizeof(uint8_t));
|
||||||
|
|
||||||
// pack in the reason the connection was denied (the client displays this)
|
// pack in the reason the connection was denied (the client displays this)
|
||||||
if (payloadSize > 0) {
|
if (payloadSize > 0) {
|
||||||
uint8_t reasonCodeWire = (uint8_t)reasonCode;
|
uint8_t reasonCodeWire = (uint8_t)reasonCode;
|
||||||
|
@ -553,7 +537,7 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H
|
||||||
connectionDeniedPacket->writePrimitive(payloadSize);
|
connectionDeniedPacket->writePrimitive(payloadSize);
|
||||||
connectionDeniedPacket->write(utfString);
|
connectionDeniedPacket->write(utfString);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send the packet off
|
// send the packet off
|
||||||
DependencyManager::get<LimitedNodeList>()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr);
|
DependencyManager::get<LimitedNodeList>()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr);
|
||||||
}
|
}
|
||||||
|
@ -561,20 +545,20 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H
|
||||||
void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr) {
|
void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr) {
|
||||||
// get the existing connection token or create a new one
|
// get the existing connection token or create a new one
|
||||||
QUuid& connectionToken = _connectionTokenHash[username.toLower()];
|
QUuid& connectionToken = _connectionTokenHash[username.toLower()];
|
||||||
|
|
||||||
if (connectionToken.isNull()) {
|
if (connectionToken.isNull()) {
|
||||||
connectionToken = QUuid::createUuid();
|
connectionToken = QUuid::createUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup a static connection token packet
|
// setup a static connection token packet
|
||||||
static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID);
|
static auto connectionTokenPacket = NLPacket::create(PacketType::DomainServerConnectionToken, NUM_BYTES_RFC4122_UUID);
|
||||||
|
|
||||||
// reset the packet before each time we send
|
// reset the packet before each time we send
|
||||||
connectionTokenPacket->reset();
|
connectionTokenPacket->reset();
|
||||||
|
|
||||||
// write the connection token
|
// write the connection token
|
||||||
connectionTokenPacket->write(connectionToken.toRfc4122());
|
connectionTokenPacket->write(connectionToken.toRfc4122());
|
||||||
|
|
||||||
// send off the packet unreliably
|
// send off the packet unreliably
|
||||||
DependencyManager::get<LimitedNodeList>()->sendUnreliablePacket(*connectionTokenPacket, senderSockAddr);
|
DependencyManager::get<LimitedNodeList>()->sendUnreliablePacket(*connectionTokenPacket, senderSockAddr);
|
||||||
}
|
}
|
||||||
|
@ -582,33 +566,33 @@ void DomainGatekeeper::sendConnectionTokenPacket(const QString& username, const
|
||||||
const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS;
|
const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS;
|
||||||
|
|
||||||
void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) {
|
void DomainGatekeeper::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) {
|
||||||
|
|
||||||
if (peer->getConnectionAttempts() >= NUM_PEER_PINGS_BEFORE_DELETE) {
|
if (peer->getConnectionAttempts() >= NUM_PEER_PINGS_BEFORE_DELETE) {
|
||||||
// we've reached the maximum number of ping attempts
|
// we've reached the maximum number of ping attempts
|
||||||
qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID();
|
qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID();
|
||||||
qDebug() << "Removing from list of connecting peers.";
|
qDebug() << "Removing from list of connecting peers.";
|
||||||
|
|
||||||
_icePeers.remove(peer->getUUID());
|
_icePeers.remove(peer->getUUID());
|
||||||
} else {
|
} else {
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
// send the ping packet to the local and public sockets for this node
|
// send the ping packet to the local and public sockets for this node
|
||||||
auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID());
|
auto localPingPacket = limitedNodeList->constructICEPingPacket(PingType::Local, limitedNodeList->getSessionUUID());
|
||||||
limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket());
|
limitedNodeList->sendPacket(std::move(localPingPacket), peer->getLocalSocket());
|
||||||
|
|
||||||
auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID());
|
auto publicPingPacket = limitedNodeList->constructICEPingPacket(PingType::Public, limitedNodeList->getSessionUUID());
|
||||||
limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket());
|
limitedNodeList->sendPacket(std::move(publicPingPacket), peer->getPublicSocket());
|
||||||
|
|
||||||
peer->incrementConnectionAttempts();
|
peer->incrementConnectionAttempts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::handlePeerPingTimeout() {
|
void DomainGatekeeper::handlePeerPingTimeout() {
|
||||||
NetworkPeer* senderPeer = qobject_cast<NetworkPeer*>(sender());
|
NetworkPeer* senderPeer = qobject_cast<NetworkPeer*>(sender());
|
||||||
|
|
||||||
if (senderPeer) {
|
if (senderPeer) {
|
||||||
SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID());
|
SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID());
|
||||||
|
|
||||||
if (sharedPeer && !sharedPeer->getActiveSocket()) {
|
if (sharedPeer && !sharedPeer->getActiveSocket()) {
|
||||||
pingPunchForConnectingPeer(sharedPeer);
|
pingPunchForConnectingPeer(sharedPeer);
|
||||||
}
|
}
|
||||||
|
@ -619,24 +603,24 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMe
|
||||||
// loop through the packet and pull out network peers
|
// loop through the packet and pull out network peers
|
||||||
// any peer we don't have we add to the hash, otherwise we update
|
// any peer we don't have we add to the hash, otherwise we update
|
||||||
QDataStream iceResponseStream(message->getMessage());
|
QDataStream iceResponseStream(message->getMessage());
|
||||||
|
|
||||||
NetworkPeer* receivedPeer = new NetworkPeer;
|
NetworkPeer* receivedPeer = new NetworkPeer;
|
||||||
iceResponseStream >> *receivedPeer;
|
iceResponseStream >> *receivedPeer;
|
||||||
|
|
||||||
if (!_icePeers.contains(receivedPeer->getUUID())) {
|
if (!_icePeers.contains(receivedPeer->getUUID())) {
|
||||||
qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
|
qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
|
||||||
SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer);
|
SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer);
|
||||||
_icePeers[receivedPeer->getUUID()] = newPeer;
|
_icePeers[receivedPeer->getUUID()] = newPeer;
|
||||||
|
|
||||||
// make sure we know when we should ping this peer
|
// make sure we know when we should ping this peer
|
||||||
connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
|
connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
|
||||||
|
|
||||||
// immediately ping the new peer, and start a timer to continue pinging it until we connect to it
|
// immediately ping the new peer, and start a timer to continue pinging it until we connect to it
|
||||||
newPeer->startPingTimer();
|
newPeer->startPingTimer();
|
||||||
|
|
||||||
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
|
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
|
||||||
<< newPeer->getUUID();
|
<< newPeer->getUUID();
|
||||||
|
|
||||||
pingPunchForConnectingPeer(newPeer);
|
pingPunchForConnectingPeer(newPeer);
|
||||||
} else {
|
} else {
|
||||||
delete receivedPeer;
|
delete receivedPeer;
|
||||||
|
@ -646,18 +630,18 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMe
|
||||||
void DomainGatekeeper::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainGatekeeper::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
|
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
|
||||||
|
|
||||||
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
|
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
QDataStream packetStream(message->getMessage());
|
QDataStream packetStream(message->getMessage());
|
||||||
|
|
||||||
QUuid nodeUUID;
|
QUuid nodeUUID;
|
||||||
packetStream >> nodeUUID;
|
packetStream >> nodeUUID;
|
||||||
|
|
||||||
SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID);
|
SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID);
|
||||||
|
|
||||||
if (sendingPeer) {
|
if (sendingPeer) {
|
||||||
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
|
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
|
||||||
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
|
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
|
||||||
|
|
|
@ -53,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:
|
||||||
|
@ -68,11 +72,7 @@ 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);
|
||||||
|
|
|
@ -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
|
||||||
|
@ -795,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);
|
||||||
|
|
||||||
|
@ -1088,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";
|
||||||
|
@ -2102,35 +2109,42 @@ void DomainServer::processPathQueryPacket(QSharedPointer<ReceivedMessage> messag
|
||||||
void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
// This packet has been matched to a source node and they're asking not to be in the domain anymore
|
// This packet has been matched to a source node and they're asking not to be in the domain anymore
|
||||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
|
||||||
const QUuid& nodeUUID = message->getSourceID();
|
const QUuid& nodeUUID = message->getSourceID();
|
||||||
|
|
||||||
qDebug() << "Received a disconnect request from node with UUID" << nodeUUID;
|
qDebug() << "Received a disconnect request from node with UUID" << nodeUUID;
|
||||||
|
|
||||||
// we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode
|
// we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode
|
||||||
// packet to nodes that don't care about this type
|
// packet to nodes that don't care about this type
|
||||||
auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID);
|
auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID);
|
||||||
|
|
||||||
if (nodeToKill) {
|
if (nodeToKill) {
|
||||||
auto nodeType = nodeToKill->getType();
|
handleKillNode(nodeToKill);
|
||||||
limitedNodeList->killNodeWithUUID(nodeUUID);
|
|
||||||
|
|
||||||
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
|
|
||||||
|
|
||||||
removedNodePacket->reset();
|
|
||||||
removedNodePacket->write(nodeUUID.toRfc4122());
|
|
||||||
|
|
||||||
// broadcast out the DomainServerRemovedNode message
|
|
||||||
limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool {
|
|
||||||
// only send the removed node packet to nodes that care about the type of node this was
|
|
||||||
auto nodeLinkedData = dynamic_cast<DomainServerNodeData*>(otherNode->getLinkedData());
|
|
||||||
return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType);
|
|
||||||
}, [&limitedNodeList](const SharedNodePointer& otherNode){
|
|
||||||
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
|
||||||
|
auto nodeType = nodeToKill->getType();
|
||||||
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
const QUuid& nodeUUID = nodeToKill->getUUID();
|
||||||
|
|
||||||
|
limitedNodeList->killNodeWithUUID(nodeUUID);
|
||||||
|
|
||||||
|
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
|
||||||
|
|
||||||
|
removedNodePacket->reset();
|
||||||
|
removedNodePacket->write(nodeUUID.toRfc4122());
|
||||||
|
|
||||||
|
// broadcast out the DomainServerRemovedNode message
|
||||||
|
limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool {
|
||||||
|
// only send the removed node packet to nodes that care about the type of node this was
|
||||||
|
auto nodeLinkedData = dynamic_cast<DomainServerNodeData*>(otherNode->getLinkedData());
|
||||||
|
return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType);
|
||||||
|
}, [&limitedNodeList](const SharedNodePointer& otherNode){
|
||||||
|
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message) {
|
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
if (restartRequired) {
|
||||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||||
|
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||||
|
} else {
|
||||||
|
unpackPermissions();
|
||||||
|
emit updateNodePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||||
|
@ -282,7 +500,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
||||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
||||||
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
|
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
|
||||||
|
|
||||||
|
|
||||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,6 +675,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
||||||
// TODO: we still need to recurse here with the description in case values in the array have special types
|
// TODO: we still need to recurse here with the description in case values in the array have special types
|
||||||
settingMap[key] = newValue.toArray().toVariantList();
|
settingMap[key] = newValue.toArray().toVariantList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) {
|
QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) {
|
||||||
|
@ -471,9 +690,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
|
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
|
||||||
auto& settingsVariant = _configMap.getUserConfig();
|
auto& settingsVariant = _configMap.getUserConfig();
|
||||||
|
bool needRestart = false;
|
||||||
|
|
||||||
// Iterate on the setting groups
|
// Iterate on the setting groups
|
||||||
foreach(const QString& rootKey, postedObject.keys()) {
|
foreach(const QString& rootKey, postedObject.keys()) {
|
||||||
QJsonValue rootValue = postedObject[rootKey];
|
QJsonValue rootValue = postedObject[rootKey];
|
||||||
|
@ -521,6 +741,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
|
|
||||||
if (!matchingDescriptionObject.isEmpty()) {
|
if (!matchingDescriptionObject.isEmpty()) {
|
||||||
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
||||||
|
if (rootKey != "security") {
|
||||||
|
needRestart = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
|
qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
|
||||||
}
|
}
|
||||||
|
@ -534,6 +757,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
if (!matchingDescriptionObject.isEmpty()) {
|
if (!matchingDescriptionObject.isEmpty()) {
|
||||||
QJsonValue settingValue = rootValue.toObject()[settingKey];
|
QJsonValue settingValue = rootValue.toObject()[settingKey];
|
||||||
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
||||||
|
if (rootKey != "security") {
|
||||||
|
needRestart = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
|
qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
|
||||||
"- cannot update setting.";
|
"- cannot update setting.";
|
||||||
|
@ -549,9 +775,42 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
|
|
||||||
// re-merge the user and master configs after a settings change
|
// re-merge the user and master configs after a settings change
|
||||||
_configMap.mergeMasterAndUserConfigs();
|
_configMap.mergeMasterAndUserConfigs();
|
||||||
|
|
||||||
|
return needRestart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare two members of a permissions list
|
||||||
|
bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
|
||||||
|
if (!v1.canConvert(QMetaType::QVariantMap) ||
|
||||||
|
!v2.canConvert(QMetaType::QVariantMap)) {
|
||||||
|
return v1.toString() < v2.toString();
|
||||||
|
}
|
||||||
|
QVariantMap m1 = v1.toMap();
|
||||||
|
QVariantMap m2 = v2.toMap();
|
||||||
|
|
||||||
|
if (!m1.contains("permissions_id") ||
|
||||||
|
!m2.contains("permissions_id")) {
|
||||||
|
return v1.toString() < v2.toString();
|
||||||
|
}
|
||||||
|
return m1["permissions_id"].toString() < m2["permissions_id"].toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServerSettingsManager::sortPermissions() {
|
||||||
|
// sort the permission-names
|
||||||
|
QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH);
|
||||||
|
if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) {
|
||||||
|
QList<QVariant>* standardPermissionsList = reinterpret_cast<QVariantList*>(standardPermissions);
|
||||||
|
std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan);
|
||||||
|
}
|
||||||
|
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH);
|
||||||
|
if (permissions && permissions->canConvert(QMetaType::QVariantList)) {
|
||||||
|
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions);
|
||||||
|
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServerSettingsManager::persistToFile() {
|
void DomainServerSettingsManager::persistToFile() {
|
||||||
|
sortPermissions();
|
||||||
|
|
||||||
// make sure we have the dir the settings file is supposed to live in
|
// make sure we have the dir the settings file is supposed to live in
|
||||||
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
|
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
|
||||||
|
|
|
@ -19,14 +19,14 @@
|
||||||
#include <HTTPManager.h>
|
#include <HTTPManager.h>
|
||||||
|
|
||||||
#include <ReceivedMessage.h>
|
#include <ReceivedMessage.h>
|
||||||
|
#include "NodePermissions.h"
|
||||||
|
|
||||||
const QString SETTINGS_PATHS_KEY = "paths";
|
const QString SETTINGS_PATHS_KEY = "paths";
|
||||||
|
|
||||||
const QString SETTINGS_PATH = "/settings";
|
const QString SETTINGS_PATH = "/settings";
|
||||||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||||
|
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
|
||||||
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
|
||||||
const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access";
|
|
||||||
|
|
||||||
class DomainServerSettingsManager : public QObject {
|
class DomainServerSettingsManager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -41,16 +41,29 @@ public:
|
||||||
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
||||||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||||
|
|
||||||
|
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); }
|
||||||
|
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); }
|
||||||
|
NodePermissions getStandardPermissionsForName(const QString& name) const;
|
||||||
|
NodePermissions getPermissionsForName(const QString& name) const;
|
||||||
|
QStringList getAllNames() { return _agentPermissions.keys(); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void updateNodePermissions();
|
||||||
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QStringList _argumentList;
|
||||||
|
|
||||||
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
||||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject);
|
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject);
|
||||||
|
|
||||||
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||||
const QJsonObject& settingDescription);
|
const QJsonObject& settingDescription);
|
||||||
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
|
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
|
||||||
|
void sortPermissions();
|
||||||
void persistToFile();
|
void persistToFile();
|
||||||
|
|
||||||
double _descriptionVersion;
|
double _descriptionVersion;
|
||||||
|
@ -58,6 +71,12 @@ private:
|
||||||
HifiConfigVariantMap _configMap;
|
HifiConfigVariantMap _configMap;
|
||||||
|
|
||||||
friend class DomainServer;
|
friend class DomainServer;
|
||||||
|
|
||||||
|
void packPermissionsForMap(QString mapName, QHash<QString, NodePermissionsPointer> agentPermissions, QString keyPath);
|
||||||
|
void packPermissions();
|
||||||
|
void unpackPermissions();
|
||||||
|
QHash<QString, NodePermissionsPointer> _standardAgentPermissions; // anonymous, logged-in, localhost
|
||||||
|
QHash<QString, NodePermissionsPointer> _agentPermissions; // specific account-names
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_DomainServerSettingsManager_h
|
#endif // hifi_DomainServerSettingsManager_h
|
||||||
|
|
|
@ -4292,7 +4292,7 @@ void Application::nodeActivated(SharedNodePointer node) {
|
||||||
if (assetDialog) {
|
if (assetDialog) {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
if (nodeList->getThisNodeCanRez()) {
|
if (nodeList->getThisNodeCanWriteAssets()) {
|
||||||
// call reload on the shown asset browser dialog to get the mappings (if permissions allow)
|
// call reload on the shown asset browser dialog to get the mappings (if permissions allow)
|
||||||
QMetaObject::invokeMethod(assetDialog, "reload");
|
QMetaObject::invokeMethod(assetDialog, "reload");
|
||||||
} else {
|
} else {
|
||||||
|
@ -4788,7 +4788,7 @@ void Application::toggleRunningScriptsWidget() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::toggleAssetServerWidget(QString filePath) {
|
void Application::toggleAssetServerWidget(QString filePath) {
|
||||||
if (!DependencyManager::get<NodeList>()->getThisNodeCanRez()) {
|
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -52,7 +52,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
|
||||||
_numCollectedPackets(0),
|
_numCollectedPackets(0),
|
||||||
_numCollectedBytes(0),
|
_numCollectedBytes(0),
|
||||||
_packetStatTimer(),
|
_packetStatTimer(),
|
||||||
_thisNodeCanRez(true)
|
_permissions(NodePermissions())
|
||||||
{
|
{
|
||||||
static bool firstCall = true;
|
static bool firstCall = true;
|
||||||
if (firstCall) {
|
if (firstCall) {
|
||||||
|
@ -130,17 +130,22 @@ void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LimitedNodeList::setIsAllowedEditor(bool isAllowedEditor) {
|
void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
|
||||||
if (_isAllowedEditor != isAllowedEditor) {
|
NodePermissions originalPermissions = _permissions;
|
||||||
_isAllowedEditor = isAllowedEditor;
|
|
||||||
emit isAllowedEditorChanged(isAllowedEditor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LimitedNodeList::setThisNodeCanRez(bool canRez) {
|
_permissions = newPermissions;
|
||||||
if (_thisNodeCanRez != canRez) {
|
|
||||||
_thisNodeCanRez = canRez;
|
if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) {
|
||||||
emit canRezChanged(canRez);
|
emit isAllowedEditorChanged(_permissions.canAdjustLocks);
|
||||||
|
}
|
||||||
|
if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) {
|
||||||
|
emit canRezChanged(_permissions.canRezPermanentEntities);
|
||||||
|
}
|
||||||
|
if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) {
|
||||||
|
emit canRezTmpChanged(_permissions.canRezTemporaryEntities);
|
||||||
|
}
|
||||||
|
if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) {
|
||||||
|
emit canWriteAssetsChanged(_permissions.canWriteToAssetServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,7 +520,7 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
|
||||||
|
|
||||||
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
||||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
||||||
bool isAllowedEditor, bool canRez,
|
const NodePermissions& permissions,
|
||||||
const QUuid& connectionSecret) {
|
const QUuid& connectionSecret) {
|
||||||
NodeHash::const_iterator it = _nodeHash.find(uuid);
|
NodeHash::const_iterator it = _nodeHash.find(uuid);
|
||||||
|
|
||||||
|
@ -524,14 +529,13 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
||||||
|
|
||||||
matchingNode->setPublicSocket(publicSocket);
|
matchingNode->setPublicSocket(publicSocket);
|
||||||
matchingNode->setLocalSocket(localSocket);
|
matchingNode->setLocalSocket(localSocket);
|
||||||
matchingNode->setIsAllowedEditor(isAllowedEditor);
|
matchingNode->setPermissions(permissions);
|
||||||
matchingNode->setCanRez(canRez);
|
|
||||||
matchingNode->setConnectionSecret(connectionSecret);
|
matchingNode->setConnectionSecret(connectionSecret);
|
||||||
|
|
||||||
return matchingNode;
|
return matchingNode;
|
||||||
} else {
|
} else {
|
||||||
// we didn't have this node, so add them
|
// we didn't have this node, so add them
|
||||||
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, isAllowedEditor, canRez, connectionSecret, this);
|
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, permissions, connectionSecret, this);
|
||||||
|
|
||||||
if (nodeType == NodeType::AudioMixer) {
|
if (nodeType == NodeType::AudioMixer) {
|
||||||
LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer);
|
LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer);
|
||||||
|
|
|
@ -104,12 +104,12 @@ public:
|
||||||
const QUuid& getSessionUUID() const { return _sessionUUID; }
|
const QUuid& getSessionUUID() const { return _sessionUUID; }
|
||||||
void setSessionUUID(const QUuid& sessionUUID);
|
void setSessionUUID(const QUuid& sessionUUID);
|
||||||
|
|
||||||
bool isAllowedEditor() const { return _isAllowedEditor; }
|
void setPermissions(const NodePermissions& newPermissions);
|
||||||
void setIsAllowedEditor(bool isAllowedEditor);
|
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||||
|
bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||||
|
bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||||
|
bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; }
|
||||||
|
|
||||||
bool getThisNodeCanRez() const { return _thisNodeCanRez; }
|
|
||||||
void setThisNodeCanRez(bool canRez);
|
|
||||||
|
|
||||||
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
|
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
|
||||||
QUdpSocket& getDTLSSocket();
|
QUdpSocket& getDTLSSocket();
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ public:
|
||||||
|
|
||||||
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
||||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
||||||
bool isAllowedEditor = false, bool canRez = false,
|
const NodePermissions& permissions = DEFAULT_AGENT_PERMISSIONS,
|
||||||
const QUuid& connectionSecret = QUuid());
|
const QUuid& connectionSecret = QUuid());
|
||||||
|
|
||||||
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
||||||
|
@ -254,6 +254,8 @@ signals:
|
||||||
|
|
||||||
void isAllowedEditorChanged(bool isAllowedEditor);
|
void isAllowedEditorChanged(bool isAllowedEditor);
|
||||||
void canRezChanged(bool canRez);
|
void canRezChanged(bool canRez);
|
||||||
|
void canRezTmpChanged(bool canRezTmp);
|
||||||
|
void canWriteAssetsChanged(bool canWriteAssets);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void connectedForLocalSocketTest();
|
void connectedForLocalSocketTest();
|
||||||
|
@ -300,8 +302,7 @@ protected:
|
||||||
int _numCollectedBytes;
|
int _numCollectedBytes;
|
||||||
|
|
||||||
QElapsedTimer _packetStatTimer;
|
QElapsedTimer _packetStatTimer;
|
||||||
bool _isAllowedEditor { false };
|
NodePermissions _permissions;
|
||||||
bool _thisNodeCanRez;
|
|
||||||
|
|
||||||
QPointer<QTimer> _initialSTUNTimer;
|
QPointer<QTimer> _initialSTUNTimer;
|
||||||
|
|
||||||
|
|
|
@ -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*)
|
||||||
|
|
|
@ -526,7 +526,7 @@ void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message)
|
||||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList);
|
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList);
|
||||||
|
|
||||||
QDataStream packetStream(message->getMessage());
|
QDataStream packetStream(message->getMessage());
|
||||||
|
|
||||||
// grab the domain's ID from the beginning of the packet
|
// grab the domain's ID from the beginning of the packet
|
||||||
QUuid domainUUID;
|
QUuid domainUUID;
|
||||||
packetStream >> domainUUID;
|
packetStream >> domainUUID;
|
||||||
|
@ -542,14 +542,9 @@ 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;
|
packetStream >> _permissions;
|
||||||
setIsAllowedEditor((bool) isAllowedEditor);
|
|
||||||
|
|
||||||
quint8 thisNodeCanRez;
|
|
||||||
packetStream >> thisNodeCanRez;
|
|
||||||
setThisNodeCanRez((bool) thisNodeCanRez);
|
|
||||||
|
|
||||||
// pull each node in the packet
|
// pull each node in the packet
|
||||||
while (packetStream.device()->pos() < message->getSize()) {
|
while (packetStream.device()->pos() < message->getSize()) {
|
||||||
parseNodeFromPacketStream(packetStream);
|
parseNodeFromPacketStream(packetStream);
|
||||||
|
@ -576,10 +571,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
|
||||||
|
@ -590,8 +584,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) {
|
||||||
|
|
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
|
||||||
|
|
|
@ -213,10 +213,12 @@ QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool
|
||||||
if (shouldCreateIfMissing || variantMap.contains(firstKey)) {
|
if (shouldCreateIfMissing || variantMap.contains(firstKey)) {
|
||||||
if (dotIndex == -1) {
|
if (dotIndex == -1) {
|
||||||
return &variantMap[firstKey];
|
return &variantMap[firstKey];
|
||||||
} else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
|
|
||||||
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
|
|
||||||
shouldCreateIfMissing);
|
|
||||||
}
|
}
|
||||||
|
if (!variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
|
||||||
|
variantMap[firstKey] = QVariantMap();
|
||||||
|
}
|
||||||
|
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
|
||||||
|
shouldCreateIfMissing);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
Loading…
Reference in a new issue