mirror of
https://github.com/overte-org/overte.git
synced 2025-07-17 14:56:45 +02:00
Merge pull request #16375 from SimonWalton-HiFi/avatar-challenge-persist
DEV-2390: Persist in checking owner and sending challenges for failures
This commit is contained in:
commit
368b2bce90
2 changed files with 87 additions and 36 deletions
|
@ -32,24 +32,42 @@ MixerAvatar::MixerAvatar() {
|
||||||
|
|
||||||
_challengeTimer.setSingleShot(true);
|
_challengeTimer.setSingleShot(true);
|
||||||
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
|
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||||
|
_challengeTimer.callOnTimeout(this, &MixerAvatar::challengeTimeout);
|
||||||
_challengeTimer.callOnTimeout(this, [this]() {
|
// QTimer::start is a set of overloaded functions.
|
||||||
if (_verifyState == challengeClient) {
|
connect(this, &MixerAvatar::startChallengeTimer, &_challengeTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
|
||||||
_pendingEvent = false;
|
|
||||||
_verifyState = verificationFailed;
|
|
||||||
_needsIdentityUpdate = true;
|
|
||||||
qCDebug(avatars) << "Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID();
|
|
||||||
} else {
|
|
||||||
qCDebug(avatars) << "Ignoring timeout of avatar challenge";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* MixerAvatar::stateToName(VerifyState state) {
|
const char* MixerAvatar::stateToName(VerifyState state) {
|
||||||
return QMetaEnum::fromType<VerifyState>().valueToKey(state);
|
return QMetaEnum::fromType<VerifyState>().valueToKey(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MixerAvatar::challengeTimeout() {
|
||||||
|
switch (_verifyState) {
|
||||||
|
case challengeClient:
|
||||||
|
_verifyState = staticValidation;
|
||||||
|
_pendingEvent = true;
|
||||||
|
if (++_numberChallenges < NUM_CHALLENGES_BEFORE_FAIL) {
|
||||||
|
qCDebug(avatars) << "Retrying (" << _numberChallenges << ") timed-out challenge for" << getDisplayName()
|
||||||
|
<< getSessionUUID();
|
||||||
|
} else {
|
||||||
|
_certifyFailed = true;
|
||||||
|
_needsIdentityUpdate = true;
|
||||||
|
qCWarning(avatars) << "ALERT: Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case verificationFailed:
|
||||||
|
qCDebug(avatars) << "Retrying failed challenge for" << getDisplayName() << getSessionUUID();
|
||||||
|
_verifyState = staticValidation;
|
||||||
|
_pendingEvent = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
qCDebug(avatars) << "Ignoring timeout of avatar challenge";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MixerAvatar::fetchAvatarFST() {
|
void MixerAvatar::fetchAvatarFST() {
|
||||||
if (_verifyState >= requestingFST && _verifyState <= challengeClient) {
|
if (_verifyState >= requestingFST && _verifyState <= challengeClient) {
|
||||||
qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState);
|
qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState);
|
||||||
|
@ -58,7 +76,7 @@ void MixerAvatar::fetchAvatarFST() {
|
||||||
|
|
||||||
_pendingEvent = false;
|
_pendingEvent = false;
|
||||||
|
|
||||||
QUrl avatarURL = getSkeletonModelURL();
|
QUrl avatarURL = _skeletonModelURL;
|
||||||
if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") {
|
if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") {
|
||||||
// Not network FST.
|
// Not network FST.
|
||||||
return;
|
return;
|
||||||
|
@ -210,6 +228,23 @@ void MixerAvatar::ownerRequestComplete() {
|
||||||
networkReply->deleteLater();
|
networkReply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MixerAvatar::requestCurrentOwnership() {
|
||||||
|
// Get registered owner's public key from metaverse.
|
||||||
|
static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" };
|
||||||
|
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
|
QNetworkRequest networkRequest;
|
||||||
|
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||||
|
requestURL.setPath(POP_MARKETPLACE_API);
|
||||||
|
networkRequest.setUrl(requestURL);
|
||||||
|
|
||||||
|
QJsonObject request;
|
||||||
|
request["certificate_id"] = _certificateIdFromFST;
|
||||||
|
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||||
|
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
|
||||||
|
}
|
||||||
|
|
||||||
void MixerAvatar::processCertifyEvents() {
|
void MixerAvatar::processCertifyEvents() {
|
||||||
if (!_pendingEvent) {
|
if (!_pendingEvent) {
|
||||||
return;
|
return;
|
||||||
|
@ -221,26 +256,15 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
case receivedFST:
|
case receivedFST:
|
||||||
{
|
{
|
||||||
generateFSTHash();
|
generateFSTHash();
|
||||||
|
_numberChallenges = 0;
|
||||||
if (_certificateIdFromFST.length() != 0) {
|
if (_certificateIdFromFST.length() != 0) {
|
||||||
QString& marketplacePublicKey = EntityItem::_marketplacePublicKey;
|
QString& marketplacePublicKey = EntityItem::_marketplacePublicKey;
|
||||||
bool staticVerification = validateFSTHash(marketplacePublicKey);
|
bool staticVerification = validateFSTHash(marketplacePublicKey);
|
||||||
_verifyState = staticVerification ? staticValidation : verificationFailed;
|
_verifyState = staticVerification ? staticValidation : verificationFailed;
|
||||||
|
|
||||||
if (_verifyState == staticValidation) {
|
if (_verifyState == staticValidation) {
|
||||||
static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" };
|
requestCurrentOwnership();
|
||||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
|
||||||
QNetworkRequest networkRequest;
|
|
||||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
||||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
|
||||||
requestURL.setPath(POP_MARKETPLACE_API);
|
|
||||||
networkRequest.setUrl(requestURL);
|
|
||||||
|
|
||||||
QJsonObject request;
|
|
||||||
request["certificate_id"] = _certificateIdFromFST;
|
|
||||||
_verifyState = requestingOwner;
|
_verifyState = requestingOwner;
|
||||||
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
|
||||||
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
|
|
||||||
} else {
|
} else {
|
||||||
_needsIdentityUpdate = true;
|
_needsIdentityUpdate = true;
|
||||||
_pendingEvent = false;
|
_pendingEvent = false;
|
||||||
|
@ -248,11 +272,21 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
}
|
}
|
||||||
} else { // FST doesn't have a certificate, so noncertified rather than failed:
|
} else { // FST doesn't have a certificate, so noncertified rather than failed:
|
||||||
_pendingEvent = false;
|
_pendingEvent = false;
|
||||||
|
_certifyFailed = false;
|
||||||
|
_needsIdentityUpdate = true;
|
||||||
_verifyState = nonCertified;
|
_verifyState = nonCertified;
|
||||||
|
qCDebug(avatars) << "Avatar " << getDisplayName() << "(" << getSessionUUID() << ") isn't certified";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case staticValidation:
|
||||||
|
{
|
||||||
|
requestCurrentOwnership();
|
||||||
|
_verifyState = requestingOwner;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ownerResponse:
|
case ownerResponse:
|
||||||
{
|
{
|
||||||
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
|
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
|
||||||
|
@ -310,23 +344,28 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
void MixerAvatar::sendOwnerChallenge() {
|
void MixerAvatar::sendOwnerChallenge() {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
QByteArray avatarID = ("{" + _marketplaceIdFromFST + "}").toUtf8();
|
QByteArray avatarID = ("{" + _marketplaceIdFromFST + "}").toUtf8();
|
||||||
QByteArray nonce = QUuid::createUuid().toByteArray();
|
if (_challengeNonce.isEmpty()) {
|
||||||
|
_challengeNonce = QUuid::createUuid().toByteArray();
|
||||||
|
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||||
|
nonceHash.addData(_challengeNonce);
|
||||||
|
_challengeNonceHash = nonceHash.result();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
||||||
2 * sizeof(int) + nonce.length() + avatarID.length(), true);
|
2 * sizeof(int) + _challengeNonce.length() + avatarID.length(), true);
|
||||||
challengeOwnershipPacket->writePrimitive(avatarID.length());
|
challengeOwnershipPacket->writePrimitive(avatarID.length());
|
||||||
challengeOwnershipPacket->writePrimitive(nonce.length());
|
challengeOwnershipPacket->writePrimitive(_challengeNonce.length());
|
||||||
challengeOwnershipPacket->write(avatarID);
|
challengeOwnershipPacket->write(avatarID);
|
||||||
challengeOwnershipPacket->write(nonce);
|
challengeOwnershipPacket->write(_challengeNonce);
|
||||||
|
|
||||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) );
|
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) );
|
||||||
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||||
nonceHash.addData(nonce);
|
nonceHash.addData(_challengeNonce);
|
||||||
_challengeNonceHash = nonceHash.result();
|
_challengeNonceHash = nonceHash.result();
|
||||||
_pendingEvent = false;
|
_pendingEvent = false;
|
||||||
|
|
||||||
// QTimer::start is a set of overloaded functions.
|
emit startChallengeTimer();
|
||||||
QMetaObject::invokeMethod(&_challengeTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||||
|
@ -337,7 +376,7 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||||
QByteArray responseData = response.readAll();
|
QByteArray responseData = response.readAll();
|
||||||
if (responseData.length() < 8) {
|
if (responseData.length() < 8) {
|
||||||
_verifyState = error;
|
_verifyState = error;
|
||||||
qCDebug(avatars) << "Avatar challenge response packet too small, length:" << responseData.length();
|
qCWarning(avatars) << "ALERT: Avatar challenge response packet too small, length:" << responseData.length();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,11 +393,14 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||||
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
||||||
QByteArray::fromBase64(signedNonce));
|
QByteArray::fromBase64(signedNonce));
|
||||||
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
||||||
|
_certifyFailed = !challengeResult;
|
||||||
_needsIdentityUpdate = true;
|
_needsIdentityUpdate = true;
|
||||||
if (_verifyState == verificationFailed) {
|
if (_certifyFailed) {
|
||||||
qCDebug(avatars) << "Dynamic verification FAILED for" << getDisplayName() << getSessionUUID();
|
qCDebug(avatars) << "Dynamic verification FAILED for" << getDisplayName() << getSessionUUID();
|
||||||
|
emit startChallengeTimer();
|
||||||
} else {
|
} else {
|
||||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID();
|
qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID();
|
||||||
|
_challengeNonce.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
||||||
|
|
||||||
void fetchAvatarFST();
|
void fetchAvatarFST();
|
||||||
virtual bool isCertifyFailed() const override { return _verifyState == verificationFailed; }
|
virtual bool isCertifyFailed() const override { return _certifyFailed; }
|
||||||
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
|
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
|
||||||
void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; }
|
void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; }
|
||||||
|
|
||||||
|
@ -58,13 +58,18 @@ private:
|
||||||
QString _certificateIdFromFST;
|
QString _certificateIdFromFST;
|
||||||
QString _dynamicMarketResponse;
|
QString _dynamicMarketResponse;
|
||||||
QString _ownerPublicKey;
|
QString _ownerPublicKey;
|
||||||
|
QByteArray _challengeNonce;
|
||||||
QByteArray _challengeNonceHash;
|
QByteArray _challengeNonceHash;
|
||||||
QTimer _challengeTimer;
|
QTimer _challengeTimer;
|
||||||
|
static constexpr int NUM_CHALLENGES_BEFORE_FAIL = 1;
|
||||||
|
int _numberChallenges { 0 };
|
||||||
|
bool _certifyFailed { false };
|
||||||
bool _needsIdentityUpdate { false };
|
bool _needsIdentityUpdate { false };
|
||||||
|
|
||||||
bool generateFSTHash();
|
bool generateFSTHash();
|
||||||
bool validateFSTHash(const QString& publicKey) const;
|
bool validateFSTHash(const QString& publicKey) const;
|
||||||
QByteArray canonicalJson(const QString fstFile);
|
QByteArray canonicalJson(const QString fstFile);
|
||||||
|
void requestCurrentOwnership();
|
||||||
void sendOwnerChallenge();
|
void sendOwnerChallenge();
|
||||||
|
|
||||||
static const QString VERIFY_FAIL_MODEL;
|
static const QString VERIFY_FAIL_MODEL;
|
||||||
|
@ -72,6 +77,10 @@ private:
|
||||||
private slots:
|
private slots:
|
||||||
void fstRequestComplete();
|
void fstRequestComplete();
|
||||||
void ownerRequestComplete();
|
void ownerRequestComplete();
|
||||||
|
void challengeTimeout();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void startChallengeTimer();
|
||||||
};
|
};
|
||||||
|
|
||||||
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
||||||
|
|
Loading…
Reference in a new issue