From ffbc5bb62c0cd771b26cb1d9b5afdccef77075b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 27 Jan 2014 03:00:49 +0100 Subject: Offline mode can be used even when online. Allow the user to pick a player name for offline mode. Big auth refactor. Now using session objects instead of the accounts themselves. Sessions only last for one instance start and hold all the auth and player data. --- logic/auth/AuthSession.cpp | 30 +++++++++++++++ logic/auth/AuthSession.h | 49 ++++++++++++++++++++++++ logic/auth/MojangAccount.cpp | 82 ++++++++++++++++++++++++++++++++++++---- logic/auth/MojangAccount.h | 38 ++++++------------- logic/auth/YggdrasilTask.h | 17 +++++++++ logic/auth/flows/RefreshTask.cpp | 4 +- logic/auth/flows/RefreshTask.h | 3 +- 7 files changed, 184 insertions(+), 39 deletions(-) create mode 100644 logic/auth/AuthSession.cpp create mode 100644 logic/auth/AuthSession.h (limited to 'logic/auth') diff --git a/logic/auth/AuthSession.cpp b/logic/auth/AuthSession.cpp new file mode 100644 index 00000000..8758bfbd --- /dev/null +++ b/logic/auth/AuthSession.cpp @@ -0,0 +1,30 @@ +#include "AuthSession.h" +#include +#include +#include +#include + +QString AuthSession::serializeUserProperties() +{ + QJsonObject userAttrs; + for (auto key : u.properties.keys()) + { + auto array = QJsonArray::fromStringList(u.properties.values(key)); + userAttrs.insert(key, array); + } + QJsonDocument value(userAttrs); + return value.toJson(QJsonDocument::Compact); + +} + +bool AuthSession::MakeOffline(QString offline_playername) +{ + if (status != PlayableOffline && status != PlayableOnline) + { + return false; + } + session = "-"; + player_name = offline_playername; + status = PlayableOffline; + return true; +} diff --git a/logic/auth/AuthSession.h b/logic/auth/AuthSession.h new file mode 100644 index 00000000..2ac170fa --- /dev/null +++ b/logic/auth/AuthSession.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +struct User +{ + QString id; + QMultiMap properties; +}; + +struct AuthSession +{ + bool MakeOffline(QString offline_playername); + + QString serializeUserProperties(); + + enum Status + { + Undetermined, + RequiresPassword, + PlayableOffline, + PlayableOnline + } status = Undetermined; + + User u; + + // client token + QString client_token; + // account user name + QString username; + // combined session ID + QString session; + // volatile auth token + QString access_token; + // profile name + QString player_name; + // profile ID + QString uuid; + // 'legacy' or 'mojang', depending on account type + QString user_type; + // Did the auth server reply? + bool auth_server_online = false; + // Did the user request online mode? + bool wants_online = true; +}; + +typedef std::shared_ptr AuthSessionPtr; diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp index f41985ec..6c937ef1 100644 --- a/logic/auth/MojangAccount.cpp +++ b/logic/auth/MojangAccount.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -165,15 +166,26 @@ AccountStatus MojangAccount::accountStatus() const { if (m_accessToken.isEmpty()) return NotVerified; - if (!m_online) + else return Verified; - return Online; } -std::shared_ptr MojangAccount::login(QString password) +std::shared_ptr MojangAccount::login(AuthSessionPtr session, + QString password) { - if (m_currentTask) - return m_currentTask; + Q_ASSERT(m_currentTask.get() == nullptr); + + // take care of the true offline status + if (accountStatus() == NotVerified && password.isEmpty()) + { + if (session) + { + session->status = AuthSession::RequiresPassword; + fillSession(session); + } + return nullptr; + } + if (password.isEmpty()) { m_currentTask.reset(new RefreshTask(this)); @@ -182,6 +194,8 @@ std::shared_ptr MojangAccount::login(QString password) { m_currentTask.reset(new AuthenticateTask(this, password)); } + m_currentTask->assignSession(session); + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); return m_currentTask; @@ -189,24 +203,76 @@ std::shared_ptr MojangAccount::login(QString password) void MojangAccount::authSucceeded() { - m_online = true; + auto session = m_currentTask->getAssignedSession(); + if (session) + { + session->status = + session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline; + fillSession(session); + session->auth_server_online = true; + } m_currentTask.reset(); emit changed(); } void MojangAccount::authFailed(QString reason) { + auto session = m_currentTask->getAssignedSession(); // This is emitted when the yggdrasil tasks time out or are cancelled. // -> we treat the error as no-op if (reason == "Yggdrasil task cancelled.") { - // do nothing + if (session) + { + session->status = accountStatus() == Verified ? AuthSession::PlayableOffline + : AuthSession::RequiresPassword; + session->auth_server_online = false; + fillSession(session); + } } else { - m_online = false; m_accessToken = QString(); emit changed(); + if (session) + { + session->status = AuthSession::RequiresPassword; + session->auth_server_online = true; + fillSession(session); + } } m_currentTask.reset(); } + +void MojangAccount::fillSession(AuthSessionPtr session) +{ + // the user name. you have to have an user name + session->username = m_username; + // volatile auth token + session->access_token = m_accessToken; + // the semi-permanent client token + session->client_token = m_clientToken; + if (currentProfile()) + { + // profile name + session->player_name = currentProfile()->name; + // profile ID + session->uuid = currentProfile()->id; + // 'legacy' or 'mojang', depending on account type + session->user_type = currentProfile()->legacy ? "legacy" : "mojang"; + if (!session->access_token.isEmpty()) + { + session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; + } + else + { + session->session = "-"; + } + } + else + { + session->player_name = "Player"; + session->session = "-"; + } + session->u = user(); +} diff --git a/logic/auth/MojangAccount.h b/logic/auth/MojangAccount.h index dd5d54ae..a0565e2c 100644 --- a/logic/auth/MojangAccount.h +++ b/logic/auth/MojangAccount.h @@ -23,6 +23,7 @@ #include #include +#include "AuthSession.h" class Task; class YggdrasilTask; @@ -45,17 +46,10 @@ struct AccountProfile bool legacy; }; -struct User -{ - QString id; - QMultiMap properties; -}; - enum AccountStatus { NotVerified, - Verified, - Online + Verified }; /** @@ -84,7 +78,7 @@ public: /* construction */ QJsonObject saveToJson() const; public: /* manipulation */ - /** + /** * 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. @@ -95,12 +89,9 @@ public: /* manipulation */ * Attempt to login. Empty password means we use the token. * If the attempt fails because we already are performing some task, it returns false. */ - std::shared_ptr login(QString password = QString()); + std::shared_ptr login(AuthSessionPtr session, + QString password = QString()); - void downgrade() - { - m_online = false; - } public: /* queries */ const QString &username() const { @@ -122,19 +113,11 @@ public: /* queries */ return m_profiles; } - const User & user() + const User &user() { return m_user; } - //! 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; @@ -169,16 +152,17 @@ protected: /* variables */ // the user structure, whatever it is. User m_user; - // true when the account is verified - bool m_online = false; - // current task we are executing here std::shared_ptr m_currentTask; -private slots: +private +slots: void authSucceeded(); void authFailed(QString reason); +private: + void fillSession(AuthSessionPtr session); + public: friend class YggdrasilTask; friend class AuthenticateTask; diff --git a/logic/auth/YggdrasilTask.h b/logic/auth/YggdrasilTask.h index 85f5a1e1..4a87067a 100644 --- a/logic/auth/YggdrasilTask.h +++ b/logic/auth/YggdrasilTask.h @@ -35,6 +35,21 @@ class YggdrasilTask : public Task public: explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); + /** + * assign a session to this task. the session will be filled with required infomration + * upon completion + */ + void assignSession(AuthSessionPtr session) + { + m_session = session; + } + + /// get the assigned session for filling with information. + AuthSessionPtr getAssignedSession() + { + return m_session; + } + /** * Class describing a Yggdrasil error response. */ @@ -117,4 +132,6 @@ protected: const int timeout_max = 10000; const int time_step = 50; + + AuthSessionPtr m_session; }; diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp index f63c736e..5a55ed91 100644 --- a/logic/auth/flows/RefreshTask.cpp +++ b/logic/auth/flows/RefreshTask.cpp @@ -25,8 +25,7 @@ #include "logger/QsLog.h" -RefreshTask::RefreshTask(MojangAccount *account, QObject *parent) - : YggdrasilTask(account, parent) +RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account) { } @@ -126,7 +125,6 @@ bool RefreshTask::processResponse(QJsonObject responseData) m_account->m_user = u; } - // We've made it through the minefield of possible errors. Return true to indicate that // we've succeeded. QLOG_DEBUG() << "Finished reading refresh response."; diff --git a/logic/auth/flows/RefreshTask.h b/logic/auth/flows/RefreshTask.h index 2fd50c60..0dadc025 100644 --- a/logic/auth/flows/RefreshTask.h +++ b/logic/auth/flows/RefreshTask.h @@ -30,7 +30,7 @@ class RefreshTask : public YggdrasilTask { Q_OBJECT public: - RefreshTask(MojangAccount * account, QObject *parent = 0); + RefreshTask(MojangAccount * account); protected: virtual QJsonObject getRequestContent() const; @@ -41,3 +41,4 @@ protected: QString getStateMessage(const YggdrasilTask::State state) const; }; + -- cgit v1.2.3