Skip to content

Commit

Permalink
Merge pull request #657 from Palakis/feature/authentication-enabled-b…
Browse files Browse the repository at this point in the history
…y-default

Config: authentication enabled by default
  • Loading branch information
tt2468 authored Feb 4, 2021
2 parents 73d93e4 + e39585b commit 1891f62
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 63 deletions.
4 changes: 4 additions & 0 deletions data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ OBSWebsocket.Settings.Password="Password"
OBSWebsocket.Settings.LockToIPv4="Lock server to only using IPv4"
OBSWebsocket.Settings.DebugEnable="Enable debug logging"
OBSWebsocket.Settings.AlertsEnable="Enable System Tray Alerts"
OBSWebsocket.Settings.AuthDisabledWarning="Running obs-websocket with authentication disabled is not recommended, as it allows attackers to easily collect sensetive data. Are you sure you want to proceed?"
OBSWebsocket.NotifyConnect.Title="New WebSocket connection"
OBSWebsocket.NotifyConnect.Message="Client %1 connected"
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
Expand All @@ -15,3 +16,6 @@ OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start,
OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started."
OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped."
OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted."
OBSWebsocket.InitialPasswordSetup.Title="obs-websocket - Server Password Configuration"
OBSWebsocket.InitialPasswordSetup.Text="It looks like you are running obs-websocket for the first time. Do you want to configure a password now for the WebSockets server? Setting a password is highly recommended."
OBSWebsocket.InitialPasswordSetup.DismissedText="You can configure a server password later in the WebSockets Server Settings. (Under the Tools menu of OBS Studio)"
2 changes: 2 additions & 0 deletions docs/partials/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Messages are exchanged between the client and the server as JSON objects.
This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio. As of v5.0.0, backwards compatability with the protocol will not be kept.

# Authentication
**Starting with obs-websocket 4.9, authentication is enabled by default and users are encouraged to configure a password on first run.**

`obs-websocket` uses SHA256 to transmit credentials.

A request for [`GetAuthRequired`](#getauthrequired) returns two elements:
Expand Down
156 changes: 102 additions & 54 deletions src/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>

#include <obs-frontend-api.h>

#include <QtCore/QObject>
#include <QtCore/QCryptographicHash>
#include <QtCore/QTime>
#include <QtWidgets/QSystemTrayIcon>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>

#define SECTION_NAME "WebsocketAPI"
#define PARAM_ENABLE "ServerEnabled"
Expand All @@ -32,6 +36,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define PARAM_SECRET "AuthSecret"
#define PARAM_SALT "AuthSalt"

#define GLOBAL_AUTH_SETUP_PROMPTED "AuthSetupPrompted"

#include "Utils.h"
#include "WSServer.h"

Expand All @@ -45,7 +51,7 @@ Config::Config() :
LockToIPv4(false),
DebugEnabled(false),
AlertsEnabled(true),
AuthRequired(false),
AuthRequired(true),
Secret(""),
Salt(""),
SettingsLoaded(false)
Expand Down Expand Up @@ -130,6 +136,70 @@ config_t* Config::GetConfigStore()
return obs_frontend_get_profile_config();
}

void Config::MigrateFromGlobalSettings()
{
config_t* source = obs_frontend_get_global_config();
config_t* destination = obs_frontend_get_profile_config();

if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE);
config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value);

config_remove_value(source, SECTION_NAME, PARAM_ENABLE);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) {
uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT);
config_set_uint(destination, SECTION_NAME, PARAM_PORT, value);

config_remove_value(source, SECTION_NAME, PARAM_PORT);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_LOCKTOIPV4)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_LOCKTOIPV4);
config_set_bool(destination, SECTION_NAME, PARAM_LOCKTOIPV4, value);

config_remove_value(source, SECTION_NAME, PARAM_LOCKTOIPV4);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG);
config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value);

config_remove_value(source, SECTION_NAME, PARAM_DEBUG);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT);
config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value);

config_remove_value(source, SECTION_NAME, PARAM_ALERT);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED);
config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value);

config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) {
const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET);
config_set_string(destination, SECTION_NAME, PARAM_SECRET, value);

config_remove_value(source, SECTION_NAME, PARAM_SECRET);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) {
const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT);
config_set_string(destination, SECTION_NAME, PARAM_SALT, value);

config_remove_value(source, SECTION_NAME, PARAM_SALT);
}

config_save(destination);
}

QString Config::GenerateSalt()
{
// Generate 32 random chars
Expand Down Expand Up @@ -233,68 +303,46 @@ void Config::OnFrontendEvent(enum obs_frontend_event event, void* param)
}
}
}
else if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
FirstRunPasswordSetup();
}
}

void Config::MigrateFromGlobalSettings()
void Config::FirstRunPasswordSetup()
{
config_t* source = obs_frontend_get_global_config();
config_t* destination = obs_frontend_get_profile_config();

if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE);
config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value);

config_remove_value(source, SECTION_NAME, PARAM_ENABLE);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) {
uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT);
config_set_uint(destination, SECTION_NAME, PARAM_PORT, value);

config_remove_value(source, SECTION_NAME, PARAM_PORT);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_LOCKTOIPV4)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_LOCKTOIPV4);
config_set_bool(destination, SECTION_NAME, PARAM_LOCKTOIPV4, value);

config_remove_value(source, SECTION_NAME, PARAM_LOCKTOIPV4);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG);
config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value);

config_remove_value(source, SECTION_NAME, PARAM_DEBUG);
// check if we already showed the auth setup prompt to the user, independently of the current settings (tied to the current profile)
config_t* globalConfig = obs_frontend_get_global_config();
bool alreadyPrompted = config_get_bool(globalConfig, SECTION_NAME, GLOBAL_AUTH_SETUP_PROMPTED);
if (alreadyPrompted) {
return;
}

if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT);
config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value);

config_remove_value(source, SECTION_NAME, PARAM_ALERT);
}

if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED);
config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value);
// lift the flag up and save it
config_set_bool(globalConfig, SECTION_NAME, GLOBAL_AUTH_SETUP_PROMPTED, true);
config_save(globalConfig);

config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED);
// check if the password is already set
auto config = GetConfig();
if (!(config->Secret.isEmpty()) && !(config->Salt.isEmpty())) {
return;
}

if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) {
const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET);
config_set_string(destination, SECTION_NAME, PARAM_SECRET, value);
obs_frontend_push_ui_translation(obs_module_get_string);
QString dialogTitle = QObject::tr("OBSWebsocket.InitialPasswordSetup.Title");
QString dialogText = QObject::tr("OBSWebsocket.InitialPasswordSetup.Text");
QString dismissedText = QObject::tr("OBSWebsocket.InitialPasswordSetup.DismissedText");
obs_frontend_pop_ui_translation();

config_remove_value(source, SECTION_NAME, PARAM_SECRET);
auto mainWindow = reinterpret_cast<QMainWindow*>(
obs_frontend_get_main_window()
);

QMessageBox::StandardButton response = QMessageBox::question(mainWindow, dialogTitle, dialogText);
if (response == QMessageBox::Yes) {
ShowPasswordSetting();
}

if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) {
const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT);
config_set_string(destination, SECTION_NAME, PARAM_SALT, value);

config_remove_value(source, SECTION_NAME, PARAM_SALT);
else {
// tell the user they still can set the password later in our settings dialog
QMessageBox::information(mainWindow, dialogTitle, dismissedText);
}

config_save(destination);
}
1 change: 1 addition & 0 deletions src/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ class Config {

private:
static void OnFrontendEvent(enum obs_frontend_event event, void* param);
static void FirstRunPasswordSetup();
};
38 changes: 31 additions & 7 deletions src/forms/settings-dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/

#include "settings-dialog.h"

#include <obs-frontend-api.h>
#include <obs-module.h>
#include <QtWidgets/QMessageBox>

#include "../obs-websocket.h"
#include "../Config.h"
#include "../WSServer.h"
#include "settings-dialog.h"


#define CHANGE_ME "changeme"

Expand All @@ -35,9 +39,6 @@ SettingsDialog::SettingsDialog(QWidget* parent) :
this, &SettingsDialog::AuthCheckboxChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted,
this, &SettingsDialog::FormAccepted);


AuthCheckboxChanged();
}

void SettingsDialog::showEvent(QShowEvent* event) {
Expand All @@ -50,8 +51,12 @@ void SettingsDialog::showEvent(QShowEvent* event) {
ui->debugEnabled->setChecked(conf->DebugEnabled);
ui->alertsEnabled->setChecked(conf->AlertsEnabled);

ui->authRequired->blockSignals(true);
ui->authRequired->setChecked(conf->AuthRequired);
ui->authRequired->blockSignals(false);

ui->password->setText(CHANGE_ME);
ui->password->setEnabled(ui->authRequired->isChecked());
}

void SettingsDialog::ToggleShowHide() {
Expand All @@ -61,11 +66,30 @@ void SettingsDialog::ToggleShowHide() {
setVisible(false);
}

void SettingsDialog::PreparePasswordEntry() {
ui->authRequired->blockSignals(true);
ui->authRequired->setChecked(true);
ui->authRequired->blockSignals(false);
ui->password->setEnabled(true);
ui->password->setFocus();
}

void SettingsDialog::AuthCheckboxChanged() {
if (ui->authRequired->isChecked())
if (ui->authRequired->isChecked()) {
ui->password->setEnabled(true);
else
ui->password->setEnabled(false);
}
else {
obs_frontend_push_ui_translation(obs_module_get_string);
QString authDisabledWarning = QObject::tr("OBSWebsocket.Settings.AuthDisabledWarning");
obs_frontend_pop_ui_translation();

QMessageBox::StandardButton response = QMessageBox::question(this, "obs-websocket", authDisabledWarning);
if (response == QMessageBox::Yes) {
ui->password->setEnabled(false);
} else {
ui->authRequired->setChecked(true);
}
}
}

void SettingsDialog::FormAccepted() {
Expand Down
1 change: 1 addition & 0 deletions src/forms/settings-dialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class SettingsDialog : public QDialog
~SettingsDialog();
void showEvent(QShowEvent* event);
void ToggleShowHide();
void PreparePasswordEntry();

private Q_SLOTS:
void AuthCheckboxChanged();
Expand Down
12 changes: 10 additions & 2 deletions src/obs-websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
ConfigPtr _config;
WSServerPtr _server;
WSEventsPtr _eventsSystem;
SettingsDialog* settingsDialog = nullptr;

bool obs_module_load(void) {
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
Expand All @@ -64,14 +65,14 @@ bool obs_module_load(void) {
// UI setup
obs_frontend_push_ui_translation(obs_module_get_string);
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
SettingsDialog* settingsDialog = new SettingsDialog(mainWindow);
settingsDialog = new SettingsDialog(mainWindow);
obs_frontend_pop_ui_translation();

const char* menuActionText =
obs_module_text("OBSWebsocket.Settings.DialogTitle");
QAction* menuAction =
(QAction*)obs_frontend_add_tools_menu_qaction(menuActionText);
QObject::connect(menuAction, &QAction::triggered, [settingsDialog] {
QObject::connect(menuAction, &QAction::triggered, [] {
// The settings dialog belongs to the main window. Should be ok
// to pass the pointer to this QAction belonging to the main window
settingsDialog->ToggleShowHide();
Expand Down Expand Up @@ -115,3 +116,10 @@ WSServerPtr GetServer() {
WSEventsPtr GetEventsSystem() {
return _eventsSystem;
}

void ShowPasswordSetting() {
if (settingsDialog) {
settingsDialog->PreparePasswordEntry();
settingsDialog->setVisible(true);
}
}
1 change: 1 addition & 0 deletions src/obs-websocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ typedef std::shared_ptr<WSEvents> WSEventsPtr;
ConfigPtr GetConfig();
WSServerPtr GetServer();
WSEventsPtr GetEventsSystem();
void ShowPasswordSetting();

#define OBS_WEBSOCKET_VERSION "4.8.0"

Expand Down

0 comments on commit 1891f62

Please sign in to comment.