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