diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json
index 911732fcef..8cd9136895 100644
--- a/domain-server/resources/describe-settings.json
+++ b/domain-server/resources/describe-settings.json
@@ -684,6 +684,79 @@
}
]
},
+ {
+ "name": "permissions",
+ "type": "table",
+ "caption": "Permissions for Specific Users",
+ "can_add_new_rows": true,
+
+ "groups": [
+ {
+ "label": "User",
+ "span": 1
+ },
+ {
+ "label": "Permissions ?",
+ "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
+ }
+ ]
+ },
{
"name": "ip_permissions",
"type": "table",
@@ -757,18 +830,17 @@
]
},
{
- "name": "permissions",
+ "name": "mac_permissions",
"type": "table",
- "caption": "Permissions for Specific Users",
+ "caption": "Permissions for Users with MAC Addresses",
"can_add_new_rows": true,
-
"groups": [
{
- "label": "User",
+ "label": "MAC Address",
"span": 1
},
{
- "label": "Permissions ?",
+ "label": "Permissions ?",
"span": 7
}
],
diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp
index 051465efd2..f55a2073d1 100644
--- a/domain-server/src/DomainGatekeeper.cpp
+++ b/domain-server/src/DomainGatekeeper.cpp
@@ -119,15 +119,20 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointersetNodeInterestSet(safeInterestSet);
nodeData->setPlaceName(nodeConnection.placeName);
+ qDebug() << "Allowed connection from node" << uuidStringWithoutCurlyBraces(node->getUUID())
+ << "on" << message->getSenderSockAddr() << "with MAC" << nodeConnection.hardwareAddress;
+
// signal that we just connected a node so the DomainServer can get it a list
// and broadcast its presence right away
emit connectedNode(node);
} else {
- qDebug() << "Refusing connection from node at" << message->getSenderSockAddr();
+ qDebug() << "Refusing connection from node at" << message->getSenderSockAddr()
+ << "with hardware address" << nodeConnection.hardwareAddress;
}
}
-NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress) {
+NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername,
+ const QHostAddress& senderAddress, const QString& hardwareAddress) {
NodePermissions userPerms;
userPerms.setAll(false);
@@ -144,8 +149,14 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms;
#endif
+ if (!hardwareAddress.isEmpty() && _server->_settingsManager.hasPermissionsForMAC(hardwareAddress)) {
+ // this user comes from a MAC we have in our permissions table, apply those permissions
+ userPerms = _server->_settingsManager.getPermissionsForMAC(hardwareAddress);
- if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
+#ifdef WANT_DEBUG
+ qDebug() << "| user-permissions: specific MAC 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);
@@ -158,6 +169,13 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername);
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific user matches, so:" << userPerms;
+#endif
+ } else if (!hardwareAddress.isEmpty() && _server->_settingsManager.hasPermissionsForMAC(hardwareAddress)) {
+ // this user comes from a MAC we have in our permissions table, apply those permissions
+ userPerms = _server->_settingsManager.getPermissionsForMAC(hardwareAddress);
+
+#ifdef WANT_DEBUG
+ qDebug() << "| user-permissions: specific MAC 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
@@ -255,7 +273,14 @@ void DomainGatekeeper::updateNodePermissions() {
// 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());
+ QString hardwareAddress;
+
+ DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData());
+ if (nodeData) {
+ hardwareAddress = nodeData->getHardwareAddress();
+ }
+
+ userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress);
}
node->setPermissions(userPerms);
@@ -308,6 +333,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
nodeData->setWalletUUID(it->second.getWalletUUID());
nodeData->setNodeVersion(it->second.getNodeVersion());
+ nodeData->setHardwareAddress(nodeConnection.hardwareAddress);
nodeData->setWasAssigned(true);
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
@@ -369,7 +395,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
}
}
- userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress());
+ userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress(),
+ nodeConnection.hardwareAddress);
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
@@ -425,6 +452,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
// if we have a username from the connect request, set it on the DomainServerNodeData
nodeData->setUsername(username);
+ // set the hardware address passed in the connect request
+ nodeData->setHardwareAddress(nodeConnection.hardwareAddress);
+
// also add an interpolation to DomainServerNodeData so that servers can get username in stats
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h
index b7d2a03af6..b17d0f61a4 100644
--- a/domain-server/src/DomainGatekeeper.h
+++ b/domain-server/src/DomainGatekeeper.h
@@ -107,7 +107,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, const QHostAddress& senderAddress);
+ NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername,
+ const QHostAddress& senderAddress, const QString& hardwareAddress);
void getGroupMemberships(const QString& username);
// void getIsGroupMember(const QString& username, const QUuid groupID);
diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h
index f95403c779..ff637844f1 100644
--- a/domain-server/src/DomainServerNodeData.h
+++ b/domain-server/src/DomainServerNodeData.h
@@ -53,6 +53,9 @@ public:
void setNodeVersion(const QString& nodeVersion) { _nodeVersion = nodeVersion; }
const QString& getNodeVersion() { return _nodeVersion; }
+
+ void setHardwareAddress(const QString& hardwareAddress) { _hardwareAddress = hardwareAddress; }
+ const QString& getHardwareAddress() { return _hardwareAddress; }
void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue);
void removeOverrideForKey(const QString& key, const QString& value);
@@ -81,6 +84,7 @@ private:
bool _isAuthenticated = true;
NodeSet _nodeInterestSet;
QString _nodeVersion;
+ QString _hardwareAddress;
QString _placeName;
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index fbc5fd4bd5..21214ed5f6 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -29,6 +29,8 @@
#include
#include
+#include "DomainServerNodeData.h"
+
#include "DomainServerSettingsManager.h"
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
@@ -439,6 +441,9 @@ void DomainServerSettingsManager::packPermissions() {
// save settings for IP addresses
packPermissionsForMap("permissions", _ipPermissions, IP_PERMISSIONS_KEYPATH);
+ // save settings for MAC addresses
+ packPermissionsForMap("permissions", _macPermissions, MAC_PERMISSIONS_KEYPATH);
+
// save settings for groups
packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH);
@@ -506,6 +511,17 @@ void DomainServerSettingsManager::unpackPermissions() {
}
});
+ needPack |= unpackPermissionsForKeypath(MAC_PERMISSIONS_KEYPATH, &_macPermissions,
+ [&](NodePermissionsPointer perms){
+ // make sure that this permission row is for a non-empty hardware
+ if (perms->getKey().first.isEmpty()) {
+ _macPermissions.remove(perms->getKey());
+
+ // we removed a row from the MAC permissions, we'll need a re-pack
+ needPack = true;
+ }
+ });
+
needPack |= unpackPermissionsForKeypath(GROUP_PERMISSIONS_KEYPATH, &_groupPermissions,
[&](NodePermissionsPointer perms){
@@ -558,7 +574,8 @@ void DomainServerSettingsManager::unpackPermissions() {
qDebug() << "--------------- permissions ---------------------";
QList> permissionsSets;
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
- << _groupPermissions.get() << _groupForbiddens.get() << _ipPermissions.get();
+ << _groupPermissions.get() << _groupForbiddens.get()
+ << _ipPermissions.get() << _macPermissions.get();
foreach (auto permissionSet, permissionsSets) {
QHashIterator i(permissionSet);
while (i.hasNext()) {
@@ -653,19 +670,25 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointergetPermissions().getVerifiedUserName();
- bool hadExistingPermissions = false;
+ bool newPermissions = 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);
+ bool hadPermissions = havePermissionsForName(verifiedUsername);
// grab or create permissions for the given username
- destinationPermissions = _agentPermissions[matchingNode->getPermissions().getKey()];
+ auto userPermissions = _agentPermissions[matchingNode->getPermissions().getKey()];
+
+ newPermissions = !hadPermissions || userPermissions->can(NodePermissions::Permission::canConnectToDomain);
+
+ // ensure that the connect permission is clear
+ userPermissions->clear(NodePermissions::Permission::canConnectToDomain);
} 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)
+ // otherwise we apply the kick to the IP from active socket for this node and the MAC address
+
+ // remove connect permissions for the IP (falling back to the public socket if not yet active)
auto& kickAddress = matchingNode->getActiveSocket()
? matchingNode->getActiveSocket()->getAddress()
: matchingNode->getPublicSocket().getAddress();
@@ -673,32 +696,41 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointercan(NodePermissions::Permission::canConnectToDomain)) {
+ newPermissions = true;
+
+ ipPermissions->clear(NodePermissions::Permission::canConnectToDomain);
+ }
+
+ // potentially remove connect permissions for the MAC address
+ DomainServerNodeData* nodeData = reinterpret_cast(matchingNode->getLinkedData());
+ if (nodeData) {
+ NodePermissionsKey macAddressKey(nodeData->getHardwareAddress(), 0);
+
+ bool hadMACPermissions = hasPermissionsForMAC(nodeData->getHardwareAddress());
+
+ auto macPermissions = _macPermissions[macAddressKey];
+
+ if (!hadMACPermissions || macPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
+ newPermissions = true;
+
+ macPermissions->clear(NodePermissions::Permission::canConnectToDomain);
+ }
+ }
}
- // make sure we didn't already have existing permissions that disallowed connect
- if (!hadExistingPermissions
- || destinationPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
-
+ if (newPermissions) {
qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID())
- << "after kick request";
-
- // ensure that the connect permission is clear
- destinationPermissions->clear(NodePermissions::Permission::canConnectToDomain);
+ << "after kick request from" << uuidStringWithoutCurlyBraces(sendingNode->getUUID());
// 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();
}
@@ -753,6 +785,16 @@ NodePermissions DomainServerSettingsManager::getPermissionsForIP(const QHostAddr
return nullPermissions;
}
+NodePermissions DomainServerSettingsManager::getPermissionsForMAC(const QString& macAddress) const {
+ NodePermissionsKey macKey = NodePermissionsKey(macAddress, 0);
+ if (_macPermissions.contains(macKey)) {
+ return *(_macPermissions[macKey].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)) {
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index c067377ffc..fcc3e9d91d 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -28,6 +28,7 @@ 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 MAC_PERMISSIONS_KEYPATH = "security.mac_permissions";
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
@@ -62,6 +63,10 @@ public:
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 MACs from the domain-server settings page
+ bool hasPermissionsForMAC(const QString& macAddress) const { return _macPermissions.contains(macAddress, 0); }
+ NodePermissions getPermissionsForMAC(const QString& macAddress) 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);
@@ -142,6 +147,7 @@ private:
NodePermissionsMap _agentPermissions; // specific account-names
NodePermissionsMap _ipPermissions; // permissions granted by node IP address
+ NodePermissionsMap _macPermissions; // permissions granted by node MAC address
NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups
NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group
diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp
index 13bb9123d8..93d6802d84 100644
--- a/domain-server/src/NodeConnectionData.cpp
+++ b/domain-server/src/NodeConnectionData.cpp
@@ -29,6 +29,9 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c
// NOTE: QDataStream::readBytes() - The buffer is allocated using new []. Destroy it with the delete [] operator.
delete[] rawBytes;
+
+ // read the hardware address sent by the client
+ dataStream >> newHeader.hardwareAddress;
}
dataStream >> newHeader.nodeType
diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h
index 9264db637e..bcbbdf0a40 100644
--- a/domain-server/src/NodeConnectionData.h
+++ b/domain-server/src/NodeConnectionData.h
@@ -28,6 +28,7 @@ public:
HifiSockAddr senderSockAddr;
QList interestList;
QString placeName;
+ QString hardwareAddress;
QByteArray protocolVersion;
};
diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index 7a778edaad..361070b306 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
@@ -346,6 +347,28 @@ void NodeList::sendDomainServerCheckIn() {
// include the protocol version signature in our connect request
QByteArray protocolVersionSig = protocolVersionsSignature();
packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size());
+
+ // if possible, include the MAC address for the current interface in our connect request
+ QString hardwareAddress;
+
+ for (auto networkInterface : QNetworkInterface::allInterfaces()) {
+ for (auto interfaceAddress : networkInterface.addressEntries()) {
+ if (interfaceAddress.ip() == _localSockAddr.getAddress()) {
+ // this is the interface whose local IP matches what we've detected the current IP to be
+ hardwareAddress = networkInterface.hardwareAddress();
+
+ // stop checking interfaces and addresses
+ break;
+ }
+ }
+
+ // stop looping if this was the current interface
+ if (!hardwareAddress.isEmpty()) {
+ break;
+ }
+ }
+
+ packetStream << hardwareAddress;
}
// pack our data to send to the domain-server including
diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp
index 88285602e1..b2fca69b03 100644
--- a/libraries/networking/src/udt/PacketHeaders.cpp
+++ b/libraries/networking/src/udt/PacketHeaders.cpp
@@ -67,7 +67,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
return static_cast(DomainConnectionDeniedVersion::IncludesExtraInfo);
case PacketType::DomainConnectRequest:
- return static_cast(DomainConnectRequestVersion::HasProtocolVersions);
+ return static_cast(DomainConnectRequestVersion::HasMACAddress);
case PacketType::DomainServerAddedNode:
return static_cast(DomainServerAddedNodeVersion::PermissionsGrid);
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 502ecc3951..8d63b972cc 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -207,7 +207,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
enum class DomainConnectRequestVersion : PacketVersion {
NoHostname = 17,
HasHostname,
- HasProtocolVersions
+ HasProtocolVersions,
+ HasMACAddress
};
enum class DomainConnectionDeniedVersion : PacketVersion {