Merge pull request #8197 from sethalves/groups

Groups
This commit is contained in:
Stephen Birarda 2016-08-02 14:34:53 -07:00 committed by GitHub
commit d15ac5761b
20 changed files with 1819 additions and 362 deletions

View file

@ -384,13 +384,13 @@
"name": "standard_permissions", "name": "standard_permissions",
"type": "table", "type": "table",
"label": "Domain-Wide User Permissions", "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 domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.", "help": "Indicate which types of users 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 domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"caption": "Standard Permissions", "caption": "Standard Permissions",
"can_add_new_rows": false, "can_add_new_rows": false,
"groups": [ "groups": [
{ {
"label": "User / Group", "label": "Type of User",
"span": 1 "span": 1
}, },
{ {
@ -409,7 +409,7 @@
"label": "Connect", "label": "Connect",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": true "default": false
}, },
{ {
"name": "id_can_adjust_locks", "name": "id_can_adjust_locks",
@ -451,6 +451,193 @@
"non-deletable-row-key": "permissions_id", "non-deletable-row-key": "permissions_id",
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"] "non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
}, },
{
"name": "group_permissions",
"type": "table",
"caption": "Permissions for Users in Groups",
"categorize_by_key": "permissions_id",
"can_add_new_categories": true,
"can_add_new_rows": false,
"new_category_placeholder": "Add Group",
"new_category_message": "Save and reload to see ranks",
"groups": [
{
"label": "Rank",
"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 users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 6
}
],
"columns": [
{
"name": "permissions_id",
"label": "Group Name",
"readonly": true,
"hidden": true
},
{
"name": "rank_id",
"label": "Rank ID",
"readonly": true,
"hidden": true
},
{
"name": "rank_order",
"label": "Rank Order",
"readonly": true,
"hidden": true
},
{
"name": "rank_name",
"label": "",
"readonly": true
},
{
"name": "group_id",
"label": "Group ID",
"readonly": true,
"hidden": true
},
{
"name": "id_can_connect",
"label": "Connect",
"type": "checkbox",
"editable": true,
"default": false
},
{
"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
}
]
},
{
"name": "group_forbiddens",
"type": "table",
"caption": "Permissions Denied to Users in Groups",
"categorize_by_key": "permissions_id",
"can_add_new_categories": true,
"can_add_new_rows": false,
"new_category_placeholder": "Add Blacklist Group",
"new_category_message": "Save and reload to see ranks",
"groups": [
{
"label": "Rank",
"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 users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 6
}
],
"columns": [
{
"name": "permissions_id",
"label": "Group Name",
"hidden": true
},
{
"name": "rank_id",
"label": "Rank ID",
"hidden": true
},
{
"name": "rank_order",
"label": "Rank Order",
"hidden": true
},
{
"name": "rank_name",
"label": "",
"readonly": true
},
{
"name": "group_id",
"label": "Group ID",
"readonly": true,
"hidden": true
},
{
"name": "id_can_connect",
"label": "Connect",
"type": "checkbox",
"editable": true,
"default": false
},
{
"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
}
]
},
{ {
"name": "permissions", "name": "permissions",
"type": "table", "type": "table",
@ -459,11 +646,11 @@
"groups": [ "groups": [
{ {
"label": "User / Group", "label": "User",
"span": 1 "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 domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>", "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 domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 6 "span": 6
} }
], ],
@ -478,7 +665,7 @@
"label": "Connect", "label": "Connect",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": true "default": false
}, },
{ {
"name": "id_can_adjust_locks", "name": "id_can_adjust_locks",

View file

@ -3,6 +3,10 @@ body {
padding-bottom: 30px; padding-bottom: 30px;
} }
[hidden] {
display: none !important;
}
.table-lead .lead-line { .table-lead .lead-line {
background-color: black; background-color: black;
} }
@ -20,7 +24,9 @@ body {
top: 40px; top: 40px;
} }
.table .value-row td, .table .inputs td { .table .value-row td,
.table .value-category td,
.table .inputs td {
vertical-align: middle; vertical-align: middle;
} }
@ -31,6 +37,31 @@ body {
margin-right: auto; margin-right: auto;
} }
.value-category:not(.inputs) {
font-weight: bold;
background: #f5f5f5;
}
.table .value-category [message]::after {
content: attr(message);
font-style: italic;
font-weight: normal;
}
.table .value-row.contracted,
.table .inputs.contracted {
display: none;
}
.toggle-category {
cursor: pointer;
}
.toggle-category-icon {
padding: 4px;
margin-right: 8px;
}
.glyphicon-remove { .glyphicon-remove {
font-size: 24px; font-size: 24px;
} }
@ -133,7 +164,7 @@ table .headers + .headers td {
color: #222; color: #222;
} }
table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td { #security table .headers td + td {
text-align: center; text-align: center;
} }

View file

@ -5,10 +5,20 @@ var Settings = {
TRIGGER_CHANGE_CLASS: 'trigger-change', TRIGGER_CHANGE_CLASS: 'trigger-change',
DATA_ROW_CLASS: 'value-row', DATA_ROW_CLASS: 'value-row',
DATA_COL_CLASS: 'value-col', DATA_COL_CLASS: 'value-col',
DATA_CATEGORY_CLASS: 'value-category',
ADD_ROW_BUTTON_CLASS: 'add-row', ADD_ROW_BUTTON_CLASS: 'add-row',
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row', ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
DEL_ROW_BUTTON_CLASS: 'del-row', DEL_ROW_BUTTON_CLASS: 'del-row',
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row', DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row',
ADD_CATEGORY_BUTTON_CLASS: 'add-category',
ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category',
TOGGLE_CATEGORY_COLUMN_CLASS: 'toggle-category',
TOGGLE_CATEGORY_SPAN_CLASS: 'toggle-category-icon',
TOGGLE_CATEGORY_SPAN_CLASSES: 'glyphicon toggle-category-icon',
TOGGLE_CATEGORY_EXPANDED_CLASS: 'glyphicon-triangle-bottom',
TOGGLE_CATEGORY_CONTRACTED_CLASS: 'glyphicon-triangle-right',
DEL_CATEGORY_BUTTON_CLASS: 'del-category',
DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category',
MOVE_UP_BUTTON_CLASS: 'move-up', MOVE_UP_BUTTON_CLASS: 'move-up',
MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up', MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
MOVE_DOWN_BUTTON_CLASS: 'move-down', MOVE_DOWN_BUTTON_CLASS: 'move-down',
@ -35,7 +45,7 @@ var viewHelpers = {
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "' data-keypath='" + keypath + "'>"; form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "' data-keypath='" + keypath + "'>";
setting_value = _(values).valueForKeyPath(keypath); setting_value = _(values).valueForKeyPath(keypath);
if (typeof setting_value == 'undefined' || setting_value === null) { if (_.isUndefined(setting_value) || _.isNull(setting_value)) {
if (_.has(setting, 'default')) { if (_.has(setting, 'default')) {
setting_value = setting.default; setting_value = setting.default;
} else { } else {
@ -49,11 +59,11 @@ var viewHelpers = {
} }
function common_attrs(extra_classes) { function common_attrs(extra_classes) {
extra_classes = (typeof extra_classes !== 'undefined' ? extra_classes : ""); extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : "");
return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '')
+ " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='" + " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='"
+ setting.name + "' name='" + keypath + "' " + setting.name + "' name='" + keypath + "' "
+ "id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath) + "'"; + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'";
} }
if (setting.type === 'checkbox') { if (setting.type === 'checkbox') {
@ -162,19 +172,31 @@ $(document).ready(function(){
}); });
$('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){
addTableRow(this); addTableRow($(this).closest('tr'));
}); });
$('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
deleteTableRow(this); deleteTableRow($(this).closest('tr'));
});
$('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){
addTableCategory($(this).closest('tr'));
});
$('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){
deleteTableCategory($(this).closest('tr'));
});
$('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){
toggleTableCategory($(this).closest('tr'));
}); });
$('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){
moveTableRow(this, true); moveTableRow($(this).closest('tr'), true);
}); });
$('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){ $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){
moveTableRow(this, false); moveTableRow($(this).closest('tr'), false);
}); });
$('#' + Settings.FORM_ID).on('keyup', function(e){ $('#' + Settings.FORM_ID).on('keyup', function(e){
@ -196,10 +218,11 @@ $(document).ready(function(){
} }
if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click() sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click();
sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click();
// set focus to the first input in the new row // set focus to the first input in the new row
$target.closest('table').find('tr.inputs input:first').focus() $target.closest('table').find('tr.inputs input:first').focus();
} }
} }
@ -629,7 +652,7 @@ function setupPlacesTable() {
label: 'Places', label: 'Places',
html_id: Settings.PLACES_TABLE_ID, html_id: Settings.PLACES_TABLE_ID,
help: "The following places currently point to this domain.</br>To point places to this domain, " help: "The following places currently point to this domain.</br>To point places to this domain, "
+ " go to the <a href='https://metaverse.highfidelity.com/user/places'>My Places</a> " + " go to the <a href='" + Settings.METAVERSE_URL + "/user/places'>My Places</a> "
+ "page in your High Fidelity Metaverse account.", + "page in your High Fidelity Metaverse account.",
read_only: true, read_only: true,
columns: [ columns: [
@ -771,7 +794,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
modal_buttons["success"] = { modal_buttons["success"] = {
label: 'Create new domain', label: 'Create new domain',
callback: function() { callback: function() {
window.open("https://metaverse.highfidelity.com/user/domains", '_blank'); window.open(Settings.METAVERSE_URL + "/user/domains", '_blank');
} }
} }
modal_body = "<p>You do not have any domains in your High Fidelity account." + modal_body = "<p>You do not have any domains in your High Fidelity account." +
@ -922,8 +945,9 @@ $('body').on('click', '.save-button', function(e){
function makeTable(setting, keypath, setting_value, isLocked) { function makeTable(setting, keypath, setting_value, isLocked) {
var isArray = !_.has(setting, 'key'); var isArray = !_.has(setting, 'key');
var isHash = !isArray; var categoryKey = setting.categorize_by_key;
var isCategorized = !!categoryKey && isArray;
if (!isArray && setting.can_order) { if (!isArray && setting.can_order) {
setting.can_order = false; setting.can_order = false;
} }
@ -937,9 +961,10 @@ function makeTable(setting, keypath, setting_value, isLocked) {
var nonDeletableRowKey = setting["non-deletable-row-key"]; var nonDeletableRowKey = setting["non-deletable-row-key"];
var nonDeletableRowValues = setting["non-deletable-row-values"]; var nonDeletableRowValues = setting["non-deletable-row-values"];
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' " +
+ "' name='" + keypath + "' id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath) "data-short-name='" + setting.name + "' name='" + keypath + "' " +
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"; "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " +
"data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
if (setting.caption) { if (setting.caption) {
html += "<caption>" + setting.caption + "</caption>" html += "<caption>" + setting.caption + "</caption>"
@ -972,24 +997,43 @@ function makeTable(setting, keypath, setting_value, isLocked) {
html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key
} }
var numVisibleColumns = 0;
_.each(setting.columns, function(col) { _.each(setting.columns, function(col) {
html += "<td class='data " + (col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data if (!col.hidden) numVisibleColumns++;
html += "<td " + (col.hidden ? "style='display: none;'" : "") + "class='data " +
(col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
}) })
if (!isLocked && !setting.read_only) { if (!isLocked && !setting.read_only) {
if (setting.can_order) { if (setting.can_order) {
numVisibleColumns++;
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>"; "'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
} }
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>" numVisibleColumns++;
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>";
} }
// populate rows in the table from existing values // populate rows in the table from existing values
var row_num = 1; var row_num = 1;
if (keypath.length > 0 && _.size(setting_value) > 0) { if (keypath.length > 0 && _.size(setting_value) > 0) {
var rowIsObject = setting.columns.length > 1;
_.each(setting_value, function(row, rowIndexOrName) { _.each(setting_value, function(row, rowIndexOrName) {
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + ">" var categoryPair = {};
var categoryValue = "";
if (isCategorized) {
categoryValue = rowIsObject ? row[categoryKey] : row;
categoryPair[categoryKey] = categoryValue;
if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) {
html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, "");
}
}
html += "<tr class='" + Settings.DATA_ROW_CLASS + "' " +
(isCategorized ? ("data-category='" + categoryValue + "'") : "") + " " +
(isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + ">";
if (setting.numbered === true) { if (setting.numbered === true) {
html += "<td class='numbered'>" + row_num + "</td>" html += "<td class='numbered'>" + row_num + "</td>"
@ -1003,8 +1047,8 @@ function makeTable(setting, keypath, setting_value, isLocked) {
_.each(setting.columns, function(col) { _.each(setting.columns, function(col) {
var colValue, colName;
if (isArray) { if (isArray) {
rowIsObject = setting.columns.length > 1;
colValue = rowIsObject ? row[col.name] : row; colValue = rowIsObject ? row[col.name] : row;
colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : "");
} else { } else {
@ -1016,20 +1060,28 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|| (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1);
if (isArray && col.type === "checkbox" && col.editable) { if (isArray && col.type === "checkbox" && col.editable) {
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" html +=
+ "<input type='checkbox' class='form-control table-checkbox' " "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
+ "name='" + colName + "'" + (colValue ? " checked" : "") + " /></td>"; "<input type='checkbox' class='form-control table-checkbox' " +
"name='" + colName + "'" + (colValue ? " checked" : "") + "/>" +
"</td>";
} else if (isArray && col.type === "time" && col.editable) { } else if (isArray && col.type === "time" && col.editable) {
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" html +=
+ "<input type='time' class='form-control table-time' " "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
+ "name='" + colName + "' value='" + (colValue || col.default || "00:00") + "' /></td>"; "<input type='time' class='form-control table-time' name='" + colName + "' " +
"value='" + (colValue || col.default || "00:00") + "'/>" +
"</td>";
} else { } else {
// Use a hidden input so that the values are posted. // Use a hidden input so that the values are posted.
html += "<td class='" + Settings.DATA_COL_CLASS + "' name='" + colName + "'>" html +=
+ colValue + "<input type='hidden' name='" + colName + "' value='" + colValue + "'/></td>"; "<td class='" + Settings.DATA_COL_CLASS + "' " + (col.hidden ? "style='display: none;'" : "") +
"name='" + colName + "'>" +
colValue +
"<input type='hidden' name='" + colName + "' value='" + colValue + "'/>" +
"</td>";
} }
}) });
if (!isLocked && !setting.read_only) { if (!isLocked && !setting.read_only) {
if (setting.can_order) { if (setting.can_order) {
@ -1047,24 +1099,53 @@ function makeTable(setting, keypath, setting_value, isLocked) {
html += "</tr>" html += "</tr>"
if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) {
html += makeTableInputs(setting, categoryPair, categoryValue);
}
row_num++ row_num++
}); });
} }
// populate inputs in the table for new values // populate inputs in the table for new values
if (!isLocked && !setting.read_only && setting.can_add_new_rows) { if (!isLocked && !setting.read_only) {
html += makeTableInputs(setting) if (setting.can_add_new_categories) {
html += makeTableCategoryInput(setting, numVisibleColumns);
}
if (setting.can_add_new_rows || setting.can_add_new_categories) {
html += makeTableInputs(setting, {}, "");
}
} }
html += "</table>" html += "</table>"
return html; return html;
} }
function makeTableInputs(setting) { function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) {
var html = "<tr class='inputs'>" var html =
"<tr class='" + Settings.DATA_CATEGORY_CLASS + "' data-key='" + categoryKey + "' data-category='" + categoryValue + "'>" +
"<td colspan='" + (numVisibleColumns - 1) + "' class='" + Settings.TOGGLE_CATEGORY_COLUMN_CLASS + "'>" +
"<span class='" + Settings.TOGGLE_CATEGORY_SPAN_CLASSES + " " + Settings.TOGGLE_CATEGORY_EXPANDED_CLASS + "'></span>" +
"<span message='" + message + "'>" + categoryValue + "</span>" +
"</td>" +
((canRemove) ? (
"<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" +
"<a href='javascript:void(0);' class='" + Settings.DEL_CATEGORY_SPAN_CLASSES + "'></a>" +
"</td>"
) : (
"<td></td>"
)) +
"</tr>";
return html;
}
function makeTableInputs(setting, initialValues, categoryValue) {
var html = "<tr class='inputs'" + (setting.can_add_new_categories && !categoryValue ? " hidden" : "") + " " +
(categoryValue ? ("data-category='" + categoryValue + "'") : "") + " " +
(setting.categorize_by_key ? ("data-keep-field='" + setting.categorize_by_key + "'") : "") + ">";
if (setting.numbered === true) { if (setting.numbered === true) {
html += "<td class='numbered'></td>" html += "<td class='numbered'></td>";
} }
if (setting.key) { if (setting.key) {
@ -1074,15 +1155,21 @@ function makeTableInputs(setting) {
} }
_.each(setting.columns, function(col) { _.each(setting.columns, function(col) {
var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default;
if (col.type === "checkbox") { if (col.type === "checkbox") {
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" html +=
+ "<input type='checkbox' class='form-control table-checkbox' " "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
+ "name='" + col.name + "'" + (col.default ? " checked" : "") + "/></td>"; "<input type='checkbox' class='form-control table-checkbox' " +
"name='" + col.name + "'" + (defaultValue ? " checked" : "") + "/>" +
"</td>";
} else { } else {
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\ html +=
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\ "<td " + (col.hidden ? "style='display: none;'" : "") + " class='" + Settings.DATA_COL_CLASS + "' " +
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\ "name='" + col.name + "'>" +
</td>" "<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "' " +
"value='" + (defaultValue || "") + "' data-default='" + (defaultValue || "") + "'" +
(col.readonly ? " readonly" : "") + ">" +
"</td>";
} }
}) })
@ -1090,12 +1177,30 @@ function makeTableInputs(setting) {
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>" html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
} }
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
"'><a href='javascript:void(0);' class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></a></td>" "'><a href='javascript:void(0);' class='" + Settings.ADD_ROW_SPAN_CLASSES + "'></a></td>"
html += "</tr>" html += "</tr>"
return html return html
} }
function makeTableCategoryInput(setting, numVisibleColumns) {
var canAddRows = setting.can_add_new_rows;
var categoryKey = setting.categorize_by_key;
var placeholder = setting.new_category_placeholder || "";
var message = setting.new_category_message || "";
var html =
"<tr class='" + Settings.DATA_CATEGORY_CLASS + " inputs' data-can-add-rows='" + canAddRows + "' " +
"data-key='" + categoryKey + "' data-message='" + message + "'>" +
"<td colspan='" + (numVisibleColumns - 1) + "'>" +
"<input type='text' class='form-control' placeholder='" + placeholder + "'/>" +
"</td>" +
"<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" +
"<a href='javascript:void(0);' class='" + Settings.ADD_CATEGORY_SPAN_CLASSES + "'></a>" +
"</td>" +
"</tr>";
return html;
}
function badgeSidebarForDifferences(changedElement) { function badgeSidebarForDifferences(changedElement) {
// figure out which group this input is in // figure out which group this input is in
var panelParentID = changedElement.closest('.panel').attr('id'); var panelParentID = changedElement.closest('.panel').attr('id');
@ -1134,13 +1239,12 @@ function badgeSidebarForDifferences(changedElement) {
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue); $("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
} }
function addTableRow(add_glyphicon) { function addTableRow(row) {
var row = $(add_glyphicon).closest('tr') var table = row.parents('table');
var isArray = table.data('setting-type') === 'array';
var keepField = row.data("keep-field");
var table = row.parents('table') var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS);
var isArray = table.data('setting-type') === 'array'
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
if (!isArray) { if (!isArray) {
// Check key spaces // Check key spaces
@ -1257,10 +1361,12 @@ function addTableRow(add_glyphicon) {
} else { } else {
console.log("Unknown table element") console.log("Unknown table element")
} }
}) });
input_clone.find('input').each(function(){ input_clone.children('td').each(function () {
$(this).val($(this).attr('data-default')); if ($(this).attr("name") !== keepField) {
$(this).find("input").val($(this).attr('data-default'));
}
}); });
if (isArray) { if (isArray) {
@ -1272,44 +1378,132 @@ function addTableRow(add_glyphicon) {
badgeSidebarForDifferences($(table)) badgeSidebarForDifferences($(table))
row.parent().append(input_clone) row.after(input_clone)
} }
function deleteTableRow(delete_glyphicon) { function deleteTableRow($row) {
var row = $(delete_glyphicon).closest('tr') var $table = $row.closest('table');
var categoryName = $row.data("category");
var isArray = $table.data('setting-type') === 'array';
var table = $(row).closest('table') $row.empty();
var isArray = table.data('setting-type') === 'array'
row.empty();
if (!isArray) { if (!isArray) {
row.html("<input type='hidden' class='form-control' name='" $row.html("<input type='hidden' class='form-control' name='" + $row.attr('name') + "' data-changed='true' value=''>");
+ row.attr('name') + "' data-changed='true' value=''>");
} else { } else {
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) {
updateDataChangedForSiblingRows(row) // This is the last row of the category, so delete the header
$table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove();
}
if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
updateDataChangedForSiblingRows($row);
// this isn't the last row - we can just remove it // this isn't the last row - we can just remove it
row.remove() $row.remove();
} else { } else {
// this is the last row, we can't remove it completely since we need to post an empty array // this is the last row, we can't remove it completely since we need to post an empty array
$row
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS) .removeClass(Settings.DATA_ROW_CLASS)
row.addClass('empty-array-row') .removeClass(Settings.NEW_ROW_CLASS)
.removeAttr("data-category")
row.html("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '') .addClass('empty-array-row')
+ "' data-changed='true' value=''>"); .html("<input type='hidden' class='form-control' name='" + $table.attr("name").replace('[]', '') + "' " +
"data-changed='true' value=''>");
} }
} }
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeSidebarForDifferences($(table)) badgeSidebarForDifferences($table);
} }
function moveTableRow(move_glyphicon, move_up) { function addTableCategory($categoryInputRow) {
var row = $(move_glyphicon).closest('tr') var $input = $categoryInputRow.find("input").first();
var categoryValue = $input.prop("value");
if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) {
$categoryInputRow.addClass("has-warning");
setTimeout(function () {
$categoryInputRow.removeClass("has-warning");
}, 400);
return;
}
var $rowInput = $categoryInputRow.next(".inputs").clone();
if (!$rowInput) {
console.error("Error cloning inputs");
}
var canAddRows = $categoryInputRow.data("can-add-rows");
var message = $categoryInputRow.data("message");
var categoryKey = $categoryInputRow.data("key");
var width = 0;
$categoryInputRow
.children("td")
.each(function () {
width += $(this).prop("colSpan") || 1;
});
$input
.prop("value", "")
.focus();
$rowInput.find("td[name='" + categoryKey + "'] > input").first()
.prop("value", categoryValue);
$rowInput
.attr("data-category", categoryValue)
.addClass(Settings.NEW_ROW_CLASS);
var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message));
$newCategoryRow.addClass(Settings.NEW_ROW_CLASS);
$categoryInputRow
.before($newCategoryRow)
.before($rowInput);
if (canAddRows) {
$rowInput.removeAttr("hidden");
} else {
addTableRow($rowInput);
}
}
function deleteTableCategory($categoryHeaderRow) {
var categoryName = $categoryHeaderRow.data("category");
$categoryHeaderRow
.closest("table")
.find("tr[data-category='" + categoryName + "']")
.each(function () {
if ($(this).hasClass(Settings.DATA_ROW_CLASS)) {
deleteTableRow($(this));
} else {
$(this).remove();
}
});
}
function toggleTableCategory($categoryHeaderRow) {
var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first();
var categoryName = $categoryHeaderRow.data("category");
var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS);
if (wasExpanded) {
$icon
.addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS)
.removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS);
} else {
$icon
.addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS)
.removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS);
}
$categoryHeaderRow
.closest("table")
.find("tr[data-category='" + categoryName + "']")
.toggleClass("contracted", wasExpanded);
}
function moveTableRow(row, move_up) {
var table = $(row).closest('table') var table = $(row).closest('table')
var isArray = table.data('setting-type') === 'array' var isArray = table.data('setting-type') === 'array'
if (!isArray) { if (!isArray) {

View file

@ -120,6 +120,85 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
} }
} }
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername) {
NodePermissions userPerms;
userPerms.setAll(false);
if (isLocalUser) {
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: is local user, so:" << userPerms;
#endif
}
if (verifiedUsername.isEmpty()) {
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms;
#endif
} else {
userPerms.setID(verifiedUsername);
if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) {
userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername);
userPerms.setVerifiedUserName(verifiedUsername);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific user matches, so:" << userPerms;
#endif
} else {
userPerms.setVerifiedUserName(verifiedUsername);
// they are logged into metaverse, but we don't have specific permissions for them.
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: user is logged-into metaverse, so:" << userPerms;
#endif
// if this user is a friend of the domain-owner, give them friend's permissions
if (_domainOwnerFriends.contains(verifiedUsername)) {
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameFriends);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: user is friends with domain-owner, so:" << userPerms;
#endif
}
// if this user is a known member of a group, give them the implied permissions
foreach (QUuid groupID, _server->_settingsManager.getGroupIDs()) {
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
if (rankID != QUuid()) {
userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rankID);
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: user is in group:" << groupID << " rank:"
<< rank.name << "so:" << userPerms;
#endif
}
}
// if this user is a known member of a blacklist group, remove the implied permissions
foreach (QUuid groupID, _server->_settingsManager.getBlacklistGroupIDs()) {
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
if (rankID != QUuid()) {
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
if (rankID != QUuid()) {
userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rankID);
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: user is in blacklist group:" << groupID << " rank:" << rank.name
<< "so:" << userPerms;
#endif
}
}
}
}
}
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: final:" << userPerms;
#endif
return userPerms;
}
void DomainGatekeeper::updateNodePermissions() { void DomainGatekeeper::updateNodePermissions() {
// If the permissions were changed on the domain-server webpage (and nothing else was), a restart isn't required -- // 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 // we reprocess the permissions map and update the nodes here. The node list is frequently sent out to all
@ -129,40 +208,29 @@ void DomainGatekeeper::updateNodePermissions() {
auto limitedNodeList = DependencyManager::get<LimitedNodeList>(); auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){ limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
QString username = node->getPermissions().getUserName(); // the id and the username in NodePermissions will often be the same, but id is set before
NodePermissions userPerms(username); // authentication and verifiedUsername is only set once they user's key has been confirmed.
QString verifiedUsername = node->getPermissions().getVerifiedUserName();
NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0));
if (node->getPermissions().isAssignment) { if (node->getPermissions().isAssignment) {
// this node is an assignment-client // this node is an assignment-client
userPerms.isAssignment = true; userPerms.isAssignment = true;
userPerms.canAdjustLocks = true; userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
userPerms.canRezPermanentEntities = true; userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
userPerms.canRezTemporaryEntities = true; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
} else { } else {
// this node is an agent // this node is an agent
userPerms.setAll(false);
const QHostAddress& addr = node->getLocalSocket().getAddress(); const QHostAddress& addr = node->getLocalSocket().getAddress();
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() || bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
addr == QHostAddress::LocalHost); addr == QHostAddress::LocalHost);
if (isLocalUser) { userPerms = setPermissionsForUser(isLocalUser, verifiedUsername);
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); node->setPermissions(userPerms);
if (!userPerms.canConnectToDomain) { if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
qDebug() << "node" << node->getUUID() << "no longer has permission to connect."; qDebug() << "node" << node->getUUID() << "no longer has permission to connect.";
// hang up on this node // hang up on this node
nodesToKill << node; nodesToKill << node;
@ -215,12 +283,13 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting // cleanup the PendingAssignedNodeData for this assignment now that it's connecting
_pendingAssignedNodes.erase(it); _pendingAssignedNodes.erase(it);
// always allow assignment clients to create and destroy entities
NodePermissions userPerms; NodePermissions userPerms;
userPerms.isAssignment = true; userPerms.isAssignment = true;
userPerms.canAdjustLocks = true; userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
userPerms.canRezPermanentEntities = true; // always allow assignment clients to create and destroy entities
userPerms.canRezTemporaryEntities = true; userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
newNode->setPermissions(userPerms); newNode->setPermissions(userPerms);
return newNode; return newNode;
} }
@ -234,64 +303,58 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
auto limitedNodeList = DependencyManager::get<LimitedNodeList>(); auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
// start with empty permissions // start with empty permissions
NodePermissions userPerms(username); NodePermissions userPerms(NodePermissionsKey(username, 0));
userPerms.setAll(false); userPerms.setAll(false);
// check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection // check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress(); QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
bool isLocalUser = bool isLocalUser =
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost); (senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
if (isLocalUser) {
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
qDebug() << "user-permissions: is local user, so:" << userPerms;
}
if (!username.isEmpty() && usernameSignature.isEmpty()) { QString verifiedUsername; // if this remains empty, consider this an anonymous connection attempt
// user is attempting to prove their identity to us, but we don't have enough information if (!username.isEmpty()) {
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr); if (usernameSignature.isEmpty()) {
// ask for their public key right now to make sure we have it // user is attempting to prove their identity to us, but we don't have enough information
requestUserPublicKey(username); sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
if (!userPerms.canConnectToDomain) { // ask for their public key right now to make sure we have it
requestUserPublicKey(username);
getGroupMemberships(username); // optimistically get started on group memberships
#ifdef WANT_DEBUG
qDebug() << "stalling login because we have no username-signature:" << username;
#endif
return SharedNodePointer(); return SharedNodePointer();
} } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
} // they sent us a username and the signature verifies it
getGroupMemberships(username);
if (username.isEmpty()) { verifiedUsername = username;
// they didn't tell us who they are
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
qDebug() << "user-permissions: no username, so:" << userPerms;
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
// they are sent us a username and the signature verifies it
if (_server->_settingsManager.havePermissionsForName(username)) {
// we have specific permissions for this user.
userPerms = _server->_settingsManager.getPermissionsForName(username);
qDebug() << "user-permissions: specific user matches, so:" << userPerms;
} else { } else {
// they are logged into metaverse, but we don't have specific permissions for them. // they sent us a username, but it didn't check out
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); requestUserPublicKey(username);
qDebug() << "user-permissions: user is logged in, so:" << userPerms; #ifdef WANT_DEBUG
} qDebug() << "stalling login because signature verification failed:" << username;
userPerms.setUserName(username); #endif
} else {
// they sent us a username, but it didn't check out
requestUserPublicKey(username);
if (!userPerms.canConnectToDomain) {
return SharedNodePointer(); return SharedNodePointer();
} }
} }
qDebug() << "user-permissions: final:" << userPerms; userPerms = setPermissionsForUser(isLocalUser, verifiedUsername);
if (!userPerms.canConnectToDomain) { if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers); nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
#ifdef WANT_DEBUG
qDebug() << "stalling login due to permissions:" << username;
#endif
return SharedNodePointer(); return SharedNodePointer();
} }
if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) { if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) {
// we can't allow this user to connect because we are at max capacity // we can't allow this user to connect because we are at max capacity
sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr, sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
DomainHandler::ConnectionRefusedReason::TooManyUsers); DomainHandler::ConnectionRefusedReason::TooManyUsers);
#ifdef WANT_DEBUG
qDebug() << "stalling login due to max capacity:" << username;
#endif
return SharedNodePointer(); return SharedNodePointer();
} }
@ -305,10 +368,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
// we have a node that already has these exact sockets - this occurs if a node // we have a node that already has these exact sockets - this occurs if a node
// is unable to connect to the domain // is unable to connect to the domain
hintNodeID = node->getUUID(); hintNodeID = node->getUUID();
return false; return false;
} }
return true; return true;
}); });
@ -328,6 +389,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
uuidStringWithoutCurlyBraces(newNode->getUUID()), username); uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
#ifdef WANT_DEBUG
qDebug() << "accepting login:" << username;
#endif
return newNode; return newNode;
} }
@ -367,7 +432,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
const HifiSockAddr& senderSockAddr) { const HifiSockAddr& senderSockAddr) {
// it's possible this user can be allowed to connect, but we need to check their username signature // it's possible this user can be allowed to connect, but we need to check their username signature
QByteArray publicKeyArray = _userPublicKeys.value(username); QByteArray publicKeyArray = _userPublicKeys.value(username.toLower());
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
@ -471,10 +536,20 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
return; return;
} }
QString lowerUsername = username.toLower();
if (_inFlightPublicKeyRequests.contains(lowerUsername)) {
// public-key request for this username is already flight, not rerequesting
return;
}
_inFlightPublicKeyRequests += lowerUsername;
// even if we have a public key for them right now, request a new one in case it has just changed // even if we have a public key for them right now, request a new one in case it has just changed
JSONCallbackParameters callbackParams; JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "publicKeyJSONErrorCallback";
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key"; const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
@ -485,28 +560,40 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
QNetworkAccessManager::GetOperation, callbackParams); QNetworkAccessManager::GetOperation, callbackParams);
} }
QString extractUsernameFromPublicKeyRequest(QNetworkReply& requestReply) {
// extract the username from the request url
QString username;
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) {
username = usernameRegex.cap(1);
}
return username.toLower();
}
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
QString username = extractUsernameFromPublicKeyRequest(requestReply);
if (jsonObject["status"].toString() == "success") { if (jsonObject["status"].toString() == "success" && username != "") {
// figure out which user this is for // figure out which user this is for
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key"; const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING); 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";
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { _userPublicKeys[username.toLower()] =
QString username = usernameRegex.cap(1); QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
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());
}
} }
_inFlightPublicKeyRequests.remove(username);
}
void DomainGatekeeper::publicKeyJSONErrorCallback(QNetworkReply& requestReply) {
qDebug() << "publicKey api call failed:" << requestReply.error();
QString username = extractUsernameFromPublicKeyRequest(requestReply);
_inFlightPublicKeyRequests.remove(username);
} }
void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) { void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) {
@ -645,3 +732,159 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage>
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr()); sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
} }
} }
void DomainGatekeeper::getGroupMemberships(const QString& username) {
// loop through the groups mentioned on the settings page and ask if this user is in each. The replies
// will be received asynchronously and permissions will be updated as the answers come in.
// if we've already asked, wait for the answer before asking again
QString lowerUsername = username.toLower();
if (_inFlightGroupMembershipsRequests.contains(lowerUsername)) {
// public-key request for this username is already flight, not rerequesting
return;
}
_inFlightGroupMembershipsRequests += lowerUsername;
QJsonObject json;
QSet<QString> groupIDSet;
foreach (QUuid groupID, _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs()) {
groupIDSet += groupID.toString().mid(1,36);
}
QJsonArray groupIDs = QJsonArray::fromStringList(groupIDSet.toList());
json["groups"] = groupIDs;
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback";
const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/members/%2";
DependencyManager::get<AccountManager>()->sendRequest(GET_IS_GROUP_MEMBER_PATH.arg(username),
AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation, callbackParams,
QJsonDocument(json).toJson());
}
QString extractUsernameFromGroupMembershipsReply(QNetworkReply& requestReply) {
// extract the username from the request url
QString username;
const QString GROUP_MEMBERSHIPS_URL_REGEX_STRING = "api\\/v1\\/groups\\/members\\/([A-Za-z0-9_\\.]+)";
QRegExp usernameRegex(GROUP_MEMBERSHIPS_URL_REGEX_STRING);
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
username = usernameRegex.cap(1);
}
return username.toLower();
}
void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) {
// {
// "data":{
// "username":"sethalves",
// "groups":{
// "fd55479a-265d-4990-854e-3d04214ad1b0":{
// "name":"Blerg Blah",
// "rank":{
// "name":"admin",
// "order":1
// }
// }
// }
// },
// "status":"success"
// }
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
if (jsonObject["status"].toString() == "success") {
QJsonObject data = jsonObject["data"].toObject();
QJsonObject groups = data["groups"].toObject();
QString username = data["username"].toString();
_server->_settingsManager.clearGroupMemberships(username);
foreach (auto groupID, groups.keys()) {
QJsonObject group = groups[groupID].toObject();
QJsonObject rank = group["rank"].toObject();
QUuid rankID = QUuid(rank["id"].toString());
_server->_settingsManager.recordGroupMembership(username, groupID, rankID);
}
} else {
qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
}
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
}
void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply& requestReply) {
qDebug() << "getIsGroupMember api call failed:" << requestReply.error();
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
}
void DomainGatekeeper::getDomainOwnerFriendsList() {
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "getDomainOwnerFriendsListJSONCallback";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "getDomainOwnerFriendsListErrorCallback";
const QString GET_FRIENDS_LIST_PATH = "api/v1/user/friends";
DependencyManager::get<AccountManager>()->sendRequest(GET_FRIENDS_LIST_PATH, AccountManagerAuth::Required,
QNetworkAccessManager::GetOperation, callbackParams, QByteArray(),
NULL, QVariantMap());
}
void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply) {
// {
// status: "success",
// data: {
// friends: [
// "chris",
// "freidrica",
// "G",
// "huffman",
// "leo",
// "philip",
// "ryan",
// "sam",
// "ZappoMan"
// ]
// }
// }
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
if (jsonObject["status"].toString() == "success") {
_domainOwnerFriends.clear();
QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray();
for (int i = 0; i < friends.size(); i++) {
_domainOwnerFriends += friends.at(i).toString();
}
} else {
qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
}
}
void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply) {
qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply.error();
}
void DomainGatekeeper::refreshGroupsCache() {
// if agents are connected to this domain, refresh our cached information about groups and memberships in such.
getDomainOwnerFriendsList();
auto nodeList = DependencyManager::get<LimitedNodeList>();
nodeList->eachNode([&](const SharedNodePointer& node) {
if (!node->getPermissions().isAssignment) {
// this node is an agent
const QString& verifiedUserName = node->getPermissions().getVerifiedUserName();
if (!verifiedUserName.isEmpty()) {
getGroupMemberships(verifiedUserName);
}
}
});
_server->_settingsManager.apiRefreshGroupInformation();
updateNodePermissions();
#if WANT_DEBUG
_server->_settingsManager.debugDumpGroupsState();
#endif
}

View file

@ -51,7 +51,16 @@ public slots:
void processICEPeerInformationPacket(QSharedPointer<ReceivedMessage> message); void processICEPeerInformationPacket(QSharedPointer<ReceivedMessage> message);
void publicKeyJSONCallback(QNetworkReply& requestReply); void publicKeyJSONCallback(QNetworkReply& requestReply);
void publicKeyJSONErrorCallback(QNetworkReply& requestReply);
void getIsGroupMemberJSONCallback(QNetworkReply& requestReply);
void getIsGroupMemberErrorCallback(QNetworkReply& requestReply);
void getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply);
void getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply);
void refreshGroupsCache();
signals: signals:
void killNode(SharedNodePointer node); void killNode(SharedNodePointer node);
void connectedNode(SharedNodePointer node); void connectedNode(SharedNodePointer node);
@ -93,6 +102,14 @@ private:
QHash<QString, QUuid> _connectionTokenHash; QHash<QString, QUuid> _connectionTokenHash;
QHash<QString, QByteArray> _userPublicKeys; QHash<QString, QByteArray> _userPublicKeys;
QSet<QString> _inFlightPublicKeyRequests; // keep track of which we've already asked for
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername);
void getGroupMemberships(const QString& username);
// void getIsGroupMember(const QString& username, const QUuid groupID);
void getDomainOwnerFriendsList();
}; };

View file

@ -184,10 +184,10 @@ void DomainMetadata::securityChanged(bool send) {
QString restriction; QString restriction;
const auto& settingsManager = static_cast<DomainServer*>(parent())->_settingsManager; const auto& settingsManager = static_cast<DomainServer*>(parent())->_settingsManager;
bool hasAnonymousAccess = bool hasAnonymousAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).can(
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).canConnectToDomain; NodePermissions::Permission::canConnectToDomain);
bool hasHifiAccess = bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can(
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).canConnectToDomain; NodePermissions::Permission::canConnectToDomain);
if (hasAnonymousAccess) { if (hasAnonymousAccess) {
restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON; restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON;
} else if (hasHifiAccess) { } else if (hasHifiAccess) {

View file

@ -38,6 +38,7 @@
#include <UUID.h> #include <UUID.h>
#include <LogHandler.h> #include <LogHandler.h>
#include <ServerPathUtils.h> #include <ServerPathUtils.h>
#include <NumericalConstants.h>
#include "DomainServerNodeData.h" #include "DomainServerNodeData.h"
#include "NodeConnectionData.h" #include "NodeConnectionData.h"
@ -106,11 +107,15 @@ DomainServer::DomainServer(int argc, char* argv[]) :
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
&_gatekeeper, &DomainGatekeeper::updateNodePermissions); &_gatekeeper, &DomainGatekeeper::updateNodePermissions);
setupGroupCacheRefresh();
// if we were given a certificate/private key or oauth credentials they must succeed // if we were given a certificate/private key or oauth credentials they must succeed
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) { if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
return; return;
} }
_settingsManager.apiRefreshGroupInformation();
setupNodeListAndAssignments(); setupNodeListAndAssignments();
setupAutomaticNetworking(); setupAutomaticNetworking();
if (!getID().isNull()) { if (!getID().isNull()) {
@ -1098,12 +1103,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting; domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
// add access level for anonymous connections // add access level for anonymous connections
// consider the domain to be "restricted" if anonymous connections are disallowed // consider the domain to be "restricted" if anonymous connections are disallowed
static const QString RESTRICTED_ACCESS_FLAG = "restricted"; static const QString RESTRICTED_ACCESS_FLAG = "restricted";
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.can(NodePermissions::Permission::canConnectToDomain);
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID()); const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
if (!temporaryDomainKey.isEmpty()) { if (!temporaryDomainKey.isEmpty()) {
@ -2327,3 +2331,14 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
// immediately send an update to the metaverse API when our ice-server changes // immediately send an update to the metaverse API when our ice-server changes
sendICEServerAddressToMetaverseAPI(); sendICEServerAddressToMetaverseAPI();
} }
void DomainServer::setupGroupCacheRefresh() {
const int REFRESH_GROUPS_INTERVAL_MSECS = 15 * MSECS_PER_SECOND;
if (!_metaverseGroupCacheTimer) {
// setup a timer to refresh this server's cached group details
_metaverseGroupCacheTimer = new QTimer { this };
connect(_metaverseGroupCacheTimer, &QTimer::timeout, &_gatekeeper, &DomainGatekeeper::refreshGroupsCache);
_metaverseGroupCacheTimer->start(REFRESH_GROUPS_INTERVAL_MSECS);
}
}

View file

@ -41,7 +41,7 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
public: public:
DomainServer(int argc, char* argv[]); DomainServer(int argc, char* argv[]);
~DomainServer(); ~DomainServer();
static int const EXIT_CODE_REBOOT; static int const EXIT_CODE_REBOOT;
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
@ -64,7 +64,7 @@ public slots:
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message); void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message); void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message); void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message);
private slots: private slots:
void aboutToQuit(); void aboutToQuit();
@ -74,7 +74,7 @@ private slots:
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); } void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); }
void sendHeartbeatToIceServer(); void sendHeartbeatToIceServer();
void handleConnectedNode(SharedNodePointer newNode); void handleConnectedNode(SharedNodePointer newNode);
void handleTempDomainSuccess(QNetworkReply& requestReply); void handleTempDomainSuccess(QNetworkReply& requestReply);
@ -96,7 +96,7 @@ signals:
void iceServerChanged(); void iceServerChanged();
void userConnected(); void userConnected();
void userDisconnected(); void userDisconnected();
private: private:
const QUuid& getID(); const QUuid& getID();
@ -136,7 +136,7 @@ private:
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment); SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment); void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
void addStaticAssignmentsToQueue(); void addStaticAssignmentsToQueue();
QUrl oauthRedirectURL(); QUrl oauthRedirectURL();
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid()); QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
@ -151,7 +151,9 @@ private:
QJsonObject jsonForSocket(const HifiSockAddr& socket); QJsonObject jsonForSocket(const HifiSockAddr& socket);
QJsonObject jsonObjectForNode(const SharedNodePointer& node); QJsonObject jsonObjectForNode(const SharedNodePointer& node);
void setupGroupCacheRefresh();
DomainGatekeeper _gatekeeper; DomainGatekeeper _gatekeeper;
HTTPManager _httpManager; HTTPManager _httpManager;
@ -184,6 +186,7 @@ private:
DomainMetadata* _metadata { nullptr }; DomainMetadata* _metadata { nullptr };
QTimer* _iceHeartbeatTimer { nullptr }; QTimer* _iceHeartbeatTimer { nullptr };
QTimer* _metaverseHeartbeatTimer { nullptr }; QTimer* _metaverseHeartbeatTimer { nullptr };
QTimer* _metaverseGroupCacheTimer { nullptr };
QList<QHostAddress> _iceServerAddresses; QList<QHostAddress> _iceServerAddresses;
QSet<QHostAddress> _failedIceServerAddresses; QSet<QHostAddress> _failedIceServerAddresses;

View file

@ -20,7 +20,7 @@
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtCore/QUrlQuery> #include <QtCore/QUrlQuery>
#include <AccountManager.h>
#include <QTimeZone> #include <QTimeZone>
#include <Assignment.h> #include <Assignment.h>
@ -31,9 +31,6 @@
#include "DomainServerSettingsManager.h" #include "DomainServerSettingsManager.h"
#define WANT_DEBUG 1
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString DESCRIPTION_SETTINGS_KEY = "settings";
@ -219,40 +216,50 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
new NodePermissions(NodePermissions::standardNameAnonymous)); new NodePermissions(NodePermissions::standardNameAnonymous));
_standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset( _standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset(
new NodePermissions(NodePermissions::standardNameLoggedIn)); new NodePermissions(NodePermissions::standardNameLoggedIn));
_standardAgentPermissions[NodePermissions::standardNameFriends].reset(
new NodePermissions(NodePermissions::standardNameFriends));
if (isRestrictedAccess) { if (isRestrictedAccess) {
// only users in allow-users list can connect // only users in allow-users list can connect
_standardAgentPermissions[NodePermissions::standardNameAnonymous]->canConnectToDomain = false; _standardAgentPermissions[NodePermissions::standardNameAnonymous]->clear(
_standardAgentPermissions[NodePermissions::standardNameLoggedIn]->canConnectToDomain = false; NodePermissions::Permission::canConnectToDomain);
_standardAgentPermissions[NodePermissions::standardNameLoggedIn]->clear(
NodePermissions::Permission::canConnectToDomain);
} // else anonymous and logged-in retain default of canConnectToDomain = true } // else anonymous and logged-in retain default of canConnectToDomain = true
foreach (QString allowedUser, allowedUsers) { foreach (QString allowedUser, allowedUsers) {
// even if isRestrictedAccess is false, we have to add explicit rows for these users. // even if isRestrictedAccess is false, we have to add explicit rows for these users.
// defaults to canConnectToDomain = true _agentPermissions[NodePermissionsKey(allowedUser, 0)].reset(new NodePermissions(allowedUser));
_agentPermissions[allowedUser].reset(new NodePermissions(allowedUser)); _agentPermissions[NodePermissionsKey(allowedUser, 0)]->set(NodePermissions::Permission::canConnectToDomain);
} }
foreach (QString allowedEditor, allowedEditors) { foreach (QString allowedEditor, allowedEditors) {
if (!_agentPermissions.contains(allowedEditor)) { NodePermissionsKey editorKey(allowedEditor, 0);
_agentPermissions[allowedEditor].reset(new NodePermissions(allowedEditor)); if (!_agentPermissions.contains(editorKey)) {
_agentPermissions[editorKey].reset(new NodePermissions(allowedEditor));
if (isRestrictedAccess) { if (isRestrictedAccess) {
// they can change locks, but can't connect. // they can change locks, but can't connect.
_agentPermissions[allowedEditor]->canConnectToDomain = false; _agentPermissions[editorKey]->clear(NodePermissions::Permission::canConnectToDomain);
} }
} }
_agentPermissions[allowedEditor]->canAdjustLocks = true; _agentPermissions[editorKey]->set(NodePermissions::Permission::canAdjustLocks);
} }
QList<QHash<QString, NodePermissionsPointer>> permissionsSets; QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets;
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get(); permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get();
foreach (auto permissionsSet, permissionsSets) { foreach (auto permissionsSet, permissionsSets) {
foreach (QString userName, permissionsSet.keys()) { foreach (NodePermissionsKey userKey, permissionsSet.keys()) {
if (onlyEditorsAreRezzers) { if (onlyEditorsAreRezzers) {
permissionsSet[userName]->canRezPermanentEntities = permissionsSet[userName]->canAdjustLocks; if (permissionsSet[userKey]->can(NodePermissions::Permission::canAdjustLocks)) {
permissionsSet[userName]->canRezTemporaryEntities = permissionsSet[userName]->canAdjustLocks; permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities);
permissionsSet[userKey]->set(NodePermissions::Permission::canRezTemporaryEntities);
} else {
permissionsSet[userKey]->clear(NodePermissions::Permission::canRezPermanentEntities);
permissionsSet[userKey]->clear(NodePermissions::Permission::canRezTemporaryEntities);
}
} else { } else {
permissionsSet[userName]->canRezPermanentEntities = true; permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities);
permissionsSet[userName]->canRezTemporaryEntities = true; permissionsSet[userKey]->set(NodePermissions::Permission::canRezTemporaryEntities);
} }
} }
} }
@ -317,36 +324,108 @@ void DomainServerSettingsManager::validateDescriptorsMap() {
} }
} }
void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows,
QString groupName, NodePermissionsPointer perms) {
// this is called when someone has used the domain-settings webpage to add a group. They type the group's name
// and give it some permissions. The domain-server asks api for the group's ranks and populates the map
// with them. Here, that initial user-entered row is removed and it's permissions are copied to all the ranks
// except owner.
QString groupNameLower = groupName.toLower();
foreach (NodePermissionsKey nameKey, permissionsRows.keys()) {
if (nameKey.first.toLower() != groupNameLower) {
continue;
}
QUuid groupID = _groupIDs[groupNameLower];
QUuid rankID = nameKey.second;
GroupRank rank = _groupRanks[groupID][rankID];
if (rank.order == 0) {
// we don't copy the initial permissions to the owner.
continue;
}
permissionsRows[nameKey]->setAll(false);
*(permissionsRows[nameKey]) |= *perms;
}
}
void DomainServerSettingsManager::packPermissionsForMap(QString mapName, void DomainServerSettingsManager::packPermissionsForMap(QString mapName,
NodePermissionsMap& agentPermissions, NodePermissionsMap& permissionsRows,
QString keyPath) { QString keyPath) {
// find (or create) the "security" section of the settings map
QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security");
if (!security || !security->canConvert(QMetaType::QVariantMap)) { if (!security || !security->canConvert(QMetaType::QVariantMap)) {
security = valueForKeyPath(_configMap.getUserConfig(), "security", true); security = valueForKeyPath(_configMap.getUserConfig(), "security", true);
(*security) = QVariantMap(); (*security) = QVariantMap();
} }
// save settings for anonymous / logged-in / localhost // find (or create) whichever subsection of "security" we are packing
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath);
if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) {
permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true); permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true);
(*permissions) = QVariantList(); (*permissions) = QVariantList();
} }
// convert details for each member of the subsection
QVariantList* permissionsList = reinterpret_cast<QVariantList*>(permissions); QVariantList* permissionsList = reinterpret_cast<QVariantList*>(permissions);
(*permissionsList).clear(); (*permissionsList).clear();
foreach (QString userName, agentPermissions.keys()) { QList<NodePermissionsKey> permissionsKeys = permissionsRows.keys();
*permissionsList += agentPermissions[userName]->toVariant();
// when a group is added from the domain-server settings page, the config map has a group-name with
// no ID or rank. We need to leave that there until we get a valid response back from the api.
// once we have the ranks and IDs, we need to delete the original entry so that it doesn't show
// up in the settings-page with undefined's after it.
QHash<QString, bool> groupNamesWithRanks;
// note which groups have rank/ID information
foreach (NodePermissionsKey userKey, permissionsKeys) {
NodePermissionsPointer perms = permissionsRows[userKey];
if (perms->getRankID() != QUuid()) {
groupNamesWithRanks[userKey.first] = true;
}
}
foreach (NodePermissionsKey userKey, permissionsKeys) {
NodePermissionsPointer perms = permissionsRows[userKey];
if (perms->isGroup()) {
QString groupName = userKey.first;
if (perms->getRankID() == QUuid() && groupNamesWithRanks.contains(groupName)) {
// copy the values from this user-added entry to the other (non-owner) ranks and remove it.
permissionsRows.remove(userKey);
initializeGroupPermissions(permissionsRows, groupName, perms);
}
}
}
// convert each group-name / rank-id pair to a variant-map
foreach (NodePermissionsKey userKey, permissionsKeys) {
if (!permissionsRows.contains(userKey)) {
continue;
}
NodePermissionsPointer perms = permissionsRows[userKey];
if (perms->isGroup()) {
QHash<QUuid, GroupRank>& groupRanks = _groupRanks[perms->getGroupID()];
*permissionsList += perms->toVariant(groupRanks);
} else {
*permissionsList += perms->toVariant();
}
} }
} }
void DomainServerSettingsManager::packPermissions() { void DomainServerSettingsManager::packPermissions() {
// transfer details from _agentPermissions to _configMap // transfer details from _agentPermissions to _configMap
// save settings for anonymous / logged-in / localhost
packPermissionsForMap("standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH); packPermissionsForMap("standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH);
// save settings for specific users // save settings for specific users
packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH);
// save settings for groups
packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH);
// save settings for blacklist groups
packPermissionsForMap("permissions", _groupForbiddens, GROUP_FORBIDDENS_KEYPATH);
persistToFile(); persistToFile();
_configMap.loadMasterAndUserConfig(_argumentList); _configMap.loadMasterAndUserConfig(_argumentList);
} }
@ -356,10 +435,13 @@ void DomainServerSettingsManager::unpackPermissions() {
_standardAgentPermissions.clear(); _standardAgentPermissions.clear();
_agentPermissions.clear(); _agentPermissions.clear();
_groupPermissions.clear();
_groupForbiddens.clear();
bool foundLocalhost = false; bool foundLocalhost = false;
bool foundAnonymous = false; bool foundAnonymous = false;
bool foundLoggedIn = false; bool foundLoggedIn = false;
bool foundFriends = false;
bool needPack = false; bool needPack = false;
QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH);
@ -374,20 +456,34 @@ void DomainServerSettingsManager::unpackPermissions() {
permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH, true); permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH, true);
(*permissions) = QVariantList(); (*permissions) = QVariantList();
} }
QVariant* groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH);
if (!groupPermissions || !groupPermissions->canConvert(QMetaType::QVariantList)) {
qDebug() << "failed to extract group permissions from settings.";
groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH, true);
(*groupPermissions) = QVariantList();
}
QVariant* groupForbiddens = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH);
if (!groupForbiddens || !groupForbiddens->canConvert(QMetaType::QVariantList)) {
qDebug() << "failed to extract group forbiddens from settings.";
groupForbiddens = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH, true);
(*groupForbiddens) = QVariantList();
}
QList<QVariant> standardPermissionsList = standardPermissions->toList(); QList<QVariant> standardPermissionsList = standardPermissions->toList();
foreach (QVariant permsHash, standardPermissionsList) { foreach (QVariant permsHash, standardPermissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID(); QString id = perms->getID();
foundLocalhost |= (id == NodePermissions::standardNameLocalhost); NodePermissionsKey idKey = NodePermissionsKey(id, 0);
foundAnonymous |= (id == NodePermissions::standardNameAnonymous); foundLocalhost |= (idKey == NodePermissions::standardNameLocalhost);
foundLoggedIn |= (id == NodePermissions::standardNameLoggedIn); foundAnonymous |= (idKey == NodePermissions::standardNameAnonymous);
if (_standardAgentPermissions.contains(id)) { foundLoggedIn |= (idKey == NodePermissions::standardNameLoggedIn);
foundFriends |= (idKey == NodePermissions::standardNameFriends);
if (_standardAgentPermissions.contains(idKey)) {
qDebug() << "duplicate name in standard permissions table: " << id; qDebug() << "duplicate name in standard permissions table: " << id;
_standardAgentPermissions[id] |= perms; *(_standardAgentPermissions[idKey]) |= *perms;
needPack = true; needPack = true;
} else { } else {
_standardAgentPermissions[id] = perms; _standardAgentPermissions[idKey] = perms;
} }
} }
@ -395,12 +491,51 @@ void DomainServerSettingsManager::unpackPermissions() {
foreach (QVariant permsHash, permissionsList) { foreach (QVariant permsHash, permissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID(); QString id = perms->getID();
if (_agentPermissions.contains(id)) { NodePermissionsKey idKey = NodePermissionsKey(id, 0);
if (_agentPermissions.contains(idKey)) {
qDebug() << "duplicate name in permissions table: " << id; qDebug() << "duplicate name in permissions table: " << id;
_agentPermissions[id] |= perms; *(_agentPermissions[idKey]) |= *perms;
needPack = true; needPack = true;
} else { } else {
_agentPermissions[id] = perms; _agentPermissions[idKey] = perms;
}
}
QList<QVariant> groupPermissionsList = groupPermissions->toList();
foreach (QVariant permsHash, groupPermissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
NodePermissionsKey idKey = perms->getKey();
if (_groupPermissions.contains(idKey)) {
qDebug() << "duplicate name in group permissions table: " << id;
*(_groupPermissions[idKey]) |= *perms;
needPack = true;
} else {
*(_groupPermissions[idKey]) = *perms;
}
if (perms->isGroup()) {
// the group-id was cached. hook-up the uuid in the uuid->group hash
_groupPermissionsByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupPermissions[idKey];
needPack |= setGroupID(perms->getID(), perms->getGroupID());
}
}
QList<QVariant> groupForbiddensList = groupForbiddens->toList();
foreach (QVariant permsHash, groupForbiddensList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
NodePermissionsKey idKey = perms->getKey();
if (_groupForbiddens.contains(idKey)) {
qDebug() << "duplicate name in group forbiddens table: " << id;
*(_groupForbiddens[idKey]) |= *perms;
needPack = true;
} else {
_groupForbiddens[idKey] = perms;
}
if (perms->isGroup()) {
// the group-id was cached. hook-up the uuid in the uuid->group hash
_groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupForbiddens[idKey];
needPack |= setGroupID(perms->getID(), perms->getGroupID());
} }
} }
@ -408,19 +543,26 @@ void DomainServerSettingsManager::unpackPermissions() {
if (!foundLocalhost) { if (!foundLocalhost) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) };
perms->setAll(true); perms->setAll(true);
_standardAgentPermissions[perms->getID()] = perms; _standardAgentPermissions[perms->getKey()] = perms;
needPack = true; needPack = true;
} }
if (!foundAnonymous) { if (!foundAnonymous) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) }; NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) };
_standardAgentPermissions[perms->getID()] = perms; _standardAgentPermissions[perms->getKey()] = perms;
needPack = true; needPack = true;
} }
if (!foundLoggedIn) { if (!foundLoggedIn) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) }; NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) };
_standardAgentPermissions[perms->getID()] = perms; _standardAgentPermissions[perms->getKey()] = perms;
needPack = true; needPack = true;
} }
if (!foundFriends) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameFriends) };
_standardAgentPermissions[perms->getKey()] = perms;
needPack = true;
}
needPack |= ensurePermissionsForGroupRanks();
if (needPack) { if (needPack) {
packPermissions(); packPermissions();
@ -428,20 +570,93 @@ void DomainServerSettingsManager::unpackPermissions() {
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "--------------- permissions ---------------------"; qDebug() << "--------------- permissions ---------------------";
QList<QHash<QString, NodePermissionsPointer>> permissionsSets; QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets;
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get(); permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
<< _groupPermissions.get() << _groupForbiddens.get();
foreach (auto permissionSet, permissionsSets) { foreach (auto permissionSet, permissionsSets) {
QHashIterator<QString, NodePermissionsPointer> i(permissionSet); QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(permissionSet);
while (i.hasNext()) { while (i.hasNext()) {
i.next(); i.next();
NodePermissionsPointer perms = i.value(); NodePermissionsPointer perms = i.value();
qDebug() << i.key() << perms; if (perms->isGroup()) {
qDebug() << i.key() << perms->getGroupID() << perms;
} else {
qDebug() << i.key() << perms;
}
} }
} }
#endif #endif
} }
NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const QString& name) const { bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() {
// make sure each rank in each group has its own set of permissions
bool changed = false;
QList<QUuid> permissionGroupIDs = getGroupIDs();
foreach (QUuid groupID, permissionGroupIDs) {
QString groupName = _groupNames[groupID];
QHash<QUuid, GroupRank>& ranksForGroup = _groupRanks[groupID];
foreach (QUuid rankID, ranksForGroup.keys()) {
NodePermissionsKey nameKey = NodePermissionsKey(groupName, rankID);
GroupByUUIDKey idKey = GroupByUUIDKey(groupID, rankID);
NodePermissionsPointer perms;
if (_groupPermissions.contains(nameKey)) {
perms = _groupPermissions[nameKey];
} else {
perms = NodePermissionsPointer(new NodePermissions(nameKey));
_groupPermissions[nameKey] = perms;
changed = true;
}
if (perms->getGroupID() != groupID) {
perms->setGroupID(groupID);
changed = true;
}
if (perms->getRankID() != rankID) {
perms->setRankID(rankID);
changed = true;
}
_groupPermissionsByUUID[idKey] = perms;
}
}
QList<QUuid> forbiddenGroupIDs = getBlacklistGroupIDs();
foreach (QUuid groupID, forbiddenGroupIDs) {
QString groupName = _groupNames[groupID];
QHash<QUuid, GroupRank>& ranksForGroup = _groupRanks[groupID];
foreach (QUuid rankID, ranksForGroup.keys()) {
NodePermissionsKey nameKey = NodePermissionsKey(groupName, rankID);
GroupByUUIDKey idKey = GroupByUUIDKey(groupID, rankID);
NodePermissionsPointer perms;
if (_groupForbiddens.contains(nameKey)) {
perms = _groupForbiddens[nameKey];
} else {
perms = NodePermissionsPointer(new NodePermissions(nameKey));
_groupForbiddens[nameKey] = perms;
changed = true;
}
if (perms->getGroupID() != groupID) {
perms->setGroupID(groupID);
changed = true;
}
if (perms->getRankID() != rankID) {
perms->setRankID(rankID);
changed = true;
}
_groupForbiddensByUUID[idKey] = perms;
}
}
return changed;
}
QStringList DomainServerSettingsManager::getAllNames() const {
QStringList result;
foreach (auto key, _agentPermissions.keys()) {
result << key.first.toLower();
}
return result;
}
NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const NodePermissionsKey& name) const {
if (_standardAgentPermissions.contains(name)) { if (_standardAgentPermissions.contains(name)) {
return *(_standardAgentPermissions[name].get()); return *(_standardAgentPermissions[name].get());
} }
@ -451,14 +666,58 @@ NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const
} }
NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const { NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const {
if (_agentPermissions.contains(name)) { NodePermissionsKey nameKey = NodePermissionsKey(name, 0);
return *(_agentPermissions[name].get()); if (_agentPermissions.contains(nameKey)) {
return *(_agentPermissions[nameKey].get());
} }
NodePermissions nullPermissions; NodePermissions nullPermissions;
nullPermissions.setAll(false); nullPermissions.setAll(false);
return nullPermissions; return nullPermissions;
} }
NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, QUuid rankID) const {
NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID);
if (_groupPermissions.contains(groupRankKey)) {
return *(_groupPermissions[groupRankKey].get());
}
NodePermissions nullPermissions;
nullPermissions.setAll(false);
return nullPermissions;
}
NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QUuid& groupID, QUuid rankID) const {
GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rankID);
if (!_groupPermissionsByUUID.contains(byUUIDKey)) {
NodePermissions nullPermissions;
nullPermissions.setAll(false);
return nullPermissions;
}
NodePermissionsKey groupKey = _groupPermissionsByUUID[byUUIDKey]->getKey();
return getPermissionsForGroup(groupKey.first, groupKey.second);
}
NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QString& groupName, QUuid rankID) const {
NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID);
if (_groupForbiddens.contains(groupRankKey)) {
return *(_groupForbiddens[groupRankKey].get());
}
NodePermissions allForbiddens;
allForbiddens.setAll(true);
return allForbiddens;
}
NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& groupID, QUuid rankID) const {
GroupByUUIDKey byUUIDKey = GroupByUUIDKey(groupID, rankID);
if (!_groupForbiddensByUUID.contains(byUUIDKey)) {
NodePermissions allForbiddens;
allForbiddens.setAll(true);
return allForbiddens;
}
NodePermissionsKey groupKey = _groupForbiddensByUUID[byUUIDKey]->getKey();
return getForbiddensForGroup(groupKey.first, groupKey.second);
}
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) {
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
@ -518,8 +777,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent()); QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object(); QJsonObject postedObject = postedDocument.object();
qDebug() << "DomainServerSettingsManager postedObject -" << postedObject;
// we recurse one level deep below each group for the appropriate setting // we recurse one level deep below each group for the appropriate setting
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject); bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject);
@ -536,6 +793,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
} else { } else {
unpackPermissions(); unpackPermissions();
apiRefreshGroupInformation();
emit updateNodePermissions(); emit updateNodePermissions();
} }
@ -550,7 +808,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray; rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object(); rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
} }
@ -730,7 +987,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
sortPermissions(); sortPermissions();
} }
QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) { QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject,
const QString& settingName) {
foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) { foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
QJsonObject settingObject = settingValue.toObject(); QJsonObject settingObject = settingValue.toObject();
if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) { if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
@ -843,6 +1101,12 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
!m2.contains("permissions_id")) { !m2.contains("permissions_id")) {
return v1.toString() < v2.toString(); return v1.toString() < v2.toString();
} }
if (m1.contains("rank_order") && m2.contains("rank_order") &&
m1["permissions_id"].toString() == m2["permissions_id"].toString()) {
return m1["rank_order"].toInt() < m2["rank_order"].toInt();
}
return m1["permissions_id"].toString() < m2["permissions_id"].toString(); return m1["permissions_id"].toString() < m2["permissions_id"].toString();
} }
@ -858,6 +1122,16 @@ void DomainServerSettingsManager::sortPermissions() {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions); QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
} }
QVariant* groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH);
if (groupPermissions && groupPermissions->canConvert(QMetaType::QVariantList)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(groupPermissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
}
QVariant* forbiddenPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH);
if (forbiddenPermissions && forbiddenPermissions->canConvert(QMetaType::QVariantList)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(forbiddenPermissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
}
} }
void DomainServerSettingsManager::persistToFile() { void DomainServerSettingsManager::persistToFile() {
@ -878,3 +1152,330 @@ void DomainServerSettingsManager::persistToFile() {
qCritical("Could not write to JSON settings file. Unable to persist settings."); qCritical("Could not write to JSON settings file. Unable to persist settings.");
} }
} }
QStringList DomainServerSettingsManager::getAllKnownGroupNames() {
// extract all the group names from the group-permissions and group-forbiddens settings
QSet<QString> result;
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(_groupPermissions.get());
while (i.hasNext()) {
i.next();
NodePermissionsKey key = i.key();
result += key.first;
}
QHashIterator<NodePermissionsKey, NodePermissionsPointer> j(_groupForbiddens.get());
while (j.hasNext()) {
j.next();
NodePermissionsKey key = j.key();
result += key.first;
}
return result.toList();
}
bool DomainServerSettingsManager::setGroupID(const QString& groupName, const QUuid& groupID) {
bool changed = false;
_groupIDs[groupName.toLower()] = groupID;
_groupNames[groupID] = groupName;
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(_groupPermissions.get());
while (i.hasNext()) {
i.next();
NodePermissionsPointer perms = i.value();
if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) {
changed = true;
perms->setGroupID(groupID);
}
}
QHashIterator<NodePermissionsKey, NodePermissionsPointer> j(_groupForbiddens.get());
while (j.hasNext()) {
j.next();
NodePermissionsPointer perms = j.value();
if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) {
changed = true;
perms->setGroupID(groupID);
}
}
return changed;
}
void DomainServerSettingsManager::apiRefreshGroupInformation() {
if (!DependencyManager::get<AccountManager>()->hasAuthEndpoint()) {
// can't yet.
return;
}
bool changed = false;
QStringList groupNames = getAllKnownGroupNames();
foreach (QString groupName, groupNames) {
QString lowerGroupName = groupName.toLower();
if (_groupIDs.contains(lowerGroupName)) {
// we already know about this one. recall setGroupID in case the group has been
// added to another section (the same group is found in both groups and blacklists).
changed = setGroupID(groupName, _groupIDs[lowerGroupName]);
continue;
}
apiGetGroupID(groupName);
}
foreach (QUuid groupID, _groupNames.keys()) {
apiGetGroupRanks(groupID);
}
changed |= ensurePermissionsForGroupRanks();
if (changed) {
packPermissions();
}
unpackPermissions();
}
void DomainServerSettingsManager::apiGetGroupID(const QString& groupName) {
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "apiGetGroupIDJSONCallback";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "apiGetGroupIDErrorCallback";
const QString GET_GROUP_ID_PATH = "api/v1/groups/names/%1";
DependencyManager::get<AccountManager>()->sendRequest(GET_GROUP_ID_PATH.arg(groupName),
AccountManagerAuth::Required,
QNetworkAccessManager::GetOperation, callbackParams);
}
void DomainServerSettingsManager::apiGetGroupIDJSONCallback(QNetworkReply& requestReply) {
// {
// "data":{
// "groups":[{
// "description":null,
// "id":"fd55479a-265d-4990-854e-3d04214ad1b0",
// "is_list":false,
// "membership":{
// "permissions":{
// "custom_1=":false,
// "custom_2=":false,
// "custom_3=":false,
// "custom_4=":false,
// "del_group=":true,
// "invite_member=":true,
// "kick_member=":true,
// "list_members=":true,
// "mv_group=":true,
// "query_members=":true,
// "rank_member=":true
// },
// "rank":{
// "name=":"owner",
// "order=":0
// }
// },
// "name":"Blerg Blah"
// }]
// },
// "status":"success"
// }
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
if (jsonObject["status"].toString() == "success") {
QJsonArray groups = jsonObject["data"].toObject()["groups"].toArray();
for (int i = 0; i < groups.size(); i++) {
QJsonObject group = groups.at(i).toObject();
QString groupName = group["name"].toString();
QUuid groupID = QUuid(group["id"].toString());
bool changed = setGroupID(groupName, groupID);
if (changed) {
packPermissions();
apiGetGroupRanks(groupID);
}
}
} else {
qDebug() << "getGroupID api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
}
}
void DomainServerSettingsManager::apiGetGroupIDErrorCallback(QNetworkReply& requestReply) {
qDebug() << "******************** getGroupID api call failed:" << requestReply.error();
}
void DomainServerSettingsManager::apiGetGroupRanks(const QUuid& groupID) {
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "apiGetGroupRanksJSONCallback";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "apiGetGroupRanksErrorCallback";
const QString GET_GROUP_RANKS_PATH = "api/v1/groups/%1/ranks";
DependencyManager::get<AccountManager>()->sendRequest(GET_GROUP_RANKS_PATH.arg(groupID.toString().mid(1,36)),
AccountManagerAuth::Required,
QNetworkAccessManager::GetOperation, callbackParams);
}
void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& requestReply) {
// {
// "data":{
// "groups":{
// "d3500f49-0655-4b1b-9846-ff8dd1b03351":{
// "members_count":1,
// "ranks":[
// {
// "id":"7979b774-e7f8-436c-9df1-912f1019f32f",
// "members_count":1,
// "name":"owner",
// "order":0,
// "permissions":{
// "custom_1":false,
// "custom_2":false,
// "custom_3":false,
// "custom_4":false,
// "edit_group":true,
// "edit_member":true,
// "edit_rank":true,
// "list_members":true,
// "list_permissions":true,
// "list_ranks":true,
// "query_member":true
// }
// }
// ]
// }
// }
// },"status":"success"
// }
bool changed = false;
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
if (jsonObject["status"].toString() == "success") {
QJsonObject groups = jsonObject["data"].toObject()["groups"].toObject();
foreach (auto groupID, groups.keys()) {
QJsonObject group = groups[groupID].toObject();
QJsonArray ranks = group["ranks"].toArray();
QHash<QUuid, GroupRank>& ranksForGroup = _groupRanks[groupID];
QHash<QUuid, bool> idsFromThisUpdate;
for (int rankIndex = 0; rankIndex < ranks.size(); rankIndex++) {
QJsonObject rank = ranks[rankIndex].toObject();
QUuid rankID = QUuid(rank["id"].toString());
int rankOrder = rank["order"].toInt();
QString rankName = rank["name"].toString();
int rankMembersCount = rank["members_count"].toInt();
GroupRank groupRank(rankID, rankOrder, rankName, rankMembersCount);
if (ranksForGroup[rankID] != groupRank) {
ranksForGroup[rankID] = groupRank;
changed = true;
}
idsFromThisUpdate[rankID] = true;
}
// clean up any that went away
foreach (QUuid rankID, ranksForGroup.keys()) {
if (!idsFromThisUpdate.contains(rankID)) {
ranksForGroup.remove(rankID);
}
}
}
changed |= ensurePermissionsForGroupRanks();
if (changed) {
packPermissions();
}
} else {
qDebug() << "getGroupRanks api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
}
}
void DomainServerSettingsManager::apiGetGroupRanksErrorCallback(QNetworkReply& requestReply) {
qDebug() << "******************** getGroupRanks api call failed:" << requestReply.error();
}
void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID) {
if (rankID != QUuid()) {
_groupMembership[name][groupID] = rankID;
} else {
_groupMembership[name].remove(groupID);
}
}
QUuid DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) {
const QHash<QUuid, QUuid>& groupsForName = _groupMembership[name];
if (groupsForName.contains(groupID)) {
return groupsForName[groupID];
}
return QUuid();
}
QList<QUuid> DomainServerSettingsManager::getGroupIDs() {
QSet<QUuid> result;
foreach (NodePermissionsKey groupKey, _groupPermissions.keys()) {
if (_groupPermissions[groupKey]->isGroup()) {
result += _groupPermissions[groupKey]->getGroupID();
}
}
return result.toList();
}
QList<QUuid> DomainServerSettingsManager::getBlacklistGroupIDs() {
QSet<QUuid> result;
foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) {
if (_groupForbiddens[groupKey]->isGroup()) {
result += _groupForbiddens[groupKey]->getGroupID();
}
}
return result.toList();
}
void DomainServerSettingsManager::debugDumpGroupsState() {
qDebug() << "--------- GROUPS ---------";
qDebug() << "_groupPermissions:";
foreach (NodePermissionsKey groupKey, _groupPermissions.keys()) {
NodePermissionsPointer perms = _groupPermissions[groupKey];
qDebug() << "| " << groupKey << perms;
}
qDebug() << "_groupForbiddens:";
foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) {
NodePermissionsPointer perms = _groupForbiddens[groupKey];
qDebug() << "| " << groupKey << perms;
}
qDebug() << "_groupIDs:";
foreach (QString groupName, _groupIDs.keys()) {
qDebug() << "| " << groupName << "==>" << _groupIDs[groupName];
}
qDebug() << "_groupNames:";
foreach (QUuid groupID, _groupNames.keys()) {
qDebug() << "| " << groupID << "==>" << _groupNames[groupID];
}
qDebug() << "_groupRanks:";
foreach (QUuid groupID, _groupRanks.keys()) {
QHash<QUuid, GroupRank>& ranksForGroup = _groupRanks[groupID];
qDebug() << "| " << groupID;
foreach (QUuid rankID, ranksForGroup.keys()) {
QString rankName = ranksForGroup[rankID].name;
qDebug() << "| " << rankID << rankName;
}
}
qDebug() << "_groupMembership";
foreach (QString userName, _groupMembership.keys()) {
QHash<QUuid, QUuid>& groupsForUser = _groupMembership[userName];
QString line = "";
foreach (QUuid groupID, groupsForUser.keys()) {
line += " g=" + groupID.toString() + ",r=" + groupsForUser[groupID].toString();
}
qDebug() << "| " << userName << line;
}
}

View file

@ -27,6 +27,11 @@ const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
class DomainServerSettingsManager : public QObject { class DomainServerSettingsManager : public QObject {
Q_OBJECT Q_OBJECT
@ -43,15 +48,55 @@ public:
QVariantMap& getDescriptorsMap(); QVariantMap& getDescriptorsMap();
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); } // these give access to anonymous/localhost/logged-in settings from the domain-server settings page
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); } bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); }
NodePermissions getStandardPermissionsForName(const QString& name) const; NodePermissions getStandardPermissionsForName(const NodePermissionsKey& name) const;
// these give access to permissions for specific user-names from the domain-server settings page
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name, 0); }
NodePermissions getPermissionsForName(const QString& name) const; NodePermissions getPermissionsForName(const QString& name) const;
QStringList getAllNames() { return _agentPermissions.keys(); } NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); }
QStringList getAllNames() const;
// these give access to permissions for specific groups from the domain-server settings page
bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const {
return _groupPermissions.contains(groupName, rankID);
}
NodePermissions getPermissionsForGroup(const QString& groupName, QUuid rankID) const;
NodePermissions getPermissionsForGroup(const QUuid& groupID, QUuid rankID) const;
// these remove permissions from users in certain groups
bool haveForbiddensForGroup(const QString& groupName, QUuid rankID) const {
return _groupForbiddens.contains(groupName, rankID);
}
NodePermissions getForbiddensForGroup(const QString& groupName, QUuid rankID) const;
NodePermissions getForbiddensForGroup(const QUuid& groupID, QUuid rankID) const;
QStringList getAllKnownGroupNames();
bool setGroupID(const QString& groupName, const QUuid& groupID);
GroupRank getGroupRank(QUuid groupID, QUuid rankID) { return _groupRanks[groupID][rankID]; }
QList<QUuid> getGroupIDs();
QList<QUuid> getBlacklistGroupIDs();
// these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api
void clearGroupMemberships(const QString& name) { _groupMembership[name].clear(); }
void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID);
QUuid isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member
// calls http api to refresh group information
void apiRefreshGroupInformation();
void debugDumpGroupsState();
signals: signals:
void updateNodePermissions(); void updateNodePermissions();
public slots:
void apiGetGroupIDJSONCallback(QNetworkReply& requestReply);
void apiGetGroupIDErrorCallback(QNetworkReply& requestReply);
void apiGetGroupRanksJSONCallback(QNetworkReply& requestReply);
void apiGetGroupRanksErrorCallback(QNetworkReply& requestReply);
private slots: private slots:
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message); void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
@ -76,11 +121,33 @@ private:
void validateDescriptorsMap(); void validateDescriptorsMap();
void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath); // these cause calls to metaverse's group api
void apiGetGroupID(const QString& groupName);
void apiGetGroupRanks(const QUuid& groupID);
void initializeGroupPermissions(NodePermissionsMap& permissionsRows, QString groupName, NodePermissionsPointer perms);
void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath);
void packPermissions(); void packPermissions();
void unpackPermissions(); void unpackPermissions();
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost bool ensurePermissionsForGroupRanks();
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner
NodePermissionsMap _agentPermissions; // specific account-names NodePermissionsMap _agentPermissions; // specific account-names
NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups
NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group
// these are like _groupPermissions and _groupForbiddens but with uuids rather than group-names in the keys
QHash<GroupByUUIDKey, NodePermissionsPointer> _groupPermissionsByUUID;
QHash<GroupByUUIDKey, NodePermissionsPointer> _groupForbiddensByUUID;
QHash<QString, QUuid> _groupIDs; // keep track of group-name to group-id mappings
QHash<QUuid, QString> _groupNames; // keep track of group-id to group-name mappings
// remember the responses to api/v1/groups/%1/ranks
QHash<QUuid, QHash<QUuid, GroupRank>> _groupRanks; // QHash<group-id, QHash<rankID, rank>>
// keep track of answers to api queries about which users are in which groups
QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>>
}; };
#endif // hifi_DomainServerSettingsManager_h #endif // hifi_DomainServerSettingsManager_h

View file

@ -200,8 +200,9 @@ void AccountManager::sendRequest(const QString& path,
const JSONCallbackParameters& callbackParams, const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray, const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart, QHttpMultiPart* dataMultiPart,
const QVariantMap& propertyMap) { const QVariantMap& propertyMap,
QUrlQuery query) {
if (thread() != QThread::currentThread()) { if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "sendRequest", QMetaObject::invokeMethod(this, "sendRequest",
Q_ARG(const QString&, path), Q_ARG(const QString&, path),
@ -213,9 +214,9 @@ void AccountManager::sendRequest(const QString& path,
Q_ARG(QVariantMap, propertyMap)); Q_ARG(QVariantMap, propertyMap));
return; return;
} }
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest; QNetworkRequest networkRequest;
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
@ -224,13 +225,17 @@ void AccountManager::sendRequest(const QString& path,
uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit());
QUrl requestURL = _authURL; QUrl requestURL = _authURL;
if (path.startsWith("/")) { if (path.startsWith("/")) {
requestURL.setPath(path); requestURL.setPath(path);
} else { } else {
requestURL.setPath("/" + path); requestURL.setPath("/" + path);
} }
if (!query.isEmpty()) {
requestURL.setQuery(query);
}
if (authType != AccountManagerAuth::None ) { if (authType != AccountManagerAuth::None ) {
if (hasValidAccessToken()) { if (hasValidAccessToken()) {
networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
@ -241,22 +246,21 @@ void AccountManager::sendRequest(const QString& path,
<< path << "that requires authentication"; << path << "that requires authentication";
return; return;
} }
} }
} }
networkRequest.setUrl(requestURL); networkRequest.setUrl(requestURL);
if (VERBOSE_HTTP_REQUEST_DEBUGGING) { if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qCDebug(networking) << "Making a request to" << qPrintable(requestURL.toString()); qCDebug(networking) << "Making a request to" << qPrintable(requestURL.toString());
if (!dataByteArray.isEmpty()) { if (!dataByteArray.isEmpty()) {
qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray); qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray);
} }
} }
QNetworkReply* networkReply = NULL; QNetworkReply* networkReply = NULL;
switch (operation) { switch (operation) {
case QNetworkAccessManager::GetOperation: case QNetworkAccessManager::GetOperation:
networkReply = networkAccessManager.get(networkRequest); networkReply = networkAccessManager.get(networkRequest);
@ -269,7 +273,7 @@ void AccountManager::sendRequest(const QString& path,
} else { } else {
networkReply = networkAccessManager.put(networkRequest, dataMultiPart); networkReply = networkAccessManager.put(networkRequest, dataMultiPart);
} }
// make sure dataMultiPart is destroyed when the reply is // make sure dataMultiPart is destroyed when the reply is
connect(networkReply, &QNetworkReply::destroyed, dataMultiPart, &QHttpMultiPart::deleteLater); connect(networkReply, &QNetworkReply::destroyed, dataMultiPart, &QHttpMultiPart::deleteLater);
} else { } else {
@ -280,7 +284,7 @@ void AccountManager::sendRequest(const QString& path,
networkReply = networkAccessManager.put(networkRequest, dataByteArray); networkReply = networkAccessManager.put(networkRequest, dataByteArray);
} }
} }
break; break;
case QNetworkAccessManager::DeleteOperation: case QNetworkAccessManager::DeleteOperation:
networkReply = networkAccessManager.sendCustomRequest(networkRequest, "DELETE"); networkReply = networkAccessManager.sendCustomRequest(networkRequest, "DELETE");
@ -289,7 +293,7 @@ void AccountManager::sendRequest(const QString& path,
// other methods not yet handled // other methods not yet handled
break; break;
} }
if (networkReply) { if (networkReply) {
if (!propertyMap.isEmpty()) { if (!propertyMap.isEmpty()) {
// we have properties to set on the reply so the user can check them after // we have properties to set on the reply so the user can check them after
@ -297,18 +301,18 @@ void AccountManager::sendRequest(const QString& path,
networkReply->setProperty(qPrintable(propertyKey), propertyMap.value(propertyKey)); networkReply->setProperty(qPrintable(propertyKey), propertyMap.value(propertyKey));
} }
} }
if (!callbackParams.isEmpty()) { if (!callbackParams.isEmpty()) {
// if we have information for a callback, insert the callbackParams into our local map // if we have information for a callback, insert the callbackParams into our local map
_pendingCallbackMap.insert(networkReply, callbackParams); _pendingCallbackMap.insert(networkReply, callbackParams);
if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) { if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)), callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
callbackParams.updateSlot.toStdString().c_str()); callbackParams.updateSlot.toStdString().c_str());
} }
} }
// if we ended up firing of a request, hook up to it now // if we ended up firing of a request, hook up to it now
connect(networkReply, SIGNAL(finished()), SLOT(processReply())); connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
} }

View file

@ -16,6 +16,7 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <QUrlQuery>
#include "NetworkAccessManager.h" #include "NetworkAccessManager.h"
@ -67,7 +68,8 @@ public:
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
const QByteArray& dataByteArray = QByteArray(), const QByteArray& dataByteArray = QByteArray(),
QHttpMultiPart* dataMultiPart = NULL, QHttpMultiPart* dataMultiPart = NULL,
const QVariantMap& propertyMap = QVariantMap()); const QVariantMap& propertyMap = QVariantMap(),
QUrlQuery query = QUrlQuery());
void setIsAgent(bool isAgent) { _isAgent = isAgent; } void setIsAgent(bool isAgent) { _isAgent = isAgent; }

View file

@ -0,0 +1,36 @@
//
// GroupRank.h
// libraries/networking/src/
//
// Created by Seth Alves on 2016-7-21.
// 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_GroupRank_h
#define hifi_GroupRank_h
class GroupRank {
public:
GroupRank() {}
GroupRank(QUuid id, unsigned int order, QString name, unsigned int membersCount) :
id(id), order(order), name(name), membersCount(membersCount) {}
QUuid id;
int order { -1 };
QString name;
int membersCount { -1 };
};
inline bool operator==(const GroupRank& lhs, const GroupRank& rhs) {
return
lhs.id == rhs.id &&
lhs.order == rhs.order &&
lhs.name == rhs.name &&
lhs.membersCount == rhs.membersCount;
}
inline bool operator!=(const GroupRank& lhs, const GroupRank& rhs) { return !(lhs == rhs); }
#endif // hifi_GroupRank_h

View file

@ -135,17 +135,21 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
_permissions = newPermissions; _permissions = newPermissions;
if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) { if (originalPermissions.can(NodePermissions::Permission::canAdjustLocks) !=
emit isAllowedEditorChanged(_permissions.canAdjustLocks); newPermissions.can(NodePermissions::Permission::canAdjustLocks)) {
emit isAllowedEditorChanged(_permissions.can(NodePermissions::Permission::canAdjustLocks));
} }
if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) { if (originalPermissions.can(NodePermissions::Permission::canRezPermanentEntities) !=
emit canRezChanged(_permissions.canRezPermanentEntities); newPermissions.can(NodePermissions::Permission::canRezPermanentEntities)) {
emit canRezChanged(_permissions.can(NodePermissions::Permission::canRezPermanentEntities));
} }
if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) { if (originalPermissions.can(NodePermissions::Permission::canRezTemporaryEntities) !=
emit canRezTmpChanged(_permissions.canRezTemporaryEntities); newPermissions.can(NodePermissions::Permission::canRezTemporaryEntities)) {
emit canRezTmpChanged(_permissions.can(NodePermissions::Permission::canRezTemporaryEntities));
} }
if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) { if (originalPermissions.can(NodePermissions::Permission::canWriteToAssetServer) !=
emit canWriteAssetsChanged(_permissions.canWriteToAssetServer); newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) {
emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer));
} }
} }

View file

@ -106,10 +106,10 @@ public:
void setSessionUUID(const QUuid& sessionUUID); void setSessionUUID(const QUuid& sessionUUID);
void setPermissions(const NodePermissions& newPermissions); void setPermissions(const NodePermissions& newPermissions);
bool isAllowedEditor() const { return _permissions.canAdjustLocks; } bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; } bool getThisNodeCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; } bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; } bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
QUdpSocket& getDTLSSocket(); QUdpSocket& getDTLSSocket();

View file

@ -65,10 +65,10 @@ public:
void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; } void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; }
NodePermissions getPermissions() const { return _permissions; } NodePermissions getPermissions() const { return _permissions; }
bool isAllowedEditor() const { return _permissions.canAdjustLocks; } bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
bool getCanRez() const { return _permissions.canRezPermanentEntities; } bool getCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; } bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; } bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message); void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message);
void addIgnoredNode(const QUuid& otherNodeID); void addIgnoredNode(const QUuid& otherNodeID);

View file

@ -283,12 +283,7 @@ void NodeList::sendDomainServerCheckIn() {
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
const QUuid& connectionToken = _domainHandler.getConnectionToken(); const QUuid& connectionToken = _domainHandler.getConnectionToken();
// we assume that we're on the same box as the DS if it has the same local address and bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull();
// it didn't present us with a connection token to use for username signature
bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|| (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull());
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain;
if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) { if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {
qWarning() << "A keypair is required to present a username signature to the domain-server" qWarning() << "A keypair is required to present a username signature to the domain-server"

View file

@ -13,76 +13,114 @@
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include "NodePermissions.h" #include "NodePermissions.h"
QString NodePermissions::standardNameLocalhost = QString("localhost"); NodePermissionsKey NodePermissions::standardNameLocalhost = NodePermissionsKey("localhost", 0);
QString NodePermissions::standardNameLoggedIn = QString("logged-in"); NodePermissionsKey NodePermissions::standardNameLoggedIn = NodePermissionsKey("logged-in", 0);
QString NodePermissions::standardNameAnonymous = QString("anonymous"); NodePermissionsKey NodePermissions::standardNameAnonymous = NodePermissionsKey("anonymous", 0);
NodePermissionsKey NodePermissions::standardNameFriends = NodePermissionsKey("friends", 0);
QStringList NodePermissions::standardNames = QList<QString>() QStringList NodePermissions::standardNames = QList<QString>()
<< NodePermissions::standardNameLocalhost << NodePermissions::standardNameLocalhost.first
<< NodePermissions::standardNameLoggedIn << NodePermissions::standardNameLoggedIn.first
<< NodePermissions::standardNameAnonymous; << NodePermissions::standardNameAnonymous.first
<< NodePermissions::standardNameFriends.first;
NodePermissions::NodePermissions(QMap<QString, QVariant> perms) {
_id = perms["permissions_id"].toString().toLower();
if (perms.contains("group_id")) {
_groupID = perms["group_id"].toUuid();
if (!_groupID.isNull()) {
_groupIDSet = true;
}
}
if (perms.contains("rank_id")) {
_rankID = QUuid(perms["rank_id"].toString());
}
permissions = NodePermissions::Permissions();
permissions |= perms["id_can_connect"].toBool() ? Permission::canConnectToDomain : Permission::none;
permissions |= perms["id_can_adjust_locks"].toBool() ? Permission::canAdjustLocks : Permission::none;
permissions |= perms["id_can_rez"].toBool() ? Permission::canRezPermanentEntities : Permission::none;
permissions |= perms["id_can_rez_tmp"].toBool() ? Permission::canRezTemporaryEntities : Permission::none;
permissions |= perms["id_can_write_to_asset_server"].toBool() ? Permission::canWriteToAssetServer : Permission::none;
permissions |= perms["id_can_connect_past_max_capacity"].toBool() ?
Permission::canConnectPastMaxCapacity : Permission::none;
}
QVariant NodePermissions::toVariant(QHash<QUuid, GroupRank> groupRanks) {
QMap<QString, QVariant> values;
values["permissions_id"] = _id;
if (_groupIDSet) {
values["group_id"] = _groupID;
if (groupRanks.contains(_rankID)) {
values["rank_id"] = _rankID;
values["rank_name"] = groupRanks[_rankID].name;
values["rank_order"] = groupRanks[_rankID].order;
}
}
values["id_can_connect"] = can(Permission::canConnectToDomain);
values["id_can_adjust_locks"] = can(Permission::canAdjustLocks);
values["id_can_rez"] = can(Permission::canRezPermanentEntities);
values["id_can_rez_tmp"] = can(Permission::canRezTemporaryEntities);
values["id_can_write_to_asset_server"] = can(Permission::canWriteToAssetServer);
values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity);
return QVariant(values);
}
void NodePermissions::setAll(bool value) {
permissions = NodePermissions::Permissions();
if (value) {
permissions = ~permissions;
}
}
NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) { NodePermissions& NodePermissions::operator|=(const NodePermissions& rhs) {
this->canConnectToDomain |= rhs.canConnectToDomain; permissions |= rhs.permissions;
this->canAdjustLocks |= rhs.canAdjustLocks;
this->canRezPermanentEntities |= rhs.canRezPermanentEntities;
this->canRezTemporaryEntities |= rhs.canRezTemporaryEntities;
this->canWriteToAssetServer |= rhs.canWriteToAssetServer;
this->canConnectPastMaxCapacity |= rhs.canConnectPastMaxCapacity;
return *this; 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;
}
NodePermissions& NodePermissions::operator&=(const NodePermissions& rhs) {
permissions &= rhs.permissions;
return *this;
}
NodePermissions NodePermissions::operator~() {
NodePermissions result = *this;
result.permissions = ~permissions;
return result;
}
QDataStream& operator<<(QDataStream& out, const NodePermissions& perms) { QDataStream& operator<<(QDataStream& out, const NodePermissions& perms) {
out << perms.canConnectToDomain; out << (uint)perms.permissions;
out << perms.canAdjustLocks;
out << perms.canRezPermanentEntities;
out << perms.canRezTemporaryEntities;
out << perms.canWriteToAssetServer;
out << perms.canConnectPastMaxCapacity;
return out; return out;
} }
QDataStream& operator>>(QDataStream& in, NodePermissions& perms) { QDataStream& operator>>(QDataStream& in, NodePermissions& perms) {
in >> perms.canConnectToDomain; uint permissionsInt;
in >> perms.canAdjustLocks; in >> permissionsInt;
in >> perms.canRezPermanentEntities; perms.permissions = (NodePermissions::Permissions)permissionsInt;
in >> perms.canRezTemporaryEntities;
in >> perms.canWriteToAssetServer;
in >> perms.canConnectPastMaxCapacity;
return in; return in;
} }
QDebug operator<<(QDebug debug, const NodePermissions& perms) { QDebug operator<<(QDebug debug, const NodePermissions& perms) {
debug.nospace() << "[permissions: " << perms.getID() << " --"; debug.nospace() << "[permissions: " << perms.getID() << "/" << perms.getVerifiedUserName() << " -- ";
if (perms.canConnectToDomain) { debug.nospace() << "rank=" << perms.getRankID()
<< ", groupID=" << perms.getGroupID() << "/" << (perms.isGroup() ? "y" : "n");
if (perms.can(NodePermissions::Permission::canConnectToDomain)) {
debug << " connect"; debug << " connect";
} }
if (perms.canAdjustLocks) { if (perms.can(NodePermissions::Permission::canAdjustLocks)) {
debug << " locks"; debug << " locks";
} }
if (perms.canRezPermanentEntities) { if (perms.can(NodePermissions::Permission::canRezPermanentEntities)) {
debug << " rez"; debug << " rez";
} }
if (perms.canRezTemporaryEntities) { if (perms.can(NodePermissions::Permission::canRezTemporaryEntities)) {
debug << " rez-tmp"; debug << " rez-tmp";
} }
if (perms.canWriteToAssetServer) { if (perms.can(NodePermissions::Permission::canWriteToAssetServer)) {
debug << " asset-server"; debug << " asset-server";
} }
if (perms.canConnectPastMaxCapacity) { if (perms.can(NodePermissions::Permission::canConnectPastMaxCapacity)) {
debug << " ignore-max-cap"; debug << " ignore-max-cap";
} }
debug.nospace() << "]"; debug.nospace() << "]";

View file

@ -18,90 +18,108 @@
#include <QVariant> #include <QVariant>
#include <QUuid> #include <QUuid>
#include "GroupRank.h"
class NodePermissions; class NodePermissions;
using NodePermissionsPointer = std::shared_ptr<NodePermissions>; using NodePermissionsPointer = std::shared_ptr<NodePermissions>;
using NodePermissionsKey = QPair<QString, QUuid>; // name, rankID
using NodePermissionsKeyList = QList<QPair<QString, QUuid>>;
class NodePermissions { class NodePermissions {
public: public:
NodePermissions() { _id = QUuid::createUuid().toString(); } NodePermissions() { _id = QUuid::createUuid().toString(); _rankID = QUuid(); }
NodePermissions(const QString& name) { _id = name.toLower(); } NodePermissions(const QString& name) { _id = name.toLower(); _rankID = QUuid(); }
NodePermissions(QMap<QString, QVariant> perms) { NodePermissions(const NodePermissionsKey& key) { _id = key.first.toLower(); _rankID = key.second; }
_id = perms["permissions_id"].toString().toLower(); NodePermissions(QMap<QString, QVariant> perms);
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; } const QString& getID() const { return _id; } // a user-name or a group-name, not verified
void setID(const QString& id) { _id = id; }
void setRankID(QUuid& rankID) { _rankID = rankID; }
const QUuid& getRankID() const { return _rankID; }
NodePermissionsKey getKey() const { return NodePermissionsKey(_id, _rankID); }
// the _id member isn't authenticated and _username is. // the _id member isn't authenticated/verified and _username is.
void setUserName(QString userName) { _userName = userName.toLower(); } void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); }
QString getUserName() { return _userName; } const QString& getVerifiedUserName() const { return _verifiedUserName; }
void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }}
const QUuid& getGroupID() const { return _groupID; }
bool isGroup() const { return _groupIDSet; }
bool isAssignment { false }; bool isAssignment { false };
// these 3 names have special meaning. // these 3 names have special meaning.
static QString standardNameLocalhost; static NodePermissionsKey standardNameLocalhost;
static QString standardNameLoggedIn; static NodePermissionsKey standardNameLoggedIn;
static QString standardNameAnonymous; static NodePermissionsKey standardNameAnonymous;
static NodePermissionsKey standardNameFriends;
static QStringList standardNames; static QStringList standardNames;
// the initializations here should match the defaults in describe-settings.json enum class Permission {
bool canConnectToDomain { true }; none = 0,
bool canAdjustLocks { false }; canConnectToDomain = 1,
bool canRezPermanentEntities { false }; canAdjustLocks = 2,
bool canRezTemporaryEntities { false }; canRezPermanentEntities = 4,
bool canWriteToAssetServer { false }; canRezTemporaryEntities = 8,
bool canConnectPastMaxCapacity { false }; canWriteToAssetServer = 16,
canConnectPastMaxCapacity = 32
};
Q_DECLARE_FLAGS(Permissions, Permission)
Permissions permissions;
void setAll(bool value) { QVariant toVariant(QHash<QUuid, GroupRank> groupRanks = QHash<QUuid, GroupRank>());
canConnectToDomain = value;
canAdjustLocks = value;
canRezPermanentEntities = value;
canRezTemporaryEntities = value;
canWriteToAssetServer = value;
canConnectPastMaxCapacity = value;
}
QVariant toVariant() { void setAll(bool value);
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 NodePermissions& rhs);
NodePermissions& operator|=(const NodePermissionsPointer& rhs); NodePermissions& operator&=(const NodePermissions& rhs);
NodePermissions operator~();
friend QDataStream& operator<<(QDataStream& out, const NodePermissions& perms); friend QDataStream& operator<<(QDataStream& out, const NodePermissions& perms);
friend QDataStream& operator>>(QDataStream& in, NodePermissions& perms); friend QDataStream& operator>>(QDataStream& in, NodePermissions& perms);
void clear(Permission p) { permissions &= (Permission) (~(uint)p); }
void set(Permission p) { permissions |= p; }
bool can(Permission p) const { return permissions.testFlag(p); }
protected: protected:
QString _id; QString _id;
QString _userName; QUuid _rankID { QUuid() }; // 0 unless this is for a group
QString _verifiedUserName;
bool _groupIDSet { false };
QUuid _groupID;
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(NodePermissions::Permissions)
// wrap QHash in a class that forces all keys to be lowercase // wrap QHash in a class that forces all keys to be lowercase
class NodePermissionsMap { class NodePermissionsMap {
public: public:
NodePermissionsMap() { } NodePermissionsMap() { }
NodePermissionsPointer& operator[](const QString& key) { return _data[key.toLower()]; } NodePermissionsPointer& operator[](const NodePermissionsKey& key) {
NodePermissionsPointer operator[](const QString& key) const { return _data.value(key.toLower()); } NodePermissionsKey dataKey(key.first.toLower(), key.second);
bool contains(const QString& key) const { return _data.contains(key.toLower()); } if (!_data.contains(dataKey)) {
QList<QString> keys() const { return _data.keys(); } _data[dataKey] = NodePermissionsPointer(new NodePermissions(key));
QHash<QString, NodePermissionsPointer> get() { return _data; } }
return _data[dataKey];
}
NodePermissionsPointer operator[](const NodePermissionsKey& key) const {
return _data.value(NodePermissionsKey(key.first.toLower(), key.second));
}
bool contains(const NodePermissionsKey& key) const {
return _data.contains(NodePermissionsKey(key.first.toLower(), key.second));
}
bool contains(const QString& keyFirst, QUuid keySecond) const {
return _data.contains(NodePermissionsKey(keyFirst.toLower(), keySecond));
}
QList<NodePermissionsKey> keys() const { return _data.keys(); }
QHash<NodePermissionsKey, NodePermissionsPointer> get() { return _data; }
void clear() { _data.clear(); } void clear() { _data.clear(); }
void remove(const NodePermissionsKey& key) { _data.remove(key); }
private: private:
QHash<QString, NodePermissionsPointer> _data; QHash<NodePermissionsKey, NodePermissionsPointer> _data;
}; };
@ -109,6 +127,8 @@ const NodePermissions DEFAULT_AGENT_PERMISSIONS;
QDebug operator<<(QDebug debug, const NodePermissions& perms); QDebug operator<<(QDebug debug, const NodePermissions& perms);
QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms); QDebug operator<<(QDebug debug, const NodePermissionsPointer& perms);
NodePermissionsPointer& operator|=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs); NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, const NodePermissionsPointer& rhs);
NodePermissionsPointer& operator&=(NodePermissionsPointer& lhs, NodePermissions::Permission rhs);
NodePermissionsPointer operator~(NodePermissionsPointer& lhs);
#endif // hifi_NodePermissions_h #endif // hifi_NodePermissions_h

View file

@ -12,7 +12,7 @@
// //
HIFI_PUBLIC_BUCKET = 'http://s3.amazonaws.com/hifi-public/'; HIFI_PUBLIC_BUCKET = 'http://s3.amazonaws.com/hifi-public/';
Script.include('../../libraries/toolBars.js'); Script.include('/~/system/libraries/toolBars.js');
const DEFAULT_NUM_LAYERS = 16; const DEFAULT_NUM_LAYERS = 16;
const DEFAULT_BASE_DIMENSION = { x: 7, y: 2, z: 7 }; const DEFAULT_BASE_DIMENSION = { x: 7, y: 2, z: 7 };