diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index f56ef7005..4f2666492 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -141,13 +141,11 @@ endif() if(BROWSER_AVAILABLE_INTERNAL) list(APPEND obs_PLATFORM_SOURCES obf.c - auth-oauth.cpp window-dock-browser.cpp window-extra-browsers.cpp ) list(APPEND obs_PLATFORM_HEADERS obf.h - auth-oauth.hpp window-dock-browser.hpp window-extra-browsers.hpp ) @@ -226,6 +224,8 @@ set(obs_SOURCES window-remux.cpp window-missing-files.cpp auth-base.cpp + auth-oauth.cpp + auth-listener.cpp source-tree.cpp scene-tree.cpp properties-view.cpp @@ -290,6 +290,8 @@ set(obs_HEADERS window-remux.hpp window-missing-files.hpp auth-base.hpp + auth-oauth.hpp + auth-listener.hpp source-tree.hpp scene-tree.hpp properties-view.hpp diff --git a/UI/auth-base.cpp b/UI/auth-base.cpp index 89c30e2c0..8ffc68c53 100644 --- a/UI/auth-base.cpp +++ b/UI/auth-base.cpp @@ -39,6 +39,17 @@ Auth::Type Auth::AuthType(const std::string &service) return Type::None; } +bool Auth::External(const std::string &service) +{ + for (auto &a : authDefs) { + if (service.find(a.def.service) != std::string::npos) { + return a.def.externalOAuth; + } + } + + return false; +} + void Auth::Load() { OBSBasic *main = OBSBasic::Get(); diff --git a/UI/auth-base.hpp b/UI/auth-base.hpp index f683ce1f1..e42d66863 100644 --- a/UI/auth-base.hpp +++ b/UI/auth-base.hpp @@ -27,11 +27,13 @@ public: enum class Type { None, OAuth_StreamKey, + OAuth_LinkedAccount, }; struct Def { std::string service; Type type; + bool externalOAuth; }; typedef std::function()> create_cb; @@ -41,6 +43,7 @@ public: inline Type type() const { return def.type; } inline const char *service() const { return def.service.c_str(); } + inline bool external() const { return def.externalOAuth; } virtual void LoadUI() {} @@ -48,6 +51,7 @@ public: static std::shared_ptr Create(const std::string &service); static Type AuthType(const std::string &service); + static bool External(const std::string &service); static void Load(); static void Save(); diff --git a/UI/auth-listener.cpp b/UI/auth-listener.cpp new file mode 100644 index 000000000..59512fc87 --- /dev/null +++ b/UI/auth-listener.cpp @@ -0,0 +1,86 @@ +#include + +#include +#include +#include +#include + +#include "obs-app.hpp" +#include "qt-wrappers.hpp" + +#define LOGO_URL "https://obsproject.com/assets/images/new_icon_small-r.png" + +static const QString serverResponseHeader = + QStringLiteral("HTTP/1.0 200 OK\n" + "Connection: close\n" + "Content-Type: text/html; charset=UTF-8\n" + "Server: OBS Studio\n" + "\n" + "OBS Studio" + ""); + +static const QString responseTemplate = + "
" + "\"OBS\"" + "
" + "

%1

"; + +AuthListener::AuthListener(QObject *parent) : QObject(parent) +{ + server = new QTcpServer(this); + connect(server, &QTcpServer::newConnection, this, + &AuthListener::NewConnection); + if (!server->listen(QHostAddress::LocalHost, 0)) { + blog(LOG_DEBUG, "Server could not start"); + emit fail(); + } else { + blog(LOG_DEBUG, "Server started at port %d", + server->serverPort()); + } +} + +quint16 AuthListener::GetPort() +{ + return server ? server->serverPort() : 0; +} + +void AuthListener::NewConnection() +{ + QTcpSocket *socket = server->nextPendingConnection(); + if (socket) { + connect(socket, &QTcpSocket::disconnected, socket, + &QTcpSocket::deleteLater); + connect(socket, &QTcpSocket::readyRead, socket, [&, socket]() { + QByteArray buffer; + while (socket->bytesAvailable() > 0) { + buffer.append(socket->readAll()); + } + socket->write(QT_TO_UTF8(serverResponseHeader)); + QString redirect = QString::fromLatin1(buffer); + blog(LOG_DEBUG, "redirect: %s", QT_TO_UTF8(redirect)); + + QRegularExpression re("(&|\\?)code=(?[^&]+)"); + QRegularExpressionMatch match = re.match(redirect); + if (!match.hasMatch()) + blog(LOG_DEBUG, "no 'code' in server redirect"); + + QString code = match.captured("code"); + + if (code.isEmpty()) { + auto data = QTStr("YouTube.Auth.NoCode"); + socket->write(QT_TO_UTF8(data)); + emit fail(); + } else { + auto data = responseTemplate.arg( + QTStr("YouTube.Auth.Ok")); + socket->write(QT_TO_UTF8(data)); + emit ok(code); + } + socket->flush(); + socket->close(); + }); + } else { + emit fail(); + } +} diff --git a/UI/auth-listener.hpp b/UI/auth-listener.hpp new file mode 100644 index 000000000..543743320 --- /dev/null +++ b/UI/auth-listener.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class AuthListener : public QObject { + Q_OBJECT + + QTcpServer *server; + +signals: + void ok(const QString &code); + void fail(); + +protected: + void NewConnection(); + +public: + explicit AuthListener(QObject *parent = 0); + quint16 GetPort(); +}; diff --git a/UI/auth-oauth.cpp b/UI/auth-oauth.cpp index d2c15322e..53361922b 100644 --- a/UI/auth-oauth.cpp +++ b/UI/auth-oauth.cpp @@ -16,15 +16,18 @@ using namespace json11; +#ifdef BROWSER_AVAILABLE #include extern QCef *cef; extern QCefCookieManager *panel_cookies; +#endif /* ------------------------------------------------------------------------- */ OAuthLogin::OAuthLogin(QWidget *parent, const std::string &url, bool token) : QDialog(parent), get_token(token) { +#ifdef BROWSER_AVAILABLE if (!cef) { return; } @@ -61,19 +64,23 @@ OAuthLogin::OAuthLogin(QWidget *parent, const std::string &url, bool token) QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(cefWidget); topLayout->addLayout(bottomLayout); +#endif } OAuthLogin::~OAuthLogin() { +#ifdef BROWSER_AVAILABLE delete cefWidget; +#endif } int OAuthLogin::exec() { +#ifdef BROWSER_AVAILABLE if (cefWidget) { return QDialog::exec(); } - +#endif return QDialog::Rejected; } @@ -174,7 +181,24 @@ bool OAuth::TokenExpired() } bool OAuth::GetToken(const char *url, const std::string &client_id, + const std::string &secret, const std::string &redirect_uri, int scope_ver, const std::string &auth_code, bool retry) +{ + return GetTokenInternal(url, client_id, secret, redirect_uri, scope_ver, + auth_code, retry); +} + +bool OAuth::GetToken(const char *url, const std::string &client_id, + int scope_ver, const std::string &auth_code, bool retry) +{ + return GetTokenInternal(url, client_id, {}, {}, scope_ver, auth_code, + retry); +} + +bool OAuth::GetTokenInternal(const char *url, const std::string &client_id, + const std::string &secret, + const std::string &redirect_uri, int scope_ver, + const std::string &auth_code, bool retry) try { std::string output; std::string error; @@ -199,6 +223,14 @@ try { std::string post_data; post_data += "action=redirect&client_id="; post_data += client_id; + if (!secret.empty()) { + post_data += "&client_secret="; + post_data += secret; + } + if (!redirect_uri.empty()) { + post_data += "&redirect_uri="; + post_data += redirect_uri; + } if (!auth_code.empty()) { post_data += "&grant_type=authorization_code&code="; diff --git a/UI/auth-oauth.hpp b/UI/auth-oauth.hpp index 7421cfedb..b558692b2 100644 --- a/UI/auth-oauth.hpp +++ b/UI/auth-oauth.hpp @@ -64,6 +64,16 @@ protected: int scope_ver, const std::string &auth_code = std::string(), bool retry = false); + bool GetToken(const char *url, const std::string &client_id, + const std::string &secret, + const std::string &redirect_uri, int scope_ver, + const std::string &auth_code, bool retry); + +private: + bool GetTokenInternal(const char *url, const std::string &client_id, + const std::string &secret, + const std::string &redirect_uri, int scope_ver, + const std::string &auth_code, bool retry); }; class OAuthStreamKey : public OAuth { diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index 7dd958de3..2b1d01a0c 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -438,7 +438,8 @@ void AutoConfigStreamPage::OnAuthConnected() std::string service = QT_TO_UTF8(ui->service->currentText()); Auth::Type type = Auth::AuthType(service); - if (type == Auth::Type::OAuth_StreamKey) { + if (type == Auth::Type::OAuth_StreamKey || + type == Auth::Type::OAuth_LinkedAccount) { OnOAuthStreamKeyConnected(); } } diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 6fe94e962..b7e43ee38 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -560,7 +560,8 @@ void OBSBasicSettings::OnAuthConnected() std::string service = QT_TO_UTF8(ui->service->currentText()); Auth::Type type = Auth::AuthType(service); - if (type == Auth::Type::OAuth_StreamKey) { + if (type == Auth::Type::OAuth_StreamKey || + type == Auth::Type::OAuth_LinkedAccount) { OnOAuthStreamKeyConnected(); }