diff options
Diffstat (limited to 'logic')
29 files changed, 537 insertions, 426 deletions
diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 2d7537d6..93e57414 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -150,7 +150,7 @@ public: virtual SettingsObject &settings() const; /// returns a valid update task if update is needed, NULL otherwise - virtual Task *doUpdate(bool prepare_for_launch) = 0; + virtual std::shared_ptr<Task> doUpdate(bool only_prepare) = 0; /// returns a valid minecraft process, ready for launch with the given account. virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) = 0; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 72b6c51a..fef27bcd 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -44,12 +44,12 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, settings->registerSetting(new Setting("IntendedJarVersion", "")); } -Task *LegacyInstance::doUpdate(bool prepare_for_launch) +std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare) { // make sure the jar mods list is initialized by asking for it. auto list = jarModList(); // create an update task - return new LegacyUpdate(this, prepare_for_launch , this); + return std::shared_ptr<Task> (new LegacyUpdate(this, only_prepare , this)); } MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account) @@ -105,7 +105,7 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account) #endif args << "-jar" << LAUNCHER_FILE; - args << account->currentProfile()->name(); + args << account->currentProfile()->name; args << account->sessionId(); args << windowTitle; args << windowSize; diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index a17ef281..1e7d9eb6 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -76,7 +76,7 @@ public: virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; - virtual Task *doUpdate(bool prepare_for_launch) override; + virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override; virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; virtual void cleanupAfterRun() override; diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index 3fc17351..6125101b 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -26,14 +26,30 @@ #include <JlCompress.h> #include "logger/QsLog.h" -LegacyUpdate::LegacyUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent) - : Task(parent), m_inst(inst), m_prepare_for_launch(prepare_for_launch) +LegacyUpdate::LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) + : Task(parent), m_inst(inst), m_only_prepare(only_prepare) { } void LegacyUpdate::executeTask() { - lwjglStart(); + if(m_only_prepare) + { + // FIXME: think this through some more. + LegacyInstance *inst = (LegacyInstance *)m_inst; + if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) + { + ModTheJar(); + } + else + { + emitSucceeded(); + } + } + else + { + lwjglStart(); + } } void LegacyUpdate::lwjglStart() diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h index d753197f..0b573ca5 100644 --- a/logic/LegacyUpdate.h +++ b/logic/LegacyUpdate.h @@ -31,7 +31,7 @@ class LegacyUpdate : public Task { Q_OBJECT public: - explicit LegacyUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0); + explicit LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent = 0); virtual void executeTask(); private @@ -72,5 +72,5 @@ private: private: NetJobPtr legacyDownloadJob; BaseInstance *m_inst = nullptr; - bool m_prepare_for_launch = false; + bool m_only_prepare = false; }; diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index 5d99bfae..209929b7 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -75,20 +75,22 @@ QString MinecraftProcess::censorPrivateInfo(QString in) { if(!m_account) return in; - else + + QString sessionId = m_account->sessionId(); + QString accessToken = m_account->accessToken(); + QString clientToken = m_account->clientToken(); + in.replace(sessionId, "<SESSION ID>"); + in.replace(accessToken, "<ACCESS TOKEN>"); + in.replace(clientToken, "<CLIENT TOKEN>"); + auto profile = m_account->currentProfile(); + if(profile) { - QString sessionId = m_account->sessionId(); - QString accessToken = m_account->accessToken(); - QString clientToken = m_account->clientToken(); - QString profileId = m_account->currentProfile()->id(); - QString profileName = m_account->currentProfile()->name(); - in.replace(sessionId, "<SESSION ID>"); - in.replace(accessToken, "<ACCESS TOKEN>"); - in.replace(clientToken, "<CLIENT TOKEN>"); + QString profileId = profile->id; + QString profileName = profile->name; in.replace(profileId, "<PROFILE ID>"); in.replace(profileName, "<PROFILE NAME>"); - return in; } + return in; } // console window diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index a6b439a1..e804de11 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -39,9 +39,9 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o reloadFullVersion(); } -Task *OneSixInstance::doUpdate(bool prepare_for_launch) +std::shared_ptr<Task> OneSixInstance::doUpdate(bool only_prepare) { - return new OneSixUpdate(this, prepare_for_launch); + return std::shared_ptr<Task> (new OneSixUpdate(this, only_prepare)); } QString replaceTokensIn(QString text, QMap<QString, QString> with) @@ -136,8 +136,8 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) token_mapping["auth_username"] = account->username(); token_mapping["auth_session"] = account->sessionId(); token_mapping["auth_access_token"] = account->accessToken(); - token_mapping["auth_player_name"] = account->currentProfile()->name(); - token_mapping["auth_uuid"] = account->currentProfile()->id(); + token_mapping["auth_player_name"] = account->currentProfile()->name; + token_mapping["auth_uuid"] = account->currentProfile()->id; // this is for offline?: /* diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index cc98d822..f869e345 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -40,7 +40,7 @@ public: QString loaderModsDir() const; virtual QString instanceConfigFolder() const override; - virtual Task *doUpdate(bool prepare_for_launch) override; + virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override; virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; virtual void cleanupAfterRun() override; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 25e16328..7be0c056 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -33,8 +33,8 @@ #include "pathutils.h" #include <JlCompress.h> -OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent) - : Task(parent), m_inst(inst), m_prepare_for_launch(prepare_for_launch) +OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) + : Task(parent), m_inst(inst), m_only_prepare(only_prepare) { } @@ -50,6 +50,23 @@ void OneSixUpdate::executeTask() return; } + if(m_only_prepare) + { + if (m_inst->shouldUpdate()) + { + emitFailed("Unable to update instance in offline mode."); + return; + } + setStatus("Testing the Java installation."); + QString java_path = m_inst->settings().get("JavaPath").toString(); + + checker.reset(new JavaChecker()); + connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, + SLOT(checkFinishedOffline(JavaCheckResult))); + checker->performCheck(java_path); + return; + } + if (m_inst->shouldUpdate()) { // Get a pointer to the version object that corresponds to the instance's version. @@ -65,35 +82,43 @@ void OneSixUpdate::executeTask() } else { - checkJava(); + checkJavaOnline(); } } -void OneSixUpdate::checkJava() +void OneSixUpdate::checkJavaOnline() { - QLOG_INFO() << m_inst->name() << ": checking java binary"; setStatus("Testing the Java installation."); - // TODO: cache this so we don't have to run an extra java process every time. QString java_path = m_inst->settings().get("JavaPath").toString(); checker.reset(new JavaChecker()); connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinished(JavaCheckResult))); + SLOT(checkFinishedOnline(JavaCheckResult))); checker->performCheck(java_path); } -void OneSixUpdate::checkFinished(JavaCheckResult result) +void OneSixUpdate::checkFinishedOnline(JavaCheckResult result) { if (result.valid) { - QLOG_INFO() << m_inst->name() << ": java is " - << (result.is_64bit ? "64 bit" : "32 bit"); java_is_64bit = result.is_64bit; jarlibStart(); } else { - QLOG_INFO() << m_inst->name() << ": java isn't valid"; + emitFailed("The java binary doesn't work. Check the settings and correct the problem"); + } +} + +void OneSixUpdate::checkFinishedOffline(JavaCheckResult result) +{ + if (result.valid) + { + java_is_64bit = result.is_64bit; + prepareForLaunch(); + } + else + { emitFailed("The java binary doesn't work. Check the settings and correct the problem"); } } @@ -160,7 +185,7 @@ void OneSixUpdate::versionFileFinished() } inst->reloadFullVersion(); - checkJava(); + checkJavaOnline(); } void OneSixUpdate::versionFileFailed() @@ -240,10 +265,7 @@ void OneSixUpdate::jarlibStart() void OneSixUpdate::jarlibFinished() { - if (m_prepare_for_launch) - prepareForLaunch(); - else - emitSucceeded(); + prepareForLaunch(); } void OneSixUpdate::jarlibFailed() diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index b86c205f..7ff9d881 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -43,11 +43,13 @@ slots: void jarlibFinished(); void jarlibFailed(); - void checkJava(); - void checkFinished(JavaCheckResult result); + void checkJavaOnline(); + void checkFinishedOnline(JavaCheckResult result); + void checkFinishedOffline(JavaCheckResult result); // extract the appropriate libraries void prepareForLaunch(); + private: NetJobPtr specificVersionDownloadJob; NetJobPtr jarlibDownloadJob; @@ -55,7 +57,7 @@ private: // target version, determined during this task std::shared_ptr<MinecraftVersion> targetVersion; BaseInstance *m_inst = nullptr; - bool m_prepare_for_launch = false; + bool m_only_prepare = false; std::shared_ptr<JavaChecker> checker; bool java_is_64bit = false; diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp index 4a61cf19..b1acfb25 100644 --- a/logic/auth/MojangAccount.cpp +++ b/logic/auth/MojangAccount.cpp @@ -16,113 +16,16 @@ */ #include "MojangAccount.h" +#include "flows/RefreshTask.h" +#include "flows/AuthenticateTask.h" #include <QUuid> #include <QJsonObject> #include <QJsonArray> +#include <QRegExp> #include <logger/QsLog.h> -MojangAccount::MojangAccount(const QString &username, QObject *parent) : QObject(parent) -{ - // Generate a client token. - m_clientToken = QUuid::createUuid().toString(); - - m_username = username; - - m_currentProfile = -1; -} - -MojangAccount::MojangAccount(const QString &username, const QString &clientToken, - const QString &accessToken, QObject *parent) - : QObject(parent) -{ - m_username = username; - m_clientToken = clientToken; - m_accessToken = accessToken; - - m_currentProfile = -1; -} - -MojangAccount::MojangAccount(const MojangAccount &other, QObject *parent) -{ - m_username = other.username(); - m_clientToken = other.clientToken(); - m_accessToken = other.accessToken(); - - m_profiles = other.m_profiles; - m_currentProfile = other.m_currentProfile; -} - -QString MojangAccount::username() const -{ - return m_username; -} - -QString MojangAccount::clientToken() const -{ - return m_clientToken; -} - -void MojangAccount::setClientToken(const QString &clientToken) -{ - m_clientToken = clientToken; -} - -QString MojangAccount::accessToken() const -{ - return m_accessToken; -} - -void MojangAccount::setAccessToken(const QString &accessToken) -{ - m_accessToken = accessToken; -} - -QString MojangAccount::sessionId() const -{ - return "token:" + m_accessToken + ":" + currentProfile()->id(); -} - -const QList<AccountProfile> MojangAccount::profiles() const -{ - return m_profiles; -} - -const AccountProfile *MojangAccount::currentProfile() const -{ - if (m_currentProfile < 0) - { - if (m_profiles.length() > 0) - return &m_profiles.at(0); - else - return nullptr; - } - else - return &m_profiles.at(m_currentProfile); -} - -bool MojangAccount::setProfile(const QString &profileId) -{ - const QList<AccountProfile> &profiles = this->profiles(); - for (int i = 0; i < profiles.length(); i++) - { - if (profiles.at(i).id() == profileId) - { - m_currentProfile = i; - return true; - } - } - return false; -} - -void MojangAccount::loadProfiles(const ProfileList &profiles) -{ - m_profiles.clear(); - for (auto profile : profiles) - m_profiles.append(profile); -} - MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) { // The JSON object must at least have a username for it to be valid. @@ -143,7 +46,7 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) return nullptr; } - ProfileList profiles; + QList<AccountProfile> profiles; for (QJsonValue profileVal : profileArray) { QJsonObject profileObject = profileVal.toObject(); @@ -154,67 +57,116 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) QLOG_WARN() << "Unable to load a profile because it was missing an ID or a name."; continue; } - profiles.append(AccountProfile(id, name)); + profiles.append({id, name}); } - MojangAccountPtr account(new MojangAccount(username, clientToken, accessToken)); - account->loadProfiles(profiles); + MojangAccountPtr account(new MojangAccount()); + account->m_username = username; + account->m_clientToken = clientToken; + account->m_accessToken = accessToken; + account->m_profiles = profiles; // Get the currently selected profile. QString currentProfile = object.value("activeProfile").toString(""); if (!currentProfile.isEmpty()) - account->setProfile(currentProfile); + account->setCurrentProfile(currentProfile); return account; } -QJsonObject MojangAccount::saveToJson() +MojangAccountPtr MojangAccount::createFromUsername(const QString& username) +{ + MojangAccountPtr account(new MojangAccount()); + account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->m_username = username; + return account; +} + +QJsonObject MojangAccount::saveToJson() const { QJsonObject json; - json.insert("username", username()); - json.insert("clientToken", clientToken()); - json.insert("accessToken", accessToken()); + json.insert("username", m_username); + json.insert("clientToken", m_clientToken); + json.insert("accessToken", m_accessToken); QJsonArray profileArray; for (AccountProfile profile : m_profiles) { QJsonObject profileObj; - profileObj.insert("id", profile.id()); - profileObj.insert("name", profile.name()); + profileObj.insert("id", profile.id); + profileObj.insert("name", profile.name); profileArray.append(profileObj); } json.insert("profiles", profileArray); - if (currentProfile() != nullptr) - json.insert("activeProfile", currentProfile()->id()); + if (m_currentProfile != -1) + json.insert("activeProfile", currentProfile()->id); return json; } - -AccountProfile::AccountProfile(const QString& id, const QString& name) +bool MojangAccount::setCurrentProfile(const QString &profileId) { - m_id = id; - m_name = name; + for (int i = 0; i < m_profiles.length(); i++) + { + if (m_profiles[i].id == profileId) + { + m_currentProfile = i; + return true; + } + } + return false; } -AccountProfile::AccountProfile(const AccountProfile &other) +const AccountProfile* MojangAccount::currentProfile() const { - m_id = other.m_id; - m_name = other.m_name; + if(m_currentProfile == -1) + return nullptr; + return &m_profiles[m_currentProfile]; } -QString AccountProfile::id() const +AccountStatus MojangAccount::accountStatus() const { - return m_id; + if(m_accessToken.isEmpty()) + return NotVerified; + if(!m_online) + return Verified; + return Online; } -QString AccountProfile::name() const +std::shared_ptr<Task> MojangAccount::login(QString password) { - return m_name; + if(m_currentTask) + return m_currentTask; + if(password.isEmpty()) + { + m_currentTask.reset(new RefreshTask(this)); + } + else + { + m_currentTask.reset(new AuthenticateTask(this, password)); + } + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + return m_currentTask; } -void MojangAccount::propagateChange() +void MojangAccount::authSucceeded() { + m_online = true; + m_currentTask.reset(); emit changed(); } + +void MojangAccount::authFailed(QString reason) +{ + // This is emitted when the yggdrasil tasks time out or are cancelled. + // -> we treat the error as no-op + if(reason != "Yggdrasil task cancelled.") + { + m_online = false; + m_accessToken = QString(); + emit changed(); + } + m_currentTask.reset(); +} diff --git a/logic/auth/MojangAccount.h b/logic/auth/MojangAccount.h index 25a85790..95f777ce 100644 --- a/logic/auth/MojangAccount.h +++ b/logic/auth/MojangAccount.h @@ -23,34 +23,26 @@ #include <memory> +class Task; +class YggdrasilTask; class MojangAccount; typedef std::shared_ptr<MojangAccount> MojangAccountPtr; Q_DECLARE_METATYPE(MojangAccountPtr) /** - * Class that represents a profile within someone's Mojang account. + * A profile within someone's Mojang account. * * Currently, the profile system has not been implemented by Mojang yet, * but we might as well add some things for it in MultiMC right now so * we don't have to rip the code to pieces to add it later. */ -class AccountProfile +struct AccountProfile { -public: - AccountProfile(const QString &id, const QString &name); - AccountProfile(const AccountProfile &other); - - QString id() const; - QString name() const; - -protected: - QString m_id; - QString m_name; + QString id; + QString name; }; -typedef QList<AccountProfile> ProfileList; - struct User { QString id; @@ -59,6 +51,13 @@ struct User QList<QPair<QString, QString>> properties; }; +enum AccountStatus +{ + NotVerified, + Verified, + Online +}; + /** * Object that stores information about a certain Mojang account. * @@ -68,106 +67,116 @@ struct User class MojangAccount : public QObject { Q_OBJECT -public: - /** - * Constructs a new MojangAccount with the given username. - * The client token will be generated automatically and the access token will be blank. - */ - explicit MojangAccount(const QString &username, QObject *parent = 0); +public: /* construction */ + //! Do not copy accounts. ever. + explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; - /** - * Constructs a new MojangAccount with the given username, client token, and access token. - */ - explicit MojangAccount(const QString &username, const QString &clientToken, - const QString &accessToken, QObject *parent = 0); + //! Default constructor + explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; - /** - * Constructs a new MojangAccount matching the given account. - */ - MojangAccount(const MojangAccount &other, QObject *parent); + //! Creates an empty account for the specified user name. + static MojangAccountPtr createFromUsername(const QString &username); - /** - * Loads a MojangAccount from the given JSON object. - */ + //! Loads a MojangAccount from the given JSON object. static MojangAccountPtr loadFromJson(const QJsonObject &json); - /** - * Saves a MojangAccount to a JSON object and returns it. - */ - QJsonObject saveToJson(); + //! Saves a MojangAccount to a JSON object and returns it. + QJsonObject saveToJson() const; +public: /* manipulation */ /** - * Update the account on disk and lists (it changed, for whatever reason) - * This is called by various Yggdrasil tasks. - */ - void propagateChange(); + * Sets the currently selected profile to the profile with the given ID string. + * If profileId is not in the list of available profiles, the function will simply return + * false. + */ + bool setCurrentProfile(const QString &profileId); + + /** + * Attempt to login. Empty password means we use the token. + * If the attempt fails because we already are performing some task, it returns false. + */ + std::shared_ptr<Task> login(QString password = QString()); + + void downgrade() + { + m_online = false; + } +public: /* queries */ + const QString &username() const + { + return m_username; + } + + const QString &clientToken() const + { + return m_clientToken; + } + + const QString &accessToken() const + { + return m_accessToken; + } + + const QList<AccountProfile> &profiles() const + { + return m_profiles; + } + + //! Get the session ID required for legacy Minecraft versions + QString sessionId() const + { + if (m_currentProfile != -1 && !m_accessToken.isEmpty()) + return "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; + return "-"; + } + + //! Returns the currently selected profile (if none, returns nullptr) + const AccountProfile *currentProfile() const; - /** - * This MojangAccount's username. May be an email address if the account is migrated. - */ - QString username() const; + //! Returns whether the account is NotVerified, Verified or Online + AccountStatus accountStatus() const; +signals: /** - * This MojangAccount's client token. This is a UUID used by Mojang's auth servers to identify this client. - * This is unique for each MojangAccount. + * This signal is emitted when the account changes */ - QString clientToken() const; + void changed(); - /** - * Sets the MojangAccount's client token to the given value. - */ - void setClientToken(const QString &token); + // TODO: better signalling for the various possible state changes - especially errors - /** - * This MojangAccount's access token. - * If the user has not chosen to stay logged in, this will be an empty string. - */ - QString accessToken() const; +protected: /* variables */ + QString m_username; - /** - * Changes this MojangAccount's access token to the given value. - */ - void setAccessToken(const QString &token); + // Used to identify the client - the user can have multiple clients for the same account + // Think: different launchers, all connecting to the same account/profile + QString m_clientToken; - /** - * Get full session ID - */ - QString sessionId() const; + // Blank if not logged in. + QString m_accessToken; - /** - * Returns a list of the available account profiles. - */ - const ProfileList profiles() const; + // Index of the selected profile within the list of available + // profiles. -1 if nothing is selected. + int m_currentProfile = -1; - /** - * Returns a pointer to the currently selected profile. - * If no profile is selected, returns the first profile in the profile list or nullptr if there are none. - */ - const AccountProfile *currentProfile() const; + // List of available profiles. + QList<AccountProfile> m_profiles; - /** - * Sets the currently selected profile to the profile with the given ID string. - * If profileId is not in the list of available profiles, the function will simply return false. - */ - bool setProfile(const QString &profileId); + // the user structure, whatever it is. + User m_user; - /** - * Clears the current account profile list and replaces it with the given profile list. - */ - void loadProfiles(const ProfileList &profiles); + // true when the account is verified + bool m_online = false; -signals: - /** - * This isgnal is emitted whrn the account changes - */ - void changed(); + // current task we are executing here + std::shared_ptr<YggdrasilTask> m_currentTask; -protected: - QString m_username; - QString m_clientToken; - QString m_accessToken; // Blank if not logged in. - int m_currentProfile; // Index of the selected profile within the list of available - // profiles. -1 if nothing is selected. - ProfileList m_profiles; // List of available profiles. - User m_user; // the user structure, whatever it is. +private slots: + void authSucceeded(); + void authFailed(QString reason); + +public: + friend class YggdrasilTask; + friend class AuthenticateTask; + friend class ValidateTask; + friend class RefreshTask; }; |
