diff options
Diffstat (limited to 'logic/auth/YggdrasilTask.cpp')
-rw-r--r-- | logic/auth/YggdrasilTask.cpp | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/logic/auth/YggdrasilTask.cpp b/logic/auth/YggdrasilTask.cpp new file mode 100644 index 00000000..277d7bfd --- /dev/null +++ b/logic/auth/YggdrasilTask.cpp @@ -0,0 +1,211 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <logic/auth/YggdrasilTask.h> + +#include <QObject> +#include <QString> +#include <QJsonObject> +#include <QJsonDocument> +#include <QNetworkReply> +#include <QByteArray> + +#include <MultiMC.h> +#include <logic/auth/MojangAccount.h> +#include <logic/net/URLConstants.h> + +YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent) + : Task(parent), m_account(account) +{ +} + +void YggdrasilTask::executeTask() +{ + setStatus(getStateMessage(STATE_SENDING_REQUEST)); + + // Get the content of the request we're going to send to the server. + QJsonDocument doc(getRequestContent()); + + auto worker = MMC->qnam(); + QUrl reqUrl("https://" + URLConstants::AUTH_BASE + getEndpoint()); + QNetworkRequest netRequest(reqUrl); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QByteArray requestData = doc.toJson(); + m_netReply = worker->post(netRequest, requestData); + connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); + connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); + connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); + connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors); + timeout_keeper.setSingleShot(true); + timeout_keeper.start(timeout_max); + counter.setSingleShot(false); + counter.start(time_step); + progress(0, timeout_max); + connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abort); + connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat); +} + +void YggdrasilTask::refreshTimers(qint64, qint64) +{ + timeout_keeper.stop(); + timeout_keeper.start(timeout_max); + progress(count = 0, timeout_max); +} +void YggdrasilTask::heartbeat() +{ + count += time_step; + progress(count, timeout_max); +} + +void YggdrasilTask::abort() +{ + progress(timeout_max, timeout_max); + m_netReply->abort(); +} + +void YggdrasilTask::sslErrors(QList<QSslError> errors) +{ + int i = 1; + for (auto error : errors) + { + QLOG_ERROR() << "LOGIN SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + QLOG_ERROR() << "Certificate in question:\n" << cert.toText(); + i++; + } +} + +void YggdrasilTask::processReply() +{ + setStatus(getStateMessage(STATE_PROCESSING_RESPONSE)); + + if (m_netReply->error() == QNetworkReply::SslHandshakeFailedError) + { + emitFailed( + tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>" + "<ul>" + "<li>You use Windows XP and need to <a " + "href=\"http://www.microsoft.com/en-us/download/details.aspx?id=38918\">update " + "your root certificates</a></li>" + "<li>Some device on your network is interfering with SSL traffic. In that case, " + "you have bigger worries than Minecraft not starting.</li>" + "<li>Possibly something else. Check the MultiMC log file for details</li>" + "</ul>")); + return; + } + + // any network errors lead to offline mode right now + if (m_netReply->error() >= QNetworkReply::ConnectionRefusedError && + m_netReply->error() <= QNetworkReply::UnknownNetworkError) + { + // WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout + emitFailed("Yggdrasil task cancelled."); + QLOG_ERROR() << "Yggdrasil task cancelled because of: " << m_netReply->error() << " : " + << m_netReply->errorString(); + return; + } + + // Try to parse the response regardless of the response code. + // Sometimes the auth server will give more information and an error code. + QJsonParseError jsonError; + QByteArray replyData = m_netReply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); + // Check the response code. + int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (responseCode == 200) + { + // If the response code was 200, then there shouldn't be an error. Make sure + // anyways. + // Also, sometimes an empty reply indicates success. If there was no data received, + // pass an empty json object to the processResponse function. + if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) + { + if (processResponse(replyData.size() > 0 ? doc.object() : QJsonObject())) + { + emitSucceeded(); + return; + } + + // errors happened anyway? + emitFailed(m_error ? m_error->m_errorMessageVerbose + : tr("An unknown error occurred when processing the response " + "from the authentication server.")); + } + else + { + emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.") + .arg(jsonError.errorString()) + .arg(jsonError.offset)); + } + return; + } + + // If the response code was not 200, then Yggdrasil may have given us information + // about the error. + // If we can parse the response, then get information from it. Otherwise just say + // there was an unknown error. + if (jsonError.error == QJsonParseError::NoError) + { + // We were able to parse the server's response. Woo! + // Call processError. If a subclass has overridden it then they'll handle their + // stuff there. + QLOG_DEBUG() << "The request failed, but the server gave us an error message. " + "Processing error."; + emitFailed(processError(doc.object())); + } + else + { + // The server didn't say anything regarding the error. Give the user an unknown + // error. + QLOG_DEBUG() << "The request failed and the server gave no error message. " + "Unknown error."; + emitFailed(tr("An unknown error occurred when trying to communicate with the " + "authentication server: %1").arg(m_netReply->errorString())); + } +} + +QString YggdrasilTask::processError(QJsonObject responseData) +{ + QJsonValue errorVal = responseData.value("error"); + QJsonValue errorMessageValue = responseData.value("errorMessage"); + QJsonValue causeVal = responseData.value("cause"); + + if (errorVal.isString() && errorMessageValue.isString()) + { + m_error = std::shared_ptr<Error>(new Error{ + errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); + return m_error->m_errorMessageVerbose; + } + else + { + // Error is not in standard format. Don't set m_error and return unknown error. + return tr("An unknown Yggdrasil error occurred."); + } +} + +QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const +{ + switch (state) + { + case STATE_SENDING_REQUEST: + return tr("Sending request to auth servers..."); + case STATE_PROCESSING_RESPONSE: + return tr("Processing response from servers..."); + default: + return tr("Processing. Please wait..."); + } +} |