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": [
{
"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>",
"span": 6
"span": 7
}
],
@ -445,6 +445,13 @@
"type": "checkbox",
"editable": true,
"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>",
"span": 6
"span": 7
}
],
@ -543,6 +550,13 @@
"type": "checkbox",
"editable": true,
"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>",
"span": 6
"span": 7
}
],
@ -635,23 +649,29 @@
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_kick",
"label": "Kick Users",
"type": "checkbox",
"editable": true,
"default": false
}
]
},
{
"name": "permissions",
"name": "ip_permissions",
"type": "table",
"caption": "Permissions for Specific Users",
"caption": "Permissions for Users from IP Addresses",
"can_add_new_rows": true,
"groups": [
{
"label": "User",
"label": "IP Address",
"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": 6
"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": 7
}
],
@ -701,6 +721,86 @@
"type": "checkbox",
"editable": true,
"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;
}
.locked {
color: #428bca;
}
.locked-table {
cursor: not-allowed;
background-color: #eee;
}
.advanced-setting {
display: none;
}

View file

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

View file

@ -41,7 +41,7 @@ var Settings = {
};
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 + "'>";
setting_value = _(values).valueForKeyPath(keypath);
@ -54,9 +54,6 @@ var viewHelpers = {
}
label_class = 'control-label';
if (isLocked) {
label_class += ' locked';
}
function common_attrs(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 += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>"
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "")
form_group += (isLocked ? " disabled" : "") + "/>"
form_group += "<div class='toggle-checkbox-container'>"
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "") + "/>"
if (setting.help) {
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
@ -88,7 +84,7 @@ var viewHelpers = {
}
if (input_type === 'table') {
form_group += makeTable(setting, keypath, setting_value, isLocked)
form_group += makeTable(setting, keypath, setting_value)
} else {
if (input_type === 'select') {
form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'"
@ -107,12 +103,10 @@ var viewHelpers = {
if (setting.href) {
form_group += "<a href='" + setting.href + "'style='display: block;' role='button'"
+ (isLocked ? " disabled" : "")
+ common_attrs("btn " + setting.classes) + " target='_blank'>"
+ setting.button_label + "</a>";
} else {
form_group += "<button " + common_attrs("btn " + setting.classes)
+ (isLocked ? " disabled" : "") + ">"
form_group += "<button " + common_attrs("btn " + setting.classes) + ">"
+ setting.button_label + "</button>";
}
@ -124,7 +118,7 @@ var viewHelpers = {
form_group += "<input type='" + input_type + "'" + common_attrs() +
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>"
"' value='" + setting_value + "'/>"
}
form_group += "<span class='help-block'>" + setting.help + "</span>"
@ -459,10 +453,8 @@ function setupHFAccountButton() {
$("[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
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
$('#metaverse .panel-body').prepend(buttonGroup);
@ -673,7 +665,7 @@ function setupPlacesTable() {
}
// 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
$('#places_paths .panel-body').prepend(placesTableGroup);
@ -873,10 +865,8 @@ function reloadSettings(callback) {
Settings.data = data;
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) {
// append the domain selection modal, as long as it's not locked
appendDomainIDButtons();
}
// append the domain selection modal
appendDomainIDButtons();
// call our method to setup the HF account button
setupHFAccountButton();
@ -889,12 +879,6 @@ function reloadSettings(callback) {
$('[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
callback(true);
}).fail(function() {
@ -943,11 +927,11 @@ $('body').on('click', '.save-button', function(e){
return false;
});
function makeTable(setting, keypath, setting_value, isLocked) {
function makeTable(setting, keypath, setting_value) {
var isArray = !_.has(setting, 'key');
var categoryKey = setting.categorize_by_key;
var isCategorized = !!categoryKey && isArray;
if (!isArray && setting.can_order) {
setting.can_order = false;
}
@ -961,7 +945,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
var nonDeletableRowKey = setting["non-deletable-row-key"];
var nonDeletableRowValues = setting["non-deletable-row-values"];
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' " +
html += "<table class='table table-bordered' " +
"data-short-name='" + setting.name + "' name='" + keypath + "' " +
"id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " +
"data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
@ -976,7 +960,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
_.each(setting.groups, function (group) {
html += "<td colspan='" + group.span + "'><strong>" + group.label + "</strong></td>"
})
if (!isLocked && !setting.read_only) {
if (!setting.read_only) {
if (setting.can_order) {
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
@ -1004,7 +988,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
(col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
})
if (!isLocked && !setting.read_only) {
if (!setting.read_only) {
if (setting.can_order) {
numVisibleColumns++;
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) {
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+
"'><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
if (!isLocked && !setting.read_only) {
if (!setting.read_only) {
if (setting.can_add_new_categories) {
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;
userPerms.setAll(false);
if (isLocalUser) {
@ -136,16 +137,29 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms;
#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 {
userPerms.setID(verifiedUsername);
if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) {
userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername);
userPerms.setVerifiedUserName(verifiedUsername);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific user matches, so:" << userPerms;
#endif
} else 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 {
userPerms.setVerifiedUserName(verifiedUsername);
// they are logged into metaverse, but we don't have specific permissions for them.
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
#ifdef WANT_DEBUG
@ -191,6 +205,9 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
}
}
}
userPerms.setID(verifiedUsername);
userPerms.setVerifiedUserName(verifiedUsername);
}
#ifdef WANT_DEBUG
@ -225,7 +242,12 @@ void DomainGatekeeper::updateNodePermissions() {
const QHostAddress& addr = node->getLocalSocket().getAddress();
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
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);
@ -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)) {
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,
const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr) {
// it's possible this user can be allowed to connect, but we need to check their username signature
QByteArray publicKeyArray = _userPublicKeys.value(username.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 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
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
QByteArray lowercaseUsername = username.toLower().toUtf8();
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
QByteArray lowercaseUsernameUTF8 = lowerUsername.toUtf8();
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsernameUTF8.append(connectionToken.toRfc4122()),
QCryptographicHash::Sha256);
if (rsaPublicKey) {
@ -575,10 +597,7 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
QString username = extractUsernameFromPublicKeyRequest(requestReply);
if (jsonObject["status"].toString() == "success" && username != "") {
// figure out which user this is for
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
qDebug() << "Storing a public key for user" << username;
if (jsonObject["status"].toString() == "success" && !username.isEmpty()) {
// pull the public key as a QByteArray from this response
const QString JSON_DATA_KEY = "data";
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> _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 getIsGroupMember(const QString& username, const QUuid groupID);
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
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
// register the gatekeeper for the packets it needs to receive
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");

View file

@ -95,6 +95,8 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
void DomainServerSettingsManager::setupConfigMap(const QStringList& 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);
// 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
// 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
&& 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
// we need to set security.restricted_access to true
QVariant* restrictedAccess = valueForKeyPath(_configMap.getUserConfig(),
RESTRICTED_ACCESS_SETTINGS_KEYPATH,
true);
QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, 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";
// this was prior to change of poorly named entitiesFileName to entitiesFilePath
QVariant* persistFileNameVariant = valueForKeyPath(_configMap.getMergedConfig(),
ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY);
QVariant* persistFileNameVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY);
if (persistFileNameVariant && persistFileNameVariant->canConvert(QMetaType::QString)) {
QString persistFileName = persistFileNameVariant->toString();
qDebug() << "Migrating persistFilename to persistFilePath for entity-server settings";
// 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
*persistFilePath = persistFileName;
// 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)) {
QVariantMap entityServerMap = entityServerVariant->toMap();
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
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)) {
QString plaintextPassword = passwordVariant->toString();
@ -273,6 +272,28 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
// This was prior to operating hours, so add default hours
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();
@ -293,9 +314,9 @@ void DomainServerSettingsManager::validateDescriptorsMap() {
static const QString WEEKEND_HOURS{ "descriptors.weekend_hours" };
static const QString UTC_OFFSET{ "descriptors.utc_offset" };
QVariant* weekdayHours = valueForKeyPath(_configMap.getUserConfig(), WEEKDAY_HOURS, true);
QVariant* weekendHours = valueForKeyPath(_configMap.getUserConfig(), WEEKEND_HOURS, true);
QVariant* utcOffset = valueForKeyPath(_configMap.getUserConfig(), UTC_OFFSET, true);
QVariant* weekdayHours = _configMap.valueForKeyPath(WEEKDAY_HOURS, true);
QVariant* weekendHours = _configMap.valueForKeyPath(WEEKEND_HOURS, true);
QVariant* utcOffset = _configMap.valueForKeyPath(UTC_OFFSET, true);
static const QString OPEN{ "open" };
static const QString CLOSE{ "close" };
@ -318,9 +339,6 @@ void DomainServerSettingsManager::validateDescriptorsMap() {
if (wasMalformed) {
// write the new settings to file
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,
QString keyPath) {
// find (or create) the "security" section of the settings map
QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security");
if (!security || !security->canConvert(QMetaType::QVariantMap)) {
security = valueForKeyPath(_configMap.getUserConfig(), "security", true);
QVariant* security = _configMap.valueForKeyPath("security", true);
if (!security->canConvert(QMetaType::QVariantMap)) {
(*security) = QVariantMap();
}
// find (or create) whichever subsection of "security" we are packing
QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath);
if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) {
permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true);
QVariant* permissions = _configMap.valueForKeyPath(keyPath, true);
if (!permissions->canConvert(QMetaType::QVariantList)) {
(*permissions) = QVariantList();
}
@ -420,6 +436,9 @@ void DomainServerSettingsManager::packPermissions() {
// save settings for specific users
packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH);
// save settings for IP addresses
packPermissionsForMap("permissions", _ipPermissions, IP_PERMISSIONS_KEYPATH);
// save settings for groups
packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH);
@ -427,139 +446,103 @@ void DomainServerSettingsManager::packPermissions() {
packPermissionsForMap("permissions", _groupForbiddens, GROUP_FORBIDDENS_KEYPATH);
persistToFile();
_configMap.loadMasterAndUserConfig(_argumentList);
}
void DomainServerSettingsManager::unpackPermissions() {
// transfer details from _configMap to _agentPermissions;
bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& keyPath,
NodePermissionsMap* mapPointer,
std::function<void(NodePermissionsPointer)> customUnpacker) {
_standardAgentPermissions.clear();
_agentPermissions.clear();
_groupPermissions.clear();
_groupForbiddens.clear();
mapPointer->clear();
bool foundLocalhost = false;
bool foundAnonymous = false;
bool foundLoggedIn = false;
bool foundFriends = false;
bool needPack = false;
QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH);
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);
QVariant* permissions = _configMap.valueForKeyPath(keyPath, true);
if (!permissions->canConvert(QMetaType::QVariantList)) {
qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings.";
(*permissions) = QVariantList();
}
QVariant* groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH);
if (!groupPermissions || !groupPermissions->canConvert(QMetaType::QVariantList)) {
qDebug() << "failed to extract group permissions from settings.";
groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH, true);
(*groupPermissions) = QVariantList();
}
QVariant* groupForbiddens = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH);
if (!groupForbiddens || !groupForbiddens->canConvert(QMetaType::QVariantList)) {
qDebug() << "failed to extract group forbiddens from settings.";
groupForbiddens = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH, true);
(*groupForbiddens) = QVariantList();
}
QList<QVariant> standardPermissionsList = standardPermissions->toList();
foreach (QVariant permsHash, standardPermissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
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;
}
}
bool needPack = false;
QList<QVariant> permissionsList = permissions->toList();
foreach (QVariant permsHash, permissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
NodePermissionsKey idKey = NodePermissionsKey(id, 0);
if (_agentPermissions.contains(idKey)) {
qDebug() << "duplicate name in permissions table: " << id;
*(_agentPermissions[idKey]) |= *perms;
NodePermissionsKey idKey = perms->getKey();
if (mapPointer->contains(idKey)) {
qDebug() << "Duplicate name in permissions table for" << keyPath << " - " << id;
*((*mapPointer)[idKey]) |= *perms;
needPack = true;
} else {
_agentPermissions[idKey] = perms;
(*mapPointer)[idKey] = perms;
}
if (customUnpacker) {
customUnpacker(perms);
}
}
QList<QVariant> groupPermissionsList = groupPermissions->toList();
foreach (QVariant permsHash, groupPermissionsList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
NodePermissionsKey idKey = perms->getKey();
if (_groupPermissions.contains(idKey)) {
qDebug() << "duplicate name in group permissions table: " << id;
*(_groupPermissions[idKey]) |= *perms;
needPack = true;
} else {
*(_groupPermissions[idKey]) = *perms;
}
if (perms->isGroup()) {
// the group-id was cached. hook-up the uuid in the uuid->group hash
_groupPermissionsByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupPermissions[idKey];
needPack |= setGroupID(perms->getID(), perms->getGroupID());
}
}
return needPack;
QList<QVariant> groupForbiddensList = groupForbiddens->toList();
foreach (QVariant permsHash, groupForbiddensList) {
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
QString id = perms->getID();
NodePermissionsKey idKey = perms->getKey();
if (_groupForbiddens.contains(idKey)) {
qDebug() << "duplicate name in group forbiddens table: " << id;
*(_groupForbiddens[idKey]) |= *perms;
needPack = true;
} else {
_groupForbiddens[idKey] = perms;
}
if (perms->isGroup()) {
// the group-id was cached. hook-up the uuid in the uuid->group hash
_groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupForbiddens[idKey];
needPack |= setGroupID(perms->getID(), perms->getGroupID());
}
}
}
void DomainServerSettingsManager::unpackPermissions() {
// transfer details from _configMap to _agentPermissions
bool needPack = false;
needPack |= unpackPermissionsForKeypath(AGENT_STANDARD_PERMISSIONS_KEYPATH, &_standardAgentPermissions);
needPack |= unpackPermissionsForKeypath(AGENT_PERMISSIONS_KEYPATH, &_agentPermissions);
needPack |= unpackPermissionsForKeypath(IP_PERMISSIONS_KEYPATH, &_ipPermissions,
[&](NodePermissionsPointer perms){
// make sure that this permission row is for a valid IP address
if (QHostAddress(perms->getKey().first).isNull()) {
_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 (!foundLocalhost) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) };
perms->setAll(true);
_standardAgentPermissions[perms->getKey()] = perms;
needPack = true;
}
if (!foundAnonymous) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) };
_standardAgentPermissions[perms->getKey()] = perms;
needPack = true;
}
if (!foundLoggedIn) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) };
_standardAgentPermissions[perms->getKey()] = perms;
needPack = true;
}
if (!foundFriends) {
NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameFriends) };
_standardAgentPermissions[perms->getKey()] = perms;
needPack = true;
foreach(const QString& standardName, NodePermissions::standardNames) {
NodePermissionsKey standardKey { standardName, 0 };
if (!_standardAgentPermissions.contains(standardKey)) {
// we don't have permissions for one of the standard groups, so we'll add them now
NodePermissionsPointer perms { new NodePermissions(standardKey) };
// the localhost user is granted all permissions by default
if (standardKey == NodePermissions::standardNameLocalhost) {
perms->setAll(true);
}
// add the permissions to the standard map
_standardAgentPermissions[standardKey] = perms;
// this will require a packing of permissions
needPack = true;
}
}
needPack |= ensurePermissionsForGroupRanks();
@ -572,7 +555,7 @@ void DomainServerSettingsManager::unpackPermissions() {
qDebug() << "--------------- permissions ---------------------";
QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets;
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
<< _groupPermissions.get() << _groupForbiddens.get();
<< _groupPermissions.get() << _groupForbiddens.get() << _ipPermissions.get();
foreach (auto permissionSet, permissionsSets) {
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(permissionSet);
while (i.hasNext()) {
@ -648,6 +631,89 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() {
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 result;
foreach (auto key, _agentPermissions.keys()) {
@ -675,6 +741,16 @@ NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString
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 {
NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID);
if (_groupPermissions.contains(groupRankKey)) {
@ -719,7 +795,7 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid&
}
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) {
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
const QVariant* foundValue = _configMap.valueForKeyPath(keyPath);
if (foundValue) {
return *foundValue;
@ -802,12 +878,10 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
// setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked";
QJsonObject rootObject;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
}
@ -852,13 +926,13 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
QVariant variantValue;
if (!groupKey.isEmpty()) {
QVariant settingsMapGroupValue = _configMap.getMergedConfig().value(groupKey);
QVariant settingsMapGroupValue = _configMap.value(groupKey);
if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingName);
}
} else {
variantValue = _configMap.getMergedConfig().value(settingName);
variantValue = _configMap.value(settingName);
}
QJsonValue result;
@ -1000,7 +1074,7 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
}
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
auto& settingsVariant = _configMap.getUserConfig();
auto& settingsVariant = _configMap.getConfig();
bool needRestart = false;
// Iterate on the setting groups
@ -1112,22 +1186,22 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
void DomainServerSettingsManager::sortPermissions() {
// 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)) {
QList<QVariant>* standardPermissionsList = reinterpret_cast<QVariantList*>(standardPermissions);
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)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(permissions);
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)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(groupPermissions);
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)) {
QList<QVariant>* permissionsList = reinterpret_cast<QVariantList*>(forbiddenPermissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
@ -1147,9 +1221,12 @@ void DomainServerSettingsManager::persistToFile() {
QFile settingsFile(_configMap.getUserConfigFilename());
if (settingsFile.open(QIODevice::WriteOnly)) {
settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson());
settingsFile.write(QJsonDocument::fromVariant(_configMap.getConfig()).toJson());
} else {
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 AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_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_FORBIDDENS_KEYPATH = "security.group_forbiddens";
@ -43,8 +44,7 @@ public:
void setupConfigMap(const QStringList& argumentList);
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
QVariantMap& getSettingsMap() { return _configMap.getConfig(); }
QVariantMap& getDescriptorsMap();
@ -58,6 +58,10 @@ public:
NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); }
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
bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const {
return _groupPermissions.contains(groupName, rankID);
@ -100,6 +104,7 @@ public slots:
private slots:
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
void processNodeKickRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
private:
QStringList _argumentList;
@ -129,11 +134,15 @@ private:
void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath);
void packPermissions();
void unpackPermissions();
bool unpackPermissionsForKeypath(const QString& keyPath, NodePermissionsMap* destinationMapPointer,
std::function<void(NodePermissionsPointer)> customUnpacker = {});
bool ensurePermissionsForGroupRanks();
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner
NodePermissionsMap _agentPermissions; // specific account-names
NodePermissionsMap _ipPermissions; // permissions granted by node IP address
NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups
NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group
// these are like _groupPermissions and _groupForbiddens but with uuids rather than group-names in the keys

View file

@ -151,6 +151,10 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
newPermissions.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() {

View file

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

View file

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

View file

@ -727,7 +727,7 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
emit ignoredNode(nodeID);
} 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);
bool isIgnoringNode(const QUuid& nodeID) const;
void kickNodeBySessionID(const QUuid& nodeID);
public slots:
void reset();
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_connect_past_max_capacity"].toBool() ?
Permission::canConnectPastMaxCapacity : Permission::none;
permissions |= perms["id_can_kick"].toBool() ? Permission::canKick : Permission::none;
}
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_write_to_asset_server"] = can(Permission::canWriteToAssetServer);
values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity);
values["id_can_kick"] = can(Permission::canKick);
return QVariant(values);
}
@ -123,6 +125,9 @@ QDebug operator<<(QDebug debug, const NodePermissions& perms) {
if (perms.can(NodePermissions::Permission::canConnectPastMaxCapacity)) {
debug << " ignore-max-cap";
}
if (perms.can(NodePermissions::Permission::canKick)) {
debug << " kick";
}
debug.nospace() << "]";
return debug.nospace();
}

View file

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

View file

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

View file

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

View file

@ -13,7 +13,23 @@
#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) {
// ask the NodeList to ignore this user (based on the session ID of their node)
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
SINGLETON_DEPENDENCY
Q_PROPERTY(bool canKick READ getCanKick)
public:
UsersScriptingInterface();
public slots:
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);
}
// 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
const QString USER_CONFIG_FILE_OPTION = "--user-config";
static const QString USER_CONFIG_FILE_NAME = "config.json";
@ -159,12 +166,10 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi
}
}
}
}
loadMapFromJSONFile(_userConfig, _userConfigFilename);
mergeMasterAndUserConfigs();
}
void HifiConfigVariantMap::mergeMasterAndUserConfigs() {

View file

@ -15,16 +15,22 @@
#include <QtCore/QStringList>
#include <QtCore/QVariantMap>
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
class HifiConfigVariantMap {
public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
HifiConfigVariantMap();
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& getConfig() { return _userConfig; }
void mergeMasterAndUserConfigs();
@ -40,6 +46,4 @@ private:
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
};
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
#endif // hifi_HifiConfigVariantMap_h

View file

@ -17,7 +17,7 @@ Script.load("system/goto.js");
Script.load("system/hmd.js");
Script.load("system/marketplace.js");
Script.load("system/edit.js");
Script.load("system/ignore.js");
Script.load("system/mod.js");
Script.load("system/selectAudioDevice.js");
Script.load("system/notifications.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/
//
// Created by Stephen Birarda on 07/11/2016
@ -12,10 +12,14 @@
// grab the toolbar
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({
objectName: 'ignore',
imageURL: Script.resolvePath("assets/images/tools/ignore.svg"),
objectName: 'mod',
imageURL: buttonImageURL(),
visible: true,
buttonState: 1,
defaultState: 2,
@ -23,19 +27,24 @@ var button = toolbar.addButton({
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 ignoreOverlays = {};
var modOverlays = {};
function removeOverlays() {
// enumerate the overlays and remove them
var ignoreOverlayKeys = Object.keys(ignoreOverlays);
var modOverlayKeys = Object.keys(modOverlays);
for (i = 0; i < ignoreOverlayKeys.length; ++i) {
var avatarID = ignoreOverlayKeys[i];
Overlays.deleteOverlay(ignoreOverlays[avatarID]);
for (i = 0; i < modOverlayKeys.length; ++i) {
var avatarID = modOverlayKeys[i];
Overlays.deleteOverlay(modOverlays[avatarID]);
}
ignoreOverlays = {};
modOverlays = {};
}
// handle clicks on the toolbar button
@ -54,6 +63,10 @@ function buttonClicked(){
button.clicked.connect(buttonClicked);
function overlayURL() {
return Script.resolvePath("assets") + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg");
}
function updateOverlays() {
if (isShowingOverlays) {
@ -80,17 +93,18 @@ function updateOverlays() {
var overlayPosition = avatar.getJointPosition("Head");
overlayPosition.y += 0.45;
if (avatarID in ignoreOverlays) {
if (avatarID in modOverlays) {
// keep the overlay above the current position of this avatar
Overlays.editOverlay(ignoreOverlays[avatarID], {
position: overlayPosition
Overlays.editOverlay(modOverlays[avatarID], {
position: overlayPosition,
url: overlayURL()
});
} else {
// add the overlay above this avatar
var newOverlay = Overlays.addOverlay("image3d", {
url: Script.resolvePath("assets/images/ignore-target-01.svg"),
url: overlayURL(),
position: overlayPosition,
size: 0.4,
size: 1,
scale: 0.4,
color: { red: 255, green: 255, blue: 255},
alpha: 1,
@ -100,7 +114,7 @@ function updateOverlays() {
});
// 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
// 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 ignoreOverlays[avatarID];
// delete the saved ID of the overlay from our mod overlays object
delete modOverlays[avatarID];
}
});
function handleSelectedOverlay(clickedOverlay) {
// see this is one of our ignore overlays
// see this is one of our mod overlays
var ignoreOverlayKeys = Object.keys(ignoreOverlays)
for (i = 0; i < ignoreOverlayKeys.length; ++i) {
var avatarID = ignoreOverlayKeys[i];
var ignoreOverlay = ignoreOverlays[avatarID];
var modOverlayKeys = Object.keys(modOverlays)
for (i = 0; i < modOverlayKeys.length; ++i) {
var avatarID = modOverlayKeys[i];
var modOverlay = modOverlays[avatarID];
if (clickedOverlay.overlayID == ignoreOverlay) {
// matched to an overlay, ask for the matching avatar to be ignored
Users.ignore(avatarID);
if (clickedOverlay.overlayID == modOverlay) {
// matched to an overlay, ask for the matching avatar to be kicked or ignored
if (Users.canKick) {
Users.kick(avatarID);
} else {
Users.ignore(avatarID);
}
// 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.
var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
var TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor.
var TRIGGER_ON_VALUE = 0.4;
var TRIGGER_OFF_VALUE = 0.15;
var triggered = false;
var activeHand = Controller.Standard.RightHand;
function controllerComputePickRay() {
var controllerPose = Controller.getPoseValue(activeHand);
if (controllerPose.valid && triggered) {
function controllerComputePickRay(hand) {
var controllerPose = Controller.getPoseValue(hand);
if (controllerPose.valid) {
var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation),
MyAvatar.position);
// 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) {
return function (value) {
if (isShowingOverlays) {
if (!triggered && (value > TRIGGER_GRAB_VALUE)) { // should we smooth?
triggered = true;
if (activeHand !== hand) {
// No switching while the other is already triggered, so no need to release.
activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
function makeClickHandler(hand) {
return function(clicked) {
if (clicked == 1.0 && isShowingOverlays) {
var pickRay = controllerComputePickRay(hand);
if (pickRay) {
var overlayIntersection = Overlays.findRayIntersection(pickRay);
if (overlayIntersection.intersects) {
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.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand));
triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
triggerMapping.enable();
// cleanup the toolbar button and overlays when script is stopped
Script.scriptEnding.connect(function() {
toolbar.removeButton('ignore');
toolbar.removeButton('mod');
removeOverlays();
triggerMapping.disable();
});