From 50cc68c63eec993e9a30ce43e3770c589c2f8a05 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sun, 19 Aug 2018 12:43:06 -0700 Subject: [PATCH 01/29] Recording app doesn't record old attachments --- libraries/avatars/src/AvatarData.cpp | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 62c7a7053f..bc504b4496 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2255,7 +2255,6 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel"); static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); // It isn't meaningful to persist sessionDisplayName. -static const QString JSON_AVATAR_ATTACHMENTS = QStringLiteral("attachments"); static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); static const QString JSON_AVATAR_VERSION = QStringLiteral("version"); @@ -2303,13 +2302,6 @@ QJsonObject AvatarData::toJson() const { if (!getDisplayName().isEmpty()) { root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName(); } - if (!getAttachmentData().isEmpty()) { - QJsonArray attachmentsJson; - for (auto attachment : getAttachmentData()) { - attachmentsJson.push_back(attachment.toJson()); - } - root[JSON_AVATAR_ATTACHMENTS] = attachmentsJson; - } _avatarEntitiesLock.withReadLock([&] { if (!_avatarEntityData.empty()) { @@ -2426,26 +2418,6 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble()); } - QVector attachments; - if (json.contains(JSON_AVATAR_ATTACHMENTS) && json[JSON_AVATAR_ATTACHMENTS].isArray()) { - QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray(); - for (auto attachmentJson : attachmentsJson) { - AttachmentData attachment; - attachment.fromJson(attachmentJson.toObject()); - attachments.push_back(attachment); - } - } - if (attachments != getAttachmentData()) { - setAttachmentData(attachments); - } - - // if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { - // QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray(); - // for (auto attachmentJson : attachmentsJson) { - // // TODO -- something - // } - // } - if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) { // because we don't have the full joint hierarchy skeleton of the model, From c564119edc03948c5d4c067769e6c7b19bbcec2f Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 20 Aug 2018 11:58:19 -0700 Subject: [PATCH 02/29] Keep attachments when playing back recordings --- libraries/avatars/src/AvatarData.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index bc504b4496..bfe483e8d7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2255,6 +2255,7 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel"); static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); // It isn't meaningful to persist sessionDisplayName. +static const QString JSON_AVATAR_ATTACHMENTS = QStringLiteral("attachments"); static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); static const QString JSON_AVATAR_VERSION = QStringLiteral("version"); @@ -2302,7 +2303,6 @@ QJsonObject AvatarData::toJson() const { if (!getDisplayName().isEmpty()) { root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName(); } - _avatarEntitiesLock.withReadLock([&] { if (!_avatarEntityData.empty()) { QJsonArray avatarEntityJson; @@ -2418,6 +2418,19 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble()); } + QVector attachments; + if (json.contains(JSON_AVATAR_ATTACHMENTS) && json[JSON_AVATAR_ATTACHMENTS].isArray()) { + QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray(); + for (auto attachmentJson : attachmentsJson) { + AttachmentData attachment; + attachment.fromJson(attachmentJson.toObject()); + attachments.push_back(attachment); + } + } + if (attachments != getAttachmentData()) { + setAttachmentData(attachments); + } + if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) { // because we don't have the full joint hierarchy skeleton of the model, From dc0fbcca59ecc3d671ed4e08177c25a3994d9975 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 20 Aug 2018 13:10:36 -0700 Subject: [PATCH 03/29] Include avatar entities on recordings --- libraries/avatars/src/AvatarData.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index bfe483e8d7..58b86db881 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2431,6 +2431,18 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { setAttachmentData(attachments); } + if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { + QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray(); + for (auto attachmentJson : attachmentsJson) { + if (attachmentJson.isObject()) { + QVariantMap entityData = attachmentJson.toObject().toVariantMap(); + QUuid entityID = entityData.value("id").toUuid(); + QByteArray properties = entityData.value("properties").toByteArray(); + updateAvatarEntity(entityID, properties); + } + } + } + if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) { // because we don't have the full joint hierarchy skeleton of the model, From 8bd9bf39b6abb052e6bcdcb92456ba407298757a Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 20 Aug 2018 14:31:03 -0700 Subject: [PATCH 04/29] Fix json load --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 58b86db881..8c72be633f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2432,7 +2432,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { } if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { - QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray(); + QJsonArray attachmentsJson = json[JSON_AVATAR_ENTITIES].toArray(); for (auto attachmentJson : attachmentsJson) { if (attachmentJson.isObject()) { QVariantMap entityData = attachmentJson.toObject().toVariantMap(); From 5f4903f884a37edb06e6189136e3949afd9c88b3 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 22 Aug 2018 08:53:51 -0700 Subject: [PATCH 05/29] Record entities --- interface/src/avatar/MyAvatar.cpp | 1 + .../src/avatars-renderer/Avatar.cpp | 2 ++ libraries/avatars/src/AvatarData.cpp | 20 +++++++++++++++---- libraries/avatars/src/AvatarData.h | 2 ++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 41855e7973..ba9d15159b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -184,6 +184,7 @@ MyAvatar::MyAvatar(QThread* thread) : if (recordingInterface->getPlayFromCurrentLocation()) { setRecordingBasis(); } + createRecordingIDs(); _previousCollisionGroup = _characterController.computeCollisionGroup(); _characterController.setCollisionless(true); } else { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 0b43fd5433..0f2c936507 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -236,6 +236,8 @@ void Avatar::updateAvatarEntities() { return; } + createRecordingIDs(); + if (getID().isNull() || getID() == AVATAR_SELF_ID || DependencyManager::get()->getSessionUUID() == QUuid()) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 8c72be633f..40df2fa3aa 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2243,6 +2243,15 @@ void AvatarData::setRecordingBasis(std::shared_ptr recordingBasis) { _recordingBasis = recordingBasis; } +void AvatarData::createRecordingIDs() { + _avatarEntitiesLock.withReadLock([&] { + _avatarEntityForRecording.clear(); + for (int i = 0; i < _avatarEntityData.size(); i++) { + _avatarEntityForRecording.insert(QUuid::createUuid()); + } + }); +} + void AvatarData::clearRecordingBasis() { _recordingBasis.reset(); } @@ -2306,10 +2315,12 @@ QJsonObject AvatarData::toJson() const { _avatarEntitiesLock.withReadLock([&] { if (!_avatarEntityData.empty()) { QJsonArray avatarEntityJson; + int entityCount = 0; for (auto entityID : _avatarEntityData.keys()) { QVariantMap entityData; - entityData.insert("id", entityID); - entityData.insert("properties", _avatarEntityData.value(entityID)); + QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID; + entityData.insert("id", newId); + entityData.insert("properties", _avatarEntityData.value(entityID).toBase64()); avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); } root[JSON_AVATAR_ENTITIES] = avatarEntityJson; @@ -2434,10 +2445,11 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { QJsonArray attachmentsJson = json[JSON_AVATAR_ENTITIES].toArray(); for (auto attachmentJson : attachmentsJson) { - if (attachmentJson.isObject()) { + if (attachmentJson.isObject()) { QVariantMap entityData = attachmentJson.toObject().toVariantMap(); QUuid entityID = entityData.value("id").toUuid(); - QByteArray properties = entityData.value("properties").toByteArray(); + auto ds = QByteArray::fromBase64(entityData.value("properties").toString().toUtf8()); + QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray()); updateAvatarEntity(entityID, properties); } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index fcc63fdc98..79d47eb03f 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1077,6 +1077,7 @@ public: void clearRecordingBasis(); TransformPointer getRecordingBasis() const; void setRecordingBasis(TransformPointer recordingBasis = TransformPointer()); + void createRecordingIDs(); QJsonObject toJson() const; void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); @@ -1410,6 +1411,7 @@ protected: mutable ReadWriteLockable _avatarEntitiesLock; AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar + AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording AvatarEntityMap _avatarEntityData; bool _avatarEntityDataLocallyEdited { false }; bool _avatarEntityDataChanged { false }; From 5a5aa2f5b9cc07a66369a28d715302d9ea1af547 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 22 Aug 2018 09:13:27 -0700 Subject: [PATCH 06/29] Remove proxy line --- libraries/avatars/src/AvatarData.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 40df2fa3aa..c97051eddf 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2448,7 +2448,6 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { if (attachmentJson.isObject()) { QVariantMap entityData = attachmentJson.toObject().toVariantMap(); QUuid entityID = entityData.value("id").toUuid(); - auto ds = QByteArray::fromBase64(entityData.value("properties").toString().toUtf8()); QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray()); updateAvatarEntity(entityID, properties); } From 579b2435c27c2733dbe3f421dc0b4d66cd81ea31 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 22 Aug 2018 11:04:26 -0700 Subject: [PATCH 07/29] don't squash delete events during shutdown so that QWebEngine process gets destroyed --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 396c6cbcac..cc24511afd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3667,7 +3667,7 @@ bool Application::event(QEvent* event) { bool Application::eventFilter(QObject* object, QEvent* event) { - if (_aboutToQuit) { + if (_aboutToQuit && event->type() != QEvent::DeferredDelete && event->type() != QEvent::Destroy) { return true; } From 3b5ba71163a66f95b20320f0fa5b02d6833f519e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 22 Aug 2018 12:23:24 -0700 Subject: [PATCH 08/29] fix a crash in ESS from missing dependency --- .../src/scripts/EntityScriptServer.cpp | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 05002828f5..1d46b6040e 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -55,7 +56,8 @@ int EntityScriptServer::_entitiesScriptEngineCount = 0; EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { qInstallMessageHandler(messageHandler); - DependencyManager::get()->setPacketSender(&_entityEditSender); + DependencyManager::set(false)->setPacketSender(&_entityEditSender); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -559,14 +561,16 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer mess void EntityScriptServer::aboutToFinish() { shutdownScriptEngine(); - auto entityScriptingInterface = DependencyManager::get(); - // our entity tree is going to go away so tell that to the EntityScriptingInterface - entityScriptingInterface->setEntityTree(nullptr); + { + auto entityScriptingInterface = DependencyManager::get(); + // our entity tree is going to go away so tell that to the EntityScriptingInterface + entityScriptingInterface->setEntityTree(nullptr); - // Should always be true as they are singletons. - if (entityScriptingInterface->getPacketSender() == &_entityEditSender) { - // The packet sender is about to go away. - entityScriptingInterface->setPacketSender(nullptr); + // Should always be true as they are singletons. + if (entityScriptingInterface->getPacketSender() == &_entityEditSender) { + // The packet sender is about to go away. + entityScriptingInterface->setPacketSender(nullptr); + } } DependencyManager::destroy(); @@ -575,8 +579,12 @@ void EntityScriptServer::aboutToFinish() { DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + // cleanup the AudioInjectorManager (and any still running injectors) DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); From 01e8fd66a3afb09e1c685a48e86c7e2c5be926d6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 22 Aug 2018 12:41:13 -0700 Subject: [PATCH 09/29] fix script engine(s) cleanup from ESS --- .../src/scripts/EntityScriptServer.cpp | 34 +++++++++++-------- libraries/script-engine/src/ScriptEngine.cpp | 4 +-- libraries/script-engine/src/ScriptEngine.h | 2 -- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 1d46b6040e..586931d403 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -457,8 +457,11 @@ void EntityScriptServer::resetEntitiesScriptEngine() { auto newEngineSP = qSharedPointerCast(newEngine); DependencyManager::get()->setEntitiesScriptEngine(newEngineSP); - disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, - this, &EntityScriptServer::updateEntityPPS); + if (_entitiesScriptEngine) { + disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, + this, &EntityScriptServer::updateEntityPPS); + } + _entitiesScriptEngine.swap(newEngine); connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); @@ -489,6 +492,21 @@ void EntityScriptServer::shutdownScriptEngine() { _shuttingDown = true; clear(); // always clear() on shutdown + + auto scriptEngines = DependencyManager::get(); + scriptEngines->shutdownScripting(); + + _entitiesScriptEngine.clear(); + + auto entityScriptingInterface = DependencyManager::get(); + // our entity tree is going to go away so tell that to the EntityScriptingInterface + entityScriptingInterface->setEntityTree(nullptr); + + // Should always be true as they are singletons. + if (entityScriptingInterface->getPacketSender() == &_entityEditSender) { + // The packet sender is about to go away. + entityScriptingInterface->setPacketSender(nullptr); + } } void EntityScriptServer::addingEntity(const EntityItemID& entityID) { @@ -561,18 +579,6 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer mess void EntityScriptServer::aboutToFinish() { shutdownScriptEngine(); - { - auto entityScriptingInterface = DependencyManager::get(); - // our entity tree is going to go away so tell that to the EntityScriptingInterface - entityScriptingInterface->setEntityTree(nullptr); - - // Should always be true as they are singletons. - if (entityScriptingInterface->getPacketSender() == &_entityEditSender) { - // The packet sender is about to go away. - entityScriptingInterface->setPacketSender(nullptr); - } - } - DependencyManager::destroy(); DependencyManager::get()->cleanup(); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 105742db35..ce4ec89950 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -176,9 +176,7 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const _timerFunctionMap(), _fileNameString(fileNameString), _arrayBufferClass(new ArrayBufferClass(this)), - _assetScriptingInterface(new AssetScriptingInterface(this)), - // don't delete `ScriptEngines` until all `ScriptEngine`s are gone - _scriptEngines(DependencyManager::get()) + _assetScriptingInterface(new AssetScriptingInterface(this)) { switch (_context) { case Context::CLIENT_SCRIPT: diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 1791360a45..94b50bfd2c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -806,8 +806,6 @@ protected: static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS; Setting::Handle _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true }; - - QSharedPointer _scriptEngines; }; ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, From 73cca4294002f99c8cc782ca0439787ac5c8966c Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 13 Aug 2018 12:35:26 -0700 Subject: [PATCH 10/29] initial prototype --- interface/src/commerce/Ledger.cpp | 23 +++++++++++++++++++++-- interface/src/commerce/Wallet.cpp | 26 ++++++++++++++++++++++++++ interface/src/commerce/Wallet.h | 4 ++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 702251f867..bcd96fdb06 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -125,8 +125,19 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { emit receiveAtResult(result); return false; // We know right away that we will fail, so tell the caller. } - - signedSend("public_key", hfc_key.toUtf8(), signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); + auto wallet = DependencyManager::get(); + QByteArray locker = wallet->getWallet(); + if (locker.isEmpty()) { + signedSend("public_key", hfc_key.toUtf8(), signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); + } else { + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["locker"] = QString::fromUtf8(locker); + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + qCDebug(commerce) << "FIXME transactionString" << transactionString; + signedSend("text", transactionString, signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); + } return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } @@ -277,17 +288,22 @@ void Ledger::accountSuccess(QNetworkReply* reply) { // lets set the appropriate stuff in the wallet now auto wallet = DependencyManager::get(); QByteArray response = reply->readAll(); + qCDebug(commerce) << "FIXME accountSuccess got" << response; QJsonObject data = QJsonDocument::fromJson(response).object()["data"].toObject(); auto salt = QByteArray::fromBase64(data["salt"].toString().toUtf8()); auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8()); auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8()); QString remotePublicKey = data["public_key"].toString(); + const QByteArray locker = data["locker"].toString().toUtf8(); bool isOverride = wallet->wasSoftReset(); wallet->setSalt(salt); wallet->setIv(iv); wallet->setCKey(ckey); + if (!locker.isEmpty()) { + wallet->setWallet(locker); + } QString keyStatus = "ok"; QStringList localPublicKeys = wallet->listPublicKeys(); @@ -301,6 +317,9 @@ void Ledger::accountSuccess(QNetworkReply* reply) { keyStatus = "preexisting"; } else if (localPublicKeys.first() != remotePublicKey) { keyStatus = "conflicting"; + } else if (locker.isEmpty()) { + QString key = localPublicKeys.first(); + receiveAt(key, key); } } diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index ef6b4654f5..197e6f60b9 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -190,6 +190,32 @@ bool writeKeys(const char* filename, EC_KEY* keys) { return retval; } +bool Wallet::setWallet(const QByteArray& wallet) { + QFile file(keyFilePath()); + if (!file.open(QIODevice::WriteOnly)) { + qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath(); + return false; + } + if (file.write(wallet) != wallet.count()) { + qCCritical(commerce) << "Unable to write wallet in" << keyFilePath(); + return false; + } + file.close(); + qCDebug(commerce) << "FIXME wrote" << wallet.count() << "to" << keyFilePath(); + return true; +} +QByteArray Wallet::getWallet() { + QFile file(keyFilePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCInfo(commerce) << "No existing wallet in" << keyFilePath(); + return QByteArray(); + } + QByteArray wallet = file.readAll(); + file.close(); + qCDebug(commerce) << "FIXME read" << wallet.count() << "from" << keyFilePath(); + return wallet; +} + QPair generateECKeypair() { EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1); diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 665afd9a23..4e7608b096 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -44,6 +44,10 @@ public: void setCKey(const QByteArray& ckey) { _ckey = ckey; } QByteArray getCKey() { return _ckey; } + // FIXME protect more + bool setWallet(const QByteArray& wallet); + QByteArray getWallet(); + bool setPassphrase(const QString& passphrase); QString* getPassphrase() { return _passphrase; } bool getPassphraseIsCached() { return !(_passphrase->isEmpty()); } From ef9cc782b91bba76aa31a629a1a6ba3beb6c6f3e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 13 Aug 2018 14:30:36 -0700 Subject: [PATCH 11/29] no need to keep old format in client --- interface/src/commerce/Ledger.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index bcd96fdb06..c0bda8493a 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -127,17 +127,13 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { } auto wallet = DependencyManager::get(); QByteArray locker = wallet->getWallet(); - if (locker.isEmpty()) { - signedSend("public_key", hfc_key.toUtf8(), signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); - } else { - QJsonObject transaction; - transaction["public_key"] = hfc_key; - transaction["locker"] = QString::fromUtf8(locker); - QJsonDocument transactionDoc{ transaction }; - auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); - qCDebug(commerce) << "FIXME transactionString" << transactionString; - signedSend("text", transactionString, signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); - } + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["locker"] = QString::fromUtf8(locker); + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + qCDebug(commerce) << "FIXME transactionString" << transactionString; + signedSend("text", transactionString, signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } From ef1aacd226b96eb3d88ce7b1826a9d1f74a3d83c Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 14 Aug 2018 13:14:48 -0700 Subject: [PATCH 12/29] handle password changes --- interface/src/commerce/Ledger.cpp | 24 +++++++++++++++--------- interface/src/commerce/Ledger.h | 3 ++- interface/src/commerce/Wallet.cpp | 13 ++++++++++++- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index c0bda8493a..4ab76c42f5 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -117,7 +117,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure); } -bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { +bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker) { auto accountManager = DependencyManager::get(); if (!accountManager->isLoggedIn()) { qCWarning(commerce) << "Cannot set receiveAt when not logged in."; @@ -125,8 +125,6 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { emit receiveAtResult(result); return false; // We know right away that we will fail, so tell the caller. } - auto wallet = DependencyManager::get(); - QByteArray locker = wallet->getWallet(); QJsonObject transaction; transaction["public_key"] = hfc_key; transaction["locker"] = QString::fromUtf8(locker); @@ -137,6 +135,16 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } +bool Ledger::receiveAt() { + auto wallet = DependencyManager::get(); + auto keys = wallet->listPublicKeys(); + if (keys.isEmpty()) { + return false; + } + auto key = keys.first(); + return receiveAt(key, key, wallet->getWallet()); +} + void Ledger::balance(const QStringList& keys) { keysQuery("balance", "balanceSuccess", "balanceFailure"); } @@ -304,18 +312,16 @@ void Ledger::accountSuccess(QNetworkReply* reply) { QString keyStatus = "ok"; QStringList localPublicKeys = wallet->listPublicKeys(); if (remotePublicKey.isEmpty() || isOverride) { - if (!localPublicKeys.isEmpty()) { - QString key = localPublicKeys.first(); - receiveAt(key, key); + if (!localPublicKeys.isEmpty()) { // Let the metaverse know about a local wallet. + receiveAt(); } } else { if (localPublicKeys.isEmpty()) { keyStatus = "preexisting"; } else if (localPublicKeys.first() != remotePublicKey) { keyStatus = "conflicting"; - } else if (locker.isEmpty()) { - QString key = localPublicKeys.first(); - receiveAt(key, key); + } else if (locker.isEmpty()) { // Matches metaverse data, but we haven't lockered it yet. + receiveAt(); } } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index ba2f167f4b..427395ee11 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -26,7 +26,8 @@ class Ledger : public QObject, public Dependency { public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); - bool receiveAt(const QString& hfc_key, const QString& signing_key); + bool receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker); + bool receiveAt(); void balance(const QStringList& keys); void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage); void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 197e6f60b9..fb6a0c9b03 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -602,7 +602,7 @@ bool Wallet::generateKeyPair() { // 2. It is maximally private, and we can step back from that later if desired. // 3. It maximally exercises all the machinery, so we are most likely to surface issues now. auto ledger = DependencyManager::get(); - return ledger->receiveAt(key, key); + return ledger->receiveAt(key, key, getWallet()); } QStringList Wallet::listPublicKeys() { @@ -741,6 +741,11 @@ QString Wallet::getKeyFilePath() { bool Wallet::writeWallet(const QString& newPassphrase) { EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str()); + auto ledger = DependencyManager::get(); + // Remove any existing locker, because it will be out of date. + if (!_publicKeys.isEmpty() && !ledger->receiveAt(_publicKeys.first(), _publicKeys.first(), QByteArray())) { + return false; // FIXME: receiveAt could fail asynchronously. + } if (keys) { // we read successfully, so now write to a new temp file QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); @@ -748,6 +753,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) { if (!newPassphrase.isEmpty()) { setPassphrase(newPassphrase); } + if (writeKeys(tempFileName.toStdString().c_str(), keys)) { if (writeSecurityImage(_securityImage, tempFileName)) { // ok, now move the temp file to the correct spot @@ -755,6 +761,11 @@ bool Wallet::writeWallet(const QString& newPassphrase) { QFile(tempFileName).rename(QString(keyFilePath())); qCDebug(commerce) << "wallet written successfully"; emit keyFilePathIfExistsResult(getKeyFilePath()); + if (!walletIsAuthenticatedWithPassphrase() || !ledger->receiveAt()) { + // FIXME: Should we fail the whole operation? + // Tricky, because we'll need the the key and file from the TEMP location... + qCWarning(commerce) << "Failed to update locker"; + } return true; } else { qCDebug(commerce) << "couldn't write security image to temp wallet"; From 13629f6870fe60b0f094c316b0b2f04e200602bc Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 15 Aug 2018 11:23:50 -0700 Subject: [PATCH 13/29] bypass setup and password, and convert old wallets to account based before lockering --- .../qml/hifi/commerce/wallet/Help.qml | 11 +++++---- .../qml/hifi/commerce/wallet/Wallet.qml | 24 ++++++++++++++++--- interface/src/commerce/Ledger.cpp | 1 + interface/src/commerce/Wallet.cpp | 24 +++++++++++++++---- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index b453509712..e38f23e5a5 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -114,11 +114,9 @@ In your Wallet's Send Money tab, choose from your list of connections, or choose ListElement { isExpanded: false; question: "What is a Security Pic?" - answer: "Your Security Pic is an encrypted image that you select during Wallet Setup. \ -It acts as an extra layer of Wallet security. \ -When you see your Security Pic, you know that your actions and data are securely making use of your private keys.\ -

Don't enter your passphrase anywhere that doesn't display your Security Pic! \ -If you don't see your Security Pic on a page that requests your Wallet passphrase, someone untrustworthy may be trying to access your Wallet."; + answer: "Your Security Pic acts as an extra layer of Wallet security. \ +When you see your Security Pic, you know that your actions and data are securely making use of your account. \ +Tap here to change your Security Pic."; } ListElement { isExpanded: false; @@ -260,6 +258,9 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta } } else if (link === "#support") { Qt.openUrlExternally("mailto:support@highfidelity.com"); + } else if (link === "#securitypic") { + console.log("HRS FIXME here"); + sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 603d7fb676..f48553a4d0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -257,9 +257,9 @@ Rectangle { Connections { onSendSignalToWallet: { if (msg.method === 'walletSecurity_changeSecurityImageCancelled') { - root.activeView = "security"; + root.activeView = "walletHome"; // was "security"; } else if (msg.method === 'walletSecurity_changeSecurityImageSuccess') { - root.activeView = "security"; + root.activeView = "walletHome"; // was "security"; } else { sendToScript(msg); } @@ -399,6 +399,9 @@ Rectangle { onSendSignalToWallet: { if (msg.method === 'walletReset' || msg.method === 'passphraseReset') { sendToScript(msg); + } else if (msg.method === 'walletSecurity_changeSecurityImage') { + securityImageChange.initModel(); + root.activeView = "securityImageChange"; } } } @@ -607,7 +610,7 @@ Rectangle { } RalewaySemiBold { - text: "SECURITY"; + text: "PURCHASES"; // was "SECURITY"; // Text size size: 16; // Anchors @@ -629,8 +632,11 @@ Rectangle { anchors.fill: parent; hoverEnabled: enabled; onClicked: { + sendToScript({method: 'goToPurchases_fromWalletHome'}); + /* was root.activeView = "security"; tabButtonsContainer.resetTabButtonColors(); + */ } onEntered: parent.color = hifi.colors.blueHighlight; onExited: parent.color = root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black; @@ -803,12 +809,24 @@ Rectangle { } function walletResetSetup() { + /* Bypass all this and do it automatically root.activeView = "walletSetup"; var timestamp = new Date(); walletSetup.startingTimestamp = timestamp; walletSetup.setupAttemptID = generateUUID(); UserActivityLogger.commerceWalletSetupStarted(timestamp, walletSetup.setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app", (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : '')); + */ + + var randomNumber = Math.floor(Math.random() * 34) + 1; + var securityImagePath = "images/" + addLeadingZero(randomNumber) + ".jpg"; + Commerce.getWalletAuthenticatedStatus(); // before writing security image, ensures that salt/account password is set. + Commerce.chooseSecurityImage(securityImagePath); + Commerce.generateKeyPair(); + } + + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; } function followReferrer(msg) { diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 4ab76c42f5..18d22057fd 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -307,6 +307,7 @@ void Ledger::accountSuccess(QNetworkReply* reply) { wallet->setCKey(ckey); if (!locker.isEmpty()) { wallet->setWallet(locker); + wallet->setPassphrase("ACCOUNT"); // We only locker wallets that have been converted to account-based auth. } QString keyStatus = "ok"; diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index fb6a0c9b03..84e90544ff 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -131,7 +131,7 @@ bool Wallet::writeBackupInstructions() { QFile outputFile(outputFilename); bool retval = false; - if (getKeyFilePath() == "") + if (getKeyFilePath().isEmpty()) { return false; } @@ -360,7 +360,7 @@ Wallet::Wallet() { uint status; QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : ""; - if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { + if (wallet->getKeyFilePath().isEmpty() || !wallet->getSecurityImage()) { if (keyStatus == "preexisting") { status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING; } else{ @@ -550,15 +550,23 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() { // FIXME: initialize OpenSSL elsewhere soon initialize(); + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: checking" << (!_passphrase || !_passphrase->isEmpty()); // this should always be false if we don't have a passphrase // cached yet if (!_passphrase || _passphrase->isEmpty()) { - return false; + if (!getKeyFilePath().isEmpty()) { // If file exists, then it is an old school file that has not been lockered. Must get user's passphrase. + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: No passphrase, but there is an existing wallet."; + return false; + } else { + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: New setup."; + setPassphrase("ACCOUNT"); // Going forward, consider this an account-based client. + } } if (_publicKeys.count() > 0) { // we _must_ be authenticated if the publicKeys are there DependencyManager::get()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY); + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet was ready"; return true; } @@ -571,10 +579,15 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() { // be sure to add the public key so we don't do this over and over _publicKeys.push_back(publicKey.toBase64()); + + if (*_passphrase != "ACCOUNT") { + changePassphrase("ACCOUNT"); // Rewrites with salt and constant, and will be lockered that way. + } + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet now ready"; return true; } } - + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet not ready"; return false; } @@ -585,6 +598,7 @@ bool Wallet::generateKeyPair() { qCInfo(commerce) << "Generating keypair."; auto keyPair = generateECKeypair(); if (!keyPair.first) { + qCWarning(commerce) << "Empty keypair"; return false; } @@ -692,11 +706,13 @@ void Wallet::chooseSecurityImage(const QString& filename) { // there _is_ a keyfile, we need to update it (similar to changing the // passphrase, we need to do so into a temp file and move it). if (!QFile(keyFilePath()).exists()) { + qCDebug(commerce) << "initial security pic set for empty wallet"; emit securityImageResult(true); return; } bool success = writeWallet(); + qCDebug(commerce) << "updated security pic" << success; emit securityImageResult(success); } From 88a3505b6a308cf53d3ad0b63af42f506df1e260 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 22 Aug 2018 12:59:43 -0700 Subject: [PATCH 14/29] cleanup --- .../qml/hifi/commerce/wallet/Help.qml | 19 +++++++++---------- .../qml/hifi/commerce/wallet/Security.qml | 7 +++++-- .../qml/hifi/commerce/wallet/Wallet.qml | 9 +++------ interface/src/commerce/Ledger.cpp | 6 ++++-- interface/src/commerce/Wallet.cpp | 2 -- interface/src/commerce/Wallet.h | 8 ++++---- 6 files changed, 25 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index e38f23e5a5..1c37ded4ee 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -64,7 +64,7 @@ Item { answer: "High Fidelity commerce is in open beta right now. Want more HFC? \ Get it by going to

BankOfHighFidelity. and meeting with the banker!"; } - ListElement { + /* ListElement { isExpanded: false; question: "What are private keys and where are they stored?"; answer: @@ -74,16 +74,16 @@ After wallet setup, a hifikey file is stored on your computer in High Fidelity I Your hifikey file contains your private key and is protected by your wallet passphrase. \

It is very important to back up your hifikey file! \ Tap here to open the folder where your HifiKeys are stored on your main display." - } - ListElement { + }*/ + /*ListElement { isExpanded: false; question: "How do I back up my private keys?"; answer: "You can back up your hifikey file (which contains your private key and is encrypted using your wallet passphrase) by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. \ Restore your hifikey file by replacing the file in Interface's AppData directory with your backup copy. \ Others with access to your back up should not be able to spend your HFC without your passphrase. \ Tap here to open the folder where your HifiKeys are stored on your main display."; - } - ListElement { + }*/ + /*ListElement { isExpanded: false; question: "What happens if I lose my private keys?"; answer: "We cannot stress enough that you should keep a backup! For security reasons, High Fidelity does not keep a copy, and cannot restore it for you. \ @@ -94,8 +94,8 @@ Here are some things to try:
    \
  • If you are a developer and have installed multiple builds of High Fidelity, your hifikey file might be in another folder
  • \


As a last resort, you can set up your Wallet again and generate a new hifikey file. \ Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over."; - } - ListElement { + }*/ + /*ListElement { isExpanded: false; question: "What if I forget my wallet passphrase?"; answer: "Your wallet passphrase is used to encrypt your private keys. Please write it down and store it securely! \ @@ -104,7 +104,7 @@ You will also no longer have access to the contents of your Wallet or My Purchas For security reasons, High Fidelity does not keep a copy of your passphrase, and can't restore it for you. \

If you still cannot remember your wallet passphrase, you can set up your Wallet again and generate a new hifikey file. \ Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over."; - } + }*/ ListElement { isExpanded: false; question: "How do I send HFC to other people?"; @@ -116,7 +116,7 @@ In your Wallet's Send Money tab, choose from your list of connections, or choose question: "What is a Security Pic?" answer: "Your Security Pic acts as an extra layer of Wallet security. \ When you see your Security Pic, you know that your actions and data are securely making use of your account. \ -Tap here to change your Security Pic."; +

Tap here to change your Security Pic."; } ListElement { isExpanded: false; @@ -259,7 +259,6 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta } else if (link === "#support") { Qt.openUrlExternally("mailto:support@highfidelity.com"); } else if (link === "#securitypic") { - console.log("HRS FIXME here"); sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 216d621bf8..08eb94a23c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -88,6 +88,7 @@ Item { color: hifi.colors.faintGray; } + /* Item { id: changePassphraseContainer; anchors.top: securityTextSeparator.bottom; @@ -154,10 +155,11 @@ Item { // Style color: hifi.colors.faintGray; } + */ Item { id: changeSecurityImageContainer; - anchors.top: changePassphraseSeparator.bottom; + anchors.top: securityTextSeparator.bottom; // changePassphraseSeparator.bottom; anchors.topMargin: 8; anchors.left: parent.left; anchors.leftMargin: 40; @@ -207,7 +209,7 @@ Item { } } } - + /* Rectangle { id: privateKeysSeparator; // Size @@ -344,6 +346,7 @@ Item { } } } + */ } // diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index f48553a4d0..ffd06cb4a8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -257,9 +257,9 @@ Rectangle { Connections { onSendSignalToWallet: { if (msg.method === 'walletSecurity_changeSecurityImageCancelled') { - root.activeView = "walletHome"; // was "security"; + root.activeView = "security"; } else if (msg.method === 'walletSecurity_changeSecurityImageSuccess') { - root.activeView = "walletHome"; // was "security"; + root.activeView = "security"; } else { sendToScript(msg); } @@ -610,7 +610,7 @@ Rectangle { } RalewaySemiBold { - text: "PURCHASES"; // was "SECURITY"; + text: "SECURITY"; // Text size size: 16; // Anchors @@ -632,11 +632,8 @@ Rectangle { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToScript({method: 'goToPurchases_fromWalletHome'}); - /* was root.activeView = "security"; tabButtonsContainer.resetTabButtonColors(); - */ } onEntered: parent.color = hifi.colors.blueHighlight; onExited: parent.color = root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 18d22057fd..67303f2a9b 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -31,7 +31,9 @@ QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) { QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object(); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); +#endif return data; } // Non-200 responses are not json: @@ -69,7 +71,9 @@ void Ledger::send(const QString& endpoint, const QString& success, const QString auto accountManager = DependencyManager::get(); const QString URL = "/api/v1/commerce/"; JSONCallbackParameters callbackParams(this, success, fail); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact); +#endif accountManager->sendRequest(URL + endpoint, authType, method, @@ -130,7 +134,6 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key, const transaction["locker"] = QString::fromUtf8(locker); QJsonDocument transactionDoc{ transaction }; auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); - qCDebug(commerce) << "FIXME transactionString" << transactionString; signedSend("text", transactionString, signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } @@ -292,7 +295,6 @@ void Ledger::accountSuccess(QNetworkReply* reply) { // lets set the appropriate stuff in the wallet now auto wallet = DependencyManager::get(); QByteArray response = reply->readAll(); - qCDebug(commerce) << "FIXME accountSuccess got" << response; QJsonObject data = QJsonDocument::fromJson(response).object()["data"].toObject(); auto salt = QByteArray::fromBase64(data["salt"].toString().toUtf8()); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 84e90544ff..5b8417be7c 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -201,7 +201,6 @@ bool Wallet::setWallet(const QByteArray& wallet) { return false; } file.close(); - qCDebug(commerce) << "FIXME wrote" << wallet.count() << "to" << keyFilePath(); return true; } QByteArray Wallet::getWallet() { @@ -212,7 +211,6 @@ QByteArray Wallet::getWallet() { } QByteArray wallet = file.readAll(); file.close(); - qCDebug(commerce) << "FIXME read" << wallet.count() << "from" << keyFilePath(); return wallet; } diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 4e7608b096..c096713058 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -44,10 +44,6 @@ public: void setCKey(const QByteArray& ckey) { _ckey = ckey; } QByteArray getCKey() { return _ckey; } - // FIXME protect more - bool setWallet(const QByteArray& wallet); - QByteArray getWallet(); - bool setPassphrase(const QString& passphrase); QString* getPassphrase() { return _passphrase; } bool getPassphraseIsCached() { return !(_passphrase->isEmpty()); } @@ -77,6 +73,7 @@ private slots: void handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: + friend class Ledger; QStringList _publicKeys{}; QPixmap* _securityImage { nullptr }; QByteArray _salt; @@ -91,6 +88,9 @@ private: bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); bool writeBackupInstructions(); + bool setWallet(const QByteArray& wallet); + QByteArray getWallet(); + void account(); }; From b3d127c9531563900998973e850716fb0edbc5a7 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 22 Aug 2018 14:31:34 -0700 Subject: [PATCH 15/29] Fix flickering --- interface/src/avatar/MyAvatar.cpp | 2 +- .../src/avatars-renderer/Avatar.cpp | 5 +++-- libraries/avatars/src/AvatarData.cpp | 18 +++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ba9d15159b..8b139ee364 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -184,7 +184,6 @@ MyAvatar::MyAvatar(QThread* thread) : if (recordingInterface->getPlayFromCurrentLocation()) { setRecordingBasis(); } - createRecordingIDs(); _previousCollisionGroup = _characterController.computeCollisionGroup(); _characterController.setCollisionless(true); } else { @@ -203,6 +202,7 @@ MyAvatar::MyAvatar(QThread* thread) : connect(recorder.data(), &Recorder::recordingStateChanged, [=] { if (recorder->isRecording()) { + createRecordingIDs(); setRecordingBasis(); } else { clearRecordingBasis(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 0f2c936507..93de1ab29b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -236,8 +236,6 @@ void Avatar::updateAvatarEntities() { return; } - createRecordingIDs(); - if (getID().isNull() || getID() == AVATAR_SELF_ID || DependencyManager::get()->getSessionUUID() == QUuid()) { @@ -368,6 +366,9 @@ void Avatar::updateAvatarEntities() { } } } + if (avatarEntities.size() != _avatarEntityForRecording.size()) { + createRecordingIDs(); + } }); setAvatarEntityDataChanged(false); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c97051eddf..6fa5ba2c5b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2443,15 +2443,15 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { } if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { - QJsonArray attachmentsJson = json[JSON_AVATAR_ENTITIES].toArray(); - for (auto attachmentJson : attachmentsJson) { - if (attachmentJson.isObject()) { - QVariantMap entityData = attachmentJson.toObject().toVariantMap(); - QUuid entityID = entityData.value("id").toUuid(); - QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray()); - updateAvatarEntity(entityID, properties); - } - } + QJsonArray attachmentsJson = json[JSON_AVATAR_ENTITIES].toArray(); + for (auto attachmentJson : attachmentsJson) { + if (attachmentJson.isObject()) { + QVariantMap entityData = attachmentJson.toObject().toVariantMap(); + QUuid entityID = entityData.value("id").toUuid(); + QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray()); + updateAvatarEntity(entityID, properties); + } + } } if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { From dd098f05c2a8e1c3239548989e6c95a9f9b2d36d Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 22 Aug 2018 14:54:07 -0700 Subject: [PATCH 16/29] Change EntityQueryInitialResultsComplete protocol to use exclusive sequence number --- assignment-client/src/entities/EntityTreeSendThread.cpp | 2 +- interface/src/octree/SafeLanding.cpp | 4 ++-- interface/src/octree/SafeLanding.h | 2 +- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index f7ca05fbf2..8b7c8771e8 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -164,7 +164,7 @@ bool EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O // Send EntityQueryInitialResultsComplete reliable packet ... auto initialCompletion = NLPacket::create(PacketType::EntityQueryInitialResultsComplete, sizeof(OCTREE_PACKET_SEQUENCE), true); - initialCompletion->writePrimitive(OCTREE_PACKET_SEQUENCE(nodeData->getSequenceNumber() - 1U)); + initialCompletion->writePrimitive(OCTREE_PACKET_SEQUENCE(nodeData->getSequenceNumber())); DependencyManager::get()->sendPacket(std::move(initialCompletion), *node); } diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 31106457fb..60b660f66a 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -122,11 +122,11 @@ bool SafeLanding::isSequenceNumbersComplete() { int sequenceSize = _initialStart <= _initialEnd ? _initialEnd - _initialStart: _initialEnd + SEQUENCE_MODULO - _initialStart; auto startIter = _sequenceNumbers.find(_initialStart); - auto endIter = _sequenceNumbers.find(_initialEnd); + auto endIter = _sequenceNumbers.find(_initialEnd - 1); if (sequenceSize == 0 || (startIter != _sequenceNumbers.end() && endIter != _sequenceNumbers.end() - && distance(startIter, endIter) == sequenceSize) ) { + && distance(startIter, endIter) == sequenceSize - 1) ) { _trackingEntities = false; // Don't track anything else that comes in. return true; } diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 210dfbac25..9177930d81 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -26,7 +26,7 @@ class SafeLanding : public QObject { public: void startEntitySequence(QSharedPointer entityTreeRenderer); void stopEntitySequence(); - void setCompletionSequenceNumbers(int first, int last); + void setCompletionSequenceNumbers(int first, int last); // 'last' exclusive. void noteReceivedsequenceNumber(int sequenceNumber); bool isLoadSequenceComplete(); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 8b50e37699..bf455de3e4 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -95,7 +95,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarIdentityRequest: return 22; default: - return 22; + return 23; } } From c32cc1ead298f973e366da23fc3fa4ad748656e9 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 22 Aug 2018 15:42:15 -0700 Subject: [PATCH 17/29] adding implementation --- interface/src/avatar/MyAvatar.cpp | 3 +-- interface/src/ui/PreferencesDialog.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b30f05225e..79c3f969b5 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -105,8 +105,8 @@ MyAvatar::MyAvatar(QThread* thread) : _eyeContactTarget(LEFT_EYE), _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), - _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false), _smoothOrientationTimer(std::numeric_limits::max()), + _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", true), _smoothOrientationInitial(), _smoothOrientationTarget(), _hmdSensorMatrix(), @@ -444,7 +444,6 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { } void MyAvatar::update(float deltaTime) { - // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index e2037d54d0..c5cce261ae 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -272,7 +272,7 @@ void setupPreferences() { auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); }; auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; preferences->addPreference(new CheckPreference(VR_MOVEMENT, - QStringLiteral("Advanced movement for hand controllers"), + QStringLiteral("Advanced movement for VR (Teleport movement when unchecked)"), getter, setter)); } { From 9518b8cdafa47a51678df0b6c604515bdab5e807 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 22 Aug 2018 15:46:02 -0700 Subject: [PATCH 18/29] rearrangement --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 79c3f969b5..840747003f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -105,8 +105,8 @@ MyAvatar::MyAvatar(QThread* thread) : _eyeContactTarget(LEFT_EYE), _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), - _smoothOrientationTimer(std::numeric_limits::max()), _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", true), + _smoothOrientationTimer(std::numeric_limits::max()), _smoothOrientationInitial(), _smoothOrientationTarget(), _hmdSensorMatrix(), From 4daa30c1d0c199f306920b344ce266cd0fa6dc4b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 22 Aug 2018 15:58:37 -0700 Subject: [PATCH 19/29] changing text for check preference --- interface/src/ui/PreferencesDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index c5cce261ae..79ca2063ec 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -272,7 +272,7 @@ void setupPreferences() { auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); }; auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; preferences->addPreference(new CheckPreference(VR_MOVEMENT, - QStringLiteral("Advanced movement for VR (Teleport movement when unchecked)"), + QStringLiteral("Advanced movement in VR (Teleport movement when unchecked)"), getter, setter)); } { From c4b916af62788d62946d59a140ad26ede6f9f81f Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 22 Aug 2018 16:22:30 -0700 Subject: [PATCH 20/29] Just bump packet version for EntityQueryInitialResultsComplete --- libraries/networking/src/udt/PacketHeaders.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index bf455de3e4..25503e192f 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -94,8 +94,10 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(AvatarQueryVersion::ConicalFrustums); case PacketType::AvatarIdentityRequest: return 22; - default: + case PacketType::EntityQueryInitialResultsComplete: return 23; + default: + return 22; } } From c0966d738e334a17f94320639accdfa7f1ae2cde Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 22 Aug 2018 16:40:49 -0700 Subject: [PATCH 21/29] Use EntityVersion for the EntityQueryInitialResultsComplete packet --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 25503e192f..0f9a49da32 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -95,7 +95,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarIdentityRequest: return 22; case PacketType::EntityQueryInitialResultsComplete: - return 23; + return static_cast(EntityVersion::ParticleSpin); default: return 22; } From b7fa47da359dbc2394c85a82280b817ed14197f7 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 23 Aug 2018 10:37:55 -0700 Subject: [PATCH 22/29] cr feedback --- .../qml/hifi/commerce/wallet/Help.qml | 41 ---- .../qml/hifi/commerce/wallet/Security.qml | 207 ------------------ 2 files changed, 248 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 1c37ded4ee..6d8fc3c33f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -64,47 +64,6 @@ Item { answer: "High Fidelity commerce is in open beta right now. Want more HFC? \ Get it by going to

BankOfHighFidelity. and meeting with the banker!"; } - /* ListElement { - isExpanded: false; - question: "What are private keys and where are they stored?"; - answer: - "A private key is a secret piece of text that is used to prove ownership, unlock confidential information, and sign transactions. \ -In High Fidelity, your private key is used to securely access the contents of your Wallet and Purchases. \ -After wallet setup, a hifikey file is stored on your computer in High Fidelity Interface's AppData directory. \ -Your hifikey file contains your private key and is protected by your wallet passphrase. \ -

It is very important to back up your hifikey file! \ -Tap here to open the folder where your HifiKeys are stored on your main display." - }*/ - /*ListElement { - isExpanded: false; - question: "How do I back up my private keys?"; - answer: "You can back up your hifikey file (which contains your private key and is encrypted using your wallet passphrase) by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. \ -Restore your hifikey file by replacing the file in Interface's AppData directory with your backup copy. \ -Others with access to your back up should not be able to spend your HFC without your passphrase. \ -Tap here to open the folder where your HifiKeys are stored on your main display."; - }*/ - /*ListElement { - isExpanded: false; - question: "What happens if I lose my private keys?"; - answer: "We cannot stress enough that you should keep a backup! For security reasons, High Fidelity does not keep a copy, and cannot restore it for you. \ -If you lose your private key, you will no longer have access to the contents of your Wallet or My Purchases. \ -Here are some things to try:
    \ -
  • If you have backed up your hifikey file before, search your backup location
  • \ -
  • Search your AppData directory in the last machine you used to set up the Wallet
  • \ -
  • If you are a developer and have installed multiple builds of High Fidelity, your hifikey file might be in another folder
  • \ -


As a last resort, you can set up your Wallet again and generate a new hifikey file. \ -Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over."; - }*/ - /*ListElement { - isExpanded: false; - question: "What if I forget my wallet passphrase?"; - answer: "Your wallet passphrase is used to encrypt your private keys. Please write it down and store it securely! \ -

If you forget your passphrase, you will no longer be able to decrypt the hifikey file that the passphrase protects. \ -You will also no longer have access to the contents of your Wallet or My Purchases. \ -For security reasons, High Fidelity does not keep a copy of your passphrase, and can't restore it for you. \ -

If you still cannot remember your wallet passphrase, you can set up your Wallet again and generate a new hifikey file. \ -Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over."; - }*/ ListElement { isExpanded: false; question: "How do I send HFC to other people?"; diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 08eb94a23c..da3331ebfc 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -88,75 +88,6 @@ Item { color: hifi.colors.faintGray; } - /* - Item { - id: changePassphraseContainer; - anchors.top: securityTextSeparator.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - height: 75; - - HiFiGlyphs { - id: changePassphraseImage; - text: hifi.glyphs.passphrase; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - RalewaySemiBold { - text: "Passphrase"; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: changePassphraseImage.right; - anchors.leftMargin: 30; - width: 50; - // Text size - size: 18; - // Style - color: hifi.colors.white; - } - - // "Change Passphrase" button - HifiControlsUit.Button { - id: changePassphraseButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - width: 140; - height: 40; - text: "Change"; - onClicked: { - sendSignalToWallet({method: 'walletSecurity_changePassphrase'}); - } - } - } - - Rectangle { - id: changePassphraseSeparator; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: changePassphraseContainer.bottom; - anchors.topMargin: 8; - // Style - color: hifi.colors.faintGray; - } - */ - Item { id: changeSecurityImageContainer; anchors.top: securityTextSeparator.bottom; // changePassphraseSeparator.bottom; @@ -209,144 +140,6 @@ Item { } } } - /* - Rectangle { - id: privateKeysSeparator; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: changeSecurityImageContainer.bottom; - anchors.topMargin: 8; - // Style - color: hifi.colors.faintGray; - } - - Item { - id: yourPrivateKeysContainer; - anchors.top: privateKeysSeparator.bottom; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - anchors.bottom: parent.bottom; - - onVisibleChanged: { - if (visible) { - Commerce.getKeyFilePathIfExists(); - } - } - - HiFiGlyphs { - id: yourPrivateKeysImage; - text: hifi.glyphs.walletKey; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 20; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - RalewaySemiBold { - id: yourPrivateKeysText; - text: "Private Keys"; - size: 18; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 32; - anchors.left: yourPrivateKeysImage.right; - anchors.leftMargin: 30; - anchors.right: parent.right; - height: 30; - // Style - color: hifi.colors.white; - } - - // Text below "private keys" - RalewayRegular { - id: explanitoryText; - text: "Your money and purchases are secured with private keys that only you have access to."; - // Text size - size: 18; - // Anchors - anchors.top: yourPrivateKeysText.bottom; - anchors.topMargin: 10; - anchors.left: yourPrivateKeysText.left; - anchors.right: yourPrivateKeysText.right; - height: paintedHeight; - // Style - color: hifi.colors.white; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - } - - Rectangle { - id: removeHmdContainer; - z: 998; - visible: false; - - gradient: Gradient { - GradientStop { - position: 0.2; - color: hifi.colors.baseGrayHighlight; - } - GradientStop { - position: 1.0; - color: hifi.colors.baseGrayShadow; - } - } - anchors.fill: backupInstructionsButton; - radius: 5; - MouseArea { - anchors.fill: parent; - propagateComposedEvents: false; - hoverEnabled: true; - } - - RalewayBold { - anchors.fill: parent; - text: "INSTRUCTIONS OPEN ON DESKTOP"; - size: 15; - color: hifi.colors.white; - verticalAlignment: Text.AlignVCenter; - horizontalAlignment: Text.AlignHCenter; - } - - Timer { - id: removeHmdContainerTimer; - interval: 5000; - onTriggered: removeHmdContainer.visible = false - } - } - - HifiControlsUit.Button { - id: backupInstructionsButton; - text: "View Backup Instructions"; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.left: explanitoryText.left; - anchors.right: explanitoryText.right; - anchors.top: explanitoryText.bottom; - anchors.topMargin: 16; - height: 40; - - onClicked: { - var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); - Qt.openUrlExternally(keyPath + "/backup_instructions.html"); - Qt.openUrlExternally(keyPath); - removeHmdContainer.visible = true; - removeHmdContainerTimer.start(); - } - } - } - */ } // From b7d074aaa4e23efe8c50c5f5c5b23f6c35bbc764 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 23 Aug 2018 10:41:19 -0700 Subject: [PATCH 23/29] remove comment --- interface/resources/qml/hifi/commerce/wallet/Security.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index da3331ebfc..e021328ebe 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -90,7 +90,7 @@ Item { Item { id: changeSecurityImageContainer; - anchors.top: securityTextSeparator.bottom; // changePassphraseSeparator.bottom; + anchors.top: securityTextSeparator.bottom; anchors.topMargin: 8; anchors.left: parent.left; anchors.leftMargin: 40; From e3d03cd321734d99060abf4c48b881a3ed7613ea Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 23 Aug 2018 13:16:02 -0700 Subject: [PATCH 24/29] Fix MS17738: Fix a case where the BLAST icon didn't show up in Snapshot Review --- scripts/system/snapshot.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 3e58bc61ad..37270f896e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -352,7 +352,7 @@ function fillImageDataFromPrevious() { containsGif: previousAnimatedSnapPath !== "", processingGif: false, shouldUpload: false, - canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"), + canBlast: snapshotDomainID === Settings.getValue("previousSnapshotDomainID"), isLoggedIn: isLoggedIn }; imageData = []; @@ -427,7 +427,7 @@ function snapshotUploaded(isError, reply) { } isUploadingPrintableStill = false; } -var href, domainID; +var href, snapshotDomainID; function takeSnapshot() { tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", @@ -452,8 +452,8 @@ function takeSnapshot() { // Even the domainID could change (e.g., if the user falls into a teleporter while recording). href = location.href; Settings.setValue("previousSnapshotHref", href); - domainID = location.domainID; - Settings.setValue("previousSnapshotDomainID", domainID); + snapshotDomainID = location.domainID; + Settings.setValue("previousSnapshotDomainID", snapshotDomainID); maybeDeleteSnapshotStories(); @@ -551,7 +551,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { HMD.openTablet(); - isDomainOpen(domainID, function (canShare) { + isDomainOpen(snapshotDomainID, function (canShare) { snapshotOptions = { containsGif: false, processingGif: false, @@ -594,7 +594,7 @@ function processingGifStarted(pathStillSnapshot) { HMD.openTablet(); - isDomainOpen(domainID, function (canShare) { + isDomainOpen(snapshotDomainID, function (canShare) { snapshotOptions = { containsGif: true, processingGif: true, @@ -622,7 +622,7 @@ function processingGifCompleted(pathAnimatedSnapshot) { Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); - isDomainOpen(domainID, function (canShare) { + isDomainOpen(snapshotDomainID, function (canShare) { snapshotOptions = { containsGif: true, processingGif: false, From 36784b0039058ce30e8d165e25379431ed37c766 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 23 Aug 2018 10:21:59 -0700 Subject: [PATCH 25/29] reset sent trait data when re-send required --- assignment-client/src/avatars/AvatarMixer.cpp | 23 ++++++++++++------- .../src/avatars/AvatarMixerClientData.cpp | 8 +++++++ .../src/avatars/AvatarMixerClientData.h | 2 ++ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 167c1cd29c..edbba20dc7 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -541,7 +541,8 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointersetLastBroadcastTime(node->getUUID(), 0); + nodeData->setLastBroadcastTime(node->getUUID(), 0); + nodeData->resetSentTraitData(node->getLocalID()); } ); } @@ -588,10 +589,10 @@ void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointergetMessage()) ); if (!avatarID.isNull()) { auto nodeList = DependencyManager::get(); - auto node = nodeList->nodeWithUUID(avatarID); - if (node) { - QMutexLocker lock(&node->getMutex()); - AvatarMixerClientData* avatarClientData = dynamic_cast(node->getLinkedData()); + auto requestedNode = nodeList->nodeWithUUID(avatarID); + + if (requestedNode) { + AvatarMixerClientData* avatarClientData = static_cast(requestedNode->getLinkedData()); if (avatarClientData) { const AvatarData& avatarData = avatarClientData->getAvatar(); QByteArray serializedAvatar = avatarData.identityByteArray(); @@ -600,6 +601,11 @@ void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointersendPacketList(std::move(identityPackets), *senderNode); ++_sumIdentityPackets; } + + AvatarMixerClientData* senderData = static_cast(senderNode->getLinkedData()); + if (senderData) { + senderData->resetSentTraitData(requestedNode->getLocalID()); + } } } } @@ -625,23 +631,24 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer while (message->getBytesLeftToRead()) { // parse out the UUID being ignored from the packet QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - - if (nodeList->nodeWithUUID(ignoredUUID)) { + auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID); + if (ignoredNode) { if (nodeData) { // Reset the lastBroadcastTime for the ignored avatar to 0 // so the AvatarMixer knows it'll have to send identity data about the ignored avatar // to the ignorer if the ignorer unignores. nodeData->setLastBroadcastTime(ignoredUUID, 0); + nodeData->resetSentTraitData(ignoredNode->getLocalID()); } // Reset the lastBroadcastTime for the ignorer (FROM THE PERSPECTIVE OF THE IGNORED) to 0 // so the AvatarMixer knows it'll have to send identity data about the ignorer // to the ignored if the ignorer unignores. - auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID); AvatarMixerClientData* ignoredNodeData = reinterpret_cast(ignoredNode->getLinkedData()); if (ignoredNodeData) { ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0); + ignoredNodeData->resetSentTraitData(senderNode->getLocalID()); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index b2490fc7b4..f524c071ec 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -228,6 +228,9 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble); } setLastBroadcastTime(other->getUUID(), 0); + + resetSentTraitData(other->getLocalID()); + DependencyManager::get()->sendPacket(std::move(killPacket), *self); } } @@ -238,6 +241,11 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, } } +void AvatarMixerClientData::resetSentTraitData(Node::LocalID nodeLocalID) { + _lastSentTraitsTimestamps[nodeLocalID] = TraitsCheckTimestamp(); + _sentTraitVersions[nodeLocalID].reset(); +} + void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { _currentViewFrustums.clear(); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 1c2694af48..a892455fe3 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -135,6 +135,8 @@ public: AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _sentTraitVersions[otherAvatar]; } + void resetSentTraitData(Node::LocalID nodeID); + private: struct PacketQueue : public std::queue> { QWeakPointer node; From 09065cf19aaa47eedbca871c3a5b7ae75b399e17 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 23 Aug 2018 10:37:41 -0700 Subject: [PATCH 26/29] provide assignment dynamic factory to agent --- assignment-client/src/Agent.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 06a14927d3..2f03f15da7 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -53,6 +53,7 @@ #include // TODO: consider moving to scriptengine.h #include "entities/AssignmentParentFinder.h" +#include "AssignmentDynamicFactory.h" #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" #include "AgentScriptingInterface.h" @@ -67,6 +68,9 @@ Agent::Agent(ReceivedMessage& message) : { DependencyManager::set(); + DependencyManager::registerInheritance(); + DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(false); @@ -860,6 +864,8 @@ void Agent::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); From e1879bf26ac5bd87996ed5d701a9ea69482aa3a8 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 23 Aug 2018 13:55:17 -0700 Subject: [PATCH 27/29] add assignment dynamic factory to ESS --- assignment-client/src/scripts/EntityScriptServer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 586931d403..272985093c 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -33,6 +33,7 @@ #include // for EntityScriptServerServices +#include "../AssignmentDynamicFactory.h" #include "EntityScriptServerLogging.h" #include "../entities/AssignmentParentFinder.h" @@ -56,6 +57,9 @@ int EntityScriptServer::_entitiesScriptEngineCount = 0; EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { qInstallMessageHandler(messageHandler); + DependencyManager::registerInheritance(); + DependencyManager::set(); + DependencyManager::set(false)->setPacketSender(&_entityEditSender); DependencyManager::set(); @@ -579,6 +583,7 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer mess void EntityScriptServer::aboutToFinish() { shutdownScriptEngine(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::get()->cleanup(); From a1d88a8588184de8983ee30fa347aad294f660eb Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 23 Aug 2018 14:45:16 -0700 Subject: [PATCH 28/29] Merge howard/wallet-locker2 into my branch --- .../qml/hifi/commerce/wallet/Help.qml | 51 +---- .../qml/hifi/commerce/wallet/Security.qml | 206 +----------------- .../qml/hifi/commerce/wallet/Wallet.qml | 15 ++ interface/src/commerce/Ledger.cpp | 36 ++- interface/src/commerce/Ledger.h | 3 +- interface/src/commerce/Wallet.cpp | 61 +++++- interface/src/commerce/Wallet.h | 4 + 7 files changed, 113 insertions(+), 263 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index b453509712..6d8fc3c33f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -63,47 +63,6 @@ Item { question: "How can I get HFC?"; answer: "High Fidelity commerce is in open beta right now. Want more HFC? \ Get it by going to

BankOfHighFidelity. and meeting with the banker!"; - } - ListElement { - isExpanded: false; - question: "What are private keys and where are they stored?"; - answer: - "A private key is a secret piece of text that is used to prove ownership, unlock confidential information, and sign transactions. \ -In High Fidelity, your private key is used to securely access the contents of your Wallet and Purchases. \ -After wallet setup, a hifikey file is stored on your computer in High Fidelity Interface's AppData directory. \ -Your hifikey file contains your private key and is protected by your wallet passphrase. \ -

It is very important to back up your hifikey file! \ -Tap here to open the folder where your HifiKeys are stored on your main display." - } - ListElement { - isExpanded: false; - question: "How do I back up my private keys?"; - answer: "You can back up your hifikey file (which contains your private key and is encrypted using your wallet passphrase) by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. \ -Restore your hifikey file by replacing the file in Interface's AppData directory with your backup copy. \ -Others with access to your back up should not be able to spend your HFC without your passphrase. \ -Tap here to open the folder where your HifiKeys are stored on your main display."; - } - ListElement { - isExpanded: false; - question: "What happens if I lose my private keys?"; - answer: "We cannot stress enough that you should keep a backup! For security reasons, High Fidelity does not keep a copy, and cannot restore it for you. \ -If you lose your private key, you will no longer have access to the contents of your Wallet or My Purchases. \ -Here are some things to try:
    \ -
  • If you have backed up your hifikey file before, search your backup location
  • \ -
  • Search your AppData directory in the last machine you used to set up the Wallet
  • \ -
  • If you are a developer and have installed multiple builds of High Fidelity, your hifikey file might be in another folder
  • \ -


As a last resort, you can set up your Wallet again and generate a new hifikey file. \ -Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over."; - } - ListElement { - isExpanded: false; - question: "What if I forget my wallet passphrase?"; - answer: "Your wallet passphrase is used to encrypt your private keys. Please write it down and store it securely! \ -

If you forget your passphrase, you will no longer be able to decrypt the hifikey file that the passphrase protects. \ -You will also no longer have access to the contents of your Wallet or My Purchases. \ -For security reasons, High Fidelity does not keep a copy of your passphrase, and can't restore it for you. \ -

If you still cannot remember your wallet passphrase, you can set up your Wallet again and generate a new hifikey file. \ -Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over."; } ListElement { isExpanded: false; @@ -114,11 +73,9 @@ In your Wallet's Send Money tab, choose from your list of connections, or choose ListElement { isExpanded: false; question: "What is a Security Pic?" - answer: "Your Security Pic is an encrypted image that you select during Wallet Setup. \ -It acts as an extra layer of Wallet security. \ -When you see your Security Pic, you know that your actions and data are securely making use of your private keys.\ -

Don't enter your passphrase anywhere that doesn't display your Security Pic! \ -If you don't see your Security Pic on a page that requests your Wallet passphrase, someone untrustworthy may be trying to access your Wallet."; + answer: "Your Security Pic acts as an extra layer of Wallet security. \ +When you see your Security Pic, you know that your actions and data are securely making use of your account. \ +

Tap here to change your Security Pic."; } ListElement { isExpanded: false; @@ -260,6 +217,8 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta } } else if (link === "#support") { Qt.openUrlExternally("mailto:support@highfidelity.com"); + } else if (link === "#securitypic") { + sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 216d621bf8..e021328ebe 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -88,76 +88,9 @@ Item { color: hifi.colors.faintGray; } - Item { - id: changePassphraseContainer; - anchors.top: securityTextSeparator.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - height: 75; - - HiFiGlyphs { - id: changePassphraseImage; - text: hifi.glyphs.passphrase; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - RalewaySemiBold { - text: "Passphrase"; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: changePassphraseImage.right; - anchors.leftMargin: 30; - width: 50; - // Text size - size: 18; - // Style - color: hifi.colors.white; - } - - // "Change Passphrase" button - HifiControlsUit.Button { - id: changePassphraseButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - width: 140; - height: 40; - text: "Change"; - onClicked: { - sendSignalToWallet({method: 'walletSecurity_changePassphrase'}); - } - } - } - - Rectangle { - id: changePassphraseSeparator; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: changePassphraseContainer.bottom; - anchors.topMargin: 8; - // Style - color: hifi.colors.faintGray; - } - Item { id: changeSecurityImageContainer; - anchors.top: changePassphraseSeparator.bottom; + anchors.top: securityTextSeparator.bottom; anchors.topMargin: 8; anchors.left: parent.left; anchors.leftMargin: 40; @@ -207,143 +140,6 @@ Item { } } } - - Rectangle { - id: privateKeysSeparator; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: changeSecurityImageContainer.bottom; - anchors.topMargin: 8; - // Style - color: hifi.colors.faintGray; - } - - Item { - id: yourPrivateKeysContainer; - anchors.top: privateKeysSeparator.bottom; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - anchors.bottom: parent.bottom; - - onVisibleChanged: { - if (visible) { - Commerce.getKeyFilePathIfExists(); - } - } - - HiFiGlyphs { - id: yourPrivateKeysImage; - text: hifi.glyphs.walletKey; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 20; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - RalewaySemiBold { - id: yourPrivateKeysText; - text: "Private Keys"; - size: 18; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 32; - anchors.left: yourPrivateKeysImage.right; - anchors.leftMargin: 30; - anchors.right: parent.right; - height: 30; - // Style - color: hifi.colors.white; - } - - // Text below "private keys" - RalewayRegular { - id: explanitoryText; - text: "Your money and purchases are secured with private keys that only you have access to."; - // Text size - size: 18; - // Anchors - anchors.top: yourPrivateKeysText.bottom; - anchors.topMargin: 10; - anchors.left: yourPrivateKeysText.left; - anchors.right: yourPrivateKeysText.right; - height: paintedHeight; - // Style - color: hifi.colors.white; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - } - - Rectangle { - id: removeHmdContainer; - z: 998; - visible: false; - - gradient: Gradient { - GradientStop { - position: 0.2; - color: hifi.colors.baseGrayHighlight; - } - GradientStop { - position: 1.0; - color: hifi.colors.baseGrayShadow; - } - } - anchors.fill: backupInstructionsButton; - radius: 5; - MouseArea { - anchors.fill: parent; - propagateComposedEvents: false; - hoverEnabled: true; - } - - RalewayBold { - anchors.fill: parent; - text: "INSTRUCTIONS OPEN ON DESKTOP"; - size: 15; - color: hifi.colors.white; - verticalAlignment: Text.AlignVCenter; - horizontalAlignment: Text.AlignHCenter; - } - - Timer { - id: removeHmdContainerTimer; - interval: 5000; - onTriggered: removeHmdContainer.visible = false - } - } - - HifiControlsUit.Button { - id: backupInstructionsButton; - text: "View Backup Instructions"; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.left: explanitoryText.left; - anchors.right: explanitoryText.right; - anchors.top: explanitoryText.bottom; - anchors.topMargin: 16; - height: 40; - - onClicked: { - var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); - Qt.openUrlExternally(keyPath + "/backup_instructions.html"); - Qt.openUrlExternally(keyPath); - removeHmdContainer.visible = true; - removeHmdContainerTimer.start(); - } - } - } } // diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 603d7fb676..ffd06cb4a8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -399,6 +399,9 @@ Rectangle { onSendSignalToWallet: { if (msg.method === 'walletReset' || msg.method === 'passphraseReset') { sendToScript(msg); + } else if (msg.method === 'walletSecurity_changeSecurityImage') { + securityImageChange.initModel(); + root.activeView = "securityImageChange"; } } } @@ -803,12 +806,24 @@ Rectangle { } function walletResetSetup() { + /* Bypass all this and do it automatically root.activeView = "walletSetup"; var timestamp = new Date(); walletSetup.startingTimestamp = timestamp; walletSetup.setupAttemptID = generateUUID(); UserActivityLogger.commerceWalletSetupStarted(timestamp, walletSetup.setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app", (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : '')); + */ + + var randomNumber = Math.floor(Math.random() * 34) + 1; + var securityImagePath = "images/" + addLeadingZero(randomNumber) + ".jpg"; + Commerce.getWalletAuthenticatedStatus(); // before writing security image, ensures that salt/account password is set. + Commerce.chooseSecurityImage(securityImagePath); + Commerce.generateKeyPair(); + } + + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; } function followReferrer(msg) { diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 702251f867..67303f2a9b 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -31,7 +31,9 @@ QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) { QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object(); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); +#endif return data; } // Non-200 responses are not json: @@ -69,7 +71,9 @@ void Ledger::send(const QString& endpoint, const QString& success, const QString auto accountManager = DependencyManager::get(); const QString URL = "/api/v1/commerce/"; JSONCallbackParameters callbackParams(this, success, fail); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact); +#endif accountManager->sendRequest(URL + endpoint, authType, method, @@ -117,7 +121,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure); } -bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { +bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker) { auto accountManager = DependencyManager::get(); if (!accountManager->isLoggedIn()) { qCWarning(commerce) << "Cannot set receiveAt when not logged in."; @@ -125,11 +129,25 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { emit receiveAtResult(result); return false; // We know right away that we will fail, so tell the caller. } - - signedSend("public_key", hfc_key.toUtf8(), signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["locker"] = QString::fromUtf8(locker); + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("text", transactionString, signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } +bool Ledger::receiveAt() { + auto wallet = DependencyManager::get(); + auto keys = wallet->listPublicKeys(); + if (keys.isEmpty()) { + return false; + } + auto key = keys.first(); + return receiveAt(key, key, wallet->getWallet()); +} + void Ledger::balance(const QStringList& keys) { keysQuery("balance", "balanceSuccess", "balanceFailure"); } @@ -283,24 +301,30 @@ void Ledger::accountSuccess(QNetworkReply* reply) { auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8()); auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8()); QString remotePublicKey = data["public_key"].toString(); + const QByteArray locker = data["locker"].toString().toUtf8(); bool isOverride = wallet->wasSoftReset(); wallet->setSalt(salt); wallet->setIv(iv); wallet->setCKey(ckey); + if (!locker.isEmpty()) { + wallet->setWallet(locker); + wallet->setPassphrase("ACCOUNT"); // We only locker wallets that have been converted to account-based auth. + } QString keyStatus = "ok"; QStringList localPublicKeys = wallet->listPublicKeys(); if (remotePublicKey.isEmpty() || isOverride) { - if (!localPublicKeys.isEmpty()) { - QString key = localPublicKeys.first(); - receiveAt(key, key); + if (!localPublicKeys.isEmpty()) { // Let the metaverse know about a local wallet. + receiveAt(); } } else { if (localPublicKeys.isEmpty()) { keyStatus = "preexisting"; } else if (localPublicKeys.first() != remotePublicKey) { keyStatus = "conflicting"; + } else if (locker.isEmpty()) { // Matches metaverse data, but we haven't lockered it yet. + receiveAt(); } } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index ba2f167f4b..427395ee11 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -26,7 +26,8 @@ class Ledger : public QObject, public Dependency { public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); - bool receiveAt(const QString& hfc_key, const QString& signing_key); + bool receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker); + bool receiveAt(); void balance(const QStringList& keys); void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage); void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index ef6b4654f5..5b8417be7c 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -131,7 +131,7 @@ bool Wallet::writeBackupInstructions() { QFile outputFile(outputFilename); bool retval = false; - if (getKeyFilePath() == "") + if (getKeyFilePath().isEmpty()) { return false; } @@ -190,6 +190,30 @@ bool writeKeys(const char* filename, EC_KEY* keys) { return retval; } +bool Wallet::setWallet(const QByteArray& wallet) { + QFile file(keyFilePath()); + if (!file.open(QIODevice::WriteOnly)) { + qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath(); + return false; + } + if (file.write(wallet) != wallet.count()) { + qCCritical(commerce) << "Unable to write wallet in" << keyFilePath(); + return false; + } + file.close(); + return true; +} +QByteArray Wallet::getWallet() { + QFile file(keyFilePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCInfo(commerce) << "No existing wallet in" << keyFilePath(); + return QByteArray(); + } + QByteArray wallet = file.readAll(); + file.close(); + return wallet; +} + QPair generateECKeypair() { EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1); @@ -334,7 +358,7 @@ Wallet::Wallet() { uint status; QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : ""; - if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { + if (wallet->getKeyFilePath().isEmpty() || !wallet->getSecurityImage()) { if (keyStatus == "preexisting") { status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING; } else{ @@ -524,15 +548,23 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() { // FIXME: initialize OpenSSL elsewhere soon initialize(); + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: checking" << (!_passphrase || !_passphrase->isEmpty()); // this should always be false if we don't have a passphrase // cached yet if (!_passphrase || _passphrase->isEmpty()) { - return false; + if (!getKeyFilePath().isEmpty()) { // If file exists, then it is an old school file that has not been lockered. Must get user's passphrase. + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: No passphrase, but there is an existing wallet."; + return false; + } else { + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: New setup."; + setPassphrase("ACCOUNT"); // Going forward, consider this an account-based client. + } } if (_publicKeys.count() > 0) { // we _must_ be authenticated if the publicKeys are there DependencyManager::get()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY); + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet was ready"; return true; } @@ -545,10 +577,15 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() { // be sure to add the public key so we don't do this over and over _publicKeys.push_back(publicKey.toBase64()); + + if (*_passphrase != "ACCOUNT") { + changePassphrase("ACCOUNT"); // Rewrites with salt and constant, and will be lockered that way. + } + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet now ready"; return true; } } - + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet not ready"; return false; } @@ -559,6 +596,7 @@ bool Wallet::generateKeyPair() { qCInfo(commerce) << "Generating keypair."; auto keyPair = generateECKeypair(); if (!keyPair.first) { + qCWarning(commerce) << "Empty keypair"; return false; } @@ -576,7 +614,7 @@ bool Wallet::generateKeyPair() { // 2. It is maximally private, and we can step back from that later if desired. // 3. It maximally exercises all the machinery, so we are most likely to surface issues now. auto ledger = DependencyManager::get(); - return ledger->receiveAt(key, key); + return ledger->receiveAt(key, key, getWallet()); } QStringList Wallet::listPublicKeys() { @@ -666,11 +704,13 @@ void Wallet::chooseSecurityImage(const QString& filename) { // there _is_ a keyfile, we need to update it (similar to changing the // passphrase, we need to do so into a temp file and move it). if (!QFile(keyFilePath()).exists()) { + qCDebug(commerce) << "initial security pic set for empty wallet"; emit securityImageResult(true); return; } bool success = writeWallet(); + qCDebug(commerce) << "updated security pic" << success; emit securityImageResult(success); } @@ -715,6 +755,11 @@ QString Wallet::getKeyFilePath() { bool Wallet::writeWallet(const QString& newPassphrase) { EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str()); + auto ledger = DependencyManager::get(); + // Remove any existing locker, because it will be out of date. + if (!_publicKeys.isEmpty() && !ledger->receiveAt(_publicKeys.first(), _publicKeys.first(), QByteArray())) { + return false; // FIXME: receiveAt could fail asynchronously. + } if (keys) { // we read successfully, so now write to a new temp file QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); @@ -722,6 +767,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) { if (!newPassphrase.isEmpty()) { setPassphrase(newPassphrase); } + if (writeKeys(tempFileName.toStdString().c_str(), keys)) { if (writeSecurityImage(_securityImage, tempFileName)) { // ok, now move the temp file to the correct spot @@ -729,6 +775,11 @@ bool Wallet::writeWallet(const QString& newPassphrase) { QFile(tempFileName).rename(QString(keyFilePath())); qCDebug(commerce) << "wallet written successfully"; emit keyFilePathIfExistsResult(getKeyFilePath()); + if (!walletIsAuthenticatedWithPassphrase() || !ledger->receiveAt()) { + // FIXME: Should we fail the whole operation? + // Tricky, because we'll need the the key and file from the TEMP location... + qCWarning(commerce) << "Failed to update locker"; + } return true; } else { qCDebug(commerce) << "couldn't write security image to temp wallet"; diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 665afd9a23..c096713058 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -73,6 +73,7 @@ private slots: void handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: + friend class Ledger; QStringList _publicKeys{}; QPixmap* _securityImage { nullptr }; QByteArray _salt; @@ -87,6 +88,9 @@ private: bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); bool writeBackupInstructions(); + bool setWallet(const QByteArray& wallet); + QByteArray getWallet(); + void account(); }; From dddb2141f041422c0b9365be84480003f28037c1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 23 Aug 2018 15:45:29 -0700 Subject: [PATCH 29/29] Implement Wallet Security feature - Auto Logout checkbox --- .../qml/LoginDialog/LinkAccountBody.qml | 1 + .../qml/hifi/commerce/wallet/Security.qml | 75 +++++++++++++++++++ .../qml/hifi/commerce/wallet/Wallet.qml | 11 +++ interface/src/Application.cpp | 6 ++ scripts/system/commerce/wallet.js | 7 ++ 5 files changed, 100 insertions(+) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 814778a4b1..4c6e5f6fce 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -120,6 +120,7 @@ Item { TextField { id: usernameField + text: Settings.getValue("wallet/savedUsername", ""); width: parent.width focus: true label: "Username or Email" diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index e021328ebe..14ac696ef7 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -140,6 +140,81 @@ Item { } } } + + Item { + id: autoLogoutContainer; + anchors.top: changeSecurityImageContainer.bottom; + anchors.topMargin: 8; + anchors.left: parent.left; + anchors.leftMargin: 40; + anchors.right: parent.right; + anchors.rightMargin: 55; + height: 75; + + HiFiGlyphs { + id: autoLogoutImage; + text: hifi.glyphs.walletKey; + // Size + size: 80; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 20; + anchors.left: parent.left; + // Style + color: hifi.colors.white; + } + + HifiControlsUit.CheckBox { + id: autoLogoutCheckbox; + checked: Settings.getValue("wallet/autoLogout", false); + text: "Automatically Log Out when Exiting Interface" + // Anchors + anchors.verticalCenter: autoLogoutImage.verticalCenter; + anchors.left: autoLogoutImage.right; + anchors.leftMargin: 20; + anchors.right: autoLogoutHelp.left; + anchors.rightMargin: 12; + boxSize: 28; + labelFontSize: 18; + color: hifi.colors.white; + onCheckedChanged: { + Settings.setValue("wallet/autoLogout", checked); + if (checked) { + Settings.setValue("wallet/savedUsername", Account.username); + } else { + Settings.setValue("wallet/savedUsername", ""); + } + } + } + + RalewaySemiBold { + id: autoLogoutHelp; + text: '[?]'; + // Anchors + anchors.verticalCenter: autoLogoutImage.verticalCenter; + anchors.right: parent.right; + width: 30; + height: 30; + // Text size + size: 18; + // Style + color: hifi.colors.blueHighlight; + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.color = hifi.colors.blueAccent; + } + onExited: { + parent.color = hifi.colors.blueHighlight; + } + onClicked: { + sendSignalToWallet({method: 'walletSecurity_autoLogoutHelp'}); + } + } + } + } } // diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index ffd06cb4a8..65d98af234 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -382,6 +382,17 @@ Rectangle { } else if (msg.method === 'walletSecurity_changeSecurityImage') { securityImageChange.initModel(); root.activeView = "securityImageChange"; + } else if (msg.method === 'walletSecurity_autoLogoutHelp') { + lightboxPopup.titleText = "Automatically Log Out"; + lightboxPopup.bodyText = "By default, after you log in to High Fidelity, you will stay logged in to your High Fidelity " + + "account even after you close and re-open Interface. This means anyone who opens Interface on your computer " + + "could make purchases with your Wallet.\n\n" + + "If you do not want to stay logged in across Interface sessions, check this box."; + lightboxPopup.button1text = "CLOSE"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.visible = true; } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1d515392b0..60d23c94dd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -375,6 +375,7 @@ static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop"; static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin"; static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; +static const QString AUTO_LOGOUT_SETTING_NAME = "wallet/autoLogout"; const std::vector> Application::_acceptedExtensions { { SVO_EXTENSION, &Application::importSVOFromURL }, @@ -1730,6 +1731,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QTimer* settingsTimer = new QTimer(); moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{ connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{ + bool autoLogout = Setting::Handle(AUTO_LOGOUT_SETTING_NAME, false).get(); + if (autoLogout) { + auto accountManager = DependencyManager::get(); + accountManager->logout(); + } // Disconnect the signal from the save settings QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings); // Stop the settings timer diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index ac25269e41..8730273e7c 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -406,6 +406,11 @@ sendMoneyRecipient = null; } + function onUsernameChanged() { + Settings.setValue("wallet/autoLogout", false); + Settings.setValue("wallet/savedUsername", ""); + } + // Function Name: fromQml() // // Description: @@ -581,6 +586,7 @@ var tablet = null; var walletEnabled = Settings.getValue("commerce", true); function startup() { + GlobalServices.myUsernameChanged.connect(onUsernameChanged); if (walletEnabled) { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ @@ -612,6 +618,7 @@ removeOverlays(); } function shutdown() { + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); button.clicked.disconnect(onButtonClicked); tablet.removeButton(button); deleteSendMoneyParticleEffect();