";
@@ -976,7 +960,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
_.each(setting.groups, function (group) {
html += "" + group.label + " | "
})
- if (!isLocked && !setting.read_only) {
+ if (!setting.read_only) {
if (setting.can_order) {
html += " | ";
@@ -1004,7 +988,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
(col.class ? col.class : '') + "'>" + col.label + "" // Data
})
- if (!isLocked && !setting.read_only) {
+ if (!setting.read_only) {
if (setting.can_order) {
numVisibleColumns++;
html += ""
@@ -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);
}
diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp
index 5b2e5a2bb0..8f8c8e001c 100644
--- a/domain-server/src/DomainGatekeeper.cpp
+++ b/domain-server/src/DomainGatekeeper.cpp
@@ -120,8 +120,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer_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";
diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h
index 12697b8f3b..06ecfcf285 100644
--- a/domain-server/src/DomainGatekeeper.h
+++ b/domain-server/src/DomainGatekeeper.h
@@ -106,7 +106,8 @@ private:
QSet _domainOwnerFriends; // keep track of friends of the domain owner
QSet _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();
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 8af364992c..23e37efaf1 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -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");
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index a02b19f9fd..dc49bc6126 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -95,6 +95,8 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointercanConvert(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 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 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 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 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 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> permissionsSets;
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
- << _groupPermissions.get() << _groupForbiddens.get();
+ << _groupPermissions.get() << _groupForbiddens.get() << _ipPermissions.get();
foreach (auto permissionSet, permissionsSets) {
QHashIterator i(permissionSet);
while (i.hasNext()) {
@@ -648,6 +631,89 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() {
return changed;
}
+
+void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer 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();
+
+ 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* standardPermissionsList = reinterpret_cast(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* permissionsList = reinterpret_cast(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* permissionsList = reinterpret_cast(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* permissionsList = reinterpret_cast(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);
}
}
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index f56b1ecd21..144589326c 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -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 message);
+ void processNodeKickRequestPacket(QSharedPointer 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 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
diff --git a/interface/resources/qml/controls-uit/WebView.qml b/interface/resources/qml/controls-uit/WebView.qml
index 4c165fc587..b599e29fe0 100644
--- a/interface/resources/qml/controls-uit/WebView.qml
+++ b/interface/resources/qml/controls-uit/WebView.qml
@@ -35,7 +35,6 @@ WebEngineView {
}
onUrlChanged: {
- console.log("Url changed to " + url);
var originalUrl = url.toString();
newUrl = urlHandler.fixupUrl(originalUrl).toString();
if (newUrl !== originalUrl) {
diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml
index 2f94740fe6..bba91c64f6 100644
--- a/interface/resources/qml/controls/WebView.qml
+++ b/interface/resources/qml/controls/WebView.qml
@@ -26,7 +26,6 @@ WebEngineView {
}
onUrlChanged: {
- console.log("Url changed to " + url);
var originalUrl = url.toString();
newUrl = urlHandler.fixupUrl(originalUrl).toString();
if (newUrl !== originalUrl) {
diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml
index fa5be18cd3..6a37886cb3 100644
--- a/interface/resources/qml/dialogs/FileDialog.qml
+++ b/interface/resources/qml/dialogs/FileDialog.qml
@@ -87,6 +87,15 @@ ModalWindow {
currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder));
}
+ helper.contentsChanged.connect(function() {
+ if (folderListModel) {
+ // Make folderListModel refresh.
+ var save = folderListModel.folder;
+ folderListModel.folder = "";
+ folderListModel.folder = save;
+ }
+ });
+
fileTableView.forceActiveFocus();
}
@@ -343,12 +352,14 @@ ModalWindow {
onFolderChanged: {
if (folder === rootFolder) {
model = driveListModel;
+ helper.monitorDirectory("");
update();
} else {
var needsUpdate = model === driveListModel && folder === folderListModel.folder;
model = folderListModel;
folderListModel.folder = folder;
+ helper.monitorDirectory(helper.urlToPath(folder));
if (needsUpdate) {
update();
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 4e8ecf3054..564a58708f 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -916,6 +916,16 @@ bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
return false;
}
+bool RenderableModelEntityItem::shouldBePhysical() const {
+ // If we have a model, make sure it hasn't failed to download.
+ // If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready.
+ if (_model && _model->didGeometryRequestFail()) {
+ return false;
+ } else {
+ return ModelEntityItem::shouldBePhysical();
+ }
+}
+
glm::quat RenderableModelEntityItem::getAbsoluteJointRotationInObjectFrame(int index) const {
if (_model) {
glm::quat result;
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h
index 339c907532..f487e79880 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h
@@ -65,6 +65,8 @@ public:
virtual bool contains(const glm::vec3& point) const override;
+ virtual bool shouldBePhysical() const override;
+
// these are in the frame of this object (model space)
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp
index 26798070a6..c4b2a6dd22 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.cpp
+++ b/libraries/model-networking/src/model-networking/ModelCache.cpp
@@ -403,6 +403,8 @@ void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) {
void GeometryResourceWatcher::resourceFinished(bool success) {
if (success) {
_geometryRef = std::make_shared(*_resource);
+ } else {
+ emit resourceFailed();
}
}
diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h
index 4a0a921a04..62037d67bc 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.h
+++ b/libraries/model-networking/src/model-networking/ModelCache.h
@@ -111,6 +111,9 @@ public:
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
+signals:
+ void resourceFailed();
+
private:
void startWatching();
void stopWatching();
diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp
index b470c2fe25..431d372089 100644
--- a/libraries/networking/src/LimitedNodeList.cpp
+++ b/libraries/networking/src/LimitedNodeList.cpp
@@ -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() {
diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h
index 7cbdcd5361..48379b5e39 100644
--- a/libraries/networking/src/LimitedNodeList.h
+++ b/libraries/networking/src/LimitedNodeList.h
@@ -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();
diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h
index 270814c35c..18088c6cea 100644
--- a/libraries/networking/src/Node.h
+++ b/libraries/networking/src/Node.h
@@ -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 message);
void addIgnoredNode(const QUuid& otherNodeID);
diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index 05b5532560..781cc00c1c 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -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.";
+
+ }
+}
diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h
index ff994ce612..f3cd5bed0d 100644
--- a/libraries/networking/src/NodeList.h
+++ b/libraries/networking/src/NodeList.h
@@ -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();
diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp
index a815884dc5..a1d4fc182e 100644
--- a/libraries/networking/src/NodePermissions.cpp
+++ b/libraries/networking/src/NodePermissions.cpp
@@ -44,6 +44,7 @@ NodePermissions::NodePermissions(QMap 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 groupRanks) {
@@ -63,6 +64,7 @@ QVariant NodePermissions::toVariant(QHash 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();
}
diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h
index 6f4309a447..5d2755f9b5 100644
--- a/libraries/networking/src/NodePermissions.h
+++ b/libraries/networking/src/NodePermissions.h
@@ -63,7 +63,8 @@ public:
canRezPermanentEntities = 4,
canRezTemporaryEntities = 8,
canWriteToAssetServer = 16,
- canConnectPastMaxCapacity = 32
+ canConnectPastMaxCapacity = 32,
+ canKick = 64
};
Q_DECLARE_FLAGS(Permissions, Permission)
Permissions permissions;
diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp
index ce1f25d45d..e9d61a827a 100644
--- a/libraries/networking/src/udt/PacketHeaders.cpp
+++ b/libraries/networking/src/udt/PacketHeaders.cpp
@@ -26,7 +26,7 @@ const QSet NON_VERIFIED_PACKETS = QSet()
<< PacketType::NodeJsonStats << PacketType::EntityQuery
<< PacketType::OctreeDataNack << PacketType::EntityEditNack
<< PacketType::DomainListRequest << PacketType::StopNode
- << PacketType::DomainDisconnectRequest;
+ << PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest;
const QSet NON_SOURCED_PACKETS = QSet()
<< PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 7281e24fa9..40524e2288 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -98,7 +98,8 @@ public:
NegotiateAudioFormat,
SelectedAudioFormat,
MoreEntityShapes,
- LAST_PACKET_TYPE = MoreEntityShapes
+ NodeKickRequest,
+ LAST_PACKET_TYPE = NodeKickRequest
};
};
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index b04a1d8023..581bd285e2 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -102,13 +102,17 @@ Model::Model(RigPointer rig, QObject* parent) :
_calculatedMeshTrianglesValid(false),
_meshGroupsKnown(false),
_isWireframe(false),
- _rig(rig) {
+ _rig(rig)
+{
// we may have been created in the network thread, but we live in the main thread
if (_viewState) {
moveToThread(_viewState->getMainThread());
}
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
+
+ // handle download failure reported by the GeometryResourceWatcher
+ connect(&_renderWatcher, &GeometryResourceWatcher::resourceFailed, this, &Model::handleGeometryResourceFailure);
}
Model::~Model() {
@@ -818,6 +822,7 @@ void Model::setURL(const QUrl& url) {
_needsReload = true;
_needsUpdateTextures = true;
_meshGroupsKnown = false;
+ _geometryRequestFailed = false;
invalidCalculatedMeshBoxes();
deleteGeometry();
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index 98e50c66f4..b95c0318b4 100644
--- a/libraries/render-utils/src/Model.h
+++ b/libraries/render-utils/src/Model.h
@@ -147,8 +147,9 @@ public:
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
const QUrl& getCollisionURL() const { return _collisionUrl; }
-
bool isActive() const { return isLoaded(); }
+
+ bool didGeometryRequestFail() const { return _geometryRequestFailed; }
bool convexHullContains(glm::vec3 point);
@@ -392,6 +393,11 @@ protected:
RigPointer _rig;
uint32_t _deleteGeometryCounter { 0 };
+
+ bool _geometryRequestFailed { false };
+
+private slots:
+ void handleGeometryResourceFailure() { _geometryRequestFailed = true; }
};
Q_DECLARE_METATYPE(ModelPointer)
diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp
index ff7ccb0164..69ad8e04ad 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.cpp
+++ b/libraries/script-engine/src/UsersScriptingInterface.cpp
@@ -13,7 +13,23 @@
#include
+UsersScriptingInterface::UsersScriptingInterface() {
+ // emit a signal when kick permissions have changed
+ auto nodeList = DependencyManager::get();
+ 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()->ignoreNodeBySessionID(nodeID);
}
+
+void UsersScriptingInterface::kick(const QUuid& nodeID) {
+ // ask the NodeList to kick the user with the given session ID
+ DependencyManager::get()->kickNodeBySessionID(nodeID);
+}
+
+bool UsersScriptingInterface::getCanKick() {
+ // ask the NodeList to return our ability to kick
+ return DependencyManager::get()->getThisNodeCanKick();
+}
diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h
index 0dc62c088c..712eeedeb6 100644
--- a/libraries/script-engine/src/UsersScriptingInterface.h
+++ b/libraries/script-engine/src/UsersScriptingInterface.h
@@ -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);
};
diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp
index 5ae5ff740d..252079f182 100644
--- a/libraries/shared/src/HifiConfigVariantMap.cpp
+++ b/libraries/shared/src/HifiConfigVariantMap.cpp
@@ -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() {
diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h
index e92561cff5..cb6e92df96 100644
--- a/libraries/shared/src/HifiConfigVariantMap.h
+++ b/libraries/shared/src/HifiConfigVariantMap.h
@@ -15,16 +15,22 @@
#include
#include
+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
diff --git a/libraries/ui/src/FileDialogHelper.cpp b/libraries/ui/src/FileDialogHelper.cpp
index 3a12e054df..9d791ec562 100644
--- a/libraries/ui/src/FileDialogHelper.cpp
+++ b/libraries/ui/src/FileDialogHelper.cpp
@@ -115,3 +115,15 @@ QList FileDialogHelper::urlToList(const QUrl& url) {
return results;
}
+void FileDialogHelper::monitorDirectory(const QString& path) {
+ if (!_fsWatcherPath.isEmpty()) {
+ _fsWatcher.removePath(_fsWatcherPath);
+ _fsWatcherPath = "";
+ }
+
+ if (!path.isEmpty()) {
+ _fsWatcher.addPath(path);
+ _fsWatcherPath = path;
+ connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &FileDialogHelper::contentsChanged);
+ }
+}
diff --git a/libraries/ui/src/FileDialogHelper.h b/libraries/ui/src/FileDialogHelper.h
index 6058f8f7bb..6c352ecdfc 100644
--- a/libraries/ui/src/FileDialogHelper.h
+++ b/libraries/ui/src/FileDialogHelper.h
@@ -9,11 +9,12 @@
#ifndef hifi_ui_FileDialogHelper_h
#define hifi_ui_FileDialogHelper_h
+#include
#include
#include
-#include
#include
#include
+#include
class FileDialogHelper : public QObject {
@@ -61,6 +62,15 @@ public:
Q_INVOKABLE QList urlToList(const QUrl& url);
Q_INVOKABLE void openDirectory(const QString& path);
+
+ Q_INVOKABLE void monitorDirectory(const QString& path);
+
+signals:
+ void contentsChanged();
+
+private:
+ QFileSystemWatcher _fsWatcher;
+ QString _fsWatcherPath;
};
diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js
index 817d63582d..0efcd0c140 100644
--- a/scripts/defaultScripts.js
+++ b/scripts/defaultScripts.js
@@ -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");
diff --git a/scripts/system/assets/images/ignore-target-01.svg b/scripts/system/assets/images/ignore-target-01.svg
deleted file mode 100644
index 98cee89ca1..0000000000
--- a/scripts/system/assets/images/ignore-target-01.svg
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
diff --git a/scripts/system/assets/images/ignore-target.svg b/scripts/system/assets/images/ignore-target.svg
new file mode 100644
index 0000000000..3d685139ec
--- /dev/null
+++ b/scripts/system/assets/images/ignore-target.svg
@@ -0,0 +1,53 @@
+
+
+
diff --git a/scripts/system/assets/images/kick-target.svg b/scripts/system/assets/images/kick-target.svg
new file mode 100644
index 0000000000..21cb3a5462
--- /dev/null
+++ b/scripts/system/assets/images/kick-target.svg
@@ -0,0 +1,33 @@
+
+
+
diff --git a/scripts/system/assets/images/progress-bar-background.svg b/scripts/system/assets/images/progress-bar-background.svg
index 732acd05ad..a8b4e1aab5 100644
--- a/scripts/system/assets/images/progress-bar-background.svg
+++ b/scripts/system/assets/images/progress-bar-background.svg
@@ -1,5 +1,5 @@
- |