From 6ae3e4a14196e9054c8249f8dbdb3a8b559013b4 Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 22 Jun 2023 05:15:11 +0200 Subject: [PATCH 01/11] libobs: Add Windows keychain API implementation --- .../sphinx/reference-libobs-util-platform.rst | 31 +++++ libobs/cmake/legacy.cmake | 2 +- libobs/cmake/os-windows.cmake | 1 + libobs/util/platform-windows.c | 106 ++++++++++++++++++ libobs/util/platform.h | 6 + 5 files changed, 145 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/reference-libobs-util-platform.rst b/docs/sphinx/reference-libobs-util-platform.rst index 3584e282e3456c..a3d40752a3b901 100644 --- a/docs/sphinx/reference-libobs-util-platform.rst +++ b/docs/sphinx/reference-libobs-util-platform.rst @@ -505,3 +505,34 @@ Other Functions Must be freed with :c:func:`bfree()`. .. versionadded:: 29.1 + +--------------------- + +.. function:: bool *os_keychain_available(void) + + Indicates whether or not the keychain APIs are implemented on this platform. + + On Windows/macOS this will always return `true` and the keychain is guaranteed to be available. + On Linux it will return `true` if OBS is compiled with libsecret, but keychain operations may still fail if no Secret Service (e.g. kwaller or gnome-keyring) is available. + +--------------------- + +.. function:: bool os_keychain_save(const char *label, const char *key, const char *data) + + Saves the string `data` into the OS keychain as key `key` with user-visible name `label`. + + `label` should be a short descriptor of the kind of data being saved (e.g. "OBS Studio OAuth Credentials"), must not be translated, and must be identical when attempting to save/load/delete the same `key`. + +--------------------- + +.. function:: bool os_keychain_load(const char *label, const char *key, char **data) + + Attempt to read the string saved with key `key` in and with label `label` from the keychain. + + If successful, `data´ must be freed with :c:func:`bfree()`. + +--------------------- + +.. function:: bool os_keychain_delete(const char *label, const char *key) + + Deletes an item from the keychain. diff --git a/libobs/cmake/legacy.cmake b/libobs/cmake/legacy.cmake index e8458591c4f2f9..3bedda97102d4a 100644 --- a/libobs/cmake/legacy.cmake +++ b/libobs/cmake/legacy.cmake @@ -316,7 +316,7 @@ if(OS_WINDOWS) target_compile_definitions(libobs PRIVATE UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_WARNINGS) set_source_files_properties(obs-win-crash-handler.c PROPERTIES COMPILE_DEFINITIONS OBS_VERSION="${OBS_VERSION_CANONICAL}") - target_link_libraries(libobs PRIVATE dxgi Avrt Dwmapi winmm Rpcrt4) + target_link_libraries(libobs PRIVATE dxgi Advapi32 Avrt Dwmapi winmm Rpcrt4) if(MSVC) target_link_libraries(libobs PUBLIC OBS::w32-pthreads) diff --git a/libobs/cmake/os-windows.cmake b/libobs/cmake/os-windows.cmake index 04500d1b04cf8c..ecf0e9b96ebc5d 100644 --- a/libobs/cmake/os-windows.cmake +++ b/libobs/cmake/os-windows.cmake @@ -46,6 +46,7 @@ set_source_files_properties(obs-win-crash-handler.c PROPERTIES COMPILE_DEFINITIO target_link_libraries( libobs PRIVATE Avrt + Advapi32 Dwmapi Dxgi winmm diff --git a/libobs/util/platform-windows.c b/libobs/util/platform-windows.c index 3a6706c3635b74..0c00a928201ec0 100644 --- a/libobs/util/platform-windows.c +++ b/libobs/util/platform-windows.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "base.h" #include "platform.h" @@ -1498,3 +1499,108 @@ char *os_generate_uuid(void) return uuid_str.array; } + +bool os_keychain_available(void) +{ + return true; +} + +bool os_keychain_save(const char *label, const char *key, const char *data) +{ + if (!label || !key || !data) + return false; + + struct dstr uuid_str = {0}; + dstr_printf(&uuid_str, "%s::%s", label, key); + + wchar_t *target_name = NULL; + os_utf8_to_wcs_ptr(uuid_str.array, 0, &target_name); + dstr_free(&uuid_str); + + if (!target_name) + return false; + + size_t size = strlen(data); + + CREDENTIAL cred = {0}; + cred.CredentialBlob = (LPBYTE)data; + cred.CredentialBlobSize = (DWORD)size; + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + cred.TargetName = target_name; + cred.Type = CRED_TYPE_GENERIC; + + bool success = CredWriteW(&cred, 0); + if (!success) { + blog(LOG_ERROR, + "Keychain item \"%s::%s\" could not be saved: %d", label, + key, GetLastError()); + } + + bfree(target_name); + + return success; +} + +bool os_keychain_load(const char *label, const char *key, char **data) +{ + if (!label || !key || !data) + return false; + + struct dstr uuid_str = {0}; + dstr_printf(&uuid_str, "%s::%s", label, key); + + wchar_t *target_name = NULL; + os_utf8_to_wcs_ptr(uuid_str.array, 0, &target_name); + dstr_free(&uuid_str); + + if (!target_name) + return false; + + PCREDENTIALW cred; + bool success = CredReadW(target_name, CRED_TYPE_GENERIC, 0, &cred); + if (success) { + *data = bstrdup_n((const char *)cred->CredentialBlob, + cred->CredentialBlobSize); + CredFree(cred); + } else { + blog(LOG_ERROR, + "Keychain item \"%s::%s\" could not be read: %d", label, + key, GetLastError()); + } + + bfree(target_name); + + return success; +} + +bool os_keychain_delete(const char *label, const char *key) +{ + if (!label || !key) + return false; + + struct dstr uuid_str = {0}; + dstr_printf(&uuid_str, "%s::%s", label, key); + + wchar_t *target_name = NULL; + os_utf8_to_wcs_ptr(uuid_str.array, 0, &target_name); + dstr_free(&uuid_str); + + if (!target_name) + return false; + + bool success = CredDeleteW(target_name, CRED_TYPE_GENERIC, 0); + if (!success) { + DWORD err = GetLastError(); + if (err == ERROR_NOT_FOUND) { + success = true; + } else { + blog(LOG_WARNING, + "Keychain item \"%s::%s\" could not be deleted: %d", + label, key, err); + } + } + + bfree(target_name); + + return success; +} diff --git a/libobs/util/platform.h b/libobs/util/platform.h index a3a2b74c4369af..40c99e8be885e7 100644 --- a/libobs/util/platform.h +++ b/libobs/util/platform.h @@ -205,6 +205,12 @@ EXPORT uint64_t os_get_proc_virtual_size(void); EXPORT char *os_generate_uuid(void); +EXPORT bool os_keychain_available(void); +EXPORT bool os_keychain_save(const char *label, const char *key, + const char *data); +EXPORT bool os_keychain_load(const char *label, const char *key, char **data); +EXPORT bool os_keychain_delete(const char *label, const char *key); + /* clang-format off */ #ifdef __APPLE__ # define ARCH_BITS 64 From 15ed3b5de9c332365c91114a63a4be3b0a7d713c Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 22 Jun 2023 05:15:24 +0200 Subject: [PATCH 02/11] libobs: Add macOS keychain API implementation --- libobs/cmake/os-macos.cmake | 3 +- libobs/util/platform-cocoa.m | 88 ++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/libobs/cmake/os-macos.cmake b/libobs/cmake/os-macos.cmake index e3da29797b27a3..f2283e6a421a99 100644 --- a/libobs/cmake/os-macos.cmake +++ b/libobs/cmake/os-macos.cmake @@ -6,7 +6,8 @@ target_link_libraries( "$" "$" "$" - "$") + "$" + "$") target_sources( libobs diff --git a/libobs/util/platform-cocoa.m b/libobs/util/platform-cocoa.m index f01a4202c9868b..cbec6c0328cf53 100644 --- a/libobs/util/platform-cocoa.m +++ b/libobs/util/platform-cocoa.m @@ -35,6 +35,7 @@ #include #import +#import #include "apple/cfstring-utils.h" @@ -505,3 +506,90 @@ bool cfstr_copy_dstr(CFStringRef cfstring, CFStringEncoding cfstring_encoding, s return (bool) success; } + +bool os_keychain_available(void) +{ + return true; +} + +bool os_keychain_save(const char *label, const char *key, const char *data) +{ + if (!label || !key || !data) + return false; + + NSData *nsData = [NSData dataWithBytesNoCopy:(void *) data length:strlen(data) freeWhenDone:NO]; + NSDictionary *insert = @{ + (__bridge id) kSecAttrAccessible: (__bridge id) kSecAttrAccessibleWhenUnlocked, + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: @(label), + (__bridge id) kSecAttrAccount: @(key), + (__bridge id) kSecValueData: nsData, + }; + + OSStatus status = SecItemAdd((__bridge CFDictionaryRef) insert, nil); + /* macOS doesn't allow us to overwrite existing keychain items, so we need to do an update instead if already exists. */ + if (status == errSecDuplicateItem) { + NSDictionary *query = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: @(label), + (__bridge id) kSecAttrAccount: @(key), + }; + status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) insert); + if (status != errSecSuccess) { + blog(LOG_ERROR, "Keychain item \"%s::%s\" could not be updated: %d", label, key, status); + } + } else if (status != errSecSuccess) { + blog(LOG_ERROR, "Keychain item \"%s::%s\" could not be saved: %d", label, key, status); + } + + return status == errSecSuccess; +} + +bool os_keychain_load(const char *label, const char *key, char **data) +{ + if (!label || !key || !data) + return false; + + NSDictionary *query = @{ + (__bridge id) kSecAttrAccessible: (__bridge id) kSecAttrAccessibleWhenUnlocked, + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecMatchLimit: (__bridge id) kSecMatchLimitOne, + (__bridge id) kSecAttrService: @(label), + (__bridge id) kSecAttrAccount: @(key), + (__bridge id) kSecReturnData: @YES, + }; + + /* The result will be a CFData holding the string we saved. */ + CFDataRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef *) &result); + if (status != errSecSuccess || !result) { + blog(LOG_ERROR, "Keychain item \"%s::%s\" could not be read: %d", label, key, status); + return false; + } + + *data = bstrdup_n((const char *) CFDataGetBytePtr(result), CFDataGetLength(result)); + CFRelease(result); + + return true; +} + +bool os_keychain_delete(const char *label, const char *key) +{ + if (!label || !key) + return false; + + NSDictionary *query = @{ + (__bridge id) kSecAttrAccessible: (__bridge id) kSecAttrAccessibleWhenUnlocked, + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: @(label), + (__bridge id) kSecAttrAccount: @(key), + }; + + OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query); + if (status != errSecSuccess && status != errSecItemNotFound) { + blog(LOG_WARNING, "Keychain item \"%s::%s\" could not be deleted: %d", label, key, status); + return false; + } + + return true; +} From a40370f6801bfa1afa477ae56b2aa06db10aa867 Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 22 Jun 2023 05:15:47 +0200 Subject: [PATCH 03/11] libobs: Add OS keychain API stubs for *nix --- libobs/util/platform-nix.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/libobs/util/platform-nix.c b/libobs/util/platform-nix.c index 59a3e4bfd97965..464d35052dc653 100644 --- a/libobs/util/platform-nix.c +++ b/libobs/util/platform-nix.c @@ -1148,3 +1148,36 @@ char *os_generate_uuid(void) uuid_unparse_lower(uuid, out); return out; } + +#ifndef __APPLE__ +bool os_keychain_available(void) +{ + return false; +} + +bool os_keychain_save(const char *label, const char *key, const char *data) +{ + /* Not implemented */ + UNUSED_PARAMETER(label); + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(data); + return false; +} + +bool os_keychain_load(const char *label, const char *key, char **data) +{ + /* Not implemented */ + UNUSED_PARAMETER(label); + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(data); + return false; +} + +bool os_keychain_delete(const char *label, const char *key) +{ + /* Not implemented */ + UNUSED_PARAMETER(label); + UNUSED_PARAMETER(key); + return false; +} +#endif From dee6dc365454f8109e3edf6f76a7ddaf14b6ea14 Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 24 Jun 2023 04:23:20 +0200 Subject: [PATCH 04/11] cmake: Add finder for libsecret --- cmake/Modules/FindLibsecret.cmake | 111 ++++++++++++++++++++++++++++++ cmake/finders/FindLibsecret.cmake | 111 ++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 cmake/Modules/FindLibsecret.cmake create mode 100644 cmake/finders/FindLibsecret.cmake diff --git a/cmake/Modules/FindLibsecret.cmake b/cmake/Modules/FindLibsecret.cmake new file mode 100644 index 00000000000000..2a3bb8d356f136 --- /dev/null +++ b/cmake/Modules/FindLibsecret.cmake @@ -0,0 +1,111 @@ +#[=======================================================================[.rst +FindLibsecret +------------- + +FindModule for libsecret and the associated library + +Imported Targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 2.0 + +This module defines the :prop_tgt:`IMPORTED` target ``Libsecret::Libsecret``. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module sets the following variables: + +``Libsecret_FOUND`` + True, if the library was found. +``Libsecret_VERSION`` + Detected version of found Libsecret library. + +Cache variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``Libsecret_INCLUDE_DIR`` + Directory containing ``secret.h``. + +#]=======================================================================] + +include(FindPackageHandleStandardArgs) + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_search_module(PC_Libsecret QUIET libsecret-1) +endif() + +# Libsecret_set_soname: Set SONAME on imported library target +macro(Libsecret_set_soname) + if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") + execute_process( + COMMAND sh -c "${CMAKE_OBJDUMP} -p '${Libsecret_LIBRARY}' | grep SONAME" + OUTPUT_VARIABLE _output + RESULT_VARIABLE _result) + + if(_result EQUAL 0) + string(REGEX REPLACE "[ \t]+SONAME[ \t]+([^ \t]+)" "\\1" _soname "${_output}") + set_property(TARGET Libsecret::Libsecret PROPERTY IMPORTED_SONAME "${_soname}") + unset(_soname) + endif() + endif() + unset(_output) + unset(_result) +endmacro() + +find_path( + Libsecret_INCLUDE_DIR + NAMES libsecret/secret.h + HINTS ${PC_Libsecret_INCLUDE_DIRS} + PATHS /usr/include /usr/local/include + PATH_SUFFIXES libsecret-1 + DOC "Libsecret include directory") + +find_library( + Libsecret_LIBRARY + NAMES secret secret-1 libsecret libsecret-1 + HINTS ${PC_Libsecret_LIBRARY_DIRS} + PATHS /usr/lib /usr/local/lib + DOC "Libsecret location") + +if(PC_Libsecret_VERSION VERSION_GREATER 0) + set(Libsecret_VERSION ${PC_Libsecret_VERSION}) +elseif(EXISTS "${Libsecret_INCLUDE_DIR}/secret-version.h") + file(STRINGS "${Libsecret_INCLUDE_DIR}/secret-version.h" _version_string) + string(REGEX REPLACE "SECRET_[A-Z]+_VERSION \\(([0-9]+)\\)" "\\1\\.\\2\\.\\3" Libsecret_VERSION "${_version_string}") +else() + if(NOT Libsecret_FIND_QUIETLY) + message(AUTHOR_WARNING "Failed to find Libsecret version.") + endif() + set(Libsecret_VERSION 0.0.0) +endif() + +find_package_handle_standard_args( + Libsecret + REQUIRED_VARS Libsecret_INCLUDE_DIR Libsecret_LIBRARY + VERSION_VAR Libsecret_VERSION REASON_FAILURE_MESSAGE "Ensure libsecret-1 is installed on the system.") +mark_as_advanced(Libsecret_INCLUDE_DIR Libsecret_LIBRARY) + +if(Libsecret_FOUND) + if(NOT TARGET Libsecret::Libsecret) + if(IS_ABSOLUTE "${Libsecret_LIBRARY}") + add_library(Libsecret::Libsecret UNKNOWN IMPORTED) + set_property(TARGET Libsecret::Libsecret PROPERTY IMPORTED_LOCATION "${Libsecret_LIBRARY}") + else() + add_library(Libsecret::Libsecret INTERFACE IMPORTED) + set_property(TARGET Libsecret::Libsecret PROPERTY IMPORTED_LIBNAME "${Libsecret_LIBRARY}") + endif() + + libsecret_set_soname() + set_target_properties(Libsecret::Libsecret PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${Libsecret_INCLUDE_DIR}") + endif() +endif() + +include(FeatureSummary) +set_package_properties( + Libsecret PROPERTIES + URL "https://gnome.pages.gitlab.gnome.org/libsecret/index.html" + DESCRIPTION "Secret Service D-Bus client library") diff --git a/cmake/finders/FindLibsecret.cmake b/cmake/finders/FindLibsecret.cmake new file mode 100644 index 00000000000000..2a3bb8d356f136 --- /dev/null +++ b/cmake/finders/FindLibsecret.cmake @@ -0,0 +1,111 @@ +#[=======================================================================[.rst +FindLibsecret +------------- + +FindModule for libsecret and the associated library + +Imported Targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 2.0 + +This module defines the :prop_tgt:`IMPORTED` target ``Libsecret::Libsecret``. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module sets the following variables: + +``Libsecret_FOUND`` + True, if the library was found. +``Libsecret_VERSION`` + Detected version of found Libsecret library. + +Cache variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``Libsecret_INCLUDE_DIR`` + Directory containing ``secret.h``. + +#]=======================================================================] + +include(FindPackageHandleStandardArgs) + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_search_module(PC_Libsecret QUIET libsecret-1) +endif() + +# Libsecret_set_soname: Set SONAME on imported library target +macro(Libsecret_set_soname) + if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") + execute_process( + COMMAND sh -c "${CMAKE_OBJDUMP} -p '${Libsecret_LIBRARY}' | grep SONAME" + OUTPUT_VARIABLE _output + RESULT_VARIABLE _result) + + if(_result EQUAL 0) + string(REGEX REPLACE "[ \t]+SONAME[ \t]+([^ \t]+)" "\\1" _soname "${_output}") + set_property(TARGET Libsecret::Libsecret PROPERTY IMPORTED_SONAME "${_soname}") + unset(_soname) + endif() + endif() + unset(_output) + unset(_result) +endmacro() + +find_path( + Libsecret_INCLUDE_DIR + NAMES libsecret/secret.h + HINTS ${PC_Libsecret_INCLUDE_DIRS} + PATHS /usr/include /usr/local/include + PATH_SUFFIXES libsecret-1 + DOC "Libsecret include directory") + +find_library( + Libsecret_LIBRARY + NAMES secret secret-1 libsecret libsecret-1 + HINTS ${PC_Libsecret_LIBRARY_DIRS} + PATHS /usr/lib /usr/local/lib + DOC "Libsecret location") + +if(PC_Libsecret_VERSION VERSION_GREATER 0) + set(Libsecret_VERSION ${PC_Libsecret_VERSION}) +elseif(EXISTS "${Libsecret_INCLUDE_DIR}/secret-version.h") + file(STRINGS "${Libsecret_INCLUDE_DIR}/secret-version.h" _version_string) + string(REGEX REPLACE "SECRET_[A-Z]+_VERSION \\(([0-9]+)\\)" "\\1\\.\\2\\.\\3" Libsecret_VERSION "${_version_string}") +else() + if(NOT Libsecret_FIND_QUIETLY) + message(AUTHOR_WARNING "Failed to find Libsecret version.") + endif() + set(Libsecret_VERSION 0.0.0) +endif() + +find_package_handle_standard_args( + Libsecret + REQUIRED_VARS Libsecret_INCLUDE_DIR Libsecret_LIBRARY + VERSION_VAR Libsecret_VERSION REASON_FAILURE_MESSAGE "Ensure libsecret-1 is installed on the system.") +mark_as_advanced(Libsecret_INCLUDE_DIR Libsecret_LIBRARY) + +if(Libsecret_FOUND) + if(NOT TARGET Libsecret::Libsecret) + if(IS_ABSOLUTE "${Libsecret_LIBRARY}") + add_library(Libsecret::Libsecret UNKNOWN IMPORTED) + set_property(TARGET Libsecret::Libsecret PROPERTY IMPORTED_LOCATION "${Libsecret_LIBRARY}") + else() + add_library(Libsecret::Libsecret INTERFACE IMPORTED) + set_property(TARGET Libsecret::Libsecret PROPERTY IMPORTED_LIBNAME "${Libsecret_LIBRARY}") + endif() + + libsecret_set_soname() + set_target_properties(Libsecret::Libsecret PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${Libsecret_INCLUDE_DIR}") + endif() +endif() + +include(FeatureSummary) +set_package_properties( + Libsecret PROPERTIES + URL "https://gnome.pages.gitlab.gnome.org/libsecret/index.html" + DESCRIPTION "Secret Service D-Bus client library") From 50e56ea62661642c4f0cbdbcdbe26f3554cbbff2 Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 24 Jun 2023 23:23:20 +0200 Subject: [PATCH 05/11] build-aux: Add libsecret to flatpak Taken from https://github.com/flathub/shared-modules/blob/master/libsecret/libsecret.json --- build-aux/com.obsproject.Studio.json | 1 + build-aux/modules/50-libsecret.json | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 build-aux/modules/50-libsecret.json diff --git a/build-aux/com.obsproject.Studio.json b/build-aux/com.obsproject.Studio.json index 56b0f5d239ec1b..ecadc7366e3b79 100644 --- a/build-aux/com.obsproject.Studio.json +++ b/build-aux/com.obsproject.Studio.json @@ -57,6 +57,7 @@ "modules/40-usrsctp.json", "modules/50-jansson.json", "modules/50-libdatachannel.json", + "modules/50-libsecret.json", "modules/50-ntv2.json", "modules/50-pipewire.json", "modules/50-swig.json", diff --git a/build-aux/modules/50-libsecret.json b/build-aux/modules/50-libsecret.json new file mode 100644 index 00000000000000..22263b46c067df --- /dev/null +++ b/build-aux/modules/50-libsecret.json @@ -0,0 +1,23 @@ +{ + "name": "libsecret", + "buildsystem": "meson", + "config-opts": [ + "-Dmanpage=false", + "-Dvapi=false", + "-Dgtk_doc=false", + "-Dintrospection=false" + ], + "cleanup": [ + "/bin", + "/include", + "/lib/pkgconfig", + "/share/man" + ], + "sources": [ + { + "type": "archive", + "url": "https://download.gnome.org/sources/libsecret/0.20/libsecret-0.20.5.tar.xz", + "sha256": "3fb3ce340fcd7db54d87c893e69bfc2b1f6e4d4b279065ffe66dac9f0fd12b4d" + } + ] +} From 7ae14b92d7eca554514fc3b7675b88e4964f15bb Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 24 Jun 2023 04:43:33 +0200 Subject: [PATCH 06/11] libobs: Add libsecret keychain implementation --- libobs/cmake/legacy.cmake | 8 +++ libobs/cmake/os-linux.cmake | 10 +++ libobs/util/platform-nix-libsecret.c | 101 +++++++++++++++++++++++++++ libobs/util/platform-nix.c | 2 +- 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 libobs/util/platform-nix-libsecret.c diff --git a/libobs/cmake/legacy.cmake b/libobs/cmake/legacy.cmake index 3bedda97102d4a..c923b6fe24c01e 100644 --- a/libobs/cmake/legacy.cmake +++ b/libobs/cmake/legacy.cmake @@ -424,6 +424,14 @@ elseif(OS_POSIX) target_link_libraries(libobs PRIVATE GIO::GIO) target_sources(libobs PRIVATE util/platform-nix-dbus.c util/platform-nix-portal.c) + + find_package(Libsecret) + if(TARGET Libsecret::Libsecret) + obs_status(STATUS "-> libsecret found, enabling keychain API") + target_link_libraries(libobs PRIVATE Libsecret::Libsecret) + target_compile_definitions(libobs PRIVATE USE_LIBSECRET) + target_sources(libobs PRIVATE util/platform-nix-libsecret.c) + endif() endif() if(TARGET XCB::XINPUT) diff --git a/libobs/cmake/os-linux.cmake b/libobs/cmake/os-linux.cmake index 9aa825f4c1b175..f931d5b9a9e0ac 100644 --- a/libobs/cmake/os-linux.cmake +++ b/libobs/cmake/os-linux.cmake @@ -5,6 +5,7 @@ find_package(x11-xcb REQUIRED) find_package(xcb COMPONENTS xcb OPTIONAL_COMPONENTS xcb-xinput QUIET) # cmake-format: on find_package(gio) +find_package(Libsecret) target_sources( libobs @@ -43,6 +44,15 @@ endif() if(TARGET gio::gio) target_sources(libobs PRIVATE util/platform-nix-dbus.c util/platform-nix-portal.c) target_link_libraries(libobs PRIVATE gio::gio) + + if(TARGET Libsecret::Libsecret) + target_compile_definitions(libobs PRIVATE USE_LIBSECRET) + target_sources(libobs PRIVATE util/platform-nix-libsecret.c) + target_link_libraries(libobs PRIVATE Libsecret::Libsecret) + target_enable_feature(libobs "Libsecret Keychain API (Linux)") + else() + target_disable_feature(libobs "Libsecret Keychain API (Linux)") + endif() endif() if(ENABLE_WAYLAND) diff --git a/libobs/util/platform-nix-libsecret.c b/libobs/util/platform-nix-libsecret.c new file mode 100644 index 00000000000000..9119c479e3798f --- /dev/null +++ b/libobs/util/platform-nix-libsecret.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023 Dennis Sädtler + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "platform.h" +#include "bmem.h" + +static const SecretSchema obs_schema = { + "com.obsproject.libobs.Secret", + SECRET_SCHEMA_NONE, + { + {"key", SECRET_SCHEMA_ATTRIBUTE_STRING}, + {"NULL", 0}, + }}; + +bool os_keychain_available(void) +{ + return true; +} + +bool os_keychain_save(const char *label, const char *key, const char *data) +{ + if (!label || !key || !data) + return false; + + GError *error = NULL; + secret_password_store_sync(&obs_schema, SECRET_COLLECTION_DEFAULT, + label, data, NULL, &error, "key", key, NULL); + if (error != NULL) { + blog(LOG_ERROR, + "Keychain item \"%s::%s\" could not be saved: %s", label, + key, error->message); + g_error_free(error); + return false; + } + + return true; +} + +bool os_keychain_load(const char *label, const char *key, char **data) +{ + if (!label || !key || !data) + return false; + + GError *error = NULL; + gchar *password = secret_password_lookup_sync(&obs_schema, NULL, &error, + "key", key, NULL); + + if (error != NULL) { + blog(LOG_ERROR, + "Keychain item \"%s::%s\" could not be read: %s", label, + key, error->message); + g_error_free(error); + return false; + } else if (password == NULL) { + return false; + } + + *data = bstrdup(password); + secret_password_free(password); + return true; +} + +bool os_keychain_delete(const char *label, const char *key) +{ + if (!label || !key) + return false; + + GError *error = NULL; + gboolean removed = secret_password_clear_sync(&obs_schema, NULL, &error, + "key", key, NULL); + + if (error != NULL) { + if (error->code == SECRET_ERROR_NO_SUCH_OBJECT) { + removed = true; + } else { + blog(LOG_ERROR, + "Keychain item \"%s::%s\" could not be deleted: %s", + label, key, error->message); + } + + g_error_free(error); + } + + return removed; +} diff --git a/libobs/util/platform-nix.c b/libobs/util/platform-nix.c index 464d35052dc653..13648ae95e6cda 100644 --- a/libobs/util/platform-nix.c +++ b/libobs/util/platform-nix.c @@ -1149,7 +1149,7 @@ char *os_generate_uuid(void) return out; } -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(USE_LIBSECRET) bool os_keychain_available(void) { return false; From 8e2f117bcbfe3cb340e16680a9ddc72b6ff9d262 Mon Sep 17 00:00:00 2001 From: derrod Date: Thu, 22 Jun 2023 05:16:02 +0200 Subject: [PATCH 07/11] UI: Use Keychain for OAuth credentials --- UI/auth-oauth.cpp | 103 ++++++++++++++++++++++++++++++++++++++------ UI/auth-oauth.hpp | 5 +++ UI/auth-youtube.cpp | 99 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 176 insertions(+), 31 deletions(-) diff --git a/UI/auth-oauth.cpp b/UI/auth-oauth.cpp index d18923d9dee36e..5c9d22148b968e 100644 --- a/UI/auth-oauth.cpp +++ b/UI/auth-oauth.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -158,14 +159,21 @@ void OAuth::DeleteCookies(const std::string &service) } } -void OAuth::SaveInternal() +static constexpr char hexChars[] = "abcdef0123456789"; +static constexpr int hexCount = sizeof(hexChars) - 1; +static constexpr int kSuffixLength = 8; + +static std::string GetRandSuffix() { - OBSBasic *main = OBSBasic::Get(); - config_set_string(main->Config(), service(), "RefreshToken", - refresh_token.c_str()); - config_set_string(main->Config(), service(), "Token", token.c_str()); - config_set_uint(main->Config(), service(), "ExpireTime", expire_time); - config_set_int(main->Config(), service(), "ScopeVer", currentScopeVer); + char state[kSuffixLength + 1]; + QRandomGenerator *rng = QRandomGenerator::system(); + int i; + + for (i = 0; i < kSuffixLength; i++) + state[i] = hexChars[rng->bounded(0, hexCount)]; + state[i] = 0; + + return state; } static inline std::string get_config_str(OBSBasic *main, const char *section, @@ -175,14 +183,85 @@ static inline std::string get_config_str(OBSBasic *main, const char *section, return val ? val : ""; } +void OAuth::SaveInternal() +{ + OBSBasic *main = OBSBasic::Get(); + + bool keychain_success = false; + + if (!App()->IsPortableMode()) { + std::string keychain_key = + get_config_str(main, service(), "KeychainItem"); + + if (keychain_key.empty()) { + keychain_key = service(); + keychain_key += "::" + GetRandSuffix(); + } + + Json data = Json::object{{"refresh_token", refresh_token}, + {"token", token}, + {"expire_time", + static_cast(expire_time)}, + {"scope_ver", currentScopeVer}}; + std::string json = data.dump(); + + if (os_keychain_save(GetKeychainLabel(), keychain_key.c_str(), + json.c_str())) { + config_set_string(main->Config(), service(), + "KeychainItem", keychain_key.c_str()); + keychain_success = true; + } + } + + if (!keychain_success) { + config_set_string(main->Config(), service(), "RefreshToken", + refresh_token.c_str()); + config_set_string(main->Config(), service(), "Token", + token.c_str()); + config_set_uint(main->Config(), service(), "ExpireTime", + expire_time); + config_set_int(main->Config(), service(), "ScopeVer", + currentScopeVer); + } +} + bool OAuth::LoadInternal() { OBSBasic *main = OBSBasic::Get(); - refresh_token = get_config_str(main, service(), "RefreshToken"); - token = get_config_str(main, service(), "Token"); - expire_time = config_get_uint(main->Config(), service(), "ExpireTime"); - currentScopeVer = - (int)config_get_int(main->Config(), service(), "ScopeVer"); + + bool keychain_success = false; + + if (!App()->IsPortableMode()) { + const char *keychain_key = config_get_string( + main->Config(), service(), "KeychainItem"); + + BPtr data; + if (keychain_key && + os_keychain_load(GetKeychainLabel(), keychain_key, &data) && + data) { + std::string err; + Json parsed = Json::parse(data, err); + if (err.empty()) { + refresh_token = + parsed["refresh_token"].string_value(); + token = parsed["token"].string_value(); + expire_time = parsed["expire_time"].int_value(); + currentScopeVer = + parsed["scope_ver"].int_value(); + keychain_success = true; + } + } + } + + if (!keychain_success) { + refresh_token = get_config_str(main, service(), "RefreshToken"); + token = get_config_str(main, service(), "Token"); + expire_time = config_get_uint(main->Config(), service(), + "ExpireTime"); + currentScopeVer = (int)config_get_int(main->Config(), service(), + "ScopeVer"); + } + return implicit ? !token.empty() : !refresh_token.empty(); } diff --git a/UI/auth-oauth.hpp b/UI/auth-oauth.hpp index 364dccdc4442ab..ce0856a56c88af 100644 --- a/UI/auth-oauth.hpp +++ b/UI/auth-oauth.hpp @@ -71,6 +71,11 @@ class OAuth : public Auth { const std::string &redirect_uri, int scope_ver, const std::string &auth_code, bool retry); + static const char *GetKeychainLabel() + { + return "OBS Studio OAuth Credentials"; + } + private: bool GetTokenInternal(const char *url, const std::string &client_id, const std::string &secret, diff --git a/UI/auth-youtube.cpp b/UI/auth-youtube.cpp index 7783fa62d4884a..bc5a696ef9f391 100644 --- a/UI/auth-youtube.cpp +++ b/UI/auth-youtube.cpp @@ -91,6 +91,13 @@ bool YoutubeAuth::RetryLogin() return true; } +static inline std::string get_config_str(OBSBasic *main, const char *section, + const char *name) +{ + const char *val = config_get_string(main->Config(), section, name); + return val ? val : ""; +} + void YoutubeAuth::SaveInternal() { OBSBasic *main = OBSBasic::Get(); @@ -98,33 +105,87 @@ void YoutubeAuth::SaveInternal() main->saveState().toBase64().constData()); const char *section_name = section.c_str(); - config_set_string(main->Config(), section_name, "RefreshToken", - refresh_token.c_str()); - config_set_string(main->Config(), section_name, "Token", token.c_str()); - config_set_uint(main->Config(), section_name, "ExpireTime", - expire_time); - config_set_int(main->Config(), section_name, "ScopeVer", - currentScopeVer); -} + bool keychain_success = false; -static inline std::string get_config_str(OBSBasic *main, const char *section, - const char *name) -{ - const char *val = config_get_string(main->Config(), section, name); - return val ? val : ""; + if (!App()->IsPortableMode()) { + std::string keychain_key = + get_config_str(main, section_name, "KeychainItem"); + + if (keychain_key.empty()) { + QString suffix = GenerateState(); + suffix.resize(8); + + keychain_key = section_name; + keychain_key += "::"; + keychain_key += QT_TO_UTF8(suffix); + } + + Json data = Json::object{{"refresh_token", refresh_token}, + {"token", token}, + {"expire_time", + static_cast(expire_time)}, + {"scope_ver", currentScopeVer}}; + std::string json = data.dump(); + + if (os_keychain_save(GetKeychainLabel(), keychain_key.c_str(), + json.c_str())) { + config_set_string(main->Config(), section_name, + "KeychainItem", keychain_key.c_str()); + keychain_success = true; + } + } + + if (!keychain_success) { + config_set_string(main->Config(), section_name, "RefreshToken", + refresh_token.c_str()); + config_set_string(main->Config(), section_name, "Token", + token.c_str()); + config_set_uint(main->Config(), section_name, "ExpireTime", + expire_time); + config_set_int(main->Config(), section_name, "ScopeVer", + currentScopeVer); + } } bool YoutubeAuth::LoadInternal() { OBSBasic *main = OBSBasic::Get(); + bool keychain_success = false; const char *section_name = section.c_str(); - refresh_token = get_config_str(main, section_name, "RefreshToken"); - token = get_config_str(main, section_name, "Token"); - expire_time = - config_get_uint(main->Config(), section_name, "ExpireTime"); - currentScopeVer = - (int)config_get_int(main->Config(), section_name, "ScopeVer"); + + if (!App()->IsPortableMode()) { + const char *keychain_key = config_get_string( + main->Config(), section_name, "KeychainItem"); + + BPtr data; + if (keychain_key && + os_keychain_load(GetKeychainLabel(), keychain_key, &data) && + data) { + std::string err; + Json parsed = Json::parse(data, err); + if (err.empty()) { + refresh_token = + parsed["refresh_token"].string_value(); + token = parsed["token"].string_value(); + expire_time = parsed["expire_time"].int_value(); + currentScopeVer = + parsed["scope_ver"].int_value(); + keychain_success = true; + } + } + } + + if (!keychain_success) { + refresh_token = + get_config_str(main, section_name, "RefreshToken"); + token = get_config_str(main, section_name, "Token"); + expire_time = config_get_uint(main->Config(), section_name, + "ExpireTime"); + currentScopeVer = (int)config_get_int(main->Config(), + section_name, "ScopeVer"); + } + firstLoad = false; return implicit ? !token.empty() : !refresh_token.empty(); } From 7781cc1fd80a9e202a7abc398e7855fce1d7fdf8 Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 24 Jun 2023 03:20:11 +0200 Subject: [PATCH 08/11] libobs: Add config_remove_section() --- .../reference-libobs-util-config-file.rst | 8 ++++++++ libobs/util/config-file.c | 18 ++++++++++++++++++ libobs/util/config-file.h | 1 + 3 files changed, 27 insertions(+) diff --git a/docs/sphinx/reference-libobs-util-config-file.rst b/docs/sphinx/reference-libobs-util-config-file.rst index 3bd4c2693d42ff..ca1b5fbc1140ff 100644 --- a/docs/sphinx/reference-libobs-util-config-file.rst +++ b/docs/sphinx/reference-libobs-util-config-file.rst @@ -245,6 +245,14 @@ Set/Get Functions :param section: The section of the value :param name: The value name +---------------------- + +.. function:: bool config_remove_section(config_t *config, const char *section) + + Removes a section. Does not remove the default values if any. + + :param config: Configuration object + :param section: The section of the value Default Value Functions ----------------------- diff --git a/libobs/util/config-file.c b/libobs/util/config-file.c index ac9bfe1cb6653a..53a72c1a03f57e 100644 --- a/libobs/util/config-file.c +++ b/libobs/util/config-file.c @@ -759,6 +759,24 @@ bool config_remove_value(config_t *config, const char *section, return success; } +bool config_remove_section(config_t *config, const char *section) +{ + struct config_section *sec; + bool success = false; + + pthread_mutex_lock(&config->mutex); + + HASH_FIND_STR(config->sections, section, sec); + if (sec) { + HASH_DELETE(hh, config->sections, sec); + config_section_free(sec); + success = true; + } + + pthread_mutex_unlock(&config->mutex); + return success; +} + const char *config_get_default_string(config_t *config, const char *section, const char *name) { diff --git a/libobs/util/config-file.h b/libobs/util/config-file.h index b9505354f47880..f8e548f10cadff 100644 --- a/libobs/util/config-file.h +++ b/libobs/util/config-file.h @@ -77,6 +77,7 @@ EXPORT double config_get_double(config_t *config, const char *section, EXPORT bool config_remove_value(config_t *config, const char *section, const char *name); +EXPORT bool config_remove_section(config_t *config, const char *section); /* * DEFAULT VALUES From fe969118b5f737d4810ae5dc558a9289043414fb Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 24 Jun 2023 03:42:20 +0200 Subject: [PATCH 09/11] UI: Delete config section when disconnecting account --- UI/auth-base.cpp | 17 +++++++++++++++++ UI/auth-base.hpp | 2 ++ UI/auth-oauth.cpp | 13 +++++++++++++ UI/auth-oauth.hpp | 1 + UI/window-basic-auto-config.cpp | 3 +++ UI/window-basic-settings-stream.cpp | 3 +++ 6 files changed, 39 insertions(+) diff --git a/UI/auth-base.cpp b/UI/auth-base.cpp index b1a4315fd29dae..06b9a7062a75ed 100644 --- a/UI/auth-base.cpp +++ b/UI/auth-base.cpp @@ -85,3 +85,20 @@ void Auth::Save() auth->SaveInternal(); config_save_safe(main->Config(), "tmp", nullptr); } + +void Auth::Delete() +{ + OBSBasic *main = OBSBasic::Get(); + Auth *auth = main->auth.get(); + if (!auth) { + if (config_has_user_value(main->Config(), "Auth", "Type")) { + config_remove_value(main->Config(), "Auth", "Type"); + config_save_safe(main->Config(), "tmp", nullptr); + } + return; + } + + config_remove_value(main->Config(), "Auth", "type"); + auth->DeleteInternal(); + config_save_safe(main->Config(), "tmp", nullptr); +} diff --git a/UI/auth-base.hpp b/UI/auth-base.hpp index a29d642c1d50d0..e8179510dc3a89 100644 --- a/UI/auth-base.hpp +++ b/UI/auth-base.hpp @@ -10,6 +10,7 @@ class Auth : public QObject { protected: virtual void SaveInternal() = 0; virtual bool LoadInternal() = 0; + virtual void DeleteInternal() = 0; bool firstLoad = true; @@ -56,6 +57,7 @@ class Auth : public QObject { static bool External(const std::string &service); static void Load(); static void Save(); + static void Delete(); protected: static void RegisterAuth(const Def &d, create_cb create); diff --git a/UI/auth-oauth.cpp b/UI/auth-oauth.cpp index 5c9d22148b968e..1f9f84b48d09c7 100644 --- a/UI/auth-oauth.cpp +++ b/UI/auth-oauth.cpp @@ -265,6 +265,19 @@ bool OAuth::LoadInternal() return implicit ? !token.empty() : !refresh_token.empty(); } +void OAuth::DeleteInternal() +{ + OBSBasic *main = OBSBasic::Get(); + + /* Delete keychain item (if it exists) */ + os_keychain_delete(GetKeychainLabel(), + config_get_string(main->Config(), service(), + "KeychainItem")); + + /* Delete OAuth config section */ + config_remove_section(main->Config(), service()); +} + bool OAuth::TokenExpired() { if (token.empty()) diff --git a/UI/auth-oauth.hpp b/UI/auth-oauth.hpp index ce0856a56c88af..69075305f33389 100644 --- a/UI/auth-oauth.hpp +++ b/UI/auth-oauth.hpp @@ -59,6 +59,7 @@ class OAuth : public Auth { virtual void SaveInternal() override; virtual bool LoadInternal() override; + virtual void DeleteInternal() override; virtual bool RetryLogin() = 0; bool TokenExpired(); diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index 3e9c366856764f..df9d00c23a3e46 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -527,6 +527,9 @@ void AutoConfigStreamPage::on_disconnectAccount_clicked() OBSBasic *main = OBSBasic::Get(); + if (auth) + auth->Delete(); + main->auth.reset(); auth.reset(); diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 511e3ec824f569..ec11de0aef6d5a 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -792,6 +792,9 @@ void OBSBasicSettings::on_disconnectAccount_clicked() return; } + if (auth) + auth->Delete(); + main->auth.reset(); auth.reset(); main->SetBroadcastFlowEnabled(false); From e505bb8d815016c478011bac581a4d0812b279f4 Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 24 Jun 2023 03:42:47 +0200 Subject: [PATCH 10/11] UI: Add OAuth token invalidation --- UI/auth-oauth.cpp | 61 ++++++++++++++++++++++++++++++++++++++++ UI/auth-oauth.hpp | 6 ++++ UI/data/locale/en-US.ini | 2 ++ 3 files changed, 69 insertions(+) diff --git a/UI/auth-oauth.cpp b/UI/auth-oauth.cpp index 1f9f84b48d09c7..058a7a77fc5a04 100644 --- a/UI/auth-oauth.cpp +++ b/UI/auth-oauth.cpp @@ -413,6 +413,67 @@ try { return false; } +bool OAuth::InvalidateToken(const char *url) +{ + return InvalidateTokenInternal(url, "", true); +} + +bool OAuth::InvalidateToken(const char *url, const std::string &client_id) +{ + return InvalidateTokenInternal(url, client_id); +} + +bool OAuth::InvalidateTokenInternal(const char *base_url, + const std::string &client_id, + const bool token_as_parameter) +try { + std::string url(base_url); + std::string output; + std::string error; + std::string desc; + std::string post_data; + + if (token.empty()) { + return true; + } + + /* Google wants the token as a parameter, but still wants us to POST... */ + if (token_as_parameter) { + url += "?token=" + token; + } else { + post_data += "token=" + token; + } + + /* Only required for Twitch as far as I can tell */ + if (!client_id.empty()) { + post_data += "&client_id=" + client_id; + } + + bool success = false; + + auto func = [&]() { + success = GetRemoteFile(url.c_str(), output, error, nullptr, + "application/x-www-form-urlencoded", + "POST", post_data.c_str(), + std::vector(), nullptr, 5, + false); + }; + + ExecThreadedWithoutBlocking(func, QTStr("Auth.Revoking.Title"), + QTStr("Auth.Revoking.Text").arg(service())); + if (!success) + throw ErrorInfo("Failed to revoke token", error); + + /* We don't really care about the result here, just assume it either + * succeeded or didn't matter. */ + return true; + +} catch (ErrorInfo &info) { + blog(LOG_WARNING, "%s: %s: %s", __FUNCTION__, info.message.c_str(), + info.error.c_str()); + return false; +} + void OAuthStreamKey::OnStreamConfig() { if (key_.empty()) diff --git a/UI/auth-oauth.hpp b/UI/auth-oauth.hpp index 69075305f33389..a0fc32fb2f2f74 100644 --- a/UI/auth-oauth.hpp +++ b/UI/auth-oauth.hpp @@ -71,6 +71,8 @@ class OAuth : public Auth { const std::string &secret, const std::string &redirect_uri, int scope_ver, const std::string &auth_code, bool retry); + bool InvalidateToken(const char *url); + bool InvalidateToken(const char *url, const std::string &client_id); static const char *GetKeychainLabel() { @@ -82,6 +84,10 @@ class OAuth : public Auth { const std::string &secret, const std::string &redirect_uri, int scope_ver, const std::string &auth_code, bool retry); + + bool InvalidateTokenInternal(const char *base_url, + const std::string &client_id, + bool token_as_parameter = false); }; class OAuthStreamKey : public OAuth { diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 94c68bba4234d7..201b0eb7f14929 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -143,6 +143,8 @@ ExtraBrowsers.DockName="Dock Name" # Auth Auth.Authing.Title="Authenticating..." Auth.Authing.Text="Authenticating with %1, please wait..." +Auth.Revoking.Title="Logging out..." +Auth.Revoking.Text="Logging out of %1, please wait..." Auth.AuthFailure.Title="Authentication Failure" Auth.AuthFailure.Text="Failed to authenticate with %1:\n\n%2: %3" Auth.InvalidScope.Title="Authentication Required" From 5c517f31c7732e9cf5179bc27d49c3c7de29d98c Mon Sep 17 00:00:00 2001 From: derrod Date: Sat, 24 Jun 2023 03:43:01 +0200 Subject: [PATCH 11/11] UI: Invalidate OAuth tokens when disconnecting accoun account --- UI/auth-restream.cpp | 7 +++++++ UI/auth-restream.hpp | 1 + UI/auth-twitch.cpp | 10 ++++++++++ UI/auth-twitch.hpp | 1 + UI/auth-youtube.cpp | 7 +++++++ UI/auth-youtube.hpp | 1 + 6 files changed, 27 insertions(+) diff --git a/UI/auth-restream.cpp b/UI/auth-restream.cpp index 9188da118c9684..e797bb98f1d791 100644 --- a/UI/auth-restream.cpp +++ b/UI/auth-restream.cpp @@ -21,6 +21,7 @@ using namespace json11; #define RESTREAM_AUTH_URL OAUTH_BASE_URL "v1/restream/redirect" #define RESTREAM_TOKEN_URL OAUTH_BASE_URL "v1/restream/token" +#define RESTREAM_REVOKE_URL "https://api.restream.io/oauth/revoke" #define RESTREAM_STREAMKEY_URL "https://api.restream.io/v2/user/streamKey" #define RESTREAM_SCOPE_VERSION 1 @@ -129,6 +130,12 @@ bool RestreamAuth::LoadInternal() return OAuthStreamKey::LoadInternal(); } +void RestreamAuth::DeleteInternal() +{ + InvalidateToken(RESTREAM_REVOKE_URL, ""); + OAuthStreamKey::DeleteInternal(); +} + void RestreamAuth::LoadUI() { if (uiLoaded) diff --git a/UI/auth-restream.hpp b/UI/auth-restream.hpp index 334906c42f7a5e..459b6159f4f484 100644 --- a/UI/auth-restream.hpp +++ b/UI/auth-restream.hpp @@ -13,6 +13,7 @@ class RestreamAuth : public OAuthStreamKey { virtual void SaveInternal() override; virtual bool LoadInternal() override; + virtual void DeleteInternal() override; bool GetChannelInfo(); diff --git a/UI/auth-twitch.cpp b/UI/auth-twitch.cpp index 386a0e9275cc9a..f9ab41ef6dbdc0 100644 --- a/UI/auth-twitch.cpp +++ b/UI/auth-twitch.cpp @@ -24,6 +24,7 @@ using namespace json11; #define TWITCH_AUTH_URL OAUTH_BASE_URL "v1/twitch/redirect" #define TWITCH_TOKEN_URL OAUTH_BASE_URL "v1/twitch/token" +#define TWITCH_REOKVE_URL "https://id.twitch.tv/oauth2/revoke" #define TWITCH_SCOPE_VERSION 1 @@ -499,6 +500,15 @@ std::shared_ptr TwitchAuth::Login(QWidget *parent, const std::string &) return nullptr; } +void TwitchAuth::DeleteInternal() +{ + std::string client_id = TWITCH_CLIENTID; + deobfuscate_str(&client_id[0], TWITCH_HASH); + + InvalidateToken(TWITCH_REOKVE_URL, client_id); + OAuthStreamKey::DeleteInternal(); +} + static std::shared_ptr CreateTwitchAuth() { return std::make_shared(twitchDef); diff --git a/UI/auth-twitch.hpp b/UI/auth-twitch.hpp index 152b2a95730fd7..6ee781f8aefff7 100644 --- a/UI/auth-twitch.hpp +++ b/UI/auth-twitch.hpp @@ -24,6 +24,7 @@ class TwitchAuth : public OAuthStreamKey { virtual void SaveInternal() override; virtual bool LoadInternal() override; + virtual void DeleteInternal() override; bool MakeApiRequest(const char *path, json11::Json &json_out); bool GetChannelInfo(); diff --git a/UI/auth-youtube.cpp b/UI/auth-youtube.cpp index bc5a696ef9f391..a648fd8e424f04 100644 --- a/UI/auth-youtube.cpp +++ b/UI/auth-youtube.cpp @@ -33,6 +33,7 @@ using namespace json11; /* ------------------------------------------------------------------------- */ #define YOUTUBE_AUTH_URL "https://accounts.google.com/o/oauth2/v2/auth" #define YOUTUBE_TOKEN_URL "https://www.googleapis.com/oauth2/v4/token" +#define YOUTUBE_REVOKE_URL "https://oauth2.googleapis.com/revoke" #define YOUTUBE_SCOPE_VERSION 1 #define YOUTUBE_API_STATE_LENGTH 32 #define SECTION_NAME "YouTube" @@ -190,6 +191,12 @@ bool YoutubeAuth::LoadInternal() return implicit ? !token.empty() : !refresh_token.empty(); } +void YoutubeAuth::DeleteInternal() +{ + InvalidateToken(YOUTUBE_REVOKE_URL); + OAuthStreamKey::DeleteInternal(); +} + #ifdef BROWSER_AVAILABLE static const char *ytchat_script = "\ const obsCSS = document.createElement('style');\ diff --git a/UI/auth-youtube.hpp b/UI/auth-youtube.hpp index ffe35c25c39c09..cbf8a812ed7193 100644 --- a/UI/auth-youtube.hpp +++ b/UI/auth-youtube.hpp @@ -49,6 +49,7 @@ class YoutubeAuth : public OAuthStreamKey { virtual bool RetryLogin() override; virtual void SaveInternal() override; virtual bool LoadInternal() override; + virtual void DeleteInternal() override; virtual void LoadUI() override; QString GenerateState();