summaryrefslogtreecommitdiffstats
path: root/logic/auth/flows
diff options
context:
space:
mode:
authorAndrew <forkk@forkk.net>2013-12-01 14:28:47 -0600
committerAndrew <forkk@forkk.net>2013-12-01 14:28:47 -0600
commit613699b3626aea750093ab7eaaeccaa28c0e87c6 (patch)
tree7fd542b8f4be10359ba2aaeac9834f1b5c86a750 /logic/auth/flows
parent2427ad687137bbcfc7303a931d7e0b74d5325e48 (diff)
parent75c4d97b90e39f2bda6e682def0fdda044cbcf6f (diff)
downloadMultiMC-613699b3626aea750093ab7eaaeccaa28c0e87c6.tar
MultiMC-613699b3626aea750093ab7eaaeccaa28c0e87c6.tar.gz
MultiMC-613699b3626aea750093ab7eaaeccaa28c0e87c6.tar.lz
MultiMC-613699b3626aea750093ab7eaaeccaa28c0e87c6.tar.xz
MultiMC-613699b3626aea750093ab7eaaeccaa28c0e87c6.zip
Merge branch 'develop' of github.com:MultiMC/MultiMC5 into develop
Diffstat (limited to 'logic/auth/flows')
-rw-r--r--logic/auth/flows/AuthenticateTask.cpp206
-rw-r--r--logic/auth/flows/AuthenticateTask.h46
-rw-r--r--logic/auth/flows/InvalidateTask.cpp0
-rw-r--r--logic/auth/flows/InvalidateTask.h0
-rw-r--r--logic/auth/flows/RefreshTask.cpp156
-rw-r--r--logic/auth/flows/RefreshTask.h43
-rw-r--r--logic/auth/flows/ValidateTask.cpp64
-rw-r--r--logic/auth/flows/ValidateTask.h43
8 files changed, 558 insertions, 0 deletions
diff --git a/logic/auth/flows/AuthenticateTask.cpp b/logic/auth/flows/AuthenticateTask.cpp
new file mode 100644
index 00000000..ec2004d6
--- /dev/null
+++ b/logic/auth/flows/AuthenticateTask.cpp
@@ -0,0 +1,206 @@
+
+/* 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/flows/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
+ * "requestUser": true/false // request the user structure
+ * }
+ */
+ 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);
+ req.insert("requestUser", true);
+
+ // 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;
+ }
+
+ // this is what the vanilla launcher passes to the userProperties launch param
+ // doesn't seem to be used for anything so far? I don't get any of this data on my account
+ // (peterixxx)
+ // is it a good idea to log this?
+ if (responseData.contains("user"))
+ {
+ auto obj = responseData.value("user").toObject();
+ auto userId = obj.value("id").toString();
+ auto propArray = obj.value("properties").toArray();
+ QLOG_DEBUG() << "User ID: " << userId;
+ QLOG_DEBUG() << "User Properties: ";
+ for (auto prop : propArray)
+ {
+ auto propTuple = prop.toObject();
+ auto name = propTuple.value("name").toString();
+ auto value = propTuple.value("value").toString();
+ QLOG_DEBUG() << name << " : " << value;
+ }
+ }
+
+ // 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/flows/AuthenticateTask.h b/logic/auth/flows/AuthenticateTask.h
new file mode 100644
index 00000000..3b99caad
--- /dev/null
+++ b/logic/auth/flows/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/flows/InvalidateTask.cpp b/logic/auth/flows/InvalidateTask.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/logic/auth/flows/InvalidateTask.cpp
diff --git a/logic/auth/flows/InvalidateTask.h b/logic/auth/flows/InvalidateTask.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/logic/auth/flows/InvalidateTask.h
diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp
new file mode 100644
index 00000000..b56ed9bc
--- /dev/null
+++ b/logic/auth/flows/RefreshTask.cpp
@@ -0,0 +1,156 @@
+/* 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/flows/RefreshTask.h>
+
+#include <logic/auth/MojangAccount.h>
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QVariant>
+#include <QDebug>
+
+#include "logger/QsLog.h"
+
+RefreshTask::RefreshTask(MojangAccountPtr account, QObject *parent)
+ : YggdrasilTask(account, parent)
+{
+}
+
+QJsonObject RefreshTask::getRequestContent() const
+{
+ /*
+ * {
+ * "clientToken": "client identifier"
+ * "accessToken": "current access token to be refreshed"
+ * "selectedProfile": // specifying this causes errors
+ * {
+ * "id": "profile ID"
+ * "name": "profile name"
+ * }
+ * "requestUser": true/false // request the user structure
+ * }
+ */
+ auto account = getMojangAccount();
+ QJsonObject req;
+ req.insert("clientToken", account->clientToken());
+ req.insert("accessToken", account->accessToken());
+ /*
+ {
+ auto currentProfile = account->currentProfile();
+ QJsonObject profile;
+ profile.insert("id", currentProfile->id());
+ profile.insert("name", currentProfile->name());
+ req.insert("selectedProfile", profile);
+ }
+ */
+ req.insert("requestUser", true);
+
+ return req;
+}
+
+bool RefreshTask::processResponse(QJsonObject responseData)
+{
+ auto account = getMojangAccount();
+
+ // 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.
+ 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 (!account->clientToken().isEmpty() && clientToken != account->clientToken())
+ {
+ // The server changed our client token! Obey its wishes, but complain. That's what I do
+ // for my parents, so...
+ QLOG_ERROR() << "Server changed our client token to '" << clientToken
+ << "'. This shouldn't happen, but it isn't really a big deal.";
+ return false;
+ }
+
+ // Now, we set the access token.
+ QLOG_DEBUG() << "Getting new 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.";
+ return false;
+ }
+
+ // we validate that the server responded right. (our current profile = returned current
+ // profile)
+ QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
+ QString currentProfileId = currentProfile.value("id").toString("");
+ if (account->currentProfile()->id() != currentProfileId)
+ {
+ // TODO: Set an error to display to the user.
+ QLOG_ERROR() << "Server didn't specify the same selected profile as ours.";
+ return false;
+ }
+
+ // this is what the vanilla launcher passes to the userProperties launch param
+ if (responseData.contains("user"))
+ {
+ auto obj = responseData.value("user").toObject();
+ auto userId = obj.value("id").toString();
+ auto propArray = obj.value("properties").toArray();
+ QLOG_DEBUG() << "User ID: " << userId;
+ QLOG_DEBUG() << "User Properties: ";
+ for (auto prop : propArray)
+ {
+ auto propTuple = prop.toObject();
+ auto name = propTuple.value("name").toString();
+ auto value = propTuple.value("value").toString();
+ QLOG_DEBUG() << name << " : " << value;
+ }
+ }
+
+ // We've made it through the minefield of possible errors. Return true to indicate that
+ // we've succeeded.
+ QLOG_DEBUG() << "Finished reading refresh response.";
+ // Reset the access token.
+ account->setAccessToken(accessToken);
+ account->propagateChange();
+ return true;
+}
+
+QString RefreshTask::getEndpoint() const
+{
+ return "refresh";
+}
+
+QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const
+{
+ switch (state)
+ {
+ case STATE_SENDING_REQUEST:
+ return tr("Refreshing: Sending request.");
+ case STATE_PROCESSING_RESPONSE:
+ return tr("Refreshing: Processing response.");
+ default:
+ return YggdrasilTask::getStateMessage(state);
+ }
+}
diff --git a/logic/auth/flows/RefreshTask.h b/logic/auth/flows/RefreshTask.h
new file mode 100644
index 00000000..2596f6c7
--- /dev/null
+++ b/logic/auth/flows/RefreshTask.h
@@ -0,0 +1,43 @@
+/* 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 a possibly timed-out access token
+ * and attempts to authenticate with Mojang's servers.
+ * If successful, it will set the new access token. The token is considered validated.
+ */
+class RefreshTask : public YggdrasilTask
+{
+ Q_OBJECT
+public:
+ RefreshTask(MojangAccountPtr account, QObject *parent = 0);
+
+protected:
+ virtual QJsonObject getRequestContent() const;
+
+ virtual QString getEndpoint() const;
+
+ virtual bool processResponse(QJsonObject responseData);
+
+ QString getStateMessage(const YggdrasilTask::State state) const;
+};
diff --git a/logic/auth/flows/ValidateTask.cpp b/logic/auth/flows/ValidateTask.cpp
new file mode 100644
index 00000000..d9e0e46b
--- /dev/null
+++ b/logic/auth/flows/ValidateTask.cpp
@@ -0,0 +1,64 @@
+
+/* 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/flows/ValidateTask.h>
+
+#include <logic/auth/MojangAccount.h>
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QVariant>
+#include <QDebug>
+
+#include "logger/QsLog.h"
+
+ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent)
+ : YggdrasilTask(account, parent)
+{
+}
+
+QJsonObject ValidateTask::getRequestContent() const
+{
+ QJsonObject req;
+ req.insert("accessToken", getMojangAccount()->accessToken());
+ return req;
+}
+
+bool ValidateTask::processResponse(QJsonObject responseData)
+{
+ // Assume that if processError wasn't called, then the request was successful.
+ emitSucceeded();
+ return true;
+}
+
+QString ValidateTask::getEndpoint() const
+{
+ return "validate";
+}
+
+QString ValidateTask::getStateMessage(const YggdrasilTask::State state) const
+{
+ switch (state)
+ {
+ case STATE_SENDING_REQUEST:
+ return tr("Validating Access Token: Sending request.");
+ case STATE_PROCESSING_RESPONSE:
+ return tr("Validating Access Token: Processing response.");
+ default:
+ return YggdrasilTask::getStateMessage(state);
+ }
+}
diff --git a/logic/auth/flows/ValidateTask.h b/logic/auth/flows/ValidateTask.h
new file mode 100644
index 00000000..3ff78c6a
--- /dev/null
+++ b/logic/auth/flows/ValidateTask.h
@@ -0,0 +1,43 @@
+/* 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 validate task takes a MojangAccount and checks to make sure its access token is valid.
+ */
+class ValidateTask : public YggdrasilTask
+{
+ Q_OBJECT
+public:
+ ValidateTask(MojangAccountPtr account, 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:
+};