Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into feature/java-downloader

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97
2024-06-30 22:54:23 +03:00
28 changed files with 635 additions and 226 deletions

View File

@@ -59,6 +59,9 @@ void AuthFlow::executeTask()
void AuthFlow::nextStep()
{
if (!Task::isRunning()) {
return;
}
if (m_steps.size() == 0) {
// we got to the end without an incident... assume this is all.
m_currentStep.reset();
@@ -143,4 +146,11 @@ bool AuthFlow::changeState(AccountTaskState newState, QString reason)
return false;
}
}
}
bool AuthFlow::abort()
{
emitAborted();
if (m_currentStep)
m_currentStep->abort();
return true;
}

View File

@@ -24,6 +24,9 @@ class AuthFlow : public Task {
AccountTaskState taskState() { return m_taskState; }
public slots:
bool abort() override;
signals:
void authorizeWithBrowser(const QUrl& url);
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);

View File

@@ -34,6 +34,7 @@ class AuthStep : public QObject {
public slots:
virtual void perform() = 0;
virtual void abort() {}
signals:
void finished(AccountTaskState resultingState, QString message);

View File

@@ -29,5 +29,5 @@ void GetSkinStep::onRequestDone()
{
if (m_task->error() == QNetworkReply::NoError)
m_data->minecraftProfile.skin.data = *m_response;
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin"));
emit finished(AccountTaskState::STATE_WORKING, tr("Got skin"));
}

View File

@@ -46,6 +46,8 @@
MSADeviceCodeStep::MSADeviceCodeStep(AccountData* data) : AuthStep(data)
{
m_clientId = APPLICATION->getMSAClientID();
connect(&m_expiration_timer, &QTimer::timeout, this, &MSADeviceCodeStep::abort);
connect(&m_pool_timer, &QTimer::timeout, this, &MSADeviceCodeStep::authenticateUser);
}
QString MSADeviceCodeStep::describe()
@@ -133,9 +135,7 @@ void MSADeviceCodeStep::deviceAutorizationFinished()
m_expiration_timer.setTimerType(Qt::VeryCoarseTimer);
m_expiration_timer.setInterval(rsp.expires_in * 1000);
m_expiration_timer.setSingleShot(true);
connect(&m_expiration_timer, &QTimer::timeout, this, &MSADeviceCodeStep::abort);
m_expiration_timer.start();
m_pool_timer.setTimerType(Qt::VeryCoarseTimer);
m_pool_timer.setSingleShot(true);
startPoolTimer();
@@ -157,8 +157,12 @@ void MSADeviceCodeStep::startPoolTimer()
if (m_is_aborted) {
return;
}
if (m_expiration_timer.remainingTime() < interval * 1000) {
perform();
return;
}
m_pool_timer.setInterval(interval * 1000);
connect(&m_pool_timer, &QTimer::timeout, this, &MSADeviceCodeStep::authenticateUser);
m_pool_timer.start();
}

View File

@@ -51,7 +51,7 @@ class MSADeviceCodeStep : public AuthStep {
QString describe() override;
public slots:
void abort();
void abort() override;
signals:
void authorizeWithBrowser(QString url, QString code, int expiresIn);

View File

@@ -35,22 +35,74 @@
#include "MSAStep.h"
#include <QtNetworkAuth/qoauthhttpserverreplyhandler.h>
#include <QAbstractOAuth2>
#include <QNetworkRequest>
#include <QOAuthHttpServerReplyHandler>
#include <QOAuthOobReplyHandler>
#include "Application.h"
#include "BuildConfig.h"
#include "FileSystem.h"
#include <QProcess>
#include <QSettings>
#include <QStandardPaths>
bool isSchemeHandlerRegistered()
{
#ifdef Q_OS_LINUX
QProcess process;
process.start("xdg-mime", { "query", "default", "x-scheme-handler/" + BuildConfig.LAUNCHER_APP_BINARY_NAME });
process.waitForFinished();
QString output = process.readAllStandardOutput().trimmed();
return output.contains(BuildConfig.LAUNCHER_APP_BINARY_NAME);
#elif defined(Q_OS_WIN)
QString regPath = QString("HKEY_CURRENT_USER\\Software\\Classes\\%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
QSettings settings(regPath, QSettings::NativeFormat);
return settings.contains("shell/open/command/.");
#endif
return true;
}
class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler {
Q_OBJECT
public:
explicit CustomOAuthOobReplyHandler(QObject* parent = nullptr) : QOAuthOobReplyHandler(parent)
{
connect(APPLICATION, &Application::oauthReplyRecieved, this, &QOAuthOobReplyHandler::callbackReceived);
}
~CustomOAuthOobReplyHandler() override
{
disconnect(APPLICATION, &Application::oauthReplyRecieved, this, &QOAuthOobReplyHandler::callbackReceived);
}
QString callback() const override { return BuildConfig.LAUNCHER_APP_BINARY_NAME + "://oauth/microsoft"; }
};
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
{
m_clientId = APPLICATION->getMSAClientID();
if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") ||
QFile::exists(FS::PathCombine(APPLICATION->root(), "portable.txt")) || !isSchemeHandlerRegistered())
auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
replyHandler->setCallbackText(
" <iframe src=\"https://prismlauncher.org/successful-login\" title=\"PrismLauncher Microsoft login\" style=\"position:fixed; "
"top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; "
"z-index:999999;\"/> ");
oauth2.setReplyHandler(replyHandler);
{
auto replyHandler = new QOAuthHttpServerReplyHandler(this);
replyHandler->setCallbackText(R"XXX(
<noscript>
<meta http-equiv="Refresh" content="0; URL=https://prismlauncher.org/successful-login" />
</noscript>
Login Successful, redirecting...
<script>
window.location.replace("https://prismlauncher.org/successful-login");
</script>
)XXX");
oauth2.setReplyHandler(replyHandler);
} else {
oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this));
}
oauth2.setAuthorizationUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize"));
oauth2.setAccessTokenUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token"));
oauth2.setScope("XboxLive.SignIn XboxLive.offline_access");
@@ -67,9 +119,27 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
});
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser);
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this, silent](const QAbstractOAuth2::Error err) {
auto state = AccountTaskState::STATE_FAILED_HARD;
if (oauth2.status() == QAbstractOAuth::Status::Granted || silent) {
if (err == QAbstractOAuth2::Error::NetworkError) {
state = AccountTaskState::STATE_OFFLINE;
} else {
state = AccountTaskState::STATE_FAILED_SOFT;
}
}
auto message = tr("Microsoft user authentication failed.");
if (silent) {
message = tr("Failed to refresh token.");
}
qWarning() << message;
emit finished(state, message);
});
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::error, this,
[this](const QString& error, const QString& errorDescription, const QUrl& uri) {
qWarning() << "Failed to login because" << error << errorDescription;
emit finished(AccountTaskState::STATE_FAILED_HARD, errorDescription);
});
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this,
[this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; });
@@ -89,20 +159,27 @@ void MSAStep::perform()
if (m_data->msaClientID != m_clientId) {
emit finished(AccountTaskState::STATE_DISABLED,
tr("Microsoft user authentication failed - client identification has changed."));
return;
}
if (m_data->msaToken.refresh_token.isEmpty()) {
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - refresh token is empty."));
return;
}
oauth2.setRefreshToken(m_data->msaToken.refresh_token);
oauth2.refreshAccessToken();
} else {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) {
oauth2.setModifyParametersFunction(
[](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
#else
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) {
oauth2.setModifyParametersFunction(
[](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
#endif
map->insert("prompt", "select_account");
});
*m_data = AccountData();
m_data->msaClientID = m_clientId;
oauth2.grant();
}
}
#include "MSAStep.moc"

View File

@@ -36,7 +36,7 @@ void MinecraftProfileStep::onRequestDone()
if (m_task->error() == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
m_data->minecraftProfile = MinecraftProfile();
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
emit finished(AccountTaskState::STATE_WORKING, tr("Account has no Minecraft profile."));
return;
}
if (m_task->error() != QNetworkReply::NoError) {