Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Artur Gomes 2017-08-16 21:00:29 +01:00
commit 8ef8e974b6
18 changed files with 697 additions and 78 deletions

View file

@ -183,6 +183,12 @@ if (WIN32)
add_dependency_external_projects(steamworks) add_dependency_external_projects(steamworks)
endif() endif()
# include OPENSSL
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
# append OpenSSL to our list of libraries to link
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
# disable /OPT:REF and /OPT:ICF for the Debug builds # disable /OPT:REF and /OPT:ICF for the Debug builds
# This will prevent the following linker warnings # This will prevent the following linker warnings
# LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification # LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification

View file

@ -66,6 +66,14 @@ Rectangle {
} }
} }
} }
onSecurityImageResult: {
securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
}
}
SecurityImageSelection {
id: securityImageSelection;
referrerURL: checkoutRoot.itemHref;
} }
// //
@ -80,6 +88,20 @@ Rectangle {
anchors.left: parent.left; anchors.left: parent.left;
anchors.top: parent.top; anchors.top: parent.top;
// Security Image
Image {
id: securityImage;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.leftMargin: 16;
height: parent.height - 5;
width: height;
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
}
// Title Bar text // Title Bar text
RalewaySemiBold { RalewaySemiBold {
id: titleBarText; id: titleBarText;
@ -87,8 +109,11 @@ Rectangle {
// Text size // Text size
size: hifi.fontSizes.overlayTitle; size: hifi.fontSizes.overlayTitle;
// Anchors // Anchors
anchors.fill: parent; anchors.top: parent.top;
anchors.left: securityImage.right;
anchors.leftMargin: 16; anchors.leftMargin: 16;
anchors.bottom: parent.bottom;
width: paintedWidth;
// Style // Style
color: hifi.colors.lightGrayText; color: hifi.colors.lightGrayText;
// Alignment // Alignment
@ -381,7 +406,7 @@ Rectangle {
// "Buy" button // "Buy" button
HifiControlsUit.Button { HifiControlsUit.Button {
id: buyButton; id: buyButton;
enabled: balanceAfterPurchase >= 0 && !alreadyOwned && inventoryReceived && balanceReceived; enabled: balanceAfterPurchase >= 0 && inventoryReceived && balanceReceived;
color: hifi.buttons.black; color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark; colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top; anchors.top: parent.top;
@ -391,9 +416,16 @@ Rectangle {
anchors.right: parent.right; anchors.right: parent.right;
anchors.rightMargin: 20; anchors.rightMargin: 20;
width: parent.width/2 - anchors.rightMargin*2; width: parent.width/2 - anchors.rightMargin*2;
text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned" : "Buy") : "--"; text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--";
onClicked: { onClicked: {
if (!alreadyOwned) {
commerce.buy(itemId, parseInt(itemPriceText.text)); commerce.buy(itemId, parseInt(itemPriceText.text));
} else {
if (urlHandler.canHandleUrl(itemHref)) {
urlHandler.handleUrl(itemHref);
}
sendToScript({method: 'checkout_buySuccess', itemId: itemId});
}
} }
} }
} }
@ -427,6 +459,7 @@ Rectangle {
itemHref = message.params.itemHref; itemHref = message.params.itemHref;
commerce.balance(); commerce.balance();
commerce.inventory(); commerce.inventory();
commerce.getSecurityImage();
break; break;
default: default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));

View file

@ -43,6 +43,14 @@ Rectangle {
inventoryContentsList.model = inventory.assets; inventoryContentsList.model = inventory.assets;
} }
} }
onSecurityImageResult: {
securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
}
}
SecurityImageSelection {
id: securityImageSelection;
referrerURL: inventoryRoot.referrerURL;
} }
// //
@ -51,12 +59,26 @@ Rectangle {
Item { Item {
id: titleBarContainer; id: titleBarContainer;
// Size // Size
width: inventoryRoot.width; width: parent.width;
height: 50; height: 50;
// Anchors // Anchors
anchors.left: parent.left; anchors.left: parent.left;
anchors.top: parent.top; anchors.top: parent.top;
// Security Image
Image {
id: securityImage;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.leftMargin: 16;
height: parent.height - 5;
width: height;
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
}
// Title Bar text // Title Bar text
RalewaySemiBold { RalewaySemiBold {
id: titleBarText; id: titleBarText;
@ -64,8 +86,11 @@ Rectangle {
// Text size // Text size
size: hifi.fontSizes.overlayTitle; size: hifi.fontSizes.overlayTitle;
// Anchors // Anchors
anchors.fill: parent; anchors.top: parent.top;
anchors.left: securityImage.right;
anchors.leftMargin: 16; anchors.leftMargin: 16;
anchors.bottom: parent.bottom;
width: paintedWidth;
// Style // Style
color: hifi.colors.lightGrayText; color: hifi.colors.lightGrayText;
// Alignment // Alignment
@ -73,6 +98,25 @@ Rectangle {
verticalAlignment: Text.AlignVCenter; verticalAlignment: Text.AlignVCenter;
} }
// "Change Security Image" button
HifiControlsUit.Button {
id: changeSecurityImageButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
anchors.topMargin: 3;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 3;
anchors.right: parent.right;
anchors.rightMargin: 20;
width: 200;
text: "Change Security Image"
onClicked: {
securityImageSelection.isManuallyChangingSecurityImage = true;
securityImageSelection.visible = true;
}
}
// Separator // Separator
HifiControlsUit.Separator { HifiControlsUit.Separator {
anchors.left: parent.left; anchors.left: parent.left;
@ -166,6 +210,7 @@ Rectangle {
} }
ListView { ListView {
id: inventoryContentsList; id: inventoryContentsList;
clip: true;
// Anchors // Anchors
anchors.top: inventoryContentsLabel.bottom; anchors.top: inventoryContentsLabel.bottom;
anchors.topMargin: 8; anchors.topMargin: 8;
@ -262,6 +307,7 @@ Rectangle {
referrerURL = message.referrerURL; referrerURL = message.referrerURL;
commerce.balance(); commerce.balance();
commerce.inventory(); commerce.inventory();
commerce.getSecurityImage();
break; break;
default: default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));

View file

@ -0,0 +1,42 @@
//
// SecurityImageModel.qml
// qml/hifi/commerce
//
// SecurityImageModel
//
// Created by Zach Fox on 2017-08-15
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
ListModel {
id: root;
ListElement{
sourcePath: "images/01cat.jpg"
securityImageEnumValue: 1;
}
ListElement{
sourcePath: "images/02car.jpg"
securityImageEnumValue: 2;
}
ListElement{
sourcePath: "images/03dog.jpg"
securityImageEnumValue: 3;
}
ListElement{
sourcePath: "images/04stars.jpg"
securityImageEnumValue: 4;
}
ListElement{
sourcePath: "images/05plane.jpg"
securityImageEnumValue: 5;
}
ListElement{
sourcePath: "images/06gingerbread.jpg"
securityImageEnumValue: 6;
}
}

View file

@ -0,0 +1,271 @@
//
// SecurityImageSelection.qml
// qml/hifi/commerce
//
// SecurityImageSelection
//
// Created by Zach Fox on 2017-08-15
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
// references XXX from root context
Rectangle {
HifiConstants { id: hifi; }
id: securityImageSelectionRoot;
property string referrerURL: "";
property bool isManuallyChangingSecurityImage: false;
anchors.fill: parent;
// Style
color: hifi.colors.baseGray;
z:999; // On top of everything else
visible: false;
Hifi.QmlCommerce {
id: commerce;
onSecurityImageResult: {
if (!isManuallyChangingSecurityImage) {
securityImageSelectionRoot.visible = (imageID == 0);
}
if (imageID > 0) {
for (var itr = 0; itr < gridModel.count; itr++) {
var thisValue = gridModel.get(itr).securityImageEnumValue;
if (thisValue === imageID) {
securityImageGrid.currentIndex = itr;
break;
}
}
}
}
}
Component.onCompleted: {
commerce.getSecurityImage();
}
//
// TITLE BAR START
//
Item {
id: titleBarContainer;
// Size
width: securityImageSelectionRoot.width;
height: 30;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
id: titleBarText;
text: "Select a Security Image";
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.fill: parent;
anchors.leftMargin: 16;
// Style
color: hifi.colors.lightGrayText;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// Separator
HifiControlsUit.Separator {
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
}
}
//
// TITLE BAR END
//
//
// EXPLANATION START
//
Item {
id: explanationContainer;
// Size
width: securityImageSelectionRoot.width;
height: 85;
// Anchors
anchors.top: titleBarContainer.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
RalewayRegular {
id: explanationText;
text: "This image will be displayed on secure Inventory and Marketplace Checkout dialogs.<b><br>If you don't see your selected image on these dialogs, do not use them!</b>";
// Text size
size: 16;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
// Style
color: hifi.colors.lightGrayText;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// Separator
HifiControlsUit.Separator {
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
}
}
//
// EXPLANATION END
//
//
// SECURITY IMAGE GRID START
//
Item {
id: securityImageGridContainer;
// Anchors
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.top: explanationContainer.bottom;
anchors.topMargin: 8;
anchors.bottom: actionButtonsContainer.top;
anchors.bottomMargin: 8;
SecurityImageModel {
id: gridModel;
}
GridView {
id: securityImageGrid;
clip: true;
// Anchors
anchors.fill: parent;
currentIndex: -1;
cellWidth: width / 2;
cellHeight: height / 3;
model: gridModel;
delegate: Item {
width: securityImageGrid.cellWidth;
height: securityImageGrid.cellHeight;
Item {
anchors.fill: parent;
Image {
width: parent.width - 8;
height: parent.height - 8;
source: sourcePath;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
}
}
MouseArea {
anchors.fill: parent;
onClicked: {
securityImageGrid.currentIndex = index;
}
}
}
highlight: Rectangle {
width: securityImageGrid.cellWidth;
height: securityImageGrid.cellHeight;
color: hifi.colors.blueHighlight;
}
}
}
//
// SECURITY IMAGE GRID END
//
//
// ACTION BUTTONS START
//
Item {
id: actionButtonsContainer;
// Size
width: securityImageSelectionRoot.width;
height: 40;
// Anchors
anchors.left: parent.left;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 8;
// "Cancel" button
HifiControlsUit.Button {
id: cancelButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
anchors.topMargin: 3;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 3;
anchors.left: parent.left;
anchors.leftMargin: 20;
width: parent.width/2 - anchors.leftMargin*2;
text: "Cancel"
onClicked: {
if (!securityImageSelectionRoot.isManuallyChangingSecurityImage) {
sendToScript({method: 'securityImageSelection_cancelClicked', referrerURL: securityImageSelectionRoot.referrerURL});
} else {
securityImageSelectionRoot.visible = false;
}
}
}
// "Confirm" button
HifiControlsUit.Button {
id: confirmButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
anchors.topMargin: 3;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 3;
anchors.right: parent.right;
anchors.rightMargin: 20;
width: parent.width/2 - anchors.rightMargin*2;
text: "Confirm";
onClicked: {
securityImageSelectionRoot.isManuallyChangingSecurityImage = false;
commerce.chooseSecurityImage(gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue);
}
}
}
//
// ACTION BUTTONS END
//
//
// FUNCTION DEFINITIONS START
//
signal sendToScript(var message);
function getImagePathFromImageID(imageID) {
return (imageID ? gridModel.get(imageID - 1).sourcePath : "");
}
//
// FUNCTION DEFINITIONS END
//
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View file

@ -24,12 +24,12 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
transaction["inventory_key"] = inventory_key; transaction["inventory_key"] = inventory_key;
transaction["inventory_buyer_username"] = buyerUsername; transaction["inventory_buyer_username"] = buyerUsername;
QJsonDocument transactionDoc{ transaction }; QJsonDocument transactionDoc{ transaction };
QString transactionString = transactionDoc.toJson(QJsonDocument::Compact); auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
auto wallet = DependencyManager::get<Wallet>(); auto wallet = DependencyManager::get<Wallet>();
QString signature = wallet->signWithKey(transactionString, hfc_key); QString signature = wallet->signWithKey(transactionString, hfc_key);
QJsonObject request; QJsonObject request;
request["transaction"] = transactionString; request["transaction"] = QString(transactionString);
request["signature"] = signature; request["signature"] = signature;
qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact); qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact);

View file

@ -19,9 +19,11 @@ HIFI_QML_DEF(QmlCommerce)
QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
auto ledger = DependencyManager::get<Ledger>(); auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult);
connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult); connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult);
connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult); connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult);
connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult);
} }
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) { void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
@ -48,4 +50,13 @@ void QmlCommerce::inventory() {
auto ledger = DependencyManager::get<Ledger>(); auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>(); auto wallet = DependencyManager::get<Wallet>();
ledger->inventory(wallet->listPublicKeys()); ledger->inventory(wallet->listPublicKeys());
} }
void QmlCommerce::chooseSecurityImage(uint imageID) {
auto wallet = DependencyManager::get<Wallet>();
wallet->chooseSecurityImage(imageID);
}
void QmlCommerce::getSecurityImage() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getSecurityImage();
}

View file

@ -30,11 +30,14 @@ signals:
// because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain). // because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain).
void balanceResult(int balance, const QString& failureMessage); void balanceResult(int balance, const QString& failureMessage);
void inventoryResult(QJsonObject inventory, const QString& failureMessage); void inventoryResult(QJsonObject inventory, const QString& failureMessage);
void securityImageResult(uint imageID);
protected: protected:
Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = "");
Q_INVOKABLE void balance(); Q_INVOKABLE void balance();
Q_INVOKABLE void inventory(); Q_INVOKABLE void inventory();
Q_INVOKABLE void chooseSecurityImage(uint imageID);
Q_INVOKABLE void getSecurityImage();
}; };
#endif // hifi_QmlCommerce_h #endif // hifi_QmlCommerce_h

View file

@ -9,30 +9,223 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <quuid.h>
#include "CommerceLogging.h" #include "CommerceLogging.h"
#include "Ledger.h" #include "Ledger.h"
#include "Wallet.h" #include "Wallet.h"
#include <PathUtils.h>
#include <QCryptographicHash>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
static const char* KEY_FILE = "hifikey";
void initialize() {
static bool initialized = false;
if (!initialized) {
SSL_load_error_strings();
SSL_library_init();
OpenSSL_add_all_algorithms();
initialized = true;
}
}
QString keyFilePath() {
return PathUtils::getAppDataFilePath(KEY_FILE);
}
// for now the callback function just returns the same string. Later we can hook
// this to the gui (some thought required)
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
// just return a hardcoded pwd for now
static const char* pwd = "pwd";
strcpy(password, pwd);
return static_cast<int>(strlen(pwd));
}
// BEGIN copied code - this will be removed/changed at some point soon
// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp.
// We will have a different implementation in practice, but this gives us a start for now
QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
RSA* keyPair = RSA_new();
BIGNUM* exponent = BN_new();
QPair<QByteArray*, QByteArray*> retval;
const unsigned long RSA_KEY_EXPONENT = 65537;
BN_set_word(exponent, RSA_KEY_EXPONENT);
// seed the random number generator before we call RSA_generate_key_ex
srand(time(NULL));
const int RSA_KEY_BITS = 2048;
if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) {
qCDebug(commerce) << "Error generating 2048-bit RSA Keypair -" << ERR_get_error();
// we're going to bust out of here but first we cleanup the BIGNUM
BN_free(exponent);
return retval;
}
// we don't need the BIGNUM anymore so clean that up
BN_free(exponent);
// grab the public key and private key from the file
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER);
unsigned char* privateKeyDER = NULL;
int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER);
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
qCDebug(commerce) << "Error getting DER public or private key from RSA struct -" << ERR_get_error();
// cleanup the RSA struct
RSA_free(keyPair);
// cleanup the public and private key DER data, if required
if (publicKeyLength > 0) {
OPENSSL_free(publicKeyDER);
}
if (privateKeyLength > 0) {
OPENSSL_free(privateKeyDER);
}
return retval;
}
// now lets persist them to files
// TODO: figure out a scheme for multiple keys, etc...
FILE* fp;
if ((fp = fopen(keyFilePath().toStdString().c_str(), "wt"))) {
if (!PEM_write_RSAPublicKey(fp, keyPair)) {
fclose(fp);
qCDebug(commerce) << "failed to write public key";
return retval;
}
if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
fclose(fp);
qCDebug(commerce) << "failed to write private key";
return retval;
}
fclose(fp);
}
RSA_free(keyPair);
// prepare the return values
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength ),
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength );
// cleanup the publicKeyDER and publicKeyDER data
OPENSSL_free(publicKeyDER);
OPENSSL_free(privateKeyDER);
return retval;
}
// END copied code (which will soon change)
// the public key can just go into a byte array
QByteArray readPublicKey(const char* filename) {
FILE* fp;
RSA* key = NULL;
if ((fp = fopen(filename, "r"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) {
// file read successfully
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_RSAPublicKey(key, &publicKeyDER);
// TODO: check for 0 length?
// cleanup
RSA_free(key);
fclose(fp);
qCDebug(commerce) << "parsed public key file successfully";
QByteArray retval((char*)publicKeyDER, publicKeyLength);
OPENSSL_free(publicKeyDER);
return retval;
} else {
qCDebug(commerce) << "couldn't parse" << filename;
}
fclose(fp);
} else {
qCDebug(commerce) << "couldn't open" << filename;
}
return QByteArray();
}
// the private key should be read/copied into heap memory. For now, we need the RSA struct
// so I'll return that. Note we need to RSA_free(key) later!!!
RSA* readPrivateKey(const char* filename) {
FILE* fp;
RSA* key = NULL;
if ((fp = fopen(filename, "r"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) {
// cleanup
fclose(fp);
qCDebug(commerce) << "parsed private key file successfully";
} else {
qCDebug(commerce) << "couldn't parse" << filename;
}
fclose(fp);
} else {
qCDebug(commerce) << "couldn't open" << filename;
}
return key;
}
bool Wallet::createIfNeeded() { bool Wallet::createIfNeeded() {
// FIXME: persist in file between sessions.
if (_publicKeys.count() > 0) return false; if (_publicKeys.count() > 0) return false;
// FIXME: initialize OpenSSL elsewhere soon
initialize();
// try to read existing keys if they exist...
auto publicKey = readPublicKey(keyFilePath().toStdString().c_str());
if (publicKey.size() > 0) {
if (auto key = readPrivateKey(keyFilePath().toStdString().c_str()) ) {
qCDebug(commerce) << "read private key";
RSA_free(key);
// K -- add the public key since we have a legit private key associated with it
_publicKeys.push_back(QUrl::toPercentEncoding(publicKey.toBase64()));
return false;
}
}
qCInfo(commerce) << "Creating wallet."; qCInfo(commerce) << "Creating wallet.";
return generateKeyPair(); return generateKeyPair();
} }
bool Wallet::generateKeyPair() { bool Wallet::generateKeyPair() {
// FIXME: need private key, too, and persist in file.
qCInfo(commerce) << "Generating keypair."; qCInfo(commerce) << "Generating keypair.";
QString key = QUuid::createUuid().toString(); auto keyPair = generateRSAKeypair();
_publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64()));
qCDebug(commerce) << "public key:" << _publicKeys.last();
_publicKeys.push_back(key); // Keep in memory for synchronous speed.
// It's arguable whether we want to change the receiveAt every time, but: // It's arguable whether we want to change the receiveAt every time, but:
// 1. It's certainly needed the first time, when createIfNeeded answers true. // 1. It's certainly needed the first time, when createIfNeeded answers true.
// 2. It is maximally private, and we can step back from that later if desired. // 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. // 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
auto ledger = DependencyManager::get<Ledger>(); auto ledger = DependencyManager::get<Ledger>();
return ledger->receiveAt(key); return ledger->receiveAt(_publicKeys.last());
} }
QStringList Wallet::listPublicKeys() { QStringList Wallet::listPublicKeys() {
qCInfo(commerce) << "Enumerating public keys."; qCInfo(commerce) << "Enumerating public keys.";
@ -40,7 +233,44 @@ QStringList Wallet::listPublicKeys() {
return _publicKeys; return _publicKeys;
} }
QString Wallet::signWithKey(const QString& text, const QString& key) { // for now a copy of how we sign in libraries/networking/src/DataServerAccountInfo -
// we sha256 the text, read the private key from disk (for now!), and return the signed
// sha256. Note later with multiple keys, we may need the key parameter (or something
// similar) so I left it alone for now. Also this will probably change when we move
// away from RSA keys anyways. Note that since this returns a QString, we better avoid
// the horror of code pages and so on (changing the bytes) by just returning a base64
// encoded string representing the signature (suitable for http, etc...)
QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
qCInfo(commerce) << "Signing text."; qCInfo(commerce) << "Signing text.";
return "fixme signed"; RSA* rsaPrivateKey = NULL;
if ((rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) {
QByteArray signature(RSA_size(rsaPrivateKey), 0);
unsigned int signatureBytes = 0;
QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256);
int encryptReturn = RSA_sign(NID_sha256,
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
reinterpret_cast<unsigned char*>(signature.data()),
&signatureBytes,
rsaPrivateKey);
// free the private key RSA struct now that we are done with it
RSA_free(rsaPrivateKey);
if (encryptReturn != -1) {
return QUrl::toPercentEncoding(signature.toBase64());
}
}
return QString();
}
void Wallet::chooseSecurityImage(uint imageID) {
_chosenSecurityImage = (SecurityImage)imageID;
emit securityImageResult(imageID);
}
void Wallet::getSecurityImage() {
emit securityImageResult(_chosenSecurityImage);
} }

View file

@ -25,10 +25,30 @@ public:
bool createIfNeeded(); bool createIfNeeded();
bool generateKeyPair(); bool generateKeyPair();
QStringList listPublicKeys(); QStringList listPublicKeys();
QString signWithKey(const QString& text, const QString& key); QString signWithKey(const QByteArray& text, const QString& key);
void chooseSecurityImage(uint imageID);
void getSecurityImage();
signals:
void securityImageResult(uint imageID);
protected:
// ALWAYS add SecurityImage enum values to the END of the enum.
// They must be in the same order as the images are listed in
// SecurityImageSelection.qml
enum SecurityImage {
NONE = 0,
Cat,
Car,
Dog,
Stars,
Plane,
Gingerbread
};
private: private:
QStringList _publicKeys{}; QStringList _publicKeys{};
SecurityImage _chosenSecurityImage = SecurityImage::NONE;
}; };
#endif // hifi_Wallet_h #endif // hifi_Wallet_h

View file

@ -168,6 +168,8 @@ void Rig::destroyAnimGraph() {
void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) {
_geometryOffset = AnimPose(geometry.offset); _geometryOffset = AnimPose(geometry.offset);
_invGeometryOffset = _geometryOffset.inverse(); _invGeometryOffset = _geometryOffset.inverse();
_geometryToRigTransform = modelOffset * geometry.offset;
_rigToGeometryTransform = glm::inverse(_geometryToRigTransform);
setModelOffset(modelOffset); setModelOffset(modelOffset);
_animSkeleton = std::make_shared<AnimSkeleton>(geometry); _animSkeleton = std::make_shared<AnimSkeleton>(geometry);
@ -1761,59 +1763,11 @@ void Rig::computeAvatarBoundingCapsule(
return; return;
} }
AnimInverseKinematics ikNode("boundingShape");
ikNode.setSkeleton(_animSkeleton);
ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation",
"leftHandType", "leftHandWeight", 1.0f, {},
QString(), QString(), QString());
ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation",
"rightHandType", "rightHandWeight", 1.0f, {},
QString(), QString(), QString());
ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation",
"leftFootType", "leftFootWeight", 1.0f, {},
QString(), QString(), QString());
ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation",
"rightFootType", "rightFootWeight", 1.0f, {},
QString(), QString(), QString());
glm::vec3 hipsPosition(0.0f); glm::vec3 hipsPosition(0.0f);
int hipsIndex = indexOfJoint("Hips"); int hipsIndex = indexOfJoint("Hips");
if (hipsIndex >= 0) { if (hipsIndex >= 0) {
hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans()); hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans());
} }
AnimVariantMap animVars;
animVars.setRigToGeometryTransform(_rigToGeometryTransform);
glm::quat handRotation = glm::angleAxis(PI, Vectors::UNIT_X);
animVars.set("leftHandPosition", hipsPosition);
animVars.set("leftHandRotation", handRotation);
animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
animVars.set("rightHandPosition", hipsPosition);
animVars.set("rightHandRotation", handRotation);
animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
int rightFootIndex = indexOfJoint("RightFoot");
int leftFootIndex = indexOfJoint("LeftFoot");
if (rightFootIndex != -1 && leftFootIndex != -1) {
glm::vec3 geomFootPosition = glm::vec3(0.0f, _animSkeleton->getAbsoluteDefaultPose(rightFootIndex).trans().y, 0.0f);
glm::vec3 footPosition = transformPoint(_geometryToRigTransform, geomFootPosition);
glm::quat footRotation = glm::angleAxis(0.5f * PI, Vectors::UNIT_X);
animVars.set("leftFootPosition", footPosition);
animVars.set("leftFootRotation", footRotation);
animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
animVars.set("rightFootPosition", footPosition);
animVars.set("rightFootRotation", footRotation);
animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
}
// call overlay twice: once to verify AnimPoseVec joints and again to do the IK
AnimNode::Triggers triggersOut;
AnimContext context(false, false, false, _geometryToRigTransform, _rigToGeometryTransform);
float dt = 1.0f; // the value of this does not matter
ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
// convert relative poses to absolute
_animSkeleton->convertRelativePosesToAbsolute(finalPoses);
// compute bounding box that encloses all points // compute bounding box that encloses all points
Extents totalExtents; Extents totalExtents;
@ -1824,15 +1778,15 @@ void Rig::computeAvatarBoundingCapsule(
// even if they do not have legs (default robot) // even if they do not have legs (default robot)
totalExtents.addPoint(glm::vec3(0.0f)); totalExtents.addPoint(glm::vec3(0.0f));
// HACK to reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints
// from the head to the hips when computing the rest of the bounding capsule. // from the head to the hips when computing the rest of the bounding capsule.
int index = indexOfJoint("Head"); int index = indexOfJoint("Head");
while (index != -1) { while (index != -1) {
const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo; const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo;
AnimPose pose = finalPoses[index]; AnimPose pose = _animSkeleton->getAbsoluteDefaultPose(index);
if (shapeInfo.points.size() > 0) { if (shapeInfo.points.size() > 0) {
for (size_t j = 0; j < shapeInfo.points.size(); ++j) { for (auto& point : shapeInfo.points) {
totalExtents.addPoint((pose * shapeInfo.points[j])); totalExtents.addPoint((pose * point));
} }
} }
index = _animSkeleton->getParentIndex(index); index = _animSkeleton->getParentIndex(index);
@ -1846,7 +1800,6 @@ void Rig::computeAvatarBoundingCapsule(
radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
heightOut = diagonal.y - 2.0f * radiusOut; heightOut = diagonal.y - 2.0f * radiusOut;
glm::vec3 rootPosition = finalPoses[geometry.rootJointIndex].trans(); glm::vec3 capsuleCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum)));
glm::vec3 rigCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); localOffsetOut = capsuleCenter - hipsPosition;
localOffsetOut = rigCenter - transformPoint(_geometryToRigTransform, rootPosition);
} }

View file

@ -21,6 +21,7 @@
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Checkout.qml"; var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Checkout.qml";
var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml"; var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml";
var MARKETPLACE_SECURITY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/SecurityImageSelection.qml";
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
@ -87,7 +88,7 @@
function onScreenChanged(type, url) { function onScreenChanged(type, url) {
onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL; onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL;
wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH)); wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH || url === MARKETPLACE_SECURITY_QML_PATH));
// for toolbar mode: change button to active when window is first openend, false otherwise. // for toolbar mode: change button to active when window is first openend, false otherwise.
marketplaceButton.editProperties({ isActive: onMarketplaceScreen }); marketplaceButton.editProperties({ isActive: onMarketplaceScreen });
if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
@ -217,8 +218,11 @@
case 'inventory_backClicked': case 'inventory_backClicked':
tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
break; break;
case 'securityImageSelection_cancelClicked':
tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
break;
default: default:
print('Unrecognized message from Checkout.qml or Inventory.qml: ' + JSON.stringify(message)); print('Unrecognized message from Checkout.qml, Inventory.qml, or SecurityImageSelection.qml: ' + JSON.stringify(message));
} }
} }