chore: reformat

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
This commit is contained in:
Sefa Eyeoglu
2023-08-02 18:35:35 +02:00
parent ce2ca13815
commit 1d468ac35a
594 changed files with 16040 additions and 16536 deletions

View File

@ -34,87 +34,90 @@
*/
#include "AccountData.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <QUuid>
#include <QRegularExpression>
#include <QUuid>
namespace {
void tokenToJSONV3(QJsonObject &parent, Katabasis::Token t, const char * tokenName) {
if(!t.persistent) {
void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenName)
{
if (!t.persistent) {
return;
}
QJsonObject out;
if(t.issueInstant.isValid()) {
if (t.issueInstant.isValid()) {
out["iat"] = QJsonValue(t.issueInstant.toMSecsSinceEpoch() / 1000);
}
if(t.notAfter.isValid()) {
if (t.notAfter.isValid()) {
out["exp"] = QJsonValue(t.notAfter.toMSecsSinceEpoch() / 1000);
}
bool save = false;
if(!t.token.isEmpty()) {
if (!t.token.isEmpty()) {
out["token"] = QJsonValue(t.token);
save = true;
}
if(!t.refresh_token.isEmpty()) {
if (!t.refresh_token.isEmpty()) {
out["refresh_token"] = QJsonValue(t.refresh_token);
save = true;
}
if(t.extra.size()) {
if (t.extra.size()) {
out["extra"] = QJsonObject::fromVariantMap(t.extra);
save = true;
}
if(save) {
if (save) {
parent[tokenName] = out;
}
}
Katabasis::Token tokenFromJSONV3(const QJsonObject &parent, const char * tokenName) {
Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName)
{
Katabasis::Token out;
auto tokenObject = parent.value(tokenName).toObject();
if(tokenObject.isEmpty()) {
if (tokenObject.isEmpty()) {
return out;
}
auto issueInstant = tokenObject.value("iat");
if(issueInstant.isDouble()) {
out.issueInstant = QDateTime::fromMSecsSinceEpoch(((int64_t) issueInstant.toDouble()) * 1000);
if (issueInstant.isDouble()) {
out.issueInstant = QDateTime::fromMSecsSinceEpoch(((int64_t)issueInstant.toDouble()) * 1000);
}
auto notAfter = tokenObject.value("exp");
if(notAfter.isDouble()) {
out.notAfter = QDateTime::fromMSecsSinceEpoch(((int64_t) notAfter.toDouble()) * 1000);
if (notAfter.isDouble()) {
out.notAfter = QDateTime::fromMSecsSinceEpoch(((int64_t)notAfter.toDouble()) * 1000);
}
auto token = tokenObject.value("token");
if(token.isString()) {
if (token.isString()) {
out.token = token.toString();
out.validity = Katabasis::Validity::Assumed;
}
auto refresh_token = tokenObject.value("refresh_token");
if(refresh_token.isString()) {
if (refresh_token.isString()) {
out.refresh_token = refresh_token.toString();
}
auto extra = tokenObject.value("extra");
if(extra.isObject()) {
if (extra.isObject()) {
out.extra = extra.toObject().toVariantMap();
}
return out;
}
void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * tokenName) {
if(p.id.isEmpty()) {
void profileToJSONV3(QJsonObject& parent, MinecraftProfile p, const char* tokenName)
{
if (p.id.isEmpty()) {
return;
}
QJsonObject out;
out["id"] = QJsonValue(p.id);
out["name"] = QJsonValue(p.name);
if(!p.currentCape.isEmpty()) {
if (!p.currentCape.isEmpty()) {
out["cape"] = p.currentCape;
}
@ -123,19 +126,19 @@ void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * token
skinObj["id"] = p.skin.id;
skinObj["url"] = p.skin.url;
skinObj["variant"] = p.skin.variant;
if(p.skin.data.size()) {
if (p.skin.data.size()) {
skinObj["data"] = QString::fromLatin1(p.skin.data.toBase64());
}
out["skin"] = skinObj;
}
QJsonArray capesArray;
for(auto & cape: p.capes) {
for (auto& cape : p.capes) {
QJsonObject capeObj;
capeObj["id"] = cape.id;
capeObj["url"] = cape.url;
capeObj["alias"] = cape.alias;
if(cape.data.size()) {
if (cape.data.size()) {
capeObj["data"] = QString::fromLatin1(cape.data.toBase64());
}
capesArray.push_back(capeObj);
@ -144,16 +147,17 @@ void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * token
parent[tokenName] = out;
}
MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * tokenName) {
MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenName)
{
MinecraftProfile out;
auto tokenObject = parent.value(tokenName).toObject();
if(tokenObject.isEmpty()) {
if (tokenObject.isEmpty()) {
return out;
}
{
auto idV = tokenObject.value("id");
auto nameV = tokenObject.value("name");
if(!idV.isString() || !nameV.isString()) {
if (!idV.isString() || !nameV.isString()) {
qWarning() << "mandatory profile attributes are missing or of unexpected type";
return MinecraftProfile();
}
@ -163,7 +167,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
{
auto skinV = tokenObject.value("skin");
if(!skinV.isObject()) {
if (!skinV.isObject()) {
qWarning() << "skin is missing";
return MinecraftProfile();
}
@ -171,7 +175,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
auto idV = skinObj.value("id");
auto urlV = skinObj.value("url");
auto variantV = skinObj.value("variant");
if(!idV.isString() || !urlV.isString() || !variantV.isString()) {
if (!idV.isString() || !urlV.isString() || !variantV.isString()) {
qWarning() << "mandatory skin attributes are missing or of unexpected type";
return MinecraftProfile();
}
@ -181,11 +185,10 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
// data for skin is optional
auto dataV = skinObj.value("data");
if(dataV.isString()) {
if (dataV.isString()) {
// TODO: validate base64
out.skin.data = QByteArray::fromBase64(dataV.toString().toLatin1());
}
else if (!dataV.isUndefined()) {
} else if (!dataV.isUndefined()) {
qWarning() << "skin data is something unexpected";
return MinecraftProfile();
}
@ -193,13 +196,13 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
{
auto capesV = tokenObject.value("capes");
if(!capesV.isArray()) {
if (!capesV.isArray()) {
qWarning() << "capes is not an array!";
return MinecraftProfile();
}
auto capesArray = capesV.toArray();
for(auto capeV: capesArray) {
if(!capeV.isObject()) {
for (auto capeV : capesArray) {
if (!capeV.isObject()) {
qWarning() << "cape is not an object!";
return MinecraftProfile();
}
@ -207,7 +210,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
auto idV = capeObj.value("id");
auto urlV = capeObj.value("url");
auto aliasV = capeObj.value("alias");
if(!idV.isString() || !urlV.isString() || !aliasV.isString()) {
if (!idV.isString() || !urlV.isString() || !aliasV.isString()) {
qWarning() << "mandatory skin attributes are missing or of unexpected type";
return MinecraftProfile();
}
@ -218,11 +221,10 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
// data for cape is optional.
auto dataV = capeObj.value("data");
if(dataV.isString()) {
if (dataV.isString()) {
// TODO: validate base64
cape.data = QByteArray::fromBase64(dataV.toString().toLatin1());
}
else if (!dataV.isUndefined()) {
} else if (!dataV.isUndefined()) {
qWarning() << "cape data is something unexpected";
return MinecraftProfile();
}
@ -232,9 +234,9 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
// current cape
{
auto capeV = tokenObject.value("cape");
if(capeV.isString()) {
if (capeV.isString()) {
auto currentCape = capeV.toString();
if(out.capes.contains(currentCape)) {
if (out.capes.contains(currentCape)) {
out.currentCape = currentCape;
}
}
@ -243,8 +245,9 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
return out;
}
void entitlementToJSONV3(QJsonObject &parent, MinecraftEntitlement p) {
if(p.validity == Katabasis::Validity::None) {
void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p)
{
if (p.validity == Katabasis::Validity::None) {
return;
}
QJsonObject out;
@ -253,15 +256,16 @@ void entitlementToJSONV3(QJsonObject &parent, MinecraftEntitlement p) {
parent["entitlement"] = out;
}
bool entitlementFromJSONV3(const QJsonObject &parent, MinecraftEntitlement & out) {
bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
{
auto entitlementObject = parent.value("entitlement").toObject();
if(entitlementObject.isEmpty()) {
if (entitlementObject.isEmpty()) {
return false;
}
{
auto ownsMinecraftV = entitlementObject.value("ownsMinecraft");
auto canPlayMinecraftV = entitlementObject.value("canPlayMinecraft");
if(!ownsMinecraftV.isBool() || !canPlayMinecraftV.isBool()) {
if (!ownsMinecraftV.isBool() || !canPlayMinecraftV.isBool()) {
qWarning() << "mandatory attributes are missing or of unexpected type";
return false;
}
@ -272,12 +276,12 @@ bool entitlementFromJSONV3(const QJsonObject &parent, MinecraftEntitlement & out
return true;
}
}
} // namespace
bool AccountData::resumeStateFromV2(QJsonObject data) {
bool AccountData::resumeStateFromV2(QJsonObject data)
{
// The JSON object must at least have a username for it to be valid.
if (!data.value("username").isString())
{
if (!data.value("username").isString()) {
qCritical() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
return false;
}
@ -287,14 +291,12 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
QString accessToken = data.value("accessToken").toString("");
QJsonArray profileArray = data.value("profiles").toArray();
if (profileArray.size() < 1)
{
if (profileArray.size() < 1) {
qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found.";
return false;
}
struct AccountProfile
{
struct AccountProfile {
QString id;
QString name;
bool legacy;
@ -304,24 +306,22 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
int currentProfileIndex = 0;
int index = -1;
QString currentProfile = data.value("activeProfile").toString("");
for (QJsonValue profileVal : profileArray)
{
for (QJsonValue profileVal : profileArray) {
index++;
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())
{
if (id.isEmpty() || name.isEmpty()) {
qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
continue;
}
if(id == currentProfile) {
if (id == currentProfile) {
currentProfileIndex = index;
}
profiles.append({id, name, legacy});
profiles.append({ id, name, legacy });
}
auto & profile = profiles[currentProfileIndex];
auto& profile = profiles[currentProfileIndex];
type = AccountType::Mojang;
legacy = profile.legacy;
@ -339,14 +339,15 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
return true;
}
bool AccountData::resumeStateFromV3(QJsonObject data) {
bool AccountData::resumeStateFromV3(QJsonObject data)
{
auto typeV = data.value("type");
if(!typeV.isString()) {
if (!typeV.isString()) {
qWarning() << "Failed to parse account data: type is missing.";
return false;
}
auto typeS = typeV.toString();
if(typeS == "MSA") {
if (typeS == "MSA") {
type = AccountType::MSA;
} else if (typeS == "Mojang") {
type = AccountType::Mojang;
@ -357,16 +358,16 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
return false;
}
if(type == AccountType::Mojang) {
if (type == AccountType::Mojang) {
legacy = data.value("legacy").toBool(false);
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
}
if(type == AccountType::MSA) {
if (type == AccountType::MSA) {
auto clientIDV = data.value("msa-client-id");
if (clientIDV.isString()) {
msaClientID = clientIDV.toString();
} // leave msaClientID empty if it doesn't exist or isn't a string
} // leave msaClientID empty if it doesn't exist or isn't a string
msaToken = tokenFromJSONV3(data, "msa");
userToken = tokenFromJSONV3(data, "utoken");
xboxApiToken = tokenFromJSONV3(data, "xrp-main");
@ -379,8 +380,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
yggdrasilToken.token = "0";
minecraftProfile = profileFromJSONV3(data, "profile");
if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
if(minecraftProfile.validity != Katabasis::Validity::None) {
if (!entitlementFromJSONV3(data, minecraftEntitlement)) {
if (minecraftProfile.validity != Katabasis::Validity::None) {
minecraftEntitlement.canPlayMinecraft = true;
minecraftEntitlement.ownsMinecraft = true;
minecraftEntitlement.validity = Katabasis::Validity::Assumed;
@ -391,26 +392,25 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
return true;
}
QJsonObject AccountData::saveState() const {
QJsonObject AccountData::saveState() const
{
QJsonObject output;
if(type == AccountType::Mojang) {
if (type == AccountType::Mojang) {
output["type"] = "Mojang";
if(legacy) {
if (legacy) {
output["legacy"] = true;
}
if(canMigrateToMSA) {
if (canMigrateToMSA) {
output["canMigrateToMSA"] = true;
}
}
else if (type == AccountType::MSA) {
} else if (type == AccountType::MSA) {
output["type"] = "MSA";
output["msa-client-id"] = msaClientID;
tokenToJSONV3(output, msaToken, "msa");
tokenToJSONV3(output, userToken, "utoken");
tokenToJSONV3(output, xboxApiToken, "xrp-main");
tokenToJSONV3(output, mojangservicesToken, "xrp-mc");
}
else if (type == AccountType::Offline) {
} else if (type == AccountType::Offline) {
output["type"] = "Offline";
}
@ -420,60 +420,68 @@ QJsonObject AccountData::saveState() const {
return output;
}
QString AccountData::userName() const {
if(type == AccountType::MSA) {
QString AccountData::userName() const
{
if (type == AccountType::MSA) {
return QString();
}
return yggdrasilToken.extra["userName"].toString();
}
QString AccountData::accessToken() const {
QString AccountData::accessToken() const
{
return yggdrasilToken.token;
}
QString AccountData::clientToken() const {
if(type != AccountType::Mojang) {
QString AccountData::clientToken() const
{
if (type != AccountType::Mojang) {
return QString();
}
return yggdrasilToken.extra["clientToken"].toString();
}
void AccountData::setClientToken(QString clientToken) {
if(type != AccountType::Mojang) {
void AccountData::setClientToken(QString clientToken)
{
if (type != AccountType::Mojang) {
return;
}
yggdrasilToken.extra["clientToken"] = clientToken;
}
void AccountData::generateClientTokenIfMissing() {
if(yggdrasilToken.extra.contains("clientToken")) {
void AccountData::generateClientTokenIfMissing()
{
if (yggdrasilToken.extra.contains("clientToken")) {
return;
}
invalidateClientToken();
}
void AccountData::invalidateClientToken() {
if(type != AccountType::Mojang) {
void AccountData::invalidateClientToken()
{
if (type != AccountType::Mojang) {
return;
}
yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
}
QString AccountData::profileId() const {
QString AccountData::profileId() const
{
return minecraftProfile.id;
}
QString AccountData::profileName() const {
if(minecraftProfile.name.size() == 0) {
QString AccountData::profileName() const
{
if (minecraftProfile.name.size() == 0) {
return QObject::tr("No profile (%1)").arg(accountDisplayString());
}
else {
} else {
return minecraftProfile.name;
}
}
QString AccountData::accountDisplayString() const {
switch(type) {
QString AccountData::accountDisplayString() const
{
switch (type) {
case AccountType::Mojang: {
return userName();
}
@ -481,7 +489,7 @@ QString AccountData::accountDisplayString() const {
return QObject::tr("<Offline>");
}
case AccountType::MSA: {
if(xboxApiToken.extra.contains("gtg")) {
if (xboxApiToken.extra.contains("gtg")) {
return xboxApiToken.extra["gtg"].toString();
}
return "Xbox profile missing";
@ -492,6 +500,7 @@ QString AccountData::accountDisplayString() const {
}
}
QString AccountData::lastError() const {
QString AccountData::lastError() const
{
return errorString;
}

View File

@ -34,11 +34,11 @@
*/
#pragma once
#include <QString>
#include <QByteArray>
#include <QVector>
#include <katabasis/Bits.h>
#include <QByteArray>
#include <QJsonObject>
#include <QString>
#include <QVector>
struct Skin {
QString id;
@ -71,22 +71,9 @@ struct MinecraftProfile {
Katabasis::Validity validity = Katabasis::Validity::None;
};
enum class AccountType {
MSA,
Mojang,
Offline
};
enum class AccountType { MSA, Mojang, Offline };
enum class AccountState {
Unchecked,
Offline,
Working,
Online,
Disabled,
Errored,
Expired,
Gone
};
enum class AccountState { Unchecked, Offline, Working, Online, Disabled, Errored, Expired, Gone };
struct AccountData {
QJsonObject saveState() const;

View File

@ -37,14 +37,14 @@
#include "AccountData.h"
#include "AccountTask.h"
#include <QIODevice>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QJsonDocument>
#include <QIODevice>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QDir>
#include <QTextStream>
#include <QTimer>
#include <QDebug>
@ -54,12 +54,10 @@
#include <chrono>
enum AccountListVersion {
MojangOnly = 2,
MojangMSA = 3
};
enum AccountListVersion { MojangOnly = 2, 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);
@ -70,7 +68,8 @@ AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) {
AccountList::~AccountList() noexcept {}
int AccountList::findAccountByProfileId(const QString& profileId) const {
int AccountList::findAccountByProfileId(const QString& profileId) const
{
for (int i = 0; i < count(); i++) {
MinecraftAccountPtr account = at(i);
if (account->profileId() == profileId) {
@ -80,7 +79,8 @@ int AccountList::findAccountByProfileId(const QString& profileId) const {
return -1;
}
MinecraftAccountPtr AccountList::getAccountByProfileName(const QString& profileName) const {
MinecraftAccountPtr AccountList::getAccountByProfileName(const QString& profileName) const
{
for (int i = 0; i < count(); i++) {
MinecraftAccountPtr account = at(i);
if (account->profileName() == profileName) {
@ -95,11 +95,12 @@ const MinecraftAccountPtr AccountList::at(int i) const
return MinecraftAccountPtr(m_accounts.at(i));
}
QStringList AccountList::profileNames() const {
QStringList AccountList::profileNames() const
{
QStringList out;
for(auto & account: m_accounts) {
auto profileName = account->profileName();
if(profileName.isEmpty()) {
for (auto& account : m_accounts) {
auto profileName = account->profileName();
if (profileName.isEmpty()) {
continue;
}
out.append(profileName);
@ -122,14 +123,14 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
// override/replace existing account with the same profileId
auto profileId = account->profileId();
if(profileId.size()) {
if (profileId.size()) {
auto existingAccount = findAccountByProfileId(profileId);
if(existingAccount != -1) {
if (existingAccount != -1) {
qDebug() << "Replacing old account with a new one with the same profile ID!";
MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount];
m_accounts[existingAccount] = account;
if(m_defaultAccount == existingAccountPtr) {
if (m_defaultAccount == existingAccountPtr) {
m_defaultAccount = account;
}
// disconnect notifications for changes in the account being replaced
@ -154,11 +155,9 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
void AccountList::removeAccount(QModelIndex index)
{
int row = index.row();
if(index.isValid() && row >= 0 && row < m_accounts.size())
{
auto & account = m_accounts[row];
if(account == m_defaultAccount)
{
if (index.isValid() && row >= 0 && row < m_accounts.size()) {
auto& account = m_accounts[row];
if (account == m_defaultAccount) {
m_defaultAccount = nullptr;
onDefaultAccountChanged();
}
@ -178,43 +177,34 @@ MinecraftAccountPtr AccountList::defaultAccount() const
void AccountList::setDefaultAccount(MinecraftAccountPtr newAccount)
{
if (!newAccount && m_defaultAccount)
{
if (!newAccount && m_defaultAccount) {
int idx = 0;
auto previousDefaultAccount = m_defaultAccount;
m_defaultAccount = nullptr;
for (MinecraftAccountPtr account : m_accounts)
{
if (account == previousDefaultAccount)
{
for (MinecraftAccountPtr account : m_accounts) {
if (account == previousDefaultAccount) {
emit dataChanged(index(idx), index(idx, columnCount(QModelIndex()) - 1));
}
idx ++;
idx++;
}
onDefaultAccountChanged();
}
else
{
} else {
auto currentDefaultAccount = m_defaultAccount;
int currentDefaultAccountIdx = -1;
auto newDefaultAccount = m_defaultAccount;
int newDefaultAccountIdx = -1;
int idx = 0;
for (MinecraftAccountPtr account : m_accounts)
{
if (account == newAccount)
{
for (MinecraftAccountPtr account : m_accounts) {
if (account == newAccount) {
newDefaultAccount = account;
newDefaultAccountIdx = idx;
}
if(currentDefaultAccount == account)
{
if (currentDefaultAccount == account) {
currentDefaultAccountIdx = idx;
}
idx++;
}
if(currentDefaultAccount != newDefaultAccount)
{
if (currentDefaultAccount != newDefaultAccount) {
emit dataChanged(index(currentDefaultAccountIdx), index(currentDefaultAccountIdx, columnCount(QModelIndex()) - 1));
emit dataChanged(index(newDefaultAccountIdx), index(newDefaultAccountIdx, columnCount(QModelIndex()) - 1));
m_defaultAccount = newDefaultAccount;
@ -231,27 +221,25 @@ void AccountList::accountChanged()
void AccountList::accountActivityChanged(bool active)
{
MinecraftAccount *account = qobject_cast<MinecraftAccount *>(sender());
MinecraftAccount* account = qobject_cast<MinecraftAccount*>(sender());
bool found = false;
for (int i = 0; i < count(); i++) {
if (at(i).get() == account) {
emit dataChanged(index(i), index(i, columnCount(QModelIndex()) - 1));
emit dataChanged(index(i), index(i, columnCount(QModelIndex()) - 1));
found = true;
break;
}
}
if(found) {
if (found) {
emit listActivityChanged();
if(active) {
if (active) {
beginActivity();
}
else {
} else {
endActivity();
}
}
}
void AccountList::onListChanged()
{
if (m_autosave)
@ -274,7 +262,7 @@ int AccountList::count() const
return m_accounts.count();
}
QVariant AccountList::data(const QModelIndex &index, int role) const
QVariant AccountList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
@ -284,70 +272,67 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
MinecraftAccountPtr account = at(index.row());
switch (role)
{
switch (role) {
case Qt::DisplayRole:
switch (index.column())
{
case ProfileNameColumn: {
return account->profileName();
}
switch (index.column()) {
case ProfileNameColumn: {
return account->profileName();
}
case NameColumn:
return account->accountDisplayString();
case NameColumn:
return account->accountDisplayString();
case TypeColumn: {
auto typeStr = account->typeString();
typeStr[0] = typeStr[0].toUpper();
return typeStr;
}
case TypeColumn: {
auto typeStr = account->typeString();
typeStr[0] = typeStr[0].toUpper();
return typeStr;
}
case StatusColumn: {
switch(account->accountState()) {
case AccountState::Unchecked: {
return tr("Unchecked", "Account status");
}
case AccountState::Offline: {
return tr("Offline", "Account status");
}
case AccountState::Online: {
return tr("Ready", "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::Disabled: {
return tr("Disabled", "Account status");
}
case AccountState::Gone: {
return tr("Gone", "Account status");
}
default: {
return tr("Unknown", "Account status");
case StatusColumn: {
switch (account->accountState()) {
case AccountState::Unchecked: {
return tr("Unchecked", "Account status");
}
case AccountState::Offline: {
return tr("Offline", "Account status");
}
case AccountState::Online: {
return tr("Ready", "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::Disabled: {
return tr("Disabled", "Account status");
}
case AccountState::Gone: {
return tr("Gone", "Account status");
}
default: {
return tr("Unknown", "Account status");
}
}
}
}
case MigrationColumn: {
if(account->isMSA() || account->isOffline()) {
return tr("N/A", "Can Migrate");
case MigrationColumn: {
if (account->isMSA() || account->isOffline()) {
return tr("N/A", "Can Migrate");
}
if (account->canMigrate()) {
return tr("Yes", "Can Migrate");
} else {
return tr("No", "Can Migrate");
}
}
if (account->canMigrate()) {
return tr("Yes", "Can Migrate");
}
else {
return tr("No", "Can Migrate");
}
}
default:
return QVariant();
default:
return QVariant();
}
case Qt::ToolTipRole:
@ -362,7 +347,6 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
} else {
return QVariant();
}
default:
return QVariant();
@ -371,79 +355,72 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
QVariant AccountList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case ProfileNameColumn:
return tr("Username");
case NameColumn:
return tr("Account");
case TypeColumn:
return tr("Type");
case StatusColumn:
return tr("Status");
case MigrationColumn:
return tr("Can Migrate?");
switch (role) {
case Qt::DisplayRole:
switch (section) {
case ProfileNameColumn:
return tr("Username");
case NameColumn:
return tr("Account");
case TypeColumn:
return tr("Type");
case StatusColumn:
return tr("Status");
case MigrationColumn:
return tr("Can Migrate?");
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section) {
case ProfileNameColumn:
return tr("Minecraft username associated with the account.");
case NameColumn:
return tr("User name of the account.");
case TypeColumn:
return tr("Type of the account - Mojang or MSA.");
case StatusColumn:
return tr("Current status of the account.");
case MigrationColumn:
return tr("Can this account migrate to a Microsoft account?");
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case ProfileNameColumn:
return tr("Minecraft username associated with the account.");
case NameColumn:
return tr("User name of the account.");
case TypeColumn:
return tr("Type of the account - Mojang or MSA.");
case StatusColumn:
return tr("Current status of the account.");
case MigrationColumn:
return tr("Can this account migrate to a Microsoft account?");
default:
return QVariant();
}
default:
return QVariant();
}
}
int AccountList::rowCount(const QModelIndex &parent) const
int AccountList::rowCount(const QModelIndex& parent) const
{
// Return count
return parent.isValid() ? 0 : count();
}
int AccountList::columnCount(const QModelIndex &parent) const
int AccountList::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : NUM_COLUMNS;
}
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
{
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
{
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid()) {
return Qt::NoItemFlags;
}
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
bool AccountList::setData(const QModelIndex &idx, const QVariant &value, int role)
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
{
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid())
{
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
return false;
}
if(role == Qt::CheckStateRole)
{
if(value == Qt::Checked)
{
if (role == Qt::CheckStateRole) {
if (value == Qt::Checked) {
MinecraftAccountPtr account = at(idx.row());
setDefaultAccount(account);
}
@ -455,8 +432,7 @@ bool AccountList::setData(const QModelIndex &idx, const QVariant &value, int rol
bool AccountList::loadList()
{
if (m_listFilePath.isEmpty())
{
if (m_listFilePath.isEmpty()) {
qCritical() << "Can't load Mojang account list. No file path given and no default set.";
return false;
}
@ -465,8 +441,7 @@ bool AccountList::loadList()
// 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))
{
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8();
return false;
}
@ -479,17 +454,15 @@ bool AccountList::loadList()
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
// Fail if the JSON is invalid.
if (parseError.error != QJsonParseError::NoError)
{
if (parseError.error != QJsonParseError::NoError) {
qCritical() << QString("Failed to parse account list file: %1 at offset %2")
.arg(parseError.errorString(), QString::number(parseError.offset))
.toUtf8();
.arg(parseError.errorString(), QString::number(parseError.offset))
.toUtf8();
return false;
}
// Make sure the root is an object.
if (!jsonDoc.isObject())
{
if (!jsonDoc.isObject()) {
qCritical() << "Invalid account list JSON: Root should be an array.";
return false;
}
@ -498,15 +471,13 @@ bool AccountList::loadList()
// Make sure the format version matches.
auto listVersion = root.value("formatVersion").toVariant().toInt();
switch(listVersion) {
switch (listVersion) {
case AccountListVersion::MojangOnly: {
return loadV2(root);
}
break;
} break;
case AccountListVersion::MojangMSA: {
return loadV3(root);
}
break;
} break;
default: {
QString newName = "accounts-old.json";
qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName;
@ -517,21 +488,20 @@ bool AccountList::loadList()
}
}
bool AccountList::loadV2(QJsonObject& root) {
bool AccountList::loadV2(QJsonObject& root)
{
beginResetModel();
auto defaultUserName = root.value("activeAccount").toString("");
QJsonArray accounts = root.value("accounts").toArray();
for (QJsonValue accountVal : accounts)
{
for (QJsonValue accountVal : accounts) {
QJsonObject accountObj = accountVal.toObject();
MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV2(accountObj);
if (account.get() != nullptr)
{
if (account.get() != nullptr) {
auto profileId = account->profileId();
if(!profileId.size()) {
if (!profileId.size()) {
continue;
}
if(findAccountByProfileId(profileId) != -1) {
if (findAccountByProfileId(profileId) != -1) {
continue;
}
connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
@ -540,9 +510,7 @@ bool AccountList::loadV2(QJsonObject& root) {
if (defaultUserName.size() && account->mojangUserName() == defaultUserName) {
m_defaultAccount = account;
}
}
else
{
} else {
qWarning() << "Failed to load an account.";
}
}
@ -550,30 +518,27 @@ bool AccountList::loadV2(QJsonObject& root) {
return true;
}
bool AccountList::loadV3(QJsonObject& root) {
bool AccountList::loadV3(QJsonObject& root)
{
beginResetModel();
QJsonArray accounts = root.value("accounts").toArray();
for (QJsonValue accountVal : accounts)
{
for (QJsonValue accountVal : accounts) {
QJsonObject accountObj = accountVal.toObject();
MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV3(accountObj);
if (account.get() != nullptr)
{
if (account.get() != nullptr) {
auto profileId = account->profileId();
if(profileId.size()) {
if(findAccountByProfileId(profileId) != -1) {
if (profileId.size()) {
if (findAccountByProfileId(profileId) != -1) {
continue;
}
}
connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged);
m_accounts.append(account);
if(accountObj.value("active").toBool(false)) {
if (accountObj.value("active").toBool(false)) {
m_defaultAccount = account;
}
}
else
{
} else {
qWarning() << "Failed to load an account.";
}
}
@ -581,23 +546,20 @@ bool AccountList::loadV3(QJsonObject& root) {
return true;
}
bool AccountList::saveList()
{
if (m_listFilePath.isEmpty())
{
if (m_listFilePath.isEmpty()) {
qCritical() << "Can't save Mojang account list. No file path given and no default set.";
return false;
}
// make sure the parent folder exists
if(!FS::ensureFilePathExists(m_listFilePath))
if (!FS::ensureFilePathExists(m_listFilePath))
return false;
// make sure the file wasn't overwritten with a folder before (fixes a bug)
QFileInfo finfo(m_listFilePath);
if(finfo.isDir())
{
if (finfo.isDir()) {
QDir badDir(m_listFilePath);
badDir.removeRecursively();
}
@ -613,10 +575,9 @@ bool AccountList::saveList()
// Build a list of accounts.
qDebug() << "Building account array.";
QJsonArray accounts;
for (MinecraftAccountPtr account : m_accounts)
{
for (MinecraftAccountPtr account : m_accounts) {
QJsonObject accountObj = account->saveToJson();
if(m_defaultAccount == account) {
if (m_defaultAccount == account) {
accountObj["active"] = true;
}
accounts.append(accountObj);
@ -634,20 +595,18 @@ bool AccountList::saveList()
// 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))
{
if (!file.open(QIODevice::WriteOnly)) {
qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8();
return false;
}
// Write the JSON to the file.
file.write(doc.toJson());
file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser);
if(file.commit()) {
file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
if (file.commit()) {
qDebug() << "Saved account list to" << m_listFilePath;
return true;
}
else {
} else {
qDebug() << "Failed to save accounts to" << m_listFilePath;
return false;
}
@ -661,30 +620,29 @@ void AccountList::setListFilePath(QString path, bool autosave)
bool AccountList::anyAccountIsValid()
{
for(auto account: m_accounts)
{
if(account->ownsMinecraft()) {
for (auto account : m_accounts) {
if (account->ownsMinecraft()) {
return true;
}
}
return false;
}
void AccountList::fillQueue() {
if(m_defaultAccount && m_defaultAccount->shouldRefresh()) {
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++) {
for (int i = 0; i < count(); i++) {
auto account = at(i);
if(account == m_defaultAccount) {
if (account == m_defaultAccount) {
continue;
}
if(account->shouldRefresh()) {
if (account->shouldRefresh()) {
auto idToRefresh = account->internalId();
queueRefresh(idToRefresh);
}
@ -692,40 +650,43 @@ void AccountList::fillQueue() {
tryNext();
}
void AccountList::requestRefresh(QString accountId) {
void AccountList::requestRefresh(QString accountId)
{
auto index = m_refreshQueue.indexOf(accountId);
if(index != -1) {
if (index != -1) {
m_refreshQueue.removeAt(index);
}
m_refreshQueue.push_front(accountId);
qDebug() << "AccountList: Pushed account with internal ID " << accountId << " to the front of the queue";
if(!isActive()) {
if (!isActive()) {
tryNext();
}
}
void AccountList::queueRefresh(QString accountId) {
if(m_refreshQueue.indexOf(accountId) != -1) {
void AccountList::queueRefresh(QString accountId)
{
if (m_refreshQueue.indexOf(accountId) != -1) {
return;
}
m_refreshQueue.push_back(accountId);
qDebug() << "AccountList: Queued account with internal ID " << accountId << " to refresh";
}
void AccountList::tryNext() {
void AccountList::tryNext()
{
while (m_refreshQueue.length()) {
auto accountId = m_refreshQueue.front();
m_refreshQueue.pop_front();
for(int i = 0; i < count(); i++) {
for (int i = 0; i < count(); i++) {
auto account = at(i);
if(account->internalId() == accountId) {
if (account->internalId() == accountId) {
m_currentTask = account->refresh();
if(m_currentTask) {
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;
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID "
<< accountId;
return;
}
}
@ -736,38 +697,43 @@ void AccountList::tryNext() {
m_refreshTimer->start(1000 * 3600);
}
void AccountList::authSucceeded() {
void AccountList::authSucceeded()
{
qDebug() << "RefreshSchedule: Background account refresh succeeded";
m_currentTask.reset();
m_nextTimer->start(1000 * 20);
}
void AccountList::authFailed(QString reason) {
void AccountList::authFailed(QString reason)
{
qDebug() << "RefreshSchedule: Background account refresh failed: " << reason;
m_currentTask.reset();
m_nextTimer->start(1000 * 20);
}
bool AccountList::isActive() const {
bool AccountList::isActive() const
{
return m_activityCount != 0;
}
void AccountList::beginActivity() {
void AccountList::beginActivity()
{
bool activating = m_activityCount == 0;
m_activityCount++;
if(activating) {
if (activating) {
emit activityChanged(true);
}
}
void AccountList::endActivity() {
if(m_activityCount == 0) {
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) {
if (deactivating) {
emit activityChanged(false);
}
}

View File

@ -37,26 +37,21 @@
#include "MinecraftAccount.h"
#include <QObject>
#include <QVariant>
#include <QAbstractListModel>
#include <QObject>
#include <QSharedPointer>
#include <QVariant>
/*!
* List of available Mojang accounts.
* This should be loaded in the background by Prism Launcher on startup.
*/
class AccountList : public QAbstractListModel
{
class AccountList : public QAbstractListModel {
Q_OBJECT
public:
enum ModelRoles
{
PointerRole = 0x34B1CB48
};
public:
enum ModelRoles { PointerRole = 0x34B1CB48 };
enum VListColumns
{
enum VListColumns {
// TODO: Add icon column.
ProfileNameColumn = 0,
NameColumn,
@ -67,24 +62,24 @@ public:
NUM_COLUMNS
};
explicit AccountList(QObject *parent = 0);
explicit AccountList(QObject* parent = 0);
virtual ~AccountList() noexcept;
const MinecraftAccountPtr at(int i) const;
int count() const;
//////// List Model Functions ////////
QVariant data(const QModelIndex &index, int role) const override;
QVariant data(const QModelIndex& index, int role) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
virtual int rowCount(const QModelIndex &parent) const override;
virtual int columnCount(const QModelIndex &parent) const override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
virtual int rowCount(const QModelIndex& parent) const override;
virtual int columnCount(const QModelIndex& parent) const override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override;
void addAccount(const MinecraftAccountPtr account);
void removeAccount(QModelIndex index);
int findAccountByProfileId(const QString &profileId) const;
MinecraftAccountPtr getAccountByProfileName(const QString &profileName) const;
int findAccountByProfileId(const QString& profileId) const;
MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;
QStringList profileNames() const;
// requesting a refresh pushes it to the front of the queue
@ -102,8 +97,8 @@ public:
void setListFilePath(QString path, bool autosave = false);
bool loadList();
bool loadV2(QJsonObject &root);
bool loadV3(QJsonObject &root);
bool loadV2(QJsonObject& root);
bool loadV3(QJsonObject& root);
bool saveList();
MinecraftAccountPtr defaultAccount() const;
@ -112,20 +107,20 @@ public:
bool isActive() const;
protected:
protected:
void beginActivity();
void endActivity();
private:
private:
const char* m_name;
uint32_t m_activityCount = 0;
signals:
signals:
void listChanged();
void listActivityChanged();
void defaultAccountChanged();
void activityChanged(bool active);
public slots:
public slots:
/**
* This is called when one of the accounts changes and the list needs to be updated
*/
@ -141,16 +136,16 @@ public slots:
*/
void fillQueue();
private slots:
private slots:
void tryNext();
void authSucceeded();
void authFailed(QString reason);
protected:
protected:
QList<QString> m_refreshQueue;
QTimer *m_refreshTimer;
QTimer *m_nextTimer;
QTimer* m_refreshTimer;
QTimer* m_nextTimer;
shared_qobject_ptr<AccountTask> m_currentTask;
/*!
@ -178,4 +173,3 @@ protected:
*/
bool m_autosave = false;
};

View File

@ -36,43 +36,41 @@
#include "AccountTask.h"
#include "MinecraftAccount.h"
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QObject>
#include <QString>
#include <QJsonObject>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QByteArray>
#include <QDebug>
AccountTask::AccountTask(AccountData *data, QObject *parent)
: Task(parent), m_data(data)
AccountTask::AccountTask(AccountData* data, QObject* parent) : Task(parent), m_data(data)
{
changeState(AccountTaskState::STATE_CREATED);
}
QString AccountTask::getStateMessage() const
{
switch (m_taskState)
{
case AccountTaskState::STATE_CREATED:
return "Waiting...";
case AccountTaskState::STATE_WORKING:
return tr("Sending request to auth servers...");
case AccountTaskState::STATE_SUCCEEDED:
return tr("Authentication task succeeded.");
case AccountTaskState::STATE_OFFLINE:
return tr("Failed to contact the authentication server.");
case AccountTaskState::STATE_DISABLED:
return tr("Client ID has changed. New session needs to be created.");
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("...");
switch (m_taskState) {
case AccountTaskState::STATE_CREATED:
return "Waiting...";
case AccountTaskState::STATE_WORKING:
return tr("Sending request to auth servers...");
case AccountTaskState::STATE_SUCCEEDED:
return tr("Authentication task succeeded.");
case AccountTaskState::STATE_OFFLINE:
return tr("Failed to contact the authentication server.");
case AccountTaskState::STATE_DISABLED:
return tr("Client ID has changed. New session needs to be created.");
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("...");
}
}
@ -82,7 +80,7 @@ bool AccountTask::changeState(AccountTaskState newState, QString reason)
// FIXME: virtual method invoked in constructor.
// We want that behavior, but maybe make it less weird?
setStatus(getStateMessage());
switch(newState) {
switch (newState) {
case AccountTaskState::STATE_CREATED: {
m_data->errorString.clear();
return true;

View File

@ -37,10 +37,10 @@
#include <tasks/Task.h>
#include <QString>
#include <QJsonObject>
#include <QTimer>
#include <qsslerror.h>
#include <QJsonObject>
#include <QString>
#include <QTimer>
#include "MinecraftAccount.h"
@ -50,37 +50,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
{
enum class AccountTaskState {
STATE_CREATED,
STATE_WORKING,
STATE_SUCCEEDED,
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
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
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
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
{
class AccountTask : public Task {
Q_OBJECT
public:
explicit AccountTask(AccountData * data, QObject *parent = 0);
virtual ~AccountTask() {};
public:
explicit AccountTask(AccountData* data, QObject* parent = 0);
virtual ~AccountTask(){};
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
AccountTaskState taskState() {
return m_taskState;
}
AccountTaskState taskState() { return m_taskState; }
signals:
void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
signals:
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
void hideVerificationUriAndCode();
protected:
protected:
/**
* Returns the state message for the given state.
* Used to set the status message for the task.
@ -88,10 +83,10 @@ protected:
*/
virtual QString getStateMessage() const;
protected slots:
protected slots:
// NOTE: true -> non-terminal state, false -> terminal state
bool changeState(AccountTaskState newState, QString reason = QString());
protected:
AccountData *m_data = nullptr;
protected:
AccountData* m_data = nullptr;
};

View File

@ -35,44 +35,44 @@
#include <cassert>
#include <QBuffer>
#include <QDebug>
#include <QTimer>
#include <QBuffer>
#include <QUrlQuery>
#include "Application.h"
#include "AuthRequest.h"
#include "katabasis/Globals.h"
AuthRequest::AuthRequest(QObject *parent): QObject(parent) {
}
AuthRequest::AuthRequest(QObject* parent) : QObject(parent) {}
AuthRequest::~AuthRequest() {
}
AuthRequest::~AuthRequest() {}
void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
void AuthRequest::get(const QNetworkRequest& req, int timeout /* = 60*1000*/)
{
setup(req, QNetworkAccessManager::GetOperation);
reply_ = APPLICATION->network()->get(request_);
status_ = Requesting;
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
#else // &QNetworkReply::error SIGNAL depricated
#else // &QNetworkReply::error SIGNAL depricated
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
#endif
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
}
void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int timeout/* = 60*1000*/) {
void AuthRequest::post(const QNetworkRequest& req, const QByteArray& data, int timeout /* = 60*1000*/)
{
setup(req, QNetworkAccessManager::PostOperation);
data_ = data;
status_ = Requesting;
reply_ = APPLICATION->network()->post(request_, data_);
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
#else // &QNetworkReply::error SIGNAL depricated
#else // &QNetworkReply::error SIGNAL depricated
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
#endif
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
@ -80,35 +80,39 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t
connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress);
}
void AuthRequest::onRequestFinished() {
void AuthRequest::onRequestFinished()
{
if (status_ == Idle) {
return;
}
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
return;
}
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
finish();
}
void AuthRequest::onRequestError(QNetworkReply::NetworkError error) {
void AuthRequest::onRequestError(QNetworkReply::NetworkError error)
{
qWarning() << "AuthRequest::onRequestError: Error" << (int)error;
if (status_ == Idle) {
return;
}
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
return;
}
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();
qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_
<< reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
// QTimer::singleShot(10, this, SLOT(finish()));
}
void AuthRequest::onSslErrors(QList<QSslError> errors) {
void AuthRequest::onSslErrors(QList<QSslError> errors)
{
int i = 1;
for (auto error : errors) {
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
@ -118,23 +122,25 @@ void AuthRequest::onSslErrors(QList<QSslError> errors) {
}
}
void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total) {
void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total)
{
if (status_ == Idle) {
qWarning() << "AuthRequest::onUploadProgress: No pending request";
return;
}
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
return;
}
// Restart timeout because request in progress
Katabasis::Reply *o2Reply = timedReplies_.find(reply_);
if(o2Reply) {
Katabasis::Reply* o2Reply = timedReplies_.find(reply_);
if (o2Reply) {
o2Reply->start();
}
emit uploadProgress(uploaded, total);
}
void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation, const QByteArray &verb) {
void AuthRequest::setup(const QNetworkRequest& req, QNetworkAccessManager::Operation operation, const QByteArray& verb)
{
request_ = req;
operation_ = operation;
url_ = req.url();
@ -152,7 +158,8 @@ void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Opera
httpStatus_ = 0;
}
void AuthRequest::finish() {
void AuthRequest::finish()
{
QByteArray data;
if (status_ == Idle) {
qWarning() << "AuthRequest::finish: No pending request";

View File

@ -1,27 +1,26 @@
#pragma once
#include <QObject>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QUrl>
#include <QByteArray>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QObject>
#include <QUrl>
#include "katabasis/Reply.h"
/// Makes authentication requests.
class AuthRequest: public QObject {
class AuthRequest : public QObject {
Q_OBJECT
public:
explicit AuthRequest(QObject *parent = 0);
public:
explicit AuthRequest(QObject* parent = 0);
~AuthRequest();
public slots:
void get(const QNetworkRequest &req, int timeout = 60*1000);
void post(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000);
public slots:
void get(const QNetworkRequest& req, int timeout = 60 * 1000);
void post(const QNetworkRequest& req, const QByteArray& data, int timeout = 60 * 1000);
signals:
signals:
/// Emitted when a request has been completed or failed.
void finished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
@ -29,7 +28,7 @@ signals:
/// Emitted when an upload has progressed.
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
protected slots:
protected slots:
/// Handle request finished.
void onRequestFinished();
@ -46,25 +45,23 @@ protected slots:
/// Handle upload progress.
void onUploadProgress(qint64 uploaded, qint64 total);
public:
public:
QNetworkReply::NetworkError error_;
int httpStatus_ = 0;
QString errorString_;
protected:
void setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray());
protected:
void setup(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& verb = QByteArray());
enum Status {
Idle, Requesting, ReRequesting
};
enum Status { Idle, Requesting, ReRequesting };
QNetworkRequest request_;
QByteArray data_;
QNetworkReply *reply_;
QNetworkReply* reply_;
Status status_;
QNetworkAccessManager::Operation operation_;
QUrl url_;
Katabasis::ReplyList timedReplies_;
QTimer *timer_;
QTimer* timer_;
};

View File

@ -1,7 +1,7 @@
#include "AuthSession.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QStringList>
QString AuthSession::serializeUserProperties()
@ -16,13 +16,11 @@ QString AuthSession::serializeUserProperties()
*/
QJsonDocument value(userAttrs);
return value.toJson(QJsonDocument::Compact);
}
bool AuthSession::MakeOffline(QString offline_playername)
{
if (status != PlayableOffline && status != PlayableOnline)
{
if (status != PlayableOffline && status != PlayableOnline) {
return false;
}
session = "-";
@ -32,7 +30,8 @@ bool AuthSession::MakeOffline(QString offline_playername)
return true;
}
void AuthSession::MakeDemo() {
void AuthSession::MakeDemo()
{
player_name = "Player";
demo = true;
}

View File

@ -1,22 +1,20 @@
#pragma once
#include <QString>
#include <QMultiMap>
#include <QString>
#include <memory>
#include "QObjectPtr.h"
class MinecraftAccount;
class QNetworkAccessManager;
struct AuthSession
{
struct AuthSession {
bool MakeOffline(QString offline_playername);
void MakeDemo();
QString serializeUserProperties();
enum Status
{
enum Status {
Undetermined,
RequiresOAuth,
RequiresPassword,
@ -45,7 +43,7 @@ struct AuthSession
// Did the user request online mode?
bool wants_online = true;
//Is this a demo session?
// Is this a demo session?
bool demo = false;
};

View File

@ -1,7 +1,5 @@
#include "AuthStep.h"
AuthStep::AuthStep(AccountData *data) : QObject(nullptr), m_data(data) {
}
AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {}
AuthStep::~AuthStep() noexcept = default;

View File

@ -1,33 +1,33 @@
#pragma once
#include <QObject>
#include <QList>
#include <QNetworkReply>
#include <QObject>
#include "AccountTask.h"
#include "QObjectPtr.h"
#include "minecraft/auth/AccountData.h"
#include "AccountTask.h"
class AuthStep : public QObject {
Q_OBJECT
public:
public:
using Ptr = shared_qobject_ptr<AuthStep>;
public:
explicit AuthStep(AccountData *data);
public:
explicit AuthStep(AccountData* data);
virtual ~AuthStep() noexcept;
virtual QString describe() = 0;
public slots:
public slots:
virtual void perform() = 0;
virtual void rehydrate() = 0;
signals:
signals:
void finished(AccountTaskState resultingState, QString message);
void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
void hideVerificationUriAndCode();
protected:
AccountData *m_data;
protected:
AccountData* m_data;
};

View File

@ -38,12 +38,12 @@
#include "MinecraftAccount.h"
#include <QCryptographicHash>
#include <QUuid>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QStringList>
#include <QJsonDocument>
#include <QUuid>
#include <QDebug>
@ -53,28 +53,30 @@
#include "flows/Mojang.h"
#include "flows/Offline.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
{
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
}
MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json) {
MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json)
{
MinecraftAccountPtr account(new MinecraftAccount());
if(account->data.resumeStateFromV2(json)) {
if (account->data.resumeStateFromV2(json)) {
return account;
}
return nullptr;
}
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) {
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
{
MinecraftAccountPtr account(new MinecraftAccount());
if(account->data.resumeStateFromV3(json)) {
if (account->data.resumeStateFromV3(json)) {
return account;
}
return nullptr;
}
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username)
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString& username)
{
auto account = makeShared<MinecraftAccount>();
account->data.type = AccountType::Mojang;
@ -90,7 +92,7 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA()
return account;
}
MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
{
auto account = makeShared<MinecraftAccount>();
account->data.type = AccountType::Offline;
@ -107,19 +109,20 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
return account;
}
QJsonObject MinecraftAccount::saveToJson() const
{
return data.saveState();
}
AccountState MinecraftAccount::accountState() const {
AccountState MinecraftAccount::accountState() const
{
return data.accountState;
}
QPixmap MinecraftAccount::getFace() const {
QPixmap MinecraftAccount::getFace() const
{
QPixmap skinTexture;
if(!skinTexture.loadFromData(data.minecraftProfile.skin.data, "PNG")) {
if (!skinTexture.loadFromData(data.minecraftProfile.skin.data, "PNG")) {
return QPixmap();
}
QPixmap skin = QPixmap(8, 8);
@ -129,67 +132,68 @@ QPixmap MinecraftAccount::getFace() const {
return skin.scaled(64, 64, Qt::KeepAspectRatio);
}
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password)
{
Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new MojangLogin(&data, password));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() {
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
{
Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new MSAInteractive(&data));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline() {
shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline()
{
Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new OfflineLogin(&data));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
if(m_currentTask) {
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
{
if (m_currentTask) {
return m_currentTask;
}
if(data.type == AccountType::MSA) {
if (data.type == AccountType::MSA) {
m_currentTask.reset(new MSASilent(&data));
}
else if(data.type == AccountType::Offline) {
} else if (data.type == AccountType::Offline) {
m_currentTask.reset(new OfflineRefresh(&data));
}
else {
} else {
m_currentTask.reset(new MojangRefresh(&data));
}
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask() {
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask()
{
return m_currentTask;
}
void MinecraftAccount::authSucceeded()
{
m_currentTask.reset();
@ -206,28 +210,24 @@ void MinecraftAccount::authFailed(QString reason)
}
case AccountTaskState::STATE_FAILED_SOFT: {
// NOTE: this doesn't do much. There was an error of some sort.
}
break;
} break;
case AccountTaskState::STATE_FAILED_HARD: {
if(isMSA()) {
if (isMSA()) {
data.msaToken.token = QString();
data.msaToken.refresh_token = QString();
data.msaToken.validity = Katabasis::Validity::None;
data.validity_ = Katabasis::Validity::None;
}
else {
} else {
data.yggdrasilToken.token = QString();
data.yggdrasilToken.validity = Katabasis::Validity::None;
data.validity_ = Katabasis::Validity::None;
}
emit changed();
}
break;
} break;
case AccountTaskState::STATE_FAILED_GONE: {
data.validity_ = Katabasis::Validity::None;
emit changed();
}
break;
} break;
case AccountTaskState::STATE_CREATED:
case AccountTaskState::STATE_WORKING:
case AccountTaskState::STATE_SUCCEEDED: {
@ -238,21 +238,23 @@ void MinecraftAccount::authFailed(QString reason)
emit activityChanged(false);
}
bool MinecraftAccount::isActive() const {
bool MinecraftAccount::isActive() const
{
return !m_currentTask.isNull();
}
bool MinecraftAccount::shouldRefresh() const {
bool MinecraftAccount::shouldRefresh() const
{
/*
* Never refresh accounts that are being used by the game, it breaks the game session.
* Always refresh accounts that have not been refreshed yet during this session.
* Don't refresh broken accounts.
* Refresh accounts that would expire in the next 12 hours (fresh token validity is 24 hours).
*/
if(isInUse()) {
if (isInUse()) {
return false;
}
switch(data.validity_) {
switch (data.validity_) {
case Katabasis::Validity::Certain: {
break;
}
@ -267,7 +269,7 @@ bool MinecraftAccount::shouldRefresh() const {
auto issuedTimestamp = data.yggdrasilToken.issueInstant;
auto expiresTimestamp = data.yggdrasilToken.notAfter;
if(!expiresTimestamp.isValid()) {
if (!expiresTimestamp.isValid()) {
expiresTimestamp = issuedTimestamp.addSecs(24 * 3600);
}
if (now.secsTo(expiresTimestamp) < (12 * 3600)) {
@ -278,14 +280,12 @@ bool MinecraftAccount::shouldRefresh() const {
void MinecraftAccount::fillSession(AuthSessionPtr session)
{
if(ownsMinecraft() && !hasProfile()) {
if (ownsMinecraft() && !hasProfile()) {
session->status = AuthSession::RequiresProfileSetup;
}
else {
if(session->wants_online) {
} else {
if (session->wants_online) {
session->status = AuthSession::PlayableOnline;
}
else {
} else {
session->status = AuthSession::PlayableOffline;
}
}
@ -303,12 +303,9 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
session->uuid = data.profileId();
// 'legacy' or 'mojang', depending on account type
session->user_type = typeString();
if (!session->access_token.isEmpty())
{
if (!session->access_token.isEmpty()) {
session->session = "token:" + data.accessToken() + ":" + data.profileId();
}
else
{
} else {
session->session = "-";
}
}
@ -316,8 +313,7 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
void MinecraftAccount::decrementUses()
{
Usable::decrementUses();
if(!isInUse())
{
if (!isInUse()) {
emit changed();
// FIXME: we now need a better way to identify accounts...
qWarning() << "Profile" << data.profileId() << "is no longer in use.";
@ -328,39 +324,31 @@ void MinecraftAccount::incrementUses()
{
bool wasInUse = isInUse();
Usable::incrementUses();
if(!wasInUse)
{
if (!wasInUse) {
emit changed();
// FIXME: we now need a better way to identify accounts...
qWarning() << "Profile" << data.profileId() << "is now in use.";
}
}
QUuid MinecraftAccount::uuidFromUsername(QString username) {
QUuid MinecraftAccount::uuidFromUsername(QString username)
{
auto input = QString("OfflinePlayer:%1").arg(username).toUtf8();
// basically a reimplementation of Java's UUID#nameUUIDFromBytes
QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto bOr = [](QByteArray& array, int index, char value) {
array[index] = array.at(index) | value;
};
auto bAnd = [](QByteArray& array, int index, char value) {
array[index] = array.at(index) & value;
};
auto bOr = [](QByteArray& array, int index, char value) { array[index] = array.at(index) | value; };
auto bAnd = [](QByteArray& array, int index, char value) { array[index] = array.at(index) & value; };
#else
auto bOr = [](QByteArray& array, qsizetype index, char value) {
array[index] |= value;
};
auto bAnd = [](QByteArray& array, qsizetype index, char value) {
array[index] &= value;
};
auto bOr = [](QByteArray& array, qsizetype index, char value) { array[index] |= value; };
auto bAnd = [](QByteArray& array, qsizetype index, char value) { array[index] &= value; };
#endif
bAnd(digest, 6, (char) 0x0f); // clear version
bOr(digest, 6, (char) 0x30); // set to version 3
bAnd(digest, 8, (char) 0x3f); // clear variant
bOr(digest, 8, (char) 0x80); // set to IETF variant
bAnd(digest, 6, (char)0x0f); // clear version
bOr(digest, 6, (char)0x30); // set to version 3
bAnd(digest, 8, (char)0x3f); // clear variant
bOr(digest, 8, (char)0x80); // set to IETF variant
return QUuid::fromRfc4122(digest);
}

View File

@ -35,20 +35,20 @@
#pragma once
#include <QObject>
#include <QString>
#include <QList>
#include <QJsonObject>
#include <QPair>
#include <QList>
#include <QMap>
#include <QObject>
#include <QPair>
#include <QPixmap>
#include <QString>
#include <memory>
#include "AuthSession.h"
#include "Usable.h"
#include "AccountData.h"
#include "AuthSession.h"
#include "QObjectPtr.h"
#include "Usable.h"
class Task;
class AccountTask;
@ -64,8 +64,7 @@ Q_DECLARE_METATYPE(MinecraftAccountPtr)
* but we might as well add some things for it in Prism Launcher right now so
* we don't have to rip the code to pieces to add it later.
*/
struct AccountProfile
{
struct AccountProfile {
QString id;
QString name;
bool legacy;
@ -77,34 +76,30 @@ struct AccountProfile
* Said information may include things such as that account's username, client token, and access
* token if the user chose to stay logged in.
*/
class MinecraftAccount :
public QObject,
public Usable
{
class MinecraftAccount : public QObject, public Usable {
Q_OBJECT
public: /* construction */
public: /* construction */
//! Do not copy accounts. ever.
explicit MinecraftAccount(const MinecraftAccount &other, QObject *parent) = delete;
explicit MinecraftAccount(const MinecraftAccount& other, QObject* parent) = delete;
//! Default constructor
explicit MinecraftAccount(QObject *parent = 0);
explicit MinecraftAccount(QObject* parent = 0);
static MinecraftAccountPtr createFromUsername(const QString &username);
static MinecraftAccountPtr createFromUsername(const QString& username);
static MinecraftAccountPtr createBlankMSA();
static MinecraftAccountPtr createOffline(const QString &username);
static MinecraftAccountPtr createOffline(const QString& username);
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject& json);
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject& json);
static QUuid uuidFromUsername(QString username);
//! Saves a MinecraftAccount to a JSON object and returns it.
QJsonObject saveToJson() const;
public: /* manipulation */
public: /* manipulation */
/**
* Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false.
@ -119,70 +114,46 @@ public: /* manipulation */
shared_qobject_ptr<AccountTask> currentTask();
public: /* queries */
QString internalId() const {
return data.internalId;
}
public: /* queries */
QString internalId() const { return data.internalId; }
QString accountDisplayString() const {
return data.accountDisplayString();
}
QString accountDisplayString() const { return data.accountDisplayString(); }
QString mojangUserName() const {
return data.userName();
}
QString mojangUserName() const { return data.userName(); }
QString accessToken() const {
return data.accessToken();
}
QString accessToken() const { return data.accessToken(); }
QString profileId() const {
return data.profileId();
}
QString profileId() const { return data.profileId(); }
QString profileName() const {
return data.profileName();
}
QString profileName() const { return data.profileName(); }
bool isActive() const;
bool canMigrate() const {
return data.canMigrateToMSA;
}
bool canMigrate() const { return data.canMigrateToMSA; }
bool isMSA() const {
return data.type == AccountType::MSA;
}
bool isMSA() const { return data.type == AccountType::MSA; }
bool isOffline() const {
return data.type == AccountType::Offline;
}
bool isOffline() const { return data.type == AccountType::Offline; }
bool ownsMinecraft() const {
return data.minecraftEntitlement.ownsMinecraft;
}
bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; }
bool hasProfile() const {
return data.profileId().size() != 0;
}
bool hasProfile() const { return data.profileId().size() != 0; }
QString typeString() const {
switch(data.type) {
QString typeString() const
{
switch (data.type) {
case AccountType::Mojang: {
if(data.legacy) {
if (data.legacy) {
return "legacy";
}
return "mojang";
}
break;
} break;
case AccountType::MSA: {
return "msa";
}
break;
} break;
case AccountType::Offline: {
return "offline";
}
break;
} break;
default: {
return "unknown";
}
@ -194,19 +165,15 @@ public: /* queries */
//! Returns the current state of the account
AccountState accountState() const;
AccountData * accountData() {
return &data;
}
AccountData* accountData() { return &data; }
bool shouldRefresh() const;
void fillSession(AuthSessionPtr session);
QString lastError() const {
return data.lastError();
}
QString lastError() const { return data.lastError(); }
signals:
signals:
/**
* This signal is emitted when the account changes
*/
@ -216,20 +183,17 @@ signals:
// TODO: better signalling for the various possible state changes - especially errors
protected: /* variables */
protected: /* variables */
AccountData data;
// current task we are executing here
shared_qobject_ptr<AccountTask> m_currentTask;
protected: /* methods */
protected: /* methods */
void incrementUses() override;
void decrementUses() override;
private
slots:
private slots:
void authSucceeded();
void authFailed(QString reason);
};

View File

@ -2,46 +2,51 @@
#include "Json.h"
#include "Logging.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
namespace Parsers {
bool getDateTime(QJsonValue value, QDateTime & out) {
if(!value.isString()) {
bool getDateTime(QJsonValue value, QDateTime& out)
{
if (!value.isString()) {
return false;
}
out = QDateTime::fromString(value.toString(), Qt::ISODate);
return out.isValid();
}
bool getString(QJsonValue value, QString & out) {
if(!value.isString()) {
bool getString(QJsonValue value, QString& out)
{
if (!value.isString()) {
return false;
}
out = value.toString();
return true;
}
bool getNumber(QJsonValue value, double & out) {
if(!value.isDouble()) {
bool getNumber(QJsonValue value, double& out)
{
if (!value.isDouble()) {
return false;
}
out = value.toDouble();
return true;
}
bool getNumber(QJsonValue value, int64_t & out) {
if(!value.isDouble()) {
bool getNumber(QJsonValue value, int64_t& out)
{
if (!value.isDouble()) {
return false;
}
out = (int64_t) value.toDouble();
out = (int64_t)value.toDouble();
return true;
}
bool getBool(QJsonValue value, bool & out) {
if(!value.isBool()) {
bool getBool(QJsonValue value, bool& out)
{
if (!value.isBool()) {
return false;
}
out = value.toBool();
@ -74,49 +79,50 @@ bool getBool(QJsonValue value, bool & out) {
// 2148916238 = child account not linked to a family
*/
bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) {
qDebug() << "Parsing" << name <<":";
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name)
{
qDebug() << "Parsing" << name << ":";
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
if (jsonError.error) {
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
return false;
}
auto obj = doc.object();
if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
if (!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
qWarning() << "User IssueInstant is not a timestamp";
return false;
}
if(!getDateTime(obj.value("NotAfter"), output.notAfter)) {
if (!getDateTime(obj.value("NotAfter"), output.notAfter)) {
qWarning() << "User NotAfter is not a timestamp";
return false;
}
if(!getString(obj.value("Token"), output.token)) {
if (!getString(obj.value("Token"), output.token)) {
qWarning() << "User Token is not a string";
return false;
}
auto arrayVal = obj.value("DisplayClaims").toObject().value("xui");
if(!arrayVal.isArray()) {
if (!arrayVal.isArray()) {
qWarning() << "Missing xui claims array";
return false;
}
bool foundUHS = false;
for(auto item: arrayVal.toArray()) {
if(!item.isObject()) {
for (auto item : arrayVal.toArray()) {
if (!item.isObject()) {
continue;
}
auto obj = item.toObject();
if(obj.contains("uhs")) {
if (obj.contains("uhs")) {
foundUHS = true;
} else {
continue;
}
// consume all 'display claims' ... whatever that means
for(auto iter = obj.begin(); iter != obj.end(); iter++) {
for (auto iter = obj.begin(); iter != obj.end(); iter++) {
QString claim;
if(!getString(obj.value(iter.key()), claim)) {
if (!getString(obj.value(iter.key()), claim)) {
qWarning() << "display claim " << iter.key() << " is not a string...";
return false;
}
@ -125,7 +131,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
break;
}
if(!foundUHS) {
if (!foundUHS) {
qWarning() << "Missing uhs";
return false;
}
@ -134,46 +140,47 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
return true;
}
bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
{
qDebug() << "Parsing Minecraft profile...";
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
if (jsonError.error) {
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
return false;
}
auto obj = doc.object();
if(!getString(obj.value("id"), output.id)) {
if (!getString(obj.value("id"), output.id)) {
qWarning() << "Minecraft profile id is not a string";
return false;
}
if(!getString(obj.value("name"), output.name)) {
if (!getString(obj.value("name"), output.name)) {
qWarning() << "Minecraft profile name is not a string";
return false;
}
auto skinsArray = obj.value("skins").toArray();
for(auto skin: skinsArray) {
for (auto skin : skinsArray) {
auto skinObj = skin.toObject();
Skin skinOut;
if(!getString(skinObj.value("id"), skinOut.id)) {
if (!getString(skinObj.value("id"), skinOut.id)) {
continue;
}
QString state;
if(!getString(skinObj.value("state"), state)) {
if (!getString(skinObj.value("state"), state)) {
continue;
}
if(state != "ACTIVE") {
if (state != "ACTIVE") {
continue;
}
if(!getString(skinObj.value("url"), skinOut.url)) {
if (!getString(skinObj.value("url"), skinOut.url)) {
continue;
}
if(!getString(skinObj.value("variant"), skinOut.variant)) {
if (!getString(skinObj.value("variant"), skinOut.variant)) {
continue;
}
// we deal with only the active skin
@ -183,23 +190,23 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
auto capesArray = obj.value("capes").toArray();
QString currentCape;
for(auto cape: capesArray) {
for (auto cape : capesArray) {
auto capeObj = cape.toObject();
Cape capeOut;
if(!getString(capeObj.value("id"), capeOut.id)) {
if (!getString(capeObj.value("id"), capeOut.id)) {
continue;
}
QString state;
if(!getString(capeObj.value("state"), state)) {
if (!getString(capeObj.value("state"), state)) {
continue;
}
if(state == "ACTIVE") {
if (state == "ACTIVE") {
currentCape = capeOut.id;
}
if(!getString(capeObj.value("url"), capeOut.url)) {
if (!getString(capeObj.value("url"), capeOut.url)) {
continue;
}
if(!getString(capeObj.value("alias"), capeOut.alias)) {
if (!getString(capeObj.value("alias"), capeOut.alias)) {
continue;
}
@ -211,30 +218,33 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
}
namespace {
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
// they are needed because the session server doesn't return skin urls for default skins
static const QString SKIN_URL_STEVE = "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
static const QString SKIN_URL_ALEX = "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
// they are needed because the session server doesn't return skin urls for default skins
static const QString SKIN_URL_STEVE =
"http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
static const QString SKIN_URL_ALEX =
"http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
bool isDefaultModelSteve(QString uuid) {
// need to calculate *Java* hashCode of UUID
// if number is even, skin/model is steve, otherwise it is alex
bool isDefaultModelSteve(QString uuid)
{
// need to calculate *Java* hashCode of UUID
// if number is even, skin/model is steve, otherwise it is alex
// just in case dashes are in the id
uuid.remove('-');
// just in case dashes are in the id
uuid.remove('-');
if (uuid.size() != 32) {
return true;
}
// qulonglong is guaranteed to be 64 bits
// we need to use unsigned numbers to guarantee truncation below
qulonglong most = uuid.left(16).toULongLong(nullptr, 16);
qulonglong least = uuid.right(16).toULongLong(nullptr, 16);
qulonglong xored = most ^ least;
return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0;
if (uuid.size() != 32) {
return true;
}
// qulonglong is guaranteed to be 64 bits
// we need to use unsigned numbers to guarantee truncation below
qulonglong most = uuid.left(16).toULongLong(nullptr, 16);
qulonglong least = uuid.right(16).toULongLong(nullptr, 16);
qulonglong xored = most ^ least;
return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0;
}
} // namespace
/**
Uses session server for skin/cape lookup instead of profile,
@ -270,31 +280,32 @@ decoded base64 "value":
}
*/
bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
{
qDebug() << "Parsing Minecraft profile...";
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
if (jsonError.error) {
qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
return false;
}
auto obj = Json::requireObject(doc, "mojang minecraft profile");
if(!getString(obj.value("id"), output.id)) {
if (!getString(obj.value("id"), output.id)) {
qWarning() << "Minecraft profile id is not a string";
return false;
}
if(!getString(obj.value("name"), output.name)) {
if (!getString(obj.value("name"), output.name)) {
qWarning() << "Minecraft profile name is not a string";
return false;
}
auto propsArray = obj.value("properties").toArray();
QByteArray texturePayload;
for( auto p : propsArray) {
for (auto p : propsArray) {
auto pObj = p.toObject();
auto name = pObj.value("name");
if (!name.isString() || name.toString() != "textures") {
@ -321,7 +332,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
}
doc = QJsonDocument::fromJson(texturePayload, &jsonError);
if(jsonError.error) {
if (jsonError.error) {
qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
return false;
}
@ -357,8 +368,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
// might not be present
getString(meta.value("model"), skinOut.variant);
}
}
else if (idx.key() == "CAPE") {
} else if (idx.key() == "CAPE") {
auto cape = idx->toObject();
if (!getString(cape.value("url"), capeOut.url)) {
qWarning() << "Cape url is not a string";
@ -374,7 +384,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
output.skin = skinOut;
if (capeOut.alias == "cape") {
output.capes = QMap<QString, Cape>({{capeOut.alias, capeOut}});
output.capes = QMap<QString, Cape>({ { capeOut.alias, capeOut } });
output.currentCape = capeOut.alias;
}
@ -382,13 +392,14 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
return true;
}
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output)
{
qDebug() << "Parsing Minecraft entitlements...";
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
if (jsonError.error) {
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
return false;
}
@ -398,16 +409,16 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
output.ownsMinecraft = false;
auto itemsArray = obj.value("items").toArray();
for(auto item: itemsArray) {
for (auto item : itemsArray) {
auto itemObj = item.toObject();
QString name;
if(!getString(itemObj.value("name"), name)) {
if (!getString(itemObj.value("name"), name)) {
continue;
}
if(name == "game_minecraft") {
if (name == "game_minecraft") {
output.canPlayMinecraft = true;
}
if(name == "product_minecraft") {
if (name == "product_minecraft") {
output.ownsMinecraft = true;
}
}
@ -415,47 +426,50 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
return true;
}
bool parseRolloutResponse(QByteArray & data, bool& result) {
bool parseRolloutResponse(QByteArray& data, bool& result)
{
qDebug() << "Parsing Rollout response...";
qCDebug(authCredentials()) << data;
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: " << jsonError.errorString();
if (jsonError.error) {
qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: "
<< jsonError.errorString();
return false;
}
auto obj = doc.object();
QString feature;
if(!getString(obj.value("feature"), feature)) {
if (!getString(obj.value("feature"), feature)) {
qWarning() << "Rollout feature is not a string";
return false;
}
if(feature != "msamigration") {
if (feature != "msamigration") {
qWarning() << "Rollout feature is not what we expected (msamigration), but is instead \"" << feature << "\"";
return false;
}
if(!getBool(obj.value("rollout"), result)) {
if (!getBool(obj.value("rollout"), result)) {
qWarning() << "Rollout feature is not a string";
return false;
}
return true;
}
bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output)
{
QJsonParseError jsonError;
qDebug() << "Parsing Mojang response...";
qCDebug(authCredentials()) << data;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
if (jsonError.error) {
qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString();
return false;
}
auto obj = doc.object();
double expires_in = 0;
if(!getNumber(obj.value("expires_in"), expires_in)) {
if (!getNumber(obj.value("expires_in"), expires_in)) {
qWarning() << "expires_in is not a valid number";
return false;
}
@ -464,13 +478,13 @@ bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
output.notAfter = currentTime.addSecs(expires_in);
QString username;
if(!getString(obj.value("username"), username)) {
if (!getString(obj.value("username"), username)) {
qWarning() << "username is not valid";
return false;
}
// TODO: it's a JWT... validate it?
if(!getString(obj.value("access_token"), output.token)) {
if (!getString(obj.value("access_token"), output.token)) {
qWarning() << "access_token is not valid";
return false;
}
@ -479,4 +493,4 @@ bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
return true;
}
}
} // namespace Parsers

View File

@ -2,19 +2,18 @@
#include "AccountData.h"
namespace Parsers
{
bool getDateTime(QJsonValue value, QDateTime & out);
bool getString(QJsonValue value, QString & out);
bool getNumber(QJsonValue value, double & out);
bool getNumber(QJsonValue value, int64_t & out);
bool getBool(QJsonValue value, bool & out);
namespace Parsers {
bool getDateTime(QJsonValue value, QDateTime& out);
bool getString(QJsonValue value, QString& out);
bool getNumber(QJsonValue value, double& out);
bool getNumber(QJsonValue value, int64_t& out);
bool getBool(QJsonValue value, bool& out);
bool parseXTokenResponse(QByteArray &data, Katabasis::Token &output, QString name);
bool parseMojangResponse(QByteArray &data, Katabasis::Token &output);
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name);
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output);
bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
bool parseMinecraftProfileMojang(QByteArray &data, MinecraftProfile &output);
bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
bool parseRolloutResponse(QByteArray &data, bool& result);
}
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output);
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output);
bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output);
bool parseRolloutResponse(QByteArray& data, bool& result);
} // namespace Parsers

View File

@ -16,24 +16,24 @@
#include "Yggdrasil.h"
#include "AccountData.h"
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QObject>
#include <QString>
#include <QJsonObject>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QByteArray>
#include <QDebug>
#include "Application.h"
Yggdrasil::Yggdrasil(AccountData *data, QObject *parent)
: AccountTask(data, parent)
Yggdrasil::Yggdrasil(AccountData* data, QObject* parent) : AccountTask(data, parent)
{
changeState(AccountTaskState::STATE_CREATED);
}
void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content)
{
changeState(AccountTaskState::STATE_WORKING);
QNetworkRequest netRequest(endpoint);
@ -52,10 +52,10 @@ void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat);
}
void Yggdrasil::executeTask() {
}
void Yggdrasil::executeTask() {}
void Yggdrasil::refresh() {
void Yggdrasil::refresh()
{
start();
/*
* {
@ -90,7 +90,8 @@ void Yggdrasil::refresh() {
sendRequest(reqUrl, requestData);
}
void Yggdrasil::login(QString password) {
void Yggdrasil::login(QString password)
{
start();
/*
* {
@ -136,20 +137,21 @@ void Yggdrasil::login(QString password) {
sendRequest(reqUrl, requestData);
}
void Yggdrasil::refreshTimers(qint64, qint64) {
void Yggdrasil::refreshTimers(qint64, qint64)
{
timeout_keeper.stop();
timeout_keeper.start(timeout_max);
progress(count = 0, timeout_max);
}
void Yggdrasil::heartbeat() {
void Yggdrasil::heartbeat()
{
count += time_step;
progress(count, timeout_max);
}
bool Yggdrasil::abort() {
bool Yggdrasil::abort()
{
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = Yggdrasil::BY_USER;
@ -157,14 +159,16 @@ bool Yggdrasil::abort() {
return true;
}
void Yggdrasil::abortByTimeout() {
void Yggdrasil::abortByTimeout()
{
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = Yggdrasil::BY_TIMEOUT;
m_netReply->abort();
}
void Yggdrasil::sslErrors(QList<QSslError> errors) {
void Yggdrasil::sslErrors(QList<QSslError> errors)
{
int i = 1;
for (auto error : errors) {
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
@ -174,7 +178,8 @@ void Yggdrasil::sslErrors(QList<QSslError> errors) {
}
}
void Yggdrasil::processResponse(QJsonObject responseData) {
void Yggdrasil::processResponse(QJsonObject responseData)
{
// Read the response data. We need to get the client token, access token, and the selected
// profile.
qDebug() << "Processing authentication response.";
@ -188,11 +193,11 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if(m_data->clientToken().isEmpty()) {
if (m_data->clientToken().isEmpty()) {
m_data->setClientToken(clientToken);
}
else if(clientToken != m_data->clientToken()) {
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
} else if (clientToken != m_data->clientToken()) {
changeState(AccountTaskState::STATE_FAILED_HARD,
tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
@ -220,8 +225,7 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
if (i.key() == "name" && i.value().isString()) {
m_data->minecraftProfile.name = i->toString();
}
else if (i.key() == "id" && i.value().isString()) {
} else if (i.key() == "id" && i.value().isString()) {
m_data->minecraftProfile.id = i->toString();
}
}
@ -237,50 +241,43 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
changeState(AccountTaskState::STATE_SUCCEEDED);
}
void Yggdrasil::processReply() {
void Yggdrasil::processReply()
{
changeState(AccountTaskState::STATE_WORKING);
switch (m_netReply->error())
{
case QNetworkReply::NoError:
break;
case QNetworkReply::TimeoutError:
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
return;
case QNetworkReply::OperationCanceledError:
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
return;
case QNetworkReply::SslHandshakeFailedError:
changeState(
AccountTaskState::STATE_FAILED_SOFT,
tr(
"<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
"<ul>"
"<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
"<li>Some device on your network is interfering with SSL traffic. In that case, "
"you have bigger worries than Minecraft not starting.</li>"
"<li>Possibly something else. Check the log file for details</li>"
"</ul>"
)
);
return;
// used for invalid credentials and similar errors. Fall through.
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentOperationNotPermittedError:
break;
case QNetworkReply::ContentGoneError: {
changeState(
AccountTaskState::STATE_FAILED_GONE,
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
);
return;
}
default:
changeState(
AccountTaskState::STATE_FAILED_SOFT,
tr("Authentication operation failed due to a network error: %1 (%2)").arg(m_netReply->errorString()).arg(m_netReply->error())
);
return;
switch (m_netReply->error()) {
case QNetworkReply::NoError:
break;
case QNetworkReply::TimeoutError:
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
return;
case QNetworkReply::OperationCanceledError:
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
return;
case QNetworkReply::SslHandshakeFailedError:
changeState(AccountTaskState::STATE_FAILED_SOFT,
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
"<ul>"
"<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
"<li>Some device on your network is interfering with SSL traffic. In that case, "
"you have bigger worries than Minecraft not starting.</li>"
"<li>Possibly something else. Check the log file for details</li>"
"</ul>"));
return;
// used for invalid credentials and similar errors. Fall through.
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentOperationNotPermittedError:
break;
case QNetworkReply::ContentGoneError: {
changeState(AccountTaskState::STATE_FAILED_GONE,
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account."));
return;
}
default:
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)")
.arg(m_netReply->errorString())
.arg(m_netReply->error()));
return;
}
// Try to parse the response regardless of the response code.
@ -299,12 +296,11 @@ void Yggdrasil::processReply() {
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) {
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
return;
}
else {
changeState(
AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to parse authentication server response JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset)
);
} else {
changeState(AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to parse authentication server response JSON response: %1 at offset %2.")
.arg(jsonError.errorString())
.arg(jsonError.offset));
qCritical() << replyData;
}
return;
@ -320,34 +316,26 @@ void Yggdrasil::processReply() {
// stuff there.
qDebug() << "The request failed, but the server gave us an error message. Processing error.";
processError(doc.object());
}
else {
} else {
// The server didn't say anything regarding the error. Give the user an unknown
// error.
qDebug() << "The request failed and the server gave no error message. Unknown error.";
changeState(
AccountTaskState::STATE_FAILED_SOFT,
tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString())
);
tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString()));
}
}
void Yggdrasil::processError(QJsonObject responseData) {
void Yggdrasil::processError(QJsonObject responseData)
{
QJsonValue errorVal = responseData.value("error");
QJsonValue errorMessageValue = responseData.value("errorMessage");
QJsonValue causeVal = responseData.value("cause");
if (errorVal.isString() && errorMessageValue.isString()) {
m_error = std::shared_ptr<Error>(
new Error {
errorVal.toString(""),
errorMessageValue.toString(""),
causeVal.toString("")
}
);
m_error = std::shared_ptr<Error>(new Error{ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("") });
changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
}
else {
} else {
// Error is not in standard format. Don't set m_error and return unknown error.
changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
}

View File

@ -17,10 +17,10 @@
#include "AccountTask.h"
#include <QString>
#include <QJsonObject>
#include <QTimer>
#include <qsslerror.h>
#include <QJsonObject>
#include <QString>
#include <QTimer>
#include "MinecraftAccount.h"
@ -30,35 +30,25 @@ class QNetworkReply;
/**
* A Yggdrasil task is a task that performs an operation on a given mojang account.
*/
class Yggdrasil : public AccountTask
{
class Yggdrasil : public AccountTask {
Q_OBJECT
public:
explicit Yggdrasil(
AccountData *data,
QObject *parent = 0
);
public:
explicit Yggdrasil(AccountData* data, QObject* parent = 0);
virtual ~Yggdrasil() = default;
void refresh();
void login(QString password);
struct Error
{
struct Error {
QString m_errorMessageShort;
QString m_errorMessageVerbose;
QString m_cause;
};
std::shared_ptr<Error> m_error;
enum AbortedBy
{
BY_NOTHING,
BY_USER,
BY_TIMEOUT
} m_aborted = BY_NOTHING;
enum AbortedBy { BY_NOTHING, BY_USER, BY_TIMEOUT } m_aborted = BY_NOTHING;
protected:
protected:
void executeTask() override;
/**
@ -78,24 +68,24 @@ protected:
*/
virtual void processError(QJsonObject responseData);
protected slots:
protected slots:
void processReply();
void refreshTimers(qint64, qint64);
void heartbeat();
void sslErrors(QList<QSslError>);
void abortByTimeout();
public slots:
public slots:
virtual bool abort() override;
private:
private:
void sendRequest(QUrl endpoint, QByteArray content);
protected:
QNetworkReply *m_netReply = nullptr;
protected:
QNetworkReply* m_netReply = nullptr;
QTimer timeout_keeper;
QTimer counter;
int count = 0; // num msec since time reset
int count = 0; // num msec since time reset
const int timeout_max = 30000;
const int time_step = 50;

View File

@ -1,36 +1,33 @@
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include "AuthFlow.h"
#include "katabasis/Globals.h"
#include <Application.h>
AuthFlow::AuthFlow(AccountData * data, QObject *parent) :
AccountTask(data, parent)
AuthFlow::AuthFlow(AccountData* data, QObject* parent) : AccountTask(data, parent) {}
void AuthFlow::succeed()
{
}
void AuthFlow::succeed() {
m_data->validity_ = Katabasis::Validity::Certain;
changeState(
AccountTaskState::STATE_SUCCEEDED,
tr("Finished all authentication steps")
);
changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps"));
}
void AuthFlow::executeTask() {
if(m_currentStep) {
void AuthFlow::executeTask()
{
if (m_currentStep) {
return;
}
changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
nextStep();
}
void AuthFlow::nextStep() {
if(m_steps.size() == 0) {
void AuthFlow::nextStep()
{
if (m_steps.size() == 0) {
// we got to the end without an incident... assume this is all.
m_currentStep.reset();
succeed();
@ -46,15 +43,13 @@ void AuthFlow::nextStep() {
m_currentStep->perform();
}
QString AuthFlow::getStateMessage() const {
switch (m_taskState)
{
QString AuthFlow::getStateMessage() const
{
switch (m_taskState) {
case AccountTaskState::STATE_WORKING: {
if(m_currentStep) {
if (m_currentStep) {
return m_currentStep->describe();
}
else {
} else {
return tr("Working...");
}
}
@ -64,8 +59,9 @@ QString AuthFlow::getStateMessage() const {
}
}
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) {
if(changeState(resultingState, message)) {
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
{
if (changeState(resultingState, message)) {
nextStep();
}
}

View File

@ -1,45 +1,42 @@
#pragma once
#include <QObject>
#include <QList>
#include <QVector>
#include <QSet>
#include <QNetworkReply>
#include <QImage>
#include <QList>
#include <QNetworkReply>
#include <QObject>
#include <QSet>
#include <QVector>
#include <katabasis/DeviceFlow.h>
#include "minecraft/auth/Yggdrasil.h"
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountTask.h"
#include "minecraft/auth/AuthStep.h"
#include "minecraft/auth/Yggdrasil.h"
class AuthFlow : public AccountTask
{
class AuthFlow : public AccountTask {
Q_OBJECT
public:
explicit AuthFlow(AccountData * data, QObject *parent = 0);
public:
explicit AuthFlow(AccountData* data, QObject* parent = 0);
Katabasis::Validity validity() {
return m_data->validity_;
};
Katabasis::Validity validity() { return m_data->validity_; };
QString getStateMessage() const override;
void executeTask() override;
signals:
signals:
void activityChanged(Katabasis::Activity activity);
private slots:
private slots:
void stepFinished(AccountTaskState resultingState, QString message);
protected:
protected:
void succeed();
void nextStep();
protected:
protected:
QList<AuthStep::Ptr> m_steps;
AuthStep::Ptr m_currentStep;
};

View File

@ -1,15 +1,16 @@
#include "MSA.h"
#include "minecraft/auth/steps/MSAStep.h"
#include "minecraft/auth/steps/XboxUserStep.h"
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
#include "minecraft/auth/steps/LauncherLoginStep.h"
#include "minecraft/auth/steps/XboxProfileStep.h"
#include "minecraft/auth/steps/EntitlementsStep.h"
#include "minecraft/auth/steps/MinecraftProfileStep.h"
#include "minecraft/auth/steps/GetSkinStep.h"
#include "minecraft/auth/steps/LauncherLoginStep.h"
#include "minecraft/auth/steps/MSAStep.h"
#include "minecraft/auth/steps/MinecraftProfileStep.h"
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
#include "minecraft/auth/steps/XboxProfileStep.h"
#include "minecraft/auth/steps/XboxUserStep.h"
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent) {
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Refresh));
m_steps.append(makeShared<XboxUserStep>(m_data));
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
@ -21,10 +22,8 @@ MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent
m_steps.append(makeShared<GetSkinStep>(m_data));
}
MSAInteractive::MSAInteractive(
AccountData* data,
QObject* parent
) : AuthFlow(data, parent) {
MSAInteractive::MSAInteractive(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Login));
m_steps.append(makeShared<XboxUserStep>(m_data));
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));

View File

@ -1,22 +1,14 @@
#pragma once
#include "AuthFlow.h"
class MSAInteractive : public AuthFlow
{
class MSAInteractive : public AuthFlow {
Q_OBJECT
public:
explicit MSAInteractive(
AccountData *data,
QObject *parent = 0
);
public:
explicit MSAInteractive(AccountData* data, QObject* parent = 0);
};
class MSASilent : public AuthFlow
{
class MSASilent : public AuthFlow {
Q_OBJECT
public:
explicit MSASilent(
AccountData * data,
QObject *parent = 0
);
public:
explicit MSASilent(AccountData* data, QObject* parent = 0);
};

View File

@ -1,25 +1,20 @@
#include "Mojang.h"
#include "minecraft/auth/steps/YggdrasilStep.h"
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
#include "minecraft/auth/steps/GetSkinStep.h"
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
#include "minecraft/auth/steps/YggdrasilStep.h"
MojangRefresh::MojangRefresh(
AccountData *data,
QObject *parent
) : AuthFlow(data, parent) {
MojangRefresh::MojangRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
m_steps.append(makeShared<GetSkinStep>(m_data));
}
MojangLogin::MojangLogin(
AccountData *data,
QString password,
QObject *parent
): AuthFlow(data, parent), m_password(password) {
MojangLogin::MojangLogin(AccountData* data, QString password, QObject* parent) : AuthFlow(data, parent), m_password(password)
{
m_steps.append(makeShared<YggdrasilStep>(m_data, m_password));
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));

View File

@ -1,26 +1,17 @@
#pragma once
#include "AuthFlow.h"
class MojangRefresh : public AuthFlow
{
class MojangRefresh : public AuthFlow {
Q_OBJECT
public:
explicit MojangRefresh(
AccountData *data,
QObject *parent = 0
);
public:
explicit MojangRefresh(AccountData* data, QObject* parent = 0);
};
class MojangLogin : public AuthFlow
{
class MojangLogin : public AuthFlow {
Q_OBJECT
public:
explicit MojangLogin(
AccountData *data,
QString password,
QObject *parent = 0
);
public:
explicit MojangLogin(AccountData* data, QString password, QObject* parent = 0);
private:
private:
QString m_password;
};

View File

@ -2,16 +2,12 @@
#include "minecraft/auth/steps/OfflineStep.h"
OfflineRefresh::OfflineRefresh(
AccountData *data,
QObject *parent
) : AuthFlow(data, parent) {
OfflineRefresh::OfflineRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<OfflineStep>(m_data));
}
OfflineLogin::OfflineLogin(
AccountData *data,
QObject *parent
) : AuthFlow(data, parent) {
OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent)
{
m_steps.append(makeShared<OfflineStep>(m_data));
}

View File

@ -1,22 +1,14 @@
#pragma once
#include "AuthFlow.h"
class OfflineRefresh : public AuthFlow
{
class OfflineRefresh : public AuthFlow {
Q_OBJECT
public:
explicit OfflineRefresh(
AccountData *data,
QObject *parent = 0
);
public:
explicit OfflineRefresh(AccountData* data, QObject* parent = 0);
};
class OfflineLogin : public AuthFlow
{
class OfflineLogin : public AuthFlow {
Q_OBJECT
public:
explicit OfflineLogin(
AccountData *data,
QObject *parent = 0
);
public:
explicit OfflineLogin(AccountData* data, QObject* parent = 0);
};

View File

@ -11,12 +11,13 @@ EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
EntitlementsStep::~EntitlementsStep() noexcept = default;
QString EntitlementsStep::describe() {
QString EntitlementsStep::describe()
{
return tr("Determining game ownership.");
}
void EntitlementsStep::perform() {
void EntitlementsStep::perform()
{
auto uuid = QUuid::createUuid();
m_entitlementsRequestId = uuid.toString().remove('{').remove('}');
auto url = "https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlementsRequestId;
@ -24,22 +25,20 @@ void EntitlementsStep::perform() {
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
AuthRequest *requestor = new AuthRequest(this);
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &EntitlementsStep::onRequestDone);
requestor->get(request);
qDebug() << "Getting entitlements...";
}
void EntitlementsStep::rehydrate() {
void EntitlementsStep::rehydrate()
{
// NOOP, for now. We only save bools and there's nothing to check.
}
void EntitlementsStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void EntitlementsStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
qCDebug(authCredentials()) << data;

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class EntitlementsStep : public AuthStep {
Q_OBJECT
public:
explicit EntitlementsStep(AccountData *data);
public:
explicit EntitlementsStep(AccountData* data);
virtual ~EntitlementsStep() noexcept;
void perform() override;
@ -17,9 +16,9 @@ public:
QString describe() override;
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
private:
private:
QString m_entitlementsRequestId;
};

View File

@ -6,34 +6,32 @@
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {
}
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {}
GetSkinStep::~GetSkinStep() noexcept = default;
QString GetSkinStep::describe() {
QString GetSkinStep::describe()
{
return tr("Getting skin.");
}
void GetSkinStep::perform() {
void GetSkinStep::perform()
{
auto url = QUrl(m_data->minecraftProfile.skin.url);
QNetworkRequest request = QNetworkRequest(url);
AuthRequest *requestor = new AuthRequest(this);
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &GetSkinStep::onRequestDone);
requestor->get(request);
}
void GetSkinStep::rehydrate() {
void GetSkinStep::rehydrate()
{
// NOOP, for now.
}
void GetSkinStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void GetSkinStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
if (error == QNetworkReply::NoError) {

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class GetSkinStep : public AuthStep {
Q_OBJECT
public:
explicit GetSkinStep(AccountData *data);
public:
explicit GetSkinStep(AccountData* data);
virtual ~GetSkinStep() noexcept;
void perform() override;
@ -17,6 +16,6 @@ public:
QString describe() override;
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};

View File

@ -8,17 +8,17 @@
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
}
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {}
LauncherLoginStep::~LauncherLoginStep() noexcept = default;
QString LauncherLoginStep::describe() {
QString LauncherLoginStep::describe()
{
return tr("Accessing Mojang services.");
}
void LauncherLoginStep::perform() {
void LauncherLoginStep::perform()
{
auto requestURL = "https://api.minecraftservices.com/launcher/login";
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
auto xToken = m_data->mojangservicesToken.token;
@ -34,22 +34,20 @@ void LauncherLoginStep::perform() {
QNetworkRequest request = QNetworkRequest(QUrl(requestURL));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
AuthRequest *requestor = new AuthRequest(this);
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &LauncherLoginStep::onRequestDone);
requestor->post(request, requestBody.toUtf8());
qDebug() << "Getting Minecraft access token...";
}
void LauncherLoginStep::rehydrate() {
void LauncherLoginStep::rehydrate()
{
// TODO: check the token validity
}
void LauncherLoginStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void LauncherLoginStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
qCDebug(authCredentials()) << data;
@ -57,27 +55,17 @@ void LauncherLoginStep::onRequestDone(
qWarning() << "Reply error:" << error;
qCDebug(authCredentials()) << data;
if (Net::isApplicationError(error)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
);
}
else {
emit finished(
AccountTaskState::STATE_OFFLINE,
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
);
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
} else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
}
return;
}
if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
if (!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
qWarning() << "Could not parse login_with_xbox response...";
qCDebug(authCredentials()) << data;
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to parse the Minecraft access token response.")
);
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response."));
return;
}
emit finished(AccountTaskState::STATE_WORKING, tr(""));

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class LauncherLoginStep : public AuthStep {
Q_OBJECT
public:
explicit LauncherLoginStep(AccountData *data);
public:
explicit LauncherLoginStep(AccountData* data);
virtual ~LauncherLoginStep() noexcept;
void perform() override;
@ -17,6 +16,6 @@ public:
QString describe() override;
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};

View File

@ -47,7 +47,8 @@
using OAuth2 = Katabasis::DeviceFlow;
using Activity = Katabasis::Activity;
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) {
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action)
{
m_clientId = APPLICATION->getMSAClientID();
OAuth2::Options opts;
opts.scope = "XboxLive.signin offline_access";
@ -64,13 +65,14 @@ MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(ac
MSAStep::~MSAStep() noexcept = default;
QString MSAStep::describe() {
QString MSAStep::describe()
{
return tr("Logging in with Microsoft account.");
}
void MSAStep::rehydrate() {
switch(m_action) {
void MSAStep::rehydrate()
{
switch (m_action) {
case Refresh: {
// TODO: check the tokens and see if they are old (older than a day)
return;
@ -82,12 +84,14 @@ void MSAStep::rehydrate() {
}
}
void MSAStep::perform() {
switch(m_action) {
void MSAStep::perform()
{
switch (m_action) {
case Refresh: {
if (m_data->msaClientID != m_clientId) {
emit hideVerificationUriAndCode();
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - client identification has changed."));
emit finished(AccountTaskState::STATE_DISABLED,
tr("Microsoft user authentication failed - client identification has changed."));
}
m_oauth2->refresh();
return;
@ -105,8 +109,9 @@ void MSAStep::perform() {
}
}
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) {
switch(activity) {
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity)
{
switch (activity) {
case Katabasis::Activity::Idle:
case Katabasis::Activity::LoggingIn:
case Katabasis::Activity::Refreshing:

View File

@ -43,13 +43,11 @@
class MSAStep : public AuthStep {
Q_OBJECT
public:
enum Action {
Refresh,
Login
};
public:
explicit MSAStep(AccountData *data, Action action);
public:
enum Action { Refresh, Login };
public:
explicit MSAStep(AccountData* data, Action action);
virtual ~MSAStep() noexcept;
void perform() override;
@ -57,11 +55,11 @@ public:
QString describe() override;
private slots:
private slots:
void onOAuthActivityChanged(Katabasis::Activity activity);
private:
Katabasis::DeviceFlow *m_oauth2 = nullptr;
private:
Katabasis::DeviceFlow* m_oauth2 = nullptr;
Action m_action;
QString m_clientId;
};

View File

@ -5,37 +5,37 @@
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {
}
MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {}
MigrationEligibilityStep::~MigrationEligibilityStep() noexcept = default;
QString MigrationEligibilityStep::describe() {
QString MigrationEligibilityStep::describe()
{
return tr("Checking for migration eligibility.");
}
void MigrationEligibilityStep::perform() {
void MigrationEligibilityStep::perform()
{
auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration");
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);
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &MigrationEligibilityStep::onRequestDone);
requestor->get(request);
}
void MigrationEligibilityStep::rehydrate() {
void MigrationEligibilityStep::rehydrate()
{
// NOOP, for now. We only save bools and there's nothing to check.
}
void MigrationEligibilityStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void MigrationEligibilityStep::onRequestDone(QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
if (error == QNetworkReply::NoError) {

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class MigrationEligibilityStep : public AuthStep {
Q_OBJECT
public:
explicit MigrationEligibilityStep(AccountData *data);
public:
explicit MigrationEligibilityStep(AccountData* data);
virtual ~MigrationEligibilityStep() noexcept;
void perform() override;
@ -17,6 +16,6 @@ public:
QString describe() override;
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};

View File

@ -7,52 +7,46 @@
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {
}
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {}
MinecraftProfileStep::~MinecraftProfileStep() noexcept = default;
QString MinecraftProfileStep::describe() {
QString MinecraftProfileStep::describe()
{
return tr("Fetching the Minecraft profile.");
}
void MinecraftProfileStep::perform() {
void MinecraftProfileStep::perform()
{
auto url = QUrl("https://api.minecraftservices.com/minecraft/profile");
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);
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &MinecraftProfileStep::onRequestDone);
requestor->get(request);
}
void MinecraftProfileStep::rehydrate() {
void MinecraftProfileStep::rehydrate()
{
// NOOP, for now. We only save bools and there's nothing to check.
}
void MinecraftProfileStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
qCDebug(authCredentials()) << data;
if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) {
if (m_data->type == AccountType::Mojang) {
m_data->minecraftEntitlement.canPlayMinecraft = false;
m_data->minecraftEntitlement.ownsMinecraft = false;
}
m_data->minecraftProfile = MinecraftProfile();
emit finished(
AccountTaskState::STATE_SUCCEEDED,
tr("Account has no Minecraft profile.")
);
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
return;
}
if (error != QNetworkReply::NoError) {
@ -65,35 +59,24 @@ void MinecraftProfileStep::onRequestDone(
qWarning() << QString::fromUtf8(data);
if (Net::isApplicationError(error)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
);
}
else {
emit finished(
AccountTaskState::STATE_OFFLINE,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
);
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
} else {
emit finished(AccountTaskState::STATE_OFFLINE,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
}
return;
}
if(!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
if (!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
m_data->minecraftProfile = MinecraftProfile();
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile response could not be parsed")
);
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
return;
}
if(m_data->type == AccountType::Mojang) {
if (m_data->type == AccountType::Mojang) {
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
}
emit finished(
AccountTaskState::STATE_WORKING,
tr("Minecraft Java profile acquisition succeeded.")
);
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
}

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class MinecraftProfileStep : public AuthStep {
Q_OBJECT
public:
explicit MinecraftProfileStep(AccountData *data);
public:
explicit MinecraftProfileStep(AccountData* data);
virtual ~MinecraftProfileStep() noexcept;
void perform() override;
@ -17,6 +16,6 @@ public:
QString describe() override;
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};

View File

@ -7,18 +7,17 @@
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {
}
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {}
MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
QString MinecraftProfileStepMojang::describe() {
QString MinecraftProfileStepMojang::describe()
{
return tr("Fetching the Minecraft profile.");
}
void MinecraftProfileStepMojang::perform() {
void MinecraftProfileStepMojang::perform()
{
if (m_data->minecraftProfile.id.isEmpty()) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
return;
@ -27,35 +26,32 @@ void MinecraftProfileStepMojang::perform() {
// use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
QNetworkRequest req = QNetworkRequest(url);
AuthRequest *request = new AuthRequest(this);
AuthRequest* request = new AuthRequest(this);
connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
request->get(req);
}
void MinecraftProfileStepMojang::rehydrate() {
void MinecraftProfileStepMojang::rehydrate()
{
// NOOP, for now. We only save bools and there's nothing to check.
}
void MinecraftProfileStepMojang::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void MinecraftProfileStepMojang::onRequestDone(QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
qCDebug(authCredentials()) << data;
if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) {
if (m_data->type == AccountType::Mojang) {
m_data->minecraftEntitlement.canPlayMinecraft = false;
m_data->minecraftEntitlement.ownsMinecraft = false;
}
m_data->minecraftProfile = MinecraftProfile();
emit finished(
AccountTaskState::STATE_SUCCEEDED,
tr("Account has no Minecraft profile.")
);
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
return;
}
if (error != QNetworkReply::NoError) {
@ -68,35 +64,24 @@ void MinecraftProfileStepMojang::onRequestDone(
qWarning() << QString::fromUtf8(data);
if (Net::isApplicationError(error)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
);
}
else {
emit finished(
AccountTaskState::STATE_OFFLINE,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
);
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
} else {
emit finished(AccountTaskState::STATE_OFFLINE,
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
}
return;
}
if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
if (!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
m_data->minecraftProfile = MinecraftProfile();
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile response could not be parsed")
);
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
return;
}
if(m_data->type == AccountType::Mojang) {
if (m_data->type == AccountType::Mojang) {
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
}
emit finished(
AccountTaskState::STATE_WORKING,
tr("Minecraft Java profile acquisition succeeded.")
);
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
}

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class MinecraftProfileStepMojang : public AuthStep {
Q_OBJECT
public:
explicit MinecraftProfileStepMojang(AccountData *data);
public:
explicit MinecraftProfileStepMojang(AccountData* data);
virtual ~MinecraftProfileStepMojang() noexcept;
void perform() override;
@ -17,6 +16,6 @@ public:
QString describe() override;
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};

View File

@ -5,14 +5,17 @@
OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}
OfflineStep::~OfflineStep() noexcept = default;
QString OfflineStep::describe() {
QString OfflineStep::describe()
{
return tr("Creating offline account.");
}
void OfflineStep::rehydrate() {
void OfflineStep::rehydrate()
{
// NOOP
}
void OfflineStep::perform() {
void OfflineStep::perform()
{
emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account."));
}

View File

@ -8,8 +8,8 @@
class OfflineStep : public AuthStep {
Q_OBJECT
public:
explicit OfflineStep(AccountData *data);
public:
explicit OfflineStep(AccountData* data);
virtual ~OfflineStep() noexcept;
void perform() override;

View File

@ -1,33 +1,32 @@
#include "XboxAuthorizationStep.h"
#include <QNetworkRequest>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token *token, QString relyingParty, QString authorizationKind):
AuthStep(data),
m_token(token),
m_relyingParty(relyingParty),
m_authorizationKind(authorizationKind)
{
}
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind)
: AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
{}
XboxAuthorizationStep::~XboxAuthorizationStep() noexcept = default;
QString XboxAuthorizationStep::describe() {
QString XboxAuthorizationStep::describe()
{
return tr("Getting authorization to access %1 services.").arg(m_authorizationKind);
}
void XboxAuthorizationStep::rehydrate() {
void XboxAuthorizationStep::rehydrate()
{
// FIXME: check if the tokens are good?
}
void XboxAuthorizationStep::perform() {
void XboxAuthorizationStep::perform()
{
QString xbox_auth_template = R"XXX(
{
"Properties": {
@ -41,129 +40,98 @@ void XboxAuthorizationStep::perform() {
}
)XXX";
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
// http://xboxlive.com
// http://xboxlive.com
QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
AuthRequest *requestor = new AuthRequest(this);
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &XboxAuthorizationStep::onRequestDone);
requestor->post(request, xbox_auth_data.toUtf8());
qDebug() << "Getting authorization token for " << m_relyingParty;
}
void XboxAuthorizationStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
qCDebug(authCredentials()) << data;
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
if (Net::isApplicationError(error)) {
if(!processSTSError(error, data, headers)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error)
);
if (!processSTSError(error, data, headers)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error));
} else {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
}
else {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)
);
}
}
else {
emit finished(
AccountTaskState::STATE_OFFLINE,
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)
);
} else {
emit finished(AccountTaskState::STATE_OFFLINE,
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
}
return;
}
Katabasis::Token temp;
if(!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind)
);
if (!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));
return;
}
if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Server has changed %1 authorization user hash in the reply. Something is wrong.").arg(m_authorizationKind)
);
if (temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Server has changed %1 authorization user hash in the reply. Something is wrong.").arg(m_authorizationKind));
return;
}
auto & token = *m_token;
auto& token = *m_token;
token = temp;
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty));
}
bool XboxAuthorizationStep::processSTSError(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
if(error == QNetworkReply::AuthenticationRequiredError) {
bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
if (error == QNetworkReply::AuthenticationRequiredError) {
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) {
if (jsonError.error) {
qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString())
);
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString()));
return true;
}
int64_t errorCode = -1;
auto obj = doc.object();
if(!Parsers::getNumber(obj.value("XErr"), errorCode)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("XErr element is missing from %1 authorization error response.").arg(m_authorizationKind)
);
if (!Parsers::getNumber(obj.value("XErr"), errorCode)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("XErr element is missing from %1 authorization error response.").arg(m_authorizationKind));
return true;
}
switch(errorCode) {
case 2148916233:{
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("This Microsoft account does not have an XBox Live profile. Buy the game on %1 first.")
.arg("<a href=\"https://www.minecraft.net/en-us/store/minecraft-java-edition\">minecraft.net</a>")
);
switch (errorCode) {
case 2148916233: {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("This Microsoft account does not have an XBox Live profile. Buy the game on %1 first.")
.arg("<a href=\"https://www.minecraft.net/en-us/store/minecraft-java-edition\">minecraft.net</a>"));
return true;
}
case 2148916235: {
// NOTE: this is the Grulovia error
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("XBox Live is not available in your country. You've been blocked.")
);
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox Live is not available in your country. You've been blocked."));
return true;
}
case 2148916238: {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("This Microsoft account is underaged and is not linked to a family.\n\nPlease set up your account according to %1.")
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4403181904525\">help.minecraft.net</a>")
);
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4403181904525\">help.minecraft.net</a>"));
return true;
}
default: {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("XSTS authentication ended with unrecognized error(s):\n\n%1").arg(errorCode)
);
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("XSTS authentication ended with unrecognized error(s):\n\n%1").arg(errorCode));
return true;
}
}

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class XboxAuthorizationStep : public AuthStep {
Q_OBJECT
public:
explicit XboxAuthorizationStep(AccountData *data, Katabasis::Token *token, QString relyingParty, QString authorizationKind);
public:
explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind);
virtual ~XboxAuthorizationStep() noexcept;
void perform() override;
@ -17,18 +16,14 @@ public:
QString describe() override;
private:
bool processSTSError(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
);
private:
bool processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
private:
Katabasis::Token *m_token;
private:
Katabasis::Token* m_token;
QString m_relyingParty;
QString m_authorizationKind;
};

View File

@ -8,66 +8,56 @@
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {
}
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {}
XboxProfileStep::~XboxProfileStep() noexcept = default;
QString XboxProfileStep::describe() {
QString XboxProfileStep::describe()
{
return tr("Fetching Xbox profile.");
}
void XboxProfileStep::rehydrate() {
void XboxProfileStep::rehydrate()
{
// NOOP, for now. We only save bools and there's nothing to check.
}
void XboxProfileStep::perform() {
void XboxProfileStep::perform()
{
auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings");
QUrlQuery q;
q.addQueryItem(
"settings",
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
"PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix,"
"UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep,"
"PreferredColor,Location,Bio,Watermarks,"
"RealName,RealNameOverride,IsQuarantined"
);
q.addQueryItem("settings",
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
"PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix,"
"UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep,"
"PreferredColor,Location,Bio,Watermarks,"
"RealName,RealNameOverride,IsQuarantined");
url.setQuery(q);
QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("x-xbl-contract-version", "3");
request.setRawHeader("Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
AuthRequest *requestor = new AuthRequest(this);
request.setRawHeader("Authorization",
QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &XboxProfileStep::onRequestDone);
requestor->get(request);
qDebug() << "Getting Xbox profile...";
}
void XboxProfileStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void XboxProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
qCDebug(authCredentials()) << data;
if (Net::isApplicationError(error)) {
emit finished(
AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)
);
}
else {
emit finished(
AccountTaskState::STATE_OFFLINE,
tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)
);
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
} else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
}
return;
}

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class XboxProfileStep : public AuthStep {
Q_OBJECT
public:
explicit XboxProfileStep(AccountData *data);
public:
explicit XboxProfileStep(AccountData* data);
virtual ~XboxProfileStep() noexcept;
void perform() override;
@ -17,6 +16,6 @@ public:
QString describe() override;
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};

View File

@ -6,22 +6,22 @@
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {
}
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {}
XboxUserStep::~XboxUserStep() noexcept = default;
QString XboxUserStep::describe() {
QString XboxUserStep::describe()
{
return tr("Logging in as an Xbox user.");
}
void XboxUserStep::rehydrate() {
void XboxUserStep::rehydrate()
{
// NOOP, for now. We only save bools and there's nothing to check.
}
void XboxUserStep::perform() {
void XboxUserStep::perform()
{
QString xbox_auth_template = R"XXX(
{
"Properties": {
@ -40,40 +40,31 @@ void XboxUserStep::perform() {
request.setRawHeader("Accept", "application/json");
// set contract-verison header (prevent err 400 bad-request?)
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
request.setRawHeader("x-xbl-contract-version", "1");
request.setRawHeader("x-xbl-contract-version", "1");
auto *requestor = new AuthRequest(this);
auto* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone);
requestor->post(request, xbox_auth_data.toUtf8());
qDebug() << "First layer of XBox auth ... commencing.";
}
void XboxUserStep::onRequestDone(
QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers
) {
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
void XboxUserStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error;
if (Net::isApplicationError(error)) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("XBox user authentication failed: %1").arg(requestor->errorString_)
);
}
else {
emit finished(
AccountTaskState::STATE_OFFLINE,
tr("XBox user authentication failed: %1").arg(requestor->errorString_)
);
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
} else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
}
return;
}
Katabasis::Token temp;
if(!Parsers::parseXTokenResponse(data, temp, "UToken")) {
if (!Parsers::parseXTokenResponse(data, temp, "UToken")) {
qWarning() << "Could not parse user authentication response...";
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood."));
return;

View File

@ -4,12 +4,11 @@
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h"
class XboxUserStep : public AuthStep {
Q_OBJECT
public:
explicit XboxUserStep(AccountData *data);
public:
explicit XboxUserStep(AccountData* data);
virtual ~XboxUserStep() noexcept;
void perform() override;
@ -17,6 +16,6 @@ public:
QString describe() override;
private slots:
private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
};

View File

@ -4,7 +4,8 @@
#include "minecraft/auth/Parsers.h"
#include "minecraft/auth/Yggdrasil.h"
YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password) {
YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password)
{
m_yggdrasil = new Yggdrasil(m_data, this);
connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed);
@ -14,28 +15,32 @@ YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(dat
YggdrasilStep::~YggdrasilStep() noexcept = default;
QString YggdrasilStep::describe() {
QString YggdrasilStep::describe()
{
return tr("Logging in with Mojang account.");
}
void YggdrasilStep::rehydrate() {
void YggdrasilStep::rehydrate()
{
// NOOP, for now.
}
void YggdrasilStep::perform() {
if(m_password.size()) {
void YggdrasilStep::perform()
{
if (m_password.size()) {
m_yggdrasil->login(m_password);
}
else {
} else {
m_yggdrasil->refresh();
}
}
void YggdrasilStep::onAuthSucceeded() {
void YggdrasilStep::onAuthSucceeded()
{
emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
}
void YggdrasilStep::onAuthFailed() {
void YggdrasilStep::onAuthFailed()
{
// TODO: hook these in again, expand to MSA
// m_error = m_yggdrasil->m_error;
// m_aborted = m_yggdrasil->m_aborted;
@ -44,7 +49,7 @@ void YggdrasilStep::onAuthFailed() {
QString errorMessage = tr("Mojang user authentication failed.");
// NOTE: soft error in the first step means 'offline'
if(state == AccountTaskState::STATE_FAILED_SOFT) {
if (state == AccountTaskState::STATE_FAILED_SOFT) {
state = AccountTaskState::STATE_OFFLINE;
errorMessage = tr("Mojang user authentication ended with a network error.");
}

View File

@ -9,8 +9,8 @@ class Yggdrasil;
class YggdrasilStep : public AuthStep {
Q_OBJECT
public:
explicit YggdrasilStep(AccountData *data, QString password);
public:
explicit YggdrasilStep(AccountData* data, QString password);
virtual ~YggdrasilStep() noexcept;
void perform() override;
@ -18,11 +18,11 @@ public:
QString describe() override;
private slots:
private slots:
void onAuthSucceeded();
void onAuthFailed();
private:
Yggdrasil *m_yggdrasil = nullptr;
private:
Yggdrasil* m_yggdrasil = nullptr;
QString m_password;
};