it's working!

This commit is contained in:
Michael Zanetti 2018-08-15 21:19:55 +02:00
parent 17ea41dd9b
commit 080a47932a
10 changed files with 147 additions and 93 deletions

View File

@ -22,11 +22,14 @@ AWSClient::AWSClient(QObject *parent) : QObject(parent)
QSettings settings;
settings.beginGroup("cloud");
m_username = settings.value("username").toString();
m_password = settings.value("password").toString();
m_accessToken = settings.value("accessToken").toByteArray();
m_accessTokenExpiry = settings.value("accessTokenExpiry").toDateTime();
m_idToken = settings.value("idToken").toByteArray();
m_refreshToken = settings.value("refreshToken").toByteArray();
m_identityId = settings.value("identityId").toByteArray();
m_accessKeyId = settings.value("accessKeyId").toByteArray();
m_secretKey = settings.value("secretKey").toByteArray();
m_sessionToken = settings.value("sessionToken").toByteArray();
@ -38,6 +41,11 @@ bool AWSClient::isLoggedIn() const
return !m_username.isEmpty() && !m_password.isEmpty();
}
QString AWSClient::username() const
{
return m_username;
}
void AWSClient::login(const QString &username, const QString &password)
{
m_username = username;
@ -47,12 +55,6 @@ void AWSClient::login(const QString &username, const QString &password)
// See: https://forums.aws.amazon.com/thread.jspa?threadID=287978
m_password = password;
QSettings settings;
settings.remove("cloud");
settings.beginGroup("cloud");
settings.setValue("username", username);
settings.setValue("password", password);
QUrl url("https://cognito-idp.eu-west-1.amazonaws.com/");
QUrlQuery query;
@ -85,6 +87,8 @@ void AWSClient::login(const QString &username, const QString &password)
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error logging in to aws:" << reply->error() << reply->errorString();
m_username.clear();
m_password.clear();
return;
}
QByteArray data = reply->readAll();
@ -92,6 +96,8 @@ void AWSClient::login(const QString &username, const QString &password)
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse AWS login response" << error.errorString();
m_username.clear();
m_password.clear();
return;
}
@ -102,7 +108,11 @@ void AWSClient::login(const QString &username, const QString &password)
m_refreshToken = authenticationResult.value("RefreshToken").toByteArray();
QSettings settings;
settings.remove("cloud");
settings.beginGroup("cloud");
settings.setValue("username", m_username);
settings.setValue("password", m_password);
settings.setValue("accessToken", m_accessToken);
settings.setValue("accessTokenExpiry", m_accessTokenExpiry);
settings.setValue("idToken", m_idToken);
@ -116,6 +126,15 @@ void AWSClient::login(const QString &username, const QString &password)
});
}
void AWSClient::logout()
{
m_username.clear();
m_password.clear();
QSettings settings;
settings.remove("cloud");
emit isLoggedInChanged();
}
void AWSClient::getId()
{
QUrl url("https://cognito-identity.eu-west-1.amazonaws.com/");
@ -154,14 +173,22 @@ void AWSClient::getId()
qWarning() << "Error parsing json reply for GetId" << error.errorString();
return;
}
QByteArray identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray();
m_identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray();
QSettings settings;
settings.beginGroup("cloud");
settings.setValue("identityId", m_identityId);
qDebug() << "Received cognito identity id" << identityId;
getCredentialsForIdentity(identityId);
qDebug() << "Received cognito identity id" << m_identityId;
getCredentialsForIdentity(m_identityId);
});
}
QByteArray AWSClient::idToken() const
{
return m_idToken;
}
void AWSClient::getCredentialsForIdentity(const QString &identityId)
{
QUrl url("https://cognito-identity.eu-west-1.amazonaws.com/");
@ -225,42 +252,34 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId)
while (!m_callQueue.isEmpty()) {
QueuedCall qc = m_callQueue.takeFirst();
switch (qc.args.count()) {
case 0:
QMetaObject::invokeMethod(this, qc.method.toUtf8().data());
break;
case 1:
QMetaObject::invokeMethod(this, qc.method.toUtf8().data(), Q_ARG(QString, qc.args.first()));
break;
default:
Q_ASSERT_X(false, "AWSClient", "Call queue handling does not yet support multiple arguments");
break;
if (qc.method == "fetchDevices") {
fetchDevices();
} else if (qc.method == "postToMQTT") {
postToMQTT(qc.boxId, qc.callback);
}
}
});
}
bool AWSClient::tokenExpired() const
bool AWSClient::tokensExpired() const
{
qDebug() << "access token expirty:" << m_accessTokenExpiry.toString() << "session token expiry" << m_sessionTokenExpiry.toString() << "current:" << QDateTime::currentDateTime().toString();
qDebug() << "access token expired:" << (m_accessTokenExpiry.addSecs(-10) < QDateTime::currentDateTime());
return (m_accessTokenExpiry.addSecs(-10) < QDateTime::currentDateTime()) || (m_sessionTokenExpiry.addSecs(-10) < QDateTime::currentDateTime());
}
void AWSClient::postToMQTT(const QString &token)
bool AWSClient::postToMQTT(const QString &boxId, std::function<void(bool)> callback)
{
if (!isLoggedIn()) {
qWarning() << "Cannot post to MQTT. Not logged in to AWS";
return;
return false;
}
if (tokenExpired()) {
qDebug() << "Cannot post to MQTT. Need to refresh the token first";
if (tokensExpired()) {
qDebug() << "Cannot post to MQTT. Need to refresh the tokens first";
refreshAccessToken();
m_callQueue.append(QueuedCall("postToMQTT", token));
return;
}
m_callQueue.append(QueuedCall("postToMQTT", boxId, callback));
return true; // So far it looks we're doing ok... let's return true
}
QString host = "a2addxakg5juii.iot.eu-west-1.amazonaws.com";
QString topic = "850593e9-f2ab-4e89-913a-16f848d48867/eu-west-1:88c8b0f1-3f26-46cb-81f3-ccc37dcb543a/proxy";
QString topic = QString("%1/%2/proxy").arg(boxId).arg(QString(m_identityId));
// This is somehow broken in AWS...
// The Signature needs to be created with having the topic percentage-encoded twice
@ -268,11 +287,11 @@ void AWSClient::postToMQTT(const QString &token)
// Now one could think this is an issue in how the signature is made, but it can't really
// be fixed there as this concerns only the actual topic, not /topics/
// so we can't percentage-encode the whole path inside the signature helper...
QString path = "/topics/" + topic.toUtf8().toPercentEncoding().toPercentEncoding() + "?qos=0";
QString path1 = "/topics/" + topic.toUtf8().toPercentEncoding() + "?qos=0";
QString path = "/topics/" + topic.toUtf8().toPercentEncoding().toPercentEncoding() + "?qos=1";
QString path1 = "/topics/" + topic.toUtf8().toPercentEncoding() + "?qos=1";
QVariantMap params;
params.insert("token", token);
params.insert("token", m_idToken);
params.insert("timestamp", QDateTime::currentDateTime().toSecsSinceEpoch());
QByteArray payload = QJsonDocument::fromVariant(params).toJson(QJsonDocument::Compact);
@ -293,10 +312,27 @@ void AWSClient::postToMQTT(const QString &token)
}
qDebug() << "Payload:" << payload;
QNetworkReply *reply = m_nam->post(request, payload);
connect(reply, &QNetworkReply::finished, this, [reply]() {
connect(reply, &QNetworkReply::finished, this, [reply, callback]() {
reply->deleteLater();
qDebug() << "post reply" << reply->readAll();
QByteArray data = reply->readAll();
qDebug() << "post reply" << data;
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse reply" << error.error << error.errorString() << data;
callback(false);
return;
}
if (jsonDoc.toVariant().toMap().value("message").toString() != "OK") {
qWarning() << "Something went wrong posting to MQTT:" << jsonDoc.toVariant().toMap().value("message").toString();
callback(false);
return;
}
callback(true);
});
return true;
}
void AWSClient::fetchDevices()
@ -305,8 +341,8 @@ void AWSClient::fetchDevices()
qWarning() << "Not logged in at AWS. Can't fetch paired devices";
return;
}
if (tokenExpired()) {
qDebug() << "Need to refresh our tokens";
if (tokensExpired()) {
qDebug() << "Cannot fetch devices. Need to refresh our tokens";
refreshAccessToken();
m_callQueue.append(QueuedCall("fetchDevices"));
return;
@ -337,20 +373,11 @@ void AWSClient::fetchDevices()
d.id = entry.toMap().value("deviceId").toString();
d.name = entry.toMap().value("name").toString();
d.online = entry.toMap().value("online").toBool();
d.token = m_accessToken;
ret.append(d);
}
emit devicesFetched(ret);
});
}
QByteArray AWSClient::accessToken() const
{
return m_accessToken;
}
void AWSClient::refreshAccessToken()
@ -359,12 +386,15 @@ void AWSClient::refreshAccessToken()
qDebug() << "Cannot refresh tokens. Not logged in to AWS";
return;
}
// We should use REFRESH_TOKEN_AUTH to refresh our tokens but it's not working
// https://forums.aws.amazon.com/thread.jspa?threadID=287978
// Let's re-login instead with user & pass
login(m_username, m_password);
return;
// We should use this to refresh our tokens but it's not working
// https://forums.aws.amazon.com/thread.jspa?threadID=287978
// Non-working block... Enable this if Amazon ever fixes their API...
QUrl url("https://cognito-idp.eu-west-1.amazonaws.com/");
QUrlQuery query;

View File

@ -12,27 +12,30 @@ public:
QString id;
QString name;
bool online;
QByteArray token;
};
class AWSClient : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged)
Q_PROPERTY(QString username READ username NOTIFY isLoggedInChanged)
public:
explicit AWSClient(QObject *parent = nullptr);
bool isLoggedIn() const;
QString username() const;
Q_INVOKABLE void login(const QString &username, const QString &password);
Q_INVOKABLE void logout();
Q_INVOKABLE void fetchDevices();
Q_INVOKABLE void postToMQTT(const QString &token);
Q_INVOKABLE bool postToMQTT(const QString &boxId, std::function<void(bool)> callback);
Q_INVOKABLE void getId();
QByteArray accessToken() const;
bool tokensExpired() const;
QByteArray idToken() const;
signals:
void isLoggedInChanged();
@ -44,7 +47,6 @@ private:
void getCredentialsForIdentity(const QString &identityId);
void connectMQTT();
bool tokenExpired() const;
private:
QNetworkAccessManager *m_nam = nullptr;
@ -57,6 +59,8 @@ private:
QByteArray m_idToken;
QByteArray m_refreshToken;
QByteArray m_identityId;
QByteArray m_accessKeyId;
QByteArray m_secretKey;
QByteArray m_sessionToken;
@ -65,9 +69,10 @@ private:
class QueuedCall {
public:
QueuedCall(const QString &method): method(method) { }
QueuedCall(const QString &method, const QString &arg1): method(method) { args.append(arg1); }
QueuedCall(const QString &method, const QString &boxId, std::function<void(bool)> callback): method(method), boxId(boxId), callback(callback) {}
QString method;
QStringList args;
QString boxId;
std::function<void(bool)> callback;
};
QList<QueuedCall> m_callQueue;

View File

@ -4,6 +4,7 @@
#include "remoteproxyconnection.h"
#include <QUrlQuery>
#include <QHostInfo>
using namespace remoteproxyclient;
@ -11,26 +12,33 @@ CloudTransport::CloudTransport(AWSClient *awsClient, QObject *parent):
NymeaTransportInterface(parent),
m_awsClient(awsClient)
{
m_remoteproxyConnection = new RemoteProxyConnection(QUuid::createUuid(), "nymea:app", RemoteProxyConnection::ConnectionTypeWebSocket, this);
m_remoteproxyConnection = new RemoteProxyConnection(QUuid::createUuid(), "nymea:app", this);
m_remoteproxyConnection->setInsecureConnection(true);
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::connected, this,[this]() {
qDebug() << "Connected to remote proxy";
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::remoteConnectionEstablished, this,[this]() {
qDebug() << "CloudTransport: Remote connection established.";
emit connected();
});
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::stateChanged, this,[this](RemoteProxyConnection::State state) {
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::disconnected, this,[this]() {
qDebug() << "CloudTransport: Disconnected.";
emit disconnected();
});
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::stateChanged, this,[](RemoteProxyConnection::State state) {
qDebug() << "Proxy state changed:" << state;
if (state == RemoteProxyConnection::StateRemoteConnected) {
emit connected();
}
});
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::ready, this,[this]() {
qDebug() << "Proxy ready:";
m_remoteproxyConnection->authenticate(m_token);
qDebug() << "Proxy ready. Authenticating channel.";
m_remoteproxyConnection->authenticate(m_awsClient->idToken());
});
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::dataReady, this, [this](const QByteArray &data) {
qDebug() << "Remote connection data received";
emit dataReady(data);
});
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::errorOccured, this, [this] (RemoteProxyConnection::Error error) {
qDebug() << "Remote proxy Error:" << error;
emit NymeaTransportInterface::error(QAbstractSocket::ConnectionRefusedError);
});
}
QStringList CloudTransport::supportedSchemes() const
@ -44,20 +52,27 @@ bool CloudTransport::connect(const QUrl &url)
qWarning() << "Not logged in to AWS, cannot connect";
return false;
}
qDebug() << "Connecting to" << url;
QUrlQuery query(url.query());
QString m_token = query.queryItemValue("token");
m_awsClient->postToMQTT(m_token);
bool postResult = m_awsClient->postToMQTT(url.host(), [this](bool success) {
if (success) {
m_remoteproxyConnection->connectServer(QHostAddress("34.244.242.103"), 443);
}
});
m_remoteproxyConnection->connectServer(QHostAddress("127.0.0.1"), 1212);
if (!postResult) {
qWarning() << "Failed to post to MQTT. Cannot continue";
return false;
}
return true;
}
void CloudTransport::disconnect()
{
qDebug() << "should disconnect";
qDebug() << "CloudTransport: Disconnecting from server.";
m_remoteproxyConnection->disconnectServer();
}
NymeaTransportInterface::ConnectionState CloudTransport::connectionState() const

View File

@ -26,8 +26,6 @@ public:
private:
AWSClient *m_awsClient = nullptr;
remoteproxyclient::RemoteProxyConnection *m_remoteproxyConnection = nullptr;
QString m_token;
};
#endif // CLOUDTRANSPORT_H

View File

@ -72,8 +72,6 @@ void NymeaDiscovery::cloudDevicesFetched(const QList<AWSDevice> &devices)
QUrl url;
url.setScheme("cloud");
url.setHost(d.id);
QUrlQuery query;
query.addQueryItem("token", d.token);
if (!device->connections()->find(url)) {
Connection *conn = new Connection(url, Connection::BearerTypeCloud, true, d.id);
device->connections()->addConnection(conn);

View File

@ -10,9 +10,6 @@ include(../config.pri)
DEFINES += WITH_ZEROCONF
include(../QtZeroConf/qtzeroconf.pri)
}
DEFINES += QT_STATIC
include(../qmqtt/src/mqtt/mqtt.pri)
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS
include(../nymea-remoteproxy/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri)

View File

@ -42,8 +42,7 @@ int main(int argc, char *argv[])
application.setOrganizationName("nymea");
foreach (const QFileInfo &fi, QDir(":/ui/fonts/").entryInfoList()) {
int id = QFontDatabase::addApplicationFont(fi.absoluteFilePath());
// qDebug() << "Added font" << fi.absoluteFilePath() << QFontDatabase::applicationFontFamilies(id);
QFontDatabase::addApplicationFont(fi.absoluteFilePath());
}
QFont applicationFont;

View File

@ -9,8 +9,6 @@ INCLUDEPATH += $$top_srcdir/libnymea-common \
LIBS += -L$$top_builddir/libnymea-app-core/ -lnymea-app-core \
-L$$top_builddir/libnymea-common/ -lnymea-common \
LIBS += -lssl -lcrypto
win32:Debug:LIBS += -L$$top_builddir/libnymea-app-core/debug \
-L$$top_builddir/libnymea-common/debug
win32:Release:LIBS += -L$$top_builddir/libnymea-app-core/release \

View File

@ -12,43 +12,57 @@ Page {
}
ColumnLayout {
anchors.fill: parent
anchors { left: parent.left; top: parent.top; right: parent.right }
visible: Engine.awsClient.isLoggedIn
Label {
Layout.fillWidth: true
Layout.margins: app.margins
wrapMode: Text.WordWrap
text: qsTr("Logged in as %1").arg(Engine.awsClient.username)
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Log out")
onClicked: Engine.awsClient.logout();
}
}
ColumnLayout {
anchors { left: parent.left; right: parent.right; top: parent.top }
visible: !Engine.awsClient.isLoggedIn
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: "Username (e-mail)"
}
TextField {
id: usernameTextField
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
placeholderText: "john@dummy.com"
inputMethodHints: Qt.ImhEmailCharactersOnly
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("Password")
}
TextField {
id: passwordTextField
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
Layout.fillWidth: true
echoMode: TextInput.Password
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("OK")
enabled: usernameTextField.displayText.length > 0 && passwordTextField.displayText.length > 0
onClicked: {
Engine.awsClient.login(usernameTextField.text, passwordTextField.text);
}
}
Button {
Layout.fillWidth: true
text: "GetId"
onClicked: Engine.awsClient.getId();
}
Button {
Layout.fillWidth: true
text: "Post to MQTT"
onClicked: Engine.awsClient.postToMQTT();
}
}
}

@ -1 +1 @@
Subproject commit a9e79bde9703c842a7ca7adead22d84bcb9d6f53
Subproject commit 0a18897de79606543b0e7a68ac9b1990a5dd761c