diff options
Diffstat (limited to 'logic/auth')
-rw-r--r-- | logic/auth/MojangAccount.cpp | 236 | ||||
-rw-r--r-- | logic/auth/MojangAccount.h | 210 | ||||
-rw-r--r-- | logic/auth/MojangAccountList.cpp | 413 | ||||
-rw-r--r-- | logic/auth/MojangAccountList.h | 194 | ||||
-rw-r--r-- | logic/auth/YggdrasilTask.cpp | 171 | ||||
-rw-r--r-- | logic/auth/YggdrasilTask.h | 62 | ||||
-rw-r--r-- | logic/auth/flows/AuthenticateTask.cpp | 37 | ||||
-rw-r--r-- | logic/auth/flows/AuthenticateTask.h | 2 | ||||
-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 | 32 | ||||
-rw-r--r-- | logic/auth/flows/RefreshTask.h | 2 | ||||
-rw-r--r-- | logic/auth/flows/ValidateTask.cpp | 4 | ||||
-rw-r--r-- | logic/auth/flows/ValidateTask.h | 6 |
14 files changed, 987 insertions, 382 deletions
diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp index 4a61cf19..bc6af98f 100644 --- a/logic/auth/MojangAccount.cpp +++ b/logic/auth/MojangAccount.cpp @@ -16,113 +16,17 @@ */ #include "MojangAccount.h" +#include "flows/RefreshTask.h" +#include "flows/AuthenticateTask.h" #include <QUuid> #include <QJsonObject> #include <QJsonArray> +#include <QRegExp> +#include <QStringList> #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. @@ -143,78 +47,160 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) return nullptr; } - ProfileList profiles; + QList<AccountProfile> profiles; for (QJsonValue profileVal : profileArray) { QJsonObject profileObject = profileVal.toObject(); QString id = profileObject.value("id").toString(""); QString name = profileObject.value("name").toString(""); + bool legacy = profileObject.value("legacy").toBool(false); 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)); + profiles.append({id, name, legacy}); } - MojangAccountPtr account(new MojangAccount(username, clientToken, accessToken)); - account->loadProfiles(profiles); + MojangAccountPtr account(new MojangAccount()); + if(object.value("user").isObject()) + { + User u; + QJsonObject userStructure = object.value("user").toObject(); + u.id = userStructure.value("id").toString(); + /* + QJsonObject propMap = userStructure.value("properties").toObject(); + for(auto key: propMap.keys()) + { + auto values = propMap.operator[](key).toArray(); + for(auto value: values) + u.properties.insert(key, value.toString()); + } + */ + account->m_user = u; + } + account->m_username = username; + account->m_clientToken = clientToken; + account->m_accessToken = accessToken; + account->m_profiles = profiles; // Get the currently selected profile. QString currentProfile = object.value("activeProfile").toString(""); if (!currentProfile.isEmpty()) - account->setProfile(currentProfile); + account->setCurrentProfile(currentProfile); + + return account; +} +MojangAccountPtr MojangAccount::createFromUsername(const QString& username) +{ + MojangAccountPtr account(new MojangAccount()); + account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->m_username = username; return account; } -QJsonObject MojangAccount::saveToJson() +QJsonObject MojangAccount::saveToJson() const { QJsonObject json; - json.insert("username", username()); - json.insert("clientToken", clientToken()); - json.insert("accessToken", accessToken()); + json.insert("username", m_username); + json.insert("clientToken", m_clientToken); + json.insert("accessToken", m_accessToken); QJsonArray profileArray; for (AccountProfile profile : m_profiles) { QJsonObject profileObj; - profileObj.insert("id", profile.id()); - profileObj.insert("name", profile.name()); + profileObj.insert("id", profile.id); + profileObj.insert("name", profile.name); + profileObj.insert("legacy", profile.legacy); profileArray.append(profileObj); } json.insert("profiles", profileArray); - if (currentProfile() != nullptr) - json.insert("activeProfile", currentProfile()->id()); + QJsonObject userStructure; + { + userStructure.insert("id", m_user.id); + /* + QJsonObject userAttrs; + for(auto key: m_user.properties.keys()) + { + auto array = QJsonArray::fromStringList(m_user.properties.values(key)); + userAttrs.insert(key, array); + } + userStructure.insert("properties", userAttrs); + */ + } + json.insert("user", userStructure); + + if (m_currentProfile != -1) + json.insert("activeProfile", currentProfile()->id); return json; } - -AccountProfile::AccountProfile(const QString& id, const QString& name) +bool MojangAccount::setCurrentProfile(const QString &profileId) { - m_id = id; - m_name = name; + for (int i = 0; i < m_profiles.length(); i++) + { + if (m_profiles[i].id == profileId) + { + m_currentProfile = i; + return true; + } + } + return false; } -AccountProfile::AccountProfile(const AccountProfile &other) +const AccountProfile* MojangAccount::currentProfile() const { - m_id = other.m_id; - m_name = other.m_name; + if(m_currentProfile == -1) + return nullptr; + return &m_profiles[m_currentProfile]; } -QString AccountProfile::id() const +AccountStatus MojangAccount::accountStatus() const { - return m_id; + if(m_accessToken.isEmpty()) + return NotVerified; + if(!m_online) + return Verified; + return Online; } -QString AccountProfile::name() const +std::shared_ptr<Task> MojangAccount::login(QString password) { - return m_name; + if(m_currentTask) + return m_currentTask; + if(password.isEmpty()) + { + m_currentTask.reset(new RefreshTask(this)); + } + else + { + m_currentTask.reset(new AuthenticateTask(this, password)); + } + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + return m_currentTask; } -void MojangAccount::propagateChange() +void MojangAccount::authSucceeded() { + m_online = true; + m_currentTask.reset(); emit changed(); } + +void MojangAccount::authFailed(QString reason) +{ + // This is emitted when the yggdrasil tasks time out or are cancelled. + // -> we treat the error as no-op + if(reason != "Yggdrasil task cancelled.") + { + m_online = false; + m_accessToken = QString(); + emit changed(); + } + m_currentTask.reset(); +} diff --git a/logic/auth/MojangAccount.h b/logic/auth/MojangAccount.h index 25a85790..325aa826 100644 --- a/logic/auth/MojangAccount.h +++ b/logic/auth/MojangAccount.h @@ -20,43 +20,42 @@ #include <QList> #include <QJsonObject> #include <QPair> +#include <QMap> #include <memory> +class Task; +class YggdrasilTask; class MojangAccount; typedef std::shared_ptr<MojangAccount> MojangAccountPtr; Q_DECLARE_METATYPE(MojangAccountPtr) /** - * Class that represents a profile within someone's Mojang account. + * 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 +struct 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; + QString id; + QString name; + bool legacy; }; -typedef QList<AccountProfile> ProfileList; - struct User { QString id; - // pair of key:value - // we don't know if the keys:value mapping is 1:1, so a list is used. - QList<QPair<QString, QString>> properties; + QMultiMap<QString,QString> properties; +}; + +enum AccountStatus +{ + NotVerified, + Verified, + Online }; /** @@ -68,106 +67,121 @@ struct User 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); +public: /* construction */ + //! Do not copy accounts. ever. + explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; - /** - * 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); + //! Default constructor + explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; - /** - * Constructs a new MojangAccount matching the given account. - */ - MojangAccount(const MojangAccount &other, QObject *parent); + //! Creates an empty account for the specified user name. + static MojangAccountPtr createFromUsername(const QString &username); - /** - * Loads a MojangAccount from the given JSON object. - */ + //! 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(); + //! Saves a MojangAccount to a JSON object and returns it. + QJsonObject saveToJson() const; +public: /* manipulation */ /** - * Update the account on disk and lists (it changed, for whatever reason) - * This is called by various Yggdrasil tasks. - */ - void propagateChange(); + * 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 setCurrentProfile(const QString &profileId); + + /** + * Attempt to login. Empty password means we use the token. + * If the attempt fails because we already are performing some task, it returns false. + */ + std::shared_ptr<Task> login(QString password = QString()); + + void downgrade() + { + m_online = false; + } +public: /* queries */ + const QString &username() const + { + return m_username; + } + + const QString &clientToken() const + { + return m_clientToken; + } + + const QString &accessToken() const + { + return m_accessToken; + } + + const QList<AccountProfile> &profiles() const + { + return m_profiles; + } + + const User & user() + { + return m_user; + } + + //! Get the session ID required for legacy Minecraft versions + QString sessionId() const + { + if (m_currentProfile != -1 && !m_accessToken.isEmpty()) + return "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; + return "-"; + } + + //! Returns the currently selected profile (if none, returns nullptr) + const AccountProfile *currentProfile() const; - /** - * This MojangAccount's username. May be an email address if the account is migrated. - */ - QString username() const; + //! Returns whether the account is NotVerified, Verified or Online + AccountStatus accountStatus() const; +signals: /** - * 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. + * This signal is emitted when the account changes */ - QString clientToken() const; + void changed(); - /** - * Sets the MojangAccount's client token to the given value. - */ - void setClientToken(const QString &token); + // TODO: better signalling for the various possible state changes - especially errors - /** - * This MojangAccount's access token. - * If the user has not chosen to stay logged in, this will be an empty string. - */ - QString accessToken() const; +protected: /* variables */ + QString m_username; - /** - * Changes this MojangAccount's access token to the given value. - */ - void setAccessToken(const QString &token); + // Used to identify the client - the user can have multiple clients for the same account + // Think: different launchers, all connecting to the same account/profile + QString m_clientToken; - /** - * Get full session ID - */ - QString sessionId() const; + // Blank if not logged in. + QString m_accessToken; - /** - * Returns a list of the available account profiles. - */ - const ProfileList profiles() const; + // Index of the selected profile within the list of available + // profiles. -1 if nothing is selected. + int m_currentProfile = -1; - /** - * 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; + // List of available profiles. + QList<AccountProfile> m_profiles; - /** - * 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); + // the user structure, whatever it is. + User m_user; - /** - * Clears the current account profile list and replaces it with the given profile list. - */ - void loadProfiles(const ProfileList &profiles); + // true when the account is verified + bool m_online = false; -signals: - /** - * This isgnal is emitted whrn the account changes - */ - void changed(); + // current task we are executing here + std::shared_ptr<YggdrasilTask> m_currentTask; -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. - User m_user; // the user structure, whatever it is. +private slots: + void authSucceeded(); + void authFailed(QString reason); + +public: + friend class YggdrasilTask; + friend class AuthenticateTask; + friend class ValidateTask; + friend class RefreshTask; }; diff --git a/logic/auth/MojangAccountList.cpp b/logic/auth/MojangAccountList.cpp new file mode 100644 index 00000000..33990662 --- /dev/null +++ b/logic/auth/MojangAccountList.cpp @@ -0,0 +1,413 @@ +/* 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/MojangAccountList.h" + +#include <QIODevice> +#include <QFile> +#include <QTextStream> +#include <QJsonDocument> +#include <QJsonArray> +#include <QJsonObject> +#include <QJsonParseError> +#include <QDir> + +#include "logger/QsLog.h" + +#include "logic/auth/MojangAccount.h" + +#define ACCOUNT_LIST_FORMAT_VERSION 2 + +MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent) +{ +} + +MojangAccountPtr MojangAccountList::findAccount(const QString &username) const +{ + for (int i = 0; i < count(); i++) + { + MojangAccountPtr account = at(i); + if (account->username() == username) + return account; + } + return nullptr; +} + +const MojangAccountPtr MojangAccountList::at(int i) const +{ + return MojangAccountPtr(m_accounts.at(i)); +} + +void MojangAccountList::addAccount(const MojangAccountPtr account) +{ + beginResetModel(); + connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); + m_accounts.append(account); + endResetModel(); + onListChanged(); +} + +void MojangAccountList::removeAccount(const QString &username) +{ + beginResetModel(); + for (auto account : m_accounts) + { + if (account->username() == username) + { + m_accounts.removeOne(account); + return; + } + } + endResetModel(); + onListChanged(); +} + +void MojangAccountList::removeAccount(QModelIndex index) +{ + beginResetModel(); + m_accounts.removeAt(index.row()); + endResetModel(); + onListChanged(); +} + +MojangAccountPtr MojangAccountList::activeAccount() const +{ + return m_activeAccount; +} + +void MojangAccountList::setActiveAccount(const QString &username) +{ + beginResetModel(); + if (username.isEmpty()) + { + m_activeAccount = nullptr; + } + else + { + for (MojangAccountPtr account : m_accounts) + { + if (account->username() == username) + m_activeAccount = account; + } + } + endResetModel(); + onActiveChanged(); +} + +void MojangAccountList::accountChanged() +{ + // the list changed. there is no doubt. + onListChanged(); +} + +void MojangAccountList::onListChanged() +{ + if (m_autosave) + // TODO: Alert the user if this fails. + saveList(); + + emit listChanged(); +} + +void MojangAccountList::onActiveChanged() +{ + if (m_autosave) + saveList(); + + emit activeAccountChanged(); +} + +int MojangAccountList::count() const +{ + return m_accounts.count(); +} + +QVariant MojangAccountList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + MojangAccountPtr account = at(index.row()); + + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case NameColumn: + return account->username(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return account->username(); + + case PointerRole: + return qVariantFromValue(account); + + case Qt::CheckStateRole: + switch (index.column()) + { + case ActiveColumn: + return account == m_activeAccount; + } + + default: + return QVariant(); + } +} + +QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case ActiveColumn: + return "Active?"; + + case NameColumn: + return "Name"; + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case NameColumn: + return "The name of the version."; + + default: + return QVariant(); + } + + default: + return QVariant(); + } +} + +int MojangAccountList::rowCount(const QModelIndex &parent) const +{ + // Return count + return count(); +} + +int MojangAccountList::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const +{ + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return Qt::NoItemFlags; + } + + return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return false; + } + + if(role == Qt::CheckStateRole) + { + if(value == Qt::Checked) + { + MojangAccountPtr account = this->at(index.row()); + this->setActiveAccount(account->username()); + } + } + + emit dataChanged(index, index); + return true; +} + +void MojangAccountList::updateListData(QList<MojangAccountPtr> versions) +{ + beginResetModel(); + m_accounts = versions; + endResetModel(); +} + +bool MojangAccountList::loadList(const QString &filePath) +{ + QString path = filePath; + if (path.isEmpty()) + path = m_listFilePath; + if (path.isEmpty()) + { + QLOG_ERROR() << "Can't load Mojang account list. No file path given and no default set."; + return false; + } + + if (!QDir::current().exists(path)) + { + QDir::current().mkpath(path); + } + + QFile file(path); + + // Try to open the file and fail if we can't. + // TODO: We should probably report this error to the user. + if (!file.open(QIODevice::ReadOnly)) + { + QLOG_ERROR() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); + return false; + } + + // Read the file and close it. + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); + + // Fail if the JSON is invalid. + if (parseError.error != QJsonParseError::NoError) + { + QLOG_ERROR() << QString("Failed to parse account list file: %1 at offset %2") + .arg(parseError.errorString(), QString::number(parseError.offset)) + .toUtf8(); + return false; + } + + // Make sure the root is an object. + if (!jsonDoc.isObject()) + { + QLOG_ERROR() << "Invalid account list JSON: Root should be an array."; + return false; + } + + QJsonObject root = jsonDoc.object(); + + // Make sure the format version matches. + if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION) + { + QString newName = "accounts-old.json"; + QLOG_WARN() << "Format version mismatch when loading account list. Existing one will be renamed to" + << newName; + + // Attempt to rename the old version. + file.rename(newName); + return false; + } + + // Now, load the accounts array. + beginResetModel(); + QJsonArray accounts = root.value("accounts").toArray(); + for (QJsonValue accountVal : accounts) + { + QJsonObject accountObj = accountVal.toObject(); + MojangAccountPtr account = MojangAccount::loadFromJson(accountObj); + if (account.get() != nullptr) + { + connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); + m_accounts.append(account); + } + else + { + QLOG_WARN() << "Failed to load an account."; + } + } + // Load the active account. + m_activeAccount = findAccount(root.value("activeAccount").toString("")); + endResetModel(); + return true; +} + +bool MojangAccountList::saveList(const QString &filePath) +{ + QString path(filePath); + if (path.isEmpty()) + path = m_listFilePath; + if (path.isEmpty()) + { + QLOG_ERROR() << "Can't save Mojang account list. No file path given and no default set."; + return false; + } + + if (!QDir::current().exists(path)) + { + QDir::current().mkpath(path); + } + + QLOG_INFO() << "Writing account list to" << path; + + QLOG_DEBUG() << "Building JSON data structure."; + // Build the JSON document to write to the list file. + QJsonObject root; + + root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION); + + // Build a list of accounts. + QLOG_DEBUG() << "Building account array."; + QJsonArray accounts; + for (MojangAccountPtr account : m_accounts) + { + QJsonObject accountObj = account->saveToJson(); + accounts.append(accountObj); + } + + // Insert the account list into the root object. + root.insert("accounts", accounts); + + if(m_activeAccount) + { + // Save the active account. + root.insert("activeAccount", m_activeAccount->username()); + } + + // Create a JSON document object to convert our JSON to bytes. + QJsonDocument doc(root); + + // Now that we're done building the JSON object, we can write it to the file. + QLOG_DEBUG() << "Writing account list to file."; + QFile file(path); + + // Try to open the file and fail if we can't. + // TODO: We should probably report this error to the user. + if (!file.open(QIODevice::WriteOnly)) + { + QLOG_ERROR() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); + return false; + } + + // Write the JSON to the file. + file.write(doc.toJson()); + file.close(); + + QLOG_INFO() << "Saved account list to" << path; + + return true; +} + +void MojangAccountList::setListFilePath(QString path, bool autosave) +{ + m_listFilePath = path; + m_autosave = autosave; +} diff --git a/logic/auth/MojangAccountList.h b/logic/auth/MojangAccountList.h new file mode 100644 index 00000000..c7e30958 --- /dev/null +++ b/logic/auth/MojangAccountList.h @@ -0,0 +1,194 @@ +/* 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 <QVariant> +#include <QAbstractListModel> +#include <QSharedPointer> + +#include "logic/auth/MojangAccount.h" + +/*! + * \brief List of available Mojang accounts. + * This should be loaded in the background by MultiMC on startup. + * + * This class also inherits from QAbstractListModel. Methods from that + * class determine how this list shows up in a list view. Said methods + * all have a default implementation, but they can be overridden by subclasses to + * change the behavior of the list. + */ +class MojangAccountList : public QAbstractListModel +{ + Q_OBJECT +public: + enum ModelRoles + { + PointerRole = 0x34B1CB48 + }; + + enum VListColumns + { + // TODO: Add icon column. + + // First column - Active? + ActiveColumn = 0, + + // Second column - Name + NameColumn, + }; + + explicit MojangAccountList(QObject *parent = 0); + + //! Gets the account at the given index. + virtual const MojangAccountPtr at(int i) const; + + //! Returns the number of accounts in the list. + virtual int count() const; + + //////// List Model Functions //////// + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role); + + /*! + * Adds a the given Mojang account to the account list. + */ + virtual void addAccount(const MojangAccountPtr account); + + /*! + * Removes the mojang account with the given username from the account list. + */ + virtual void removeAccount(const QString &username); + + /*! + * Removes the account at the given QModelIndex. + */ + virtual void removeAccount(QModelIndex index); + + /*! + * \brief Finds an account by its username. + * \param The username of the account to find. + * \return A const pointer to the account with the given username. NULL if + * one doesn't exist. + */ + virtual MojangAccountPtr findAccount(const QString &username) const; + + /*! + * Sets the default path to save the list file to. + * If autosave is true, this list will automatically save to the given path whenever it changes. + * THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately + * after calling this function to ensure an autosaved change doesn't overwrite the list you intended + * to load. + */ + virtual void setListFilePath(QString path, bool autosave = false); + + /*! + * \brief Loads the account list from the given file path. + * If the given file is an empty string (default), will load from the default account list file. + * \return True if successful, otherwise false. + */ + virtual bool loadList(const QString &file = ""); + + /*! + * \brief Saves the account list to the given file. + * If the given file is an empty string (default), will save from the default account list file. + * \return True if successful, otherwise false. + */ + virtual bool saveList(const QString &file = ""); + + /*! + * \brief Gets a pointer to the account that the user has selected as their "active" account. + * Which account is active can be overridden on a per-instance basis, but this will return the one that + * is set as active globally. + * \return The currently active MojangAccount. If there isn't an active account, returns a null pointer. + */ + virtual MojangAccountPtr activeAccount() const; + + /*! + * Sets the given account as the current active account. + * If the username given is an empty string, sets the active account to nothing. + */ + virtual void setActiveAccount(const QString &username); + +signals: + /*! + * Signal emitted to indicate that the account list has changed. + * This will also fire if the value of an element in the list changes (will be implemented + * later). + */ + void listChanged(); + + /*! + * Signal emitted to indicate that the active account has changed. + */ + void activeAccountChanged(); + +public +slots: + /** + * This is called when one of the accounts changes and the list needs to be updated + */ + void accountChanged(); + +protected: + /*! + * Called whenever the list changes. + * This emits the listChanged() signal and autosaves the list (if autosave is enabled). + */ + void onListChanged(); + + /*! + * Called whenever the active account changes. + * Emits the activeAccountChanged() signal and autosaves the list if enabled. + */ + void onActiveChanged(); + + QList<MojangAccountPtr> m_accounts; + + /*! + * Account that is currently active. + */ + MojangAccountPtr m_activeAccount; + + //! Path to the account list file. Empty string if there isn't one. + QString m_listFilePath; + + /*! + * If true, the account list will automatically save to the account list path when it changes. + * Ignored if m_listFilePath is blank. + */ + bool m_autosave = false; + +protected +slots: + /*! + * Updates this list with the given list of accounts. + * This is done by copying each account in the given list and inserting it + * into this one. + * We need to do this so that we can set the parents of the accounts are set to this + * account list. This can't be done in the load task, because the accounts the load + * task creates are on the load task's thread and Qt won't allow their parents + * to be set to something created on another thread. + * To get around that problem, we invoke this method on the GUI thread, which + * then copies the accounts and sets their parents correctly. + * \param accounts List of accounts whose parents should be set. + */ + virtual void updateListData(QList<MojangAccountPtr> versions); +}; diff --git a/logic/auth/YggdrasilTask.cpp b/logic/auth/YggdrasilTask.cpp index 797e84cd..088e1fc0 100644 --- a/logic/auth/YggdrasilTask.cpp +++ b/logic/auth/YggdrasilTask.cpp @@ -24,17 +24,11 @@ #include <MultiMC.h> #include <logic/auth/MojangAccount.h> +#include <logic/net/URLConstants.h> -YggdrasilTask::YggdrasilTask(MojangAccountPtr account, QObject *parent) : Task(parent) +YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent) + : Task(parent), m_account(account) { - m_error = nullptr; - m_account = account; -} - -YggdrasilTask::~YggdrasilTask() -{ - if (m_error) - delete m_error; } void YggdrasilTask::executeTask() @@ -45,97 +39,126 @@ void YggdrasilTask::executeTask() QJsonDocument doc(getRequestContent()); auto worker = MMC->qnam(); - connect(worker.get(), SIGNAL(finished(QNetworkReply *)), this, - SLOT(processReply(QNetworkReply *))); - - QUrl reqUrl("https://authserver.mojang.com/" + getEndpoint()); + 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); + 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::processReply(QNetworkReply *reply) +void YggdrasilTask::refreshTimers(qint64, qint64) { - setStatus(getStateMessage(STATE_PROCESSING_RESPONSE)); + timeout_keeper.stop(); + timeout_keeper.start(timeout_max); + progress(count = 0, timeout_max); +} +void YggdrasilTask::heartbeat() +{ + count += time_step; + progress(count, timeout_max); +} - if (m_netReply != reply) - // Wrong reply for some reason... - return; +void YggdrasilTask::abort() +{ + progress(timeout_max, timeout_max); + m_netReply->abort(); +} - if (reply->error() == QNetworkReply::OperationCanceledError) +void YggdrasilTask::processReply() +{ + setStatus(getStateMessage(STATE_PROCESSING_RESPONSE)); + + // 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."); return; } - else + + // 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) { - // 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(); - - 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 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())) - { - 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(); - } - } - else + if (processResponse(replyData.size() > 0 ? doc.object() : QJsonObject())) { - emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset)); + 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 { - // 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(reply->errorString())); - } + 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 msgVal = responseData.value("errorMessage"); + QJsonValue errorMessageValue = responseData.value("errorMessage"); QJsonValue causeVal = responseData.value("cause"); - if (errorVal.isString() && msgVal.isString()) + if (errorVal.isString() && errorMessageValue.isString()) { - m_error = new Error(errorVal.toString(""), msgVal.toString(""), causeVal.toString("")); - return m_error->getDisplayMessage(); + m_error = std::shared_ptr<Error>(new Error{ + errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); + return m_error->m_errorMessageVerbose; } else { @@ -156,13 +179,3 @@ QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const 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 index 62638c9d..1f81a2d0 100644 --- a/logic/auth/YggdrasilTask.h +++ b/logic/auth/YggdrasilTask.h @@ -19,6 +19,7 @@ #include <QString> #include <QJsonObject> +#include <QTimer> #include "logic/auth/MojangAccount.h" @@ -31,45 +32,18 @@ class YggdrasilTask : public Task { Q_OBJECT public: - explicit YggdrasilTask(MojangAccountPtr account, QObject *parent = 0); - ~YggdrasilTask(); + explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); /** * Class describing a Yggdrasil error response. */ - class Error + struct 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_errorMessageShort; + QString m_errorMessageVerbose; 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. @@ -120,13 +94,25 @@ protected: */ virtual QString getStateMessage(const State state) const; - MojangAccountPtr m_account; - - QNetworkReply *m_netReply; - - Error *m_error; - protected slots: - void processReply(QNetworkReply *reply); + void processReply(); + void refreshTimers(qint64, qint64); + void heartbeat(); + +public +slots: + virtual void abort() override; + +protected: + // FIXME: segfault disaster waiting to happen + MojangAccount *m_account = nullptr; + QNetworkReply *m_netReply = nullptr; + std::shared_ptr<Error> m_error; + QTimer timeout_keeper; + QTimer counter; + int count = 0; // num msec since time reset + + const int timeout_max = 10000; + const int time_step = 50; }; diff --git a/logic/auth/flows/AuthenticateTask.cpp b/logic/auth/flows/AuthenticateTask.cpp index ec2004d6..f60be35d 100644 --- a/logic/auth/flows/AuthenticateTask.cpp +++ b/logic/auth/flows/AuthenticateTask.cpp @@ -26,7 +26,7 @@ #include "logger/QsLog.h" -AuthenticateTask::AuthenticateTask(MojangAccountPtr account, const QString &password, +AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password, QObject *parent) : YggdrasilTask(account, parent), m_password(password) { @@ -59,14 +59,14 @@ QJsonObject AuthenticateTask::getRequestContent() const req.insert("agent", agent); } - req.insert("username", getMojangAccount()->username()); + req.insert("username", m_account->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()); + if (!m_account->m_clientToken.isEmpty()) + req.insert("clientToken", m_account->m_clientToken); return req; } @@ -76,7 +76,7 @@ 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."; - + // QLOG_DEBUG() << responseData; // If we already have a client token, make sure the one the server gave us matches our // existing one. QLOG_DEBUG() << "Getting client token."; @@ -88,8 +88,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) QLOG_ERROR() << "Server didn't send a client token."; return false; } - if (!getMojangAccount()->clientToken().isEmpty() && - clientToken != getMojangAccount()->clientToken()) + if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) { // The server changed our client token! Obey its wishes, but complain. That's what I do // for my parents, so... @@ -97,7 +96,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) << "'. This shouldn't happen, but it isn't really a big deal."; } // Set the client token. - getMojangAccount()->setClientToken(clientToken); + m_account->m_clientToken = clientToken; // Now, we set the access token. QLOG_DEBUG() << "Getting access token."; @@ -109,7 +108,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) QLOG_ERROR() << "Server didn't send an access token."; } // Set the access token. - getMojangAccount()->setAccessToken(accessToken); + m_account->m_accessToken = accessToken; // Now we load the list of available profiles. // Mojang hasn't yet implemented the profile system, @@ -117,13 +116,14 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) // don't have trouble implementing it later. QLOG_DEBUG() << "Loading profile list."; QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); - ProfileList loadedProfiles; + QList<AccountProfile> 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(""); + bool legacy = profile.value("legacy").toBool(false); if (id.isEmpty() || name.isEmpty()) { @@ -135,10 +135,10 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) } // Now, add a new AccountProfile entry to the list. - loadedProfiles.append(AccountProfile(id, name)); + loadedProfiles.append({id, name, legacy}); } // Put the list of profiles we loaded into the MojangAccount object. - getMojangAccount()->loadProfiles(loadedProfiles); + m_account->m_profiles = 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 @@ -153,7 +153,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) QLOG_ERROR() << "Server didn't specify a currently selected profile."; return false; } - if (!getMojangAccount()->setProfile(currentProfileId)) + if (!m_account->setCurrentProfile(currentProfileId)) { // TODO: Set an error to display to the user. QLOG_ERROR() << "Server specified a selected profile that wasn't in the available " @@ -162,23 +162,20 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) } // 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")) { + User u; auto obj = responseData.value("user").toObject(); - auto userId = obj.value("id").toString(); + u.id = 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; + u.properties.insert(name, value); } + m_account->m_user = u; } // We've made it through the minefield of possible errors. Return true to indicate that diff --git a/logic/auth/flows/AuthenticateTask.h b/logic/auth/flows/AuthenticateTask.h index 3b99caad..b6564657 100644 --- a/logic/auth/flows/AuthenticateTask.h +++ b/logic/auth/flows/AuthenticateTask.h @@ -30,7 +30,7 @@ class AuthenticateTask : public YggdrasilTask { Q_OBJECT public: - AuthenticateTask(MojangAccountPtr account, const QString &password, QObject *parent = 0); + AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0); protected: virtual QJsonObject getRequestContent() const; diff --git a/logic/auth/flows/InvalidateTask.cpp b/logic/auth/flows/InvalidateTask.cpp deleted file mode 100644 index e69de29b..00000000 --- a/logic/auth/flows/InvalidateTask.cpp +++ /dev/null diff --git a/logic/auth/flows/InvalidateTask.h b/logic/auth/flows/InvalidateTask.h deleted file mode 100644 index e69de29b..00000000 --- a/logic/auth/flows/InvalidateTask.h +++ /dev/null diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp index b56ed9bc..5f68ccc7 100644 --- a/logic/auth/flows/RefreshTask.cpp +++ b/logic/auth/flows/RefreshTask.cpp @@ -25,7 +25,7 @@ #include "logger/QsLog.h" -RefreshTask::RefreshTask(MojangAccountPtr account, QObject *parent) +RefreshTask::RefreshTask(MojangAccount *account, QObject *parent) : YggdrasilTask(account, parent) { } @@ -44,13 +44,12 @@ QJsonObject RefreshTask::getRequestContent() const * "requestUser": true/false // request the user structure * } */ - auto account = getMojangAccount(); QJsonObject req; - req.insert("clientToken", account->clientToken()); - req.insert("accessToken", account->accessToken()); + req.insert("clientToken", m_account->m_clientToken); + req.insert("accessToken", m_account->m_accessToken); /* { - auto currentProfile = account->currentProfile(); + auto currentProfile = m_account->currentProfile(); QJsonObject profile; profile.insert("id", currentProfile->id()); profile.insert("name", currentProfile->name()); @@ -64,12 +63,11 @@ QJsonObject RefreshTask::getRequestContent() const 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."; + // QLOG_DEBUG() << responseData; // 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(""); @@ -80,7 +78,7 @@ bool RefreshTask::processResponse(QJsonObject responseData) QLOG_ERROR() << "Server didn't send a client token."; return false; } - if (!account->clientToken().isEmpty() && clientToken != account->clientToken()) + if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) { // The server changed our client token! Obey its wishes, but complain. That's what I do // for my parents, so... @@ -104,7 +102,7 @@ bool RefreshTask::processResponse(QJsonObject responseData) // profile) QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); QString currentProfileId = currentProfile.value("id").toString(""); - if (account->currentProfile()->id() != currentProfileId) + if (m_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."; @@ -114,26 +112,26 @@ bool RefreshTask::processResponse(QJsonObject responseData) // this is what the vanilla launcher passes to the userProperties launch param if (responseData.contains("user")) { + User u; auto obj = responseData.value("user").toObject(); - auto userId = obj.value("id").toString(); + u.id = 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; + u.properties.insert(name, value); } + m_account->m_user = u; } + // 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(); + m_account->m_accessToken = accessToken; return true; } @@ -147,9 +145,9 @@ QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const switch (state) { case STATE_SENDING_REQUEST: - return tr("Refreshing: Sending request."); + return tr("Refreshing login token."); case STATE_PROCESSING_RESPONSE: - return tr("Refreshing: Processing response."); + return tr("Refreshing login token: Processing response."); default: return YggdrasilTask::getStateMessage(state); } diff --git a/logic/auth/flows/RefreshTask.h b/logic/auth/flows/RefreshTask.h index 2596f6c7..2fd50c60 100644 --- a/logic/auth/flows/RefreshTask.h +++ b/logic/auth/flows/RefreshTask.h @@ -30,7 +30,7 @@ class RefreshTask : public YggdrasilTask { Q_OBJECT public: - RefreshTask(MojangAccountPtr account, QObject *parent = 0); + RefreshTask(MojangAccount * account, QObject *parent = 0); protected: virtual QJsonObject getRequestContent() const; diff --git a/logic/auth/flows/ValidateTask.cpp b/logic/auth/flows/ValidateTask.cpp index d9e0e46b..84d5e703 100644 --- a/logic/auth/flows/ValidateTask.cpp +++ b/logic/auth/flows/ValidateTask.cpp @@ -26,7 +26,7 @@ #include "logger/QsLog.h" -ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent) +ValidateTask::ValidateTask(MojangAccount * account, QObject *parent) : YggdrasilTask(account, parent) { } @@ -34,7 +34,7 @@ ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent) QJsonObject ValidateTask::getRequestContent() const { QJsonObject req; - req.insert("accessToken", getMojangAccount()->accessToken()); + req.insert("accessToken", m_account->m_accessToken); return req; } diff --git a/logic/auth/flows/ValidateTask.h b/logic/auth/flows/ValidateTask.h index 3ff78c6a..0e34f0c3 100644 --- a/logic/auth/flows/ValidateTask.h +++ b/logic/auth/flows/ValidateTask.h @@ -13,6 +13,10 @@ * limitations under the License. */ +/* + * :FIXME: DEAD CODE, DEAD CODE, DEAD CODE! :FIXME: + */ + #pragma once #include <logic/auth/YggdrasilTask.h> @@ -28,7 +32,7 @@ class ValidateTask : public YggdrasilTask { Q_OBJECT public: - ValidateTask(MojangAccountPtr account, QObject *parent = 0); + ValidateTask(MojangAccount *account, QObject *parent = 0); protected: virtual QJsonObject getRequestContent() const; |