From c0e53c6669b1f808c53a6a28df79e338ed154f4f Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 1 Dec 2020 09:31:59 +0100 Subject: [PATCH] added authentication --- miele/integrationpluginmiele.cpp | 149 +++++++++---- miele/integrationpluginmiele.h | 26 +-- miele/integrationpluginmiele.json | 13 +- miele/miele.cpp | 341 +++++++++++++++++++++++++++++- miele/miele.h | 19 +- 5 files changed, 463 insertions(+), 85 deletions(-) diff --git a/miele/integrationpluginmiele.cpp b/miele/integrationpluginmiele.cpp index edba98d6..c1e2ba98 100644 --- a/miele/integrationpluginmiele.cpp +++ b/miele/integrationpluginmiele.cpp @@ -29,7 +29,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginmiele.h" -#include "extern-plugininfo.h" +#include "plugininfo.h" #include #include @@ -46,21 +46,23 @@ IntegrationPluginMiele::IntegrationPluginMiele() m_idParamTypeIds.insert(cookTopThingClassId, cookTopThingIdParamTypeId); m_idParamTypeIds.insert(hoodThingClassId, hoodThingIdParamTypeId); m_idParamTypeIds.insert(cleaningRobotThingClassId, cleaningRobotThingIdParamTypeId); + + m_connectedStateTypeIds.insert(ovenThingClassId, ovenConnectedStateTypeId); + m_connectedStateTypeIds.insert(fridgeThingClassId, fridgeConnectedStateTypeId); + m_connectedStateTypeIds.insert(dryerThingClassId, dryerConnectedStateTypeId); + m_connectedStateTypeIds.insert(coffeeMakerThingClassId, coffeeMakerConnectedStateTypeId); + m_connectedStateTypeIds.insert(dishwasherThingClassId, dishwasherConnectedStateTypeId); + m_connectedStateTypeIds.insert(washerThingClassId, washerConnectedStateTypeId); + m_connectedStateTypeIds.insert(cookTopThingClassId, cookTopConnectedStateTypeId); + m_connectedStateTypeIds.insert(cleaningRobotThingClassId, cleaningRobotConnectedStateTypeId); + m_connectedStateTypeIds.insert(hoodThingClassId, hoodConnectedStateTypeId); } void IntegrationPluginMiele::startPairing(ThingPairingInfo *info) { if (info->thingClassId() == mieleAccountThingClassId) { - QByteArray clientId = configValue(mielePluginCustomClientIdParamTypeId).toByteArray(); - if (clientId.isEmpty()) { - clientId = apiKeyStorage()->requestKey("miele").data("clientKey"); - } - if (clientId.isEmpty()) { - info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available.")); - return; - } - Miele *miele = new Miele(hardwareManager()->networkManager(), clientId, this); + Miele *miele = createMieleConnection(); QUrl url = miele->getLoginUrl(QUrl("https://127.0.0.1:8888")); qCDebug(dcMiele()) << "HomeConnect url:" << url; m_setupMieleConnections.insert(info->thingId(), miele); @@ -84,7 +86,7 @@ void IntegrationPluginMiele::confirmPairing(ThingPairingInfo *info, const QStrin Miele *miele = m_setupMieleConnections.value(info->thingId()); if (!miele) { - qWarning(dcMiele()) << "No Miele connection found for device:" << info->thingName(); + qWarning(dcMiele()) << "No Miele connection found for thing:" << info->thingName(); m_setupMieleConnections.remove(info->thingId()); info->finish(Thing::ThingErrorHardwareFailure); return; @@ -118,7 +120,7 @@ void IntegrationPluginMiele::setupThing(ThingSetupInfo *info) m_mieleConnections.insert(thing, miele); info->finish(Thing::ThingErrorNoError); } else { - //device loaded from the device database, needs a new access token; + // device loaded from the device database, needs a new access token; pluginStorage()->beginGroup(thing->id().toString()); QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); pluginStorage()->endGroup(); @@ -126,15 +128,10 @@ void IntegrationPluginMiele::setupThing(ThingSetupInfo *info) info->finish(Thing::ThingErrorAuthenticationFailure, tr("Refresh token is not available.")); return; } - QByteArray clientId = configValue(mielePluginCustomClientIdParamTypeId).toByteArray(); - if (clientId.isEmpty()) { - clientId = apiKeyStorage()->requestKey("miele").data("clientId"); + miele = createMieleConnection(); + if (!miele) { + return info->finish(Thing::ThingErrorSetupFailed); } - if (clientId.isEmpty()) { - info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client id is not available.")); - return; - } - miele = new Miele(hardwareManager()->networkManager(), clientId, this); miele->getAccessTokenFromRefreshToken(refreshToken); m_asyncSetup.insert(miele, info); } @@ -176,26 +173,26 @@ void IntegrationPluginMiele::postSetupThing(Thing *thing) } if (thing->thingClassId() == mieleAccountThingClassId) { - Miele *miele = m_mieleConnections.value(thing); - miele->getDevices(); - //miele->connectEventStream(); - thing->setStateValue(mieleAccountConnectedStateTypeId, true); - thing->setStateValue(mieleAccountLoggedInStateTypeId, true); - //TBD Set user name - } else if (m_idParamTypeIds.contains(thing->thingClassId())) { - Thing *parentThing = myThings().findById(thing->parentId()); - if (!parentThing) - qCWarning(dcMiele()) << "Could not find parent with Id" << thing->parentId().toString(); - Miele *miele = m_mieleConnections.value(parentThing); - QString deviceId = thing->paramValue(m_idParamTypeIds.value(thing->thingClassId())).toString(); - if (!miele) { - qCWarning(dcMiele()) << "Could not find HomeConnect connection for thing" << thing->name(); - } else { - miele->getDevice(deviceId); - } - } else { - Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); - } + Miele *miele = m_mieleConnections.value(thing); + miele->getDevices(); + //miele->connectEventStream(); + thing->setStateValue(mieleAccountConnectedStateTypeId, true); + thing->setStateValue(mieleAccountLoggedInStateTypeId, true); + //TBD Set user name + } else if (m_idParamTypeIds.contains(thing->thingClassId())) { + Thing *parentThing = myThings().findById(thing->parentId()); + if (!parentThing) + qCWarning(dcMiele()) << "Could not find parent with Id" << thing->parentId().toString(); + Miele *miele = m_mieleConnections.value(parentThing); + QString deviceId = thing->paramValue(m_idParamTypeIds.value(thing->thingClassId())).toString(); + if (!miele) { + qCWarning(dcMiele()) << "Could not find HomeConnect connection for thing" << thing->name(); + } else { + miele->getDevice(deviceId); + } + } else { + Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } } void IntegrationPluginMiele::executeAction(ThingActionInfo *info) @@ -233,4 +230,76 @@ void IntegrationPluginMiele::thingRemoved(Thing *thing) } } +Miele *IntegrationPluginMiele::createMieleConnection() +{ + QByteArray clientId = configValue(mielePluginCustomClientIdParamTypeId).toByteArray(); + QByteArray clientSecret = configValue(mielePluginCustomClientSecretParamTypeId).toByteArray(); + if (clientId.isEmpty() || clientSecret.isEmpty()) { + clientId = apiKeyStorage()->requestKey("miele").data("clientId"); + clientSecret = apiKeyStorage()->requestKey("miele").data("clientSecret"); + } else { + qCDebug(dcMiele()) << "Using custom api credentials"; + } + if (clientId.isEmpty() || clientSecret.isEmpty()) { + return nullptr; + } else { + qCDebug(dcMiele()) << "Using api credentials from API key provider"; + } + return new Miele(hardwareManager()->networkManager(), clientId, clientSecret, "en", this); +} + +void IntegrationPluginMiele::onConnectionChanged(bool connected) +{ + Miele *miele = static_cast(sender()); + Thing *thing = m_mieleConnections.key(miele); + if (!thing) + return; + thing->setStateValue(mieleAccountConnectedStateTypeId, connected); + if (!connected) { + Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) { + child->setStateValue(m_connectedStateTypeIds.value(child->thingClassId()), connected); + } + } +} + +void IntegrationPluginMiele::onAuthenticationStatusChanged(bool authenticated) +{ + Miele *mieleConnection = static_cast(sender()); + if (m_asyncSetup.contains(mieleConnection)) { + ThingSetupInfo *info = m_asyncSetup.take(mieleConnection); + if (authenticated) { + m_mieleConnections.insert(info->thing(), mieleConnection); + info->finish(Thing::ThingErrorNoError); + } else { + mieleConnection->deleteLater(); + info->finish(Thing::ThingErrorHardwareFailure); + } + } else { + Thing *thing = m_mieleConnections.key(mieleConnection); + if (!thing) + return; + + thing->setStateValue(mieleAccountLoggedInStateTypeId, authenticated); + if (!authenticated) { + //refresh access token needs to be refreshed + pluginStorage()->beginGroup(thing->id().toString()); + QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); + pluginStorage()->endGroup(); + mieleConnection->getAccessTokenFromRefreshToken(refreshToken); + } + } +} + +void IntegrationPluginMiele::onRequestExecuted(QUuid requestId, bool success) +{ + if (m_pendingActions.contains(requestId)) { + ThingActionInfo *info = m_pendingActions.value(requestId); + if (success) { + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + } +} + diff --git a/miele/integrationpluginmiele.h b/miele/integrationpluginmiele.h index b49a715a..87999748 100644 --- a/miele/integrationpluginmiele.h +++ b/miele/integrationpluginmiele.h @@ -55,10 +55,6 @@ public: void executeAction(ThingActionInfo *info) override; void thingRemoved(Thing *thing) override; - void browseThing(BrowseResult *result) override; - void browserItem(BrowserItemResult *result) override; - void executeBrowserItem(BrowserActionInfo *info) override; - private: PluginTimer *m_pluginTimer15min = nullptr; @@ -68,31 +64,11 @@ private: QHash m_mieleConnections; QHash m_pendingActions; - QHash m_selectedProgram; - QHash m_idParamTypeIds; QHash m_connectedStateTypeIds; - QHash m_doorStateStateTypeIds; - QHash m_localControlStateTypeIds; - QHash m_remoteControlActivationStateTypeIds; - QHash m_remoteStartAllowanceStateTypeIds; - QHash m_operationStateTypeIds; - QHash m_doorStateTypeIds; - QHash m_selectedProgramStateTypeIds; - QHash m_progressStateTypeIds; - QHash m_endTimerStateTypeIds; - QHash m_startActionTypeIds; - QHash m_stopActionTypeIds; - - QHash m_programFinishedEventTypeIds; - - QHash m_coffeeStrengthTypes; - - void parseKey(Thing *thing, const QString &key, const QVariant &value); - void parseSettingKey(Thing *thing, const QString &key, const QVariant &value); - bool checkIfActionIsPossible(ThingActionInfo *info); + Miele *createMieleConnection(); private slots: void onConnectionChanged(bool connected); diff --git a/miele/integrationpluginmiele.json b/miele/integrationpluginmiele.json index 8a1f8a62..1841a3d3 100644 --- a/miele/integrationpluginmiele.json +++ b/miele/integrationpluginmiele.json @@ -9,6 +9,13 @@ "displayName": "Custom client id", "defaultValue": "", "type": "QString" + }, + { + "id": "cd4709fb-adc2-4403-99b5-93dc9cc7f0d8", + "name": "customClientSecret", + "displayName": "Custom client secret", + "defaultValue": "", + "type": "QString" } ], "apiKeys": ["miele"], @@ -56,7 +63,7 @@ ] }, { - "id": "cf542b5e-2903-497c-95a0-042df6535450", + "id": "98491bd0-9ba7-4060-924d-153900f470b5", "name": "oven", "displayName": "Oven", "interfaces": ["connectable"], @@ -64,7 +71,7 @@ "browsable": true, "paramTypes": [ { - "id": "1e287843-41dd-49c8-a250-a21ed0e4215c", + "id": "5be2f375-8068-40b8-9a88-fc5a07508d82", "name": "id", "displayName": "ID", "defaultValue": "-", @@ -73,7 +80,7 @@ ], "stateTypes": [ { - "id": "9c2c9360-0a4a-43ff-8d15-9cefa80380ec", + "id": "eba13efe-cc1c-4954-bcaf-beed1d490f6d", "name": "connected", "displayName": "Connected", "displayNameEvent": "Connected changed", diff --git a/miele/miele.cpp b/miele/miele.cpp index c3f1df69..76e7bf48 100644 --- a/miele/miele.cpp +++ b/miele/miele.cpp @@ -36,11 +36,13 @@ #include #include #include +#include - -Miele::Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, QObject *parent) : +Miele::Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, const QByteArray &clientSecret, const QString &language, QObject *parent) : QObject(parent), + m_language(language), m_clientId(clientId), + m_clientSecret(clientSecret), m_networkManager(networkmanager) { m_tokenRefreshTimer = new QTimer(this); @@ -58,15 +60,15 @@ QByteArray Miele::refreshToken() return m_refreshToken; } -QUrl Miele::getLoginUrl(const QUrl &redirectUrl) +QUrl Miele::getLoginUrl(const QUrl &redirectUrl, const QString &state) { if (m_clientId.isEmpty()) { - qWarning(dcMiele) << "Client key not defined!"; + qCWarning(dcMiele) << "Client key not defined!"; return QUrl(""); } if (redirectUrl.isEmpty()){ - qWarning(dcMiele()) << "No redirect uri defined!"; + qCWarning(dcMiele()) << "No redirect uri defined!"; } m_redirectUri = QUrl::toPercentEncoding(redirectUrl.toString()); @@ -75,18 +77,178 @@ QUrl Miele::getLoginUrl(const QUrl &redirectUrl) queryParams.addQueryItem("client_id", m_clientId); queryParams.addQueryItem("redirect_uri", m_redirectUri); queryParams.addQueryItem("response_type", "code"); - m_state = QUuid::createUuid().toString(); - queryParams.addQueryItem("state", m_state); + if (!state.isEmpty()) + queryParams.addQueryItem("state", state); url.setQuery(queryParams); return url; } +void Miele::getAccessTokenFromRefreshToken(const QByteArray &refreshToken) +{ + if (refreshToken.isEmpty()) { + qCWarning(dcMiele()) << "No refresh token given!"; + setAuthenticated(false); + return; + } + + QUrl url(m_tokenUrl); + QUrlQuery query; + query.clear(); + query.addQueryItem("grant_type", "refresh_token"); + query.addQueryItem("refresh_token", refreshToken); + query.addQueryItem("client_id", m_clientId); + query.addQueryItem("client_secret", m_clientSecret); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8()); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, reply](){ + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { + return; + } + QJsonDocument data = QJsonDocument::fromJson(rawData); + + if(!data.toVariant().toMap().contains("access_token")) { + setAuthenticated(false); + return; + } + m_accessToken = data.toVariant().toMap().value("access_token").toByteArray(); + emit receivedAccessToken(m_accessToken); + + if (data.toVariant().toMap().contains("expires_in")) { + int expireTime = data.toVariant().toMap().value("expires_in").toInt(); + qCDebug(dcMiele) << "Access token expires int" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcMiele()) << "Access token refresh timer not initialized"; + return; + } + if (expireTime < 20) { + qCWarning(dcMiele()) << "Expire time too short"; + return; + } + m_tokenRefreshTimer->start((expireTime - 20) * 1000); + } + }); +} + +void Miele::getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode) +{ + if(authorizationCode.isEmpty()) + qCWarning(dcMiele) << "No authorization code given!"; + + QUrl url = QUrl(m_tokenUrl); + QUrlQuery query; + query.clear(); + query.addQueryItem("client_id", m_clientId); + query.addQueryItem("client_secret", m_clientSecret); + query.addQueryItem("vg", "de-DE"); + query.addQueryItem("redirect_uri", m_redirectUri); + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", authorizationCode); + url.setQuery(query); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8()); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, reply](){ + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { + return; + } + QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData); + if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) { + setAuthenticated(false); + return; + } + m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); + receivedAccessToken(m_accessToken); + m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray(); + receivedRefreshToken(m_refreshToken); + + if (jsonDoc.toVariant().toMap().contains("expires_in")) { + int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); + qCDebug(dcMiele()) << "Token expires in" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcMiele()) << "Token refresh timer not initialized"; + setAuthenticated(false); + return; + } + if (expireTime < 20) { + qCWarning(dcMiele()) << "Expire time too short"; + return; + } + m_tokenRefreshTimer->start((expireTime - 20) * 1000); + } + }); +} + void Miele::getDevices() { + QUrl url = m_apiUrl; + url.setPath("/v1/devices"); + url.setQuery("language=en"); + QNetworkRequest request(url); + request.setRawHeader("access_token", m_accessToken); + request.setRawHeader("language", "en"); + request.setRawHeader("accept", "application/json; charset=utf-8"); + + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, reply]{ + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { + return; + } + QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap(); + qCDebug(dcMiele()) << "Get devices" << map; + if (map.contains("data")) { + QVariantMap dataMap = map.value("data").toMap(); + qCDebug(dcMiele()) << "key" << dataMap.value("key").toString() << "value" << dataMap.value("value").toString() << dataMap.value("unit").toString(); + } else if (map.contains("error")) { + qCWarning(dcMiele()) << "Send command" << map.value("error").toMap().value("description").toString(); + } + }); +} + +void Miele::getDevice(const QString &deviceId) +{ + QUrl url = m_apiUrl; + url.setPath("/v1/devices/"+deviceId); + url.setQuery("language=en"); + + QNetworkRequest request(url); + request.setRawHeader("access_token", m_accessToken); + request.setRawHeader("language", "en"); + request.setRawHeader("accept", "application/json; charset=utf-8"); + + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, reply]{ + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { + return; + } + QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap(); + qCDebug(dcMiele()) << "Get device" << map; + }); +} + +void Miele::getActions(const QString &deviceId) +{ + Q_UNUSED(deviceId) } @@ -146,6 +308,65 @@ QUuid Miele::setTargetTemperature(const QString &deviceId, int zone, int targetT return putAction(deviceId, doc); } +QUuid Miele::setColors(const QString &deviceId, Miele::Color color) +{ + QJsonDocument doc; + QJsonObject object; + QString colorString = QMetaEnum::fromType().valueToKey(color); + object.insert("color", colorString.remove("Color").toLower()); + doc.setObject(object); + return putAction(deviceId, doc); +} + +QUuid Miele::setModes(const QString &deviceId, Miele::Mode mode) +{ + QJsonDocument doc; + QJsonObject object; + object.insert("modes", mode); + doc.setObject(object); + return putAction(deviceId, doc); +} + +QUuid Miele::setVentilationStep(const QString &deviceId, int step) +{ + QJsonDocument doc; + QJsonObject object; + object.insert("ventilationStep", step); + doc.setObject(object); + return putAction(deviceId, doc); +} + +QUuid Miele::setStartTime(const QString &deviceId, int seconds) +{ + QJsonDocument doc; + QJsonObject object; + object.insert("startTime", seconds); + doc.setObject(object); + return putAction(deviceId, doc); +} + +void Miele::getAllEvents() +{ + QUrl url = m_apiUrl; + url.setPath("/v1/devices/all/events"); + + QNetworkRequest request(url); + request.setRawHeader("authorization", m_accessToken); + request.setRawHeader("accept", "*/*"); + + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, [reply, this] { + reply->deleteLater(); + QTimer::singleShot(5000, this, [this] {getAllEvents();}); //try to reconnect every 5 seconds + }); + connect(reply, &QNetworkReply::readyRead, this, [this, reply]{ + + while (reply->canReadLine()) { + + } + }); +} + QUuid Miele::putAction(const QString &deviceId, const QJsonDocument &action) { QUuid commandId = QUuid::createUuid(); @@ -179,3 +400,109 @@ QUuid Miele::putAction(const QString &deviceId, const QJsonDocument &action) }); return commandId; } + +void Miele::setAuthenticated(bool state) +{ + if (state != m_authenticated) { + m_authenticated = state; + emit authenticationStatusChanged(state); + } +} + +void Miele::setConnected(bool state) +{ + if (state != m_connected) { + m_connected = state; + emit connectionChanged(state); + } +} + +bool Miele::checkStatusCode(QNetworkReply *reply, const QByteArray &rawData) +{ + // Check for the internet connection + if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError || + reply->error() == QNetworkReply::NetworkError::UnknownNetworkError || + reply->error() == QNetworkReply::NetworkError::TemporaryNetworkFailureError) { + qCWarning(dcMiele()) << "Connection error" << reply->errorString(); + setConnected(false); + setAuthenticated(false); + return false; + } else { + setConnected(true); + } + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData, &error); + + switch (status){ + case 200: //The request was successful. Typically returned for successful GET requests. + case 204: //The request was successful. Typically returned for successful PUT/DELETE requests with no payload. + break; + case 400: //Error occurred (e.g. validation error - value is out of range) + if(!jsonDoc.toVariant().toMap().contains("error")) { + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_client") { + qWarning(dcMiele()) << "Client token provided doesn’t correspond to client that generated auth code."; + } + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_redirect_uri") { + qWarning(dcMiele()) << "Missing redirect_uri parameter."; + } + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_code") { + qWarning(dcMiele()) << "Expired authorization code."; + } + } + setAuthenticated(false); + return false; + case 401: + qWarning(dcMiele()) << "Client does not have permission to use this API."; + setAuthenticated(false); + return false; + case 403: + qCWarning(dcMiele()) << "Forbidden, Scope has not been granted or home appliance is not assigned to HC account"; + setAuthenticated(false); + return false; + case 404: + qCWarning(dcMiele()) << "Not Found. This resource is not available (e.g. no images on washing machine)"; + return false; + case 405: + qWarning(dcMiele()) << "Wrong HTTP method used."; + setAuthenticated(false); + return false; + case 408: + qCWarning(dcMiele())<< "Request Timeout, API Server failed to produce an answer or has no connection to backend service"; + return false; + case 409: + qCWarning(dcMiele()) << "Conflict - Command/Query cannot be executed for the home appliance, the error response contains the error details"; + qCWarning(dcMiele()) << "Error" << jsonDoc.toVariant().toMap().value("error").toString(); + return false; + case 415: + qCWarning(dcMiele())<< "Unsupported Media Type. The request's Content-Type is not supported"; + return false; + case 429: + qCWarning(dcMiele())<< "Too Many Requests, the number of requests for a specific endpoint exceeded the quota of the client"; + return false; + case 500: + qCWarning(dcMiele())<< "Internal Server Error, in case of a server configuration error or any errors in resource files"; + return false; + case 503: + qCWarning(dcMiele())<< "Service Unavailable,if a required backend service is not available"; + return false; + default: + break; + } + + if (error.error != QJsonParseError::NoError) { + qCWarning(dcMiele()) << "Received invalide JSON object" << rawData; + qCWarning(dcMiele()) << "Status" << status; + setAuthenticated(false); + return false; + } + + setAuthenticated(true); + return true; +} + +void Miele::onRefreshTimeout() +{ + qCDebug(dcMiele()) << "Refresh authentication token"; + getAccessTokenFromRefreshToken(m_refreshToken); +} diff --git a/miele/miele.h b/miele/miele.h index 1996e994..533348e0 100644 --- a/miele/miele.h +++ b/miele/miele.h @@ -68,6 +68,7 @@ public: ColorPurple, ColorTurquoise }; + Q_ENUM(Color) enum Status { StatusOff = 1, @@ -89,12 +90,11 @@ public: StatusNotConnected = 255 }; - Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, QObject *parent = nullptr); + Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, const QByteArray &clientSecret, const QString &language = "en", QObject *parent = nullptr); QByteArray accessToken(); QByteArray refreshToken(); - void setSimulationMode(bool simulation); - QUrl getLoginUrl(const QUrl &redirectUrl); + QUrl getLoginUrl(const QUrl &redirectUrl, const QString &state = ""); void getAccessTokenFromRefreshToken(const QByteArray &refreshToken); void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode); @@ -114,12 +114,14 @@ public: QUuid setColors(const QString &deviceId, Color color); QUuid setModes(const QString &deviceId, Mode mode); QUuid setVentilationStep(const QString &deviceId, int step); - QUuid setStartTime(); + QUuid setStartTime(const QString &deviceId, int seconds); // EVENTS void getAllEvents(); private: - + QString m_language; + QByteArray m_clientId; + QByteArray m_clientSecret; NetworkAccessManager *m_networkManager = nullptr; QUuid putAction(const QString &deviceId, const QJsonDocument &action); // @@ -127,21 +129,18 @@ private: QUrl m_authorizationUrl = QUrl("https://api.mcs3.miele.com/thirdparty/login/"); QUrl m_tokenUrl = QUrl("https://api.mcs3.miele.com/thirdparty/token/"); QUrl m_apiUrl = QUrl("https://api.mcs3.miele.com/"); - QByteArray m_clientId; QByteArray m_accessToken; QByteArray m_refreshToken; QByteArray m_redirectUri = "https://127.0.0.1:8888"; - QString m_state; QTimer *m_tokenRefreshTimer = nullptr; - void setAuthenticated(bool state); - void setConnected(bool state); - bool m_authenticated = false; bool m_connected = false; + void setAuthenticated(bool state); + void setConnected(bool state); bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData); signals: void connectionChanged(bool connected);