diff options
Diffstat (limited to 'launcher/minecraft/auth')
51 files changed, 1665 insertions, 1228 deletions
diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 09cd2c73..fa42747e 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -41,6 +41,16 @@ enum class AccountType { Mojang }; +enum class AccountState { + Unchecked, + Offline, + Working, + Online, + Errored, + Expired, + Gone +}; + struct AccountData { QJsonObject saveState() const; bool resumeStateFromV2(QJsonObject data); @@ -77,4 +87,9 @@ struct AccountData { MinecraftProfile minecraftProfile; MinecraftEntitlement minecraftEntitlement; Katabasis::Validity validity_ = Katabasis::Validity::None; + + // runtime only information (not saved with the account) + QString internalId; + QString errorString; + AccountState accountState = AccountState::Unchecked; }; diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index d7537345..c44e3e89 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -15,6 +15,7 @@ #include "AccountList.h" #include "AccountData.h" +#include "AccountTask.h" #include <QIODevice> #include <QFile> @@ -24,6 +25,7 @@ #include <QJsonObject> #include <QJsonParseError> #include <QDir> +#include <QTimer> #include <QDebug> @@ -35,7 +37,14 @@ enum AccountListVersion { MojangMSA = 3 }; -AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) { } +AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) { + m_refreshTimer = new QTimer(this); + m_refreshTimer->setSingleShot(true); + connect(m_refreshTimer, &QTimer::timeout, this, &AccountList::fillQueue); + m_nextTimer = new QTimer(this); + m_nextTimer->setSingleShot(true); + connect(m_nextTimer, &QTimer::timeout, this, &AccountList::tryNext); +} AccountList::~AccountList() noexcept {} @@ -244,13 +253,29 @@ QVariant AccountList::data(const QModelIndex &index, int role) const } case StatusColumn: { - if(account->isActive()) { - return tr("Working", "Account status"); - } - if(account->isExpired()) { - return tr("Expired", "Account status"); + switch(account->accountState()) { + case AccountState::Unchecked: { + return tr("Unchecked", "Account status"); + } + case AccountState::Offline: { + return tr("Offline", "Account status"); + } + case AccountState::Online: { + return tr("Online", "Account status"); + } + case AccountState::Working: { + return tr("Working", "Account status"); + } + case AccountState::Errored: { + return tr("Errored", "Account status"); + } + case AccountState::Expired: { + return tr("Expired", "Account status"); + } + case AccountState::Gone: { + return tr("Gone", "Account status"); + } } - return tr("Ready", "Account status"); } case ProfileNameColumn: { @@ -583,10 +608,105 @@ void AccountList::setListFilePath(QString path, bool autosave) bool AccountList::anyAccountIsValid() { - for(auto account:m_accounts) + for(auto account: m_accounts) { - if(account->accountStatus() != NotVerified) + if(account->ownsMinecraft()) { return true; + } } return false; } + +void AccountList::fillQueue() { + + if(m_defaultAccount && m_defaultAccount->shouldRefresh()) { + auto idToRefresh = m_defaultAccount->internalId(); + m_refreshQueue.push_back(idToRefresh); + qDebug() << "AccountList: Queued default account with internal ID " << idToRefresh << " to refresh first"; + } + + for(int i = 0; i < count(); i++) { + auto account = at(i); + if(account == m_defaultAccount) { + continue; + } + + if(account->shouldRefresh()) { + auto idToRefresh = account->internalId(); + m_refreshQueue.push_back(idToRefresh); + qDebug() << "AccountList: Queued account with internal ID " << idToRefresh << " to refresh"; + } + } + m_refreshQueue.removeDuplicates(); + tryNext(); +} + +void AccountList::requestRefresh(QString accountId) { + m_refreshQueue.push_back(accountId); + if(!isActive()) { + tryNext(); + } +} + +void AccountList::tryNext() { + beginActivity(); + while (m_refreshQueue.length()) { + auto accountId = m_refreshQueue.front(); + m_refreshQueue.pop_front(); + for(int i = 0; i < count(); i++) { + auto account = at(i); + if(account->internalId() == accountId) { + m_currentTask = account->refresh(); + if(m_currentTask) { + connect(m_currentTask.get(), &AccountTask::succeeded, this, &AccountList::authSucceeded); + connect(m_currentTask.get(), &AccountTask::failed, this, &AccountList::authFailed); + m_currentTask->start(); + qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID " << accountId; + return; + } + } + } + qDebug() << "RefreshSchedule: Account with with internal ID " << accountId << " not found."; + } + endActivity(); + // if we get here, no account needed refreshing. Schedule refresh in an hour. + m_refreshTimer->start(std::chrono::hours(1)); +} + +void AccountList::authSucceeded() { + qDebug() << "RefreshSchedule: Background account refresh succeeded"; + m_currentTask.reset(); + endActivity(); + m_nextTimer->start(std::chrono::seconds(20)); +} + +void AccountList::authFailed(QString reason) { + qDebug() << "RefreshSchedule: Background account refresh failed: " << reason; + m_currentTask.reset(); + endActivity(); + m_nextTimer->start(std::chrono::seconds(20)); +} + +bool AccountList::isActive() const { + return m_activityCount != 0; +} + +void AccountList::beginActivity() { + bool activating = m_activityCount == 0; + m_activityCount++; + if(activating) { + emit activityChanged(true); + } +} + +void AccountList::endActivity() { + if(m_activityCount == 0) { + qWarning() << m_name << " - Activity count would become below zero"; + return; + } + bool deactivating = m_activityCount == 1; + m_activityCount--; + if(deactivating) { + emit activityChanged(false); + } +} diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h index 08004628..75686c21 100644 --- a/launcher/minecraft/auth/AccountList.h +++ b/launcher/minecraft/auth/AccountList.h @@ -67,6 +67,8 @@ public: MinecraftAccountPtr getAccountByProfileName(const QString &profileName) const; QStringList profileNames() const; + void requestRefresh(QString accountId); + /*! * Sets the path to load/save the list file from/to. * If autosave is true, this list will automatically save to the given path whenever it changes. @@ -85,10 +87,20 @@ public: void setDefaultAccount(MinecraftAccountPtr profileId); bool anyAccountIsValid(); + bool isActive() const; + +protected: + void beginActivity(); + void endActivity(); + +private: + const char* m_name; + uint32_t m_activityCount = 0; signals: void listChanged(); void listActivityChanged(); void defaultAccountChanged(); + void activityChanged(bool active); public slots: /** @@ -101,7 +113,23 @@ public slots: */ void accountActivityChanged(bool active); + /** + * This is initially to run background account refresh tasks, or on a hourly timer + */ + void fillQueue(); + +private slots: + void tryNext(); + + void authSucceeded(); + void authFailed(QString reason); + protected: + QList<QString> m_refreshQueue; + QTimer *m_refreshTimer; + QTimer *m_nextTimer; + shared_qobject_ptr<AccountTask> m_currentTask; + /*! * Called whenever the list changes. * This emits the listChanged() signal and autosaves the list (if autosave is enabled). diff --git a/launcher/minecraft/auth/AccountTask.cpp b/launcher/minecraft/auth/AccountTask.cpp index 25d753de..98d8d94d 100644 --- a/launcher/minecraft/auth/AccountTask.cpp +++ b/launcher/minecraft/auth/AccountTask.cpp @@ -28,40 +28,79 @@ AccountTask::AccountTask(AccountData *data, QObject *parent) : Task(parent), m_data(data) { - changeState(STATE_CREATED); + changeState(AccountTaskState::STATE_CREATED); } QString AccountTask::getStateMessage() const { - switch (m_accountState) + switch (m_taskState) { - case STATE_CREATED: + case AccountTaskState::STATE_CREATED: return "Waiting..."; - case STATE_WORKING: + case AccountTaskState::STATE_WORKING: return tr("Sending request to auth servers..."); - case STATE_SUCCEEDED: + case AccountTaskState::STATE_SUCCEEDED: return tr("Authentication task succeeded."); - case STATE_FAILED_SOFT: + case AccountTaskState::STATE_OFFLINE: return tr("Failed to contact the authentication server."); - case STATE_FAILED_HARD: - return tr("Failed to authenticate."); - case STATE_FAILED_GONE: + case AccountTaskState::STATE_FAILED_SOFT: + return tr("Encountered an error during authentication."); + case AccountTaskState::STATE_FAILED_HARD: + return tr("Failed to authenticate. The session has expired."); + case AccountTaskState::STATE_FAILED_GONE: return tr("Failed to authenticate. The account no longer exists."); default: return tr("..."); } } -void AccountTask::changeState(AccountTask::State newState, QString reason) +bool AccountTask::changeState(AccountTaskState newState, QString reason) { - m_accountState = newState; + m_taskState = newState; setStatus(getStateMessage()); - if (newState == STATE_SUCCEEDED) - { - emitSucceeded(); - } - else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT || newState == STATE_FAILED_GONE) - { - emitFailed(reason); + switch(newState) { + case AccountTaskState::STATE_CREATED: { + m_data->errorString.clear(); + return true; + } + case AccountTaskState::STATE_WORKING: { + m_data->accountState = AccountState::Working; + return true; + } + case AccountTaskState::STATE_SUCCEEDED: { + m_data->accountState = AccountState::Online; + emitSucceeded(); + return false; + } + case AccountTaskState::STATE_OFFLINE: { + m_data->errorString = reason; + m_data->accountState = AccountState::Offline; + emitFailed(reason); + return false; + } + case AccountTaskState::STATE_FAILED_SOFT: { + m_data->errorString = reason; + m_data->accountState = AccountState::Errored; + emitFailed(reason); + return false; + } + case AccountTaskState::STATE_FAILED_HARD: { + m_data->errorString = reason; + m_data->accountState = AccountState::Expired; + emitFailed(reason); + return false; + } + case AccountTaskState::STATE_FAILED_GONE: { + m_data->errorString = reason; + m_data->accountState = AccountState::Gone; + emitFailed(reason); + return false; + } + default: { + QString error = tr("Unknown account task state: %1").arg(int(newState)); + m_data->accountState = AccountState::Errored; + emitFailed(error); + return false; + } } } diff --git a/launcher/minecraft/auth/AccountTask.h b/launcher/minecraft/auth/AccountTask.h index 4f3bd52a..dac3f1b5 100644 --- a/launcher/minecraft/auth/AccountTask.h +++ b/launcher/minecraft/auth/AccountTask.h @@ -26,62 +26,32 @@ class QNetworkReply; +/** + * Enum for describing the state of the current task. + * Used by the getStateMessage function to determine what the status message should be. + */ +enum class AccountTaskState +{ + STATE_CREATED, + STATE_WORKING, + STATE_SUCCEEDED, + STATE_FAILED_SOFT, //!< soft failure. authentication went through partially + STATE_FAILED_HARD, //!< hard failure. main tokens are invalid + STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists + STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way +}; + class AccountTask : public Task { - friend class AuthContext; Q_OBJECT public: explicit AccountTask(AccountData * data, QObject *parent = 0); virtual ~AccountTask() {}; - /** - * assign a session to this task. the session will be filled with required infomration - * upon completion - */ - void assignSession(AuthSessionPtr session) - { - m_session = session; - } - - /// get the assigned session for filling with information. - AuthSessionPtr getAssignedSession() - { - return m_session; - } - - /** - * Class describing a Account error response. - */ - struct Error - { - QString m_errorMessageShort; - QString m_errorMessageVerbose; - QString m_cause; - }; - - enum AbortedBy - { - BY_NOTHING, - BY_USER, - BY_TIMEOUT - } m_aborted = BY_NOTHING; - - /** - * Enum for describing the state of the current task. - * Used by the getStateMessage function to determine what the status message should be. - */ - enum State - { - STATE_CREATED, - STATE_WORKING, - STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated - STATE_FAILED_HARD, //!< hard failure. auth is invalid - STATE_FAILED_GONE, //!< hard failure. auth is invalid, and the account no longer exists - STATE_SUCCEEDED - } m_accountState = STATE_CREATED; + AccountTaskState m_taskState = AccountTaskState::STATE_CREATED; - State accountState() { - return m_accountState; + AccountTaskState taskState() { + return m_taskState; } signals: @@ -98,11 +68,9 @@ protected: virtual QString getStateMessage() const; protected slots: - void changeState(State newState, QString reason=QString()); + // NOTE: true -> non-terminal state, false -> terminal state + bool changeState(AccountTaskState newState, QString reason = QString()); protected: - // FIXME: segfault disaster waiting to happen AccountData *m_data = nullptr; - std::shared_ptr<Error> m_error; - AuthSessionPtr m_session; }; diff --git a/launcher/minecraft/auth/flows/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp index 82dba591..459d2354 100644 --- a/launcher/minecraft/auth/flows/AuthRequest.cpp +++ b/launcher/minecraft/auth/AuthRequest.cpp @@ -44,6 +44,7 @@ void AuthRequest::onRequestFinished() { if (reply_ != qobject_cast<QNetworkReply *>(sender())) { return; } + httpStatus_ = 200; finish(); } @@ -55,10 +56,11 @@ void AuthRequest::onRequestError(QNetworkReply::NetworkError error) { if (reply_ != qobject_cast<QNetworkReply *>(sender())) { return; } - qWarning() << "AuthRequest::onRequestError: Error string: " << reply_->errorString(); - int httpStatus = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + errorString_ = reply_->errorString(); + httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); error_ = error; + qWarning() << "AuthRequest::onRequestError: Error string: " << errorString_; + qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_ << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); // QTimer::singleShot(10, this, SLOT(finish())); } @@ -103,6 +105,8 @@ void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Opera status_ = Requesting; error_ = QNetworkReply::NoError; + errorString_.clear(); + httpStatus_ = 0; } void AuthRequest::finish() { diff --git a/launcher/minecraft/auth/flows/AuthRequest.h b/launcher/minecraft/auth/AuthRequest.h index a547aea4..89f7a123 100644 --- a/launcher/minecraft/auth/flows/AuthRequest.h +++ b/launcher/minecraft/auth/AuthRequest.h @@ -46,6 +46,11 @@ protected slots: /// Handle upload progress. void onUploadProgress(qint64 uploaded, qint64 total); +public: + QNetworkReply::NetworkError error_; + int httpStatus_ = 0; + QString errorString_; + protected: void setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray()); @@ -60,5 +65,6 @@ protected: QNetworkAccessManager::Operation operation_; QUrl url_; Katabasis::ReplyList timedReplies_; - QNetworkReply::NetworkError error_; + + QTimer *timer_; }; diff --git a/launcher/minecraft/auth/AuthStep.cpp b/launcher/minecraft/auth/AuthStep.cpp new file mode 100644 index 00000000..ffa2581b --- /dev/null +++ b/launcher/minecraft/auth/AuthStep.cpp @@ -0,0 +1,7 @@ +#include "AuthStep.h" + +AuthStep::AuthStep(AccountData *data) : QObject(nullptr), m_data(data) { +} + +AuthStep::~AuthStep() noexcept = default; + diff --git a/launcher/minecraft/auth/AuthStep.h b/launcher/minecraft/auth/AuthStep.h new file mode 100644 index 00000000..2a8dc2ca --- /dev/null +++ b/launcher/minecraft/auth/AuthStep.h @@ -0,0 +1,33 @@ +#pragma once +#include <QObject> +#include <QList> +#include <QNetworkReply> + +#include "QObjectPtr.h" +#include "minecraft/auth/AccountData.h" +#include "AccountTask.h" + +class AuthStep : public QObject { + Q_OBJECT + +public: + using Ptr = shared_qobject_ptr<AuthStep>; + +public: + explicit AuthStep(AccountData *data); + virtual ~AuthStep() noexcept; + + virtual QString describe() = 0; + +public slots: + virtual void perform() = 0; + virtual void rehydrate() = 0; + +signals: + void finished(AccountTaskState resultingState, QString message); |
