From cea0e182c0b24928aae74352355fe552167dcf4c Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 14 Jul 2016 11:02:32 -0700 Subject: [PATCH] Interface login through steam --- interface/resources/images/steam-sign-in.png | Bin 0 -> 5861 bytes interface/resources/qml/LoginDialog.qml | 50 +++-- interface/src/Application.cpp | 7 +- interface/src/main.cpp | 9 +- interface/src/ui/LoginDialog.cpp | 15 ++ interface/src/ui/LoginDialog.h | 1 + libraries/networking/src/AccountManager.cpp | 23 +++ libraries/networking/src/AccountManager.h | 1 + .../src/steamworks-wrapper/SteamClient.cpp | 171 ++++++++++++++++++ .../src/steamworks-wrapper/SteamClient.h | 15 +- 10 files changed, 275 insertions(+), 17 deletions(-) create mode 100644 interface/resources/images/steam-sign-in.png diff --git a/interface/resources/images/steam-sign-in.png b/interface/resources/images/steam-sign-in.png new file mode 100644 index 0000000000000000000000000000000000000000..148e6ab280472d5b4b3f069a2c267790e19d13c2 GIT binary patch literal 5861 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 #include #include +#include #include #include #include #include #include -#include +#include #include #include -#include +#include #include #include #include @@ -2910,6 +2911,8 @@ void Application::idle(float nsecsElapsed) { PROFILE_RANGE(__FUNCTION__); + SteamClient::runCallbacks(); + float secondsSinceLastUpdate = nsecsElapsed / NSECS_PER_MSEC / MSECS_PER_SECOND; // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus. diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 8fc0384aee..527b7f2331 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -8,6 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include #include @@ -20,12 +22,13 @@ #include #include +#include + #include "AddressManager.h" #include "Application.h" #include "InterfaceLogging.h" #include "UserActivityLogger.h" #include "MainWindow.h" -#include #ifdef HAS_BUGSPLAT #include @@ -137,6 +140,8 @@ int main(int argc, const char* argv[]) { // or in the main window ctor, before GL startup. Application::initPlugins(arguments); + SteamClient::init(); + int exitCode; { QSettings::setDefaultFormat(QSettings::IniFormat); @@ -202,6 +207,8 @@ int main(int argc, const char* argv[]) { Application::shutdownPlugins(); + SteamClient::shutdown(); + qCDebug(interfaceapp, "Normal exit."); #if !defined(DEBUG) && !defined(Q_OS_LINUX) // HACK: exit immediately (don't handle shutdown callbacks) for Release build diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 80d52b7a07..8240340381 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "AccountManager.h" #include "DependencyManager.h" @@ -82,6 +83,20 @@ void LoginDialog::login(const QString& username, const QString& password) { DependencyManager::get()->requestAccessToken(username, password); } +void LoginDialog::loginThroughSteam() { + qDebug() << "Attempting to login through Steam"; + setStatusText("Logging in..."); + + SteamClient::requestTicket([this](Ticket ticket) { + if (ticket.isNull()) { + setStatusText("Steam client not logged into an account"); + return; + } + + DependencyManager::get()->requestAccessTokenWithSteam(ticket); + }); +} + void LoginDialog::openUrl(const QString& url) { qDebug() << url; QDesktopServices::openUrl(url); diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 25ecf45898..0dd4b5e96f 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -41,6 +41,7 @@ protected: void handleLoginFailed(); Q_INVOKABLE void login(const QString& username, const QString& password); + Q_INVOKABLE void loginThroughSteam(); Q_INVOKABLE void openUrl(const QString& url); private: QString _statusText; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index c4bfae7cac..8c0fa5ed92 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -505,6 +505,29 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } +void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest request; + request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + + QUrl grantURL = _authURL; + grantURL.setPath("/oauth/token"); + + const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; + + QByteArray postData; + postData.append("grant_type=password&"); + postData.append("ticket=" + QUrl::toPercentEncoding(authSessionTicket) + "&"); + postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); + + request.setUrl(grantURL); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply* requestReply = networkAccessManager.post(request, postData); + connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); + connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); +} void AccountManager::requestAccessTokenFinished() { QNetworkReply* requestReply = reinterpret_cast(sender()); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 846cdb6220..eb4d224501 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -96,6 +96,7 @@ public: public slots: void requestAccessToken(const QString& login, const QString& password); + void requestAccessTokenWithSteam(QByteArray authSessionTicket); void requestAccessTokenFinished(); void requestProfileFinished(); diff --git a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp index 0f06e03672..1dbfc0ce00 100644 --- a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp +++ b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.cpp @@ -11,9 +11,180 @@ #include "SteamClient.h" +#include +#include + +#include + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Woverloaded-virtual" +#endif + #include +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + + +static const Ticket INVALID_TICKET = Ticket(); + +class SteamTicketRequests { +public: + SteamTicketRequests(); + ~SteamTicketRequests(); + + HAuthTicket startRequest(TicketRequestCallback callback); + void stopRequest(HAuthTicket authTicket); + + STEAM_CALLBACK(SteamTicketRequests, onGetAuthSessionTicketResponse, + GetAuthSessionTicketResponse_t, _getAuthSessionTicketResponse); + +private: + void stopAll(); + + struct PendingTicket { + HAuthTicket authTicket; + Ticket ticket; + TicketRequestCallback callback; + }; + + std::vector _pendingTickets; +}; + +SteamTicketRequests::SteamTicketRequests() : + _getAuthSessionTicketResponse(this, &SteamTicketRequests::onGetAuthSessionTicketResponse) +{ +} + +SteamTicketRequests::~SteamTicketRequests() { + stopAll(); +} + +HAuthTicket SteamTicketRequests::startRequest(TicketRequestCallback callback) { + static const uint32 MAX_TICKET_SIZE { 1024 }; + uint32 ticketSize { 0 }; + char ticket[MAX_TICKET_SIZE]; + + auto authTicket = SteamUser()->GetAuthSessionTicket(ticket, MAX_TICKET_SIZE, &ticketSize); + qDebug() << "Got Steam auth session ticket:" << authTicket; + + if (authTicket == k_HAuthTicketInvalid) { + qWarning() << "Auth session ticket is invalid."; + callback(INVALID_TICKET); + } else { + PendingTicket pendingTicket{ authTicket, QByteArray(ticket, ticketSize).toHex(), callback }; + _pendingTickets.push_back(pendingTicket); + } + + return authTicket; +} + +void SteamTicketRequests::stopRequest(HAuthTicket authTicket) { + auto it = std::find_if(_pendingTickets.begin(), _pendingTickets.end(), [&authTicket](const PendingTicket& pendingTicket) { + return pendingTicket.authTicket == authTicket; + }); + + if (it != _pendingTickets.end()) { + SteamUser()->CancelAuthTicket(it->authTicket); + it->callback(INVALID_TICKET); + _pendingTickets.erase(it); + } +} + +void SteamTicketRequests::stopAll() { + auto steamUser = SteamUser(); + if (steamUser) { + for (const auto& pendingTicket : _pendingTickets) { + steamUser->CancelAuthTicket(pendingTicket.authTicket); + pendingTicket.callback(INVALID_TICKET); + } + } + _pendingTickets.clear(); +} + +void SteamTicketRequests::onGetAuthSessionTicketResponse(GetAuthSessionTicketResponse_t* pCallback) { + auto authTicket = pCallback->m_hAuthTicket; + + auto it = std::find_if(_pendingTickets.begin(), _pendingTickets.end(), [&authTicket](const PendingTicket& pendingTicket) { + return pendingTicket.authTicket == authTicket; + }); + + + if (it != _pendingTickets.end()) { + + if (pCallback->m_eResult == k_EResultOK) { + qDebug() << "Got steam callback, auth session ticket is valid. Send it." << authTicket; + it->callback(it->ticket); + } else { + qWarning() << "Steam auth session ticket callback encountered an error:" << pCallback->m_eResult; + it->callback(INVALID_TICKET); + } + + _pendingTickets.erase(it); + } else { + qWarning() << "Could not find steam auth session ticket in list of pending tickets:" << authTicket; + } +} +static std::atomic_bool initialized { false }; +static std::unique_ptr steamTicketRequests; + +bool SteamClient::init() { + if (!initialized) { + initialized = SteamAPI_Init(); + } + + if (!steamTicketRequests && initialized) { + steamTicketRequests.reset(new SteamTicketRequests()); + } + + return initialized; +} + +void SteamClient::shutdown() { + if (initialized) { + SteamAPI_Shutdown(); + } + + if (steamTicketRequests) { + steamTicketRequests.reset(); + } +} + +void SteamClient::runCallbacks() { + if (!initialized) { + init(); + } + + if (!initialized) { + qDebug() << "Steam not initialized"; + return; + } + + auto steamPipe = SteamAPI_GetHSteamPipe(); + if (!steamPipe) { + qDebug() << "Could not get SteamPipe"; + return; + } + + Steam_RunCallbacks(steamPipe, false); +} + +void SteamClient::requestTicket(TicketRequestCallback callback) { + if (!initialized) { + init(); + } + + if (!initialized) { + qDebug() << "Steam not initialized"; + return; + } + + steamTicketRequests->startRequest(callback); +} + diff --git a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h index 369641b0c7..ac5c648ead 100644 --- a/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h +++ b/libraries/steamworks-wrapper/src/steamworks-wrapper/SteamClient.h @@ -13,8 +13,21 @@ #ifndef hifi_SteamClient_h #define hifi_SteamClient_h -class SteamClient { +#include +#include + +using Ticket = QByteArray; +using TicketRequestCallback = std::function; + +class SteamClient { +public: + static bool init(); + static void shutdown(); + + static void runCallbacks(); + + static void requestTicket(TicketRequestCallback callback); };