Merge branch 'vive-ui' of https://github.com/highfidelity/hifi into use-system-pointer

This commit is contained in:
howard-stearns 2016-06-13 17:06:43 -07:00
commit 0366c891cc
49 changed files with 1586 additions and 662 deletions

View file

@ -3,9 +3,10 @@ Please read the [general build guide](BUILD.md) for information on dependencies
###Homebrew ###Homebrew
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all High Fidelity dependencies very simple. [Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all High Fidelity dependencies very simple.
brew install cmake openssl qt5 brew tap homebrew/versions
brew install cmake openssl qt55
We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x and above provide a mechanism to disable the wireless scanning we previously had a custom patch for. We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x provide a mechanism to disable the wireless scanning we previously had a custom patch for.
###OpenSSL and Qt ###OpenSSL and Qt

View file

@ -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) };

View file

@ -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);

View file

@ -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);
} }

View file

@ -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 &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"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 &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 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 &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 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},

View file

@ -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;
} }

View file

@ -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")
} }

View file

@ -62,10 +62,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
QByteArray myProtocolVersion = protocolVersionsSignature(); QByteArray myProtocolVersion = protocolVersionsSignature();
if (nodeConnection.protocolVersion != myProtocolVersion) { if (nodeConnection.protocolVersion != myProtocolVersion) {
QString protocolVersionError = "Protocol version mismatch - Domain version:" + QCoreApplication::applicationVersion(); sendProtocolMismatchConnectionDenial(message->getSenderSockAddr());
qDebug() << "Protocol Version mismatch - denying connection.";
sendConnectionDeniedPacket(protocolVersionError, message->getSenderSockAddr(),
DomainHandler::ConnectionRefusedReason::ProtocolMismatch);
return; return;
} }
@ -123,6 +120,60 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
} }
} }
void DomainGatekeeper::updateNodePermissions() {
// If the permissions were changed on the domain-server webpage (and nothing else was), a restart isn't required --
// we reprocess the permissions map and update the nodes here. The node list is frequently sent out to all
// the connected nodes, so these changes are propagated to other nodes.
QList<SharedNodePointer> nodesToKill;
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
QString username = node->getPermissions().getUserName();
NodePermissions userPerms(username);
if (node->getPermissions().isAssignment) {
// this node is an assignment-client
userPerms.isAssignment = true;
userPerms.canAdjustLocks = true;
userPerms.canRezPermanentEntities = true;
userPerms.canRezTemporaryEntities = true;
} else {
// this node is an agent
userPerms.setAll(false);
const QHostAddress& addr = node->getLocalSocket().getAddress();
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
addr == QHostAddress::LocalHost);
if (isLocalUser) {
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
}
if (username.isEmpty()) {
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
} else {
if (_server->_settingsManager.havePermissionsForName(username)) {
userPerms = _server->_settingsManager.getPermissionsForName(username);
} else {
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
}
}
}
node->setPermissions(userPerms);
if (!userPerms.canConnectToDomain) {
qDebug() << "node" << node->getUUID() << "no longer has permission to connect.";
// hang up on this node
nodesToKill << node;
}
});
foreach (auto node, nodesToKill) {
emit killNode(node);
}
}
SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection, SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
const PendingAssignedNodeData& pendingAssignment) { const PendingAssignedNodeData& pendingAssignment) {
@ -165,15 +216,16 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
_pendingAssignedNodes.erase(it); _pendingAssignedNodes.erase(it);
// always allow assignment clients to create and destroy entities // always allow assignment clients to create and destroy entities
newNode->setIsAllowedEditor(true); NodePermissions userPerms;
newNode->setCanRez(true); userPerms.isAssignment = true;
userPerms.canAdjustLocks = true;
userPerms.canRezPermanentEntities = true;
userPerms.canRezTemporaryEntities = true;
newNode->setPermissions(userPerms);
return newNode; return newNode;
} }
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection, SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
const QString& username, const QString& username,
@ -181,92 +233,66 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
auto limitedNodeList = DependencyManager::get<LimitedNodeList>(); auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
bool isRestrictingAccess = // start with empty permissions
_server->_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); NodePermissions userPerms(username);
userPerms.setAll(false);
// check if this user is on our local machine - if this is true they are always allowed to connect // check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress(); QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
bool isLocalUser = bool isLocalUser =
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
if (isLocalUser) {
// if we're using restricted access and this user is not local make sure we got a user signature userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
if (isRestrictingAccess && !isLocalUser) { qDebug() << "user-permissions: is local user, so:" << userPerms;
if (!username.isEmpty()) {
if (usernameSignature.isEmpty()) {
// if user didn't include usernameSignature in connect request, send a connectionToken packet
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
// ask for their public key right now to make sure we have it
requestUserPublicKey(username);
return SharedNodePointer();
}
}
} }
bool verifiedUsername = false; if (!username.isEmpty() && usernameSignature.isEmpty()) {
// user is attempting to prove their identity to us, but we don't have enough information
// if we do not have a local user we need to subject them to our verification and capacity checks sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
if (!isLocalUser) { // ask for their public key right now to make sure we have it
requestUserPublicKey(username);
// check if we need to look at the username signature if (!userPerms.canConnectToDomain) {
if (isRestrictingAccess) {
if (isVerifiedAllowedUser(username, usernameSignature, nodeConnection.senderSockAddr)) {
// we verified the user via their username and signature - set the verifiedUsername
// so we don't re-decrypt their sig if we're trying to exempt them from max capacity check (due to
// being in the allowed editors list)
verifiedUsername = true;
} else {
// failed to verify user - return a null shared ptr
return SharedNodePointer();
}
}
if (!isWithinMaxCapacity(username, usernameSignature, verifiedUsername, nodeConnection.senderSockAddr)) {
// we can't allow this user to connect because we are at max capacity (and they either aren't an allowed editor
// or couldn't be verified as one)
return SharedNodePointer(); return SharedNodePointer();
} }
} }
// if this user is in the editors list (or if the editors list is empty) set the user's node's isAllowedEditor to true if (username.isEmpty()) {
const QVariant* allowedEditorsVariant = // they didn't tell us who they are
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH); userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList(); qDebug() << "user-permissions: no username, so:" << userPerms;
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
// if the allowed editors list is empty then everyone can adjust locks // they are sent us a username and the signature verifies it
bool isAllowedEditor = allowedEditors.empty(); if (_server->_settingsManager.havePermissionsForName(username)) {
// we have specific permissions for this user.
if (allowedEditors.contains(username, Qt::CaseInsensitive)) { userPerms = _server->_settingsManager.getPermissionsForName(username);
// we have a non-empty allowed editors list - check if this user is verified to be in it qDebug() << "user-permissions: specific user matches, so:" << userPerms;
if (!verifiedUsername) {
if (!verifyUserSignature(username, usernameSignature, HifiSockAddr())) {
// failed to verify a user that is in the allowed editors list
// TODO: fix public key refresh in interface/metaverse and force this check
qDebug() << "Could not verify user" << username << "as allowed editor. In the interim this user"
<< "will be given edit rights to avoid a thrasing of public key requests and connect requests.";
}
isAllowedEditor = true;
} else { } else {
// already verified this user and they are in the allowed editors list // they are logged into metaverse, but we don't have specific permissions for them.
isAllowedEditor = true; userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
qDebug() << "user-permissions: user is logged in, so:" << userPerms;
}
userPerms.setUserName(username);
} else {
// they sent us a username, but it didn't check out
requestUserPublicKey(username);
if (!userPerms.canConnectToDomain) {
return SharedNodePointer();
} }
} }
// check if only editors should be able to rez entities qDebug() << "user-permissions: final:" << userPerms;
const QVariant* editorsAreRezzersVariant =
valueForKeyPath(_server->_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH);
bool onlyEditorsAreRezzers = false; if (!userPerms.canConnectToDomain) {
if (editorsAreRezzersVariant) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool(); nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
return SharedNodePointer();
} }
bool canRez = true; if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) {
if (onlyEditorsAreRezzers) { // we can't allow this user to connect because we are at max capacity
canRez = isAllowedEditor; sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
DomainHandler::ConnectionRefusedReason::TooManyUsers);
return SharedNodePointer();
} }
QUuid hintNodeID; QUuid hintNodeID;
@ -290,8 +316,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID); SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
// set the edit rights for this user // set the edit rights for this user
newNode->setIsAllowedEditor(isAllowedEditor); newNode->setPermissions(userPerms);
newNode->setCanRez(canRez);
// grab the linked data for our new node so we can set the username // grab the linked data for our new node so we can set the username
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData()); DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
@ -367,7 +392,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
rsaPublicKey); rsaPublicKey);
if (decryptResult == 1) { if (decryptResult == 1) {
qDebug() << "Username signature matches for" << username << "- allowing connection."; qDebug() << "Username signature matches for" << username;
// free up the public key and remove connection token before we return // free up the public key and remove connection token before we return
RSA_free(rsaPublicKey); RSA_free(rsaPublicKey);
@ -407,67 +432,17 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
return false; return false;
} }
bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, bool DomainGatekeeper::isWithinMaxCapacity() {
const HifiSockAddr& senderSockAddr) {
if (username.isEmpty()) {
qDebug() << "Connect request denied - no username provided.";
sendConnectionDeniedPacket("No username provided", senderSockAddr,
DomainHandler::ConnectionRefusedReason::LoginError);
return false;
}
QStringList allowedUsers =
_server->_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
if (!verifyUserSignature(username, usernameSignature, senderSockAddr)) {
return false;
}
} else {
qDebug() << "Connect request denied for user" << username << "- not in allowed users list.";
sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr,
DomainHandler::ConnectionRefusedReason::NotAuthorized);
return false;
}
return true;
}
bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature,
bool& verifiedUsername,
const HifiSockAddr& senderSockAddr) {
// find out what our maximum capacity is // find out what our maximum capacity is
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); const QVariant* maximumUserCapacityVariant =
valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
if (maximumUserCapacity > 0) { if (maximumUserCapacity > 0) {
unsigned int connectedUsers = _server->countConnectedUsers(); unsigned int connectedUsers = _server->countConnectedUsers();
if (connectedUsers >= maximumUserCapacity) { if (connectedUsers >= maximumUserCapacity) {
// too many users, deny the new connection unless this user is an allowed editor
const QVariant* allowedEditorsVariant =
valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
if (allowedEditors.contains(username)) {
if (verifiedUsername || verifyUserSignature(username, usernameSignature, senderSockAddr)) {
verifiedUsername = true;
qDebug() << "Above maximum capacity -" << connectedUsers << "/" << maximumUserCapacity <<
"but user" << username << "is in allowed editors list so will be allowed to connect.";
return true;
}
}
// deny connection from this user
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection."; qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
sendConnectionDeniedPacket("Too many connected users.", senderSockAddr,
DomainHandler::ConnectionRefusedReason::TooManyUsers);
return false; return false;
} }
@ -479,8 +454,7 @@ bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteA
void DomainGatekeeper::preloadAllowedUserPublicKeys() { void DomainGatekeeper::preloadAllowedUserPublicKeys() {
const QVariant* allowedUsersVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH); QStringList allowedUsers = _server->_settingsManager.getAllNames();
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
if (allowedUsers.size() > 0) { if (allowedUsers.size() > 0) {
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not // in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
@ -492,6 +466,11 @@ void DomainGatekeeper::preloadAllowedUserPublicKeys() {
} }
void DomainGatekeeper::requestUserPublicKey(const QString& username) { void DomainGatekeeper::requestUserPublicKey(const QString& username) {
// don't request public keys for the standard psuedo-account-names
if (NodePermissions::standardNames.contains(username, Qt::CaseInsensitive)) {
return;
}
// even if we have a public key for them right now, request a new one in case it has just changed // even if we have a public key for them right now, request a new one in case it has just changed
JSONCallbackParameters callbackParams; JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackReceiver = this;
@ -530,15 +509,24 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
} }
} }
void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) {
QString protocolVersionError = "Protocol version mismatch - Domain version: " + QCoreApplication::applicationVersion();
qDebug() << "Protocol Version mismatch - denying connection.";
sendConnectionDeniedPacket(protocolVersionError, senderSockAddr,
DomainHandler::ConnectionRefusedReason::ProtocolMismatch);
}
void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
DomainHandler::ConnectionRefusedReason reasonCode) { DomainHandler::ConnectionRefusedReason reasonCode) {
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection // this is an agent and we've decided we won't let them connect - send them a packet to deny connection
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) {

View file

@ -42,6 +42,8 @@ public:
void preloadAllowedUserPublicKeys(); void preloadAllowedUserPublicKeys();
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); } void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
static void sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr);
public slots: public slots:
void processConnectRequestPacket(QSharedPointer<ReceivedMessage> message); void processConnectRequestPacket(QSharedPointer<ReceivedMessage> message);
void processICEPingPacket(QSharedPointer<ReceivedMessage> message); void processICEPingPacket(QSharedPointer<ReceivedMessage> message);
@ -51,8 +53,12 @@ public slots:
void publicKeyJSONCallback(QNetworkReply& requestReply); void publicKeyJSONCallback(QNetworkReply& requestReply);
signals: signals:
void killNode(SharedNodePointer node);
void connectedNode(SharedNodePointer node); void connectedNode(SharedNodePointer node);
public slots:
void updateNodePermissions();
private slots: private slots:
void handlePeerPingTimeout(); void handlePeerPingTimeout();
private: private:
@ -66,18 +72,14 @@ private:
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr); const HifiSockAddr& senderSockAddr);
bool isVerifiedAllowedUser(const QString& username, const QByteArray& usernameSignature, bool isWithinMaxCapacity();
const HifiSockAddr& senderSockAddr);
bool isWithinMaxCapacity(const QString& username, const QByteArray& usernameSignature,
bool& verifiedUsername,
const HifiSockAddr& senderSockAddr);
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr); const HifiSockAddr& senderSockAddr);
void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr); void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr);
void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, static void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown); DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown);
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);

View file

@ -101,6 +101,13 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// make sure we hear about newly connected nodes from our gatekeeper // make sure we hear about newly connected nodes from our gatekeeper
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode); connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
// if a connected node loses connection privileges, hang up on it
connect(&_gatekeeper, &DomainGatekeeper::killNode, this, &DomainServer::handleKillNode);
// if permissions are updated, relay the changes to the Node datastructures
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
// we either read a certificate and private key or were not passed one // we either read a certificate and private key or were not passed one
// and completed login or did not need to // and completed login or did not need to
@ -318,16 +325,11 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) {
auto nodeList = DependencyManager::get<LimitedNodeList>(); auto nodeList = DependencyManager::get<LimitedNodeList>();
// This implements a special case that handles OLD clients which don't know how to negotiate matching // if this is a mismatching connect packet, we can't simply drop it on the floor
// protocol versions. We know these clients will sent DomainConnectRequest with older versions. We also // send back a packet to the interface that tells them we refuse connection for a mismatch
// know these clients will show a warning dialog if they get an EntityData with a protocol version they if (headerType == PacketType::DomainConnectRequest
// don't understand, so we can send them an empty EntityData with our latest version and they will && headerVersion != versionForPacketType(PacketType::DomainConnectRequest)) {
// warn the user that the protocol is not compatible DomainGatekeeper::sendProtocolMismatchConnectionDenial(packet.getSenderSockAddr());
if (headerType == PacketType::DomainConnectRequest &&
headerVersion < static_cast<PacketVersion>(DomainConnectRequestVersion::HasProtocolVersions)) {
auto packetWithBadVersion = NLPacket::create(PacketType::EntityData);
nodeList->sendPacket(std::move(packetWithBadVersion), packet.getSenderSockAddr());
return false;
} }
// let the normal nodeList implementation handle all other packets. // let the normal nodeList implementation handle all other packets.
@ -800,8 +802,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
extendedHeaderStream << limitedNodeList->getSessionUUID(); extendedHeaderStream << limitedNodeList->getSessionUUID();
extendedHeaderStream << node->getUUID(); extendedHeaderStream << node->getUUID();
extendedHeaderStream << (quint8) node->isAllowedEditor(); extendedHeaderStream << node->getPermissions();
extendedHeaderStream << (quint8) node->getCanRez();
auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader); auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader);
@ -1093,11 +1094,12 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting; domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
// Add a flag to indicate if this domain uses restricted access - // add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
// for now that will exclude it from listings const QString RESTRICTED_ACCESS_FLAG = "restricted";
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
domainObject[RESTRICTED_ACCESS_FLAG] = // consider the domain to have restricted access if "anonymous" connections can't connect to the domain.
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
// Add the metadata to the heartbeat // Add the metadata to the heartbeat
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
@ -2117,25 +2119,32 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
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;

View file

@ -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);

View file

@ -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,8 +690,9 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
return QJsonObject(); return QJsonObject();
} }
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
auto& settingsVariant = _configMap.getUserConfig(); auto& settingsVariant = _configMap.getUserConfig();
bool needRestart = false;
// Iterate on the setting groups // Iterate on the setting groups
foreach(const QString& rootKey, postedObject.keys()) { foreach(const QString& rootKey, postedObject.keys()) {
@ -521,6 +741,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) { if (!matchingDescriptionObject.isEmpty()) {
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
if (rootKey != "security") {
needRestart = true;
}
} else { } else {
qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting."; qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
} }
@ -534,6 +757,9 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) { if (!matchingDescriptionObject.isEmpty()) {
QJsonValue settingValue = rootValue.toObject()[settingKey]; QJsonValue settingValue = rootValue.toObject()[settingKey];
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
if (rootKey != "security") {
needRestart = true;
}
} else { } else {
qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey << qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
"- cannot update setting."; "- cannot update setting.";
@ -549,9 +775,42 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
// re-merge the user and master configs after a settings change // re-merge the user and master configs after a settings change
_configMap.mergeMasterAndUserConfigs(); _configMap.mergeMasterAndUserConfigs();
return needRestart;
}
// Compare two members of a permissions list
bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
if (!v1.canConvert(QMetaType::QVariantMap) ||
!v2.canConvert(QMetaType::QVariantMap)) {
return v1.toString() < v2.toString();
}
QVariantMap m1 = v1.toMap();
QVariantMap m2 = v2.toMap();
if (!m1.contains("permissions_id") ||
!m2.contains("permissions_id")) {
return v1.toString() < v2.toString();
}
return m1["permissions_id"].toString() < m2["permissions_id"].toString();
}
void DomainServerSettingsManager::sortPermissions() {
// sort the permission-names
QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH);
if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) {
QList<QVariant>* standardPermissionsList = reinterpret_cast<QVariantList*>(standardPermissions);
std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan);
}
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH);
if (permissions && permissions->canConvert(QMetaType::QVariantList)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
}
} }
void DomainServerSettingsManager::persistToFile() { void DomainServerSettingsManager::persistToFile() {
sortPermissions();
// make sure we have the dir the settings file is supposed to live in // make sure we have the dir the settings file is supposed to live in
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename()); QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());

View file

@ -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

View file

@ -199,7 +199,7 @@ FocusScope {
anchors.leftMargin: hifi.dimensions.textPadding anchors.leftMargin: hifi.dimensions.textPadding
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
id: popupText id: popupText
text: listView.model[index] text: listView.model[index] ? listView.model[index] : ""
size: hifi.fontSizes.textFieldInput size: hifi.fontSizes.textFieldInput
color: hifi.colors.baseGray color: hifi.colors.baseGray
} }

View file

@ -186,7 +186,12 @@ ModalWindow {
} }
if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) { if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
if (root.selectDirectory) {
currentSelection.text = currentText !== "This PC" ? currentText : "";
d.currentSelectionUrl = helper.pathToUrl(currentText);
}
fileTableModel.folder = folder; fileTableModel.folder = folder;
fileTableView.forceActiveFocus();
} }
} }
} }
@ -212,9 +217,11 @@ ModalWindow {
function update() { function update() {
var row = fileTableView.currentRow; var row = fileTableView.currentRow;
openButton.text = root.selectDirectory && row === -1 ? "Choose" : "Open"
if (row === -1) { if (row === -1) {
if (!root.selectDirectory) {
currentSelection.text = "";
currentSelectionIsFolder = false;
}
return; return;
} }
@ -445,12 +452,6 @@ ModalWindow {
onSortIndicatorOrderChanged: { updateSort(); } onSortIndicatorOrderChanged: { updateSort(); }
onActiveFocusChanged: {
if (activeFocus && currentRow == -1) {
fileTableView.selection.select(0)
}
}
itemDelegate: Item { itemDelegate: Item {
clip: true clip: true
@ -607,6 +608,12 @@ ModalWindow {
readOnly: !root.saveDialog readOnly: !root.saveDialog
activeFocusOnTab: !readOnly activeFocusOnTab: !readOnly
onActiveFocusChanged: if (activeFocus) { selectAll(); } onActiveFocusChanged: if (activeFocus) { selectAll(); }
onTextChanged: {
if (root.saveDialog && text !== "") {
fileTableView.selection.clear();
fileTableView.currentRow = -1;
}
}
onAccepted: okAction.trigger(); onAccepted: okAction.trigger();
} }
@ -652,7 +659,7 @@ ModalWindow {
Action { Action {
id: okAction id: okAction
text: root.saveDialog ? "Save" : (root.selectDirectory ? "Choose" : "Open") text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open"
enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
onTriggered: { onTriggered: {
if (!root.selectDirectory && !d.currentSelectionIsFolder if (!root.selectDirectory && !d.currentSelectionIsFolder
@ -676,7 +683,6 @@ ModalWindow {
return; return;
} }
// Handle the ambiguity between different cases // Handle the ambiguity between different cases
// * typed name (with or without extension) // * typed name (with or without extension)
// * full path vs relative vs filename only // * full path vs relative vs filename only

View file

@ -630,7 +630,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
connect(&domainHandler, &DomainHandler::resetting, nodeList.data(), &NodeList::resetDomainServerCheckInVersion);
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused);
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
@ -654,9 +653,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated); connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated);
connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID);
connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID);
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, this, &Application::limitOfSilentDomainCheckInsReached);
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
// you might think we could just do this in NodeList but we only want this connection for Interface
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset);
// connect to appropriate slots on AccountManager // connect to appropriate slots on AccountManager
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
@ -1074,8 +1075,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) {
switch (static_cast<DomainHandler::ConnectionRefusedReason>(reasonCode)) { switch (static_cast<DomainHandler::ConnectionRefusedReason>(reasonCode)) {
case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: case DomainHandler::ConnectionRefusedReason::ProtocolMismatch:
notifyPacketVersionMismatch();
break;
case DomainHandler::ConnectionRefusedReason::TooManyUsers: case DomainHandler::ConnectionRefusedReason::TooManyUsers:
case DomainHandler::ConnectionRefusedReason::Unknown: { case DomainHandler::ConnectionRefusedReason::Unknown: {
QString message = "Unable to connect to the location you are visiting.\n"; QString message = "Unable to connect to the location you are visiting.\n";
@ -4209,6 +4208,7 @@ void Application::resetSensors(bool andReload) {
DependencyManager::get<DdeFaceTracker>()->reset(); DependencyManager::get<DdeFaceTracker>()->reset();
DependencyManager::get<EyeTracker>()->reset(); DependencyManager::get<EyeTracker>()->reset();
getActiveDisplayPlugin()->resetSensors(); getActiveDisplayPlugin()->resetSensors();
_overlayConductor.centerUI();
getMyAvatar()->reset(andReload); getMyAvatar()->reset(andReload);
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection); QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
} }
@ -4294,7 +4294,7 @@ void Application::nodeActivated(SharedNodePointer node) {
if (assetDialog) { if (assetDialog) {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
if (nodeList->getThisNodeCanRez()) { if (nodeList->getThisNodeCanWriteAssets()) {
// call reload on the shown asset browser dialog to get the mappings (if permissions allow) // call reload on the shown asset browser dialog to get the mappings (if permissions allow)
QMetaObject::invokeMethod(assetDialog, "reload"); QMetaObject::invokeMethod(assetDialog, "reload");
} else { } else {
@ -4620,17 +4620,6 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const {
Physics::setSessionUUID(sessionUUID); Physics::setSessionUUID(sessionUUID);
} }
// If we're not getting anything back from the domain server checkin, it might be that the domain speaks an
// older version of the DomainConnectRequest protocol. We will attempt to send and older version of DomainConnectRequest.
// We won't actually complete the connection, but if the server responds, we know that it needs to be upgraded (or we
// need to be downgraded to talk to it).
void Application::limitOfSilentDomainCheckInsReached() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->downgradeDomainServerCheckInVersion(); // attempt to use an older domain checkin version
nodeList->reset();
}
bool Application::askToSetAvatarUrl(const QString& url) { bool Application::askToSetAvatarUrl(const QString& url) {
QUrl realUrl(url); QUrl realUrl(url);
if (realUrl.isLocalFile()) { if (realUrl.isLocalFile()) {
@ -4801,7 +4790,7 @@ void Application::toggleRunningScriptsWidget() const {
} }
void Application::toggleAssetServerWidget(QString filePath) { void Application::toggleAssetServerWidget(QString filePath) {
if (!DependencyManager::get<NodeList>()->getThisNodeCanRez()) { if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
return; return;
} }

View file

@ -318,7 +318,6 @@ private slots:
bool displayAvatarAttachmentConfirmationDialog(const QString& name) const; bool displayAvatarAttachmentConfirmationDialog(const QString& name) const;
void setSessionUUID(const QUuid& sessionUUID) const; void setSessionUUID(const QUuid& sessionUUID) const;
void limitOfSilentDomainCheckInsReached();
void domainChanged(const QString& domainHostname); void domainChanged(const QString& domainHostname);
void updateWindowTitle() const; void updateWindowTitle() const;

View file

@ -136,8 +136,8 @@ Menu::Menu() {
Qt::CTRL | Qt::SHIFT | Qt::Key_A, Qt::CTRL | Qt::SHIFT | Qt::Key_A,
qApp, SLOT(toggleAssetServerWidget())); qApp, SLOT(toggleAssetServerWidget()));
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
QObject::connect(nodeList.data(), &NodeList::canRezChanged, assetServerAction, &QAction::setEnabled); QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled);
assetServerAction->setEnabled(nodeList->getThisNodeCanRez()); assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets());
// Edit > Package Model... [advanced] // Edit > Package Model... [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
@ -256,8 +256,7 @@ Menu::Menu() {
UNSPECIFIED_POSITION, "Advanced"); UNSPECIFIED_POSITION, "Advanced");
// View > Overlays // View > Overlays
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true, addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true);
qApp, SLOT(setOverlaysVisible(bool)));
// Navigate menu ---------------------------------- // Navigate menu ----------------------------------
MenuWrapper* navigateMenu = addMenu("Navigate"); MenuWrapper* navigateMenu = addMenu("Navigate");
@ -542,6 +541,9 @@ Menu::Menu() {
#if (PR_BUILD || DEV_BUILD) #if (PR_BUILD || DEV_BUILD)
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false, addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false,
qApp, SLOT(sendWrongProtocolVersionsSignature(bool))); qApp, SLOT(sendWrongProtocolVersionsSignature(bool)));
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false,
nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool)));
#endif #endif

View file

@ -166,6 +166,7 @@ namespace MenuOption {
const QString RunTimingTests = "Run Timing Tests"; const QString RunTimingTests = "Run Timing Tests";
const QString ScriptEditor = "Script Editor..."; const QString ScriptEditor = "Script Editor...";
const QString ScriptedMotorControl = "Enable Scripted Motor Control"; const QString ScriptedMotorControl = "Enable Scripted Motor Control";
const QString SendWrongDSConnectVersion = "Send wrong DS connect version";
const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SendWrongProtocolVersion = "Send wrong protocol version";
const QString SetHomeLocation = "Set Home Location"; const QString SetHomeLocation = "Set Home Location";
const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowDSConnectTable = "Show Domain Connection Timing";

View file

@ -1246,8 +1246,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
_characterController.setPositionAndOrientation(getPosition(), getOrientation()); _characterController.setPositionAndOrientation(getPosition(), getOrientation());
if (qApp->isHMDMode()) { if (qApp->isHMDMode()) {
bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput());
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput);
} else { } else {
_follow.deactivate(); _follow.deactivate();
} }
@ -2132,3 +2131,7 @@ bool MyAvatar::didTeleport() {
lastPosition = pos; lastPosition = pos;
return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME); return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME);
} }
bool MyAvatar::hasDriveInput() const {
return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
}

View file

@ -266,6 +266,8 @@ public:
controller::Pose getLeftHandControllerPoseInAvatarFrame() const; controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
controller::Pose getRightHandControllerPoseInAvatarFrame() const; controller::Pose getRightHandControllerPoseInAvatarFrame() const;
bool hasDriveInput() const;
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
Q_INVOKABLE bool getCharacterControllerEnabled(); Q_INVOKABLE bool getCharacterControllerEnabled();

View file

@ -17,122 +17,164 @@
#include "OverlayConductor.h" #include "OverlayConductor.h"
OverlayConductor::OverlayConductor() { OverlayConductor::OverlayConductor() {
} }
OverlayConductor::~OverlayConductor() { OverlayConductor::~OverlayConductor() {
} }
void OverlayConductor::update(float dt) { bool OverlayConductor::headOutsideOverlay() const {
glm::mat4 hmdMat = qApp->getHMDSensorPose();
glm::vec3 hmdPos = extractTranslation(hmdMat);
glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f));
updateMode(); Transform uiTransform = qApp->getApplicationCompositor().getModelTransform();
glm::vec3 uiPos = uiTransform.getTranslation();
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
switch (_mode) { const float MAX_COMPOSITOR_DISTANCE = 0.6f;
case SITTING: { const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
// when sitting, the overlay is at the origin, facing down the -z axis. if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
// the camera is taken directly from the HMD. glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
Transform identity; return true;
qApp->getApplicationCompositor().setModelTransform(identity);
qApp->getApplicationCompositor().setCameraBaseTransform(identity);
break;
}
case STANDING: {
// when standing, the overlay is at a reference position, which is set when the overlay is
// enabled. The camera is taken directly from the HMD, but in world space.
// So the sensorToWorldMatrix must be applied.
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
Transform t;
t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix());
qApp->getApplicationCompositor().setCameraBaseTransform(t);
// detect when head moves out side of sweet spot, or looks away.
mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose();
vec3 headWorldPos = extractTranslation(headMat);
vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f);
Transform modelXform = qApp->getApplicationCompositor().getModelTransform();
vec3 compositorWorldPos = modelXform.getTranslation();
vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
const float MAX_COMPOSITOR_DISTANCE = 0.6f;
const float MAX_COMPOSITOR_ANGLE = 110.0f;
if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE ||
glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) {
// fade out the overlay
setEnabled(false);
}
break;
}
case FLAT:
// do nothing
break;
} }
return false;
} }
void OverlayConductor::updateMode() { bool OverlayConductor::updateAvatarIsAtRest() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (myAvatar->getClearOverlayWhenDriving()) {
float speed = glm::length(myAvatar->getVelocity()); const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
const float MIN_DRIVING = 0.2f; const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
const float MAX_NOT_DRIVING = 0.01f;
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; const float AT_REST_THRESHOLD = 0.01f;
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD;
bool nowDriving = _driving; // Assume current _driving mode unless... if (desiredAtRest != _desiredAtRest) {
if (speed > MIN_DRIVING) { // ... we're definitely moving... // start timer
nowDriving = true; _desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS);
} else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. }
nowDriving = false;
} _desiredAtRest = desiredAtRest;
// Check that we're in this new mode for long enough to really trigger a transition.
if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) {
_timeInPotentialMode = 0; // timer expired
} else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. // change state!
_timeInPotentialMode = usecTimestampNow(); _currentAtRest = _desiredAtRest;
} else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { // disable timer
_timeInPotentialMode = 0; // a real transition _desiredAtRestTimer = 0;
if (nowDriving) { }
_wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
} else { // reset when coming out of driving return _currentAtRest;
_mode = FLAT; // Seems appropriate to let things reset, below, after the following. }
// All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments.
qApp->getActiveDisplayPlugin()->resetSensors(); bool OverlayConductor::updateAvatarHasDriveInput() {
myAvatar->reset(true, false, false); MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms
const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s
bool desiredDriving = myAvatar->hasDriveInput();
if (desiredDriving != _desiredDriving) {
// start timer
_desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS);
}
_desiredDriving = desiredDriving;
if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) {
// timer expired
// change state!
_currentDriving = _desiredDriving;
// disable timer
_desiredDrivingTimer = 0;
}
return _currentDriving;
}
void OverlayConductor::centerUI() {
// place the overlay at the current hmd position in sensor space
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
}
bool OverlayConductor::userWishesToHide() const {
// user pressed toggle button.
return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
}
bool OverlayConductor::userWishesToShow() const {
// user pressed toggle button.
return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && !Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
}
void OverlayConductor::setState(State state) {
#ifdef WANT_DEBUG
static QString stateToString[NumStates] = { "Enabled", "DisabledByDrive", "DisabledByHead", "DisabledByToggle" };
qDebug() << "OverlayConductor " << stateToString[state] << "<--" << stateToString[_state];
#endif
_state = state;
}
OverlayConductor::State OverlayConductor::getState() const {
return _state;
}
void OverlayConductor::update(float dt) {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// centerUI when hmd mode is first enabled
if (qApp->isHMDMode() && !_hmdMode) {
centerUI();
}
_hmdMode = qApp->isHMDMode();
bool prevDriving = _currentDriving;
bool isDriving = updateAvatarHasDriveInput();
bool drivingChanged = prevDriving != isDriving;
bool isAtRest = updateAvatarIsAtRest();
switch (getState()) {
case Enabled:
if (myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode() && headOutsideOverlay()) {
setState(DisabledByHead);
setEnabled(false);
} }
if (_wantsOverlays) { if (userWishesToHide()) {
setEnabled(!nowDriving); setState(DisabledByToggle);
setEnabled(false);
} }
_driving = nowDriving; if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) {
} // Else haven't accumulated enough time in new mode, but keep timing. setState(DisabledByDrive);
setEnabled(false);
}
break;
case DisabledByDrive:
if (!isDriving || userWishesToShow()) {
setState(Enabled);
setEnabled(true);
}
break;
case DisabledByHead:
if (isAtRest || userWishesToShow()) {
setState(Enabled);
setEnabled(true);
}
break;
case DisabledByToggle:
if (userWishesToShow()) {
setState(Enabled);
setEnabled(true);
}
break;
default:
break;
} }
Mode newMode; _prevOverlayMenuChecked = Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
if (qApp->isHMDMode()) {
newMode = SITTING;
} else {
newMode = FLAT;
}
if (newMode != _mode) {
switch (newMode) {
case SITTING: {
// enter the SITTING state
// place the overlay at origin
qApp->getApplicationCompositor().setModelTransform(Transform());
break;
}
case STANDING: { // STANDING mode is not currently used.
// enter the STANDING state
// place the overlay at the current hmd position in world space
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
break;
}
case FLAT:
// do nothing
break;
}
}
_mode = newMode;
} }
void OverlayConductor::setEnabled(bool enabled) { void OverlayConductor::setEnabled(bool enabled) {
@ -143,12 +185,14 @@ void OverlayConductor::setEnabled(bool enabled) {
_enabled = enabled; // set the new value _enabled = enabled; // set the new value
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->setPinned(!_enabled); offscreenUi->setPinned(!_enabled);
// ensure that the the state of the menu item reflects the state of the overlay.
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, _enabled);
_prevOverlayMenuChecked = _enabled;
// if the new state is visible/enabled... // if the new state is visible/enabled...
if (_enabled && _mode == STANDING) { if (_enabled && qApp->isHMDMode()) {
// place the overlay at the current hmd position in world space centerUI();
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
} }
} }

View file

@ -20,20 +20,41 @@ public:
void setEnabled(bool enable); void setEnabled(bool enable);
bool getEnabled() const; bool getEnabled() const;
private: void centerUI();
void updateMode();
enum Mode { private:
FLAT, bool headOutsideOverlay() const;
SITTING, bool updateAvatarHasDriveInput();
STANDING bool updateAvatarIsAtRest();
bool userWishesToHide() const;
bool userWishesToShow() const;
enum State {
Enabled = 0,
DisabledByDrive,
DisabledByHead,
DisabledByToggle,
NumStates
}; };
Mode _mode { FLAT }; void setState(State state);
State getState() const;
State _state { DisabledByDrive };
bool _prevOverlayMenuChecked { true };
bool _enabled { false }; bool _enabled { false };
bool _driving { false }; bool _hmdMode { false };
quint64 _timeInPotentialMode { 0 };
bool _wantsOverlays { true }; // used by updateAvatarHasDriveInput
quint64 _desiredDrivingTimer { 0 };
bool _desiredDriving { false };
bool _currentDriving { false };
// used by updateAvatarIsAtRest
quint64 _desiredAtRestTimer { 0 };
bool _desiredAtRest { true };
bool _currentAtRest { true };
}; };
#endif #endif

View file

@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3&
} }
glm::mat4 CompositorHelper::getUiTransform() const { glm::mat4 CompositorHelper::getUiTransform() const {
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()); glm::mat4 modelMat;
_modelTransform.getMatrix(modelMat);
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat;
} }
//Finds the collision point of a world space ray //Finds the collision point of a world space ray

View file

@ -253,12 +253,13 @@ void HmdDisplayPlugin::compositeScene() {
void HmdDisplayPlugin::compositeOverlay() { void HmdDisplayPlugin::compositeOverlay() {
using namespace oglplus; using namespace oglplus;
auto compositorHelper = DependencyManager::get<CompositorHelper>(); auto compositorHelper = DependencyManager::get<CompositorHelper>();
glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix();
useProgram(_program); useProgram(_program);
_sphereSection->Use(); _sphereSection->Use();
for_each_eye([&](Eye eye) { for_each_eye([&](Eye eye) {
eyeViewport(eye); eyeViewport(eye);
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat;
auto mvp = _eyeProjections[eye] * modelView; auto mvp = _eyeProjections[eye] * modelView;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp); Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_sphereSection->Draw(); _sphereSection->Draw();

View file

@ -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);

View file

@ -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);

View file

@ -26,6 +26,8 @@
#include "LogHandler.h" #include "LogHandler.h"
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;
const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour
EntityTree::EntityTree(bool shouldReaverage) : EntityTree::EntityTree(bool shouldReaverage) :
Octree(shouldReaverage), Octree(shouldReaverage),
@ -128,13 +130,16 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
EntityItemProperties properties = origProperties; EntityItemProperties properties = origProperties;
bool allowLockChange; bool allowLockChange;
bool canRezPermanentEntities;
QUuid senderID; QUuid senderID;
if (senderNode.isNull()) { if (senderNode.isNull()) {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
allowLockChange = nodeList->isAllowedEditor(); allowLockChange = nodeList->isAllowedEditor();
canRezPermanentEntities = nodeList->getThisNodeCanRez();
senderID = nodeList->getSessionUUID(); senderID = nodeList->getSessionUUID();
} else { } else {
allowLockChange = senderNode->isAllowedEditor(); allowLockChange = senderNode->isAllowedEditor();
canRezPermanentEntities = senderNode->getCanRez();
senderID = senderNode->getUUID(); senderID = senderNode->getUUID();
} }
@ -143,6 +148,12 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
return false; return false;
} }
if (!canRezPermanentEntities && (entity->getLifetime() != properties.getLifetime())) {
// we don't allow a Node that can't create permanent entities to adjust lifetimes on existing ones
qCDebug(entities) << "Refusing disallowed entity lifetime adjustment.";
return false;
}
// enforce support for locked entities. If an entity is currently locked, then the only // enforce support for locked entities. If an entity is currently locked, then the only
// property we allow you to change is the locked property. // property we allow you to change is the locked property.
if (entity->getLocked()) { if (entity->getLocked()) {
@ -308,17 +319,39 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
return true; return true;
} }
bool EntityTree::permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) {
float lifeTime = properties.getLifetime();
if (lifeTime == 0.0f || lifeTime > _maxTmpEntityLifetime) {
// this is an attempt to rez a permanent entity.
if (!canRez) {
return false;
}
} else {
// this is an attempt to rez a temporary entity.
if (!canRezTmp) {
return false;
}
}
return true;
}
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer result = NULL; EntityItemPointer result = NULL;
auto nodeList = DependencyManager::get<NodeList>();
if (!nodeList) {
qDebug() << "EntityTree::addEntity -- can't get NodeList";
return nullptr;
}
bool clientOnly = properties.getClientOnly(); bool clientOnly = properties.getClientOnly();
if (!clientOnly && getIsClient()) { if (!clientOnly && getIsClient() &&
!permissionsAllowRez(properties, nodeList->getThisNodeCanRez(), nodeList->getThisNodeCanRezTmp())) {
// if our Node isn't allowed to create entities in this domain, don't try. // if our Node isn't allowed to create entities in this domain, don't try.
auto nodeList = DependencyManager::get<NodeList>(); return nullptr;
if (nodeList && !nodeList->getThisNodeCanRez()) {
return NULL;
}
} }
bool recordCreationTime = false; bool recordCreationTime = false;
@ -920,7 +953,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
endUpdate = usecTimestampNow(); endUpdate = usecTimestampNow();
_totalUpdates++; _totalUpdates++;
} else if (message.getType() == PacketType::EntityAdd) { } else if (message.getType() == PacketType::EntityAdd) {
if (senderNode->getCanRez()) { if (permissionsAllowRez(properties, senderNode->getCanRez(), senderNode->getCanRezTmp())) {
// this is a new entity... assign a new entityID // this is a new entity... assign a new entityID
properties.setCreated(properties.getLastEdited()); properties.setCreated(properties.getLastEdited());
startCreate = usecTimestampNow(); startCreate = usecTimestampNow();
@ -1430,6 +1463,12 @@ bool EntityTree::readFromMap(QVariantMap& map) {
QVariantList entitiesQList = map["Entities"].toList(); QVariantList entitiesQList = map["Entities"].toList();
QScriptEngine scriptEngine; QScriptEngine scriptEngine;
if (entitiesQList.length() == 0) {
// Empty map or invalidly formed file.
return false;
}
bool success = true;
foreach (QVariant entityVariant, entitiesQList) { foreach (QVariant entityVariant, entitiesQList) {
// QVariantMap --> QScriptValue --> EntityItemProperties --> Entity // QVariantMap --> QScriptValue --> EntityItemProperties --> Entity
QVariantMap entityMap = entityVariant.toMap(); QVariantMap entityMap = entityVariant.toMap();
@ -1447,9 +1486,10 @@ bool EntityTree::readFromMap(QVariantMap& map) {
EntityItemPointer entity = addEntity(entityItemID, properties); EntityItemPointer entity = addEntity(entityItemID, properties);
if (!entity) { if (!entity) {
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
success = false;
} }
} }
return true; return success;
} }
void EntityTree::resetClientEditStats() { void EntityTree::resetClientEditStats() {

View file

@ -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

View file

@ -144,12 +144,21 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
// 4. domain network address (IP or dns resolvable hostname) // 4. domain network address (IP or dns resolvable hostname)
// use our regex'ed helpers to figure out what we're supposed to do with this // use our regex'ed helpers to figure out what we're supposed to do with this
if (!handleUsername(lookupUrl.authority())) { if (handleUsername(lookupUrl.authority())) {
// handled a username for lookup
// in case we're failing to connect to where we thought this user was
// store their username as previous lookup so we can refresh their location via API
_previousLookup = lookupUrl;
} else {
// we're assuming this is either a network address or global place name // we're assuming this is either a network address or global place name
// check if it is a network address first // check if it is a network address first
bool hostChanged; bool hostChanged;
if (handleNetworkAddress(lookupUrl.host() if (handleNetworkAddress(lookupUrl.host()
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) {
// a network address lookup clears the previous lookup since we don't expect to re-attempt it
_previousLookup.clear();
// If the host changed then we have already saved to history // If the host changed then we have already saved to history
if (hostChanged) { if (hostChanged) {
@ -165,10 +174,16 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
// we may have a path that defines a relative viewpoint - if so we should jump to that now // we may have a path that defines a relative viewpoint - if so we should jump to that now
handlePath(path, trigger); handlePath(path, trigger);
} else if (handleDomainID(lookupUrl.host())){ } else if (handleDomainID(lookupUrl.host())){
// store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info
_previousLookup = lookupUrl;
// no place name - this is probably a domain ID // no place name - this is probably a domain ID
// try to look up the domain ID on the metaverse API // try to look up the domain ID on the metaverse API
attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger);
} else { } else {
// store this place name as the previous lookup in case we fail to connect and want to refresh API info
_previousLookup = lookupUrl;
// wasn't an address - lookup the place name // wasn't an address - lookup the place name
// we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after
attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger); attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger);
@ -180,9 +195,13 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
} else if (lookupUrl.toString().startsWith('/')) { } else if (lookupUrl.toString().startsWith('/')) {
qCDebug(networking) << "Going to relative path" << lookupUrl.path(); qCDebug(networking) << "Going to relative path" << lookupUrl.path();
// a path lookup clears the previous lookup since we don't expect to re-attempt it
_previousLookup.clear();
// if this is a relative path then handle it as a relative viewpoint // if this is a relative path then handle it as a relative viewpoint
handlePath(lookupUrl.path(), trigger, true); handlePath(lookupUrl.path(), trigger, true);
emit lookupResultsFinished(); emit lookupResultsFinished();
return true; return true;
} }
@ -276,7 +295,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
qCDebug(networking) << "Possible domain change required to connect to" << domainHostname qCDebug(networking) << "Possible domain change required to connect to" << domainHostname
<< "on" << domainPort; << "on" << domainPort;
emit possibleDomainChangeRequired(domainHostname, domainPort); emit possibleDomainChangeRequired(domainHostname, domainPort, domainID);
} else { } else {
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
@ -315,7 +334,10 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString();
if (!overridePath.isEmpty()) { if (!overridePath.isEmpty()) {
handlePath(overridePath, trigger); // make sure we don't re-handle an overriden path if this was a refresh of info from API
if (trigger != LookupTrigger::AttemptedRefresh) {
handlePath(overridePath, trigger);
}
} else { } else {
// take the path that came back // take the path that came back
const QString PLACE_PATH_KEY = "path"; const QString PLACE_PATH_KEY = "path";
@ -362,7 +384,7 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) {
if (errorReply.error() == QNetworkReply::ContentNotFoundError) { if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
// if this is a lookup that has no result, don't keep re-trying it // if this is a lookup that has no result, don't keep re-trying it
//_previousLookup.clear(); _previousLookup.clear();
emit lookupResultIsNotFound(); emit lookupResultIsNotFound();
} }
@ -598,7 +620,7 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress);
emit possibleDomainChangeRequired(hostname, port); emit possibleDomainChangeRequired(hostname, port, QUuid());
return hostChanged; return hostChanged;
} }
@ -618,6 +640,13 @@ void AddressManager::goToUser(const QString& username) {
QByteArray(), nullptr, requestParams); QByteArray(), nullptr, requestParams);
} }
void AddressManager::refreshPreviousLookup() {
// if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history)
if (!_previousLookup.isEmpty()) {
handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh);
}
}
void AddressManager::copyAddress() { void AddressManager::copyAddress() {
QApplication::clipboard()->setText(currentAddress().toString()); QApplication::clipboard()->setText(currentAddress().toString());
} }
@ -629,7 +658,10 @@ void AddressManager::copyPath() {
void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
// if we're cold starting and this is called for the first address (from settings) we don't do anything // if we're cold starting and this is called for the first address (from settings) we don't do anything
if (trigger != LookupTrigger::StartupFromSettings && trigger != LookupTrigger::DomainPathResponse) { if (trigger != LookupTrigger::StartupFromSettings
&& trigger != LookupTrigger::DomainPathResponse
&& trigger != LookupTrigger::AttemptedRefresh) {
if (trigger == LookupTrigger::Back) { if (trigger == LookupTrigger::Back) {
// we're about to push to the forward stack // we're about to push to the forward stack
// if it's currently empty emit our signal to say that going forward is now possible // if it's currently empty emit our signal to say that going forward is now possible

View file

@ -48,7 +48,8 @@ public:
Forward, Forward,
StartupFromSettings, StartupFromSettings,
DomainPathResponse, DomainPathResponse,
Internal Internal,
AttemptedRefresh
}; };
bool isConnected(); bool isConnected();
@ -89,6 +90,8 @@ public slots:
void goToUser(const QString& username); void goToUser(const QString& username);
void refreshPreviousLookup();
void storeCurrentAddress(); void storeCurrentAddress();
void copyAddress(); void copyAddress();
@ -99,7 +102,7 @@ signals:
void lookupResultIsOffline(); void lookupResultIsOffline();
void lookupResultIsNotFound(); void lookupResultIsNotFound();
void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort); void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort, const QUuid& domainID);
void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID); void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID);
void locationChangeRequired(const glm::vec3& newPosition, void locationChangeRequired(const glm::vec3& newPosition,
@ -152,6 +155,8 @@ private:
quint64 _lastBackPush = 0; quint64 _lastBackPush = 0;
QString _newHostLookupPath; QString _newHostLookupPath;
QUrl _previousLookup;
}; };
#endif // hifi_AddressManager_h #endif // hifi_AddressManager_h

View file

@ -14,6 +14,7 @@
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
#include <QtCore/QDataStream> #include <QtCore/QDataStream>
#include "AddressManager.h"
#include "Assignment.h" #include "Assignment.h"
#include "HifiSockAddr.h" #include "HifiSockAddr.h"
#include "NodeList.h" #include "NodeList.h"
@ -28,17 +29,10 @@
DomainHandler::DomainHandler(QObject* parent) : DomainHandler::DomainHandler(QObject* parent) :
QObject(parent), QObject(parent),
_uuid(),
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_assignmentUUID(),
_connectionToken(),
_iceDomainID(),
_iceClientID(),
_iceServerSockAddr(),
_icePeer(this), _icePeer(this),
_isConnected(false), _settingsTimer(this),
_settingsObject(), _apiRefreshTimer(this)
_settingsTimer(this)
{ {
_sockAddr.setObjectName("DomainServer"); _sockAddr.setObjectName("DomainServer");
@ -49,6 +43,16 @@ DomainHandler::DomainHandler(QObject* parent) :
static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000; static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000;
_settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS); _settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS);
connect(&_settingsTimer, &QTimer::timeout, this, &DomainHandler::settingsReceiveFail); connect(&_settingsTimer, &QTimer::timeout, this, &DomainHandler::settingsReceiveFail);
// setup the API refresh timer for auto connection information refresh from API when failing to connect
const int API_REFRESH_TIMEOUT_MSEC = 2500;
_apiRefreshTimer.setInterval(API_REFRESH_TIMEOUT_MSEC);
auto addressManager = DependencyManager::get<AddressManager>();
connect(&_apiRefreshTimer, &QTimer::timeout, addressManager.data(), &AddressManager::refreshPreviousLookup);
// stop the refresh timer if we connect to a domain
connect(this, &DomainHandler::connectedToDomain, &_apiRefreshTimer, &QTimer::stop);
} }
void DomainHandler::disconnect() { void DomainHandler::disconnect() {
@ -93,10 +97,14 @@ void DomainHandler::softReset() {
clearSettings(); clearSettings();
_domainConnectionRefusals.clear();
_connectionDenialsSinceKeypairRegen = 0; _connectionDenialsSinceKeypairRegen = 0;
// cancel the failure timeout for any pending requests for settings // cancel the failure timeout for any pending requests for settings
QMetaObject::invokeMethod(&_settingsTimer, "stop"); QMetaObject::invokeMethod(&_settingsTimer, "stop");
// restart the API refresh timer in case we fail to connect and need to refresh information
QMetaObject::invokeMethod(&_apiRefreshTimer, "start");
} }
void DomainHandler::hardReset() { void DomainHandler::hardReset() {
@ -105,7 +113,7 @@ void DomainHandler::hardReset() {
softReset(); softReset();
qCDebug(networking) << "Hard reset in NodeList DomainHandler."; qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
_iceDomainID = QUuid(); _pendingDomainID = QUuid();
_iceServerSockAddr = HifiSockAddr(); _iceServerSockAddr = HifiSockAddr();
_hostname = QString(); _hostname = QString();
_sockAddr.clear(); _sockAddr.clear();
@ -139,7 +147,9 @@ void DomainHandler::setUUID(const QUuid& uuid) {
} }
} }
void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const QUuid& domainID) {
_pendingDomainID = domainID;
if (hostname != _hostname || _sockAddr.getPort() != port) { if (hostname != _hostname || _sockAddr.getPort() != port) {
// re-set the domain info so that auth information is reloaded // re-set the domain info so that auth information is reloaded
@ -149,9 +159,6 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
// set the new hostname // set the new hostname
_hostname = hostname; _hostname = hostname;
// FIXME - is this the right place???
_domainConnectionRefusals.clear();
qCDebug(networking) << "Updated domain hostname to" << _hostname; qCDebug(networking) << "Updated domain hostname to" << _hostname;
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
@ -174,14 +181,15 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
} }
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
if (id != _uuid) {
if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) {
// re-set the domain info to connect to new domain // re-set the domain info to connect to new domain
hardReset(); hardReset();
// refresh our ICE client UUID to something new // refresh our ICE client UUID to something new
_iceClientID = QUuid::createUuid(); _iceClientID = QUuid::createUuid();
_iceDomainID = id; _pendingDomainID = id;
HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr; HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr;
replaceableSockAddr->~HifiSockAddr(); replaceableSockAddr->~HifiSockAddr();
@ -255,6 +263,7 @@ void DomainHandler::setIsConnected(bool isConnected) {
// we've connected to new domain - time to ask it for global settings // we've connected to new domain - time to ask it for global settings
requestDomainSettings(); requestDomainSettings();
} else { } else {
emit disconnectedFromDomain(); emit disconnectedFromDomain();
} }
@ -305,6 +314,9 @@ void DomainHandler::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> me
qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr; qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr;
if (getIP().isNull()) { if (getIP().isNull()) {
// we're hearing back from this domain-server, no need to refresh API information
_apiRefreshTimer.stop();
// for now we're unsafely assuming this came back from the domain // for now we're unsafely assuming this came back from the domain
if (senderSockAddr == _icePeer.getLocalSocket()) { if (senderSockAddr == _icePeer.getLocalSocket()) {
qCDebug(networking) << "Connecting to domain using local socket"; qCDebug(networking) << "Connecting to domain using local socket";
@ -333,17 +345,20 @@ void DomainHandler::processDTLSRequirementPacket(QSharedPointer<ReceivedMessage>
void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> message) { void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> message) {
if (_icePeer.hasSockets()) { if (_icePeer.hasSockets()) {
qDebug() << "Received an ICE peer packet for domain-server but we already have sockets. Not processing."; qDebug() << "Received an ICE peer packet for domain-server but we already have sockets. Not processing.";
// bail on processing this packet if our ice peer doesn't have sockets // bail on processing this packet if our ice peer already has sockets
return; return;
} }
// start or restart the API refresh timer now that we have new information
_apiRefreshTimer.start();
QDataStream iceResponseStream(message->getMessage()); QDataStream iceResponseStream(message->getMessage());
iceResponseStream >> _icePeer; iceResponseStream >> _icePeer;
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation); DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation);
if (_icePeer.getUUID() != _iceDomainID) { if (_icePeer.getUUID() != _pendingDomainID) {
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
_icePeer.reset(); _icePeer.reset();
} else { } else {
@ -373,6 +388,9 @@ bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) {
} }
void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) { void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
// we're hearing from this domain-server, don't need to refresh API info
_apiRefreshTimer.stop();
// Read deny reason from packet // Read deny reason from packet
uint8_t reasonCodeWire; uint8_t reasonCodeWire;

View file

@ -59,7 +59,7 @@ public:
const QUuid& getAssignmentUUID() const { return _assignmentUUID; } const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
const QUuid& getICEDomainID() const { return _iceDomainID; } const QUuid& getPendingDomainID() const { return _pendingDomainID; }
const QUuid& getICEClientID() const { return _iceClientID; } const QUuid& getICEClientID() const { return _iceClientID; }
@ -76,7 +76,6 @@ public:
void requestDomainSettings(); void requestDomainSettings();
const QJsonObject& getSettingsObject() const { return _settingsObject; } const QJsonObject& getSettingsObject() const { return _settingsObject; }
void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; } void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; }
const QString& getPendingPath() { return _pendingPath; } const QString& getPendingPath() { return _pendingPath; }
void clearPendingPath() { _pendingPath.clear(); } void clearPendingPath() { _pendingPath.clear(); }
@ -94,7 +93,7 @@ public:
}; };
public slots: public slots:
void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT); void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid());
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
void processSettingsPacketList(QSharedPointer<ReceivedMessage> packetList); void processSettingsPacketList(QSharedPointer<ReceivedMessage> packetList);
@ -136,11 +135,11 @@ private:
HifiSockAddr _sockAddr; HifiSockAddr _sockAddr;
QUuid _assignmentUUID; QUuid _assignmentUUID;
QUuid _connectionToken; QUuid _connectionToken;
QUuid _iceDomainID; QUuid _pendingDomainID; // ID of domain being connected to, via ICE or direct connection
QUuid _iceClientID; QUuid _iceClientID;
HifiSockAddr _iceServerSockAddr; HifiSockAddr _iceServerSockAddr;
NetworkPeer _icePeer; NetworkPeer _icePeer;
bool _isConnected; bool _isConnected { false };
QJsonObject _settingsObject; QJsonObject _settingsObject;
QString _pendingPath; QString _pendingPath;
QTimer _settingsTimer; QTimer _settingsTimer;
@ -148,6 +147,8 @@ private:
QStringList _domainConnectionRefusals; QStringList _domainConnectionRefusals;
bool _hasCheckedForAccessToken { false }; bool _hasCheckedForAccessToken { false };
int _connectionDenialsSinceKeypairRegen { 0 }; int _connectionDenialsSinceKeypairRegen { 0 };
QTimer _apiRefreshTimer;
}; };
#endif // hifi_DomainHandler_h #endif // hifi_DomainHandler_h

View file

@ -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);

View file

@ -104,11 +104,11 @@ public:
const QUuid& getSessionUUID() const { return _sessionUUID; } const QUuid& getSessionUUID() const { return _sessionUUID; }
void setSessionUUID(const QUuid& sessionUUID); void setSessionUUID(const QUuid& sessionUUID);
bool isAllowedEditor() const { return _isAllowedEditor; } void setPermissions(const NodePermissions& newPermissions);
void setIsAllowedEditor(bool isAllowedEditor); bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; }
bool getThisNodeCanRez() const { return _thisNodeCanRez; } bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
void setThisNodeCanRez(bool canRez); bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; }
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
QUdpSocket& getDTLSSocket(); QUdpSocket& getDTLSSocket();
@ -137,7 +137,7 @@ public:
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
bool isAllowedEditor = false, bool canRez = false, const NodePermissions& permissions = DEFAULT_AGENT_PERMISSIONS,
const QUuid& connectionSecret = QUuid()); const QUuid& connectionSecret = QUuid());
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
@ -254,6 +254,8 @@ signals:
void isAllowedEditorChanged(bool isAllowedEditor); void isAllowedEditorChanged(bool isAllowedEditor);
void canRezChanged(bool canRez); void canRezChanged(bool canRez);
void canRezTmpChanged(bool canRezTmp);
void canWriteAssetsChanged(bool canWriteAssets);
protected slots: protected slots:
void connectedForLocalSocketTest(); void connectedForLocalSocketTest();
@ -300,8 +302,7 @@ protected:
int _numCollectedBytes; int _numCollectedBytes;
QElapsedTimer _packetStatTimer; QElapsedTimer _packetStatTimer;
bool _isAllowedEditor { false }; NodePermissions _permissions;
bool _thisNodeCanRez;
QPointer<QTimer> _initialSTUNTimer; QPointer<QTimer> _initialSTUNTimer;

View file

@ -184,6 +184,11 @@ void NLPacket::setType(PacketType type) {
writeTypeAndVersion(); writeTypeAndVersion();
} }
void NLPacket::setVersion(PacketVersion version) {
_version = version;
writeTypeAndVersion();
}
void NLPacket::readType() { void NLPacket::readType() {
_type = NLPacket::typeInHeader(*this); _type = NLPacket::typeInHeader(*this);
} }

View file

@ -65,6 +65,7 @@ public:
void setType(PacketType type); void setType(PacketType type);
PacketVersion getVersion() const { return _version; } PacketVersion getVersion() const { return _version; }
void setVersion(PacketVersion version);
const QUuid& getSourceID() const { return _sourceID; } const QUuid& getSourceID() const { return _sourceID; }

View file

@ -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;
} }

View file

@ -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*)

View file

@ -50,7 +50,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
// handle domain change signals from AddressManager // handle domain change signals from AddressManager
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired, connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired,
&_domainHandler, &DomainHandler::setHostnameAndPort); &_domainHandler, &DomainHandler::setSocketAndID);
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID, connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID,
&_domainHandler, &DomainHandler::setIceServerHostnameAndID); &_domainHandler, &DomainHandler::setIceServerHostnameAndID);
@ -250,7 +250,6 @@ void NodeList::sendDomainServerCheckIn() {
qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in."; qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
handleICEConnectionToDomainServer(); handleICEConnectionToDomainServer();
} else if (!_domainHandler.getIP().isNull()) { } else if (!_domainHandler.getIP().isNull()) {
bool isUsingDTLS = false;
PacketType domainPacketType = !_domainHandler.isConnected() PacketType domainPacketType = !_domainHandler.isConnected()
? PacketType::DomainConnectRequest : PacketType::DomainListRequest; ? PacketType::DomainConnectRequest : PacketType::DomainListRequest;
@ -292,12 +291,18 @@ void NodeList::sendDomainServerCheckIn() {
return; return;
} }
auto packetVersion = (domainPacketType == PacketType::DomainConnectRequest) ? _domainConnectRequestVersion : 0; auto domainPacket = NLPacket::create(domainPacketType);
auto domainPacket = NLPacket::create(domainPacketType, -1, false, false, packetVersion);
QDataStream packetStream(domainPacket.get()); QDataStream packetStream(domainPacket.get());
if (domainPacketType == PacketType::DomainConnectRequest) { if (domainPacketType == PacketType::DomainConnectRequest) {
#if (PR_BUILD || DEV_BUILD)
if (_shouldSendNewerVersion) {
domainPacket->setVersion(versionForPacketType(domainPacketType) + 1);
}
#endif
QUuid connectUUID; QUuid connectUUID;
if (!_domainHandler.getAssignmentUUID().isNull()) { if (!_domainHandler.getAssignmentUUID().isNull()) {
@ -315,18 +320,14 @@ void NodeList::sendDomainServerCheckIn() {
packetStream << connectUUID; packetStream << connectUUID;
// include the protocol version signature in our connect request // include the protocol version signature in our connect request
if (_domainConnectRequestVersion >= static_cast<PacketVersion>(DomainConnectRequestVersion::HasProtocolVersions)) { QByteArray protocolVersionSig = protocolVersionsSignature();
QByteArray protocolVersionSig = protocolVersionsSignature(); packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size());
packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size());
}
} }
// pack our data to send to the domain-server including // pack our data to send to the domain-server including
// the hostname information (so the domain-server can see which place name we came in on) // the hostname information (so the domain-server can see which place name we came in on)
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
if (_domainConnectRequestVersion >= static_cast<PacketVersion>(DomainConnectRequestVersion::HasHostname)) { packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
}
if (!_domainHandler.isConnected()) { if (!_domainHandler.isConnected()) {
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
@ -341,9 +342,7 @@ void NodeList::sendDomainServerCheckIn() {
flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn); flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn);
if (!isUsingDTLS) { sendPacket(std::move(domainPacket), _domainHandler.getSockAddr());
sendPacket(std::move(domainPacket), _domainHandler.getSockAddr());
}
if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
@ -462,7 +461,7 @@ void NodeList::handleICEConnectionToDomainServer() {
LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(), LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(),
_domainHandler.getICEClientID(), _domainHandler.getICEClientID(),
_domainHandler.getICEDomainID()); _domainHandler.getPendingDomainID());
} }
} }
@ -475,7 +474,7 @@ void NodeList::pingPunchForDomainServer() {
if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) { if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) {
qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID"
<< uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); << uuidStringWithoutCurlyBraces(_domainHandler.getPendingDomainID());
} else { } else {
if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) {
// if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat
@ -543,13 +542,10 @@ void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message)
packetStream >> newUUID; packetStream >> newUUID;
setSessionUUID(newUUID); setSessionUUID(newUUID);
quint8 isAllowedEditor; // pull the permissions/right/privileges for this node out of the stream
packetStream >> isAllowedEditor; NodePermissions newPermissions;
setIsAllowedEditor((bool) isAllowedEditor); packetStream >> newPermissions;
setPermissions(newPermissions);
quint8 thisNodeCanRez;
packetStream >> thisNodeCanRez;
setThisNodeCanRez((bool) thisNodeCanRez);
// pull each node in the packet // pull each node in the packet
while (packetStream.device()->pos() < message->getSize()) { while (packetStream.device()->pos() < message->getSize()) {
@ -577,10 +573,9 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
qint8 nodeType; qint8 nodeType;
QUuid nodeUUID, connectionUUID; QUuid nodeUUID, connectionUUID;
HifiSockAddr nodePublicSocket, nodeLocalSocket; HifiSockAddr nodePublicSocket, nodeLocalSocket;
bool isAllowedEditor; NodePermissions permissions;
bool canRez;
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> isAllowedEditor >> canRez; packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> permissions;
// if the public socket address is 0 then it's reachable at the same IP // if the public socket address is 0 then it's reachable at the same IP
// as the domain server // as the domain server
@ -591,8 +586,7 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
packetStream >> connectionUUID; packetStream >> connectionUUID;
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket,
nodeLocalSocket, isAllowedEditor, canRez, nodeLocalSocket, permissions, connectionUUID);
connectionUUID);
} }
void NodeList::sendAssignment(Assignment& assignment) { void NodeList::sendAssignment(Assignment& assignment) {

View file

@ -68,9 +68,6 @@ public:
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
/// downgrades the DomainConnnectRequest PacketVersion to attempt to probe for older domain servers
void downgradeDomainServerCheckInVersion() { _domainConnectRequestVersion--; }
public slots: public slots:
void reset(); void reset();
void sendDomainServerCheckIn(); void sendDomainServerCheckIn();
@ -88,8 +85,9 @@ public slots:
void processICEPingPacket(QSharedPointer<ReceivedMessage> message); void processICEPingPacket(QSharedPointer<ReceivedMessage> message);
void resetDomainServerCheckInVersion() #if (PR_BUILD || DEV_BUILD)
{ _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); } void toggleSendNewerDSConnectVersion(bool shouldSendNewerVersion) { _shouldSendNewerVersion = shouldSendNewerVersion; }
#endif
signals: signals:
void limitOfSilentDomainCheckInsReached(); void limitOfSilentDomainCheckInsReached();
@ -105,6 +103,7 @@ private slots:
void pingPunchForDomainServer(); void pingPunchForDomainServer();
void sendKeepAlivePings(); void sendKeepAlivePings();
private: private:
NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile
NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
@ -130,7 +129,9 @@ private:
bool _isShuttingDown { false }; bool _isShuttingDown { false };
QTimer _keepAlivePingTimer; QTimer _keepAlivePingTimer;
PacketVersion _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); #if (PR_BUILD || DEV_BUILD)
bool _shouldSendNewerVersion { false };
#endif
}; };
#endif // hifi_NodeList_h #endif // hifi_NodeList_h

View 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();
}

View 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

View file

@ -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;
} }

View file

@ -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

View file

@ -1863,9 +1863,9 @@ bool Octree::readJSONFromStream(unsigned long streamLength, QDataStream& inputSt
QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer); QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer);
QVariant asVariant = asDocument.toVariant(); QVariant asVariant = asDocument.toVariant();
QVariantMap asMap = asVariant.toMap(); QVariantMap asMap = asVariant.toMap();
readFromMap(asMap); bool success = readFromMap(asMap);
delete[] rawData; delete[] rawData;
return true; return success;
} }
void Octree::writeToFile(const char* fileName, OctreeElementPointer element, QString persistAsFileType) { void Octree::writeToFile(const char* fileName, OctreeElementPointer element, QString persistAsFileType) {

View file

@ -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;

View file

@ -894,30 +894,24 @@ function MyController(hand) {
var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined); var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined);
var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES);
if (grabData) { if (grabData) {
var hotspotPos = grabProps.position;
// does this entity have an attach point? // does this entity have an attach point?
var wearableData = getEntityCustomData("wearable", entities[i], undefined); var wearableData = getEntityCustomData("wearable", entities[i], undefined);
if (wearableData) { if (wearableData && wearableData.joints) {
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand";
if (wearableData[handJointName]) { if (wearableData.joints[handJointName]) {
// draw the hotspot around the attach point. // draw the hotspot
hotspotPos = wearableData[handJointName][0]; this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", {
position: grabProps.position,
size: 0.2,
color: { red: 90, green: 255, blue: 90 },
alpha: 0.7,
solid: true,
visible: true,
ignoreRayIntersection: false,
drawInFront: false
}));
} }
} }
// draw a hotspot!
this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", {
position: hotspotPos,
size: 0.2,
color: { red: 90, green: 255, blue: 90 },
alpha: 0.7,
solid: true,
visible: true,
ignoreRayIntersection: false,
drawInFront: false
}));
} }
} }
}; };

View file

@ -1222,7 +1222,7 @@ function handeMenuEvent(menuItem) {
Window.alert("No entities have been selected."); Window.alert("No entities have been selected.");
} else { } else {
var filename = "entities__" + Window.location.hostname + ".svo.json"; var filename = "entities__" + Window.location.hostname + ".svo.json";
filename = Window.save("Select where to save", filename, "*.json") filename = Window.save("Select Where to Save", filename, "*.json")
if (filename) { if (filename) {
var success = Clipboard.exportEntities(filename, selectionManager.selections); var success = Clipboard.exportEntities(filename, selectionManager.selections);
if (!success) { if (!success) {
@ -1234,7 +1234,7 @@ function handeMenuEvent(menuItem) {
var importURL = null; var importURL = null;
if (menuItem == "Import Entities") { if (menuItem == "Import Entities") {
var fullPath = Window.browse("Select models to import", "", "*.json"); var fullPath = Window.browse("Select Model to Import", "", "*.json");
if (fullPath) { if (fullPath) {
importURL = "file:///" + fullPath; importURL = "file:///" + fullPath;
} }