GH-4317 Detect forced migration state and show errors for it

This commit is contained in:
Petr Mrázek 2022-04-23 01:31:03 +02:00
parent ef1bf57b58
commit b39410a2c2
13 changed files with 143 additions and 1 deletions

View File

@ -224,6 +224,8 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/EntitlementsStep.cpp
minecraft/auth/steps/EntitlementsStep.h
minecraft/auth/steps/ForcedMigrationStep.cpp
minecraft/auth/steps/ForcedMigrationStep.h
minecraft/auth/steps/GetSkinStep.cpp
minecraft/auth/steps/GetSkinStep.h
minecraft/auth/steps/LauncherLoginStep.cpp

View File

@ -239,6 +239,18 @@ void LaunchController::login() {
emitFailed(errorString);
return;
}
case AccountState::MustMigrate: {
auto errorString = tr("The account must be migrated to a Microsoft account.");
QMessageBox::warning(
m_parentWidget,
tr("Account requires migration"),
errorString,
QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok
);
emitFailed(errorString);
return;
}
}
}
emitFailed(tr("Failed to launch."));

View File

@ -322,6 +322,7 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
if(type == AccountType::Mojang) {
legacy = data.value("legacy").toBool(false);
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
mustMigrateToMSA = data.value("mustMigrateToMSA").toBool(false);
}
if(type == AccountType::MSA) {
@ -355,6 +356,9 @@ QJsonObject AccountData::saveState() const {
if(canMigrateToMSA) {
output["canMigrateToMSA"] = true;
}
if(mustMigrateToMSA) {
output["mustMigrateToMSA"] = true;
}
}
else if (type == AccountType::MSA) {
output["type"] = "MSA";

View File

@ -48,7 +48,8 @@ enum class AccountState {
Online,
Errored,
Expired,
Gone
Gone,
MustMigrate
};
struct AccountData {
@ -79,6 +80,7 @@ struct AccountData {
AccountType type = AccountType::MSA;
bool legacy = false;
bool canMigrateToMSA = false;
bool mustMigrateToMSA = false;
Katabasis::Token msaToken;
Katabasis::Token userToken;

View File

@ -294,6 +294,9 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
case AccountState::Gone: {
return tr("Gone", "Account status");
}
case AccountState::MustMigrate: {
return tr("Must Migrate", "Account status");
}
}
}

View File

@ -45,6 +45,8 @@ QString AccountTask::getStateMessage() const
return tr("Failed to contact the authentication server.");
case AccountTaskState::STATE_FAILED_SOFT:
return tr("Encountered an error during authentication.");
case AccountTaskState::STATE_FAILED_MUST_MIGRATE:
return tr("Failed to authenticate. The account must be migrated to a Microsoft account to be usable.");
case AccountTaskState::STATE_FAILED_HARD:
return tr("Failed to authenticate. The session has expired.");
case AccountTaskState::STATE_FAILED_GONE:
@ -84,6 +86,12 @@ bool AccountTask::changeState(AccountTaskState newState, QString reason)
emitFailed(reason);
return false;
}
case AccountTaskState::STATE_FAILED_MUST_MIGRATE: {
m_data->errorString = reason;
m_data->accountState = AccountState::MustMigrate;
emitFailed(reason);
return false;
}
case AccountTaskState::STATE_FAILED_HARD: {
m_data->errorString = reason;
m_data->accountState = AccountState::Expired;

View File

@ -36,6 +36,7 @@ enum class AccountTaskState
STATE_WORKING,
STATE_SUCCEEDED,
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
STATE_FAILED_MUST_MIGRATE, //!< soft failure. main tokens are valid, but the account must be migrated
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

View File

@ -145,6 +145,7 @@ void MinecraftAccount::authFailed(QString reason)
{
switch (m_currentTask->taskState()) {
case AccountTaskState::STATE_OFFLINE:
case AccountTaskState::STATE_FAILED_MUST_MIGRATE:
case AccountTaskState::STATE_FAILED_SOFT: {
// NOTE: this doesn't do much. There was an error of some sort.
}

View File

@ -247,6 +247,36 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
return true;
}
bool parseForcedMigrationResponse(QByteArray & data, bool& result) {
qDebug() << "Parsing Rollout response...";
#ifndef NDEBUG
qDebug() << data;
#endif
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigrationforced as JSON: " << jsonError.errorString();
return false;
}
auto obj = doc.object();
QString feature;
if(!getString(obj.value("feature"), feature)) {
qWarning() << "Rollout feature is not a string";
return false;
}
if(feature != "msamigrationforced") {
qWarning() << "Rollout feature is not what we expected (msamigrationforced), but is instead \"" << feature << "\"";
return false;
}
if(!getBool(obj.value("rollout"), result)) {
qWarning() << "Rollout feature is not a string";
return false;
}
return true;
}
bool parseRolloutResponse(QByteArray & data, bool& result) {
qDebug() << "Parsing Rollout response...";
#ifndef NDEBUG

View File

@ -16,4 +16,5 @@ namespace Parsers
bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
bool parseRolloutResponse(QByteArray &data, bool& result);
bool parseForcedMigrationResponse(QByteArray & data, bool& result);
}

View File

@ -2,6 +2,7 @@
#include "minecraft/auth/steps/YggdrasilStep.h"
#include "minecraft/auth/steps/MinecraftProfileStep.h"
#include "minecraft/auth/steps/ForcedMigrationStep.h"
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
#include "minecraft/auth/steps/GetSkinStep.h"
@ -10,6 +11,7 @@ MojangRefresh::MojangRefresh(
QObject *parent
) : AuthFlow(data, parent) {
m_steps.append(new YggdrasilStep(m_data, QString()));
m_steps.append(new ForcedMigrationStep(m_data));
m_steps.append(new MinecraftProfileStep(m_data));
m_steps.append(new MigrationEligibilityStep(m_data));
m_steps.append(new GetSkinStep(m_data));
@ -21,6 +23,7 @@ MojangLogin::MojangLogin(
QObject *parent
): AuthFlow(data, parent), m_password(password) {
m_steps.append(new YggdrasilStep(m_data, m_password));
m_steps.append(new ForcedMigrationStep(m_data));
m_steps.append(new MinecraftProfileStep(m_data));
m_steps.append(new MigrationEligibilityStep(m_data));
m_steps.append(new GetSkinStep(m_data));

View File

@ -0,0 +1,52 @@
#include "ForcedMigrationStep.h"
#include <QNetworkRequest>
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
ForcedMigrationStep::ForcedMigrationStep(AccountData* data) : AuthStep(data) {
}
ForcedMigrationStep::~ForcedMigrationStep() noexcept = default;
QString ForcedMigrationStep::describe() {
return tr("Checking for migration eligibility.");
}
void ForcedMigrationStep::perform() {
auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigrationforced");
QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
AuthRequest *requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &ForcedMigrationStep::onRequestDone);
requestor->get(request);
}
void ForcedMigrationStep::rehydrate() {
// NOOP, for now. We only save bools and there's nothing to check.
}
void ForcedMigrationStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater();
if (error == QNetworkReply::NoError) {
Parsers::parseForcedMigrationResponse(data, m_data->mustMigrateToMSA);
}
if(m_data->mustMigrateToMSA) {
emit finished(AccountTaskState::STATE_FAILED_MUST_MIGRATE, tr("The account must be migrated to a Microsoft account."));
}
else {
emit finished(AccountTaskState::STATE_WORKING, tr("Got forced migration flags"));
}
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <QObject>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class ForcedMigrationStep : public AuthStep {
Q_OBJECT
public:
explicit ForcedMigrationStep(AccountData *data);
virtual ~ForcedMigrationStep() noexcept;
void perform() override;
void rehydrate() override;
QString describe() override;
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};