mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-06-23 08:09:08 +02:00
avatar whitelist
This commit is contained in:
parent
cbff1910e2
commit
6271b8ee7c
7 changed files with 83 additions and 11 deletions
|
@ -132,7 +132,7 @@ void AvatarMixer::start() {
|
||||||
auto start = usecTimestampNow();
|
auto start = usecTimestampNow();
|
||||||
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
|
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
|
||||||
std::for_each(cbegin, cend, [&](const SharedNodePointer& node) {
|
std::for_each(cbegin, cend, [&](const SharedNodePointer& node) {
|
||||||
manageDisplayName(node);
|
manageIdentityData(node);
|
||||||
++_sumListeners;
|
++_sumListeners;
|
||||||
});
|
});
|
||||||
}, &lockWait, &nodeTransform, &functor);
|
}, &lockWait, &nodeTransform, &functor);
|
||||||
|
@ -183,8 +183,9 @@ void AvatarMixer::start() {
|
||||||
|
|
||||||
// NOTE: nodeData->getAvatar() might be side effected, must be called when access to node/nodeData
|
// NOTE: nodeData->getAvatar() might be side effected, must be called when access to node/nodeData
|
||||||
// is guaranteed to not be accessed by other thread
|
// is guaranteed to not be accessed by other thread
|
||||||
void AvatarMixer::manageDisplayName(const SharedNodePointer& node) {
|
void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
||||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||||
|
bool sendIdentity = false;
|
||||||
if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) {
|
if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) {
|
||||||
AvatarData& avatar = nodeData->getAvatar();
|
AvatarData& avatar = nodeData->getAvatar();
|
||||||
const QString& existingBaseDisplayName = nodeData->getBaseDisplayName();
|
const QString& existingBaseDisplayName = nodeData->getBaseDisplayName();
|
||||||
|
@ -210,9 +211,36 @@ void AvatarMixer::manageDisplayName(const SharedNodePointer& node) {
|
||||||
soFar.second++; // refcount
|
soFar.second++; // refcount
|
||||||
nodeData->flagIdentityChange();
|
nodeData->flagIdentityChange();
|
||||||
nodeData->setAvatarSessionDisplayNameMustChange(false);
|
nodeData->setAvatarSessionDisplayNameMustChange(false);
|
||||||
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name.
|
sendIdentity = true;
|
||||||
qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
|
qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
|
||||||
}
|
}
|
||||||
|
if (nodeData && nodeData->getAvatarSkeletonModelUrlMustChange()) { // never true for an empty _avatarWhitelist
|
||||||
|
nodeData->setAvatarSkeletonModelUrlMustChange(false);
|
||||||
|
AvatarData& avatar = nodeData->getAvatar();
|
||||||
|
static const QUrl emptyURL("");
|
||||||
|
QUrl url = avatar.cannonicalSkeletonModelURL(emptyURL);
|
||||||
|
if (!isAvatarInWhitelist(url)) {
|
||||||
|
qCDebug(avatars) << "Forbidden avatar" << nodeData->getNodeID() << avatar.getSkeletonModelURL() << "replaced with" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar);
|
||||||
|
avatar.setSkeletonModelURL(_replacementAvatar);
|
||||||
|
sendIdentity = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sendIdentity) {
|
||||||
|
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarMixer::isAvatarInWhitelist(const QUrl& url) {
|
||||||
|
for (const auto& whiteListedPrefix : _avatarWhitelist) {
|
||||||
|
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
|
||||||
|
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
|
||||||
|
if (url.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
|
||||||
|
url.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) {
|
void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) {
|
||||||
|
@ -402,13 +430,17 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
||||||
AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity);
|
AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity);
|
||||||
bool identityChanged = false;
|
bool identityChanged = false;
|
||||||
bool displayNameChanged = false;
|
bool displayNameChanged = false;
|
||||||
avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged);
|
bool skeletonModelUrlChanged = false;
|
||||||
|
avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged, skeletonModelUrlChanged);
|
||||||
if (identityChanged) {
|
if (identityChanged) {
|
||||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||||
nodeData->flagIdentityChange();
|
nodeData->flagIdentityChange();
|
||||||
if (displayNameChanged) {
|
if (displayNameChanged) {
|
||||||
nodeData->setAvatarSessionDisplayNameMustChange(true);
|
nodeData->setAvatarSessionDisplayNameMustChange(true);
|
||||||
}
|
}
|
||||||
|
if (skeletonModelUrlChanged && !_avatarWhitelist.isEmpty()) {
|
||||||
|
nodeData->setAvatarSkeletonModelUrlMustChange(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -764,4 +796,19 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
||||||
qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale
|
qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale
|
||||||
<< "and a maximum avatar scale of" << _domainMaximumScale;
|
<< "and a maximum avatar scale of" << _domainMaximumScale;
|
||||||
|
|
||||||
|
const QString AVATAR_WHITELIST_DEFAULT{ "" };
|
||||||
|
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
|
||||||
|
_avatarWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION].toString(AVATAR_WHITELIST_DEFAULT).split(',', QString::KeepEmptyParts);
|
||||||
|
|
||||||
|
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar";
|
||||||
|
_replacementAvatar = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION].toString(REPLACEMENT_AVATAR_DEFAULT);
|
||||||
|
|
||||||
|
if ((_avatarWhitelist.count() == 1) && _avatarWhitelist[0].isEmpty()) {
|
||||||
|
_avatarWhitelist.clear(); // KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok).
|
||||||
|
}
|
||||||
|
if (_avatarWhitelist.isEmpty()) {
|
||||||
|
qCDebug(avatars) << "All avatars are allowed.";
|
||||||
|
} else {
|
||||||
|
qCDebug(avatars) << "Avatars other than" << _avatarWhitelist << "will be replaced by" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,12 @@ private:
|
||||||
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
||||||
void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
||||||
|
|
||||||
void manageDisplayName(const SharedNodePointer& node);
|
void manageIdentityData(const SharedNodePointer& node);
|
||||||
|
bool isAvatarInWhitelist(const QUrl& url);
|
||||||
|
|
||||||
|
const QString REPLACEMENT_AVATAR_DEFAULT{ "" };
|
||||||
|
QStringList _avatarWhitelist { };
|
||||||
|
QString _replacementAvatar { REPLACEMENT_AVATAR_DEFAULT };
|
||||||
|
|
||||||
p_high_resolution_clock::time_point _lastFrameTimestamp;
|
p_high_resolution_clock::time_point _lastFrameTimestamp;
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,8 @@ public:
|
||||||
void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); }
|
void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); }
|
||||||
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
|
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
|
||||||
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
|
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
|
||||||
|
bool getAvatarSkeletonModelUrlMustChange() const { return _avatarSkeletonModelUrlMustChange; }
|
||||||
|
void setAvatarSkeletonModelUrlMustChange(bool set = true) { _avatarSkeletonModelUrlMustChange = set; }
|
||||||
|
|
||||||
void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; }
|
void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; }
|
||||||
void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; }
|
void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; }
|
||||||
|
@ -146,6 +148,7 @@ private:
|
||||||
|
|
||||||
uint64_t _identityChangeTimestamp;
|
uint64_t _identityChangeTimestamp;
|
||||||
bool _avatarSessionDisplayNameMustChange{ true };
|
bool _avatarSessionDisplayNameMustChange{ true };
|
||||||
|
bool _avatarSkeletonModelUrlMustChange{ true };
|
||||||
|
|
||||||
int _numAvatarsSentLastFrame = 0;
|
int _numAvatarsSentLastFrame = 0;
|
||||||
int _numFramesSinceAdjustment = 0;
|
int _numFramesSinceAdjustment = 0;
|
||||||
|
|
|
@ -866,6 +866,22 @@
|
||||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.",
|
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.",
|
||||||
"placeholder": 3.0,
|
"placeholder": 3.0,
|
||||||
"default": 3.0
|
"default": 3.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "avatar_whitelist",
|
||||||
|
"label": "Avatars Allowed from:",
|
||||||
|
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
|
||||||
|
"placeholder": "",
|
||||||
|
"default": "",
|
||||||
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "replacement_avatar",
|
||||||
|
"label": "Replacement Avatar for disallowed avatars",
|
||||||
|
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic defalt avatar is used.",
|
||||||
|
"placeholder": "",
|
||||||
|
"default": "",
|
||||||
|
"advanced": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1504,7 +1504,7 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
|
||||||
return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL;
|
return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) {
|
void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, bool& skeletonModelUrlChanged) {
|
||||||
|
|
||||||
if (identity.sequenceId < _identitySequenceId) {
|
if (identity.sequenceId < _identitySequenceId) {
|
||||||
qCDebug(avatars) << "Ignoring older identity packet for avatar" << getSessionUUID()
|
qCDebug(avatars) << "Ignoring older identity packet for avatar" << getSessionUUID()
|
||||||
|
@ -1517,6 +1517,7 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC
|
||||||
if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) {
|
if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) {
|
||||||
setSkeletonModelURL(identity.skeletonModelURL);
|
setSkeletonModelURL(identity.skeletonModelURL);
|
||||||
identityChanged = true;
|
identityChanged = true;
|
||||||
|
skeletonModelUrlChanged = true;
|
||||||
if (_firstSkeletonCheck) {
|
if (_firstSkeletonCheck) {
|
||||||
displayNameChanged = true;
|
displayNameChanged = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,6 +368,7 @@ public:
|
||||||
virtual ~AvatarData();
|
virtual ~AvatarData();
|
||||||
|
|
||||||
static const QUrl& defaultFullAvatarModelUrl();
|
static const QUrl& defaultFullAvatarModelUrl();
|
||||||
|
QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
|
||||||
|
|
||||||
virtual bool isMyAvatar() const { return false; }
|
virtual bool isMyAvatar() const { return false; }
|
||||||
|
|
||||||
|
@ -536,9 +537,8 @@ public:
|
||||||
|
|
||||||
static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut);
|
static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut);
|
||||||
|
|
||||||
// identityChanged returns true if identity has changed, false otherwise.
|
// identityChanged returns true if identity has changed, false otherwise. Similarly for displaNameChanged and skeletonModelUrlChange.
|
||||||
// displayNameChanged returns true if displayName has changed, false otherwise.
|
void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, bool& skeletonModelUrlChanged);
|
||||||
void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged);
|
|
||||||
|
|
||||||
QByteArray identityByteArray() const;
|
QByteArray identityByteArray() const;
|
||||||
|
|
||||||
|
@ -697,7 +697,6 @@ protected:
|
||||||
QVector<AttachmentData> _attachmentData;
|
QVector<AttachmentData> _attachmentData;
|
||||||
QString _displayName;
|
QString _displayName;
|
||||||
QString _sessionDisplayName { };
|
QString _sessionDisplayName { };
|
||||||
QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
|
|
||||||
|
|
||||||
QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
|
QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
|
||||||
QStringList _jointNames; ///< in order of depth-first traversal
|
QStringList _jointNames; ///< in order of depth-first traversal
|
||||||
|
|
|
@ -148,8 +148,9 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
||||||
auto avatar = newOrExistingAvatar(identity.uuid, sendingNode);
|
auto avatar = newOrExistingAvatar(identity.uuid, sendingNode);
|
||||||
bool identityChanged = false;
|
bool identityChanged = false;
|
||||||
bool displayNameChanged = false;
|
bool displayNameChanged = false;
|
||||||
|
bool skeletonModelUrlChanged = false;
|
||||||
// In this case, the "sendingNode" is the Avatar Mixer.
|
// In this case, the "sendingNode" is the Avatar Mixer.
|
||||||
avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged);
|
avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged, skeletonModelUrlChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue