Explorar el Código

UI: Add support for external browser OAuth

(Jim note: Adds abstraction to the OAuth class to allow the ability to
perform OAuth via external browser, and adds an AuthListener to act as
the local auth server.)
Yuriy Chumak hace 4 años
padre
commit
0654675f32

+ 4 - 2
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

+ 11 - 0
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();

+ 4 - 0
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<std::shared_ptr<Auth>()> 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<Auth> 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();
 

+ 86 - 0
UI/auth-listener.cpp

@@ -0,0 +1,86 @@
+#include <auth-listener.hpp>
+
+#include <QRegularExpression>
+#include <QRegularExpressionMatch>
+#include <QString>
+#include <QtNetwork/QTcpSocket>
+
+#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"
+		       "<html><head><title>OBS Studio"
+		       "</title></head>");
+
+static const QString responseTemplate =
+	"<center>"
+	"<img src=\"" LOGO_URL
+	"\" alt=\"OBS\" class=\"center\"  height=\"60\" width=\"60\">"
+	"</center>"
+	"<center><p style=\"font-family:verdana; font-size:13pt\">%1</p></center>";
+
+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=(?<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();
+	}
+}

+ 21 - 0
UI/auth-listener.hpp

@@ -0,0 +1,21 @@
+#pragma once
+
+#include <QObject>
+#include <QtNetwork/QTcpServer>
+
+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();
+};

+ 33 - 1
UI/auth-oauth.cpp

@@ -16,15 +16,18 @@
 
 using namespace json11;
 
+#ifdef BROWSER_AVAILABLE
 #include <browser-panel.hpp>
 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;
 }
 
@@ -173,8 +180,25 @@ bool OAuth::TokenExpired()
 	return false;
 }
 
+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=";

+ 10 - 0
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 {

+ 2 - 1
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();
 	}
 }

+ 2 - 1
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();
 	}