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.
This commit is contained in:
Petr Mrázek
2014-01-27 03:00:49 +01:00
parent d7113de3bd
commit ffbc5bb62c
27 changed files with 417 additions and 278 deletions

View File

@ -0,0 +1,30 @@
#include "AuthSession.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QStringList>
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;
}

49
logic/auth/AuthSession.h Normal file
View File

@ -0,0 +1,49 @@
#pragma once
#include <QString>
#include <QMultiMap>
#include <memory>
struct User
{
QString id;
QMultiMap<QString, QString> 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<AuthSession> AuthSessionPtr;

View File

@ -24,6 +24,7 @@
#include <QJsonArray>
#include <QRegExp>
#include <QStringList>
#include <QJsonDocument>
#include <logger/QsLog.h>
@ -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<YggdrasilTask> MojangAccount::login(QString password)
std::shared_ptr<YggdrasilTask> 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<YggdrasilTask> 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<YggdrasilTask> 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();
}

View File

@ -23,6 +23,7 @@
#include <QMap>
#include <memory>
#include "AuthSession.h"
class Task;
class YggdrasilTask;
@ -45,17 +46,10 @@ struct AccountProfile
bool legacy;
};
struct User
{
QString id;
QMultiMap<QString,QString> 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<YggdrasilTask> login(QString password = QString());
std::shared_ptr<YggdrasilTask> 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<YggdrasilTask> m_currentTask;
private slots:
private
slots:
void authSucceeded();
void authFailed(QString reason);
private:
void fillSession(AuthSessionPtr session);
public:
friend class YggdrasilTask;
friend class AuthenticateTask;

View File

@ -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;
};

View File

@ -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.";

View File

@ -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;
};