mirror of
https://github.com/lubosz/overte.git
synced 2025-04-08 01:22:08 +02:00
Merge pull request #8009 from sethalves/permissions-grid
Permissions grid
This commit is contained in:
commit
27c6df660c
28 changed files with 1200 additions and 413 deletions
|
@ -286,8 +286,8 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<ReceivedMes
|
|||
|
||||
if (!senderID.isNull()) {
|
||||
// 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.3,
|
||||
"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",
|
||||
|
@ -157,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",
|
||||
|
@ -187,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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -218,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",
|
||||
|
@ -302,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",
|
||||
|
@ -353,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",
|
||||
|
@ -380,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",
|
||||
|
@ -479,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",
|
||||
|
@ -501,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 {
|
||||
|
@ -65,53 +65,53 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
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);
|
||||
|
@ -120,18 +120,74 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::updateNodePermissions() {
|
||||
// If the permissions were changed on the domain-server webpage (and nothing else was), a restart isn't required --
|
||||
// we reprocess the permissions map and update the nodes here. The node list is frequently sent out to all
|
||||
// the connected nodes, so these changes are propagated to other nodes.
|
||||
|
||||
QList<SharedNodePointer> nodesToKill;
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->eachNodeBreakable([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
|
||||
QString username = node->getPermissions().getUserName();
|
||||
NodePermissions userPerms(username);
|
||||
|
||||
if (node->getPermissions().isAssignment) {
|
||||
// this node is an assignment-client
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
} else {
|
||||
// this node is an agent
|
||||
userPerms.setAll(false);
|
||||
|
||||
const QHostAddress& addr = node->getLocalSocket().getAddress();
|
||||
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||
addr == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
} else {
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
} else {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node->setPermissions(userPerms);
|
||||
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
qDebug() << "node" << node->getUUID() << "no longer has permission to connect.";
|
||||
// hang up on this node
|
||||
nodesToKill << node;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
foreach (auto node, nodesToKill) {
|
||||
emit killNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
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"
|
||||
|
@ -146,124 +202,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
|
||||
userPerms.setUserName(username);
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
|
@ -282,24 +313,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;
|
||||
}
|
||||
|
||||
|
@ -307,11 +337,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();
|
||||
|
@ -322,15 +352,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;
|
||||
}
|
||||
|
||||
|
@ -340,21 +370,21 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
|
||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||
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()),
|
||||
|
@ -362,29 +392,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.";
|
||||
|
@ -399,86 +429,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
|
||||
|
@ -489,15 +468,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);
|
||||
|
@ -505,22 +489,22 @@ 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());
|
||||
}
|
||||
|
@ -536,16 +520,16 @@ void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr&
|
|||
DomainHandler::ConnectionRefusedReason::ProtocolMismatch);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
||||
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,
|
||||
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;
|
||||
|
@ -553,7 +537,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);
|
||||
}
|
||||
|
@ -561,20 +545,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);
|
||||
}
|
||||
|
@ -582,33 +566,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);
|
||||
}
|
||||
|
@ -619,24 +603,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;
|
||||
|
@ -646,18 +630,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());
|
||||
|
|
|
@ -53,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:
|
||||
|
@ -68,11 +72,7 @@ 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);
|
||||
|
|
|
@ -101,6 +101,13 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
// 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
|
||||
|
@ -795,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);
|
||||
|
||||
|
@ -1088,11 +1094,12 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& 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
|
||||
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||
domainObject[RESTRICTED_ACCESS_FLAG] =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
||||
// 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";
|
||||
|
||||
// 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";
|
||||
|
@ -2102,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;
|
||||
|
||||
|
|
|
@ -114,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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4292,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 {
|
||||
|
@ -4788,7 +4788,7 @@ void Application::toggleRunningScriptsWidget() const {
|
|||
}
|
||||
|
||||
void Application::toggleAssetServerWidget(QString filePath) {
|
||||
if (!DependencyManager::get<NodeList>()->getThisNodeCanRez()) {
|
||||
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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*)
|
||||
|
|
|
@ -526,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;
|
||||
|
@ -542,14 +542,9 @@ 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
|
||||
packetStream >> _permissions;
|
||||
|
||||
quint8 thisNodeCanRez;
|
||||
packetStream >> thisNodeCanRez;
|
||||
setThisNodeCanRez((bool) thisNodeCanRez);
|
||||
|
||||
// pull each node in the packet
|
||||
while (packetStream.device()->pos() < message->getSize()) {
|
||||
parseNodeFromPacketStream(packetStream);
|
||||
|
@ -576,10 +571,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
|
||||
|
@ -590,8 +584,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) {
|
||||
|
|
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
|
||||
|
|
|
@ -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