summaryrefslogtreecommitdiffstats
path: root/logic/auth
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2013-11-24 18:41:35 +0100
committerPetr Mrázek <peterix@gmail.com>2013-11-24 18:41:35 +0100
commit82225a21e1b7f1671a42c1511032c59ecda0503f (patch)
treebed0663df62a7210da6928c50a6ba99aed53cd1f /logic/auth
parentca297fca79a7b1b96e41ba5abed4956af9383c16 (diff)
parent75e7932607bdd84d2867765eb6f07dcec95ee193 (diff)
downloadMultiMC-82225a21e1b7f1671a42c1511032c59ecda0503f.tar
MultiMC-82225a21e1b7f1671a42c1511032c59ecda0503f.tar.gz
MultiMC-82225a21e1b7f1671a42c1511032c59ecda0503f.tar.lz
MultiMC-82225a21e1b7f1671a42c1511032c59ecda0503f.tar.xz
MultiMC-82225a21e1b7f1671a42c1511032c59ecda0503f.zip
Merge branch 'feature_yggdrasil' into develop
Conflicts: gui/MainWindow.cpp logic/OneSixInstance.h Fix missing session id functionality for legacy and old onesix.
Diffstat (limited to 'logic/auth')
-rw-r--r--logic/auth/AuthenticateTask.cpp180
-rw-r--r--logic/auth/AuthenticateTask.h46
-rw-r--r--logic/auth/MojangAccount.cpp220
-rw-r--r--logic/auth/MojangAccount.h154
-rw-r--r--logic/auth/YggdrasilTask.cpp182
-rw-r--r--logic/auth/YggdrasilTask.h128
6 files changed, 910 insertions, 0 deletions
diff --git a/logic/auth/AuthenticateTask.cpp b/logic/auth/AuthenticateTask.cpp
new file mode 100644
index 00000000..a9c2c03f
--- /dev/null
+++ b/logic/auth/AuthenticateTask.cpp
@@ -0,0 +1,180 @@
+
+/* 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/AuthenticateTask.h>
+
+#include <logic/auth/MojangAccount.h>
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QVariant>
+#include <QDebug>
+
+#include "logger/QsLog.h"
+
+AuthenticateTask::AuthenticateTask(MojangAccountPtr account, const QString& password, QObject* parent) :
+ YggdrasilTask(account, parent), m_password(password)
+{
+}
+
+QJsonObject AuthenticateTask::getRequestContent() const
+{
+ /*
+ * {
+ * "agent": { // optional
+ * "name": "Minecraft", // So far this is the only encountered value
+ * "version": 1 // This number might be increased
+ * // by the vanilla client in the future
+ * },
+ * "username": "mojang account name", // Can be an email address or player name for
+ // unmigrated accounts
+ * "password": "mojang account password",
+ * "clientToken": "client identifier" // optional
+ * }
+ */
+ QJsonObject req;
+
+ {
+ QJsonObject agent;
+ // C++ makes string literals void* for some stupid reason, so we have to tell it QString... Thanks Obama.
+ agent.insert("name", QString("Minecraft"));
+ agent.insert("version", 1);
+ req.insert("agent", agent);
+ }
+
+ req.insert("username", getMojangAccount()->username());
+ req.insert("password", m_password);
+
+ // If we already have a client token, give it to the server.
+ // Otherwise, let the server give us one.
+ if (!getMojangAccount()->clientToken().isEmpty())
+ req.insert("clientToken", getMojangAccount()->clientToken());
+
+ return req;
+}
+
+bool AuthenticateTask::processResponse(QJsonObject responseData)
+{
+ // Read the response data. We need to get the client token, access token, and the selected profile.
+ QLOG_DEBUG() << "Processing authentication response.";
+
+ // If we already have a client token, make sure the one the server gave us matches our existing one.
+ QLOG_DEBUG() << "Getting client token.";
+ QString clientToken = responseData.value("clientToken").toString("");
+ if (clientToken.isEmpty())
+ {
+ // Fail if the server gave us an empty client token
+ // TODO: Set an error properly to display to the user.
+ QLOG_ERROR() << "Server didn't send a client token.";
+ return false;
+ }
+ if (!getMojangAccount()->clientToken().isEmpty() && clientToken != getMojangAccount()->clientToken())
+ {
+ // The server changed our client token! Obey its wishes, but complain. That's what I do for my parents, so...
+ QLOG_WARN() << "Server changed our client token to '" << clientToken
+ << "'. This shouldn't happen, but it isn't really a big deal.";
+ }
+ // Set the client token.
+ getMojangAccount()->setClientToken(clientToken);
+
+
+ // Now, we set the access token.
+ QLOG_DEBUG() << "Getting access token.";
+ QString accessToken = responseData.value("accessToken").toString("");
+ if (accessToken.isEmpty())
+ {
+ // Fail if the server didn't give us an access token.
+ // TODO: Set an error properly to display to the user.
+ QLOG_ERROR() << "Server didn't send an access token.";
+ }
+ // Set the access token.
+ getMojangAccount()->setAccessToken(accessToken);
+
+
+ // Now we load the list of available profiles.
+ // Mojang hasn't yet implemented the profile system,
+ // but we might as well support what's there so we
+ // don't have trouble implementing it later.
+ QLOG_DEBUG() << "Loading profile list.";
+ QJsonArray availableProfiles = responseData.value("availableProfiles").toArray();
+ ProfileList loadedProfiles;
+ for (auto iter : availableProfiles)
+ {
+ QJsonObject profile = iter.toObject();
+ // Profiles are easy, we just need their ID and name.
+ QString id = profile.value("id").toString("");
+ QString name = profile.value("name").toString("");
+
+ if (id.isEmpty() || name.isEmpty())
+ {
+ // This should never happen, but we might as well
+ // warn about it if it does so we can debug it easily.
+ // You never know when Mojang might do something truly derpy.
+ QLOG_WARN() << "Found entry in available profiles list with missing ID or name field. Ignoring it.";
+ }
+
+ // Now, add a new AccountProfile entry to the list.
+ loadedProfiles.append(AccountProfile(id, name));
+ }
+ // Put the list of profiles we loaded into the MojangAccount object.
+ getMojangAccount()->loadProfiles(loadedProfiles);
+
+
+ // Finally, we set the current profile to the correct value. This is pretty simple.
+ // We do need to make sure that the current profile that the server gave us
+ // is actually in the available profiles list.
+ // If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know).
+ QLOG_DEBUG() << "Setting current profile.";
+ QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
+ QString currentProfileId = currentProfile.value("id").toString("");
+ if (currentProfileId.isEmpty())
+ {
+ // TODO: Set an error to display to the user.
+ QLOG_ERROR() << "Server didn't specify a currently selected profile.";
+ return false;
+ }
+ if (!getMojangAccount()->setProfile(currentProfileId))
+ {
+ // TODO: Set an error to display to the user.
+ QLOG_ERROR() << "Server specified a selected profile that wasn't in the available profiles list.";
+ return false;
+ }
+
+
+ // We've made it through the minefield of possible errors. Return true to indicate that we've succeeded.
+ QLOG_DEBUG() << "Finished reading authentication response.";
+ return true;
+}
+
+QString AuthenticateTask::getEndpoint() const
+{
+ return "authenticate";
+}
+
+QString AuthenticateTask::getStateMessage(const YggdrasilTask::State state) const
+{
+ switch (state)
+ {
+ case STATE_SENDING_REQUEST:
+ return tr("Authenticating: Sending request.");
+ case STATE_PROCESSING_RESPONSE:
+ return tr("Authenticating: Processing response.");
+ default:
+ return YggdrasilTask::getStateMessage(state);
+ }
+}
+
diff --git a/logic/auth/AuthenticateTask.h b/logic/auth/AuthenticateTask.h
new file mode 100644
index 00000000..54a6b79a
--- /dev/null
+++ b/logic/auth/AuthenticateTask.h
@@ -0,0 +1,46 @@
+/* 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.
+ */
+
+#pragma once
+
+#include <logic/auth/YggdrasilTask.h>
+
+#include <QObject>
+#include <QString>
+#include <QJsonObject>
+
+/**
+ * The authenticate task takes a MojangAccount with no access token and password and attempts to authenticate with Mojang's servers.
+ * If successful, it will set the MojangAccount's access token.
+ */
+class AuthenticateTask : public YggdrasilTask
+{
+Q_OBJECT
+public:
+ AuthenticateTask(MojangAccountPtr account, const QString& password, QObject* parent=0);
+
+protected:
+ virtual QJsonObject getRequestContent() const;
+
+ virtual QString getEndpoint() const;
+
+ virtual bool processResponse(QJsonObject responseData);
+
+ QString getStateMessage(const YggdrasilTask::State state) const;
+
+private:
+ QString m_password;
+};
+
diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp
new file mode 100644
index 00000000..4f3839bc
--- /dev/null
+++ b/logic/auth/MojangAccount.cpp
@@ -0,0 +1,220 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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 "MojangAccount.h"
+
+#include <QUuid>
+#include <QJsonObject>
+#include <QJsonArray>
+
+#include <logger/QsLog.h>
+
+MojangAccount::MojangAccount(const QString& username, QObject* parent) :
+ QObject(parent)
+{
+ // Generate a client token.
+ m_clientToken = QUuid::createUuid().toString();
+
+ m_username = username;
+
+ m_currentProfile = -1;
+}
+
+MojangAccount::MojangAccount(const QString& username, const QString& clientToken,
+ const QString& accessToken, QObject* parent) :
+ QObject(parent)
+{
+ m_username = username;
+ m_clientToken = clientToken;
+ m_accessToken = accessToken;
+
+ m_currentProfile = -1;
+}
+
+MojangAccount::MojangAccount(const MojangAccount& other, QObject* parent)
+{
+ m_username = other.username();
+ m_clientToken = other.clientToken();
+ m_accessToken = other.accessToken();
+
+ m_profiles = other.m_profiles;
+ m_currentProfile = other.m_currentProfile;
+}
+
+
+QString MojangAccount::username() const
+{
+ return m_username;
+}
+
+QString MojangAccount::clientToken() const
+{
+ return m_clientToken;
+}
+
+void MojangAccount::setClientToken(const QString& clientToken)
+{
+ m_clientToken = clientToken;
+}
+
+
+QString MojangAccount::accessToken() const
+{
+ return m_accessToken;
+}
+
+void MojangAccount::setAccessToken(const QString& accessToken)
+{
+ m_accessToken = accessToken;
+}
+
+QString MojangAccount::sessionId() const
+{
+ return "token:" + m_accessToken + ":" + currentProfile()->id();
+}
+
+const QList<AccountProfile> MojangAccount::profiles() const
+{
+ return m_profiles;
+}
+
+const AccountProfile* MojangAccount::currentProfile() const
+{
+ if (m_currentProfile < 0)
+ {
+ if (m_profiles.length() > 0)
+ return &m_profiles.at(0);
+ else
+ return nullptr;
+ }
+ else
+ return &m_profiles.at(m_currentProfile);
+}
+
+bool MojangAccount::setProfile(const QString& profileId)
+{
+ const QList<AccountProfile>& profiles = this->profiles();
+ for (int i = 0; i < profiles.length(); i++)
+ {
+ if (profiles.at(i).id() == profileId)
+ {
+ m_currentProfile = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+void MojangAccount::loadProfiles(const ProfileList& profiles)
+{
+ m_profiles.clear();
+ for (auto profile : profiles)
+ m_profiles.append(profile);
+}
+
+MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject& object)
+{
+ // The JSON object must at least have a username for it to be valid.
+ if (!object.value("username").isString())
+ {
+ QLOG_ERROR() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
+ return nullptr;
+ }
+
+ QString username = object.value("username").toString("");
+ QString clientToken = object.value("clientToken").toString("");
+ QString accessToken = object.value("accessToken").toString("");
+
+ QJsonArray profileArray = object.value("profiles").toArray();
+ if (profileArray.size() < 1)
+ {
+ QLOG_ERROR() << "Can't load Mojang account with username \"" << username << "\". No profiles found.";
+ return nullptr;
+ }
+
+ ProfileList profiles;
+ for (QJsonValue profileVal : profileArray)
+ {
+ QJsonObject profileObject = profileVal.toObject();
+ QString id = profileObject.value("id").toString("");
+ QString name = profileObject.value("name").toString("");
+ if (id.isEmpty() || name.isEmpty())
+ {
+ QLOG_WARN() << "Unable to load a profile because it was missing an ID or a name.";
+ continue;
+ }
+ profiles.append(AccountProfile(id, name));
+ }
+
+ MojangAccountPtr account(new MojangAccount(username, clientToken, accessToken));
+ account->loadProfiles(profiles);
+
+ // Get the currently selected profile.
+ QString currentProfile = object.value("activeProfile").toString("");
+ if (!currentProfile.isEmpty())
+ account->setProfile(currentProfile);
+
+ return account;
+}
+
+QJsonObject MojangAccount::saveToJson()
+{
+ QJsonObject json;
+ json.insert("username", username());
+ json.insert("clientToken", clientToken());
+ json.insert("accessToken", accessToken());
+
+ QJsonArray profileArray;
+ for (AccountProfile profile : m_profiles)
+ {
+ QJsonObject profileObj;
+ profileObj.insert("id", profile.id());
+ profileObj.insert("name", profile.name());
+ profileArray.append(profileObj);
+ }
+ json.insert("profiles", profileArray);
+
+ if (currentProfile() != nullptr)
+ json.insert("activeProfile", currentProfile()->id());
+
+ return json;
+}
+
+
+AccountProfile::AccountProfile(const QString& id, const QString& name)
+{
+ m_id = id;
+ m_name = name;
+}
+
+AccountProfile::AccountProfile(const AccountProfile& other)
+{
+ m_id = other.m_id;
+ m_name = other.m_name;
+}
+
+QString AccountProfile::id() const
+{
+ return m_id;
+}
+
+QString AccountProfile::name() const
+{
+ return m_name;
+}
+
+
diff --git a/logic/auth/MojangAccount.h b/logic/auth/MojangAccount.h
new file mode 100644
index 00000000..062b8aa2
--- /dev/null
+++ b/logic/auth/MojangAccount.h
@@ -0,0 +1,154 @@
+/* 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.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QString>
+#include <QList>
+#include <QJsonObject>
+
+#include <memory>
+
+class MojangAccount;
+
+typedef std::shared_ptr<MojangAccount> MojangAccountPtr;
+Q_DECLARE_METATYPE(MojangAccountPtr)
+
+
+/**
+ * Class that represents a profile within someone's Mojang account.
+ *
+ * Currently, the profile system has not been implemented by Mojang yet,
+ * but we might as well add some things for it in MultiMC right now so
+ * we don't have to rip the code to pieces to add it later.
+ */
+class AccountProfile
+{
+public:
+ AccountProfile(const QString& id, const QString& name);
+ AccountProfile(const AccountProfile& other);
+
+ QString id() const;
+ QString name() const;
+protected:
+ QString m_id;
+ QString m_name;
+};
+
+
+typedef QList<AccountProfile> ProfileList;
+
+
+/**
+ * Object that stores information about a certain Mojang account.
+ *
+ * Said information may include things such as that account's username, client token, and access
+ * token if the user chose to stay logged in.
+ */
+class MojangAccount : public QObject
+{
+Q_OBJECT
+public:
+ /**
+ * Constructs a new MojangAccount with the given username.
+ * The client token will be generated automatically and the access token will be blank.
+ */
+ explicit MojangAccount(const QString& username, QObject* parent = 0);
+
+ /**
+ * Constructs a new MojangAccount with the given username, client token, and access token.
+ */
+ explicit MojangAccount(const QString& username, const QString& clientToken, const QString& accessToken, QObject* parent = 0);
+
+ /**
+ * Constructs a new MojangAccount matching the given account.
+ */
+ MojangAccount(const MojangAccount& other, QObject* parent);
+
+ /**
+ * Loads a MojangAccount from the given JSON object.
+ */
+ static MojangAccountPtr loadFromJson(const QJsonObject& json);
+
+ /**
+ * Saves a MojangAccount to a JSON object and returns it.
+ */
+ QJsonObject saveToJson();
+
+
+ /**
+ * This MojangAccount's username. May be an email address if the account is migrated.
+ */
+ QString username() const;
+
+ /**
+ * This MojangAccount's client token. This is a UUID used by Mojang's auth servers to identify this client.
+ * This is unique for each MojangAccount.
+ */
+ QString clientToken() const;
+
+ /**
+ * Sets the MojangAccount's client token to the given value.
+ */
+ void setClientToken(const QString& token);
+
+ /**
+ * This MojangAccount's access token.
+ * If the user has not chosen to stay logged in, this will be an empty string.
+ */
+ QString accessToken() const;
+
+ /**
+ * Changes this MojangAccount's access token to the given value.
+ */
+ void setAccessToken(const QString& token);
+
+ /**
+ * Get full session ID
+ */
+ QString sessionId() const;
+
+ /**
+ * Returns a list of the available account profiles.
+ */
+ const ProfileList profiles() const;
+
+ /**
+ * Returns a pointer to the currently selected profile.
+ * If no profile is selected, returns the first profile in the profile list or nullptr if there are none.
+ */
+ const AccountProfile* currentProfile() const;
+
+ /**
+ * Sets the currently selected profile to the profile with the given ID string.
+ * If profileId is not in the list of available profiles, the function will simply return false.
+ */
+ bool setProfile(const QString& profileId);
+
+ /**
+ * Clears the current account profile list and replaces it with the given profile list.
+ */
+ void loadProfiles(const ProfileList& profiles);
+
+
+protected:
+ QString m_username;
+ QString m_clientToken;
+ QString m_accessToken; // Blank if not logged in.
+ int m_currentProfile; // Index of the selected profile within the list of available profiles. -1 if nothing is selected.
+ ProfileList m_profiles; // List of available profiles.
+};
+
diff --git a/logic/auth/YggdrasilTask.cpp b/logic/auth/YggdrasilTask.cpp
new file mode 100644
index 00000000..39dfb749
--- /dev/null
+++ b/logic/auth/YggdrasilTask.cpp
@@ -0,0 +1,182 @@
+/* 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>
+
+YggdrasilTask::YggdrasilTask(MojangAccountPtr account, QObject* parent) : Task(parent)
+{
+ m_error = nullptr;
+ m_account = account;
+}
+
+
+YggdrasilTask::~YggdrasilTask()
+{
+ if (m_error)
+ delete m_error;
+}
+
+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();
+ connect(worker.get(), SIGNAL(finished(QNetworkReply*)), this,
+ SLOT(processReply(QNetworkReply*)));
+
+ QUrl reqUrl("https://authserver.mojang.com/" + getEndpoint());
+ QNetworkRequest netRequest(reqUrl);
+ netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+ m_netReply = worker->post(netRequest, doc.toJson());
+}
+
+void YggdrasilTask::processReply(QNetworkReply* reply)
+{
+ setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
+
+ if (m_netReply != reply)
+ // Wrong reply for some reason...
+ return;
+
+ // Check for errors.
+ switch (reply->error())
+ {
+ case QNetworkReply::NoError:
+ {
+ // 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 = reply->readAll();
+ QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
+
+ // Check the response code.
+ int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ switch (responseCode)
+ {
+ case 200:
+ {
+ // If the response code was 200, then there shouldn't be an error. Make sure anyways.
+ switch (jsonError.error)
+ {
+ case QJsonParseError::NoError:
+ if (!processResponse(doc.object()))
+ {
+ YggdrasilTask::Error* err = getError();
+ if (err)
+ emitFailed(err->getErrorMessage());
+ else
+ emitFailed(tr("An unknown error occurred when processing the response from the authentication server."));
+ }
+ else
+ {
+ emitSucceeded();
+ }
+ break;
+
+ default:
+ emitFailed(tr("Failed to parse Yggdrasil JSON response: \"%1\".").arg(jsonError.errorString()));
+ break;
+ }
+ break;
+ }
+
+ default:
+ // If the response code was something else, 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.
+ switch (jsonError.error)
+ {
+ case 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.
+ processError(doc.object());
+ break;
+
+ default:
+ // The server didn't say anything regarding the error. Give the user an unknown error.
+ emitFailed(tr("Login failed: Unknown HTTP code %1 encountered.").arg(responseCode));
+ break;
+ }
+ break;
+ }
+
+ break;
+ }
+
+ case QNetworkReply::OperationCanceledError:
+ emitFailed(tr("Login canceled."));
+ break;
+
+ default:
+ emitFailed(tr("An unknown error occurred when trying to communicate with the authentication server."));
+ break;
+ }
+}
+
+QString YggdrasilTask::processError(QJsonObject responseData)
+{
+ QJsonValue errorVal = responseData.value("error");
+ QJsonValue msgVal = responseData.value("errorMessage");
+ QJsonValue causeVal = responseData.value("cause");
+
+ if (errorVal.isString() && msgVal.isString() && causeVal.isString())
+ {
+ m_error = new Error(errorVal.toString(""), msgVal.toString(""), causeVal.toString(""));
+ return m_error->getDisplayMessage();
+ }
+ 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.");
+ }
+}
+
+YggdrasilTask::Error *YggdrasilTask::getError() const
+{
+ return this->m_error;
+}
+
+MojangAccountPtr YggdrasilTask::getMojangAccount() const
+{
+ return this->m_account;
+}
+
diff --git a/logic/auth/YggdrasilTask.h b/logic/auth/YggdrasilTask.h
new file mode 100644
index 00000000..6aebae16
--- /dev/null
+++ b/logic/auth/YggdrasilTask.h
@@ -0,0 +1,128 @@
+/* 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.
+ */
+
+#pragma once
+
+#include <logic/tasks/Task.h>
+
+#include <QString>
+#include <QJsonObject>
+
+#include "logic/auth/MojangAccount.h"
+
+class QNetworkReply;
+
+
+/**
+ * A Yggdrasil task is a task that performs an operation on a given mojang account.
+ */
+class YggdrasilTask : public Task
+{
+Q_OBJECT
+public:
+ explicit YggdrasilTask(MojangAccountPtr account, QObject* parent=0);
+ ~YggdrasilTask();
+
+ /**
+ * Class describing a Yggdrasil error response.
+ */
+ class Error
+ {
+ public:
+ Error(const QString& shortError, const QString& errorMessage, const QString& cause) :
+ m_shortError(shortError), m_errorMessage(errorMessage), m_cause(cause) {}
+
+ QString getShortError() const { return m_shortError; }
+ QString getErrorMessage() const { return m_errorMessage; }
+ QString getCause() const { return m_cause; }
+
+ /// Gets the string to display in the GUI for describing this error.
+ QString getDisplayMessage() { return getErrorMessage(); }
+
+ protected:
+ QString m_shortError;
+ QString m_errorMessage;
+ QString m_cause;
+ };
+
+ /**
+ * Gets the Mojang account that this task is operating on.
+ */
+ virtual MojangAccountPtr getMojangAccount() const;
+
+ /**
+ * Returns a pointer to a YggdrasilTask::Error object if an error has occurred.
+ * If no error has occurred, returns a null pointer.
+ */
+ virtual Error* getError() const;
+
+protected:
+ /**
+ * Enum for describing the state of the current task.
+ * Used by the getStateMessage function to determine what the status message should be.
+ */
+ enum State
+ {
+ STATE_SENDING_REQUEST,
+ STATE_PROCESSING_RESPONSE,
+ STATE_OTHER,
+ };
+
+ virtual void executeTask();
+
+ /**
+ * Gets the JSON object that will be sent to the authentication server.
+ * Should be overridden by subclasses.
+ */
+ virtual QJsonObject getRequestContent() const = 0;
+
+ /**
+ * Gets the endpoint to POST to.
+ * No leading slash.
+ */
+ virtual QString getEndpoint() const = 0;
+
+ /**
+ * Processes the response received from the server.
+ * If an error occurred, this should emit a failed signal and return false.
+ * If Yggdrasil gave an error response, it should call setError() first, and then return false.
+ * Otherwise, it should return true.
+ */
+ virtual bool processResponse(QJsonObject responseData) = 0;
+
+ /**
+ * Processes an error response received from the server.
+ * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
+ * \returns a QString error message that will be passed to emitFailed.
+ */
+ virtual QString processError(QJsonObject responseData);
+
+ /**
+ * Returns the state message for the given state.
+ * Used to set the status message for the task.
+ * Should be overridden by subclasses that want to change messages for a given state.
+ */
+ virtual QString getStateMessage(const State state) const;
+
+ MojangAccountPtr m_account;
+
+ QNetworkReply* m_netReply;
+
+ Error* m_error;
+
+protected slots:
+ void processReply(QNetworkReply* reply);
+};
+