diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index af82fb3bc9..556ba5de3a 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -3,15 +3,23 @@ // assignment-client/src/avatars // // Created by Simon Walton April 2019 -// // Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "MixerAvatar.h" #include "AvatarLogging.h" @@ -22,9 +30,17 @@ void MixerAvatar::fetchAvatarFST() { if (avatarURL.isEmpty()) { return; } - _avatarURLString = avatarURL.toDisplayString(); + // Match UUID + version + static const QRegularExpression marketIdRegex{ "^https?://mpassets.highfidelity.com/([-0-9a-z]{36,})/" }; + auto marketIdMatch = marketIdRegex.match(avatarURL.toDisplayString()); + if (marketIdMatch.hasMatch()) { + QMutexLocker certifyLocker(&_avatarCertifyLock); + _marketplaceIdString = marketIdMatch.captured(1); + } else { + _marketplaceIdString = "2119142f-0cd6-4126-b18e-06b53afcc0a9"; // XXX: plants entity, for testing + } - ResourceRequest * fstRequest = resourceManager->createResourceRequest(this, avatarURL); + ResourceRequest* fstRequest = resourceManager->createResourceRequest(this, avatarURL); if (fstRequest) { QMutexLocker certifyLocker(&_avatarCertifyLock); @@ -33,27 +49,94 @@ void MixerAvatar::fetchAvatarFST() { } _avatarRequest = fstRequest; connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete); + _verifyState = kRequestingFST; fstRequest->send(); } else { qCDebug(avatars) << "Couldn't create FST request for" << avatarURL; } - } +// TESTING +static const QString PLANT_CERTID { + "MEYCIQDxKA62xq/G/x1aWpXyJbGjIHm6SU4ceQu2ljtFRfeu/QIhAKw2uEfLId8sqLfEoErOlvu2UV2wbP3ttrYP1hoZT0Ge"}; + void MixerAvatar::fstRequestComplete() { ResourceRequest* fstRequest = static_cast(QObject::sender()); QMutexLocker certifyLocker(&_avatarCertifyLock); if (fstRequest == _avatarRequest) { auto result = fstRequest->getResult(); if (result != ResourceRequest::Success) { + _verifyState = kError; qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result; } else { _avatarFSTContents = fstRequest->getData(); - _avatarFSTValid = true; + _verifyState = kReceivedFST; + generateFSTHash(); + QString& marketplacePublicKey = EntityItem::_marketplacePublicKey; + _certificateId = PLANT_CERTID; + bool staticVerification = validateFSTHash(marketplacePublicKey); + if (!staticVerification) { + _verifyState = kNoncertified; + } + + if (!_certificateId.isEmpty()) { + 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"] = _certificateId; + _verifyState = kRequestingOwner; + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + networkReply->setParent(this); + connect(networkReply, &QNetworkReply::finished, [this, networkReply]() { + QString responseString = networkReply->readAll(); + qCDebug(avatars) << "Marketplace response for avatar" << getDisplayName() << ":" << responseString; + QJsonDocument responseJson = QJsonDocument::fromJson(responseString.toUtf8()); + if (networkReply->error() == QNetworkReply::NoError) { + volatile int x = 1; + if (responseJson["status"].toString() == "success") { + auto jsonData = responseJson["data"]; + // owner, owner key? + } + } else { + auto jsonData = responseJson["data"]; + if (!jsonData.isUndefined() + && !jsonData.toObject()["message"].isUndefined()) { + qCDebug(avatars) << "Certificate Id lookup failed for" << getDisplayName() << ":" + << jsonData.toObject()["message"].toString(); + _verifyState = kError; + } + } + networkReply->deleteLater(); + }); + + } } _avatarRequest->deleteLater(); _avatarRequest = nullptr; - } else { + } else { qCDebug(avatars) << "Incorrect request for" << getDisplayName(); } } + +bool MixerAvatar::generateFSTHash() { + if (_avatarFSTContents.length() == 0) { + return false; + } + QCryptographicHash fstHash(QCryptographicHash::Sha224); + fstHash.addData(_avatarFSTContents); + _certificateHash = fstHash.result(); + return true; +} + +bool MixerAvatar::validateFSTHash(const QString& publicKey) { + // Guess we should refactor this stuff into a Authorization namespace ... + return EntityItemProperties::verifySignature(publicKey, _certificateHash, + QByteArray::fromBase64(_certificateId.toUtf8())); +} diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index f89004d997..74309db461 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -29,12 +29,22 @@ public: private: bool _needsHeroCheck{ false }; + // Avatar certification/verification + enum VerifyState { kNoncertified, kRequestingFST, kReceivedFST, kStaticValidation, kRequestingOwner, kChallengeClient, kVerified, + kVerificationFailed, kVerificationSucceeded, kError }; + Q_ENUM(VerifyState); + VerifyState _verifyState { kNoncertified }; QMutex _avatarCertifyLock; - ResourceRequest* _avatarRequest{ nullptr }; - QString _avatarURLString; + ResourceRequest* _avatarRequest { nullptr }; + QString _marketplaceIdString; QByteArray _avatarFSTContents; + QByteArray _certificateHash; + QString _certificateId; bool _avatarFSTValid { false }; + bool generateFSTHash(); + bool validateFSTHash(const QString& publicKey); + private slots: void fstRequestComplete(); };