Merge pull request #8326 from birarda/kick

kick
This commit is contained in:
Seth Alves 2016-08-03 09:21:09 -07:00 committed by GitHub
commit 9962d47173
28 changed files with 775 additions and 327 deletions

View file

@ -1,5 +1,5 @@
{ {
"version": 1.5, "version": 1.7,
"settings": [ "settings": [
{ {
"name": "metaverse", "name": "metaverse",
@ -395,7 +395,7 @@
}, },
{ {
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>", "label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 6 "span": 7
} }
], ],
@ -445,6 +445,13 @@
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
},
{
"name": "id_can_kick",
"label": "Kick Users",
"type": "checkbox",
"editable": true,
"default": false
} }
], ],
@ -468,7 +475,7 @@
}, },
{ {
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>", "label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 6 "span": 7
} }
], ],
@ -543,6 +550,13 @@
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
},
{
"name": "id_can_kick",
"label": "Kick Users",
"type": "checkbox",
"editable": true,
"default": false
} }
] ]
}, },
@ -563,7 +577,7 @@
}, },
{ {
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>", "label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 6 "span": 7
} }
], ],
@ -635,23 +649,29 @@
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
},
{
"name": "id_can_kick",
"label": "Kick Users",
"type": "checkbox",
"editable": true,
"default": false
} }
] ]
}, },
{ {
"name": "permissions", "name": "ip_permissions",
"type": "table", "type": "table",
"caption": "Permissions for Specific Users", "caption": "Permissions for Users from IP Addresses",
"can_add_new_rows": true, "can_add_new_rows": true,
"groups": [ "groups": [
{ {
"label": "User", "label": "IP Address",
"span": 1 "span": 1
}, },
{ {
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>", "label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 6 "span": 7
} }
], ],
@ -701,6 +721,86 @@
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
},
{
"name": "id_can_kick",
"label": "Kick Users",
"type": "checkbox",
"editable": true,
"default": false
}
]
},
{
"name": "permissions",
"type": "table",
"caption": "Permissions for Specific Users",
"can_add_new_rows": true,
"groups": [
{
"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 &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 7
}
],
"columns": [
{
"name": "permissions_id",
"label": ""
},
{
"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": "id_can_kick",
"label": "Kick Users",
"type": "checkbox",
"editable": true,
"default": false
} }
] ]
} }

View file

@ -75,15 +75,6 @@ span.port {
color: #666666; color: #666666;
} }
.locked {
color: #428bca;
}
.locked-table {
cursor: not-allowed;
background-color: #eee;
}
.advanced-setting { .advanced-setting {
display: none; display: none;
} }

View file

@ -57,15 +57,13 @@
<div class="panel-body"> <div class="panel-body">
<% _.each(split_settings[0], function(setting) { %> <% _.each(split_settings[0], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %> <% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, false, <%= getFormGroup(keypath, setting, values, false) %>
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %> <% }); %>
<% if (!_.isEmpty(split_settings[1])) { %> <% if (!_.isEmpty(split_settings[1])) { %>
<% $("#advanced-toggle-button").show() %> <% $("#advanced-toggle-button").show() %>
<% _.each(split_settings[1], function(setting) { %> <% _.each(split_settings[1], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %> <% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, true, <%= getFormGroup(keypath, setting, values, true) %>
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %> <% }); %>
<% }%> <% }%>
</div> </div>

View file

@ -41,7 +41,7 @@ var Settings = {
}; };
var viewHelpers = { var viewHelpers = {
getFormGroup: function(keypath, setting, values, isAdvanced, isLocked) { getFormGroup: function(keypath, setting, values, isAdvanced) {
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "' data-keypath='" + keypath + "'>"; form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "' data-keypath='" + keypath + "'>";
setting_value = _(values).valueForKeyPath(keypath); setting_value = _(values).valueForKeyPath(keypath);
@ -54,9 +54,6 @@ var viewHelpers = {
} }
label_class = 'control-label'; label_class = 'control-label';
if (isLocked) {
label_class += ' locked';
}
function common_attrs(extra_classes) { function common_attrs(extra_classes) {
extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : "");
@ -71,9 +68,8 @@ var viewHelpers = {
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>" form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
} }
form_group += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>" form_group += "<div class='toggle-checkbox-container'>"
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "") form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "") + "/>"
form_group += (isLocked ? " disabled" : "") + "/>"
if (setting.help) { if (setting.help) {
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>"; form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
@ -88,7 +84,7 @@ var viewHelpers = {
} }
if (input_type === 'table') { if (input_type === 'table') {
form_group += makeTable(setting, keypath, setting_value, isLocked) form_group += makeTable(setting, keypath, setting_value)
} else { } else {
if (input_type === 'select') { if (input_type === 'select') {
form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'" form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'"
@ -107,12 +103,10 @@ var viewHelpers = {
if (setting.href) { if (setting.href) {
form_group += "<a href='" + setting.href + "'style='display: block;' role='button'" form_group += "<a href='" + setting.href + "'style='display: block;' role='button'"
+ (isLocked ? " disabled" : "")
+ common_attrs("btn " + setting.classes) + " target='_blank'>" + common_attrs("btn " + setting.classes) + " target='_blank'>"
+ setting.button_label + "</a>"; + setting.button_label + "</a>";
} else { } else {
form_group += "<button " + common_attrs("btn " + setting.classes) form_group += "<button " + common_attrs("btn " + setting.classes) + ">"
+ (isLocked ? " disabled" : "") + ">"
+ setting.button_label + "</button>"; + setting.button_label + "</button>";
} }
@ -124,7 +118,7 @@ var viewHelpers = {
form_group += "<input type='" + input_type + "'" + common_attrs() + form_group += "<input type='" + input_type + "'" + common_attrs() +
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") + "placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>" "' value='" + setting_value + "'/>"
} }
form_group += "<span class='help-block'>" + setting.help + "</span>" form_group += "<span class='help-block'>" + setting.help + "</span>"
@ -459,10 +453,8 @@ function setupHFAccountButton() {
$("[data-keypath='metaverse.automatic_networking']").hide(); $("[data-keypath='metaverse.automatic_networking']").hide();
} }
var tokenLocked = _(Settings.data).valueForKeyPath("locked.metaverse.access_token");
// use the existing getFormGroup helper to ask for a button // use the existing getFormGroup helper to ask for a button
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false, tokenLocked); var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false);
// add the button group to the top of the metaverse panel // add the button group to the top of the metaverse panel
$('#metaverse .panel-body').prepend(buttonGroup); $('#metaverse .panel-body').prepend(buttonGroup);
@ -673,7 +665,7 @@ function setupPlacesTable() {
} }
// get a table for the places // get a table for the places
var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false, false); var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false);
// append the places table in the right place // append the places table in the right place
$('#places_paths .panel-body').prepend(placesTableGroup); $('#places_paths .panel-body').prepend(placesTableGroup);
@ -873,10 +865,8 @@ function reloadSettings(callback) {
Settings.data = data; Settings.data = data;
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) { // append the domain selection modal
// append the domain selection modal, as long as it's not locked appendDomainIDButtons();
appendDomainIDButtons();
}
// call our method to setup the HF account button // call our method to setup the HF account button
setupHFAccountButton(); setupHFAccountButton();
@ -889,12 +879,6 @@ function reloadSettings(callback) {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
// add tooltip to locked settings
$('label.locked').tooltip({
placement: 'right',
title: 'This setting is in the master config file and cannot be changed'
});
// call the callback now that settings are loaded // call the callback now that settings are loaded
callback(true); callback(true);
}).fail(function() { }).fail(function() {
@ -943,11 +927,11 @@ $('body').on('click', '.save-button', function(e){
return false; return false;
}); });
function makeTable(setting, keypath, setting_value, isLocked) { function makeTable(setting, keypath, setting_value) {
var isArray = !_.has(setting, 'key'); var isArray = !_.has(setting, 'key');
var categoryKey = setting.categorize_by_key; var categoryKey = setting.categorize_by_key;
var isCategorized = !!categoryKey && isArray; var isCategorized = !!categoryKey && isArray;
if (!isArray && setting.can_order) { if (!isArray && setting.can_order) {
setting.can_order = false; setting.can_order = false;
} }
@ -961,7 +945,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
var nonDeletableRowKey = setting["non-deletable-row-key"]; var nonDeletableRowKey = setting["non-deletable-row-key"];
var nonDeletableRowValues = setting["non-deletable-row-values"]; var nonDeletableRowValues = setting["non-deletable-row-values"];
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' " + html += "<table class='table table-bordered' " +
"data-short-name='" + setting.name + "' name='" + keypath + "' " + "data-short-name='" + setting.name + "' name='" + keypath + "' " +
"id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " +
"data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"; "data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
@ -976,7 +960,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
_.each(setting.groups, function (group) { _.each(setting.groups, function (group) {
html += "<td colspan='" + group.span + "'><strong>" + group.label + "</strong></td>" html += "<td colspan='" + group.span + "'><strong>" + group.label + "</strong></td>"
}) })
if (!isLocked && !setting.read_only) { if (!setting.read_only) {
if (setting.can_order) { if (setting.can_order) {
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>"; "'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
@ -1004,7 +988,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
(col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data (col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
}) })
if (!isLocked && !setting.read_only) { if (!setting.read_only) {
if (setting.can_order) { if (setting.can_order) {
numVisibleColumns++; numVisibleColumns++;
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
@ -1083,7 +1067,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
}); });
if (!isLocked && !setting.read_only) { if (!setting.read_only) {
if (setting.can_order) { if (setting.can_order) {
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+ html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+
"'><a href='javascript:void(0);' class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a>" "'><a href='javascript:void(0);' class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a>"
@ -1108,7 +1092,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
} }
// populate inputs in the table for new values // populate inputs in the table for new values
if (!isLocked && !setting.read_only) { if (!setting.read_only) {
if (setting.can_add_new_categories) { if (setting.can_add_new_categories) {
html += makeTableCategoryInput(setting, numVisibleColumns); html += makeTableCategoryInput(setting, numVisibleColumns);
} }

View file

@ -120,8 +120,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
} }
} }
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername) { NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress) {
NodePermissions userPerms; NodePermissions userPerms;
userPerms.setAll(false); userPerms.setAll(false);
if (isLocalUser) { if (isLocalUser) {
@ -136,16 +137,29 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms; qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms;
#endif #endif
if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
// this user comes from an IP we have in our permissions table, apply those permissions
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific IP matches, so:" << userPerms;
#endif
}
} else { } else {
userPerms.setID(verifiedUsername);
if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) { if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) {
userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername);
userPerms.setVerifiedUserName(verifiedUsername);
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific user matches, so:" << userPerms; qDebug() << "| user-permissions: specific user matches, so:" << userPerms;
#endif
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
// this user comes from an IP we have in our permissions table, apply those permissions
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific IP matches, so:" << userPerms;
#endif #endif
} else { } else {
userPerms.setVerifiedUserName(verifiedUsername);
// they are logged into metaverse, but we don't have specific permissions for them. // they are logged into metaverse, but we don't have specific permissions for them.
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
@ -191,6 +205,9 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
} }
} }
} }
userPerms.setID(verifiedUsername);
userPerms.setVerifiedUserName(verifiedUsername);
} }
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
@ -225,7 +242,12 @@ void DomainGatekeeper::updateNodePermissions() {
const QHostAddress& addr = node->getLocalSocket().getAddress(); const QHostAddress& addr = node->getLocalSocket().getAddress();
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() || bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
addr == QHostAddress::LocalHost); addr == QHostAddress::LocalHost);
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername);
// at this point we don't have a sending socket for packets from this node - assume it is the active socket
// or the public socket if we haven't activated a socket for the node yet
HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket();
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress());
} }
node->setPermissions(userPerms); node->setPermissions(userPerms);
@ -337,7 +359,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
} }
} }
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername); userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress());
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
@ -430,11 +452,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
bool DomainGatekeeper::verifyUserSignature(const QString& username, bool DomainGatekeeper::verifyUserSignature(const QString& username,
const QByteArray& usernameSignature, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr) { const HifiSockAddr& senderSockAddr) {
// it's possible this user can be allowed to connect, but we need to check their username signature // it's possible this user can be allowed to connect, but we need to check their username signature
QByteArray publicKeyArray = _userPublicKeys.value(username.toLower()); auto lowerUsername = username.toLower();
QByteArray publicKeyArray = _userPublicKeys.value(lowerUsername);
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); const QUuid& connectionToken = _connectionTokenHash.value(lowerUsername);
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) { if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
// if we do have a public key for the user, check for a signature match // if we do have a public key for the user, check for a signature match
@ -444,8 +466,8 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
// first load up the public key into an RSA struct // first load up the public key into an RSA struct
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size()); RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
QByteArray lowercaseUsername = username.toLower().toUtf8(); QByteArray lowercaseUsernameUTF8 = lowerUsername.toUtf8();
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsernameUTF8.append(connectionToken.toRfc4122()),
QCryptographicHash::Sha256); QCryptographicHash::Sha256);
if (rsaPublicKey) { if (rsaPublicKey) {
@ -575,10 +597,7 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
QString username = extractUsernameFromPublicKeyRequest(requestReply); QString username = extractUsernameFromPublicKeyRequest(requestReply);
if (jsonObject["status"].toString() == "success" && username != "") { if (jsonObject["status"].toString() == "success" && !username.isEmpty()) {
// figure out which user this is for
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
qDebug() << "Storing a public key for user" << username;
// pull the public key as a QByteArray from this response // pull the public key as a QByteArray from this response
const QString JSON_DATA_KEY = "data"; const QString JSON_DATA_KEY = "data";
const QString JSON_PUBLIC_KEY_KEY = "public_key"; const QString JSON_PUBLIC_KEY_KEY = "public_key";

View file

@ -106,7 +106,8 @@ private:
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername); NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress);
void getGroupMemberships(const QString& username); void getGroupMemberships(const QString& username);
// void getIsGroupMember(const QString& username, const QUuid groupID); // void getIsGroupMember(const QString& username, const QUuid groupID);
void getDomainOwnerFriendsList(); void getDomainOwnerFriendsList();

View file

@ -411,6 +411,7 @@ void DomainServer::setupNodeListAndAssignments() {
// NodeList won't be available to the settings manager when it is created, so call registerListener here // NodeList won't be available to the settings manager when it is created, so call registerListener here
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
// register the gatekeeper for the packets it needs to receive // register the gatekeeper for the packets it needs to receive
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");

View file

@ -95,6 +95,8 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) { void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_argumentList = argumentList; _argumentList = argumentList;
// after 1.7 we no longer use the master or merged configs - this is kept in place for migration
_configMap.loadMasterAndUserConfig(_argumentList); _configMap.loadMasterAndUserConfig(_argumentList);
// What settings version were we before and what are we using now? // What settings version were we before and what are we using now?
@ -118,7 +120,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
// This was prior to the introduction of security.restricted_access // This was prior to the introduction of security.restricted_access
// If the user has a list of allowed users then set their value for security.restricted_access to true // If the user has a list of allowed users then set their value for security.restricted_access to true
QVariant* allowedUsers = valueForKeyPath(_configMap.getMergedConfig(), ALLOWED_USERS_SETTINGS_KEYPATH); QVariant* allowedUsers = _configMap.valueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH);
if (allowedUsers if (allowedUsers
&& allowedUsers->canConvert(QMetaType::QVariantList) && allowedUsers->canConvert(QMetaType::QVariantList)
@ -129,9 +131,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
// In the pre-toggle system the user had a list of allowed users, so // In the pre-toggle system the user had a list of allowed users, so
// we need to set security.restricted_access to true // we need to set security.restricted_access to true
QVariant* restrictedAccess = valueForKeyPath(_configMap.getUserConfig(), QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true);
RESTRICTED_ACCESS_SETTINGS_KEYPATH,
true);
*restrictedAccess = QVariant(true); *restrictedAccess = QVariant(true);
@ -149,21 +149,20 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
static const QString ENTITY_FILE_PATH_KEYPATH = ENTITY_SERVER_SETTINGS_KEY + ".persistFilePath"; static const QString ENTITY_FILE_PATH_KEYPATH = ENTITY_SERVER_SETTINGS_KEY + ".persistFilePath";
// this was prior to change of poorly named entitiesFileName to entitiesFilePath // this was prior to change of poorly named entitiesFileName to entitiesFilePath
QVariant* persistFileNameVariant = valueForKeyPath(_configMap.getMergedConfig(), QVariant* persistFileNameVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY);
ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY);
if (persistFileNameVariant && persistFileNameVariant->canConvert(QMetaType::QString)) { if (persistFileNameVariant && persistFileNameVariant->canConvert(QMetaType::QString)) {
QString persistFileName = persistFileNameVariant->toString(); QString persistFileName = persistFileNameVariant->toString();
qDebug() << "Migrating persistFilename to persistFilePath for entity-server settings"; qDebug() << "Migrating persistFilename to persistFilePath for entity-server settings";
// grab the persistFilePath option, create it if it doesn't exist // grab the persistFilePath option, create it if it doesn't exist
QVariant* persistFilePath = valueForKeyPath(_configMap.getUserConfig(), ENTITY_FILE_PATH_KEYPATH, true); QVariant* persistFilePath = _configMap.valueForKeyPath(ENTITY_FILE_PATH_KEYPATH, true);
// write the migrated value // write the migrated value
*persistFilePath = persistFileName; *persistFilePath = persistFileName;
// remove the old setting // remove the old setting
QVariant* entityServerVariant = valueForKeyPath(_configMap.getUserConfig(), ENTITY_SERVER_SETTINGS_KEY); QVariant* entityServerVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY);
if (entityServerVariant && entityServerVariant->canConvert(QMetaType::QVariantMap)) { if (entityServerVariant && entityServerVariant->canConvert(QMetaType::QVariantMap)) {
QVariantMap entityServerMap = entityServerVariant->toMap(); QVariantMap entityServerMap = entityServerVariant->toMap();
entityServerMap.remove(ENTITY_FILE_NAME_KEY); entityServerMap.remove(ENTITY_FILE_NAME_KEY);
@ -185,7 +184,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
// If we have a password in the previous settings file, make it base 64 // If we have a password in the previous settings file, make it base 64
static const QString BASIC_AUTH_PASSWORD_KEY_PATH { "security.http_password" }; static const QString BASIC_AUTH_PASSWORD_KEY_PATH { "security.http_password" };
QVariant* passwordVariant = valueForKeyPath(_configMap.getUserConfig(), BASIC_AUTH_PASSWORD_KEY_PATH); QVariant* passwordVariant = _configMap.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH);
if (passwordVariant && passwordVariant->canConvert(QMetaType::QString)) { if (passwordVariant && passwordVariant->canConvert(QMetaType::QString)) {
QString plaintextPassword = passwordVariant->toString(); QString plaintextPassword = passwordVariant->toString();
@ -273,6 +272,28 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
// This was prior to operating hours, so add default hours // This was prior to operating hours, so add default hours
validateDescriptorsMap(); validateDescriptorsMap();
} }
if (oldVersion < 1.6) {
unpackPermissions();
// This was prior to addition of kick permissions, add that to localhost permissions by default
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canKick);
packPermissions();
}
if (oldVersion < 1.7) {
// This was prior to the removal of the master config file
// So we write the merged config to the user config file, and stop reading from the user config file
qDebug() << "Migrating merged config to user config file. The master config file is deprecated.";
// replace the user config by the merged config
_configMap.getConfig() = _configMap.getMergedConfig();
// persist the new config so the user config file has the correctly merged config
persistToFile();
}
} }
unpackPermissions(); unpackPermissions();
@ -293,9 +314,9 @@ void DomainServerSettingsManager::validateDescriptorsMap() {
static const QString WEEKEND_HOURS{ "descriptors.weekend_hours" }; static const QString WEEKEND_HOURS{ "descriptors.weekend_hours" };
static const QString UTC_OFFSET{ "descriptors.utc_offset" }; static const QString UTC_OFFSET{ "descriptors.utc_offset" };
QVariant* weekdayHours = valueForKeyPath(_configMap.getUserConfig(), WEEKDAY_HOURS, true); QVariant* weekdayHours = _configMap.valueForKeyPath(WEEKDAY_HOURS, true);
QVariant* weekendHours = valueForKeyPath(_configMap.getUserConfig(), WEEKEND_HOURS, true); QVariant* weekendHours = _configMap.valueForKeyPath(WEEKEND_HOURS, true);
QVariant* utcOffset = valueForKeyPath(_configMap.getUserConfig(), UTC_OFFSET, true); QVariant* utcOffset = _configMap.valueForKeyPath(UTC_OFFSET, true);
static const QString OPEN{ "open" }; static const QString OPEN{ "open" };
static const QString CLOSE{ "close" }; static const QString CLOSE{ "close" };
@ -318,9 +339,6 @@ void DomainServerSettingsManager::validateDescriptorsMap() {
if (wasMalformed) { if (wasMalformed) {
// write the new settings to file // write the new settings to file
persistToFile(); persistToFile();
// reload the master and user config so the merged config is correct
_configMap.loadMasterAndUserConfig(_argumentList);
} }
} }
@ -354,16 +372,14 @@ void DomainServerSettingsManager::packPermissionsForMap(QString mapName,
NodePermissionsMap& permissionsRows, NodePermissionsMap& permissionsRows,
QString keyPath) { QString keyPath) {
// find (or create) the "security" section of the settings map // find (or create) the "security" section of the settings map
QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); QVariant* security = _configMap.valueForKeyPath("security", true);
if (!security || !security->canConvert(QMetaType::QVariantMap)) { if (!security->canConvert(QMetaType::QVariantMap)) {
security = valueForKeyPath(_configMap.getUserConfig(), "security", true);
(*security) = QVariantMap(); (*security) = QVariantMap();
} }
// find (or create) whichever subsection of "security" we are packing // find (or create) whichever subsection of "security" we are packing
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); QVariant* permissions = _configMap.valueForKeyPath(keyPath, true);
if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { if (!permissions->canConvert(QMetaType::QVariantList)) {
permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true);
(*permissions) = QVariantList(); (*permissions) = QVariantList();
} }
@ -420,6 +436,9 @@ void DomainServerSettingsManager::packPermissions() {
// save settings for specific users // save settings for specific users
packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH);
// save settings for IP addresses
packPermissionsForMap("permissions", _ipPermissions, IP_PERMISSIONS_KEYPATH);
// save settings for groups // save settings for groups
packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH); packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH);
@ -427,139 +446,103 @@ void DomainServerSettingsManager::packPermissions() {
packPermissionsForMap("permissions", _groupForbiddens, GROUP_FORBIDDENS_KEYPATH); packPermissionsForMap("permissions", _groupForbiddens, GROUP_FORBIDDENS_KEYPATH);
persistToFile(); persistToFile();
_configMap.loadMasterAndUserConfig(_argumentList);
} }
void DomainServerSettingsManager::unpackPermissions() { bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& keyPath,
// transfer details from _configMap to _agentPermissions; NodePermissionsMap* mapPointer,
std::function<void(NodePermissionsPointer)> customUnpacker) {
_standardAgentPermissions.clear(); mapPointer->clear();
_agentPermissions.clear();
_groupPermissions.clear();
_groupForbiddens.clear();
bool foundLocalhost = false; QVariant* permissions = _configMap.valueForKeyPath(keyPath, true);
bool foundAnonymous = false; if (!permissions->canConvert(QMetaType::QVariantList)) {
bool foundLoggedIn = false; qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings.";
bool foundFriends = false;
bool needPack = false;
QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH);
if (!standardPermissions || !standardPermissions->canConvert(QMetaType::QVariantList)) {
qDebug() << "failed to extract standard permissions from settings.";
standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH, true);
(*standardPermissions) = QVariantList();
}
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH);
if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) {
qDebug() << "failed to extract permissions from settings.";
permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH, true);
(*permissions) = QVariantList(); (*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(); bool needPack = false;
foreach (QVariant permsHash, standardPermissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
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[idKey]) |= *perms;
needPack = true;
} else {
_standardAgentPermissions[idKey] = perms;
}
}
QList<QVariant> permissionsList = permissions->toList(); QList<QVariant> permissionsList = permissions->toList();
foreach (QVariant permsHash, permissionsList) { foreach (QVariant permsHash, permissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID(); QString id = perms->getID();
NodePermissionsKey idKey = NodePermissionsKey(id, 0);
if (_agentPermissions.contains(idKey)) { NodePermissionsKey idKey = perms->getKey();
qDebug() << "duplicate name in permissions table: " << id;
*(_agentPermissions[idKey]) |= *perms; if (mapPointer->contains(idKey)) {
qDebug() << "Duplicate name in permissions table for" << keyPath << " - " << id;
*((*mapPointer)[idKey]) |= *perms;
needPack = true; needPack = true;
} else { } else {
_agentPermissions[idKey] = perms; (*mapPointer)[idKey] = perms;
}
if (customUnpacker) {
customUnpacker(perms);
} }
} }
QList<QVariant> groupPermissionsList = groupPermissions->toList(); return needPack;
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()) }; void DomainServerSettingsManager::unpackPermissions() {
QString id = perms->getID(); // transfer details from _configMap to _agentPermissions
NodePermissionsKey idKey = perms->getKey();
if (_groupForbiddens.contains(idKey)) { bool needPack = false;
qDebug() << "duplicate name in group forbiddens table: " << id;
*(_groupForbiddens[idKey]) |= *perms; needPack |= unpackPermissionsForKeypath(AGENT_STANDARD_PERMISSIONS_KEYPATH, &_standardAgentPermissions);
needPack = true;
} else { needPack |= unpackPermissionsForKeypath(AGENT_PERMISSIONS_KEYPATH, &_agentPermissions);
_groupForbiddens[idKey] = perms;
} needPack |= unpackPermissionsForKeypath(IP_PERMISSIONS_KEYPATH, &_ipPermissions,
if (perms->isGroup()) { [&](NodePermissionsPointer perms){
// the group-id was cached. hook-up the uuid in the uuid->group hash // make sure that this permission row is for a valid IP address
_groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupForbiddens[idKey]; if (QHostAddress(perms->getKey().first).isNull()) {
needPack |= setGroupID(perms->getID(), perms->getGroupID()); _ipPermissions.remove(perms->getKey());
}
} // we removed a row from the IP permissions, we'll need a re-pack
needPack = true;
}
});
needPack |= unpackPermissionsForKeypath(GROUP_PERMISSIONS_KEYPATH, &_groupPermissions,
[&](NodePermissionsPointer 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[perms->getKey()];
needPack |= setGroupID(perms->getID(), perms->getGroupID());
}
});
needPack |= unpackPermissionsForKeypath(GROUP_FORBIDDENS_KEYPATH, &_groupForbiddens,
[&](NodePermissionsPointer 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[perms->getKey()];
needPack |= setGroupID(perms->getID(), perms->getGroupID());
}
});
// if any of the standard names are missing, add them // if any of the standard names are missing, add them
if (!foundLocalhost) { foreach(const QString& standardName, NodePermissions::standardNames) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; NodePermissionsKey standardKey { standardName, 0 };
perms->setAll(true); if (!_standardAgentPermissions.contains(standardKey)) {
_standardAgentPermissions[perms->getKey()] = perms; // we don't have permissions for one of the standard groups, so we'll add them now
needPack = true; NodePermissionsPointer perms { new NodePermissions(standardKey) };
}
if (!foundAnonymous) { // the localhost user is granted all permissions by default
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) }; if (standardKey == NodePermissions::standardNameLocalhost) {
_standardAgentPermissions[perms->getKey()] = perms; perms->setAll(true);
needPack = true; }
}
if (!foundLoggedIn) { // add the permissions to the standard map
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) }; _standardAgentPermissions[standardKey] = perms;
_standardAgentPermissions[perms->getKey()] = perms;
needPack = true; // this will require a packing of permissions
} needPack = true;
if (!foundFriends) { }
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameFriends) };
_standardAgentPermissions[perms->getKey()] = perms;
needPack = true;
} }
needPack |= ensurePermissionsForGroupRanks(); needPack |= ensurePermissionsForGroupRanks();
@ -572,7 +555,7 @@ void DomainServerSettingsManager::unpackPermissions() {
qDebug() << "--------------- permissions ---------------------"; qDebug() << "--------------- permissions ---------------------";
QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets; QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets;
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
<< _groupPermissions.get() << _groupForbiddens.get(); << _groupPermissions.get() << _groupForbiddens.get() << _ipPermissions.get();
foreach (auto permissionSet, permissionsSets) { foreach (auto permissionSet, permissionsSets) {
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(permissionSet); QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(permissionSet);
while (i.hasNext()) { while (i.hasNext()) {
@ -648,6 +631,89 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() {
return changed; return changed;
} }
void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// before we do any processing on this packet make sure it comes from a node that is allowed to kick
if (sendingNode->getCanKick()) {
// pull the UUID being kicked from the packet
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (!nodeUUID.isNull() && nodeUUID != sendingNode->getUUID()) {
// make sure we actually have a node with this UUID
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
auto matchingNode = limitedNodeList->nodeWithUUID(nodeUUID);
if (matchingNode) {
// we have a matching node, time to decide how to store updated permissions for this node
NodePermissionsPointer destinationPermissions;
auto verifiedUsername = matchingNode->getPermissions().getVerifiedUserName();
bool hadExistingPermissions = false;
if (!verifiedUsername.isEmpty()) {
// if we have a verified user name for this user, we apply the kick to the username
// check if there were already permissions
hadExistingPermissions = havePermissionsForName(verifiedUsername);
// grab or create permissions for the given username
destinationPermissions = _agentPermissions[matchingNode->getPermissions().getKey()];
} else {
// otherwise we apply the kick to the IP from active socket for this node
// (falling back to the public socket if not yet active)
auto& kickAddress = matchingNode->getActiveSocket()
? matchingNode->getActiveSocket()->getAddress()
: matchingNode->getPublicSocket().getAddress();
NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid());
// check if there were already permissions for the IP
hadExistingPermissions = hasPermissionsForIP(kickAddress);
// grab or create permissions for the given IP address
destinationPermissions = _ipPermissions[ipAddressKey];
}
// make sure we didn't already have existing permissions that disallowed connect
if (!hadExistingPermissions
|| destinationPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID())
<< "after kick request";
// ensure that the connect permission is clear
destinationPermissions->clear(NodePermissions::Permission::canConnectToDomain);
// we've changed permissions, time to store them to disk and emit our signal to say they have changed
packPermissions();
emit updateNodePermissions();
} else {
qWarning() << "Received kick request for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID())
<< "that already did not have permission to connect";
// in this case, though we don't expect the node to be connected to the domain, it is
// emit updateNodePermissions so that the DomainGatekeeper kicks it out
emit updateNodePermissions();
}
} else {
qWarning() << "Node kick request received for unknown node. Refusing to process.";
}
} else {
// this isn't a UUID we can use
qWarning() << "Node kick request received for invalid node ID or from node being kicked. Refusing to process.";
}
} else {
qWarning() << "Refusing to process a kick packet from node" << uuidStringWithoutCurlyBraces(sendingNode->getUUID())
<< "that does not have kick permissions.";
}
}
QStringList DomainServerSettingsManager::getAllNames() const { QStringList DomainServerSettingsManager::getAllNames() const {
QStringList result; QStringList result;
foreach (auto key, _agentPermissions.keys()) { foreach (auto key, _agentPermissions.keys()) {
@ -675,6 +741,16 @@ NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString
return nullPermissions; return nullPermissions;
} }
NodePermissions DomainServerSettingsManager::getPermissionsForIP(const QHostAddress& address) const {
NodePermissionsKey ipKey = NodePermissionsKey(address.toString(), 0);
if (_ipPermissions.contains(ipKey)) {
return *(_ipPermissions[ipKey].get());
}
NodePermissions nullPermissions;
nullPermissions.setAll(false);
return nullPermissions;
}
NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, QUuid rankID) const { NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, QUuid rankID) const {
NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID); NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID);
if (_groupPermissions.contains(groupRankKey)) { if (_groupPermissions.contains(groupRankKey)) {
@ -719,7 +795,7 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid&
} }
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) {
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); const QVariant* foundValue = _configMap.valueForKeyPath(keyPath);
if (foundValue) { if (foundValue) {
return *foundValue; return *foundValue;
@ -802,12 +878,10 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
// setup a JSON Object with descriptions and non-omitted settings // setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions"; const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values"; const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked";
QJsonObject rootObject; QJsonObject rootObject;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray; rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
} }
@ -852,13 +926,13 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
QVariant variantValue; QVariant variantValue;
if (!groupKey.isEmpty()) { if (!groupKey.isEmpty()) {
QVariant settingsMapGroupValue = _configMap.getMergedConfig().value(groupKey); QVariant settingsMapGroupValue = _configMap.value(groupKey);
if (!settingsMapGroupValue.isNull()) { if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingName); variantValue = settingsMapGroupValue.toMap().value(settingName);
} }
} else { } else {
variantValue = _configMap.getMergedConfig().value(settingName); variantValue = _configMap.value(settingName);
} }
QJsonValue result; QJsonValue result;
@ -1000,7 +1074,7 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
} }
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
auto& settingsVariant = _configMap.getUserConfig(); auto& settingsVariant = _configMap.getConfig();
bool needRestart = false; bool needRestart = false;
// Iterate on the setting groups // Iterate on the setting groups
@ -1112,22 +1186,22 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
void DomainServerSettingsManager::sortPermissions() { void DomainServerSettingsManager::sortPermissions() {
// sort the permission-names // sort the permission-names
QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH);
if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) { if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) {
QList<QVariant>* standardPermissionsList = reinterpret_cast<QVariantList*>(standardPermissions); QList<QVariant>* standardPermissionsList = reinterpret_cast<QVariantList*>(standardPermissions);
std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan); std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan);
} }
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); QVariant* permissions = _configMap.valueForKeyPath(AGENT_PERMISSIONS_KEYPATH);
if (permissions && permissions->canConvert(QMetaType::QVariantList)) { if (permissions && permissions->canConvert(QMetaType::QVariantList)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions); QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
} }
QVariant* groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH); QVariant* groupPermissions = _configMap.valueForKeyPath(GROUP_PERMISSIONS_KEYPATH);
if (groupPermissions && groupPermissions->canConvert(QMetaType::QVariantList)) { if (groupPermissions && groupPermissions->canConvert(QMetaType::QVariantList)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(groupPermissions); QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(groupPermissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
} }
QVariant* forbiddenPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH); QVariant* forbiddenPermissions = _configMap.valueForKeyPath(GROUP_FORBIDDENS_KEYPATH);
if (forbiddenPermissions && forbiddenPermissions->canConvert(QMetaType::QVariantList)) { if (forbiddenPermissions && forbiddenPermissions->canConvert(QMetaType::QVariantList)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(forbiddenPermissions); QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(forbiddenPermissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
@ -1147,9 +1221,12 @@ void DomainServerSettingsManager::persistToFile() {
QFile settingsFile(_configMap.getUserConfigFilename()); QFile settingsFile(_configMap.getUserConfigFilename());
if (settingsFile.open(QIODevice::WriteOnly)) { if (settingsFile.open(QIODevice::WriteOnly)) {
settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson()); settingsFile.write(QJsonDocument::fromVariant(_configMap.getConfig()).toJson());
} else { } else {
qCritical("Could not write to JSON settings file. Unable to persist settings."); qCritical("Could not write to JSON settings file. Unable to persist settings.");
// failed to write, reload whatever the current config state is
_configMap.loadConfig(_argumentList);
} }
} }

View file

@ -27,6 +27,7 @@ const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions"; const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
@ -43,8 +44,7 @@ public:
void setupConfigMap(const QStringList& argumentList); void setupConfigMap(const QStringList& argumentList);
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath); QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } QVariantMap& getSettingsMap() { return _configMap.getConfig(); }
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
QVariantMap& getDescriptorsMap(); QVariantMap& getDescriptorsMap();
@ -58,6 +58,10 @@ public:
NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); } NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); }
QStringList getAllNames() const; QStringList getAllNames() const;
// these give access to permissions for specific IPs from the domain-server settings page
bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), 0); }
NodePermissions getPermissionsForIP(const QHostAddress& address) const;
// these give access to permissions for specific groups from the domain-server settings page // these give access to permissions for specific groups from the domain-server settings page
bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const { bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const {
return _groupPermissions.contains(groupName, rankID); return _groupPermissions.contains(groupName, rankID);
@ -100,6 +104,7 @@ public slots:
private slots: private slots:
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message); void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
void processNodeKickRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
private: private:
QStringList _argumentList; QStringList _argumentList;
@ -129,11 +134,15 @@ private:
void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath); void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath);
void packPermissions(); void packPermissions();
void unpackPermissions(); void unpackPermissions();
bool unpackPermissionsForKeypath(const QString& keyPath, NodePermissionsMap* destinationMapPointer,
std::function<void(NodePermissionsPointer)> customUnpacker = {});
bool ensurePermissionsForGroupRanks(); bool ensurePermissionsForGroupRanks();
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner
NodePermissionsMap _agentPermissions; // specific account-names NodePermissionsMap _agentPermissions; // specific account-names
NodePermissionsMap _ipPermissions; // permissions granted by node IP address
NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups
NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group 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 // these are like _groupPermissions and _groupForbiddens but with uuids rather than group-names in the keys

View file

@ -151,6 +151,10 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) { newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) {
emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer)); emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer));
} }
if (originalPermissions.can(NodePermissions::Permission::canKick) !=
newPermissions.can(NodePermissions::Permission::canKick)) {
emit canKickChanged(_permissions.can(NodePermissions::Permission::canKick));
}
} }
QUdpSocket& LimitedNodeList::getDTLSSocket() { QUdpSocket& LimitedNodeList::getDTLSSocket() {

View file

@ -110,6 +110,7 @@ public:
bool getThisNodeCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); } bool getThisNodeCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); }
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
QUdpSocket& getDTLSSocket(); QUdpSocket& getDTLSSocket();
@ -258,6 +259,7 @@ signals:
void canRezChanged(bool canRez); void canRezChanged(bool canRez);
void canRezTmpChanged(bool canRezTmp); void canRezTmpChanged(bool canRezTmp);
void canWriteAssetsChanged(bool canWriteAssets); void canWriteAssetsChanged(bool canWriteAssets);
void canKickChanged(bool canKick);
protected slots: protected slots:
void connectedForLocalSocketTest(); void connectedForLocalSocketTest();

View file

@ -69,6 +69,7 @@ public:
bool getCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); } bool getCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
bool getCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); }
void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message); void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message);
void addIgnoredNode(const QUuid& otherNodeID); void addIgnoredNode(const QUuid& otherNodeID);

View file

@ -727,7 +727,7 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
emit ignoredNode(nodeID); emit ignoredNode(nodeID);
} else { } else {
qWarning() << "UsersScriptingInterface::ignore called with an invalid ID or an ID which matches the current session ID."; qWarning() << "NodeList::ignoreNodeBySessionID called with an invalid ID or an ID which matches the current session ID.";
} }
} }
@ -759,3 +759,28 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
} }
} }
} }
void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
// send a request to domain-server to kick the node with the given session ID
// the domain-server will handle the persistence of the kick (via username or IP)
if (!nodeID.isNull() && _sessionUUID != nodeID ) {
if (getThisNodeCanKick()) {
// setup the packet
auto kickPacket = NLPacket::create(PacketType::NodeKickRequest, NUM_BYTES_RFC4122_UUID, true);
// write the node ID to the packet
kickPacket->write(nodeID.toRfc4122());
qDebug() << "Sending packet to kick node" << uuidStringWithoutCurlyBraces(nodeID);
sendPacket(std::move(kickPacket), _domainHandler.getSockAddr());
} else {
qWarning() << "You do not have permissions to kick in this domain."
<< "Request to kick node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent";
}
} else {
qWarning() << "NodeList::kickNodeBySessionID called with an invalid ID or an ID which matches the current session ID.";
}
}

View file

@ -73,6 +73,8 @@ public:
void ignoreNodeBySessionID(const QUuid& nodeID); void ignoreNodeBySessionID(const QUuid& nodeID);
bool isIgnoringNode(const QUuid& nodeID) const; bool isIgnoringNode(const QUuid& nodeID) const;
void kickNodeBySessionID(const QUuid& nodeID);
public slots: public slots:
void reset(); void reset();
void sendDomainServerCheckIn(); void sendDomainServerCheckIn();

View file

@ -44,6 +44,7 @@ NodePermissions::NodePermissions(QMap<QString, QVariant> perms) {
permissions |= perms["id_can_write_to_asset_server"].toBool() ? Permission::canWriteToAssetServer : Permission::none; permissions |= perms["id_can_write_to_asset_server"].toBool() ? Permission::canWriteToAssetServer : Permission::none;
permissions |= perms["id_can_connect_past_max_capacity"].toBool() ? permissions |= perms["id_can_connect_past_max_capacity"].toBool() ?
Permission::canConnectPastMaxCapacity : Permission::none; Permission::canConnectPastMaxCapacity : Permission::none;
permissions |= perms["id_can_kick"].toBool() ? Permission::canKick : Permission::none;
} }
QVariant NodePermissions::toVariant(QHash<QUuid, GroupRank> groupRanks) { QVariant NodePermissions::toVariant(QHash<QUuid, GroupRank> groupRanks) {
@ -63,6 +64,7 @@ QVariant NodePermissions::toVariant(QHash<QUuid, GroupRank> groupRanks) {
values["id_can_rez_tmp"] = can(Permission::canRezTemporaryEntities); values["id_can_rez_tmp"] = can(Permission::canRezTemporaryEntities);
values["id_can_write_to_asset_server"] = can(Permission::canWriteToAssetServer); values["id_can_write_to_asset_server"] = can(Permission::canWriteToAssetServer);
values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity); values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity);
values["id_can_kick"] = can(Permission::canKick);
return QVariant(values); return QVariant(values);
} }
@ -123,6 +125,9 @@ QDebug operator<<(QDebug debug, const NodePermissions& perms) {
if (perms.can(NodePermissions::Permission::canConnectPastMaxCapacity)) { if (perms.can(NodePermissions::Permission::canConnectPastMaxCapacity)) {
debug << " ignore-max-cap"; debug << " ignore-max-cap";
} }
if (perms.can(NodePermissions::Permission::canKick)) {
debug << " kick";
}
debug.nospace() << "]"; debug.nospace() << "]";
return debug.nospace(); return debug.nospace();
} }

View file

@ -63,7 +63,8 @@ public:
canRezPermanentEntities = 4, canRezPermanentEntities = 4,
canRezTemporaryEntities = 8, canRezTemporaryEntities = 8,
canWriteToAssetServer = 16, canWriteToAssetServer = 16,
canConnectPastMaxCapacity = 32 canConnectPastMaxCapacity = 32,
canKick = 64
}; };
Q_DECLARE_FLAGS(Permissions, Permission) Q_DECLARE_FLAGS(Permissions, Permission)
Permissions permissions; Permissions permissions;

View file

@ -26,7 +26,7 @@ const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
<< PacketType::NodeJsonStats << PacketType::EntityQuery << PacketType::NodeJsonStats << PacketType::EntityQuery
<< PacketType::OctreeDataNack << PacketType::EntityEditNack << PacketType::OctreeDataNack << PacketType::EntityEditNack
<< PacketType::DomainListRequest << PacketType::StopNode << PacketType::DomainListRequest << PacketType::StopNode
<< PacketType::DomainDisconnectRequest; << PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest;
const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>() const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
<< PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment << PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment

View file

@ -98,7 +98,8 @@ public:
NegotiateAudioFormat, NegotiateAudioFormat,
SelectedAudioFormat, SelectedAudioFormat,
MoreEntityShapes, MoreEntityShapes,
LAST_PACKET_TYPE = MoreEntityShapes NodeKickRequest,
LAST_PACKET_TYPE = NodeKickRequest
}; };
}; };

View file

@ -13,7 +13,23 @@
#include <NodeList.h> #include <NodeList.h>
UsersScriptingInterface::UsersScriptingInterface() {
// emit a signal when kick permissions have changed
auto nodeList = DependencyManager::get<NodeList>();
connect(nodeList.data(), &LimitedNodeList::canKickChanged, this, &UsersScriptingInterface::canKickChanged);
}
void UsersScriptingInterface::ignore(const QUuid& nodeID) { void UsersScriptingInterface::ignore(const QUuid& nodeID) {
// ask the NodeList to ignore this user (based on the session ID of their node) // ask the NodeList to ignore this user (based on the session ID of their node)
DependencyManager::get<NodeList>()->ignoreNodeBySessionID(nodeID); DependencyManager::get<NodeList>()->ignoreNodeBySessionID(nodeID);
} }
void UsersScriptingInterface::kick(const QUuid& nodeID) {
// ask the NodeList to kick the user with the given session ID
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
}
bool UsersScriptingInterface::getCanKick() {
// ask the NodeList to return our ability to kick
return DependencyManager::get<NodeList>()->getThisNodeCanKick();
}

View file

@ -20,8 +20,19 @@ class UsersScriptingInterface : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
Q_PROPERTY(bool canKick READ getCanKick)
public:
UsersScriptingInterface();
public slots: public slots:
void ignore(const QUuid& nodeID); void ignore(const QUuid& nodeID);
void kick(const QUuid& nodeID);
bool getCanKick();
signals:
void canKickChanged(bool canKick);
}; };

View file

@ -111,6 +111,13 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi
loadMapFromJSONFile(_masterConfig, masterConfigFilepath); loadMapFromJSONFile(_masterConfig, masterConfigFilepath);
} }
// load the user config - that method replace loadMasterAndUserConfig after the 1.7 migration
loadConfig(argumentList);
mergeMasterAndUserConfigs();
}
void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
// load the user config // load the user config
const QString USER_CONFIG_FILE_OPTION = "--user-config"; const QString USER_CONFIG_FILE_OPTION = "--user-config";
static const QString USER_CONFIG_FILE_NAME = "config.json"; static const QString USER_CONFIG_FILE_NAME = "config.json";
@ -159,12 +166,10 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi
} }
} }
} }
} }
loadMapFromJSONFile(_userConfig, _userConfigFilename); loadMapFromJSONFile(_userConfig, _userConfigFilename);
mergeMasterAndUserConfigs();
} }
void HifiConfigVariantMap::mergeMasterAndUserConfigs() { void HifiConfigVariantMap::mergeMasterAndUserConfigs() {

View file

@ -15,16 +15,22 @@
#include <QtCore/QStringList> #include <QtCore/QStringList>
#include <QtCore/QVariantMap> #include <QtCore/QVariantMap>
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
class HifiConfigVariantMap { class HifiConfigVariantMap {
public: public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
HifiConfigVariantMap(); HifiConfigVariantMap();
void loadMasterAndUserConfig(const QStringList& argumentList); void loadMasterAndUserConfig(const QStringList& argumentList);
void loadConfig(const QStringList& argumentList);
const QVariant value(const QString& key) const { return _userConfig.value(key); }
QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false)
{ return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); }
const QVariantMap& getMasterConfig() const { return _masterConfig; }
QVariantMap& getUserConfig() { return _userConfig; }
QVariantMap& getMergedConfig() { return _mergedConfig; } QVariantMap& getMergedConfig() { return _mergedConfig; }
QVariantMap& getConfig() { return _userConfig; }
void mergeMasterAndUserConfigs(); void mergeMasterAndUserConfigs();
@ -40,6 +46,4 @@ private:
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap); void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
}; };
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
#endif // hifi_HifiConfigVariantMap_h #endif // hifi_HifiConfigVariantMap_h

View file

@ -17,7 +17,7 @@ Script.load("system/goto.js");
Script.load("system/hmd.js"); Script.load("system/hmd.js");
Script.load("system/marketplace.js"); Script.load("system/marketplace.js");
Script.load("system/edit.js"); Script.load("system/edit.js");
Script.load("system/ignore.js"); Script.load("system/mod.js");
Script.load("system/selectAudioDevice.js"); Script.load("system/selectAudioDevice.js");
Script.load("system/notifications.js"); Script.load("system/notifications.js");
Script.load("system/controllers/handControllerGrab.js"); Script.load("system/controllers/handControllerGrab.js");

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 23.7 34.7" style="enable-background:new 0 0 23.7 34.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#58595B;}
.st2{fill:#EF3B4E;}
</style>
<path class="st0" d="M23,12.5c0-6.2-5-11.2-11.2-11.2c-6.2,0-11.2,5-11.2,11.2c0,5.2,3.6,9.6,8.4,10.8l3.2,10.1l2.6-10.2
C19.6,21.8,23,17.5,23,12.5z M11.9,22.2c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
C21.6,17.9,17.3,22.2,11.9,22.2z"/>
<path class="st0" d="M12.3,33.9L9,23.4c-5-1.3-8.5-5.8-8.5-10.9c0-6.2,5.1-11.3,11.3-11.3c6.2,0,11.3,5.1,11.3,11.3
c0,5-3.3,9.4-8.1,10.8L12.3,33.9z M11.9,1.4c-6.1,0-11.1,5-11.1,11.1c0,5.1,3.4,9.5,8.3,10.7l0.1,0l0,0.1l3.1,9.7l2.5-9.9l0.1,0
c4.7-1.4,8-5.7,8-10.6C22.9,6.4,18,1.4,11.9,1.4z M11.9,22.4c-5.5,0-9.9-4.4-9.9-9.9s4.4-9.9,9.9-9.9s9.9,4.4,9.9,9.9
S17.3,22.4,11.9,22.4z M11.9,2.8c-5.3,0-9.7,4.3-9.7,9.7c0,5.3,4.3,9.7,9.7,9.7s9.7-4.3,9.7-9.7C21.5,7.1,17.2,2.8,11.9,2.8z"/>
<g>
<path class="st0" d="M16,8.3c-0.4-0.4-1.1-0.4-1.5,0L11.8,11L9,8.2c-0.4-0.4-1.1-0.4-1.5,0C7.1,8.7,7,9.4,7.5,9.8l2.7,2.7l-2.7,2.7
c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0c0.4-0.4,0.4-1.1,0-1.5l-2.7-2.7l2.7-2.7
C16.5,9.4,16.5,8.7,16,8.3z"/>
<path class="st1" d="M8.3,17.3c-0.3,0-0.6-0.1-0.9-0.4C7.2,16.7,7,16.4,7,16.1c0-0.3,0.1-0.6,0.4-0.9l2.6-2.7L7.4,9.9
c-0.5-0.5-0.5-1.2,0-1.7c0.5-0.5,1.2-0.5,1.7,0l2.7,2.7l2.6-2.7c0.2-0.2,0.5-0.4,0.9-0.4l0,0c0.3,0,0.6,0.1,0.9,0.4l0,0
c0,0,0,0,0,0l0,0c0.2,0.2,0.4,0.5,0.4,0.9c0,0.3-0.1,0.6-0.4,0.9l-2.7,2.7l2.7,2.6c0.2,0.2,0.4,0.5,0.4,0.9c0,0.3-0.1,0.6-0.4,0.9
c-0.5,0.5-1.3,0.5-1.7,0l-2.7-2.6l-2.7,2.7C8.9,17.2,8.6,17.3,8.3,17.3z M8.3,8.4C8.3,8.4,8.3,8.4,8.3,8.4C8,8.4,7.7,8.3,7.6,8.5
C7.4,8.7,7.3,8.9,7.3,9.1c0,0.3,0.1,0.5,0.3,0.6l2.8,2.8l-2.8,2.8c-0.4,0.4-0.4,1,0,1.4c0.4,0.4,1,0.4,1.4,0l2.8-2.8l2.8,2.8
c0.4,0.4,1,0.4,1.4,0c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7l-2.8-2.8L16,9.7c0.2-0.2,0.3-0.4,0.3-0.7
c0-0.3-0.1-0.5-0.3-0.7l0,0c-0.2-0.2-0.4-0.3-0.7-0.3l0,0c-0.3,0-0.5,0.1-0.7,0.3l-2.8,2.8L8.9,8.5C8.8,8.3,8.5,8.4,8.3,8.4z"/>
</g>
<g>
<path class="st2" d="M23,12c0-6.2-5-11.2-11.2-11.2C5.7,0.8,0.7,5.9,0.7,12c0,5.2,3.6,9.6,8.4,10.8L12.3,33L15,22.8
C19.6,21.4,23,17.1,23,12z M11.9,21.8c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
C21.7,17.4,17.3,21.8,11.9,21.8z"/>
<path class="st2" d="M16.1,7.8c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7L9,7.8c-0.4-0.4-1.1-0.4-1.5,0C7.1,8.3,7.1,9,7.5,9.4l2.7,2.7
l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0c0.4-0.4,0.4-1.1,0-1.5l-2.7-2.7
l2.7-2.7C16.5,9,16.5,8.3,16.1,7.8z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 293 225.4" style="enable-background:new 0 0 293 225.4;" xml:space="preserve">
<style type="text/css">
.st0{fill:#EF3B4E;}
.st1{fill:#FFFFFF;}
</style>
<polygon points="121.5,121.9 152,219 177.7,121.9 "/>
<polygon class="st0" points="119.2,119 149.7,216.1 175.4,119 "/>
<path d="M16.2,23.2H283c3.9,0,7.1,3.2,7.1,7.1V88c0,3.9-3.2,7.1-7.1,7.1H16.2c-3.9,0-7.1-3.2-7.1-7.1V30.3
C9.1,26.4,12.3,23.2,16.2,23.2z"/>
<path class="st0" d="M13.9,20.3h266.8c3.9,0,7.1,3.2,7.1,7.1v57.7c0,3.9-3.2,7.1-7.1,7.1H13.9c-3.9,0-7.1-3.2-7.1-7.1V27.4
C6.8,23.5,10,20.3,13.9,20.3z"/>
<path d="M40,79.8v-41h8v41H40z"/>
<path d="M85.6,75.2c-2.8,3.1-6.7,4.9-10.9,4.9c-2.6-0.1-5.2-0.8-7.5-2c-2.1-1.1-4-2.6-5.6-4.3c-1.7-2-3.1-4.3-4-6.7
c-1.2-2.6-1.8-5.4-1.9-8.2c0-2.6,0.6-5.3,1.5-7.7c1-2.5,2.5-4.7,4.4-6.6c1.6-1.8,3.6-3.3,5.8-4.4c5.6-2.5,12-2.2,17.4,0.8
c2.5,1.6,4.5,3.9,5.8,6.6l-6,4.4c-0.8-1.9-2-3.5-3.7-4.7c-1.7-1.1-3.7-1.6-5.8-1.6c-1.6,0-3.2,0.3-4.6,1.1c-1.4,0.7-2.5,1.8-3.5,3
c-1,1.3-1.7,2.8-2.2,4.4c-0.5,1.7-0.8,3.5-0.8,5.3c0,1.8,0.3,3.7,0.9,5.4c0.5,1.6,1.3,3.1,2.4,4.3c1,1.2,2.3,2.2,3.7,2.9
c1.4,0.7,3,1.1,4.6,1.1c4-0.2,7.7-2.2,10-5.5v-2.9h-8.3v-5.8h14.8v21h-6.6V75.2z"/>
<path d="M108.4,53.5v26.3h-8.3V38.7h6.2l21.4,27V38.8h8v41h-6.2L108.4,53.5z"/>
<path d="M163.1,80.1c-2.8-0.1-5.5-0.7-7.9-2c-2.4-1.1-4.5-2.7-6.2-4.7c-1.7-2-3.1-4.2-4-6.7c-0.9-2.5-1.4-5.1-1.3-7.7
c0-5.4,2-10.6,5.6-14.6c1.7-1.9,3.8-3.5,6.2-4.6c2.4-1.2,5.1-1.8,7.8-1.7c2.7,0,5.5,0.6,7.9,1.8c2.4,1.2,4.5,2.8,6.2,4.8
c3.4,4,5.3,9.1,5.3,14.4c0,2.7-0.5,5.3-1.4,7.8c-0.9,2.4-2.3,4.6-4,6.6c-1.7,1.9-3.8,3.4-6.2,4.5C168.5,79.3,165.8,80,163.1,80.1z
M151.7,59.3c0,1.7,0.3,3.5,0.8,5.1c0.5,1.6,1.2,3.1,2.2,4.4c1,1.3,2.2,2.3,3.7,3.1c1.5,0.8,3.1,1.2,4.8,1.2
c1.7,0.1,3.4-0.3,4.9-1.2c1.4-0.9,2.7-2.1,3.6-3.5c1-1.3,1.7-2.8,2.2-4.4c0.5-1.6,0.8-3.3,0.8-5c0-1.7-0.2-3.5-0.8-5.1
c-0.5-1.6-1.3-3.1-2.3-4.4c-1.1-1.2-2.5-2.1-4-2.7c-3-1.5-6.6-1.5-9.6,0c-1.4,0.8-2.6,1.8-3.6,3.1c-1,1.3-1.7,2.8-2.2,4.4
C151.8,55.9,151.6,57.6,151.7,59.3z"/>
<path d="M190.3,79.8V38.7h18.1c1.8,0,3.6,0.4,5.2,1.2c1.6,0.8,3,1.8,4.1,3.1c1.2,1.3,2.1,2.8,2.7,4.4c0.6,1.6,1,3.2,1,4.9
c0,2.6-0.7,5.1-2,7.2c-1.2,2.1-3.1,3.8-5.4,4.7l9.6,15.4h-8.9L206.2,66h-7.9v13.8H190.3z M198.3,58.8h9.6c0.8,0.2,1.6,0.2,2.4,0
c0.6-0.4,1.2-0.9,1.6-1.4c0.5-0.6,0.8-1.4,1.1-2.1c0.1-0.9,0.1-1.7,0-2.6c0.2-0.9,0.2-1.8,0-2.7c-0.3-0.8-0.7-1.5-1.3-2.1
c-0.5-0.6-1.1-1-1.8-1.3c-0.6-0.3-1.3-0.5-2-0.5h-9.6L198.3,58.8z"/>
<path d="M259.2,72.8v6.9h-28.9V38.7h28v7h-19.6v9.6h17.3v6.7h-17.3v10.7H259.2z"/>
<path class="st1" d="M38.1,78.1V37.2h8v40.9H38.1z"/>
<path class="st1" d="M83.7,73.5c-2.9,3-6.9,4.6-11,4.5c-2.5,0.1-5-0.3-7.3-1.3c-2.3-1.1-4.4-2.7-6.1-4.6c-1.7-2-3.1-4.3-4-6.7
c-1-2.5-1.5-5.2-1.4-7.9c-0.1-2.8,0.4-5.6,1.4-8.3c1-2.4,2.4-4.7,4.1-6.6c1.8-1.9,3.8-3.4,6.2-4.4c5.6-2.3,12-1.8,17.3,1.3
c2.6,1.6,4.7,3.8,6,6.6l-6,4.4c-0.9-1.9-2.3-3.6-4.1-4.7c-1.7-1.1-3.7-1.6-5.8-1.6c-1.6,0-3.2,0.3-4.6,1.1c-1.4,0.7-2.5,1.8-3.5,3
c-1,1.3-1.7,2.8-2.2,4.4c-0.6,1.6-1,3.3-1.1,5c0,1.8,0.3,3.7,0.9,5.4c0.5,1.6,1.3,3.1,2.4,4.3c1,1.2,2.3,2.2,3.7,2.9
c1.4,0.7,3,1.1,4.6,1.1c4.2,0,8.2-2.1,10.6-5.5v-2.9h-8.3V57h15v21h-6.7V73.5z"/>
<path class="st1" d="M106.6,51.8v26.2h-8v-41h6.2l21.4,27V37.2h8v40.9h-6.5L106.6,51.8z"/>
<path class="st1" d="M161.2,78.1c-2.7,0-5.4-0.6-7.9-1.7c-2.4-1.1-4.5-2.7-6.2-4.7c-1.7-2-3.1-4.2-4-6.7c-0.9-2.5-1.4-5.1-1.3-7.7
c0-5.4,2-10.6,5.6-14.6c1.7-1.9,3.8-3.5,6.2-4.6c2.4-1.2,5.1-1.8,7.8-1.7c2.7,0,5.5,0.6,7.9,1.8c2.4,1.2,4.5,2.8,6.2,4.8
c3.4,4,5.3,9.1,5.3,14.4c0,2.7-0.5,5.3-1.4,7.8c-0.9,2.4-2.3,4.6-4,6.6c-1.7,1.9-3.8,3.4-6.2,4.5C166.7,77.4,164,78.1,161.2,78.1z
M149.9,57.6c0,1.7,0.3,3.5,0.8,5.1c0.5,1.6,1.2,3.1,2.2,4.4c1,1.3,2.2,2.3,3.7,3.1c1.5,0.8,3.1,1.2,4.8,1.2
c1.7,0.1,3.4-0.3,4.9-1.2c1.4-0.8,2.6-1.9,3.6-3.2c1-1.3,1.7-2.8,2.2-4.4c0.5-1.6,0.8-3.3,0.8-5c0-1.7-0.2-3.5-0.8-5.1
c-0.5-1.6-1.3-3.1-2.3-4.4c-1-1.2-2.2-2.3-3.6-3c-3-1.5-6.6-1.5-9.6,0c-1.4,0.8-2.6,1.8-3.6,3.1c-1,1.3-1.7,2.8-2.2,4.4
C150.2,54.2,149.9,55.9,149.9,57.6z"/>
<path class="st1" d="M188.4,78.1v-41h17.7c1.8,0,3.6,0.4,5.2,1.2c1.7,0.7,3.2,1.8,4.4,3.2c1.2,1.3,2.1,2.8,2.7,4.4
c0.6,1.6,1,3.2,1,4.9c0,2.6-0.7,5.1-2,7.2c-1.2,2.1-3.1,3.8-5.4,4.7l9.6,15.4h-8.8l-8.6-13.8h-7.8v13.7H188.4z M196.5,57.4h9.6
c0.7,0,1.4-0.2,2-0.5c0.6-0.4,1.2-0.9,1.6-1.4c0.5-0.6,0.8-1.4,1.1-2.1c0.1-0.9,0.1-1.7,0-2.6c0.2-0.9,0.2-1.8,0-2.7
c-0.3-0.8-0.7-1.5-1.3-2.1c-0.5-0.6-1.1-1-1.8-1.3c-0.5-0.3-1.1-0.4-1.6-0.5h-9.6V57.4z"/>
<path class="st1" d="M257.4,71.1v6.9h-28.9v-41h28v7h-19.6v9.6h17.5v6.7h-17.5v10.7H257.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 293 225.4" style="enable-background:new 0 0 293 225.4;" xml:space="preserve">
<style type="text/css">
.st0{fill:#EF3B4E;}
.st1{fill:#FFFFFF;}
</style>
<path d="M15.1,22.7h266.8c3.9,0,7.1,3.2,7.1,7.1v57.7c0,3.9-3.2,7.1-7.1,7.1H15.1c-3.9,0-7.1-3.2-7.1-7.1V29.9
C7.9,25.9,11.1,22.7,15.1,22.7z"/>
<polygon points="120.4,121.9 150.8,219 176.6,121.9 "/>
<path class="st0" d="M12.8,19.9h266.8c3.9,0,7.1,3.2,7.1,7.1v57.7c0,3.9-3.2,7.1-7.1,7.1H12.8c-3.9,0-7.1-3.2-7.1-7.1V27
C5.6,23,8.8,19.9,12.8,19.9z"/>
<path d="M81.8,74.3V34.8h7.5v19.3l17.2-19.3h8L99,52.3l16.1,22h-7.7L94.8,56.7l-5.5,5.7v12H81.8z"/>
<path d="M120.4,74.3V34.8h7.5v39.5h-7.7H120.4z"/>
<path d="M135.4,54.3c0-2.4,0.4-4.8,1.3-7.1c0.8-2.3,2.1-4.5,3.7-6.4c1.7-1.9,3.7-3.4,6-4.5c2.5-1.2,5.2-1.8,8-1.7
c3.4-0.2,6.7,0.7,9.6,2.4c2.5,1.5,4.4,3.7,5.7,6.3l-5.9,4c-0.4-1-1-2-1.7-2.8c-0.7-0.7-1.4-1.4-2.3-1.8c-0.8-0.4-1.7-0.8-2.6-1
c-0.9-0.1-1.7-0.1-2.6,0c-1.7,0-3.3,0.4-4.8,1.2c-1.4,0.8-2.6,1.8-3.5,3.1c-0.9,1.3-1.6,2.7-2,4.2c-0.5,1.5-0.7,3.1-0.7,4.7
c0,1.7,0.2,3.4,0.8,5c0.5,1.5,1.3,3,2.3,4.2c1,1.2,2.2,2.2,3.6,3c1.4,0.8,2.9,1.1,4.5,1.2c0.7,0,1.4-0.1,2.1-0.3
c1.9-0.4,3.6-1.5,4.9-2.9c0.7-0.8,1.3-1.8,1.7-2.8l6.3,3.7c-0.6,1.5-1.5,2.8-2.6,4c-1.1,1.1-2.4,2.1-3.9,2.9c-1.4,0.8-3,1.4-4.5,1.8
c-1.5,0.4-3.1,0.6-4.7,0.6c-2.6,0-5.2-0.6-7.5-1.8c-2.3-1.4-4.3-3.2-5.9-5.4C137.3,64.1,135.5,59.3,135.4,54.3z"/>
<path d="M176.1,74.3V34.8h7.7v19.3L201,34.8h8l-15.1,17.5l16.1,22H202l-12.6-17.6l-5.3,5.8v12L176.1,74.3L176.1,74.3z"/>
<path class="st1" d="M79.7,72.7V33.2h7.7v19.3l17.2-19.3h8L97.6,50.6l16.1,22h-8.1L93,54.9l-5.3,5.8v12H79.7z"/>
<path class="st1" d="M118.2,72.7V33.2h7.7v39.5H118.2z"/>
<path class="st1" d="M133.6,52.5c0-2.4,0.4-4.8,1.3-7.1c0.8-2.3,2.1-4.5,3.8-6.4c1.7-1.9,3.7-3.4,6-4.5c2.5-1.2,5.2-1.8,8-1.7
c3.4-0.2,6.7,0.7,9.6,2.4c2.5,1.5,4.4,3.7,5.7,6.3l-5.9,4c-0.4-1-1-2-1.7-2.8c-0.7-0.7-1.4-1.4-2.3-1.8c-0.8-0.4-1.7-0.8-2.6-1
c-0.9-0.1-1.7-0.1-2.6,0c-1.7,0-3.3,0.4-4.8,1.2c-1.4,0.8-2.6,1.8-3.5,3.1c-0.9,1.3-1.6,2.7-2,4.2c-0.5,1.5-0.7,3.1-0.7,4.7
c0,1.7,0.2,3.4,0.8,5c0.5,1.5,1.3,3,2.3,4.2c1,1.2,2.2,2.2,3.6,3c1.4,0.8,2.9,1.1,4.5,1.2c0.9,0.1,1.7,0.1,2.6,0
c1.9-0.4,3.6-1.5,4.9-2.9c0.7-0.8,1.3-1.8,1.7-2.8l6.3,3.7c-0.6,1.5-1.5,2.8-2.6,4c-1.1,1.1-2.4,2.1-3.9,2.9c-1.4,0.8-3,1.4-4.5,1.8
c-1.5,0.4-3.1,0.6-4.7,0.6c-2.6,0-5.2-0.6-7.5-1.8c-2.2-1.2-4.2-2.7-5.9-4.6C135.6,63.3,133.6,58,133.6,52.5z"/>
<path class="st1" d="M174.6,72.7V33.2h7.7v19.3l17.2-19.3h8l-15.2,17.5l16.1,22h-8.1l-12.6-17.6l-5.3,5.8v12H174.6z"/>
<polygon class="st0" points="118,119 148.5,216.1 174.3,119 "/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#FFFFFF;}
.st2{fill:#1E1E1E;}
.st3{fill:#333333;}
</style>
<g id="Layer_2">
<g>
<g>
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
</g>
</g>
<g>
<g>
<path class="st0" d="M50,196.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V196.1z"/>
</g>
</g>
<g>
<g>
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
</g>
</g>
<g>
<path class="st2" d="M50,96.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.1z"/>
</g>
</g>
<path class="st1" d="M24.7,81.2c-6.2,0-11.2-5-11.2-11.2c0-6.2,5-11.2,11.2-11.2c6.2,0,11.2,5,11.2,11.2
C35.9,76.2,30.9,81.2,24.7,81.2z M24.7,60.3c-5.4,0-9.8,4.4-9.8,9.8c0,5.4,4.4,9.8,9.8,9.8c5.4,0,9.8-4.4,9.8-9.8
C34.5,64.6,30.1,60.3,24.7,60.3z"/>
<path class="st1" d="M27.1,79.5c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
c1,0,2-0.2,3-0.4C27.5,80.4,27.3,80,27.1,79.5z"/>
<path class="st1" d="M31.2,66.5L31.2,66.5c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,66.9,31.4,66.7,31.2,66.5z"/>
<circle class="st1" cx="24.7" cy="64" r="1.7"/>
<path class="st3" d="M27.1,29.7c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9C35.6,13.7,30.7,9,24.7,9c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
c1,0,2-0.2,3-0.4C27.5,30.5,27.3,30.1,27.1,29.7z"/>
<path class="st3" d="M31.2,16.6L31.2,16.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,17.1,31.4,16.8,31.2,16.6z"/>
<circle class="st3" cx="24.7" cy="14.1" r="1.7"/>
<g>
<polygon class="st3" points="36.4,30.1 36.4,30.1 36.4,30.1 "/>
<path class="st3" d="M35.2,25.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
c0.4-0.4,0.4-1.1,0-1.5L35.2,25.8z"/>
</g>
<path class="st1" d="M27.3,129.6c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
c1,0,2-0.2,3-0.4C27.6,130.5,27.4,130.1,27.3,129.6z"/>
<path class="st1" d="M31.3,116.6L31.3,116.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
c0.3-0.1,0.4-0.3,0.5-0.6C31.6,117,31.5,116.8,31.3,116.6z"/>
<circle class="st1" cx="24.9" cy="114.1" r="1.7"/>
<g>
<polygon class="st1" points="36.5,130.1 36.5,130.1 36.5,130.1 "/>
<path class="st1" d="M35.3,125.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
c0.4-0.4,0.4-1.1,0-1.5L35.3,125.8z"/>
</g>
<path class="st1" d="M27.1,179.9c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
c1,0,2-0.2,3-0.4C27.5,180.7,27.3,180.3,27.1,179.9z"/>
<path class="st1" d="M31.2,166.8L31.2,166.8c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,167.3,31.4,167,31.2,166.8z"/>
<circle class="st1" cx="24.7" cy="164.3" r="1.7"/>
<g>
<polygon class="st1" points="36.4,180.3 36.4,180.3 36.4,180.3 "/>
<path class="st1" d="M35.2,176l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
c0.4-0.4,0.4-1.1,0-1.5L35.2,176z"/>
</g>
<g>
<path class="st1" d="M14.7,92.3v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
<path class="st1" d="M20.9,92.3v-6.4h1.2v6.4H20.9z"/>
<path class="st1" d="M23.3,89.1c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.3-0.3
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
C23.4,89.9,23.3,89.5,23.3,89.1z"/>
<path class="st1" d="M30,92.3v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30z"/>
</g>
<g>
<path class="st3" d="M14.7,42.6v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
<path class="st3" d="M20.9,42.6v-6.4h1.2v6.4H20.9z"/>
<path class="st3" d="M23.3,39.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.3-0.3
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2S25.2,37.8,25,38c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
C23.4,40.2,23.3,39.8,23.3,39.4z"/>
<path class="st3" d="M30,42.6v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30z"/>
</g>
<g>
<path class="st1" d="M14.8,142.4v-6.4H16v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.8z"/>
<path class="st1" d="M21,142.4v-6.4h1.2v6.4H21z"/>
<path class="st1" d="M23.4,139.2c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.3-0.3
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
s0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5c-0.2,0.1-0.5,0.2-0.7,0.3
c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
C23.5,140,23.4,139.6,23.4,139.2z"/>
<path class="st1" d="M30.1,142.4v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30.1z"/>
</g>
<g>
<path class="st1" d="M14.7,192.7v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
<path class="st1" d="M20.9,192.7v-6.4h1.2v6.4H20.9z"/>
<path class="st1" d="M23.3,189.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.3-0.3
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3s-0.3-0.1-0.4-0.2
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
C23.4,190.2,23.3,189.8,23.3,189.4z"/>
<path class="st1" d="M29.9,192.7v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H29.9z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -1,5 +1,5 @@
// //
// ignore.js // mod.js
// scripts/system/ // scripts/system/
// //
// Created by Stephen Birarda on 07/11/2016 // Created by Stephen Birarda on 07/11/2016
@ -12,10 +12,14 @@
// grab the toolbar // grab the toolbar
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
// setup the ignore button and add it to the toolbar function buttonImageURL() {
return Script.resolvePath("assets/images/tools/" + (Users.canKick ? 'kick.svg' : 'ignore.svg'));
}
// setup the mod button and add it to the toolbar
var button = toolbar.addButton({ var button = toolbar.addButton({
objectName: 'ignore', objectName: 'mod',
imageURL: Script.resolvePath("assets/images/tools/ignore.svg"), imageURL: buttonImageURL(),
visible: true, visible: true,
buttonState: 1, buttonState: 1,
defaultState: 2, defaultState: 2,
@ -23,19 +27,24 @@ var button = toolbar.addButton({
alpha: 0.9 alpha: 0.9
}); });
// if this user's kick permissions change, change the state of the button in the HUD
Users.canKickChanged.connect(function(canKick){
button.writeProperty('imageURL', buttonImageURL());
});
var isShowingOverlays = false; var isShowingOverlays = false;
var ignoreOverlays = {}; var modOverlays = {};
function removeOverlays() { function removeOverlays() {
// enumerate the overlays and remove them // enumerate the overlays and remove them
var ignoreOverlayKeys = Object.keys(ignoreOverlays); var modOverlayKeys = Object.keys(modOverlays);
for (i = 0; i < ignoreOverlayKeys.length; ++i) { for (i = 0; i < modOverlayKeys.length; ++i) {
var avatarID = ignoreOverlayKeys[i]; var avatarID = modOverlayKeys[i];
Overlays.deleteOverlay(ignoreOverlays[avatarID]); Overlays.deleteOverlay(modOverlays[avatarID]);
} }
ignoreOverlays = {}; modOverlays = {};
} }
// handle clicks on the toolbar button // handle clicks on the toolbar button
@ -54,6 +63,10 @@ function buttonClicked(){
button.clicked.connect(buttonClicked); button.clicked.connect(buttonClicked);
function overlayURL() {
return Script.resolvePath("assets") + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg");
}
function updateOverlays() { function updateOverlays() {
if (isShowingOverlays) { if (isShowingOverlays) {
@ -80,17 +93,18 @@ function updateOverlays() {
var overlayPosition = avatar.getJointPosition("Head"); var overlayPosition = avatar.getJointPosition("Head");
overlayPosition.y += 0.45; overlayPosition.y += 0.45;
if (avatarID in ignoreOverlays) { if (avatarID in modOverlays) {
// keep the overlay above the current position of this avatar // keep the overlay above the current position of this avatar
Overlays.editOverlay(ignoreOverlays[avatarID], { Overlays.editOverlay(modOverlays[avatarID], {
position: overlayPosition position: overlayPosition,
url: overlayURL()
}); });
} else { } else {
// add the overlay above this avatar // add the overlay above this avatar
var newOverlay = Overlays.addOverlay("image3d", { var newOverlay = Overlays.addOverlay("image3d", {
url: Script.resolvePath("assets/images/ignore-target-01.svg"), url: overlayURL(),
position: overlayPosition, position: overlayPosition,
size: 0.4, size: 1,
scale: 0.4, scale: 0.4,
color: { red: 255, green: 255, blue: 255}, color: { red: 255, green: 255, blue: 255},
alpha: 1, alpha: 1,
@ -100,7 +114,7 @@ function updateOverlays() {
}); });
// push this overlay to our array of overlays // push this overlay to our array of overlays
ignoreOverlays[avatarID] = newOverlay; modOverlays[avatarID] = newOverlay;
} }
} }
} }
@ -113,24 +127,28 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){
// we are currently showing overlays and an avatar just went away // we are currently showing overlays and an avatar just went away
// first remove the rendered overlay // first remove the rendered overlay
Overlays.deleteOverlay(ignoreOverlays[avatarID]); Overlays.deleteOverlay(modOverlays[avatarID]);
// delete the saved ID of the overlay from our ignored overlays object // delete the saved ID of the overlay from our mod overlays object
delete ignoreOverlays[avatarID]; delete modOverlays[avatarID];
} }
}); });
function handleSelectedOverlay(clickedOverlay) { function handleSelectedOverlay(clickedOverlay) {
// see this is one of our ignore overlays // see this is one of our mod overlays
var ignoreOverlayKeys = Object.keys(ignoreOverlays) var modOverlayKeys = Object.keys(modOverlays)
for (i = 0; i < ignoreOverlayKeys.length; ++i) { for (i = 0; i < modOverlayKeys.length; ++i) {
var avatarID = ignoreOverlayKeys[i]; var avatarID = modOverlayKeys[i];
var ignoreOverlay = ignoreOverlays[avatarID]; var modOverlay = modOverlays[avatarID];
if (clickedOverlay.overlayID == ignoreOverlay) { if (clickedOverlay.overlayID == modOverlay) {
// matched to an overlay, ask for the matching avatar to be ignored // matched to an overlay, ask for the matching avatar to be kicked or ignored
Users.ignore(avatarID); if (Users.canKick) {
Users.kick(avatarID);
} else {
Users.ignore(avatarID);
}
// cleanup of the overlay is handled by the connection to avatarRemovedEvent // cleanup of the overlay is handled by the connection to avatarRemovedEvent
} }
@ -161,15 +179,9 @@ Controller.mousePressEvent.connect(function(event){
// But we dont' get mousePressEvents. // But we dont' get mousePressEvents.
var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
var TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. function controllerComputePickRay(hand) {
var TRIGGER_ON_VALUE = 0.4; var controllerPose = Controller.getPoseValue(hand);
var TRIGGER_OFF_VALUE = 0.15; if (controllerPose.valid) {
var triggered = false;
var activeHand = Controller.Standard.RightHand;
function controllerComputePickRay() {
var controllerPose = Controller.getPoseValue(activeHand);
if (controllerPose.valid && triggered) {
var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation),
MyAvatar.position); MyAvatar.position);
// This gets point direction right, but if you want general quaternion it would be more complicated: // This gets point direction right, but if you want general quaternion it would be more complicated:
@ -178,37 +190,27 @@ function controllerComputePickRay() {
} }
} }
function makeTriggerHandler(hand) { function makeClickHandler(hand) {
return function (value) { return function(clicked) {
if (isShowingOverlays) { if (clicked == 1.0 && isShowingOverlays) {
if (!triggered && (value > TRIGGER_GRAB_VALUE)) { // should we smooth? var pickRay = controllerComputePickRay(hand);
triggered = true; if (pickRay) {
if (activeHand !== hand) { var overlayIntersection = Overlays.findRayIntersection(pickRay);
// No switching while the other is already triggered, so no need to release. if (overlayIntersection.intersects) {
activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; handleSelectedOverlay(overlayIntersection);
} }
var pickRay = controllerComputePickRay();
if (pickRay) {
var overlayIntersection = Overlays.findRayIntersection(pickRay);
if (overlayIntersection.intersects) {
handleSelectedOverlay(overlayIntersection);
}
}
} else if (triggered && (value < TRIGGER_OFF_VALUE)) {
triggered = false;
} }
} }
}; };
} }
triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
triggerMapping.enable(); triggerMapping.enable();
// cleanup the toolbar button and overlays when script is stopped // cleanup the toolbar button and overlays when script is stopped
Script.scriptEnding.connect(function() { Script.scriptEnding.connect(function() {
toolbar.removeButton('ignore'); toolbar.removeButton('mod');
removeOverlays(); removeOverlays();
triggerMapping.disable(); triggerMapping.disable();
}); });