mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-14 11:46:34 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into default-when-av-fails
This commit is contained in:
commit
7e94d4c694
26 changed files with 1852 additions and 364 deletions
|
@ -384,13 +384,13 @@
|
|||
"name": "standard_permissions",
|
||||
"type": "table",
|
||||
"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 “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"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 “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"caption": "Standard Permissions",
|
||||
"can_add_new_rows": false,
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User / Group",
|
||||
"label": "Type of User",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
|
@ -409,7 +409,7 @@
|
|||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": true
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
|
@ -451,6 +451,193 @@
|
|||
"non-deletable-row-key": "permissions_id",
|
||||
"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 “locked” 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’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’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 “locked” 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’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’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",
|
||||
"type": "table",
|
||||
|
@ -459,11 +646,11 @@
|
|||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User / Group",
|
||||
"label": "User",
|
||||
"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 “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"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 “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
|
||||
"span": 6
|
||||
}
|
||||
],
|
||||
|
@ -478,7 +665,7 @@
|
|||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": true
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
|
|
|
@ -3,6 +3,10 @@ body {
|
|||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.table-lead .lead-line {
|
||||
background-color: black;
|
||||
}
|
||||
|
@ -20,7 +24,9 @@ body {
|
|||
top: 40px;
|
||||
}
|
||||
|
||||
.table .value-row td, .table .inputs td {
|
||||
.table .value-row td,
|
||||
.table .value-category td,
|
||||
.table .inputs td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
@ -31,6 +37,31 @@ body {
|
|||
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 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
@ -133,7 +164,7 @@ table .headers + .headers td {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,20 @@ var Settings = {
|
|||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
DATA_COL_CLASS: 'value-col',
|
||||
DATA_CATEGORY_CLASS: 'value-category',
|
||||
ADD_ROW_BUTTON_CLASS: 'add-row',
|
||||
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
|
||||
DEL_ROW_BUTTON_CLASS: '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_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
|
||||
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 + "'>";
|
||||
setting_value = _(values).valueForKeyPath(keypath);
|
||||
|
||||
if (typeof setting_value == 'undefined' || setting_value === null) {
|
||||
if (_.isUndefined(setting_value) || _.isNull(setting_value)) {
|
||||
if (_.has(setting, 'default')) {
|
||||
setting_value = setting.default;
|
||||
} else {
|
||||
|
@ -49,11 +59,11 @@ var viewHelpers = {
|
|||
}
|
||||
|
||||
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' : '')
|
||||
+ " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='"
|
||||
+ 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') {
|
||||
|
@ -162,19 +172,31 @@ $(document).ready(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(){
|
||||
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(){
|
||||
moveTableRow(this, true);
|
||||
moveTableRow($(this).closest('tr'), true);
|
||||
});
|
||||
|
||||
$('#' + 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){
|
||||
|
@ -196,10 +218,11 @@ $(document).ready(function(){
|
|||
}
|
||||
|
||||
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
|
||||
$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',
|
||||
html_id: Settings.PLACES_TABLE_ID,
|
||||
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.",
|
||||
read_only: true,
|
||||
columns: [
|
||||
|
@ -771,7 +794,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
modal_buttons["success"] = {
|
||||
label: 'Create new domain',
|
||||
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." +
|
||||
|
@ -922,8 +945,9 @@ $('body').on('click', '.save-button', function(e){
|
|||
|
||||
function makeTable(setting, keypath, setting_value, isLocked) {
|
||||
var isArray = !_.has(setting, 'key');
|
||||
var isHash = !isArray;
|
||||
|
||||
var categoryKey = setting.categorize_by_key;
|
||||
var isCategorized = !!categoryKey && isArray;
|
||||
|
||||
if (!isArray && setting.can_order) {
|
||||
setting.can_order = false;
|
||||
}
|
||||
|
@ -937,9 +961,10 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
var nonDeletableRowKey = setting["non-deletable-row-key"];
|
||||
var nonDeletableRowValues = setting["non-deletable-row-values"];
|
||||
|
||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name
|
||||
+ "' name='" + keypath + "' id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath)
|
||||
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' " +
|
||||
"data-short-name='" + setting.name + "' name='" + keypath + "' " +
|
||||
"id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " +
|
||||
"data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||
|
||||
if (setting.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
|
||||
}
|
||||
|
||||
var numVisibleColumns = 0;
|
||||
_.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 (setting.can_order) {
|
||||
numVisibleColumns++;
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>"
|
||||
numVisibleColumns++;
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>";
|
||||
}
|
||||
|
||||
// populate rows in the table from existing values
|
||||
var row_num = 1;
|
||||
|
||||
if (keypath.length > 0 && _.size(setting_value) > 0) {
|
||||
var rowIsObject = setting.columns.length > 1;
|
||||
|
||||
_.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) {
|
||||
html += "<td class='numbered'>" + row_num + "</td>"
|
||||
|
@ -1003,8 +1047,8 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|
||||
_.each(setting.columns, function(col) {
|
||||
|
||||
var colValue, colName;
|
||||
if (isArray) {
|
||||
rowIsObject = setting.columns.length > 1;
|
||||
colValue = rowIsObject ? row[col.name] : row;
|
||||
colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : "");
|
||||
} else {
|
||||
|
@ -1016,20 +1060,28 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|| (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1);
|
||||
|
||||
if (isArray && col.type === "checkbox" && col.editable) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||
+ "name='" + colName + "'" + (colValue ? " checked" : "") + " /></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='checkbox' class='form-control table-checkbox' " +
|
||||
"name='" + colName + "'" + (colValue ? " checked" : "") + "/>" +
|
||||
"</td>";
|
||||
} else if (isArray && col.type === "time" && col.editable) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='time' class='form-control table-time' "
|
||||
+ "name='" + colName + "' value='" + (colValue || col.default || "00:00") + "' /></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='time' class='form-control table-time' name='" + colName + "' " +
|
||||
"value='" + (colValue || col.default || "00:00") + "'/>" +
|
||||
"</td>";
|
||||
} else {
|
||||
// Use a hidden input so that the values are posted.
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "' name='" + colName + "'>"
|
||||
+ colValue + "<input type='hidden' name='" + colName + "' value='" + colValue + "'/></td>";
|
||||
html +=
|
||||
"<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 (setting.can_order) {
|
||||
|
@ -1047,24 +1099,53 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|
||||
html += "</tr>"
|
||||
|
||||
if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) {
|
||||
html += makeTableInputs(setting, categoryPair, categoryValue);
|
||||
}
|
||||
|
||||
row_num++
|
||||
});
|
||||
}
|
||||
|
||||
// populate inputs in the table for new values
|
||||
if (!isLocked && !setting.read_only && setting.can_add_new_rows) {
|
||||
html += makeTableInputs(setting)
|
||||
if (!isLocked && !setting.read_only) {
|
||||
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>"
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting) {
|
||||
var html = "<tr class='inputs'>"
|
||||
function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) {
|
||||
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) {
|
||||
html += "<td class='numbered'></td>"
|
||||
html += "<td class='numbered'></td>";
|
||||
}
|
||||
|
||||
if (setting.key) {
|
||||
|
@ -1074,15 +1155,21 @@ function makeTableInputs(setting) {
|
|||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default;
|
||||
if (col.type === "checkbox") {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||
+ "name='" + col.name + "'" + (col.default ? " checked" : "") + "/></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='checkbox' class='form-control table-checkbox' " +
|
||||
"name='" + col.name + "'" + (defaultValue ? " checked" : "") + "/>" +
|
||||
"</td>";
|
||||
} else {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||
</td>"
|
||||
html +=
|
||||
"<td " + (col.hidden ? "style='display: none;'" : "") + " class='" + Settings.DATA_COL_CLASS + "' " +
|
||||
"name='" + col.name + "'>" +
|
||||
"<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.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>"
|
||||
|
||||
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) {
|
||||
// figure out which group this input is in
|
||||
var panelParentID = changedElement.closest('.panel').attr('id');
|
||||
|
@ -1134,13 +1239,12 @@ function badgeSidebarForDifferences(changedElement) {
|
|||
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
|
||||
}
|
||||
|
||||
function addTableRow(add_glyphicon) {
|
||||
var row = $(add_glyphicon).closest('tr')
|
||||
function addTableRow(row) {
|
||||
var table = row.parents('table');
|
||||
var isArray = table.data('setting-type') === 'array';
|
||||
var keepField = row.data("keep-field");
|
||||
|
||||
var table = row.parents('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS);
|
||||
|
||||
if (!isArray) {
|
||||
// Check key spaces
|
||||
|
@ -1257,10 +1361,12 @@ function addTableRow(add_glyphicon) {
|
|||
} else {
|
||||
console.log("Unknown table element")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
input_clone.find('input').each(function(){
|
||||
$(this).val($(this).attr('data-default'));
|
||||
input_clone.children('td').each(function () {
|
||||
if ($(this).attr("name") !== keepField) {
|
||||
$(this).find("input").val($(this).attr('data-default'));
|
||||
}
|
||||
});
|
||||
|
||||
if (isArray) {
|
||||
|
@ -1272,44 +1378,132 @@ function addTableRow(add_glyphicon) {
|
|||
|
||||
badgeSidebarForDifferences($(table))
|
||||
|
||||
row.parent().append(input_clone)
|
||||
row.after(input_clone)
|
||||
}
|
||||
|
||||
function deleteTableRow(delete_glyphicon) {
|
||||
var row = $(delete_glyphicon).closest('tr')
|
||||
function deleteTableRow($row) {
|
||||
var $table = $row.closest('table');
|
||||
var categoryName = $row.data("category");
|
||||
var isArray = $table.data('setting-type') === 'array';
|
||||
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
row.empty();
|
||||
$row.empty();
|
||||
|
||||
if (!isArray) {
|
||||
row.html("<input type='hidden' class='form-control' name='"
|
||||
+ row.attr('name') + "' data-changed='true' value=''>");
|
||||
$row.html("<input type='hidden' class='form-control' name='" + $row.attr('name') + "' data-changed='true' value=''>");
|
||||
} else {
|
||||
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows(row)
|
||||
if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) {
|
||||
// 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
|
||||
row.remove()
|
||||
$row.remove();
|
||||
} else {
|
||||
// this is the last row, we can't remove it completely since we need to post an empty array
|
||||
|
||||
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
|
||||
row.addClass('empty-array-row')
|
||||
|
||||
row.html("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '')
|
||||
+ "' data-changed='true' value=''>");
|
||||
$row
|
||||
.removeClass(Settings.DATA_ROW_CLASS)
|
||||
.removeClass(Settings.NEW_ROW_CLASS)
|
||||
.removeAttr("data-category")
|
||||
.addClass('empty-array-row')
|
||||
.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
|
||||
badgeSidebarForDifferences($(table))
|
||||
badgeSidebarForDifferences($table);
|
||||
}
|
||||
|
||||
function moveTableRow(move_glyphicon, move_up) {
|
||||
var row = $(move_glyphicon).closest('tr')
|
||||
function addTableCategory($categoryInputRow) {
|
||||
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 isArray = table.data('setting-type') === 'array'
|
||||
if (!isArray) {
|
||||
|
|
|
@ -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() {
|
||||
// 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
|
||||
|
@ -129,40 +208,29 @@ void DomainGatekeeper::updateNodePermissions() {
|
|||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
|
||||
QString username = node->getPermissions().getUserName();
|
||||
NodePermissions userPerms(username);
|
||||
// the id and the username in NodePermissions will often be the same, but id is set before
|
||||
// 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) {
|
||||
// this node is an assignment-client
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
|
||||
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
|
||||
} else {
|
||||
// this node is an agent
|
||||
userPerms.setAll(false);
|
||||
|
||||
const QHostAddress& addr = node->getLocalSocket().getAddress();
|
||||
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||
addr == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
} else {
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
} else {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
}
|
||||
}
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername);
|
||||
}
|
||||
|
||||
node->setPermissions(userPerms);
|
||||
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
qDebug() << "node" << node->getUUID() << "no longer has permission to connect.";
|
||||
// hang up on this node
|
||||
nodesToKill << node;
|
||||
|
@ -215,12 +283,13 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
||||
_pendingAssignedNodes.erase(it);
|
||||
|
||||
// always allow assignment clients to create and destroy entities
|
||||
NodePermissions userPerms;
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
|
||||
// always allow assignment clients to create and destroy entities
|
||||
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
|
||||
newNode->setPermissions(userPerms);
|
||||
return newNode;
|
||||
}
|
||||
|
@ -234,64 +303,58 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// start with empty permissions
|
||||
NodePermissions userPerms(username);
|
||||
NodePermissions userPerms(NodePermissionsKey(username, 0));
|
||||
userPerms.setAll(false);
|
||||
|
||||
// check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
|
||||
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
||||
bool isLocalUser =
|
||||
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
qDebug() << "user-permissions: is local user, so:" << userPerms;
|
||||
}
|
||||
|
||||
if (!username.isEmpty() && usernameSignature.isEmpty()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
QString verifiedUsername; // if this remains empty, consider this an anonymous connection attempt
|
||||
if (!username.isEmpty()) {
|
||||
if (usernameSignature.isEmpty()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
getGroupMemberships(username); // optimistically get started on group memberships
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because we have no username-signature:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
// they didn't tell us who they are
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
qDebug() << "user-permissions: no username, so:" << userPerms;
|
||||
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// they are sent us a username and the signature verifies it
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
// we have specific permissions for this user.
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
qDebug() << "user-permissions: specific user matches, so:" << userPerms;
|
||||
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// they sent us a username and the signature verifies it
|
||||
getGroupMemberships(username);
|
||||
verifiedUsername = username;
|
||||
} else {
|
||||
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
qDebug() << "user-permissions: user is logged in, so:" << userPerms;
|
||||
}
|
||||
userPerms.setUserName(username);
|
||||
} else {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because signature verification failed:" << username;
|
||||
#endif
|
||||
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.",
|
||||
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to permissions:" << username;
|
||||
#endif
|
||||
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
|
||||
sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to max capacity:" << username;
|
||||
#endif
|
||||
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
|
||||
// is unable to connect to the domain
|
||||
hintNodeID = node->getUUID();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -328,6 +389,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "accepting login:" << username;
|
||||
#endif
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
|
@ -367,7 +432,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
// 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());
|
||||
|
||||
|
@ -471,10 +536,20 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
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
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "publicKeyJSONErrorCallback";
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
|
||||
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) {
|
||||
QString username = usernameRegex.cap(1);
|
||||
|
||||
qDebug() << "Storing a public key for user" << username;
|
||||
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
_userPublicKeys[username] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
_userPublicKeys[username.toLower()] =
|
||||
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) {
|
||||
|
@ -645,3 +732,159 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage>
|
|||
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
|
||||
}
|
||||
|
|
|
@ -51,7 +51,16 @@ public slots:
|
|||
void processICEPeerInformationPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
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:
|
||||
void killNode(SharedNodePointer node);
|
||||
void connectedNode(SharedNodePointer node);
|
||||
|
@ -93,6 +102,14 @@ private:
|
|||
|
||||
QHash<QString, QUuid> _connectionTokenHash;
|
||||
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();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -184,10 +184,10 @@ void DomainMetadata::securityChanged(bool send) {
|
|||
QString restriction;
|
||||
|
||||
const auto& settingsManager = static_cast<DomainServer*>(parent())->_settingsManager;
|
||||
bool hasAnonymousAccess =
|
||||
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).canConnectToDomain;
|
||||
bool hasHifiAccess =
|
||||
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).canConnectToDomain;
|
||||
bool hasAnonymousAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
if (hasAnonymousAccess) {
|
||||
restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON;
|
||||
} else if (hasHifiAccess) {
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <UUID.h>
|
||||
#include <LogHandler.h>
|
||||
#include <ServerPathUtils.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
#include "NodeConnectionData.h"
|
||||
|
@ -106,11 +107,15 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
||||
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
||||
|
||||
setupGroupCacheRefresh();
|
||||
|
||||
// if we were given a certificate/private key or oauth credentials they must succeed
|
||||
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_settingsManager.apiRefreshGroupInformation();
|
||||
|
||||
setupNodeListAndAssignments();
|
||||
setupAutomaticNetworking();
|
||||
if (!getID().isNull()) {
|
||||
|
@ -1098,12 +1103,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
||||
|
||||
|
||||
// add access level for anonymous connections
|
||||
// consider the domain to be "restricted" if anonymous connections are disallowed
|
||||
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||
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());
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
|||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
|
||||
static int const EXIT_CODE_REBOOT;
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
|
@ -64,7 +64,7 @@ public slots:
|
|||
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
||||
|
@ -74,7 +74,7 @@ private slots:
|
|||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); }
|
||||
void sendHeartbeatToIceServer();
|
||||
|
||||
|
||||
void handleConnectedNode(SharedNodePointer newNode);
|
||||
|
||||
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
||||
|
@ -96,7 +96,7 @@ signals:
|
|||
void iceServerChanged();
|
||||
void userConnected();
|
||||
void userDisconnected();
|
||||
|
||||
|
||||
private:
|
||||
const QUuid& getID();
|
||||
|
||||
|
@ -136,7 +136,7 @@ private:
|
|||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
|
@ -151,7 +151,9 @@ private:
|
|||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
|
||||
void setupGroupCacheRefresh();
|
||||
|
||||
DomainGatekeeper _gatekeeper;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
|
@ -184,6 +186,7 @@ private:
|
|||
DomainMetadata* _metadata { nullptr };
|
||||
QTimer* _iceHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseGroupCacheTimer { nullptr };
|
||||
|
||||
QList<QHostAddress> _iceServerAddresses;
|
||||
QSet<QHostAddress> _failedIceServerAddresses;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QUrlQuery>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include <Assignment.h>
|
||||
|
@ -31,9 +31,6 @@
|
|||
|
||||
#include "DomainServerSettingsManager.h"
|
||||
|
||||
#define WANT_DEBUG 1
|
||||
|
||||
|
||||
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
|
||||
|
||||
const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
||||
|
@ -219,40 +216,50 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
new NodePermissions(NodePermissions::standardNameAnonymous));
|
||||
_standardAgentPermissions[NodePermissions::standardNameLoggedIn].reset(
|
||||
new NodePermissions(NodePermissions::standardNameLoggedIn));
|
||||
_standardAgentPermissions[NodePermissions::standardNameFriends].reset(
|
||||
new NodePermissions(NodePermissions::standardNameFriends));
|
||||
|
||||
if (isRestrictedAccess) {
|
||||
// only users in allow-users list can connect
|
||||
_standardAgentPermissions[NodePermissions::standardNameAnonymous]->canConnectToDomain = false;
|
||||
_standardAgentPermissions[NodePermissions::standardNameLoggedIn]->canConnectToDomain = false;
|
||||
_standardAgentPermissions[NodePermissions::standardNameAnonymous]->clear(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
_standardAgentPermissions[NodePermissions::standardNameLoggedIn]->clear(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
} // else anonymous and logged-in retain default of canConnectToDomain = true
|
||||
|
||||
foreach (QString allowedUser, allowedUsers) {
|
||||
// even if isRestrictedAccess is false, we have to add explicit rows for these users.
|
||||
// defaults to canConnectToDomain = true
|
||||
_agentPermissions[allowedUser].reset(new NodePermissions(allowedUser));
|
||||
_agentPermissions[NodePermissionsKey(allowedUser, 0)].reset(new NodePermissions(allowedUser));
|
||||
_agentPermissions[NodePermissionsKey(allowedUser, 0)]->set(NodePermissions::Permission::canConnectToDomain);
|
||||
}
|
||||
|
||||
foreach (QString allowedEditor, allowedEditors) {
|
||||
if (!_agentPermissions.contains(allowedEditor)) {
|
||||
_agentPermissions[allowedEditor].reset(new NodePermissions(allowedEditor));
|
||||
NodePermissionsKey editorKey(allowedEditor, 0);
|
||||
if (!_agentPermissions.contains(editorKey)) {
|
||||
_agentPermissions[editorKey].reset(new NodePermissions(allowedEditor));
|
||||
if (isRestrictedAccess) {
|
||||
// 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();
|
||||
foreach (auto permissionsSet, permissionsSets) {
|
||||
foreach (QString userName, permissionsSet.keys()) {
|
||||
foreach (NodePermissionsKey userKey, permissionsSet.keys()) {
|
||||
if (onlyEditorsAreRezzers) {
|
||||
permissionsSet[userName]->canRezPermanentEntities = permissionsSet[userName]->canAdjustLocks;
|
||||
permissionsSet[userName]->canRezTemporaryEntities = permissionsSet[userName]->canAdjustLocks;
|
||||
if (permissionsSet[userKey]->can(NodePermissions::Permission::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 {
|
||||
permissionsSet[userName]->canRezPermanentEntities = true;
|
||||
permissionsSet[userName]->canRezTemporaryEntities = true;
|
||||
permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities);
|
||||
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,
|
||||
NodePermissionsMap& agentPermissions,
|
||||
NodePermissionsMap& permissionsRows,
|
||||
QString keyPath) {
|
||||
// find (or create) the "security" section of the settings map
|
||||
QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security");
|
||||
if (!security || !security->canConvert(QMetaType::QVariantMap)) {
|
||||
security = valueForKeyPath(_configMap.getUserConfig(), "security", true);
|
||||
(*security) = QVariantMap();
|
||||
}
|
||||
|
||||
// save settings for anonymous / logged-in / localhost
|
||||
// find (or create) whichever subsection of "security" we are packing
|
||||
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath);
|
||||
if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) {
|
||||
permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true);
|
||||
(*permissions) = QVariantList();
|
||||
}
|
||||
|
||||
// convert details for each member of the subsection
|
||||
QVariantList* permissionsList = reinterpret_cast<QVariantList*>(permissions);
|
||||
(*permissionsList).clear();
|
||||
foreach (QString userName, agentPermissions.keys()) {
|
||||
*permissionsList += agentPermissions[userName]->toVariant();
|
||||
QList<NodePermissionsKey> permissionsKeys = permissionsRows.keys();
|
||||
|
||||
// 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() {
|
||||
// transfer details from _agentPermissions to _configMap
|
||||
|
||||
// save settings for anonymous / logged-in / localhost
|
||||
packPermissionsForMap("standard_permissions", _standardAgentPermissions, AGENT_STANDARD_PERMISSIONS_KEYPATH);
|
||||
|
||||
// save settings for specific users
|
||||
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();
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
}
|
||||
|
@ -356,10 +435,13 @@ void DomainServerSettingsManager::unpackPermissions() {
|
|||
|
||||
_standardAgentPermissions.clear();
|
||||
_agentPermissions.clear();
|
||||
_groupPermissions.clear();
|
||||
_groupForbiddens.clear();
|
||||
|
||||
bool foundLocalhost = false;
|
||||
bool foundAnonymous = false;
|
||||
bool foundLoggedIn = false;
|
||||
bool foundFriends = false;
|
||||
bool needPack = false;
|
||||
|
||||
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) = 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();
|
||||
foreach (QVariant permsHash, standardPermissionsList) {
|
||||
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
|
||||
QString id = perms->getID();
|
||||
foundLocalhost |= (id == NodePermissions::standardNameLocalhost);
|
||||
foundAnonymous |= (id == NodePermissions::standardNameAnonymous);
|
||||
foundLoggedIn |= (id == NodePermissions::standardNameLoggedIn);
|
||||
if (_standardAgentPermissions.contains(id)) {
|
||||
NodePermissionsKey idKey = NodePermissionsKey(id, 0);
|
||||
foundLocalhost |= (idKey == NodePermissions::standardNameLocalhost);
|
||||
foundAnonymous |= (idKey == NodePermissions::standardNameAnonymous);
|
||||
foundLoggedIn |= (idKey == NodePermissions::standardNameLoggedIn);
|
||||
foundFriends |= (idKey == NodePermissions::standardNameFriends);
|
||||
if (_standardAgentPermissions.contains(idKey)) {
|
||||
qDebug() << "duplicate name in standard permissions table: " << id;
|
||||
_standardAgentPermissions[id] |= perms;
|
||||
*(_standardAgentPermissions[idKey]) |= *perms;
|
||||
needPack = true;
|
||||
} else {
|
||||
_standardAgentPermissions[id] = perms;
|
||||
_standardAgentPermissions[idKey] = perms;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,12 +491,51 @@ void DomainServerSettingsManager::unpackPermissions() {
|
|||
foreach (QVariant permsHash, permissionsList) {
|
||||
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
|
||||
QString id = perms->getID();
|
||||
if (_agentPermissions.contains(id)) {
|
||||
NodePermissionsKey idKey = NodePermissionsKey(id, 0);
|
||||
if (_agentPermissions.contains(idKey)) {
|
||||
qDebug() << "duplicate name in permissions table: " << id;
|
||||
_agentPermissions[id] |= perms;
|
||||
*(_agentPermissions[idKey]) |= *perms;
|
||||
needPack = true;
|
||||
} 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) {
|
||||
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) };
|
||||
perms->setAll(true);
|
||||
_standardAgentPermissions[perms->getID()] = perms;
|
||||
_standardAgentPermissions[perms->getKey()] = perms;
|
||||
needPack = true;
|
||||
}
|
||||
if (!foundAnonymous) {
|
||||
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) };
|
||||
_standardAgentPermissions[perms->getID()] = perms;
|
||||
_standardAgentPermissions[perms->getKey()] = perms;
|
||||
needPack = true;
|
||||
}
|
||||
if (!foundLoggedIn) {
|
||||
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) };
|
||||
_standardAgentPermissions[perms->getID()] = perms;
|
||||
_standardAgentPermissions[perms->getKey()] = perms;
|
||||
needPack = true;
|
||||
}
|
||||
if (!foundFriends) {
|
||||
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameFriends) };
|
||||
_standardAgentPermissions[perms->getKey()] = perms;
|
||||
needPack = true;
|
||||
}
|
||||
|
||||
needPack |= ensurePermissionsForGroupRanks();
|
||||
|
||||
if (needPack) {
|
||||
packPermissions();
|
||||
|
@ -428,20 +570,93 @@ void DomainServerSettingsManager::unpackPermissions() {
|
|||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "--------------- permissions ---------------------";
|
||||
QList<QHash<QString, NodePermissionsPointer>> permissionsSets;
|
||||
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get();
|
||||
QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets;
|
||||
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
|
||||
<< _groupPermissions.get() << _groupForbiddens.get();
|
||||
foreach (auto permissionSet, permissionsSets) {
|
||||
QHashIterator<QString, NodePermissionsPointer> i(permissionSet);
|
||||
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(permissionSet);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
NodePermissionsPointer perms = i.value();
|
||||
qDebug() << i.key() << perms;
|
||||
if (perms->isGroup()) {
|
||||
qDebug() << i.key() << perms->getGroupID() << perms;
|
||||
} else {
|
||||
qDebug() << i.key() << perms;
|
||||
}
|
||||
}
|
||||
}
|
||||
#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)) {
|
||||
return *(_standardAgentPermissions[name].get());
|
||||
}
|
||||
|
@ -451,14 +666,58 @@ NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const
|
|||
}
|
||||
|
||||
NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const {
|
||||
if (_agentPermissions.contains(name)) {
|
||||
return *(_agentPermissions[name].get());
|
||||
NodePermissionsKey nameKey = NodePermissionsKey(name, 0);
|
||||
if (_agentPermissions.contains(nameKey)) {
|
||||
return *(_agentPermissions[nameKey].get());
|
||||
}
|
||||
NodePermissions nullPermissions;
|
||||
nullPermissions.setAll(false);
|
||||
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) {
|
||||
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
|
||||
|
||||
|
@ -518,8 +777,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
|||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||
QJsonObject postedObject = postedDocument.object();
|
||||
|
||||
qDebug() << "DomainServerSettingsManager postedObject -" << postedObject;
|
||||
|
||||
// we recurse one level deep below each group for the appropriate setting
|
||||
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject);
|
||||
|
||||
|
@ -536,6 +793,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
|||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||
} else {
|
||||
unpackPermissions();
|
||||
apiRefreshGroupInformation();
|
||||
emit updateNodePermissions();
|
||||
}
|
||||
|
||||
|
@ -550,7 +808,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
|||
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
|
||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
||||
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||
}
|
||||
|
||||
|
@ -730,7 +987,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
|||
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()) {
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
|
||||
|
@ -843,6 +1101,12 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
|
|||
!m2.contains("permissions_id")) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -858,6 +1122,16 @@ void DomainServerSettingsManager::sortPermissions() {
|
|||
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions);
|
||||
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() {
|
||||
|
@ -878,3 +1152,330 @@ void DomainServerSettingsManager::persistToFile() {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,11 @@ const QString SETTINGS_PATH = "/settings";
|
|||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_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 {
|
||||
Q_OBJECT
|
||||
|
@ -43,15 +48,55 @@ public:
|
|||
|
||||
QVariantMap& getDescriptorsMap();
|
||||
|
||||
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); }
|
||||
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); }
|
||||
NodePermissions getStandardPermissionsForName(const QString& name) const;
|
||||
// these give access to anonymous/localhost/logged-in settings from the domain-server settings page
|
||||
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); }
|
||||
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;
|
||||
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:
|
||||
void updateNodePermissions();
|
||||
|
||||
public slots:
|
||||
void apiGetGroupIDJSONCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupIDErrorCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupRanksJSONCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupRanksErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
private slots:
|
||||
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
@ -76,11 +121,33 @@ private:
|
|||
|
||||
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 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 _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
|
||||
|
|
|
@ -39,6 +39,23 @@ Windows.ScrollingWindow {
|
|||
// missing signal
|
||||
signal sendToScript(var message);
|
||||
|
||||
signal moved(vector2d position);
|
||||
signal resized(size size);
|
||||
|
||||
function notifyMoved() {
|
||||
moved(Qt.vector2d(x, y));
|
||||
}
|
||||
|
||||
function notifyResized() {
|
||||
resized(Qt.size(width, height));
|
||||
}
|
||||
|
||||
onXChanged: notifyMoved();
|
||||
onYChanged: notifyMoved();
|
||||
|
||||
onWidthChanged: notifyResized();
|
||||
onHeightChanged: notifyResized();
|
||||
|
||||
Item {
|
||||
width: pane.contentWidth
|
||||
implicitHeight: pane.scrollHeight
|
||||
|
|
|
@ -78,7 +78,10 @@ Decoration {
|
|||
id: closeClickArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: window.shown = false;
|
||||
onClicked: {
|
||||
window.shown = false;
|
||||
window.windowClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ Fadable {
|
|||
//
|
||||
// Signals
|
||||
//
|
||||
signal windowClosed();
|
||||
signal windowDestroyed();
|
||||
signal mouseEntered();
|
||||
signal mouseExited();
|
||||
|
|
|
@ -200,8 +200,9 @@ void AccountManager::sendRequest(const QString& path,
|
|||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray,
|
||||
QHttpMultiPart* dataMultiPart,
|
||||
const QVariantMap& propertyMap) {
|
||||
|
||||
const QVariantMap& propertyMap,
|
||||
QUrlQuery query) {
|
||||
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "sendRequest",
|
||||
Q_ARG(const QString&, path),
|
||||
|
@ -213,9 +214,9 @@ void AccountManager::sendRequest(const QString& path,
|
|||
Q_ARG(QVariantMap, propertyMap));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
|
||||
|
@ -224,13 +225,17 @@ void AccountManager::sendRequest(const QString& path,
|
|||
uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit());
|
||||
|
||||
QUrl requestURL = _authURL;
|
||||
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
requestURL.setPath(path);
|
||||
} else {
|
||||
requestURL.setPath("/" + path);
|
||||
}
|
||||
|
||||
|
||||
if (!query.isEmpty()) {
|
||||
requestURL.setQuery(query);
|
||||
}
|
||||
|
||||
if (authType != AccountManagerAuth::None ) {
|
||||
if (hasValidAccessToken()) {
|
||||
networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
|
||||
|
@ -241,22 +246,21 @@ void AccountManager::sendRequest(const QString& path,
|
|||
<< path << "that requires authentication";
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qCDebug(networking) << "Making a request to" << qPrintable(requestURL.toString());
|
||||
|
||||
|
||||
if (!dataByteArray.isEmpty()) {
|
||||
qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
|
||||
|
||||
switch (operation) {
|
||||
case QNetworkAccessManager::GetOperation:
|
||||
networkReply = networkAccessManager.get(networkRequest);
|
||||
|
@ -269,7 +273,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
} else {
|
||||
networkReply = networkAccessManager.put(networkRequest, dataMultiPart);
|
||||
}
|
||||
|
||||
|
||||
// make sure dataMultiPart is destroyed when the reply is
|
||||
connect(networkReply, &QNetworkReply::destroyed, dataMultiPart, &QHttpMultiPart::deleteLater);
|
||||
} else {
|
||||
|
@ -280,7 +284,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
networkReply = networkAccessManager.put(networkRequest, dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
case QNetworkAccessManager::DeleteOperation:
|
||||
networkReply = networkAccessManager.sendCustomRequest(networkRequest, "DELETE");
|
||||
|
@ -289,7 +293,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
// other methods not yet handled
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (networkReply) {
|
||||
if (!propertyMap.isEmpty()) {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!callbackParams.isEmpty()) {
|
||||
// if we have information for a callback, insert the callbackParams into our local map
|
||||
_pendingCallbackMap.insert(networkReply, callbackParams);
|
||||
|
||||
|
||||
if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
|
||||
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
|
||||
callbackParams.updateSlot.toStdString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we ended up firing of a request, hook up to it now
|
||||
connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "NetworkAccessManager.h"
|
||||
|
||||
|
@ -67,7 +68,8 @@ public:
|
|||
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
|
||||
const QByteArray& dataByteArray = QByteArray(),
|
||||
QHttpMultiPart* dataMultiPart = NULL,
|
||||
const QVariantMap& propertyMap = QVariantMap());
|
||||
const QVariantMap& propertyMap = QVariantMap(),
|
||||
QUrlQuery query = QUrlQuery());
|
||||
|
||||
void setIsAgent(bool isAgent) { _isAgent = isAgent; }
|
||||
|
||||
|
|
36
libraries/networking/src/GroupRank.h
Normal file
36
libraries/networking/src/GroupRank.h
Normal 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
|
|
@ -135,17 +135,21 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
|
|||
|
||||
_permissions = newPermissions;
|
||||
|
||||
if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) {
|
||||
emit isAllowedEditorChanged(_permissions.canAdjustLocks);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canAdjustLocks) !=
|
||||
newPermissions.can(NodePermissions::Permission::canAdjustLocks)) {
|
||||
emit isAllowedEditorChanged(_permissions.can(NodePermissions::Permission::canAdjustLocks));
|
||||
}
|
||||
if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) {
|
||||
emit canRezChanged(_permissions.canRezPermanentEntities);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canRezPermanentEntities) !=
|
||||
newPermissions.can(NodePermissions::Permission::canRezPermanentEntities)) {
|
||||
emit canRezChanged(_permissions.can(NodePermissions::Permission::canRezPermanentEntities));
|
||||
}
|
||||
if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) {
|
||||
emit canRezTmpChanged(_permissions.canRezTemporaryEntities);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canRezTemporaryEntities) !=
|
||||
newPermissions.can(NodePermissions::Permission::canRezTemporaryEntities)) {
|
||||
emit canRezTmpChanged(_permissions.can(NodePermissions::Permission::canRezTemporaryEntities));
|
||||
}
|
||||
if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) {
|
||||
emit canWriteAssetsChanged(_permissions.canWriteToAssetServer);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canWriteToAssetServer) !=
|
||||
newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) {
|
||||
emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -106,10 +106,10 @@ public:
|
|||
void setSessionUUID(const QUuid& sessionUUID);
|
||||
|
||||
void setPermissions(const NodePermissions& newPermissions);
|
||||
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||
bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||
bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||
bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; }
|
||||
bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
|
||||
bool getThisNodeCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
|
||||
bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
|
||||
bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
|
||||
|
||||
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
|
||||
QUdpSocket& getDTLSSocket();
|
||||
|
|
|
@ -65,10 +65,10 @@ public:
|
|||
|
||||
void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; }
|
||||
NodePermissions getPermissions() const { return _permissions; }
|
||||
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||
bool getCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||
bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||
bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; }
|
||||
bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
|
||||
bool getCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
|
||||
bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
|
||||
bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
|
||||
|
||||
void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message);
|
||||
void addIgnoredNode(const QUuid& otherNodeID);
|
||||
|
|
|
@ -283,12 +283,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
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
|
||||
// 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;
|
||||
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull();
|
||||
|
||||
if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {
|
||||
qWarning() << "A keypair is required to present a username signature to the domain-server"
|
||||
|
|
|
@ -13,76 +13,114 @@
|
|||
#include <QtCore/QDebug>
|
||||
#include "NodePermissions.h"
|
||||
|
||||
QString NodePermissions::standardNameLocalhost = QString("localhost");
|
||||
QString NodePermissions::standardNameLoggedIn = QString("logged-in");
|
||||
QString NodePermissions::standardNameAnonymous = QString("anonymous");
|
||||
NodePermissionsKey NodePermissions::standardNameLocalhost = NodePermissionsKey("localhost", 0);
|
||||
NodePermissionsKey NodePermissions::standardNameLoggedIn = NodePermissionsKey("logged-in", 0);
|
||||
NodePermissionsKey NodePermissions::standardNameAnonymous = NodePermissionsKey("anonymous", 0);
|
||||
NodePermissionsKey NodePermissions::standardNameFriends = NodePermissionsKey("friends", 0);
|
||||
|
||||
QStringList NodePermissions::standardNames = QList<QString>()
|
||||
<< NodePermissions::standardNameLocalhost
|
||||
<< NodePermissions::standardNameLoggedIn
|
||||
<< NodePermissions::standardNameAnonymous;
|
||||
<< NodePermissions::standardNameLocalhost.first
|
||||
<< NodePermissions::standardNameLoggedIn.first
|
||||
<< 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) {
|
||||
this->canConnectToDomain |= rhs.canConnectToDomain;
|
||||
this->canAdjustLocks |= rhs.canAdjustLocks;
|
||||
this->canRezPermanentEntities |= rhs.canRezPermanentEntities;
|
||||
this->canRezTemporaryEntities |= rhs.canRezTemporaryEntities;
|
||||
this->canWriteToAssetServer |= rhs.canWriteToAssetServer;
|
||||
this->canConnectPastMaxCapacity |= rhs.canConnectPastMaxCapacity;
|
||||
permissions |= rhs.permissions;
|
||||
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) {
|
||||
out << perms.canConnectToDomain;
|
||||
out << perms.canAdjustLocks;
|
||||
out << perms.canRezPermanentEntities;
|
||||
out << perms.canRezTemporaryEntities;
|
||||
out << perms.canWriteToAssetServer;
|
||||
out << perms.canConnectPastMaxCapacity;
|
||||
out << (uint)perms.permissions;
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, NodePermissions& perms) {
|
||||
in >> perms.canConnectToDomain;
|
||||
in >> perms.canAdjustLocks;
|
||||
in >> perms.canRezPermanentEntities;
|
||||
in >> perms.canRezTemporaryEntities;
|
||||
in >> perms.canWriteToAssetServer;
|
||||
in >> perms.canConnectPastMaxCapacity;
|
||||
uint permissionsInt;
|
||||
in >> permissionsInt;
|
||||
perms.permissions = (NodePermissions::Permissions)permissionsInt;
|
||||
return in;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const NodePermissions& perms) {
|
||||
debug.nospace() << "[permissions: " << perms.getID() << " --";
|
||||
if (perms.canConnectToDomain) {
|
||||
debug.nospace() << "[permissions: " << perms.getID() << "/" << perms.getVerifiedUserName() << " -- ";
|
||||
debug.nospace() << "rank=" << perms.getRankID()
|
||||
<< ", groupID=" << perms.getGroupID() << "/" << (perms.isGroup() ? "y" : "n");
|
||||
if (perms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
debug << " connect";
|
||||
}
|
||||
if (perms.canAdjustLocks) {
|
||||
if (perms.can(NodePermissions::Permission::canAdjustLocks)) {
|
||||
debug << " locks";
|
||||
}
|
||||
if (perms.canRezPermanentEntities) {
|
||||
if (perms.can(NodePermissions::Permission::canRezPermanentEntities)) {
|
||||
debug << " rez";
|
||||
}
|
||||
if (perms.canRezTemporaryEntities) {
|
||||
if (perms.can(NodePermissions::Permission::canRezTemporaryEntities)) {
|
||||
debug << " rez-tmp";
|
||||
}
|
||||
if (perms.canWriteToAssetServer) {
|
||||
if (perms.can(NodePermissions::Permission::canWriteToAssetServer)) {
|
||||
debug << " asset-server";
|
||||
}
|
||||
if (perms.canConnectPastMaxCapacity) {
|
||||
if (perms.can(NodePermissions::Permission::canConnectPastMaxCapacity)) {
|
||||
debug << " ignore-max-cap";
|
||||
}
|
||||
debug.nospace() << "]";
|
||||
|
|
|
@ -18,90 +18,108 @@
|
|||
#include <QVariant>
|
||||
#include <QUuid>
|
||||
|
||||
#include "GroupRank.h"
|
||||
|
||||
class NodePermissions;
|
||||
using NodePermissionsPointer = std::shared_ptr<NodePermissions>;
|
||||
using NodePermissionsKey = QPair<QString, QUuid>; // name, rankID
|
||||
using NodePermissionsKeyList = QList<QPair<QString, QUuid>>;
|
||||
|
||||
|
||||
class NodePermissions {
|
||||
public:
|
||||
NodePermissions() { _id = QUuid::createUuid().toString(); }
|
||||
NodePermissions(const QString& name) { _id = name.toLower(); }
|
||||
NodePermissions(QMap<QString, QVariant> perms) {
|
||||
_id = perms["permissions_id"].toString().toLower();
|
||||
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();
|
||||
}
|
||||
NodePermissions() { _id = QUuid::createUuid().toString(); _rankID = QUuid(); }
|
||||
NodePermissions(const QString& name) { _id = name.toLower(); _rankID = QUuid(); }
|
||||
NodePermissions(const NodePermissionsKey& key) { _id = key.first.toLower(); _rankID = key.second; }
|
||||
NodePermissions(QMap<QString, QVariant> perms);
|
||||
|
||||
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.
|
||||
void setUserName(QString userName) { _userName = userName.toLower(); }
|
||||
QString getUserName() { return _userName; }
|
||||
// the _id member isn't authenticated/verified and _username is.
|
||||
void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); }
|
||||
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 };
|
||||
|
||||
// these 3 names have special meaning.
|
||||
static QString standardNameLocalhost;
|
||||
static QString standardNameLoggedIn;
|
||||
static QString standardNameAnonymous;
|
||||
static NodePermissionsKey standardNameLocalhost;
|
||||
static NodePermissionsKey standardNameLoggedIn;
|
||||
static NodePermissionsKey standardNameAnonymous;
|
||||
static NodePermissionsKey standardNameFriends;
|
||||
static QStringList standardNames;
|
||||
|
||||
// the initializations here should match the defaults in describe-settings.json
|
||||
bool canConnectToDomain { true };
|
||||
bool canAdjustLocks { false };
|
||||
bool canRezPermanentEntities { false };
|
||||
bool canRezTemporaryEntities { false };
|
||||
bool canWriteToAssetServer { false };
|
||||
bool canConnectPastMaxCapacity { false };
|
||||
enum class Permission {
|
||||
none = 0,
|
||||
canConnectToDomain = 1,
|
||||
canAdjustLocks = 2,
|
||||
canRezPermanentEntities = 4,
|
||||
canRezTemporaryEntities = 8,
|
||||
canWriteToAssetServer = 16,
|
||||
canConnectPastMaxCapacity = 32
|
||||
};
|
||||
Q_DECLARE_FLAGS(Permissions, Permission)
|
||||
Permissions permissions;
|
||||
|
||||
void setAll(bool value) {
|
||||
canConnectToDomain = value;
|
||||
canAdjustLocks = value;
|
||||
canRezPermanentEntities = value;
|
||||
canRezTemporaryEntities = value;
|
||||
canWriteToAssetServer = value;
|
||||
canConnectPastMaxCapacity = value;
|
||||
}
|
||||
QVariant toVariant(QHash<QUuid, GroupRank> groupRanks = QHash<QUuid, GroupRank>());
|
||||
|
||||
QVariant toVariant() {
|
||||
QMap<QString, QVariant> values;
|
||||
values["permissions_id"] = _id;
|
||||
values["id_can_connect"] = canConnectToDomain;
|
||||
values["id_can_adjust_locks"] = canAdjustLocks;
|
||||
values["id_can_rez"] = canRezPermanentEntities;
|
||||
values["id_can_rez_tmp"] = canRezTemporaryEntities;
|
||||
values["id_can_write_to_asset_server"] = canWriteToAssetServer;
|
||||
values["id_can_connect_past_max_capacity"] = canConnectPastMaxCapacity;
|
||||
return QVariant(values);
|
||||
}
|
||||
void setAll(bool value);
|
||||
|
||||
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& 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:
|
||||
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
|
||||
class NodePermissionsMap {
|
||||
public:
|
||||
NodePermissionsMap() { }
|
||||
NodePermissionsPointer& operator[](const QString& key) { return _data[key.toLower()]; }
|
||||
NodePermissionsPointer operator[](const QString& key) const { return _data.value(key.toLower()); }
|
||||
bool contains(const QString& key) const { return _data.contains(key.toLower()); }
|
||||
QList<QString> keys() const { return _data.keys(); }
|
||||
QHash<QString, NodePermissionsPointer> get() { return _data; }
|
||||
NodePermissionsPointer& operator[](const NodePermissionsKey& key) {
|
||||
NodePermissionsKey dataKey(key.first.toLower(), key.second);
|
||||
if (!_data.contains(dataKey)) {
|
||||
_data[dataKey] = NodePermissionsPointer(new NodePermissions(key));
|
||||
}
|
||||
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 remove(const NodePermissionsKey& key) { _data.remove(key); }
|
||||
|
||||
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 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
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static QMessageBox::StandardButton question(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
static QMessageBox::StandardButton warning(const QString& title, const QString& text,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
|
|
|
@ -124,6 +124,10 @@ void QmlWindowClass::initQml(QVariantMap properties) {
|
|||
// Forward messages received from QML on to the script
|
||||
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
|
||||
connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
|
||||
|
||||
connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection);
|
||||
connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection);
|
||||
connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
Q_ASSERT(_qmlWindow);
|
||||
|
@ -259,7 +263,12 @@ void QmlWindowClass::close() {
|
|||
}
|
||||
}
|
||||
|
||||
void QmlWindowClass::hasMoved(QVector2D position) {
|
||||
emit moved(glm::vec2(position.x(), position.y()));
|
||||
}
|
||||
|
||||
void QmlWindowClass::hasClosed() {
|
||||
emit closed();
|
||||
}
|
||||
|
||||
void QmlWindowClass::raise() {
|
||||
|
|
|
@ -62,6 +62,7 @@ signals:
|
|||
void fromQml(const QVariant& message);
|
||||
|
||||
protected slots:
|
||||
void hasMoved(QVector2D);
|
||||
void hasClosed();
|
||||
void qmlToScript(const QVariant& message);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
//
|
||||
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_BASE_DIMENSION = { x: 7, y: 2, z: 7 };
|
||||
|
|
Loading…
Reference in a new issue