Merge pull request #2402 from Trial97/refactor_auth
Improve Microsoft login
This commit is contained in:
commit
acd23ff163
25
.github/workflows/build.yml
vendored
25
.github/workflows/build.yml
vendored
@ -57,14 +57,14 @@ jobs:
|
|||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "5.12.8"
|
qt_version: "5.12.8"
|
||||||
qt_modules: ""
|
qt_modules: "qtnetworkauth"
|
||||||
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "6.2.4"
|
qt_version: "6.2.4"
|
||||||
qt_modules: "qt5compat qtimageformats"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MinGW-w64"
|
name: "Windows-MinGW-w64"
|
||||||
@ -78,9 +78,9 @@ jobs:
|
|||||||
vcvars_arch: "amd64"
|
vcvars_arch: "amd64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: ''
|
qt_arch: ""
|
||||||
qt_version: '6.7.0'
|
qt_version: "6.7.0"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MSVC-arm64"
|
name: "Windows-MSVC-arm64"
|
||||||
@ -89,18 +89,18 @@ jobs:
|
|||||||
vcvars_arch: "amd64_arm64"
|
vcvars_arch: "amd64_arm64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: 'win64_msvc2019_arm64'
|
qt_arch: "win64_msvc2019_arm64"
|
||||||
qt_version: '6.7.0'
|
qt_version: "6.7.0"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
name: macOS
|
name: macOS
|
||||||
macosx_deployment_target: 11.0
|
macosx_deployment_target: 11.0
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_arch: ''
|
qt_arch: ""
|
||||||
qt_version: '6.7.0'
|
qt_version: "6.7.0"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
name: macOS-Legacy
|
name: macOS-Legacy
|
||||||
@ -108,7 +108,7 @@ jobs:
|
|||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_version: "5.15.2"
|
qt_version: "5.15.2"
|
||||||
qt_modules: ""
|
qt_modules: "qtnetworkauth"
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
@ -150,6 +150,7 @@ jobs:
|
|||||||
quazip-qt6:p
|
quazip-qt6:p
|
||||||
ccache:p
|
ccache:p
|
||||||
qt6-5compat:p
|
qt6-5compat:p
|
||||||
|
qt6-networkauth:p
|
||||||
cmark:p
|
cmark:p
|
||||||
|
|
||||||
- name: Force newer ccache
|
- name: Force newer ccache
|
||||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
sudo apt-get -y update
|
sudo apt-get -y update
|
||||||
|
|
||||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev
|
||||||
|
|
||||||
- name: Configure and Build
|
- name: Configure and Build
|
||||||
run: |
|
run: |
|
||||||
|
@ -282,7 +282,7 @@ endif()
|
|||||||
include(QtVersionlessBackport)
|
include(QtVersionlessBackport)
|
||||||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||||
set(QT_VERSION_MAJOR 5)
|
set(QT_VERSION_MAJOR 5)
|
||||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
|
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
|
||||||
|
|
||||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
find_package(QuaZip-Qt5 1.3 QUIET)
|
find_package(QuaZip-Qt5 1.3 QUIET)
|
||||||
@ -296,7 +296,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
|||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
|
||||||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||||
set(QT_VERSION_MAJOR 6)
|
set(QT_VERSION_MAJOR 6)
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat)
|
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
|
||||||
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
||||||
|
|
||||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
@ -523,7 +523,6 @@ if(NOT cmark_FOUND)
|
|||||||
else()
|
else()
|
||||||
message(STATUS "Using system cmark")
|
message(STATUS "Using system cmark")
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
|
|
||||||
add_subdirectory(libraries/gamemode)
|
add_subdirectory(libraries/gamemode)
|
||||||
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
||||||
if (NOT ghc_filesystem_FOUND)
|
if (NOT ghc_filesystem_FOUND)
|
||||||
|
26
COPYING.md
26
COPYING.md
@ -333,32 +333,6 @@
|
|||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
## O2 (Katabasis fork)
|
|
||||||
|
|
||||||
Copyright (c) 2012, Akos Polster
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
## Gamemode
|
## Gamemode
|
||||||
|
|
||||||
Copyright (c) 2017-2022, Feral Interactive
|
Copyright (c) 2017-2022, Feral Interactive
|
||||||
|
@ -126,7 +126,6 @@ set(NET_SOURCES
|
|||||||
net/MetaCacheSink.h
|
net/MetaCacheSink.h
|
||||||
net/Logging.h
|
net/Logging.h
|
||||||
net/Logging.cpp
|
net/Logging.cpp
|
||||||
net/NetAction.h
|
|
||||||
net/NetJob.cpp
|
net/NetJob.cpp
|
||||||
net/NetJob.h
|
net/NetJob.h
|
||||||
net/NetUtils.h
|
net/NetUtils.h
|
||||||
@ -210,28 +209,17 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/AccountData.h
|
minecraft/auth/AccountData.h
|
||||||
minecraft/auth/AccountList.cpp
|
minecraft/auth/AccountList.cpp
|
||||||
minecraft/auth/AccountList.h
|
minecraft/auth/AccountList.h
|
||||||
minecraft/auth/AccountTask.cpp
|
|
||||||
minecraft/auth/AccountTask.h
|
|
||||||
minecraft/auth/AuthRequest.cpp
|
|
||||||
minecraft/auth/AuthRequest.h
|
|
||||||
minecraft/auth/AuthSession.cpp
|
minecraft/auth/AuthSession.cpp
|
||||||
minecraft/auth/AuthSession.h
|
minecraft/auth/AuthSession.h
|
||||||
minecraft/auth/AuthStep.cpp
|
|
||||||
minecraft/auth/AuthStep.h
|
minecraft/auth/AuthStep.h
|
||||||
minecraft/auth/MinecraftAccount.cpp
|
minecraft/auth/MinecraftAccount.cpp
|
||||||
minecraft/auth/MinecraftAccount.h
|
minecraft/auth/MinecraftAccount.h
|
||||||
minecraft/auth/Parsers.cpp
|
minecraft/auth/Parsers.cpp
|
||||||
minecraft/auth/Parsers.h
|
minecraft/auth/Parsers.h
|
||||||
|
|
||||||
minecraft/auth/flows/AuthFlow.cpp
|
minecraft/auth/AuthFlow.cpp
|
||||||
minecraft/auth/flows/AuthFlow.h
|
minecraft/auth/AuthFlow.h
|
||||||
minecraft/auth/flows/MSA.cpp
|
|
||||||
minecraft/auth/flows/MSA.h
|
|
||||||
minecraft/auth/flows/Offline.cpp
|
|
||||||
minecraft/auth/flows/Offline.h
|
|
||||||
|
|
||||||
minecraft/auth/steps/OfflineStep.cpp
|
|
||||||
minecraft/auth/steps/OfflineStep.h
|
|
||||||
minecraft/auth/steps/EntitlementsStep.cpp
|
minecraft/auth/steps/EntitlementsStep.cpp
|
||||||
minecraft/auth/steps/EntitlementsStep.h
|
minecraft/auth/steps/EntitlementsStep.h
|
||||||
minecraft/auth/steps/GetSkinStep.cpp
|
minecraft/auth/steps/GetSkinStep.cpp
|
||||||
@ -240,6 +228,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/steps/LauncherLoginStep.h
|
minecraft/auth/steps/LauncherLoginStep.h
|
||||||
minecraft/auth/steps/MinecraftProfileStep.cpp
|
minecraft/auth/steps/MinecraftProfileStep.cpp
|
||||||
minecraft/auth/steps/MinecraftProfileStep.h
|
minecraft/auth/steps/MinecraftProfileStep.h
|
||||||
|
minecraft/auth/steps/MSADeviceCodeStep.cpp
|
||||||
|
minecraft/auth/steps/MSADeviceCodeStep.h
|
||||||
minecraft/auth/steps/MSAStep.cpp
|
minecraft/auth/steps/MSAStep.cpp
|
||||||
minecraft/auth/steps/MSAStep.h
|
minecraft/auth/steps/MSAStep.h
|
||||||
minecraft/auth/steps/XboxAuthorizationStep.cpp
|
minecraft/auth/steps/XboxAuthorizationStep.cpp
|
||||||
@ -624,7 +614,6 @@ set(PRISMUPDATER_SOURCES
|
|||||||
net/HttpMetaCache.h
|
net/HttpMetaCache.h
|
||||||
net/Logging.h
|
net/Logging.h
|
||||||
net/Logging.cpp
|
net/Logging.cpp
|
||||||
net/NetAction.h
|
|
||||||
net/NetRequest.cpp
|
net/NetRequest.cpp
|
||||||
net/NetRequest.h
|
net/NetRequest.h
|
||||||
net/NetJob.cpp
|
net/NetJob.cpp
|
||||||
@ -1241,7 +1230,6 @@ target_link_libraries(Launcher_logic
|
|||||||
tomlplusplus::tomlplusplus
|
tomlplusplus::tomlplusplus
|
||||||
qdcss
|
qdcss
|
||||||
BuildConfig
|
BuildConfig
|
||||||
Katabasis
|
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
ghcFilesystem::ghc_filesystem
|
ghcFilesystem::ghc_filesystem
|
||||||
)
|
)
|
||||||
@ -1259,6 +1247,7 @@ target_link_libraries(Launcher_logic
|
|||||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||||
Qt${QT_VERSION_MAJOR}::Gui
|
Qt${QT_VERSION_MAJOR}::Gui
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
|
Qt${QT_VERSION_MAJOR}::NetworkAuth
|
||||||
${Launcher_QT_LIBS}
|
${Launcher_QT_LIBS}
|
||||||
)
|
)
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
@ -1329,7 +1318,6 @@ if(Launcher_BUILD_UPDATER)
|
|||||||
Qt${QT_VERSION_MAJOR}::Network
|
Qt${QT_VERSION_MAJOR}::Network
|
||||||
${Launcher_QT_LIBS}
|
${Launcher_QT_LIBS}
|
||||||
cmark::cmark
|
cmark::cmark
|
||||||
Katabasis
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
InstanceCreationTask::InstanceCreationTask() = default;
|
|
||||||
|
|
||||||
void InstanceCreationTask::executeTask()
|
void InstanceCreationTask::executeTask()
|
||||||
{
|
{
|
||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
class InstanceCreationTask : public InstanceTask {
|
class InstanceCreationTask : public InstanceTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
InstanceCreationTask();
|
InstanceCreationTask() = default;
|
||||||
virtual ~InstanceCreationTask() = default;
|
virtual ~InstanceCreationTask() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -57,7 +57,6 @@
|
|||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "JavaCommon.h"
|
#include "JavaCommon.h"
|
||||||
#include "launch/steps/TextPrint.h"
|
#include "launch/steps/TextPrint.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
|
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
#include "net/Download.h"
|
#include "net/Download.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
QSet<QString> collectPathsFromDir(QString dirPath)
|
QSet<QString> collectPathsFromDir(QString dirPath)
|
||||||
@ -276,7 +277,7 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
|||||||
|
|
||||||
} // namespace AssetsUtils
|
} // namespace AssetsUtils
|
||||||
|
|
||||||
NetAction::Ptr AssetObject::getDownloadAction()
|
Net::NetRequest::Ptr AssetObject::getDownloadAction()
|
||||||
{
|
{
|
||||||
QFileInfo objectFile(getLocalPath());
|
QFileInfo objectFile(getLocalPath());
|
||||||
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
|
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
|
||||||
|
@ -17,14 +17,14 @@
|
|||||||
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include "net/NetAction.h"
|
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
struct AssetObject {
|
struct AssetObject {
|
||||||
QString getRelPath();
|
QString getRelPath();
|
||||||
QUrl getUrl();
|
QUrl getUrl();
|
||||||
QString getLocalPath();
|
QString getLocalPath();
|
||||||
NetAction::Ptr getDownloadAction();
|
Net::NetRequest::Ptr getDownloadAction();
|
||||||
|
|
||||||
QString hash;
|
QString hash;
|
||||||
qint64 size;
|
qint64 size;
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
#include "Library.h"
|
#include "Library.h"
|
||||||
#include "MinecraftInstance.h"
|
#include "MinecraftInstance.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
#include <BuildConfig.h>
|
#include <BuildConfig.h>
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
@ -74,12 +75,12 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
|
QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
|
||||||
class HttpMetaCache* cache,
|
class HttpMetaCache* cache,
|
||||||
QStringList& failedLocalFiles,
|
QStringList& failedLocalFiles,
|
||||||
const QString& overridePath) const
|
const QString& overridePath) const
|
||||||
{
|
{
|
||||||
QList<NetAction::Ptr> out;
|
QList<Net::NetRequest::Ptr> out;
|
||||||
bool stale = isAlwaysStale();
|
bool stale = isAlwaysStale();
|
||||||
bool local = isLocal();
|
bool local = isLocal();
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <net/NetAction.h>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
@ -48,6 +47,7 @@
|
|||||||
#include "MojangDownloadInfo.h"
|
#include "MojangDownloadInfo.h"
|
||||||
#include "Rule.h"
|
#include "Rule.h"
|
||||||
#include "RuntimeContext.h"
|
#include "RuntimeContext.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
class Library;
|
class Library;
|
||||||
class MinecraftInstance;
|
class MinecraftInstance;
|
||||||
@ -144,10 +144,10 @@ class Library {
|
|||||||
bool isForge() const;
|
bool isForge() const;
|
||||||
|
|
||||||
// Get a list of downloads for this library
|
// Get a list of downloads for this library
|
||||||
QList<NetAction::Ptr> getDownloads(const RuntimeContext& runtimeContext,
|
QList<Net::NetRequest::Ptr> getDownloads(const RuntimeContext& runtimeContext,
|
||||||
class HttpMetaCache* cache,
|
class HttpMetaCache* cache,
|
||||||
QStringList& failedLocalFiles,
|
QStringList& failedLocalFiles,
|
||||||
const QString& overridePath) const;
|
const QString& overridePath) const;
|
||||||
|
|
||||||
QString getCompatibleNative(const RuntimeContext& runtimeContext) const;
|
QString getCompatibleNative(const RuntimeContext& runtimeContext) const;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenName)
|
void tokenToJSONV3(QJsonObject& parent, Token t, const char* tokenName)
|
||||||
{
|
{
|
||||||
if (!t.persistent) {
|
if (!t.persistent) {
|
||||||
return;
|
return;
|
||||||
@ -74,9 +74,9 @@ void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenNam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName)
|
Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName)
|
||||||
{
|
{
|
||||||
Katabasis::Token out;
|
Token out;
|
||||||
auto tokenObject = parent.value(tokenName).toObject();
|
auto tokenObject = parent.value(tokenName).toObject();
|
||||||
if (tokenObject.isEmpty()) {
|
if (tokenObject.isEmpty()) {
|
||||||
return out;
|
return out;
|
||||||
@ -94,7 +94,7 @@ Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenNam
|
|||||||
auto token = tokenObject.value("token");
|
auto token = tokenObject.value("token");
|
||||||
if (token.isString()) {
|
if (token.isString()) {
|
||||||
out.token = token.toString();
|
out.token = token.toString();
|
||||||
out.validity = Katabasis::Validity::Assumed;
|
out.validity = Validity::Assumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto refresh_token = tokenObject.value("refresh_token");
|
auto refresh_token = tokenObject.value("refresh_token");
|
||||||
@ -241,13 +241,13 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.validity = Katabasis::Validity::Assumed;
|
out.validity = Validity::Assumed;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p)
|
void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p)
|
||||||
{
|
{
|
||||||
if (p.validity == Katabasis::Validity::None) {
|
if (p.validity == Validity::None) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonObject out;
|
QJsonObject out;
|
||||||
@ -271,7 +271,7 @@ bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
|
|||||||
}
|
}
|
||||||
out.canPlayMinecraft = canPlayMinecraftV.toBool(false);
|
out.canPlayMinecraft = canPlayMinecraftV.toBool(false);
|
||||||
out.ownsMinecraft = ownsMinecraftV.toBool(false);
|
out.ownsMinecraft = ownsMinecraftV.toBool(false);
|
||||||
out.validity = Katabasis::Validity::Assumed;
|
out.validity = Validity::Assumed;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -313,10 +313,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
|
|||||||
|
|
||||||
minecraftProfile = profileFromJSONV3(data, "profile");
|
minecraftProfile = profileFromJSONV3(data, "profile");
|
||||||
if (!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
if (!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||||
if (minecraftProfile.validity != Katabasis::Validity::None) {
|
if (minecraftProfile.validity != Validity::None) {
|
||||||
minecraftEntitlement.canPlayMinecraft = true;
|
minecraftEntitlement.canPlayMinecraft = true;
|
||||||
minecraftEntitlement.ownsMinecraft = true;
|
minecraftEntitlement.ownsMinecraft = true;
|
||||||
minecraftEntitlement.validity = Katabasis::Validity::Assumed;
|
minecraftEntitlement.validity = Validity::Assumed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,12 +34,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <katabasis/Bits.h>
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
enum class Validity { None, Assumed, Certain };
|
||||||
|
|
||||||
|
struct Token {
|
||||||
|
QDateTime issueInstant;
|
||||||
|
QDateTime notAfter;
|
||||||
|
QString token;
|
||||||
|
QString refresh_token;
|
||||||
|
QVariantMap extra;
|
||||||
|
|
||||||
|
Validity validity = Validity::None;
|
||||||
|
bool persistent = true;
|
||||||
|
};
|
||||||
|
|
||||||
struct Skin {
|
struct Skin {
|
||||||
QString id;
|
QString id;
|
||||||
QString url;
|
QString url;
|
||||||
@ -59,7 +76,7 @@ struct Cape {
|
|||||||
struct MinecraftEntitlement {
|
struct MinecraftEntitlement {
|
||||||
bool ownsMinecraft = false;
|
bool ownsMinecraft = false;
|
||||||
bool canPlayMinecraft = false;
|
bool canPlayMinecraft = false;
|
||||||
Katabasis::Validity validity = Katabasis::Validity::None;
|
Validity validity = Validity::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MinecraftProfile {
|
struct MinecraftProfile {
|
||||||
@ -68,7 +85,7 @@ struct MinecraftProfile {
|
|||||||
Skin skin;
|
Skin skin;
|
||||||
QString currentCape;
|
QString currentCape;
|
||||||
QMap<QString, Cape> capes;
|
QMap<QString, Cape> capes;
|
||||||
Katabasis::Validity validity = Katabasis::Validity::None;
|
Validity validity = Validity::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AccountType { MSA, Offline };
|
enum class AccountType { MSA, Offline };
|
||||||
@ -93,15 +110,15 @@ struct AccountData {
|
|||||||
AccountType type = AccountType::MSA;
|
AccountType type = AccountType::MSA;
|
||||||
|
|
||||||
QString msaClientID;
|
QString msaClientID;
|
||||||
Katabasis::Token msaToken;
|
Token msaToken;
|
||||||
Katabasis::Token userToken;
|
Token userToken;
|
||||||
Katabasis::Token xboxApiToken;
|
Token xboxApiToken;
|
||||||
Katabasis::Token mojangservicesToken;
|
Token mojangservicesToken;
|
||||||
|
|
||||||
Katabasis::Token yggdrasilToken;
|
Token yggdrasilToken;
|
||||||
MinecraftProfile minecraftProfile;
|
MinecraftProfile minecraftProfile;
|
||||||
MinecraftEntitlement minecraftEntitlement;
|
MinecraftEntitlement minecraftEntitlement;
|
||||||
Katabasis::Validity validity_ = Katabasis::Validity::None;
|
Validity validity_ = Validity::None;
|
||||||
|
|
||||||
// runtime only information (not saved with the account)
|
// runtime only information (not saved with the account)
|
||||||
QString internalId;
|
QString internalId;
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
#include "AccountList.h"
|
#include "AccountList.h"
|
||||||
#include "AccountData.h"
|
#include "AccountData.h"
|
||||||
#include "AccountTask.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@ -639,8 +639,8 @@ void AccountList::tryNext()
|
|||||||
if (account->internalId() == accountId) {
|
if (account->internalId() == accountId) {
|
||||||
m_currentTask = account->refresh();
|
m_currentTask = account->refresh();
|
||||||
if (m_currentTask) {
|
if (m_currentTask) {
|
||||||
connect(m_currentTask.get(), &AccountTask::succeeded, this, &AccountList::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded);
|
||||||
connect(m_currentTask.get(), &AccountTask::failed, this, &AccountList::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &AccountList::authFailed);
|
||||||
m_currentTask->start();
|
m_currentTask->start();
|
||||||
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID "
|
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID "
|
||||||
<< accountId;
|
<< accountId;
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "MinecraftAccount.h"
|
#include "MinecraftAccount.h"
|
||||||
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@ -144,7 +145,7 @@ class AccountList : public QAbstractListModel {
|
|||||||
QList<QString> m_refreshQueue;
|
QList<QString> m_refreshQueue;
|
||||||
QTimer* m_refreshTimer;
|
QTimer* m_refreshTimer;
|
||||||
QTimer* m_nextTimer;
|
QTimer* m_nextTimer;
|
||||||
shared_qobject_ptr<AccountTask> m_currentTask;
|
shared_qobject_ptr<AuthFlow> m_currentTask;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Called whenever the list changes.
|
* Called whenever the list changes.
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "AccountTask.h"
|
|
||||||
#include "MinecraftAccount.h"
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
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("...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AccountTask::changeState(AccountTaskState newState, QString reason)
|
|
||||||
{
|
|
||||||
m_taskState = newState;
|
|
||||||
// FIXME: virtual method invoked in constructor.
|
|
||||||
// We want that behavior, but maybe make it less weird?
|
|
||||||
setStatus(getStateMessage());
|
|
||||||
switch (newState) {
|
|
||||||
case AccountTaskState::STATE_CREATED: {
|
|
||||||
m_data->errorString.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_WORKING: {
|
|
||||||
m_data->accountState = AccountState::Working;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_SUCCEEDED: {
|
|
||||||
m_data->accountState = AccountState::Online;
|
|
||||||
emitSucceeded();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_OFFLINE: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Offline;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_DISABLED: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Disabled;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_FAILED_SOFT: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Errored;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_FAILED_HARD: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Expired;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_FAILED_GONE: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Gone;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
QString error = tr("Unknown account task state: %1").arg(int(newState));
|
|
||||||
m_data->accountState = AccountState::Errored;
|
|
||||||
emitFailed(error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <tasks/Task.h>
|
|
||||||
|
|
||||||
#include <qsslerror.h>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "MinecraftAccount.h"
|
|
||||||
|
|
||||||
class QNetworkReply;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum for describing the state of the current task.
|
|
||||||
* Used by the getStateMessage function to determine what the status message should be.
|
|
||||||
*/
|
|
||||||
enum class AccountTaskState {
|
|
||||||
STATE_CREATED,
|
|
||||||
STATE_WORKING,
|
|
||||||
STATE_SUCCEEDED,
|
|
||||||
STATE_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 {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit AccountTask(AccountData* data, QObject* parent = 0);
|
|
||||||
virtual ~AccountTask(){};
|
|
||||||
|
|
||||||
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
|
|
||||||
|
|
||||||
AccountTaskState taskState() { return m_taskState; }
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
|
||||||
void hideVerificationUriAndCode();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* Returns the state message for the given state.
|
|
||||||
* Used to set the status message for the task.
|
|
||||||
* Should be overridden by subclasses that want to change messages for a given state.
|
|
||||||
*/
|
|
||||||
virtual QString getStateMessage() const;
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
// NOTE: true -> non-terminal state, false -> terminal state
|
|
||||||
bool changeState(AccountTaskState newState, QString reason = QString());
|
|
||||||
|
|
||||||
protected:
|
|
||||||
AccountData* m_data = nullptr;
|
|
||||||
};
|
|
146
launcher/minecraft/auth/AuthFlow.cpp
Normal file
146
launcher/minecraft/auth/AuthFlow.cpp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#include <QDebug>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
#include "minecraft/auth/steps/EntitlementsStep.h"
|
||||||
|
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||||
|
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
||||||
|
#include "minecraft/auth/steps/MSADeviceCodeStep.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"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
#include "AuthFlow.h"
|
||||||
|
|
||||||
|
#include <Application.h>
|
||||||
|
|
||||||
|
AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(parent), m_data(data)
|
||||||
|
{
|
||||||
|
if (data->type == AccountType::MSA) {
|
||||||
|
if (action == Action::DeviceCode) {
|
||||||
|
auto oauthStep = makeShared<MSADeviceCodeStep>(m_data);
|
||||||
|
connect(oauthStep.get(), &MSADeviceCodeStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowserWithExtra);
|
||||||
|
connect(this, &Task::aborted, oauthStep.get(), &MSADeviceCodeStep::abort);
|
||||||
|
m_steps.append(oauthStep);
|
||||||
|
} else {
|
||||||
|
auto oauthStep = makeShared<MSAStep>(m_data, action == Action::Refresh);
|
||||||
|
connect(oauthStep.get(), &MSAStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowser);
|
||||||
|
m_steps.append(oauthStep);
|
||||||
|
}
|
||||||
|
m_steps.append(makeShared<XboxUserStep>(m_data));
|
||||||
|
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
||||||
|
m_steps.append(
|
||||||
|
makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
|
||||||
|
m_steps.append(makeShared<LauncherLoginStep>(m_data));
|
||||||
|
m_steps.append(makeShared<XboxProfileStep>(m_data));
|
||||||
|
m_steps.append(makeShared<EntitlementsStep>(m_data));
|
||||||
|
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
|
||||||
|
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||||
|
}
|
||||||
|
changeState(AccountTaskState::STATE_CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthFlow::succeed()
|
||||||
|
{
|
||||||
|
m_data->validity_ = Validity::Certain;
|
||||||
|
changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthFlow::executeTask()
|
||||||
|
{
|
||||||
|
changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
|
||||||
|
nextStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthFlow::nextStep()
|
||||||
|
{
|
||||||
|
if (m_steps.size() == 0) {
|
||||||
|
// we got to the end without an incident... assume this is all.
|
||||||
|
m_currentStep.reset();
|
||||||
|
succeed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_currentStep = m_steps.front();
|
||||||
|
qDebug() << "AuthFlow:" << m_currentStep->describe();
|
||||||
|
m_steps.pop_front();
|
||||||
|
connect(m_currentStep.get(), &AuthStep::finished, this, &AuthFlow::stepFinished);
|
||||||
|
|
||||||
|
m_currentStep->perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
|
||||||
|
{
|
||||||
|
if (changeState(resultingState, message))
|
||||||
|
nextStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AuthFlow::changeState(AccountTaskState newState, QString reason)
|
||||||
|
{
|
||||||
|
m_taskState = newState;
|
||||||
|
setDetails(reason);
|
||||||
|
switch (newState) {
|
||||||
|
case AccountTaskState::STATE_CREATED: {
|
||||||
|
setStatus(tr("Waiting..."));
|
||||||
|
m_data->errorString.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_WORKING: {
|
||||||
|
setStatus(m_currentStep ? m_currentStep->describe() : tr("Working..."));
|
||||||
|
m_data->accountState = AccountState::Working;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_SUCCEEDED: {
|
||||||
|
setStatus(tr("Authentication task succeeded."));
|
||||||
|
m_data->accountState = AccountState::Online;
|
||||||
|
emitSucceeded();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_OFFLINE: {
|
||||||
|
setStatus(tr("Failed to contact the authentication server."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Offline;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_DISABLED: {
|
||||||
|
setStatus(tr("Client ID has changed. New session needs to be created."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Disabled;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_FAILED_SOFT: {
|
||||||
|
setStatus(tr("Encountered an error during authentication."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Errored;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_FAILED_HARD: {
|
||||||
|
setStatus(tr("Failed to authenticate. The session has expired."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Expired;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_FAILED_GONE: {
|
||||||
|
setStatus(tr("Failed to authenticate. The account no longer exists."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Gone;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
setStatus(tr("..."));
|
||||||
|
QString error = tr("Unknown account task state: %1").arg(int(newState));
|
||||||
|
m_data->accountState = AccountState::Errored;
|
||||||
|
emitFailed(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
launcher/minecraft/auth/AuthFlow.h
Normal file
45
launcher/minecraft/auth/AuthFlow.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QList>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
class AuthFlow : public Task {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class Action { Refresh, Login, DeviceCode };
|
||||||
|
|
||||||
|
explicit AuthFlow(AccountData* data, Action action = Action::Refresh, QObject* parent = 0);
|
||||||
|
virtual ~AuthFlow() = default;
|
||||||
|
|
||||||
|
void executeTask() override;
|
||||||
|
|
||||||
|
AccountTaskState taskState() { return m_taskState; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void authorizeWithBrowser(const QUrl& url);
|
||||||
|
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void succeed();
|
||||||
|
void nextStep();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// NOTE: true -> non-terminal state, false -> terminal state
|
||||||
|
bool changeState(AccountTaskState newState, QString reason = QString());
|
||||||
|
void stepFinished(AccountTaskState resultingState, QString message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
|
||||||
|
QList<AuthStep::Ptr> m_steps;
|
||||||
|
AuthStep::Ptr m_currentStep;
|
||||||
|
AccountData* m_data = nullptr;
|
||||||
|
};
|
@ -1,175 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QUrlQuery>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
#include "AuthRequest.h"
|
|
||||||
#include "katabasis/Globals.h"
|
|
||||||
|
|
||||||
AuthRequest::AuthRequest(QObject* parent) : QObject(parent) {}
|
|
||||||
|
|
||||||
AuthRequest::~AuthRequest() {}
|
|
||||||
|
|
||||||
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
|
|
||||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
|
||||||
#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*/)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
|
||||||
#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);
|
|
||||||
connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::onRequestFinished()
|
|
||||||
{
|
|
||||||
if (status_ == Idle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::onRequestError(QNetworkReply::NetworkError error)
|
|
||||||
{
|
|
||||||
qWarning() << "AuthRequest::onRequestError: Error" << (int)error;
|
|
||||||
if (status_ == Idle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
|
|
||||||
// QTimer::singleShot(10, this, SLOT(finish()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::onSslErrors(QList<QSslError> errors)
|
|
||||||
{
|
|
||||||
int i = 1;
|
|
||||||
for (auto error : errors) {
|
|
||||||
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
|
|
||||||
auto cert = error.certificate();
|
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total)
|
|
||||||
{
|
|
||||||
if (status_ == Idle) {
|
|
||||||
qWarning() << "AuthRequest::onUploadProgress: No pending request";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Restart timeout because request in progress
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
request_ = req;
|
|
||||||
operation_ = operation;
|
|
||||||
url_ = req.url();
|
|
||||||
|
|
||||||
QUrl url = url_;
|
|
||||||
request_.setUrl(url);
|
|
||||||
|
|
||||||
if (!verb.isEmpty()) {
|
|
||||||
request_.setRawHeader(Katabasis::HTTP_HTTP_HEADER, verb);
|
|
||||||
}
|
|
||||||
|
|
||||||
status_ = Requesting;
|
|
||||||
error_ = QNetworkReply::NoError;
|
|
||||||
errorString_.clear();
|
|
||||||
httpStatus_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::finish()
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
if (status_ == Idle) {
|
|
||||||
qWarning() << "AuthRequest::finish: No pending request";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
data = reply_->readAll();
|
|
||||||
status_ = Idle;
|
|
||||||
timedReplies_.remove(reply_);
|
|
||||||
reply_->disconnect(this);
|
|
||||||
reply_->deleteLater();
|
|
||||||
QList<QNetworkReply::RawHeaderPair> headers = reply_->rawHeaderPairs();
|
|
||||||
emit finished(error_, data, headers);
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include "katabasis/Reply.h"
|
|
||||||
|
|
||||||
/// Makes authentication requests.
|
|
||||||
class AuthRequest : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
|
|
||||||
/// Emitted when a request has been completed or failed.
|
|
||||||
void finished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
|
||||||
|
|
||||||
/// Emitted when an upload has progressed.
|
|
||||||
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
|
|
||||||
/// Handle request finished.
|
|
||||||
void onRequestFinished();
|
|
||||||
|
|
||||||
/// Handle request error.
|
|
||||||
void onRequestError(QNetworkReply::NetworkError error);
|
|
||||||
|
|
||||||
/// Handle ssl errors.
|
|
||||||
void onSslErrors(QList<QSslError> errors);
|
|
||||||
|
|
||||||
/// Finish the request, emit finished() signal.
|
|
||||||
void finish();
|
|
||||||
|
|
||||||
/// Handle upload progress.
|
|
||||||
void onUploadProgress(qint64 uploaded, qint64 total);
|
|
||||||
|
|
||||||
public:
|
|
||||||
QNetworkReply::NetworkError error_;
|
|
||||||
int httpStatus_ = 0;
|
|
||||||
QString errorString_;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void setup(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& verb = QByteArray());
|
|
||||||
|
|
||||||
enum Status { Idle, Requesting, ReRequesting };
|
|
||||||
|
|
||||||
QNetworkRequest request_;
|
|
||||||
QByteArray data_;
|
|
||||||
QNetworkReply* reply_;
|
|
||||||
Status status_;
|
|
||||||
QNetworkAccessManager::Operation operation_;
|
|
||||||
QUrl url_;
|
|
||||||
Katabasis::ReplyList timedReplies_;
|
|
||||||
|
|
||||||
QTimer* timer_;
|
|
||||||
};
|
|
@ -1,5 +0,0 @@
|
|||||||
#include "AuthStep.h"
|
|
||||||
|
|
||||||
AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {}
|
|
||||||
|
|
||||||
AuthStep::~AuthStep() noexcept = default;
|
|
@ -3,30 +3,40 @@
|
|||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "AccountTask.h"
|
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "minecraft/auth/AccountData.h"
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for describing the state of the current task.
|
||||||
|
* Used by the getStateMessage function to determine what the status message should be.
|
||||||
|
*/
|
||||||
|
enum class AccountTaskState {
|
||||||
|
STATE_CREATED,
|
||||||
|
STATE_WORKING,
|
||||||
|
STATE_SUCCEEDED,
|
||||||
|
STATE_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 AuthStep : public QObject {
|
class AuthStep : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Ptr = shared_qobject_ptr<AuthStep>;
|
using Ptr = shared_qobject_ptr<AuthStep>;
|
||||||
|
|
||||||
public:
|
explicit AuthStep(AccountData* data) : QObject(nullptr), m_data(data){};
|
||||||
explicit AuthStep(AccountData* data);
|
virtual ~AuthStep() noexcept = default;
|
||||||
virtual ~AuthStep() noexcept;
|
|
||||||
|
|
||||||
virtual QString describe() = 0;
|
virtual QString describe() = 0;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void perform() = 0;
|
virtual void perform() = 0;
|
||||||
virtual void rehydrate() = 0;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished(AccountTaskState resultingState, QString message);
|
void finished(AccountTaskState resultingState, QString message);
|
||||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
|
||||||
void hideVerificationUriAndCode();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AccountData* m_data;
|
AccountData* m_data;
|
||||||
|
@ -50,9 +50,8 @@
|
|||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#include "flows/MSA.h"
|
|
||||||
#include "flows/Offline.h"
|
|
||||||
#include "minecraft/auth/AccountData.h"
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
|
|
||||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
@ -80,7 +79,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
|
|||||||
auto account = makeShared<MinecraftAccount>();
|
auto account = makeShared<MinecraftAccount>();
|
||||||
account->data.type = AccountType::Offline;
|
account->data.type = AccountType::Offline;
|
||||||
account->data.yggdrasilToken.token = "0";
|
account->data.yggdrasilToken.token = "0";
|
||||||
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
|
account->data.yggdrasilToken.validity = Validity::Certain;
|
||||||
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
account->data.yggdrasilToken.extra["userName"] = username;
|
account->data.yggdrasilToken.extra["userName"] = username;
|
||||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||||
@ -88,7 +87,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
|
|||||||
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
||||||
account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]"));
|
account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]"));
|
||||||
account->data.minecraftProfile.name = username;
|
account->data.minecraftProfile.name = username;
|
||||||
account->data.minecraftProfile.validity = Katabasis::Validity::Certain;
|
account->data.minecraftProfile.validity = Validity::Certain;
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,11 +119,11 @@ QPixmap MinecraftAccount::getFace() const
|
|||||||
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
|
shared_qobject_ptr<AuthFlow> MinecraftAccount::login(bool useDeviceCode)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
|
|
||||||
m_currentTask.reset(new MSAInteractive(&data));
|
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this));
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
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")); });
|
||||||
@ -132,29 +131,13 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
|
|||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline()
|
shared_qobject_ptr<AuthFlow> MinecraftAccount::refresh()
|
||||||
{
|
|
||||||
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")); });
|
|
||||||
emit activityChanged(true);
|
|
||||||
return m_currentTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
|
|
||||||
{
|
{
|
||||||
if (m_currentTask) {
|
if (m_currentTask) {
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type == AccountType::MSA) {
|
m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh, this));
|
||||||
m_currentTask.reset(new MSASilent(&data));
|
|
||||||
} else {
|
|
||||||
m_currentTask.reset(new OfflineRefresh(&data));
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
@ -163,7 +146,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
|
|||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask()
|
shared_qobject_ptr<AuthFlow> MinecraftAccount::currentTask()
|
||||||
{
|
{
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
@ -189,17 +172,17 @@ void MinecraftAccount::authFailed(QString reason)
|
|||||||
if (accountType() == AccountType::MSA) {
|
if (accountType() == AccountType::MSA) {
|
||||||
data.msaToken.token = QString();
|
data.msaToken.token = QString();
|
||||||
data.msaToken.refresh_token = QString();
|
data.msaToken.refresh_token = QString();
|
||||||
data.msaToken.validity = Katabasis::Validity::None;
|
data.msaToken.validity = Validity::None;
|
||||||
data.validity_ = Katabasis::Validity::None;
|
data.validity_ = Validity::None;
|
||||||
} else {
|
} else {
|
||||||
data.yggdrasilToken.token = QString();
|
data.yggdrasilToken.token = QString();
|
||||||
data.yggdrasilToken.validity = Katabasis::Validity::None;
|
data.yggdrasilToken.validity = Validity::None;
|
||||||
data.validity_ = Katabasis::Validity::None;
|
data.validity_ = Validity::None;
|
||||||
}
|
}
|
||||||
emit changed();
|
emit changed();
|
||||||
} break;
|
} break;
|
||||||
case AccountTaskState::STATE_FAILED_GONE: {
|
case AccountTaskState::STATE_FAILED_GONE: {
|
||||||
data.validity_ = Katabasis::Validity::None;
|
data.validity_ = Validity::None;
|
||||||
emit changed();
|
emit changed();
|
||||||
} break;
|
} break;
|
||||||
case AccountTaskState::STATE_CREATED:
|
case AccountTaskState::STATE_CREATED:
|
||||||
@ -229,13 +212,13 @@ bool MinecraftAccount::shouldRefresh() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch (data.validity_) {
|
switch (data.validity_) {
|
||||||
case Katabasis::Validity::Certain: {
|
case Validity::Certain: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Katabasis::Validity::None: {
|
case Validity::None: {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case Katabasis::Validity::Assumed: {
|
case Validity::Assumed: {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,15 +43,13 @@
|
|||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "AccountData.h"
|
#include "AccountData.h"
|
||||||
#include "AuthSession.h"
|
#include "AuthSession.h"
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "Usable.h"
|
#include "Usable.h"
|
||||||
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
|
|
||||||
class Task;
|
class Task;
|
||||||
class AccountTask;
|
|
||||||
class MinecraftAccount;
|
class MinecraftAccount;
|
||||||
|
|
||||||
using MinecraftAccountPtr = shared_qobject_ptr<MinecraftAccount>;
|
using MinecraftAccountPtr = shared_qobject_ptr<MinecraftAccount>;
|
||||||
@ -97,13 +95,11 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
QJsonObject saveToJson() const;
|
QJsonObject saveToJson() const;
|
||||||
|
|
||||||
public: /* manipulation */
|
public: /* manipulation */
|
||||||
shared_qobject_ptr<AccountTask> loginMSA();
|
shared_qobject_ptr<AuthFlow> login(bool useDeviceCode = false);
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> loginOffline();
|
shared_qobject_ptr<AuthFlow> refresh();
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> refresh();
|
shared_qobject_ptr<AuthFlow> currentTask();
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> currentTask();
|
|
||||||
|
|
||||||
public: /* queries */
|
public: /* queries */
|
||||||
QString internalId() const { return data.internalId; }
|
QString internalId() const { return data.internalId; }
|
||||||
@ -166,7 +162,7 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
AccountData data;
|
AccountData data;
|
||||||
|
|
||||||
// current task we are executing here
|
// current task we are executing here
|
||||||
shared_qobject_ptr<AccountTask> m_currentTask;
|
shared_qobject_ptr<AuthFlow> m_currentTask;
|
||||||
|
|
||||||
protected: /* methods */
|
protected: /* methods */
|
||||||
void incrementUses() override;
|
void incrementUses() override;
|
||||||
|
@ -79,7 +79,7 @@ bool getBool(QJsonValue value, bool& out)
|
|||||||
// 2148916238 = child account not linked to a family
|
// 2148916238 = child account not linked to a family
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name)
|
bool parseXTokenResponse(QByteArray& data, Token& output, QString name)
|
||||||
{
|
{
|
||||||
qDebug() << "Parsing" << name << ":";
|
qDebug() << "Parsing" << name << ":";
|
||||||
qCDebug(authCredentials()) << data;
|
qCDebug(authCredentials()) << data;
|
||||||
@ -135,7 +135,7 @@ bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString nam
|
|||||||
qWarning() << "Missing uhs";
|
qWarning() << "Missing uhs";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
qDebug() << name << "is valid.";
|
qDebug() << name << "is valid.";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
|
|||||||
output.capes[capeOut.id] = capeOut;
|
output.capes[capeOut.id] = capeOut;
|
||||||
}
|
}
|
||||||
output.currentCape = currentCape;
|
output.currentCape = currentCape;
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +388,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
|||||||
output.currentCape = capeOut.alias;
|
output.currentCape = capeOut.alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,7 +422,7 @@ bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output)
|
|||||||
output.ownsMinecraft = true;
|
output.ownsMinecraft = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +456,7 @@ bool parseRolloutResponse(QByteArray& data, bool& result)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output)
|
bool parseMojangResponse(QByteArray& data, Token& output)
|
||||||
{
|
{
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
qDebug() << "Parsing Mojang response...";
|
qDebug() << "Parsing Mojang response...";
|
||||||
@ -488,7 +488,7 @@ bool parseMojangResponse(QByteArray& data, Katabasis::Token& output)
|
|||||||
qWarning() << "access_token is not valid";
|
qWarning() << "access_token is not valid";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
qDebug() << "Mojang response is valid.";
|
qDebug() << "Mojang response is valid.";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ bool getNumber(QJsonValue value, double& out);
|
|||||||
bool getNumber(QJsonValue value, int64_t& out);
|
bool getNumber(QJsonValue value, int64_t& out);
|
||||||
bool getBool(QJsonValue value, bool& out);
|
bool getBool(QJsonValue value, bool& out);
|
||||||
|
|
||||||
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name);
|
bool parseXTokenResponse(QByteArray& data, Token& output, QString name);
|
||||||
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output);
|
bool parseMojangResponse(QByteArray& data, Token& output);
|
||||||
|
|
||||||
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output);
|
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output);
|
||||||
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output);
|
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output);
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
#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) {}
|
|
||||||
|
|
||||||
void AuthFlow::succeed()
|
|
||||||
{
|
|
||||||
m_data->validity_ = Katabasis::Validity::Certain;
|
|
||||||
changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthFlow::executeTask()
|
|
||||||
{
|
|
||||||
if (m_currentStep) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
|
|
||||||
nextStep();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthFlow::nextStep()
|
|
||||||
{
|
|
||||||
if (m_steps.size() == 0) {
|
|
||||||
// we got to the end without an incident... assume this is all.
|
|
||||||
m_currentStep.reset();
|
|
||||||
succeed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_currentStep = m_steps.front();
|
|
||||||
qDebug() << "AuthFlow:" << m_currentStep->describe();
|
|
||||||
m_steps.pop_front();
|
|
||||||
connect(m_currentStep.get(), &AuthStep::finished, this, &AuthFlow::stepFinished);
|
|
||||||
connect(m_currentStep.get(), &AuthStep::showVerificationUriAndCode, this, &AuthFlow::showVerificationUriAndCode);
|
|
||||||
connect(m_currentStep.get(), &AuthStep::hideVerificationUriAndCode, this, &AuthFlow::hideVerificationUriAndCode);
|
|
||||||
|
|
||||||
m_currentStep->perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AuthFlow::getStateMessage() const
|
|
||||||
{
|
|
||||||
switch (m_taskState) {
|
|
||||||
case AccountTaskState::STATE_WORKING: {
|
|
||||||
if (m_currentStep) {
|
|
||||||
return m_currentStep->describe();
|
|
||||||
} else {
|
|
||||||
return tr("Working...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return AccountTask::getStateMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
|
|
||||||
{
|
|
||||||
if (changeState(resultingState, message)) {
|
|
||||||
nextStep();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QImage>
|
|
||||||
#include <QList>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QSet>
|
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
#include <katabasis/DeviceFlow.h>
|
|
||||||
|
|
||||||
#include "minecraft/auth/AccountData.h"
|
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
|
||||||
|
|
||||||
class AuthFlow : public AccountTask {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AuthFlow(AccountData* data, QObject* parent = 0);
|
|
||||||
|
|
||||||
Katabasis::Validity validity() { return m_data->validity_; };
|
|
||||||
|
|
||||||
QString getStateMessage() const override;
|
|
||||||
|
|
||||||
void executeTask() override;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void activityChanged(Katabasis::Activity activity);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void stepFinished(AccountTaskState resultingState, QString message);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void succeed();
|
|
||||||
void nextStep();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QList<AuthStep::Ptr> m_steps;
|
|
||||||
AuthStep::Ptr m_currentStep;
|
|
||||||
};
|
|
@ -1,36 +0,0 @@
|
|||||||
#include "MSA.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/steps/EntitlementsStep.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)
|
|
||||||
{
|
|
||||||
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"));
|
|
||||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
|
|
||||||
m_steps.append(makeShared<LauncherLoginStep>(m_data));
|
|
||||||
m_steps.append(makeShared<XboxProfileStep>(m_data));
|
|
||||||
m_steps.append(makeShared<EntitlementsStep>(m_data));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
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"));
|
|
||||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
|
|
||||||
m_steps.append(makeShared<LauncherLoginStep>(m_data));
|
|
||||||
m_steps.append(makeShared<XboxProfileStep>(m_data));
|
|
||||||
m_steps.append(makeShared<EntitlementsStep>(m_data));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "AuthFlow.h"
|
|
||||||
|
|
||||||
class MSAInteractive : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MSAInteractive(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
class MSASilent : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MSASilent(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
@ -1,13 +0,0 @@
|
|||||||
#include "Offline.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/steps/OfflineStep.h"
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<OfflineStep>(m_data));
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "AuthFlow.h"
|
|
||||||
|
|
||||||
class OfflineRefresh : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit OfflineRefresh(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
class OfflineLogin : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit OfflineLogin(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
@ -1,16 +1,20 @@
|
|||||||
#include "EntitlementsStep.h"
|
#include "EntitlementsStep.h"
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
#include <QUrl>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
|
EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
EntitlementsStep::~EntitlementsStep() noexcept = default;
|
|
||||||
|
|
||||||
QString EntitlementsStep::describe()
|
QString EntitlementsStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Determining game ownership.");
|
return tr("Determining game ownership.");
|
||||||
@ -19,35 +23,31 @@ QString EntitlementsStep::describe()
|
|||||||
void EntitlementsStep::perform()
|
void EntitlementsStep::perform()
|
||||||
{
|
{
|
||||||
auto uuid = QUuid::createUuid();
|
auto uuid = QUuid::createUuid();
|
||||||
m_entitlementsRequestId = uuid.toString().remove('{').remove('}');
|
m_entitlements_request_id = uuid.toString().remove('{').remove('}');
|
||||||
auto url = "https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlementsRequestId;
|
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
QUrl url("https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlements_request_id);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||||
request.setRawHeader("Accept", "application/json");
|
{ "Accept", "application/json" },
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
{ "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } };
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
|
||||||
connect(requestor, &AuthRequest::finished, this, &EntitlementsStep::onRequestDone);
|
m_response.reset(new QByteArray());
|
||||||
requestor->get(request);
|
m_task = Net::Download::makeByteArray(url, m_response);
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &EntitlementsStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
qDebug() << "Getting entitlements...";
|
qDebug() << "Getting entitlements...";
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntitlementsStep::rehydrate()
|
void EntitlementsStep::onRequestDone()
|
||||||
{
|
{
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
qCDebug(authCredentials()) << *m_response;
|
||||||
}
|
|
||||||
|
|
||||||
void EntitlementsStep::onRequestDone([[maybe_unused]] QNetworkReply::NetworkError error,
|
|
||||||
QByteArray data,
|
|
||||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
|
|
||||||
// TODO: check presence of same entitlementsRequestId?
|
// TODO: check presence of same entitlementsRequestId?
|
||||||
// TODO: validate JWTs?
|
// TODO: validate JWTs?
|
||||||
Parsers::parseMinecraftEntitlements(data, m_data->minecraftEntitlement);
|
Parsers::parseMinecraftEntitlements(*m_response, m_data->minecraftEntitlement);
|
||||||
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements"));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements"));
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class EntitlementsStep : public AuthStep {
|
class EntitlementsStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EntitlementsStep(AccountData* data);
|
explicit EntitlementsStep(AccountData* data);
|
||||||
virtual ~EntitlementsStep() noexcept;
|
virtual ~EntitlementsStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_entitlementsRequestId;
|
QString m_entitlements_request_id;
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -3,13 +3,10 @@
|
|||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "Application.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.");
|
return tr("Getting skin.");
|
||||||
@ -17,25 +14,20 @@ QString GetSkinStep::describe()
|
|||||||
|
|
||||||
void GetSkinStep::perform()
|
void GetSkinStep::perform()
|
||||||
{
|
{
|
||||||
auto url = QUrl(m_data->minecraftProfile.skin.url);
|
QUrl url(m_data->minecraftProfile.skin.url);
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
m_response.reset(new QByteArray());
|
||||||
connect(requestor, &AuthRequest::finished, this, &GetSkinStep::onRequestDone);
|
m_task = Net::Download::makeByteArray(url, m_response);
|
||||||
requestor->get(request);
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &GetSkinStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetSkinStep::rehydrate()
|
void GetSkinStep::onRequestDone()
|
||||||
{
|
{
|
||||||
// NOOP, for now.
|
if (m_task->error() == QNetworkReply::NoError)
|
||||||
}
|
m_data->minecraftProfile.skin.data = *m_response;
|
||||||
|
|
||||||
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) {
|
|
||||||
m_data->minecraftProfile.skin.data = data;
|
|
||||||
}
|
|
||||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin"));
|
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin"));
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class GetSkinStep : public AuthStep {
|
class GetSkinStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GetSkinStep(AccountData* data);
|
explicit GetSkinStep(AccountData* data);
|
||||||
virtual ~GetSkinStep() noexcept;
|
virtual ~GetSkinStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
#include "LauncherLoginStep.h"
|
#include "LauncherLoginStep.h"
|
||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
#include "net/Upload.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.");
|
return tr("Accessing Mojang services.");
|
||||||
@ -19,7 +19,7 @@ QString LauncherLoginStep::describe()
|
|||||||
|
|
||||||
void LauncherLoginStep::perform()
|
void LauncherLoginStep::perform()
|
||||||
{
|
{
|
||||||
auto requestURL = "https://api.minecraftservices.com/launcher/login";
|
QUrl url("https://api.minecraftservices.com/launcher/login");
|
||||||
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
|
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
|
||||||
auto xToken = m_data->mojangservicesToken.token;
|
auto xToken = m_data->mojangservicesToken.token;
|
||||||
|
|
||||||
@ -31,40 +31,37 @@ void LauncherLoginStep::perform()
|
|||||||
)XXX";
|
)XXX";
|
||||||
auto requestBody = mc_auth_template.arg(uhs, xToken);
|
auto requestBody = mc_auth_template.arg(uhs, xToken);
|
||||||
|
|
||||||
QNetworkRequest request = QNetworkRequest(QUrl(requestURL));
|
auto headers = QList<Net::HeaderPair>{
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
{ "Content-Type", "application/json" },
|
||||||
request.setRawHeader("Accept", "application/json");
|
{ "Accept", "application/json" },
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
};
|
||||||
connect(requestor, &AuthRequest::finished, this, &LauncherLoginStep::onRequestDone);
|
|
||||||
requestor->post(request, requestBody.toUtf8());
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8());
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &LauncherLoginStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
qDebug() << "Getting Minecraft access token...";
|
qDebug() << "Getting Minecraft access token...";
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherLoginStep::rehydrate()
|
void LauncherLoginStep::onRequestDone()
|
||||||
{
|
{
|
||||||
// TODO: check the token validity
|
qCDebug(authCredentials()) << *m_response;
|
||||||
}
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "Reply error:" << m_task->error();
|
||||||
void LauncherLoginStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
{
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString()));
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
if (error != QNetworkReply::NoError) {
|
|
||||||
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 {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
|
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
|
if (!Parsers::parseMojangResponse(*m_response, m_data->yggdrasilToken)) {
|
||||||
qWarning() << "Could not parse login_with_xbox response...";
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
class LauncherLoginStep : public AuthStep {
|
class LauncherLoginStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LauncherLoginStep(AccountData* data);
|
explicit LauncherLoginStep(AccountData* data);
|
||||||
virtual ~LauncherLoginStep() noexcept;
|
virtual ~LauncherLoginStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
270
launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp
Normal file
270
launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "MSADeviceCodeStep.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "Json.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code
|
||||||
|
MSADeviceCodeStep::MSADeviceCodeStep(AccountData* data) : AuthStep(data)
|
||||||
|
{
|
||||||
|
m_clientId = APPLICATION->getMSAClientID();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MSADeviceCodeStep::describe()
|
||||||
|
{
|
||||||
|
return tr("Logging in with Microsoft account(device code).");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::perform()
|
||||||
|
{
|
||||||
|
QUrlQuery data;
|
||||||
|
data.addQueryItem("client_id", m_clientId);
|
||||||
|
data.addQueryItem("scope", "XboxLive.SignIn XboxLive.offline_access");
|
||||||
|
auto payload = data.query(QUrl::FullyEncoded).toUtf8();
|
||||||
|
QUrl url("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode");
|
||||||
|
auto headers = QList<Net::HeaderPair>{
|
||||||
|
{ "Content-Type", "application/x-www-form-urlencoded" },
|
||||||
|
{ "Accept", "application/json" },
|
||||||
|
};
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, payload);
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceAutorizationResponse {
|
||||||
|
QString device_code;
|
||||||
|
QString user_code;
|
||||||
|
QString verification_uri;
|
||||||
|
int expires_in;
|
||||||
|
int interval;
|
||||||
|
|
||||||
|
QString error;
|
||||||
|
QString error_description;
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data)
|
||||||
|
{
|
||||||
|
QJsonParseError err;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||||
|
if (err.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.isObject()) {
|
||||||
|
qWarning() << "Device autorization response is not an object";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
return {
|
||||||
|
Json::ensureString(obj, "device_code"), Json::ensureString(obj, "user_code"), Json::ensureString(obj, "verification_uri"),
|
||||||
|
Json::ensureInteger(obj, "expires_in"), Json::ensureInteger(obj, "interval"), Json::ensureString(obj, "error"),
|
||||||
|
Json::ensureString(obj, "error_description"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::deviceAutorizationFinished()
|
||||||
|
{
|
||||||
|
auto rsp = parseDeviceAutorizationResponse(*m_response);
|
||||||
|
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
|
||||||
|
qWarning() << "Device authorization failed:" << rsp.error;
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD,
|
||||||
|
tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_task->wasSuccessful() || m_task->error() != QNetworkReply::NoError) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to retrieve device authorization"));
|
||||||
|
qDebug() << *m_response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rsp.device_code.isEmpty() || rsp.user_code.isEmpty() || rsp.verification_uri.isEmpty() || rsp.expires_in == 0) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: required fields missing"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rsp.interval != 0) {
|
||||||
|
interval = rsp.interval;
|
||||||
|
}
|
||||||
|
m_device_code = rsp.device_code;
|
||||||
|
emit authorizeWithBrowser(rsp.verification_uri, rsp.user_code, rsp.expires_in);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::abort()
|
||||||
|
{
|
||||||
|
m_expiration_timer.stop();
|
||||||
|
m_pool_timer.stop();
|
||||||
|
if (m_task) {
|
||||||
|
m_task->abort();
|
||||||
|
}
|
||||||
|
m_is_aborted = true;
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Task aborted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::startPoolTimer()
|
||||||
|
{
|
||||||
|
if (m_is_aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_pool_timer.setInterval(interval * 1000);
|
||||||
|
connect(&m_pool_timer, &QTimer::timeout, this, &MSADeviceCodeStep::authenticateUser);
|
||||||
|
m_pool_timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::authenticateUser()
|
||||||
|
{
|
||||||
|
QUrlQuery data;
|
||||||
|
data.addQueryItem("client_id", m_clientId);
|
||||||
|
data.addQueryItem("grant_type", "urn:ietf:params:oauth:grant-type:device_code");
|
||||||
|
data.addQueryItem("device_code", m_device_code);
|
||||||
|
auto payload = data.query(QUrl::FullyEncoded).toUtf8();
|
||||||
|
QUrl url("https://login.microsoftonline.com/consumers/oauth2/v2.0/token");
|
||||||
|
auto headers = QList<Net::HeaderPair>{
|
||||||
|
{ "Content-Type", "application/x-www-form-urlencoded" },
|
||||||
|
{ "Accept", "application/json" },
|
||||||
|
};
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, payload);
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::authenticationFinished);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AuthenticationResponse {
|
||||||
|
QString access_token;
|
||||||
|
QString token_type;
|
||||||
|
QString refresh_token;
|
||||||
|
int expires_in;
|
||||||
|
|
||||||
|
QString error;
|
||||||
|
QString error_description;
|
||||||
|
|
||||||
|
QVariantMap extra;
|
||||||
|
};
|
||||||
|
|
||||||
|
AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
|
||||||
|
{
|
||||||
|
QJsonParseError err;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||||
|
if (err.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.isObject()) {
|
||||||
|
qWarning() << "Device autorization response is not an object";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
return { Json::ensureString(obj, "access_token"),
|
||||||
|
Json::ensureString(obj, "token_type"),
|
||||||
|
Json::ensureString(obj, "refresh_token"),
|
||||||
|
Json::ensureInteger(obj, "expires_in"),
|
||||||
|
Json::ensureString(obj, "error"),
|
||||||
|
Json::ensureString(obj, "error_description"),
|
||||||
|
obj.toVariantMap() };
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::authenticationFinished()
|
||||||
|
{
|
||||||
|
if (m_task->error() == QNetworkReply::TimeoutError) {
|
||||||
|
// rfc8628#section-3.5
|
||||||
|
// "On encountering a connection timeout, clients MUST unilaterally
|
||||||
|
// reduce their polling frequency before retrying. The use of an
|
||||||
|
// exponential backoff algorithm to achieve this, such as doubling the
|
||||||
|
// polling interval on each such connection timeout, is RECOMMENDED."
|
||||||
|
interval *= 2;
|
||||||
|
startPoolTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto rsp = parseAuthenticationResponse(*m_response);
|
||||||
|
if (rsp.error == "slow_down") {
|
||||||
|
// rfc8628#section-3.5
|
||||||
|
// "A variant of 'authorization_pending', the authorization request is
|
||||||
|
// still pending and polling should continue, but the interval MUST
|
||||||
|
// be increased by 5 seconds for this and all subsequent requests."
|
||||||
|
interval += 5;
|
||||||
|
startPoolTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rsp.error == "authorization_pending") {
|
||||||
|
// keep trying - rfc8628#section-3.5
|
||||||
|
// "The authorization request is still pending as the end user hasn't
|
||||||
|
// yet completed the user-interaction steps (Section 3.3)."
|
||||||
|
startPoolTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
|
||||||
|
qWarning() << "Device Access failed:" << rsp.error;
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD,
|
||||||
|
tr("Device Access failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_task->wasSuccessful() || m_task->error() != QNetworkReply::NoError) {
|
||||||
|
startPoolTimer(); // it failed so just try again without increasing the interval
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_expiration_timer.stop();
|
||||||
|
m_data->msaClientID = m_clientId;
|
||||||
|
m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
|
m_data->msaToken.notAfter = QDateTime::currentDateTime().addSecs(rsp.expires_in);
|
||||||
|
m_data->msaToken.extra = rsp.extra;
|
||||||
|
m_data->msaToken.refresh_token = rsp.refresh_token;
|
||||||
|
m_data->msaToken.token = rsp.access_token;
|
||||||
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got"));
|
||||||
|
}
|
76
launcher/minecraft/auth/steps/MSADeviceCodeStep.h
Normal file
76
launcher/minecraft/auth/steps/MSADeviceCodeStep.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
|
class MSADeviceCodeStep : public AuthStep {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit MSADeviceCodeStep(AccountData* data);
|
||||||
|
virtual ~MSADeviceCodeStep() noexcept = default;
|
||||||
|
|
||||||
|
void perform() override;
|
||||||
|
|
||||||
|
QString describe() override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void abort();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void authorizeWithBrowser(QString url, QString code, int expiresIn);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void deviceAutorizationFinished();
|
||||||
|
void startPoolTimer();
|
||||||
|
void authenticateUser();
|
||||||
|
void authenticationFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_clientId;
|
||||||
|
QString m_device_code;
|
||||||
|
bool m_is_aborted = false;
|
||||||
|
int interval = 5;
|
||||||
|
|
||||||
|
QTimer m_pool_timer;
|
||||||
|
QTimer m_expiration_timer;
|
||||||
|
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_task;
|
||||||
|
};
|
@ -35,123 +35,74 @@
|
|||||||
|
|
||||||
#include "MSAStep.h"
|
#include "MSAStep.h"
|
||||||
|
|
||||||
|
#include <QtNetworkAuth/qoauthhttpserverreplyhandler.h>
|
||||||
|
#include <QAbstractOAuth2>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
|
||||||
|
|
||||||
using OAuth2 = Katabasis::DeviceFlow;
|
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
|
||||||
using Activity = Katabasis::Activity;
|
|
||||||
|
|
||||||
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action)
|
|
||||||
{
|
{
|
||||||
m_clientId = APPLICATION->getMSAClientID();
|
m_clientId = APPLICATION->getMSAClientID();
|
||||||
OAuth2::Options opts;
|
|
||||||
opts.scope = "XboxLive.signin offline_access";
|
|
||||||
opts.clientIdentifier = m_clientId;
|
|
||||||
opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
|
|
||||||
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
|
|
||||||
|
|
||||||
// FIXME: OAuth2 is not aware of our fancy shared pointers
|
auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
|
||||||
m_oauth2 = new OAuth2(opts, m_data->msaToken, this, APPLICATION->network().get());
|
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);
|
||||||
|
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");
|
||||||
|
oauth2.setClientIdentifier(m_clientId);
|
||||||
|
oauth2.setNetworkAccessManager(APPLICATION->network().get());
|
||||||
|
|
||||||
connect(m_oauth2, &OAuth2::activityChanged, this, &MSAStep::onOAuthActivityChanged);
|
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] {
|
||||||
connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &MSAStep::showVerificationUriAndCode);
|
m_data->msaClientID = oauth2.clientIdentifier();
|
||||||
|
m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
|
m_data->msaToken.notAfter = oauth2.expirationAt();
|
||||||
|
m_data->msaToken.extra = oauth2.extraTokens();
|
||||||
|
m_data->msaToken.refresh_token = oauth2.refreshToken();
|
||||||
|
m_data->msaToken.token = oauth2.token();
|
||||||
|
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::extraTokensChanged, this,
|
||||||
|
[this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; });
|
||||||
|
|
||||||
|
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this,
|
||||||
|
[this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; });
|
||||||
}
|
}
|
||||||
|
|
||||||
MSAStep::~MSAStep() noexcept = default;
|
|
||||||
|
|
||||||
QString MSAStep::describe()
|
QString MSAStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Logging in with Microsoft account.");
|
return tr("Logging in with Microsoft account.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSAStep::rehydrate()
|
|
||||||
{
|
|
||||||
switch (m_action) {
|
|
||||||
case Refresh: {
|
|
||||||
// TODO: check the tokens and see if they are old (older than a day)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Login: {
|
|
||||||
// NOOP
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSAStep::perform()
|
void MSAStep::perform()
|
||||||
{
|
{
|
||||||
switch (m_action) {
|
if (m_silent) {
|
||||||
case Refresh: {
|
if (m_data->msaClientID != m_clientId) {
|
||||||
if (m_data->msaClientID != m_clientId) {
|
emit finished(AccountTaskState::STATE_DISABLED,
|
||||||
emit hideVerificationUriAndCode();
|
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;
|
|
||||||
}
|
}
|
||||||
case Login: {
|
oauth2.setRefreshToken(m_data->msaToken.refresh_token);
|
||||||
QVariantMap extraOpts;
|
oauth2.refreshAccessToken();
|
||||||
extraOpts["prompt"] = "select_account";
|
} else {
|
||||||
m_oauth2->setExtraRequestParams(extraOpts);
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0
|
||||||
|
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) {
|
||||||
|
#else
|
||||||
|
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) {
|
||||||
|
#endif
|
||||||
|
map->insert("prompt", "select_account");
|
||||||
|
});
|
||||||
|
|
||||||
*m_data = AccountData();
|
*m_data = AccountData();
|
||||||
m_data->msaClientID = m_clientId;
|
m_data->msaClientID = m_clientId;
|
||||||
m_oauth2->login();
|
oauth2.grant();
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity)
|
|
||||||
{
|
|
||||||
switch (activity) {
|
|
||||||
case Katabasis::Activity::Idle:
|
|
||||||
case Katabasis::Activity::LoggingIn:
|
|
||||||
case Katabasis::Activity::Refreshing:
|
|
||||||
case Katabasis::Activity::LoggingOut: {
|
|
||||||
// We asked it to do something, it's doing it. Nothing to act upon.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Katabasis::Activity::Succeeded: {
|
|
||||||
// Succeeded or did not invalidate tokens
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
QVariantMap extraTokens = m_oauth2->extraTokens();
|
|
||||||
if (!extraTokens.isEmpty()) {
|
|
||||||
qCDebug(authCredentials()) << "Extra tokens in response:";
|
|
||||||
foreach (QString key, extraTokens.keys()) {
|
|
||||||
qCDebug(authCredentials()) << "\t" << key << ":" << extraTokens.value(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Katabasis::Activity::FailedSoft: {
|
|
||||||
// NOTE: soft error in the first step means 'offline'
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Microsoft user authentication ended with a network error."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Katabasis::Activity::FailedGone: {
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_GONE, tr("Microsoft user authentication failed - user no longer exists."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Katabasis::Activity::FailedHard: {
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication completed with an unrecognized result."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,30 +36,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
|
||||||
#include <katabasis/DeviceFlow.h>
|
#include <QtNetworkAuth/qoauth2authorizationcodeflow.h>
|
||||||
|
|
||||||
class MSAStep : public AuthStep {
|
class MSAStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum Action { Refresh, Login };
|
explicit MSAStep(AccountData* data, bool silent = false);
|
||||||
|
virtual ~MSAStep() noexcept = default;
|
||||||
public:
|
|
||||||
explicit MSAStep(AccountData* data, Action action);
|
|
||||||
virtual ~MSAStep() noexcept;
|
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
signals:
|
||||||
void onOAuthActivityChanged(Katabasis::Activity activity);
|
void authorizeWithBrowser(const QUrl& url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Katabasis::DeviceFlow* m_oauth2 = nullptr;
|
bool m_silent;
|
||||||
Action m_action;
|
|
||||||
QString m_clientId;
|
QString m_clientId;
|
||||||
|
QOAuth2AuthorizationCodeFlow oauth2;
|
||||||
};
|
};
|
||||||
|
@ -2,15 +2,13 @@
|
|||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "Logging.h"
|
#include "Application.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.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.");
|
return tr("Fetching the Minecraft profile.");
|
||||||
@ -18,52 +16,47 @@ QString MinecraftProfileStep::describe()
|
|||||||
|
|
||||||
void MinecraftProfileStep::perform()
|
void MinecraftProfileStep::perform()
|
||||||
{
|
{
|
||||||
auto url = QUrl("https://api.minecraftservices.com/minecraft/profile");
|
QUrl url("https://api.minecraftservices.com/minecraft/profile");
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
{ "Accept", "application/json" },
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
{ "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } };
|
||||||
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
m_response.reset(new QByteArray());
|
||||||
connect(requestor, &AuthRequest::finished, this, &MinecraftProfileStep::onRequestDone);
|
m_task = Net::Download::makeByteArray(url, m_response);
|
||||||
requestor->get(request);
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &MinecraftProfileStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftProfileStep::rehydrate()
|
void MinecraftProfileStep::onRequestDone()
|
||||||
{
|
{
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
if (m_task->error() == QNetworkReply::ContentNotFoundError) {
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
||||||
m_data->minecraftProfile = MinecraftProfile();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (error != QNetworkReply::NoError) {
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
qWarning() << "Error getting profile:";
|
qWarning() << "Error getting profile:";
|
||||||
qWarning() << " HTTP Status: " << requestor->httpStatus_;
|
qWarning() << " HTTP Status: " << m_task->replyStatusCode();
|
||||||
qWarning() << " Internal error no.: " << error;
|
qWarning() << " Internal error no.: " << m_task->error();
|
||||||
qWarning() << " Error string: " << requestor->errorString_;
|
qWarning() << " Error string: " << m_task->errorString();
|
||||||
|
|
||||||
qWarning() << " Response:";
|
qWarning() << " Response:";
|
||||||
qWarning() << QString::fromUtf8(data);
|
qWarning() << QString::fromUtf8(*m_response);
|
||||||
|
|
||||||
if (Net::isApplicationError(error)) {
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString()));
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString()));
|
||||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
|
if (!Parsers::parseMinecraftProfile(*m_response, m_data->minecraftProfile)) {
|
||||||
m_data->minecraftProfile = 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;
|
return;
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class MinecraftProfileStep : public AuthStep {
|
class MinecraftProfileStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MinecraftProfileStep(AccountData* data);
|
explicit MinecraftProfileStep(AccountData* data);
|
||||||
virtual ~MinecraftProfileStep() noexcept;
|
virtual ~MinecraftProfileStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
#include "OfflineStep.h"
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
|
|
||||||
OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}
|
|
||||||
OfflineStep::~OfflineStep() noexcept = default;
|
|
||||||
|
|
||||||
QString OfflineStep::describe()
|
|
||||||
{
|
|
||||||
return tr("Creating offline account.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void OfflineStep::rehydrate()
|
|
||||||
{
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
|
|
||||||
void OfflineStep::perform()
|
|
||||||
{
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account."));
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
|
||||||
|
|
||||||
#include <katabasis/DeviceFlow.h>
|
|
||||||
|
|
||||||
class OfflineStep : public AuthStep {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit OfflineStep(AccountData* data);
|
|
||||||
virtual ~OfflineStep() noexcept;
|
|
||||||
|
|
||||||
void perform() override;
|
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
|
||||||
};
|
|
@ -4,27 +4,22 @@
|
|||||||
#include <QJsonParseError>
|
#include <QJsonParseError>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind)
|
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind)
|
||||||
: AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(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);
|
return tr("Getting authorization to access %1 services.").arg(m_authorizationKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxAuthorizationStep::rehydrate()
|
|
||||||
{
|
|
||||||
// FIXME: check if the tokens are good?
|
|
||||||
}
|
|
||||||
|
|
||||||
void XboxAuthorizationStep::perform()
|
void XboxAuthorizationStep::perform()
|
||||||
{
|
{
|
||||||
QString xbox_auth_template = R"XXX(
|
QString xbox_auth_template = R"XXX(
|
||||||
@ -41,40 +36,44 @@ void XboxAuthorizationStep::perform()
|
|||||||
)XXX";
|
)XXX";
|
||||||
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
|
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"));
|
QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize");
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
auto headers = QList<Net::HeaderPair>{
|
||||||
request.setRawHeader("Accept", "application/json");
|
{ "Content-Type", "application/json" },
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
{ "Accept", "application/json" },
|
||||||
connect(requestor, &AuthRequest::finished, this, &XboxAuthorizationStep::onRequestDone);
|
};
|
||||||
requestor->post(request, xbox_auth_data.toUtf8());
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &XboxAuthorizationStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
qDebug() << "Getting authorization token for " << m_relyingParty;
|
qDebug() << "Getting authorization token for " << m_relyingParty;
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
void XboxAuthorizationStep::onRequestDone()
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
qCDebug(authCredentials()) << *m_response;
|
||||||
requestor->deleteLater();
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "Reply error:" << m_task->error();
|
||||||
qCDebug(authCredentials()) << data;
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
if (error != QNetworkReply::NoError) {
|
if (!processSTSError()) {
|
||||||
qWarning() << "Reply error:" << error;
|
|
||||||
if (Net::isApplicationError(error)) {
|
|
||||||
if (!processSTSError(error, data, headers)) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error));
|
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_task->error()));
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
|
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_task->errorString()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||||
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
|
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_task->errorString()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Katabasis::Token temp;
|
Token temp;
|
||||||
if (!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) {
|
if (!Parsers::parseXTokenResponse(*m_response, temp, m_authorizationKind)) {
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));
|
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));
|
||||||
return;
|
return;
|
||||||
@ -91,11 +90,11 @@ void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QBy
|
|||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty));
|
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)
|
bool XboxAuthorizationStep::processSTSError()
|
||||||
{
|
{
|
||||||
if (error == QNetworkReply::AuthenticationRequiredError) {
|
if (m_task->error() == QNetworkReply::AuthenticationRequiredError) {
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
|
||||||
if (jsonError.error) {
|
if (jsonError.error) {
|
||||||
qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
|
qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
|
@ -1,29 +1,32 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
class XboxAuthorizationStep : public AuthStep {
|
class XboxAuthorizationStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind);
|
explicit XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind);
|
||||||
virtual ~XboxAuthorizationStep() noexcept;
|
virtual ~XboxAuthorizationStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
bool processSTSError();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Katabasis::Token* m_token;
|
Token* m_token;
|
||||||
QString m_relyingParty;
|
QString m_relyingParty;
|
||||||
QString m_authorizationKind;
|
QString m_authorizationKind;
|
||||||
|
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -3,28 +3,21 @@
|
|||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.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.");
|
return tr("Fetching Xbox profile.");
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
QUrl url("https://profile.xboxlive.com/users/me/profile/settings");
|
||||||
QUrlQuery q;
|
QUrlQuery q;
|
||||||
q.addQueryItem("settings",
|
q.addQueryItem("settings",
|
||||||
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
|
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
|
||||||
@ -33,36 +26,38 @@ void XboxProfileStep::perform()
|
|||||||
"PreferredColor,Location,Bio,Watermarks,"
|
"PreferredColor,Location,Bio,Watermarks,"
|
||||||
"RealName,RealNameOverride,IsQuarantined");
|
"RealName,RealNameOverride,IsQuarantined");
|
||||||
url.setQuery(q);
|
url.setQuery(q);
|
||||||
|
auto headers = QList<Net::HeaderPair>{
|
||||||
|
{ "Content-Type", "application/json" },
|
||||||
|
{ "Accept", "application/json" },
|
||||||
|
{ "x-xbl-contract-version", "3" },
|
||||||
|
{ "Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8() }
|
||||||
|
};
|
||||||
|
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
m_response.reset(new QByteArray());
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
m_task = Net::Download::makeByteArray(url, m_response);
|
||||||
request.setRawHeader("Accept", "application/json");
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
request.setRawHeader("x-xbl-contract-version", "3");
|
|
||||||
request.setRawHeader("Authorization",
|
connect(m_task.get(), &Task::finished, this, &XboxProfileStep::onRequestDone);
|
||||||
QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
m_task->setNetwork(APPLICATION->network());
|
||||||
connect(requestor, &AuthRequest::finished, this, &XboxProfileStep::onRequestDone);
|
m_task->start();
|
||||||
requestor->get(request);
|
|
||||||
qDebug() << "Getting Xbox profile...";
|
qDebug() << "Getting Xbox profile...";
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
void XboxProfileStep::onRequestDone()
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
requestor->deleteLater();
|
qWarning() << "Reply error:" << m_task->error();
|
||||||
|
qCDebug(authCredentials()) << *m_response;
|
||||||
if (error != QNetworkReply::NoError) {
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
qWarning() << "Reply error:" << error;
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString()));
|
||||||
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 {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
|
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(authCredentials()) << "XBox profile: " << data;
|
qCDebug(authCredentials()) << "XBox profile: " << *m_response;
|
||||||
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class XboxProfileStep : public AuthStep {
|
class XboxProfileStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit XboxProfileStep(AccountData* data);
|
explicit XboxProfileStep(AccountData* data);
|
||||||
virtual ~XboxProfileStep() noexcept;
|
virtual ~XboxProfileStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -2,24 +2,18 @@
|
|||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "Application.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.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.");
|
return tr("Logging in as an Xbox user.");
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
QString xbox_auth_template = R"XXX(
|
||||||
@ -35,36 +29,39 @@ void XboxUserStep::perform()
|
|||||||
)XXX";
|
)XXX";
|
||||||
auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token);
|
auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token);
|
||||||
|
|
||||||
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
QUrl url("https://user.auth.xboxlive.com/user/authenticate");
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
auto headers = QList<Net::HeaderPair>{
|
||||||
request.setRawHeader("Accept", "application/json");
|
{ "Content-Type", "application/json" },
|
||||||
// set contract-version header (prevent err 400 bad-request?)
|
{ "Accept", "application/json" },
|
||||||
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
// set contract-version header (prevent err 400 bad-request?)
|
||||||
request.setRawHeader("x-xbl-contract-version", "1");
|
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
||||||
|
{ "x-xbl-contract-version", "1" }
|
||||||
|
};
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
auto* requestor = new AuthRequest(this);
|
connect(m_task.get(), &Task::finished, this, &XboxUserStep::onRequestDone);
|
||||||
connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone);
|
|
||||||
requestor->post(request, xbox_auth_data.toUtf8());
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
qDebug() << "First layer of XBox auth ... commencing.";
|
qDebug() << "First layer of XBox auth ... commencing.";
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxUserStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
void XboxUserStep::onRequestDone()
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
requestor->deleteLater();
|
qWarning() << "Reply error:" << m_task->error();
|
||||||
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
if (error != QNetworkReply::NoError) {
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(m_task->errorString()));
|
||||||
qWarning() << "Reply error:" << error;
|
|
||||||
if (Net::isApplicationError(error)) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
|
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
|
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(m_task->errorString()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Katabasis::Token temp;
|
Token temp;
|
||||||
if (!Parsers::parseXTokenResponse(data, temp, "UToken")) {
|
if (!Parsers::parseXTokenResponse(*m_response, temp, "UToken")) {
|
||||||
qWarning() << "Could not parse user authentication response...";
|
qWarning() << "Could not parse user authentication response...";
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood."));
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood."));
|
||||||
return;
|
return;
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
class XboxUserStep : public AuthStep {
|
class XboxUserStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit XboxUserStep(AccountData* data);
|
explicit XboxUserStep(AccountData* data);
|
||||||
virtual ~XboxUserStep() noexcept;
|
virtual ~XboxUserStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -45,8 +45,8 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
|
|||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->replyStatusCode();
|
||||||
|
|
||||||
callbacks.on_fail(reason, network_error_code);
|
callbacks.on_fail(reason, network_error_code);
|
||||||
});
|
});
|
||||||
@ -104,8 +104,8 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
|
|||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->replyStatusCode();
|
||||||
|
|
||||||
callbacks.on_fail(reason, network_error_code);
|
callbacks.on_fail(reason, network_error_code);
|
||||||
});
|
});
|
||||||
@ -155,8 +155,8 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
|
|||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->replyStatusCode();
|
||||||
|
|
||||||
callbacks.on_fail(reason, network_error_code);
|
callbacks.on_fail(reason, network_error_code);
|
||||||
});
|
});
|
||||||
|
@ -261,7 +261,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
// FIXME: This really needs to be put into a ConcurrentTask of
|
// FIXME: This really needs to be put into a ConcurrentTask of
|
||||||
// MultipleOptionsTask's , once those exist :)
|
// MultipleOptionsTask's , once those exist :)
|
||||||
auto param = dl.toWeakRef();
|
auto param = dl.toWeakRef();
|
||||||
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
|
connect(dl.get(), &Task::failed, [this, &file, file_path, param] {
|
||||||
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
||||||
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||||
m_files_job->addNetAction(ndl);
|
m_files_job->addNetAction(ndl);
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
#include "ByteArraySink.h"
|
#include "ByteArraySink.h"
|
||||||
#include "ChecksumValidator.h"
|
#include "ChecksumValidator.h"
|
||||||
#include "MetaCacheSink.h"
|
#include "MetaCacheSink.h"
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
|
@ -19,9 +19,6 @@
|
|||||||
|
|
||||||
#include "net/ApiUpload.h"
|
#include "net/ApiUpload.h"
|
||||||
#include "ByteArraySink.h"
|
#include "ByteArraySink.h"
|
||||||
#include "ChecksumValidator.h"
|
|
||||||
#include "MetaCacheSink.h"
|
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
|
@ -74,10 +74,6 @@ class ByteArraySink : public Sink {
|
|||||||
|
|
||||||
auto abort() -> Task::State override
|
auto abort() -> Task::State override
|
||||||
{
|
{
|
||||||
if (m_output)
|
|
||||||
m_output->clear();
|
|
||||||
else
|
|
||||||
qWarning() << "ByteArraySink did not clear the buffer because it's not addressable";
|
|
||||||
failAllValidators();
|
failAllValidators();
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,6 @@
|
|||||||
#include "ChecksumValidator.h"
|
#include "ChecksumValidator.h"
|
||||||
#include "MetaCacheSink.h"
|
#include "MetaCacheSink.h"
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
|
||||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
#include "HeaderProxy.h"
|
|
||||||
|
|
||||||
class NetAction : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
protected:
|
|
||||||
explicit NetAction() : Task() {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
using Ptr = shared_qobject_ptr<NetAction>;
|
|
||||||
|
|
||||||
virtual ~NetAction() = default;
|
|
||||||
|
|
||||||
QUrl url() { return m_url; }
|
|
||||||
|
|
||||||
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
|
|
||||||
|
|
||||||
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
|
|
||||||
virtual void init() = 0;
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
|
|
||||||
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
|
|
||||||
virtual void downloadFinished() = 0;
|
|
||||||
virtual void downloadReadyRead() = 0;
|
|
||||||
|
|
||||||
virtual void sslErrors(const QList<QSslError>& errors)
|
|
||||||
{
|
|
||||||
int i = 1;
|
|
||||||
for (auto error : errors) {
|
|
||||||
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
|
|
||||||
auto cert = error.certificate();
|
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
|
|
||||||
{
|
|
||||||
m_network = network;
|
|
||||||
executeTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void executeTask() override {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
|
||||||
|
|
||||||
/// the network reply
|
|
||||||
unique_qobject_ptr<QNetworkReply> m_reply;
|
|
||||||
|
|
||||||
/// source URL
|
|
||||||
QUrl m_url;
|
|
||||||
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
|
|
||||||
};
|
|
@ -36,6 +36,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "NetJob.h"
|
#include "NetJob.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
#include "tasks/ConcurrentTask.h"
|
#include "tasks/ConcurrentTask.h"
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
@ -48,7 +49,7 @@ NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> netwo
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
auto NetJob::addNetAction(NetAction::Ptr action) -> bool
|
auto NetJob::addNetAction(Net::NetRequest::Ptr action) -> bool
|
||||||
{
|
{
|
||||||
action->setNetwork(m_network);
|
action->setNetwork(m_network);
|
||||||
|
|
||||||
@ -111,11 +112,11 @@ auto NetJob::abort() -> bool
|
|||||||
return fullyAborted;
|
return fullyAborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto NetJob::getFailedActions() -> QList<NetAction*>
|
auto NetJob::getFailedActions() -> QList<Net::NetRequest*>
|
||||||
{
|
{
|
||||||
QList<NetAction*> failed;
|
QList<Net::NetRequest*> failed;
|
||||||
for (auto index : m_failed) {
|
for (auto index : m_failed) {
|
||||||
failed.push_back(dynamic_cast<NetAction*>(index.get()));
|
failed.push_back(dynamic_cast<Net::NetRequest*>(index.get()));
|
||||||
}
|
}
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
@ -124,7 +125,7 @@ auto NetJob::getFailedFiles() -> QList<QString>
|
|||||||
{
|
{
|
||||||
QList<QString> failed;
|
QList<QString> failed;
|
||||||
for (auto index : m_failed) {
|
for (auto index : m_failed) {
|
||||||
failed.append(static_cast<NetAction*>(index.get())->url().toString());
|
failed.append(static_cast<Net::NetRequest*>(index.get())->url().toString());
|
||||||
}
|
}
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#include <QtNetwork>
|
#include <QtNetwork>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "NetAction.h"
|
#include "net/NetRequest.h"
|
||||||
#include "tasks/ConcurrentTask.h"
|
#include "tasks/ConcurrentTask.h"
|
||||||
|
|
||||||
// Those are included so that they are also included by anyone using NetJob
|
// Those are included so that they are also included by anyone using NetJob
|
||||||
@ -58,9 +58,9 @@ class NetJob : public ConcurrentTask {
|
|||||||
auto size() const -> int;
|
auto size() const -> int;
|
||||||
|
|
||||||
auto canAbort() const -> bool override;
|
auto canAbort() const -> bool override;
|
||||||
auto addNetAction(NetAction::Ptr action) -> bool;
|
auto addNetAction(Net::NetRequest::Ptr action) -> bool;
|
||||||
|
|
||||||
auto getFailedActions() -> QList<NetAction*>;
|
auto getFailedActions() -> QList<Net::NetRequest*>;
|
||||||
auto getFailedFiles() -> QList<QString>;
|
auto getFailedFiles() -> QList<QString>;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -37,10 +37,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "NetRequest.h"
|
#include "NetRequest.h"
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QUrl>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
@ -48,8 +49,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
#include "MMCTime.h"
|
#include "MMCTime.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
|
||||||
@ -105,7 +104,6 @@ void NetRequest::executeTask()
|
|||||||
for (auto& header_proxy : m_headerProxies) {
|
for (auto& header_proxy : m_headerProxies) {
|
||||||
header_proxy->writeHeaders(request);
|
header_proxy->writeHeaders(request);
|
||||||
}
|
}
|
||||||
// TODO remove duplication
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
request.setTransferTimeout();
|
request.setTransferTimeout();
|
||||||
@ -114,11 +112,12 @@ void NetRequest::executeTask()
|
|||||||
m_last_progress_time = m_clock.now();
|
m_last_progress_time = m_clock.now();
|
||||||
m_last_progress_bytes = 0;
|
m_last_progress_bytes = 0;
|
||||||
|
|
||||||
QNetworkReply* rep = getReply(request);
|
auto rep = getReply(request);
|
||||||
if (rep == nullptr) // it failed
|
if (rep == nullptr) // it failed
|
||||||
return;
|
return;
|
||||||
m_reply.reset(rep);
|
m_reply.reset(rep);
|
||||||
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress);
|
connect(rep, &QNetworkReply::uploadProgress, this, &NetRequest::onProgress);
|
||||||
|
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::onProgress);
|
||||||
connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
|
connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
|
||||||
#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(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
|
connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
|
||||||
@ -129,7 +128,7 @@ void NetRequest::executeTask()
|
|||||||
connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
|
connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
void NetRequest::onProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
auto now = m_clock.now();
|
auto now = m_clock.now();
|
||||||
auto elapsed = now - m_last_progress_time;
|
auto elapsed = now - m_last_progress_time;
|
||||||
@ -172,7 +171,9 @@ void NetRequest::downloadError(QNetworkReply::NetworkError error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// error happened during download.
|
// error happened during download.
|
||||||
qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
|
qCCritical(logCat) << getUid().toString() << "Failed" << m_url.toString() << "with reason" << error;
|
||||||
|
if (m_reply)
|
||||||
|
qCCritical(logCat) << getUid().toString() << "HTTP Status" << replyStatusCode() << ";error" << errorString();
|
||||||
m_state = State::Failed;
|
m_state = State::Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,7 +238,7 @@ auto NetRequest::handleRedirect() -> bool
|
|||||||
|
|
||||||
m_url = QUrl(redirect.toString());
|
m_url = QUrl(redirect.toString());
|
||||||
qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
|
||||||
startAction(m_network);
|
executeTask();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -255,21 +256,18 @@ void NetRequest::downloadFinished()
|
|||||||
{
|
{
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
} else if (m_state == State::Failed) {
|
} else if (m_state == State::Failed) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
emit failed(m_reply->errorString());
|
||||||
emit failed("");
|
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
} else if (m_state == State::AbortedByUser) {
|
} else if (m_state == State::AbortedByUser) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
|
||||||
emit aborted();
|
emit aborted();
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
@ -283,7 +281,7 @@ void NetRequest::downloadFinished()
|
|||||||
if (m_state != State::Succeeded) {
|
if (m_state != State::Succeeded) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
emit failed("");
|
emit failed("failed to write in sink");
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -294,13 +292,11 @@ void NetRequest::downloadFinished()
|
|||||||
if (m_state != State::Succeeded) {
|
if (m_state != State::Succeeded) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
emit failed("failed to finalize the request");
|
||||||
emit failed("");
|
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_reply.reset();
|
|
||||||
qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString();
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
emit finished();
|
emit finished();
|
||||||
@ -334,4 +330,23 @@ auto NetRequest::abort() -> bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int NetRequest::replyStatusCode() const
|
||||||
|
{
|
||||||
|
return m_reply ? m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply::NetworkError NetRequest::error() const
|
||||||
|
{
|
||||||
|
return m_reply ? m_reply->error() : QNetworkReply::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl NetRequest::url() const
|
||||||
|
{
|
||||||
|
return m_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetRequest::errorString() const
|
||||||
|
{
|
||||||
|
return m_reply ? m_reply->errorString() : "";
|
||||||
|
}
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
@ -39,20 +39,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QUrl>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include "NetAction.h"
|
#include "HeaderProxy.h"
|
||||||
#include "Sink.h"
|
#include "Sink.h"
|
||||||
#include "Validator.h"
|
#include "Validator.h"
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "net/Logging.h"
|
#include "net/Logging.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class NetRequest : public NetAction {
|
class NetRequest : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
explicit NetRequest() : NetAction() {}
|
explicit NetRequest() : Task() {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Ptr = shared_qobject_ptr<class NetRequest>;
|
using Ptr = shared_qobject_ptr<class NetRequest>;
|
||||||
@ -61,26 +64,30 @@ class NetRequest : public NetAction {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
~NetRequest() override = default;
|
~NetRequest() override = default;
|
||||||
|
|
||||||
void init() override {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void addValidator(Validator* v);
|
void addValidator(Validator* v);
|
||||||
auto abort() -> bool override;
|
auto abort() -> bool override;
|
||||||
auto canAbort() const -> bool override { return true; }
|
auto canAbort() const -> bool override { return true; }
|
||||||
|
|
||||||
|
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
|
||||||
|
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
|
||||||
|
|
||||||
|
virtual void init() {}
|
||||||
|
|
||||||
|
QUrl url() const;
|
||||||
|
int replyStatusCode() const;
|
||||||
|
QNetworkReply::NetworkError error() const;
|
||||||
|
QString errorString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto handleRedirect() -> bool;
|
auto handleRedirect() -> bool;
|
||||||
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
|
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
void onProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
void downloadError(QNetworkReply::NetworkError error) override;
|
void downloadError(QNetworkReply::NetworkError error);
|
||||||
void sslErrors(const QList<QSslError>& errors) override;
|
void sslErrors(const QList<QSslError>& errors);
|
||||||
void downloadFinished() override;
|
void downloadFinished();
|
||||||
void downloadReadyRead() override;
|
void downloadReadyRead();
|
||||||
|
|
||||||
public slots:
|
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -93,6 +100,15 @@ class NetRequest : public NetAction {
|
|||||||
std::chrono::steady_clock m_clock;
|
std::chrono::steady_clock m_clock;
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
|
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
|
||||||
qint64 m_last_progress_bytes;
|
qint64 m_last_progress_bytes;
|
||||||
|
|
||||||
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
|
|
||||||
|
/// the network reply
|
||||||
|
unique_qobject_ptr<QNetworkReply> m_reply;
|
||||||
|
|
||||||
|
/// source URL
|
||||||
|
QUrl m_url;
|
||||||
|
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
|
||||||
};
|
};
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
||||||
|
@ -35,9 +35,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
#include "Validator.h"
|
#include "Validator.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class Sink {
|
class Sink {
|
||||||
|
@ -46,7 +46,8 @@ namespace Net {
|
|||||||
|
|
||||||
QNetworkReply* Upload::getReply(QNetworkRequest& request)
|
QNetworkReply* Upload::getReply(QNetworkRequest& request)
|
||||||
{
|
{
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
if (!request.hasRawHeader("Content-Type"))
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
return m_network->post(request, m_post_data);
|
return m_network->post(request, m_post_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
#include <QNetworkReply>
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class Validator {
|
class Validator {
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
qt.*.debug=false
|
qt.*.debug=false
|
||||||
# don't log credentials by default
|
# don't log credentials by default
|
||||||
launcher.auth.credentials.debug=false
|
launcher.auth.credentials.debug=false
|
||||||
katabasis.*.debug=false
|
|
||||||
# remove the debug lines, other log levels still get through
|
# remove the debug lines, other log levels still get through
|
||||||
launcher.task.net.download.debug=false
|
launcher.task.net.download.debug=false
|
||||||
# enable or disable whole catageries
|
# enable or disable whole catageries
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
#include "ui_MSALoginDialog.h"
|
#include "ui_MSALoginDialog.h"
|
||||||
|
|
||||||
#include "DesktopServices.h"
|
#include "DesktopServices.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
@ -47,30 +47,29 @@
|
|||||||
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
|
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->progressBar->setVisible(false);
|
|
||||||
ui->actionButton->setVisible(false);
|
|
||||||
// ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
|
||||||
|
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
ui->cancel->setEnabled(false);
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
ui->link->setVisible(false);
|
||||||
|
ui->copy->setVisible(false);
|
||||||
|
ui->progressBar->setVisible(false);
|
||||||
|
|
||||||
|
connect(ui->cancel, &QPushButton::pressed, this, &QDialog::reject);
|
||||||
|
connect(ui->copy, &QPushButton::pressed, this, &MSALoginDialog::copyUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MSALoginDialog::exec()
|
int MSALoginDialog::exec()
|
||||||
{
|
{
|
||||||
setUserInputsEnabled(false);
|
|
||||||
ui->progressBar->setVisible(true);
|
|
||||||
|
|
||||||
// Setup the login task and start it
|
// Setup the login task and start it
|
||||||
m_account = MinecraftAccount::createBlankMSA();
|
m_account = MinecraftAccount::createBlankMSA();
|
||||||
m_loginTask = m_account->loginMSA();
|
m_task = m_account->login(m_using_device_code);
|
||||||
connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
connect(m_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
||||||
connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
|
connect(m_task.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
|
||||||
connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
connect(m_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
||||||
connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress);
|
connect(m_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
|
||||||
connect(m_loginTask.get(), &AccountTask::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode);
|
connect(m_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra);
|
||||||
connect(m_loginTask.get(), &AccountTask::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode);
|
connect(ui->cancel, &QPushButton::pressed, m_task.get(), &Task::abort);
|
||||||
connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
|
connect(&m_external_timer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
|
||||||
m_loginTask->start();
|
m_task->start();
|
||||||
|
|
||||||
return QDialog::exec();
|
return QDialog::exec();
|
||||||
}
|
}
|
||||||
@ -80,60 +79,6 @@ MSALoginDialog::~MSALoginDialog()
|
|||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSALoginDialog::externalLoginTick()
|
|
||||||
{
|
|
||||||
m_externalLoginElapsed++;
|
|
||||||
ui->progressBar->setValue(m_externalLoginElapsed);
|
|
||||||
ui->progressBar->repaint();
|
|
||||||
|
|
||||||
if (m_externalLoginElapsed >= m_externalLoginTimeout) {
|
|
||||||
m_externalLoginTimer.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn)
|
|
||||||
{
|
|
||||||
m_externalLoginElapsed = 0;
|
|
||||||
m_externalLoginTimeout = expiresIn;
|
|
||||||
|
|
||||||
m_externalLoginTimer.setInterval(1000);
|
|
||||||
m_externalLoginTimer.setSingleShot(false);
|
|
||||||
m_externalLoginTimer.start();
|
|
||||||
|
|
||||||
ui->progressBar->setMaximum(expiresIn);
|
|
||||||
ui->progressBar->setValue(m_externalLoginElapsed);
|
|
||||||
|
|
||||||
QString urlString = uri.toString();
|
|
||||||
QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, urlString);
|
|
||||||
if (urlString == "https://www.microsoft.com/link" && !code.isEmpty()) {
|
|
||||||
urlString += QString("?otc=%1").arg(code);
|
|
||||||
DesktopServices::openUrl(urlString);
|
|
||||||
ui->label->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
|
|
||||||
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
|
|
||||||
.arg(linkString, code));
|
|
||||||
} else {
|
|
||||||
ui->label->setText(
|
|
||||||
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
|
|
||||||
}
|
|
||||||
ui->actionButton->setVisible(true);
|
|
||||||
connect(ui->actionButton, &QPushButton::clicked, [=]() {
|
|
||||||
DesktopServices::openUrl(uri);
|
|
||||||
QClipboard* cb = QApplication::clipboard();
|
|
||||||
cb->setText(code);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSALoginDialog::hideVerificationUriAndCode()
|
|
||||||
{
|
|
||||||
m_externalLoginTimer.stop();
|
|
||||||
ui->actionButton->setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSALoginDialog::setUserInputsEnabled(bool enable)
|
|
||||||
{
|
|
||||||
ui->buttonBox->setEnabled(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSALoginDialog::onTaskFailed(const QString& reason)
|
void MSALoginDialog::onTaskFailed(const QString& reason)
|
||||||
{
|
{
|
||||||
// Set message
|
// Set message
|
||||||
@ -146,12 +91,7 @@ void MSALoginDialog::onTaskFailed(const QString& reason)
|
|||||||
processed += "<br />";
|
processed += "<br />";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui->label->setText(processed);
|
ui->message->setText(processed);
|
||||||
|
|
||||||
// Re-enable user-interaction
|
|
||||||
setUserInputsEnabled(true);
|
|
||||||
ui->progressBar->setVisible(false);
|
|
||||||
ui->actionButton->setVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSALoginDialog::onTaskSucceeded()
|
void MSALoginDialog::onTaskSucceeded()
|
||||||
@ -161,22 +101,81 @@ void MSALoginDialog::onTaskSucceeded()
|
|||||||
|
|
||||||
void MSALoginDialog::onTaskStatus(const QString& status)
|
void MSALoginDialog::onTaskStatus(const QString& status)
|
||||||
{
|
{
|
||||||
ui->label->setText(status);
|
ui->message->setText(status);
|
||||||
}
|
ui->cancel->setEnabled(false);
|
||||||
|
ui->link->setVisible(false);
|
||||||
void MSALoginDialog::onTaskProgress(qint64 current, qint64 total)
|
ui->copy->setVisible(false);
|
||||||
{
|
ui->progressBar->setVisible(false);
|
||||||
ui->progressBar->setMaximum(total);
|
|
||||||
ui->progressBar->setValue(current);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public interface
|
// Public interface
|
||||||
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg)
|
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg, bool usingDeviceCode)
|
||||||
{
|
{
|
||||||
MSALoginDialog dlg(parent);
|
MSALoginDialog dlg(parent);
|
||||||
dlg.ui->label->setText(msg);
|
dlg.m_using_device_code = usingDeviceCode;
|
||||||
|
dlg.ui->message->setText(msg);
|
||||||
if (dlg.exec() == QDialog::Accepted) {
|
if (dlg.exec() == QDialog::Accepted) {
|
||||||
return dlg.m_account;
|
return dlg.m_account;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::authorizeWithBrowser(const QUrl& url)
|
||||||
|
{
|
||||||
|
ui->cancel->setEnabled(true);
|
||||||
|
ui->link->setVisible(true);
|
||||||
|
ui->copy->setVisible(true);
|
||||||
|
DesktopServices::openUrl(url);
|
||||||
|
ui->link->setText(url.toDisplayString());
|
||||||
|
ui->message->setText(
|
||||||
|
tr("Browser opened to complete the login process."
|
||||||
|
"<br /><br />"
|
||||||
|
"If your browser hasn't opened, please manually open the below link in your browser:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::copyUrl()
|
||||||
|
{
|
||||||
|
QClipboard* cb = QApplication::clipboard();
|
||||||
|
cb->setText(ui->link->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn)
|
||||||
|
{
|
||||||
|
m_external_elapsed = 0;
|
||||||
|
m_external_timeout = expiresIn;
|
||||||
|
|
||||||
|
m_external_timer.setInterval(1000);
|
||||||
|
m_external_timer.setSingleShot(false);
|
||||||
|
m_external_timer.start();
|
||||||
|
|
||||||
|
ui->progressBar->setMaximum(expiresIn);
|
||||||
|
ui->progressBar->setValue(m_external_elapsed);
|
||||||
|
|
||||||
|
QString linkString = QString("<a href=\"%1\">%2</a>").arg(url, url);
|
||||||
|
if (url == "https://www.microsoft.com/link" && !code.isEmpty()) {
|
||||||
|
url += QString("?otc=%1").arg(code);
|
||||||
|
ui->message->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
|
||||||
|
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
|
||||||
|
.arg(linkString, code));
|
||||||
|
} else {
|
||||||
|
ui->message->setText(
|
||||||
|
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
|
||||||
|
}
|
||||||
|
ui->cancel->setEnabled(true);
|
||||||
|
ui->link->setVisible(true);
|
||||||
|
ui->copy->setVisible(true);
|
||||||
|
ui->progressBar->setVisible(true);
|
||||||
|
DesktopServices::openUrl(url);
|
||||||
|
ui->link->setText(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::externalLoginTick()
|
||||||
|
{
|
||||||
|
m_external_elapsed++;
|
||||||
|
ui->progressBar->setValue(m_external_elapsed);
|
||||||
|
ui->progressBar->repaint();
|
||||||
|
|
||||||
|
if (m_external_elapsed >= m_external_timeout) {
|
||||||
|
m_external_timer.stop();
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@
|
|||||||
#include <QtCore/QEventLoop>
|
#include <QtCore/QEventLoop>
|
||||||
#include <QtWidgets/QDialog>
|
#include <QtWidgets/QDialog>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
#include "minecraft/auth/MinecraftAccount.h"
|
#include "minecraft/auth/MinecraftAccount.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
@ -31,29 +32,29 @@ class MSALoginDialog : public QDialog {
|
|||||||
public:
|
public:
|
||||||
~MSALoginDialog();
|
~MSALoginDialog();
|
||||||
|
|
||||||
static MinecraftAccountPtr newAccount(QWidget* parent, QString message);
|
static MinecraftAccountPtr newAccount(QWidget* parent, QString message, bool usingDeviceCode = false);
|
||||||
int exec() override;
|
int exec() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit MSALoginDialog(QWidget* parent = 0);
|
explicit MSALoginDialog(QWidget* parent = 0);
|
||||||
|
|
||||||
void setUserInputsEnabled(bool enable);
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onTaskFailed(const QString& reason);
|
void onTaskFailed(const QString& reason);
|
||||||
void onTaskSucceeded();
|
void onTaskSucceeded();
|
||||||
void onTaskStatus(const QString& status);
|
void onTaskStatus(const QString& status);
|
||||||
void onTaskProgress(qint64 current, qint64 total);
|
void authorizeWithBrowser(const QUrl& url);
|
||||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);
|
||||||
void hideVerificationUriAndCode();
|
void copyUrl();
|
||||||
|
|
||||||
void externalLoginTick();
|
void externalLoginTick();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MSALoginDialog* ui;
|
Ui::MSALoginDialog* ui;
|
||||||
MinecraftAccountPtr m_account;
|
MinecraftAccountPtr m_account;
|
||||||
shared_qobject_ptr<AccountTask> m_loginTask;
|
shared_qobject_ptr<AuthFlow> m_task;
|
||||||
QTimer m_externalLoginTimer;
|
|
||||||
int m_externalLoginElapsed = 0;
|
int m_external_elapsed;
|
||||||
int m_externalLoginTimeout = 0;
|
int m_external_timeout;
|
||||||
|
QTimer m_external_timer;
|
||||||
|
|
||||||
|
bool m_using_device_code = false;
|
||||||
};
|
};
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>491</width>
|
<width>491</width>
|
||||||
<height>143</height>
|
<height>208</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
@ -21,15 +21,28 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="message">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>500</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">Message label placeholder.
|
<string notr="true"/>
|
||||||
|
|
||||||
aaaaa</string>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="textFormat">
|
<property name="textFormat">
|
||||||
<enum>Qt::RichText</enum>
|
<enum>Qt::RichText</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="openExternalLinks">
|
<property name="openExternalLinks">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -38,6 +51,28 @@ aaaaa</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="linkLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="link">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="copy">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="copy">
|
||||||
|
<normaloff>.</normaloff>.</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QProgressBar" name="progressBar">
|
<widget class="QProgressBar" name="progressBar">
|
||||||
<property name="value">
|
<property name="value">
|
||||||
@ -49,25 +84,11 @@ aaaaa</string>
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<widget class="QPushButton" name="cancel">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QPushButton" name="actionButton">
|
<string>Cancel</string>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Open page and copy code</string>
|
</widget>
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Cancel</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#include "OfflineLoginDialog.h"
|
#include "OfflineLoginDialog.h"
|
||||||
#include "ui_OfflineLoginDialog.h"
|
#include "ui_OfflineLoginDialog.h"
|
||||||
|
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
|
|
||||||
#include <QtWidgets/QPushButton>
|
#include <QtWidgets/QPushButton>
|
||||||
|
|
||||||
OfflineLoginDialog::OfflineLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog)
|
OfflineLoginDialog::OfflineLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog)
|
||||||
@ -28,7 +26,7 @@ void OfflineLoginDialog::accept()
|
|||||||
|
|
||||||
// Setup the login task and start it
|
// Setup the login task and start it
|
||||||
m_account = MinecraftAccount::createOffline(ui->userTextBox->text());
|
m_account = MinecraftAccount::createOffline(ui->userTextBox->text());
|
||||||
m_loginTask = m_account->loginOffline();
|
m_loginTask = m_account->login();
|
||||||
connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed);
|
connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed);
|
||||||
connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded);
|
connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded);
|
||||||
connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus);
|
connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus);
|
||||||
|
@ -45,8 +45,9 @@
|
|||||||
#include "ui/dialogs/ProgressDialog.h"
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
|
|
||||||
#include <Application.h>
|
#include <Application.h>
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent)
|
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent)
|
||||||
: QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
|
: QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
|
||||||
@ -150,28 +151,27 @@ void ProfileSetupDialog::checkName(const QString& name)
|
|||||||
currentCheck = name;
|
currentCheck = name;
|
||||||
isChecking = true;
|
isChecking = true;
|
||||||
|
|
||||||
auto token = m_accountToSetup->accessToken();
|
QUrl url(QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name));
|
||||||
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||||
|
{ "Accept", "application/json" },
|
||||||
|
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
|
||||||
|
|
||||||
auto url = QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name);
|
m_check_response.reset(new QByteArray());
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
if (m_check_task)
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
disconnect(m_check_task.get(), nullptr, this, nullptr);
|
||||||
request.setRawHeader("Accept", "application/json");
|
m_check_task = Net::Download::makeByteArray(url, m_check_response);
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
|
m_check_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished);
|
||||||
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::checkFinished);
|
|
||||||
requestor->get(request);
|
m_check_task->setNetwork(APPLICATION->network());
|
||||||
|
m_check_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileSetupDialog::checkFinished(QNetworkReply::NetworkError error,
|
void ProfileSetupDialog::checkFinished()
|
||||||
QByteArray profileData,
|
|
||||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
if (m_check_task->error() == QNetworkReply::NoError) {
|
||||||
requestor->deleteLater();
|
auto doc = QJsonDocument::fromJson(*m_check_response);
|
||||||
|
|
||||||
if (error == QNetworkReply::NoError) {
|
|
||||||
auto doc = QJsonDocument::fromJson(profileData);
|
|
||||||
auto root = doc.object();
|
auto root = doc.object();
|
||||||
auto statusValue = root.value("status").toString("INVALID");
|
auto statusValue = root.value("status").toString("INVALID");
|
||||||
if (statusValue == "AVAILABLE") {
|
if (statusValue == "AVAILABLE") {
|
||||||
@ -195,20 +195,22 @@ void ProfileSetupDialog::setupProfile(const QString& profileName)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto token = m_accountToSetup->accessToken();
|
|
||||||
|
|
||||||
auto url = QString("https://api.minecraftservices.com/minecraft/profile");
|
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
request.setRawHeader("Accept", "application/json");
|
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
|
|
||||||
|
|
||||||
QString payloadTemplate("{\"profileName\":\"%1\"}");
|
QString payloadTemplate("{\"profileName\":\"%1\"}");
|
||||||
auto profileData = payloadTemplate.arg(profileName).toUtf8();
|
|
||||||
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
QUrl url("https://api.minecraftservices.com/minecraft/profile");
|
||||||
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished);
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||||
requestor->post(request, profileData);
|
{ "Accept", "application/json" },
|
||||||
|
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
|
||||||
|
|
||||||
|
m_profile_response.reset(new QByteArray());
|
||||||
|
m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8());
|
||||||
|
m_profile_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished);
|
||||||
|
|
||||||
|
m_profile_task->setNetwork(APPLICATION->network());
|
||||||
|
m_profile_task->start();
|
||||||
|
|
||||||
isWorking = true;
|
isWorking = true;
|
||||||
|
|
||||||
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
|
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
|
||||||
@ -244,22 +246,17 @@ struct MojangError {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void ProfileSetupDialog::setupProfileFinished(QNetworkReply::NetworkError error,
|
void ProfileSetupDialog::setupProfileFinished()
|
||||||
QByteArray errorData,
|
|
||||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
isWorking = false;
|
isWorking = false;
|
||||||
if (error == QNetworkReply::NoError) {
|
if (m_profile_task->error() == QNetworkReply::NoError) {
|
||||||
/*
|
/*
|
||||||
* data contains the profile in the response
|
* data contains the profile in the response
|
||||||
* ... we could parse it and update the account, but let's just return back to the normal login flow instead...
|
* ... we could parse it and update the account, but let's just return back to the normal login flow instead...
|
||||||
*/
|
*/
|
||||||
accept();
|
accept();
|
||||||
} else {
|
} else {
|
||||||
auto parsedError = MojangError::fromJSON(errorData);
|
auto parsedError = MojangError::fromJSON(*m_profile_response);
|
||||||
ui->errorLabel->setVisible(true);
|
ui->errorLabel->setVisible(true);
|
||||||
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
|
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
|
||||||
qDebug() << parsedError.rawError;
|
qDebug() << parsedError.rawError;
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#include <minecraft/auth/MinecraftAccount.h>
|
#include <minecraft/auth/MinecraftAccount.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include "net/Download.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ProfileSetupDialog;
|
class ProfileSetupDialog;
|
||||||
@ -40,10 +42,10 @@ class ProfileSetupDialog : public QDialog {
|
|||||||
void on_buttonBox_rejected();
|
void on_buttonBox_rejected();
|
||||||
|
|
||||||
void nameEdited(const QString& name);
|
void nameEdited(const QString& name);
|
||||||
void checkFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
|
||||||
void startCheck();
|
void startCheck();
|
||||||
|
|
||||||
void setupProfileFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
void checkFinished();
|
||||||
|
void setupProfileFinished();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void scheduleCheck(const QString& name);
|
void scheduleCheck(const QString& name);
|
||||||
@ -67,4 +69,10 @@ class ProfileSetupDialog : public QDialog {
|
|||||||
QString currentCheck;
|
QString currentCheck;
|
||||||
|
|
||||||
QTimer checkStartTimer;
|
QTimer checkStartTimer;
|
||||||
|
|
||||||
|
std::shared_ptr<QByteArray> m_check_response;
|
||||||
|
Net::Download::Ptr m_check_task;
|
||||||
|
|
||||||
|
std::shared_ptr<QByteArray> m_profile_response;
|
||||||
|
Net::Upload::Ptr m_profile_task;
|
||||||
};
|
};
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
|
|
||||||
#include <QItemSelectionModel>
|
#include <QItemSelectionModel>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
@ -134,8 +135,19 @@ void AccountListPage::listChanged()
|
|||||||
|
|
||||||
void AccountListPage::on_actionAddMicrosoft_triggered()
|
void AccountListPage::on_actionAddMicrosoft_triggered()
|
||||||
{
|
{
|
||||||
MinecraftAccountPtr account =
|
QMessageBox box(this);
|
||||||
MSALoginDialog::newAccount(this, tr("Please enter your Mojang account email and password to add your account."));
|
box.setWindowTitle(tr("Add account"));
|
||||||
|
box.setText(tr("How do you want to login?"));
|
||||||
|
box.setIcon(QMessageBox::Question);
|
||||||
|
auto deviceCode = box.addButton(tr("Legacy"), QMessageBox::ButtonRole::YesRole);
|
||||||
|
auto authCode = box.addButton(tr("Recommended"), QMessageBox::ButtonRole::NoRole);
|
||||||
|
auto cancel = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole);
|
||||||
|
box.setDefaultButton(authCode);
|
||||||
|
box.exec();
|
||||||
|
if ((box.clickedButton() != deviceCode && box.clickedButton() != authCode) || box.clickedButton() == cancel)
|
||||||
|
return;
|
||||||
|
MinecraftAccountPtr account = MSALoginDialog::newAccount(
|
||||||
|
this, tr("Please enter your Mojang account email and password to add your account."), box.clickedButton() == deviceCode);
|
||||||
|
|
||||||
if (account) {
|
if (account) {
|
||||||
m_accounts->addAccount(account);
|
m_accounts->addAccount(account);
|
||||||
|
@ -331,7 +331,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
|
|||||||
auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
|
auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
|
||||||
|
|
||||||
auto full_file_path = cache_entry->getFullPath();
|
auto full_file_path = cache_entry->getFullPath();
|
||||||
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {
|
connect(icon_fetch_action.get(), &Task::succeeded, this, [=] {
|
||||||
auto icon = QIcon(full_file_path);
|
auto icon = QIcon(full_file_path);
|
||||||
QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 })));
|
QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 })));
|
||||||
|
|
||||||
@ -339,7 +339,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
|
|||||||
|
|
||||||
emit dataChanged(index, index, { Qt::DecorationRole });
|
emit dataChanged(index, index, { Qt::DecorationRole });
|
||||||
});
|
});
|
||||||
connect(icon_fetch_action.get(), &NetAction::failed, this, [=] {
|
connect(icon_fetch_action.get(), &Task::failed, this, [=] {
|
||||||
m_currently_running_icon_actions.remove(url);
|
m_currently_running_icon_actions.remove(url);
|
||||||
m_failed_icon_actions.insert(url);
|
m_failed_icon_actions.insert(url);
|
||||||
});
|
});
|
||||||
|
@ -352,10 +352,10 @@ void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
|
|||||||
void ModpackListModel::searchRequestFailed(QString reason)
|
void ModpackListModel::searchRequestFailed(QString reason)
|
||||||
{
|
{
|
||||||
auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
|
auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
|
||||||
if (!failed_action->m_reply) {
|
if (failed_action->replyStatusCode() == -1) {
|
||||||
// Network error
|
// Network error
|
||||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
|
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
|
||||||
} else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) {
|
} else if (failed_action->replyStatusCode() == 409) {
|
||||||
// 409 Gone, notify user to update
|
// 409 Gone, notify user to update
|
||||||
QMessageBox::critical(nullptr, tr("Error"),
|
QMessageBox::critical(nullptr, tr("Error"),
|
||||||
//: %1 refers to the launcher itself
|
//: %1 refers to the launcher itself
|
||||||
|
@ -32,14 +32,6 @@ Simple Java tool that prints the JVM details - version and platform bitness.
|
|||||||
|
|
||||||
Do what you want with it. It is so trivial that noone cares.
|
Do what you want with it. It is so trivial that noone cares.
|
||||||
|
|
||||||
## Katabasis
|
|
||||||
|
|
||||||
Oauth2 library customized for Microsoft authentication.
|
|
||||||
|
|
||||||
This is a fork of the [O2 library](https://github.com/pipacs/o2).
|
|
||||||
|
|
||||||
MIT licensed.
|
|
||||||
|
|
||||||
## launcher
|
## launcher
|
||||||
|
|
||||||
Java launcher part for Minecraft.
|
Java launcher part for Minecraft.
|
||||||
|
2
libraries/katabasis/.gitignore
vendored
2
libraries/katabasis/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
build/
|
|
||||||
*.kdev4
|
|
@ -1,58 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.9.4)
|
|
||||||
|
|
||||||
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
|
|
||||||
if(IS_IN_SOURCE_BUILD)
|
|
||||||
message(FATAL_ERROR "You are building Katabasis in-source. Please separate the build tree from the source tree.")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
project(Katabasis)
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
|
||||||
set(CMAKE_C_STANDARD_REQUIRED true)
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
|
||||||
|
|
||||||
if(QT_VERSION_MAJOR EQUAL 5)
|
|
||||||
find_package(Qt5 COMPONENTS Core Network REQUIRED)
|
|
||||||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
|
||||||
find_package(Qt6 COMPONENTS Core Network REQUIRED)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set( katabasis_PRIVATE
|
|
||||||
src/DeviceFlow.cpp
|
|
||||||
src/JsonResponse.cpp
|
|
||||||
src/JsonResponse.h
|
|
||||||
src/PollServer.cpp
|
|
||||||
src/Reply.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set( katabasis_PUBLIC
|
|
||||||
include/katabasis/DeviceFlow.h
|
|
||||||
include/katabasis/Globals.h
|
|
||||||
include/katabasis/PollServer.h
|
|
||||||
include/katabasis/Reply.h
|
|
||||||
include/katabasis/RequestParameter.h
|
|
||||||
)
|
|
||||||
|
|
||||||
ecm_qt_declare_logging_category(katabasis_PRIVATE
|
|
||||||
HEADER KatabasisLogging.h # NOTE: this won't be in src/, but CMAKE_BINARY_DIR/src isn't included by default so this should be fine
|
|
||||||
IDENTIFIER katabasisCredentials
|
|
||||||
CATEGORY_NAME "katabasis.credentials"
|
|
||||||
DEFAULT_SEVERITY Warning
|
|
||||||
DESCRIPTION "Secrets and credentials from Katabasis"
|
|
||||||
EXPORT "Katabasis"
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library( Katabasis STATIC ${katabasis_PRIVATE} ${katabasis_PUBLIC} )
|
|
||||||
target_link_libraries(Katabasis Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network)
|
|
||||||
|
|
||||||
# needed for statically linked Katabasis in shared libs on x86_64
|
|
||||||
set_target_properties(Katabasis
|
|
||||||
PROPERTIES POSITION_INDEPENDENT_CODE TRUE
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(Katabasis PUBLIC include PRIVATE src include/katabasis)
|
|
@ -1,23 +0,0 @@
|
|||||||
Copyright (c) 2012, Akos Polster
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,36 +0,0 @@
|
|||||||
# Katabasis - MS-flavored OAuth for Qt, derived from the O2 library
|
|
||||||
|
|
||||||
This library's sole purpose is to make interacting with MSA and various MSA and XBox authenticated services less painful.
|
|
||||||
|
|
||||||
It may be possible to backport some of the changes to O2 in the future, but for the sake of going fast, all compatibility concerns have been ignored.
|
|
||||||
|
|
||||||
[You can find the original library's git repository here.](https://github.com/pipacs/o2)
|
|
||||||
|
|
||||||
Notes to contributors:
|
|
||||||
|
|
||||||
* Please follow the coding style of the existing source, where reasonable
|
|
||||||
* Code contributions are released under Simplified BSD License, as specified in LICENSE. Do not contribute if this license does not suit your code
|
|
||||||
* If you are interested in working on this, come to the Prism Launcher Discord server and talk first
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Clone the Github repository, integrate the it into your CMake build system.
|
|
||||||
|
|
||||||
The library is static only, dynamic linking and system-wide installation are out of scope and undesirable.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
At this stage, don't, unless you want to help with the library itself.
|
|
||||||
|
|
||||||
This is an experimental fork of the O2 library and is undergoing a big design/architecture shift in order to support different features:
|
|
||||||
|
|
||||||
* Multiple accounts
|
|
||||||
* Multi-stage authentication/authorization schemes
|
|
||||||
* Tighter control over token chains and their storage
|
|
||||||
* Talking to complex APIs and individually authorized microservices
|
|
||||||
* Token lifetime management, 'offline mode' and resilience in face of network failures
|
|
||||||
* Token and claims/entitlements validation
|
|
||||||
* Caching of some API results
|
|
||||||
* XBox magic
|
|
||||||
* Mojang magic
|
|
||||||
* Generally, magic that you would spend weeks on researching while getting confused by contradictory/incomplete documentation (if any is available)
|
|
@ -1,108 +0,0 @@
|
|||||||
## O2 library by Akos Polster and contributors
|
|
||||||
|
|
||||||
[The origin of this fork.](https://github.com/pipacs/o2)
|
|
||||||
|
|
||||||
> Copyright (c) 2012, Akos Polster
|
|
||||||
> All rights reserved.
|
|
||||||
>
|
|
||||||
> Redistribution and use in source and binary forms, with or without
|
|
||||||
> modification, are permitted provided that the following conditions are met:
|
|
||||||
>
|
|
||||||
> * Redistributions of source code must retain the above copyright notice, this
|
|
||||||
> list of conditions and the following disclaimer.
|
|
||||||
>
|
|
||||||
> * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
> this list of conditions and the following disclaimer in the documentation
|
|
||||||
> and/or other materials provided with the distribution.
|
|
||||||
>
|
|
||||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
## SimpleCrypt by Andre Somers
|
|
||||||
|
|
||||||
Cryptographic methods for Qt.
|
|
||||||
|
|
||||||
> Copyright (c) 2011, Andre Somers
|
|
||||||
> All rights reserved.
|
|
||||||
>
|
|
||||||
> Redistribution and use in source and binary forms, with or without
|
|
||||||
> modification, are permitted provided that the following conditions are met:
|
|
||||||
>
|
|
||||||
> * Redistributions of source code must retain the above copyright
|
|
||||||
> notice, this list of conditions and the following disclaimer.
|
|
||||||
> * Redistributions in binary form must reproduce the above copyright
|
|
||||||
> notice, this list of conditions and the following disclaimer in the
|
|
||||||
> documentation and/or other materials provided with the distribution.
|
|
||||||
> * Neither the name of the Rathenau Instituut, Andre Somers nor the
|
|
||||||
> names of its contributors may be used to endorse or promote products
|
|
||||||
> derived from this software without specific prior written permission.
|
|
||||||
>
|
|
||||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
> ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
> WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
> DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
|
|
||||||
> DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
> (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
> ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
> SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
## Mandeep Sandhu <mandeepsandhu.chd@gmail.com>
|
|
||||||
|
|
||||||
Configurable settings storage, Twitter XAuth specialization, new demos, cleanups.
|
|
||||||
|
|
||||||
> "Hi Akos,
|
|
||||||
>
|
|
||||||
> I'm writing this mail to confirm that my contributions to the O2 library, available here <https://github.com/pipacs/o2>, can be freely distributed according to the project's license (as shown in the LICENSE file).
|
|
||||||
>
|
|
||||||
> Regards,
|
|
||||||
> -mandeep"
|
|
||||||
|
|
||||||
## Sergey Gavrushkin <https://github.com/ncux>
|
|
||||||
|
|
||||||
FreshBooks specialization
|
|
||||||
|
|
||||||
## Theofilos Intzoglou <https://github.com/parapente>
|
|
||||||
|
|
||||||
Hubic specialization
|
|
||||||
|
|
||||||
## Dimitar
|
|
||||||
|
|
||||||
SurveyMonkey specialization
|
|
||||||
|
|
||||||
## David Brooks <https://github.com/dbrnz>
|
|
||||||
|
|
||||||
CMake related fixes and improvements.
|
|
||||||
|
|
||||||
## Lukas Vogel <https://github.com/lukedirtwalker>
|
|
||||||
|
|
||||||
Spotify support
|
|
||||||
|
|
||||||
## Alan Garny <https://github.com/agarny>
|
|
||||||
|
|
||||||
Windows DLL build support
|
|
||||||
|
|
||||||
## MartinMikita <https://github.com/MartinMikita>
|
|
||||||
|
|
||||||
Bug fixes
|
|
||||||
|
|
||||||
## Larry Shaffer <https://github.com/dakcarto>
|
|
||||||
|
|
||||||
Versioning, shared lib, install target and header support
|
|
||||||
|
|
||||||
## Gilmanov Ildar <https://github.com/gilmanov-ildar>
|
|
||||||
|
|
||||||
Bug fixes, support for ```qml``` module
|
|
||||||
|
|
||||||
## Fabian Vogt <https://github.com/Vogtinator>
|
|
||||||
|
|
||||||
Bug fixes, support for building without Qt keywords enabled
|
|
@ -1,33 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QMap>
|
|
||||||
#include <QString>
|
|
||||||
#include <QVariantMap>
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
enum class Activity {
|
|
||||||
Idle,
|
|
||||||
LoggingIn,
|
|
||||||
LoggingOut,
|
|
||||||
Refreshing,
|
|
||||||
FailedSoft, //!< soft failure. this generally means the user auth details haven't been invalidated
|
|
||||||
FailedHard, //!< hard failure. auth is invalid
|
|
||||||
FailedGone, //!< hard failure. auth is invalid, and the account no longer exists
|
|
||||||
Succeeded
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Validity { None, Assumed, Certain };
|
|
||||||
|
|
||||||
struct Token {
|
|
||||||
QDateTime issueInstant;
|
|
||||||
QDateTime notAfter;
|
|
||||||
QString token;
|
|
||||||
QString refresh_token;
|
|
||||||
QVariantMap extra;
|
|
||||||
|
|
||||||
Validity validity = Validity::None;
|
|
||||||
bool persistent = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,149 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QPair>
|
|
||||||
|
|
||||||
#include "Bits.h"
|
|
||||||
#include "Reply.h"
|
|
||||||
#include "RequestParameter.h"
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
class ReplyServer;
|
|
||||||
class PollServer;
|
|
||||||
|
|
||||||
/// Simple OAuth2 Device Flow authenticator.
|
|
||||||
class DeviceFlow : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
Q_ENUMS(GrantFlow)
|
|
||||||
|
|
||||||
public:
|
|
||||||
struct Options {
|
|
||||||
QString userAgent = QStringLiteral("Katabasis/1.0");
|
|
||||||
QString responseType = QStringLiteral("code");
|
|
||||||
QString scope;
|
|
||||||
QString clientIdentifier;
|
|
||||||
QString clientSecret;
|
|
||||||
QUrl authorizationUrl;
|
|
||||||
QUrl accessTokenUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Are we authenticated?
|
|
||||||
bool linked();
|
|
||||||
|
|
||||||
/// Authentication token.
|
|
||||||
QString token();
|
|
||||||
|
|
||||||
/// Provider-specific extra tokens, available after a successful authentication
|
|
||||||
QVariantMap extraTokens();
|
|
||||||
|
|
||||||
public:
|
|
||||||
// TODO: put in `Options`
|
|
||||||
/// User-defined extra parameters to append to request URL
|
|
||||||
QVariantMap extraRequestParams();
|
|
||||||
void setExtraRequestParams(const QVariantMap& value);
|
|
||||||
|
|
||||||
// TODO: split up the class into multiple, each implementing one OAuth2 flow
|
|
||||||
/// Grant type (if non-standard)
|
|
||||||
QString grantType();
|
|
||||||
void setGrantType(const QString& value);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Constructor.
|
|
||||||
/// @param parent Parent object.
|
|
||||||
explicit DeviceFlow(Options& opts, Token& token, QObject* parent = 0, QNetworkAccessManager* manager = 0);
|
|
||||||
|
|
||||||
/// Get refresh token.
|
|
||||||
QString refreshToken();
|
|
||||||
|
|
||||||
/// Get token expiration time
|
|
||||||
QDateTime expires();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
/// Authenticate.
|
|
||||||
void login();
|
|
||||||
|
|
||||||
/// De-authenticate.
|
|
||||||
void logout();
|
|
||||||
|
|
||||||
/// Refresh token.
|
|
||||||
bool refresh();
|
|
||||||
|
|
||||||
/// Handle situation where reply server has opted to close its connection
|
|
||||||
void serverHasClosed(bool paramsfound = false);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/// Emitted when client needs to open a web browser window, with the given URL.
|
|
||||||
void openBrowser(const QUrl& url);
|
|
||||||
|
|
||||||
/// Emitted when client can close the browser window.
|
|
||||||
void closeBrowser();
|
|
||||||
|
|
||||||
/// Emitted when client needs to show a verification uri and user code
|
|
||||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
|
||||||
|
|
||||||
/// Emitted when the internal state changes
|
|
||||||
void activityChanged(Activity activity);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
/// Handle verification response.
|
|
||||||
void onVerificationReceived(QMap<QString, QString>);
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
/// Handle completion of a Device Authorization Request
|
|
||||||
void onDeviceAuthReplyFinished();
|
|
||||||
|
|
||||||
/// Handle completion of a refresh request.
|
|
||||||
void onRefreshFinished();
|
|
||||||
|
|
||||||
/// Handle failure of a refresh request.
|
|
||||||
void onRefreshError(QNetworkReply::NetworkError error, QNetworkReply* reply);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/// Set refresh token.
|
|
||||||
void setRefreshToken(const QString& v);
|
|
||||||
|
|
||||||
/// Set token expiration time.
|
|
||||||
void setExpires(QDateTime v);
|
|
||||||
|
|
||||||
/// Start polling authorization server
|
|
||||||
void startPollServer(const QVariantMap& params, int expiresIn);
|
|
||||||
|
|
||||||
/// Set authentication token.
|
|
||||||
void setToken(const QString& v);
|
|
||||||
|
|
||||||
/// Set the linked state
|
|
||||||
void setLinked(bool v);
|
|
||||||
|
|
||||||
/// Set extra tokens found in OAuth response
|
|
||||||
void setExtraTokens(QVariantMap extraTokens);
|
|
||||||
|
|
||||||
/// Set local poll server
|
|
||||||
void setPollServer(PollServer* server);
|
|
||||||
|
|
||||||
PollServer* pollServer() const;
|
|
||||||
|
|
||||||
void updateActivity(Activity activity);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Options options_;
|
|
||||||
|
|
||||||
QVariantMap extraReqParams_;
|
|
||||||
QNetworkAccessManager* manager_ = nullptr;
|
|
||||||
ReplyList timedReplies_;
|
|
||||||
QString grantType_;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Token& token_;
|
|
||||||
|
|
||||||
private:
|
|
||||||
PollServer* pollServer_ = nullptr;
|
|
||||||
Activity activity_ = Activity::Idle;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,59 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
// Common constants
|
|
||||||
const char ENCRYPTION_KEY[] = "12345678";
|
|
||||||
const char MIME_TYPE_XFORM[] = "application/x-www-form-urlencoded";
|
|
||||||
const char MIME_TYPE_JSON[] = "application/json";
|
|
||||||
|
|
||||||
// OAuth 1/1.1 Request Parameters
|
|
||||||
const char OAUTH_CALLBACK[] = "oauth_callback";
|
|
||||||
const char OAUTH_CONSUMER_KEY[] = "oauth_consumer_key";
|
|
||||||
const char OAUTH_NONCE[] = "oauth_nonce";
|
|
||||||
const char OAUTH_SIGNATURE[] = "oauth_signature";
|
|
||||||
const char OAUTH_SIGNATURE_METHOD[] = "oauth_signature_method";
|
|
||||||
const char OAUTH_TIMESTAMP[] = "oauth_timestamp";
|
|
||||||
const char OAUTH_VERSION[] = "oauth_version";
|
|
||||||
// OAuth 1/1.1 Response Parameters
|
|
||||||
const char OAUTH_TOKEN[] = "oauth_token";
|
|
||||||
const char OAUTH_TOKEN_SECRET[] = "oauth_token_secret";
|
|
||||||
const char OAUTH_CALLBACK_CONFIRMED[] = "oauth_callback_confirmed";
|
|
||||||
const char OAUTH_VERFIER[] = "oauth_verifier";
|
|
||||||
|
|
||||||
// OAuth 2 Request Parameters
|
|
||||||
const char OAUTH2_RESPONSE_TYPE[] = "response_type";
|
|
||||||
const char OAUTH2_CLIENT_ID[] = "client_id";
|
|
||||||
const char OAUTH2_CLIENT_SECRET[] = "client_secret";
|
|
||||||
const char OAUTH2_USERNAME[] = "username";
|
|
||||||
const char OAUTH2_PASSWORD[] = "password";
|
|
||||||
const char OAUTH2_REDIRECT_URI[] = "redirect_uri";
|
|
||||||
const char OAUTH2_SCOPE[] = "scope";
|
|
||||||
const char OAUTH2_GRANT_TYPE_CODE[] = "code";
|
|
||||||
const char OAUTH2_GRANT_TYPE_TOKEN[] = "token";
|
|
||||||
const char OAUTH2_GRANT_TYPE_PASSWORD[] = "password";
|
|
||||||
const char OAUTH2_GRANT_TYPE_DEVICE[] = "urn:ietf:params:oauth:grant-type:device_code";
|
|
||||||
const char OAUTH2_GRANT_TYPE[] = "grant_type";
|
|
||||||
const char OAUTH2_API_KEY[] = "api_key";
|
|
||||||
const char OAUTH2_STATE[] = "state";
|
|
||||||
const char OAUTH2_CODE[] = "code";
|
|
||||||
|
|
||||||
// OAuth 2 Response Parameters
|
|
||||||
const char OAUTH2_ACCESS_TOKEN[] = "access_token";
|
|
||||||
const char OAUTH2_REFRESH_TOKEN[] = "refresh_token";
|
|
||||||
const char OAUTH2_EXPIRES_IN[] = "expires_in";
|
|
||||||
const char OAUTH2_DEVICE_CODE[] = "device_code";
|
|
||||||
const char OAUTH2_USER_CODE[] = "user_code";
|
|
||||||
const char OAUTH2_VERIFICATION_URI[] = "verification_uri";
|
|
||||||
const char OAUTH2_VERIFICATION_URL[] = "verification_url"; // Google sign-in
|
|
||||||
const char OAUTH2_VERIFICATION_URI_COMPLETE[] = "verification_uri_complete";
|
|
||||||
const char OAUTH2_INTERVAL[] = "interval";
|
|
||||||
|
|
||||||
// Parameter values
|
|
||||||
const char AUTHORIZATION_CODE[] = "authorization_code";
|
|
||||||
|
|
||||||
// Standard HTTP headers
|
|
||||||
const char HTTP_HTTP_HEADER[] = "HTTP";
|
|
||||||
const char HTTP_AUTHORIZATION_HEADER[] = "Authorization";
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,51 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QMap>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
/// Poll an authorization server for token
|
|
||||||
class PollServer : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit PollServer(QNetworkAccessManager* manager,
|
|
||||||
const QNetworkRequest& request,
|
|
||||||
const QByteArray& payload,
|
|
||||||
int expiresIn,
|
|
||||||
QObject* parent = 0);
|
|
||||||
|
|
||||||
/// Seconds to wait between polling requests
|
|
||||||
Q_PROPERTY(int interval READ interval WRITE setInterval)
|
|
||||||
int interval() const;
|
|
||||||
void setInterval(int interval);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void verificationReceived(QMap<QString, QString>);
|
|
||||||
void serverClosed(bool); // whether it has found parameters
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void startPolling();
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
void onPollTimeout();
|
|
||||||
void onExpiration();
|
|
||||||
void onReplyFinished();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QNetworkAccessManager* manager_;
|
|
||||||
const QNetworkRequest request_;
|
|
||||||
const QByteArray payload_;
|
|
||||||
const int expiresIn_;
|
|
||||||
QTimer expirationTimer;
|
|
||||||
QTimer pollTimer;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,63 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QList>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
constexpr int defaultTimeout = 30 * 1000;
|
|
||||||
|
|
||||||
/// A network request/reply pair that can time out.
|
|
||||||
class Reply : public QTimer {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
Reply(QNetworkReply* reply, int timeOut = defaultTimeout, QObject* parent = 0);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void error(QNetworkReply::NetworkError);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
/// When time out occurs, the QNetworkReply's error() signal is triggered.
|
|
||||||
void onTimeOut();
|
|
||||||
|
|
||||||
public:
|
|
||||||
QNetworkReply* reply;
|
|
||||||
bool timedOut = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// List of O2Replies.
|
|
||||||
class ReplyList {
|
|
||||||
public:
|
|
||||||
ReplyList() { ignoreSslErrors_ = false; }
|
|
||||||
|
|
||||||
/// Destructor.
|
|
||||||
/// Deletes all O2Reply instances in the list.
|
|
||||||
virtual ~ReplyList();
|
|
||||||
|
|
||||||
/// Create a new O2Reply from a QNetworkReply, and add it to this list.
|
|
||||||
void add(QNetworkReply* reply, int timeOut = defaultTimeout);
|
|
||||||
|
|
||||||
/// Add an O2Reply to the list, while taking ownership of it.
|
|
||||||
void add(Reply* reply);
|
|
||||||
|
|
||||||
/// Remove item from the list that corresponds to a QNetworkReply.
|
|
||||||
void remove(QNetworkReply* reply);
|
|
||||||
|
|
||||||
/// Find an O2Reply in the list, corresponding to a QNetworkReply.
|
|
||||||
/// @return Matching O2Reply or NULL.
|
|
||||||
Reply* find(QNetworkReply* reply);
|
|
||||||
|
|
||||||
bool ignoreSslErrors();
|
|
||||||
void setIgnoreSslErrors(bool ignoreSslErrors);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QList<Reply*> replies_;
|
|
||||||
bool ignoreSslErrors_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
/// Request parameter (name-value pair) participating in authentication.
|
|
||||||
struct RequestParameter {
|
|
||||||
RequestParameter(const QByteArray& n, const QByteArray& v) : name(n), value(v) {}
|
|
||||||
bool operator<(const RequestParameter& other) const { return (name == other.name) ? (value < other.value) : (name < other.name); }
|
|
||||||
QByteArray name;
|
|
||||||
QByteArray value;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,467 +0,0 @@
|
|||||||
#include <QCryptographicHash>
|
|
||||||
#include <QDataStream>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QList>
|
|
||||||
#include <QMap>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QPair>
|
|
||||||
#include <QTcpServer>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QUuid>
|
|
||||||
#include <QVariantMap>
|
|
||||||
|
|
||||||
#include <QUrlQuery>
|
|
||||||
|
|
||||||
#include "katabasis/DeviceFlow.h"
|
|
||||||
#include "katabasis/Globals.h"
|
|
||||||
#include "katabasis/PollServer.h"
|
|
||||||
|
|
||||||
#include "JsonResponse.h"
|
|
||||||
#include "KatabasisLogging.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// ref: https://tools.ietf.org/html/rfc8628#section-3.2
|
|
||||||
// Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both.
|
|
||||||
bool hasMandatoryDeviceAuthParams(const QVariantMap& params)
|
|
||||||
{
|
|
||||||
if (!params.contains(Katabasis::OAUTH2_DEVICE_CODE))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!params.contains(Katabasis::OAUTH2_USER_CODE))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!(params.contains(Katabasis::OAUTH2_VERIFICATION_URI) || params.contains(Katabasis::OAUTH2_VERIFICATION_URL)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!params.contains(Katabasis::OAUTH2_EXPIRES_IN))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray createQueryParameters(const QList<Katabasis::RequestParameter>& parameters)
|
|
||||||
{
|
|
||||||
QByteArray ret;
|
|
||||||
bool first = true;
|
|
||||||
for (auto& h : parameters) {
|
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
ret.append("&");
|
|
||||||
}
|
|
||||||
ret.append(QUrl::toPercentEncoding(h.name) + "=" + QUrl::toPercentEncoding(h.value));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
DeviceFlow::DeviceFlow(Options& opts, Token& token, QObject* parent, QNetworkAccessManager* manager) : QObject(parent), token_(token)
|
|
||||||
{
|
|
||||||
manager_ = manager ? manager : new QNetworkAccessManager(this);
|
|
||||||
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
|
|
||||||
options_ = opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceFlow::linked()
|
|
||||||
{
|
|
||||||
return token_.validity != Validity::None;
|
|
||||||
}
|
|
||||||
void DeviceFlow::setLinked(bool v)
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::setLinked:" << (v ? "true" : "false");
|
|
||||||
token_.validity = v ? Validity::Certain : Validity::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::updateActivity(Activity activity)
|
|
||||||
{
|
|
||||||
if (activity_ == activity) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
activity_ = activity;
|
|
||||||
switch (activity) {
|
|
||||||
case Katabasis::Activity::Idle:
|
|
||||||
case Katabasis::Activity::LoggingIn:
|
|
||||||
case Katabasis::Activity::LoggingOut:
|
|
||||||
case Katabasis::Activity::Refreshing:
|
|
||||||
// non-terminal states...
|
|
||||||
break;
|
|
||||||
case Katabasis::Activity::FailedSoft:
|
|
||||||
// terminal state, tokens did not change
|
|
||||||
break;
|
|
||||||
case Katabasis::Activity::FailedHard:
|
|
||||||
case Katabasis::Activity::FailedGone:
|
|
||||||
// terminal state, tokens are invalid
|
|
||||||
token_ = Token();
|
|
||||||
break;
|
|
||||||
case Katabasis::Activity::Succeeded:
|
|
||||||
setLinked(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
emit activityChanged(activity_);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DeviceFlow::token()
|
|
||||||
{
|
|
||||||
return token_.token;
|
|
||||||
}
|
|
||||||
void DeviceFlow::setToken(const QString& v)
|
|
||||||
{
|
|
||||||
token_.token = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap DeviceFlow::extraTokens()
|
|
||||||
{
|
|
||||||
return token_.extra;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setExtraTokens(QVariantMap extraTokens)
|
|
||||||
{
|
|
||||||
token_.extra = extraTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setPollServer(PollServer* server)
|
|
||||||
{
|
|
||||||
if (pollServer_)
|
|
||||||
pollServer_->deleteLater();
|
|
||||||
|
|
||||||
pollServer_ = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
PollServer* DeviceFlow::pollServer() const
|
|
||||||
{
|
|
||||||
return pollServer_;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap DeviceFlow::extraRequestParams()
|
|
||||||
{
|
|
||||||
return extraReqParams_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setExtraRequestParams(const QVariantMap& value)
|
|
||||||
{
|
|
||||||
extraReqParams_ = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DeviceFlow::grantType()
|
|
||||||
{
|
|
||||||
if (!grantType_.isEmpty())
|
|
||||||
return grantType_;
|
|
||||||
|
|
||||||
return OAUTH2_GRANT_TYPE_DEVICE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setGrantType(const QString& value)
|
|
||||||
{
|
|
||||||
grantType_ = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First get the URL and token to display to the user
|
|
||||||
void DeviceFlow::login()
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::link";
|
|
||||||
|
|
||||||
updateActivity(Activity::LoggingIn);
|
|
||||||
setLinked(false);
|
|
||||||
setToken("");
|
|
||||||
setExtraTokens(QVariantMap());
|
|
||||||
setRefreshToken(QString());
|
|
||||||
setExpires(QDateTime());
|
|
||||||
|
|
||||||
QList<RequestParameter> parameters;
|
|
||||||
parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8()));
|
|
||||||
parameters.append(RequestParameter(OAUTH2_SCOPE, options_.scope.toUtf8()));
|
|
||||||
QByteArray payload = createQueryParameters(parameters);
|
|
||||||
|
|
||||||
QUrl url(options_.authorizationUrl);
|
|
||||||
QNetworkRequest deviceRequest(url);
|
|
||||||
deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
||||||
QNetworkReply* tokenReply = manager_->post(deviceRequest, payload);
|
|
||||||
|
|
||||||
connect(tokenReply, &QNetworkReply::finished, this, &DeviceFlow::onDeviceAuthReplyFinished, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, once we get them, present them to the user
|
|
||||||
void DeviceFlow::onDeviceAuthReplyFinished()
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished";
|
|
||||||
QNetworkReply* tokenReply = qobject_cast<QNetworkReply*>(sender());
|
|
||||||
if (!tokenReply) {
|
|
||||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: reply is null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tokenReply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray replyData = tokenReply->readAll();
|
|
||||||
|
|
||||||
// Dump replyData
|
|
||||||
// SENSITIVE DATA in RelWithDebInfo or Debug builds
|
|
||||||
// qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: replyData\n";
|
|
||||||
// qDebug() << QString( replyData );
|
|
||||||
|
|
||||||
QVariantMap params = parseJsonResponse(replyData);
|
|
||||||
|
|
||||||
// Dump tokens
|
|
||||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Tokens returned:\n";
|
|
||||||
foreach (QString key, params.keys()) {
|
|
||||||
// SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first
|
|
||||||
qDebug() << key << ": " << params.value(key).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for mandatory parameters
|
|
||||||
if (hasMandatoryDeviceAuthParams(params)) {
|
|
||||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Device auth request response";
|
|
||||||
|
|
||||||
const QString userCode = params.take(OAUTH2_USER_CODE).toString();
|
|
||||||
QUrl uri = params.take(OAUTH2_VERIFICATION_URI).toUrl();
|
|
||||||
if (uri.isEmpty())
|
|
||||||
uri = params.take(OAUTH2_VERIFICATION_URL).toUrl();
|
|
||||||
|
|
||||||
if (params.contains(OAUTH2_VERIFICATION_URI_COMPLETE))
|
|
||||||
emit openBrowser(params.take(OAUTH2_VERIFICATION_URI_COMPLETE).toUrl());
|
|
||||||
|
|
||||||
bool ok = false;
|
|
||||||
int expiresIn = params[OAUTH2_EXPIRES_IN].toInt(&ok);
|
|
||||||
if (!ok) {
|
|
||||||
qWarning() << "DeviceFlow::startPollServer: No expired_in parameter";
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit showVerificationUriAndCode(uri, userCode, expiresIn);
|
|
||||||
|
|
||||||
startPollServer(params, expiresIn);
|
|
||||||
} else {
|
|
||||||
qWarning() << "DeviceFlow::onDeviceAuthReplyFinished: Mandatory parameters missing from response";
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokenReply->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spin up polling for the user completing the login flow out of band
|
|
||||||
void DeviceFlow::startPollServer(const QVariantMap& params, int expiresIn)
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::startPollServer: device_ and user_code expires in" << expiresIn << "seconds";
|
|
||||||
|
|
||||||
QUrl url(options_.accessTokenUrl);
|
|
||||||
QNetworkRequest authRequest(url);
|
|
||||||
authRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
||||||
|
|
||||||
const QString deviceCode = params[OAUTH2_DEVICE_CODE].toString();
|
|
||||||
const QString grantType = grantType_.isEmpty() ? OAUTH2_GRANT_TYPE_DEVICE : grantType_;
|
|
||||||
|
|
||||||
QList<RequestParameter> parameters;
|
|
||||||
parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8()));
|
|
||||||
if (!options_.clientSecret.isEmpty()) {
|
|
||||||
parameters.append(RequestParameter(OAUTH2_CLIENT_SECRET, options_.clientSecret.toUtf8()));
|
|
||||||
}
|
|
||||||
parameters.append(RequestParameter(OAUTH2_CODE, deviceCode.toUtf8()));
|
|
||||||
parameters.append(RequestParameter(OAUTH2_GRANT_TYPE, grantType.toUtf8()));
|
|
||||||
QByteArray payload = createQueryParameters(parameters);
|
|
||||||
|
|
||||||
PollServer* pollServer = new PollServer(manager_, authRequest, payload, expiresIn, this);
|
|
||||||
if (params.contains(OAUTH2_INTERVAL)) {
|
|
||||||
bool ok = false;
|
|
||||||
int interval = params[OAUTH2_INTERVAL].toInt(&ok);
|
|
||||||
if (ok) {
|
|
||||||
pollServer->setInterval(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connect(pollServer, &PollServer::verificationReceived, this, &DeviceFlow::onVerificationReceived);
|
|
||||||
connect(pollServer, &PollServer::serverClosed, this, &DeviceFlow::serverHasClosed);
|
|
||||||
setPollServer(pollServer);
|
|
||||||
pollServer->startPolling();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once the user completes the flow, update the internal state and report it to observers
|
|
||||||
void DeviceFlow::onVerificationReceived(const QMap<QString, QString> response)
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::onVerificationReceived: Emitting closeBrowser()";
|
|
||||||
emit closeBrowser();
|
|
||||||
|
|
||||||
if (response.contains("error")) {
|
|
||||||
qWarning() << "DeviceFlow::onVerificationReceived: Verification failed:" << response;
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for mandatory tokens
|
|
||||||
if (response.contains(OAUTH2_ACCESS_TOKEN)) {
|
|
||||||
qDebug() << "DeviceFlow::onVerificationReceived: Access token returned for implicit or device flow";
|
|
||||||
setToken(response.value(OAUTH2_ACCESS_TOKEN));
|
|
||||||
if (response.contains(OAUTH2_EXPIRES_IN)) {
|
|
||||||
bool ok = false;
|
|
||||||
int expiresIn = response.value(OAUTH2_EXPIRES_IN).toInt(&ok);
|
|
||||||
if (ok) {
|
|
||||||
qDebug() << "DeviceFlow::onVerificationReceived: Token expires in" << expiresIn << "seconds";
|
|
||||||
setExpires(QDateTime::currentDateTimeUtc().addSecs(expiresIn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (response.contains(OAUTH2_REFRESH_TOKEN)) {
|
|
||||||
setRefreshToken(response.value(OAUTH2_REFRESH_TOKEN));
|
|
||||||
}
|
|
||||||
updateActivity(Activity::Succeeded);
|
|
||||||
} else {
|
|
||||||
qWarning() << "DeviceFlow::onVerificationReceived: Access token missing from response for implicit or device flow";
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or if the flow fails or the polling times out, update the internal state with error and report it to observers
|
|
||||||
void DeviceFlow::serverHasClosed(bool paramsfound)
|
|
||||||
{
|
|
||||||
if (!paramsfound) {
|
|
||||||
// server has probably timed out after receiving first response
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
}
|
|
||||||
// poll server is not re-used for later auth requests
|
|
||||||
setPollServer(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::logout()
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::unlink";
|
|
||||||
updateActivity(Activity::LoggingOut);
|
|
||||||
// FIXME: implement logout flows... if they exist
|
|
||||||
token_ = Token();
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
}
|
|
||||||
|
|
||||||
QDateTime DeviceFlow::expires()
|
|
||||||
{
|
|
||||||
return token_.notAfter;
|
|
||||||
}
|
|
||||||
void DeviceFlow::setExpires(QDateTime v)
|
|
||||||
{
|
|
||||||
token_.notAfter = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DeviceFlow::refreshToken()
|
|
||||||
{
|
|
||||||
return token_.refresh_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setRefreshToken(const QString& v)
|
|
||||||
{
|
|
||||||
qCDebug(katabasisCredentials) << "new refresh token:" << v;
|
|
||||||
token_.refresh_token = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
QByteArray buildRequestBody(const QMap<QString, QString>& parameters)
|
|
||||||
{
|
|
||||||
QByteArray body;
|
|
||||||
bool first = true;
|
|
||||||
foreach (QString key, parameters.keys()) {
|
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
body.append("&");
|
|
||||||
}
|
|
||||||
QString value = parameters.value(key);
|
|
||||||
body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value));
|
|
||||||
}
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool DeviceFlow::refresh()
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::refresh: Token: ..." << refreshToken().right(7);
|
|
||||||
|
|
||||||
updateActivity(Activity::Refreshing);
|
|
||||||
|
|
||||||
if (refreshToken().isEmpty()) {
|
|
||||||
qWarning() << "DeviceFlow::refresh: No refresh token";
|
|
||||||
onRefreshError(QNetworkReply::AuthenticationRequiredError, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (options_.accessTokenUrl.isEmpty()) {
|
|
||||||
qWarning() << "DeviceFlow::refresh: Refresh token URL not set";
|
|
||||||
onRefreshError(QNetworkReply::AuthenticationRequiredError, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkRequest refreshRequest(options_.accessTokenUrl);
|
|
||||||
refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, MIME_TYPE_XFORM);
|
|
||||||
QMap<QString, QString> parameters;
|
|
||||||
parameters.insert(OAUTH2_CLIENT_ID, options_.clientIdentifier);
|
|
||||||
if (!options_.clientSecret.isEmpty()) {
|
|
||||||
parameters.insert(OAUTH2_CLIENT_SECRET, options_.clientSecret);
|
|
||||||
}
|
|
||||||
parameters.insert(OAUTH2_REFRESH_TOKEN, refreshToken());
|
|
||||||
parameters.insert(OAUTH2_GRANT_TYPE, OAUTH2_REFRESH_TOKEN);
|
|
||||||
|
|
||||||
QByteArray data = buildRequestBody(parameters);
|
|
||||||
QNetworkReply* refreshReply = manager_->post(refreshRequest, data);
|
|
||||||
timedReplies_.add(refreshReply);
|
|
||||||
connect(refreshReply, &QNetworkReply::finished, this, &DeviceFlow::onRefreshFinished, Qt::QueuedConnection);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::onRefreshFinished()
|
|
||||||
{
|
|
||||||
QNetworkReply* refreshReply = qobject_cast<QNetworkReply*>(sender());
|
|
||||||
|
|
||||||
auto networkError = refreshReply->error();
|
|
||||||
if (networkError == QNetworkReply::NoError) {
|
|
||||||
QByteArray reply = refreshReply->readAll();
|
|
||||||
QVariantMap tokens = parseJsonResponse(reply);
|
|
||||||
setToken(tokens.value(OAUTH2_ACCESS_TOKEN).toString());
|
|
||||||
setExpires(QDateTime::currentDateTimeUtc().addSecs(tokens.value(OAUTH2_EXPIRES_IN).toInt()));
|
|
||||||
QString refreshToken = tokens.value(OAUTH2_REFRESH_TOKEN).toString();
|
|
||||||
if (!refreshToken.isEmpty()) {
|
|
||||||
setRefreshToken(refreshToken);
|
|
||||||
} else {
|
|
||||||
qDebug() << "No new refresh token. Keep the old one.";
|
|
||||||
}
|
|
||||||
timedReplies_.remove(refreshReply);
|
|
||||||
refreshReply->deleteLater();
|
|
||||||
updateActivity(Activity::Succeeded);
|
|
||||||
qDebug() << "New token expires in" << expires() << "seconds";
|
|
||||||
} else {
|
|
||||||
// FIXME: differentiate the error more here
|
|
||||||
onRefreshError(networkError, refreshReply);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::onRefreshError(QNetworkReply::NetworkError error, QNetworkReply* refreshReply)
|
|
||||||
{
|
|
||||||
QString errorString = "No Reply";
|
|
||||||
if (refreshReply) {
|
|
||||||
timedReplies_.remove(refreshReply);
|
|
||||||
errorString = refreshReply->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (error) {
|
|
||||||
// used for invalid credentials and similar errors. Fall through.
|
|
||||||
case QNetworkReply::AuthenticationRequiredError:
|
|
||||||
case QNetworkReply::ContentAccessDenied:
|
|
||||||
case QNetworkReply::ContentOperationNotPermittedError:
|
|
||||||
case QNetworkReply::ProtocolInvalidOperationError:
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
break;
|
|
||||||
case QNetworkReply::ContentGoneError: {
|
|
||||||
updateActivity(Activity::FailedGone);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QNetworkReply::TimeoutError:
|
|
||||||
case QNetworkReply::OperationCanceledError:
|
|
||||||
case QNetworkReply::SslHandshakeFailedError:
|
|
||||||
default:
|
|
||||||
updateActivity(Activity::FailedSoft);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (refreshReply) {
|
|
||||||
refreshReply->deleteLater();
|
|
||||||
}
|
|
||||||
qDebug() << "DeviceFlow::onRefreshFinished: Error" << static_cast<int>(error) << " - " << errorString;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,27 +0,0 @@
|
|||||||
#include "JsonResponse.h"
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
QVariantMap parseJsonResponse(const QByteArray& data)
|
|
||||||
{
|
|
||||||
QJsonParseError err;
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
|
||||||
if (err.error != QJsonParseError::NoError) {
|
|
||||||
qWarning() << "parseTokenResponse: Failed to parse token response due to err:" << err.errorString();
|
|
||||||
return QVariantMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!doc.isObject()) {
|
|
||||||
qWarning() << "parseTokenResponse: Token response is not an object";
|
|
||||||
return QVariantMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc.object().toVariantMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QVariantMap>
|
|
||||||
|
|
||||||
class QByteArray;
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
/// Parse JSON data into a QVariantMap
|
|
||||||
QVariantMap parseJsonResponse(const QByteArray& data);
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,118 +0,0 @@
|
|||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
#include "JsonResponse.h"
|
|
||||||
#include "katabasis/PollServer.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
QMap<QString, QString> toVerificationParams(const QVariantMap& map)
|
|
||||||
{
|
|
||||||
QMap<QString, QString> params;
|
|
||||||
for (QVariantMap::const_iterator i = map.constBegin(); i != map.constEnd(); ++i) {
|
|
||||||
params[i.key()] = i.value().toString();
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
PollServer::PollServer(QNetworkAccessManager* manager,
|
|
||||||
const QNetworkRequest& request,
|
|
||||||
const QByteArray& payload,
|
|
||||||
int expiresIn,
|
|
||||||
QObject* parent)
|
|
||||||
: QObject(parent), manager_(manager), request_(request), payload_(payload), expiresIn_(expiresIn)
|
|
||||||
{
|
|
||||||
expirationTimer.setTimerType(Qt::VeryCoarseTimer);
|
|
||||||
expirationTimer.setInterval(expiresIn * 1000);
|
|
||||||
expirationTimer.setSingleShot(true);
|
|
||||||
connect(&expirationTimer, SIGNAL(timeout()), this, SLOT(onExpiration()));
|
|
||||||
expirationTimer.start();
|
|
||||||
|
|
||||||
pollTimer.setTimerType(Qt::VeryCoarseTimer);
|
|
||||||
pollTimer.setInterval(5 * 1000);
|
|
||||||
pollTimer.setSingleShot(true);
|
|
||||||
connect(&pollTimer, SIGNAL(timeout()), this, SLOT(onPollTimeout()));
|
|
||||||
}
|
|
||||||
|
|
||||||
int PollServer::interval() const
|
|
||||||
{
|
|
||||||
return pollTimer.interval() / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PollServer::setInterval(int interval)
|
|
||||||
{
|
|
||||||
pollTimer.setInterval(interval * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PollServer::startPolling()
|
|
||||||
{
|
|
||||||
if (expirationTimer.isActive()) {
|
|
||||||
pollTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PollServer::onPollTimeout()
|
|
||||||
{
|
|
||||||
qDebug() << "PollServer::onPollTimeout: retrying";
|
|
||||||
QNetworkReply* reply = manager_->post(request_, payload_);
|
|
||||||
connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PollServer::onExpiration()
|
|
||||||
{
|
|
||||||
pollTimer.stop();
|
|
||||||
emit serverClosed(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PollServer::onReplyFinished()
|
|
||||||
{
|
|
||||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
|
||||||
|
|
||||||
if (!reply) {
|
|
||||||
qDebug() << "PollServer::onReplyFinished: reply is null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray replyData = reply->readAll();
|
|
||||||
QMap<QString, QString> params = toVerificationParams(parseJsonResponse(replyData));
|
|
||||||
|
|
||||||
// Dump replyData
|
|
||||||
// SENSITIVE DATA in RelWithDebInfo or Debug builds
|
|
||||||
// qDebug() << "PollServer::onReplyFinished: replyData\n";
|
|
||||||
// qDebug() << QString( replyData );
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::TimeoutError) {
|
|
||||||
// rfc8628#section-3.2
|
|
||||||
// "On encountering a connection timeout, clients MUST unilaterally
|
|
||||||
// reduce their polling frequency before retrying. The use of an
|
|
||||||
// exponential backoff algorithm to achieve this, such as doubling the
|
|
||||||
// polling interval on each such connection timeout, is RECOMMENDED."
|
|
||||||
setInterval(interval() * 2);
|
|
||||||
pollTimer.start();
|
|
||||||
} else {
|
|
||||||
QString error = params.value("error");
|
|
||||||
if (error == "slow_down") {
|
|
||||||
// rfc8628#section-3.2
|
|
||||||
// "A variant of 'authorization_pending', the authorization request is
|
|
||||||
// still pending and polling should continue, but the interval MUST
|
|
||||||
// be increased by 5 seconds for this and all subsequent requests."
|
|
||||||
setInterval(interval() + 5);
|
|
||||||
pollTimer.start();
|
|
||||||
} else if (error == "authorization_pending") {
|
|
||||||
// keep trying - rfc8628#section-3.2
|
|
||||||
// "The authorization request is still pending as the end user hasn't
|
|
||||||
// yet completed the user-interaction steps (Section 3.3)."
|
|
||||||
pollTimer.start();
|
|
||||||
} else {
|
|
||||||
expirationTimer.stop();
|
|
||||||
emit serverClosed(true);
|
|
||||||
// let O2 handle the other cases
|
|
||||||
emit verificationReceived(params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reply->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -1,74 +0,0 @@
|
|||||||
#include <QNetworkReply>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "katabasis/Reply.h"
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
Reply::Reply(QNetworkReply* r, int timeOut, QObject* parent) : QTimer(parent), reply(r)
|
|
||||||
{
|
|
||||||
setSingleShot(true);
|
|
||||||
connect(this, &Reply::timeout, this, &Reply::onTimeOut, Qt::QueuedConnection);
|
|
||||||
start(timeOut);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Reply::onTimeOut()
|
|
||||||
{
|
|
||||||
timedOut = true;
|
|
||||||
reply->abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------
|
|
||||||
|
|
||||||
ReplyList::~ReplyList()
|
|
||||||
{
|
|
||||||
foreach (Reply* timedReply, replies_) {
|
|
||||||
delete timedReply;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplyList::add(QNetworkReply* reply, int timeOut)
|
|
||||||
{
|
|
||||||
if (reply && ignoreSslErrors()) {
|
|
||||||
reply->ignoreSslErrors();
|
|
||||||
}
|
|
||||||
add(new Reply(reply, timeOut));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplyList::add(Reply* reply)
|
|
||||||
{
|
|
||||||
replies_.append(reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplyList::remove(QNetworkReply* reply)
|
|
||||||
{
|
|
||||||
Reply* o2Reply = find(reply);
|
|
||||||
if (o2Reply) {
|
|
||||||
o2Reply->stop();
|
|
||||||
(void)replies_.removeOne(o2Reply);
|
|
||||||
// we took ownership, we must free
|
|
||||||
delete o2Reply;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Reply* ReplyList::find(QNetworkReply* reply)
|
|
||||||
{
|
|
||||||
foreach (Reply* timedReply, replies_) {
|
|
||||||
if (timedReply->reply == reply) {
|
|
||||||
return timedReply;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReplyList::ignoreSslErrors()
|
|
||||||
{
|
|
||||||
return ignoreSslErrors_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReplyList::setIgnoreSslErrors(bool ignoreSslErrors)
|
|
||||||
{
|
|
||||||
ignoreSslErrors_ = ignoreSslErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
@ -9,6 +9,7 @@
|
|||||||
jdk17,
|
jdk17,
|
||||||
zlib,
|
zlib,
|
||||||
qtbase,
|
qtbase,
|
||||||
|
qtnetworkauth,
|
||||||
quazip,
|
quazip,
|
||||||
extra-cmake-modules,
|
extra-cmake-modules,
|
||||||
tomlplusplus,
|
tomlplusplus,
|
||||||
@ -42,6 +43,7 @@ assert lib.assertMsg (stdenv.isLinux || !gamemodeSupport) "gamemodeSupport is on
|
|||||||
buildInputs =
|
buildInputs =
|
||||||
[
|
[
|
||||||
qtbase
|
qtbase
|
||||||
|
qtnetworkauth
|
||||||
zlib
|
zlib
|
||||||
quazip
|
quazip
|
||||||
ghc_filesystem
|
ghc_filesystem
|
||||||
|
@ -95,8 +95,8 @@ class LibraryTest : public QObject {
|
|||||||
auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString());
|
auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString());
|
||||||
QCOMPARE(downloads.size(), 1);
|
QCOMPARE(downloads.size(), 1);
|
||||||
QCOMPARE(failedFiles, {});
|
QCOMPARE(failedFiles, {});
|
||||||
NetAction::Ptr dl = downloads[0];
|
Net::NetRequest::Ptr dl = downloads[0];
|
||||||
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar"));
|
QCOMPARE(dl->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar"));
|
||||||
}
|
}
|
||||||
void test_legacy_url_local_broken()
|
void test_legacy_url_local_broken()
|
||||||
{
|
{
|
||||||
@ -147,7 +147,7 @@ class LibraryTest : public QObject {
|
|||||||
QCOMPARE(dls.size(), 1);
|
QCOMPARE(dls.size(), 1);
|
||||||
QCOMPARE(failedFiles, {});
|
QCOMPARE(failedFiles, {});
|
||||||
auto dl = dls[0];
|
auto dl = dls[0];
|
||||||
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar"));
|
QCOMPARE(dl->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void test_legacy_native_arch()
|
void test_legacy_native_arch()
|
||||||
@ -170,8 +170,8 @@ class LibraryTest : public QObject {
|
|||||||
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
|
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
|
||||||
QCOMPARE(dls.size(), 2);
|
QCOMPARE(dls.size(), 2);
|
||||||
QCOMPARE(failedFiles, {});
|
QCOMPARE(failedFiles, {});
|
||||||
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar"));
|
QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar"));
|
||||||
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar"));
|
QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar"));
|
||||||
}
|
}
|
||||||
r.system = "windows";
|
r.system = "windows";
|
||||||
{
|
{
|
||||||
@ -185,8 +185,8 @@ class LibraryTest : public QObject {
|
|||||||
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
|
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
|
||||||
QCOMPARE(dls.size(), 2);
|
QCOMPARE(dls.size(), 2);
|
||||||
QCOMPARE(failedFiles, {});
|
QCOMPARE(failedFiles, {});
|
||||||
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar"));
|
QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar"));
|
||||||
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar"));
|
QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar"));
|
||||||
}
|
}
|
||||||
r.system = "osx";
|
r.system = "osx";
|
||||||
{
|
{
|
||||||
@ -200,8 +200,8 @@ class LibraryTest : public QObject {
|
|||||||
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
|
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
|
||||||
QCOMPARE(dls.size(), 2);
|
QCOMPARE(dls.size(), 2);
|
||||||
QCOMPARE(failedFiles, {});
|
QCOMPARE(failedFiles, {});
|
||||||
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar"));
|
QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar"));
|
||||||
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar"));
|
QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void test_legacy_native_arch_local_override()
|
void test_legacy_native_arch_local_override()
|
||||||
@ -244,7 +244,7 @@ class LibraryTest : public QObject {
|
|||||||
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
|
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
|
||||||
QCOMPARE(dls.size(), 1);
|
QCOMPARE(dls.size(), 1);
|
||||||
QCOMPARE(failedFiles, {});
|
QCOMPARE(failedFiles, {});
|
||||||
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
|
QCOMPARE(dls[0]->url(), QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
|
||||||
}
|
}
|
||||||
r.system = "osx";
|
r.system = "osx";
|
||||||
test->setHint("local");
|
test->setHint("local");
|
||||||
@ -300,7 +300,7 @@ class LibraryTest : public QObject {
|
|||||||
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
|
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
|
||||||
QCOMPARE(dls.size(), 1);
|
QCOMPARE(dls.size(), 1);
|
||||||
QCOMPARE(failedFiles, {});
|
QCOMPARE(failedFiles, {});
|
||||||
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/"
|
QCOMPARE(dls[0]->url(), QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/"
|
||||||
"lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
|
"lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
|
||||||
}
|
}
|
||||||
void test_onenine_native_arch()
|
void test_onenine_native_arch()
|
||||||
@ -317,9 +317,9 @@ class LibraryTest : public QObject {
|
|||||||
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
|
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
|
||||||
QCOMPARE(dls.size(), 2);
|
QCOMPARE(dls.size(), 2);
|
||||||
QCOMPARE(failedFiles, {});
|
QCOMPARE(failedFiles, {});
|
||||||
QCOMPARE(dls[0]->m_url,
|
QCOMPARE(dls[0]->url(),
|
||||||
QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
|
QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
|
||||||
QCOMPARE(dls[1]->m_url,
|
QCOMPARE(dls[1]->url(),
|
||||||
QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
|
QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user