diff options
Diffstat (limited to 'logic/auth/flows')
-rw-r--r-- | logic/auth/flows/AuthenticateTask.cpp | 206 | ||||
-rw-r--r-- | logic/auth/flows/AuthenticateTask.h | 46 | ||||
-rw-r--r-- | logic/auth/flows/InvalidateTask.cpp | 0 | ||||
-rw-r--r-- | logic/auth/flows/InvalidateTask.h | 0 | ||||
-rw-r--r-- | logic/auth/flows/RefreshTask.cpp | 156 | ||||
-rw-r--r-- | logic/auth/flows/RefreshTask.h | 43 | ||||
-rw-r--r-- | logic/auth/flows/ValidateTask.cpp | 64 | ||||
-rw-r--r-- | logic/auth/flows/ValidateTask.h | 43 |
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: +}; |