Merge pull request #11518 from akallabeth/webview-update

Webview update
This commit is contained in:
akallabeth
2025-05-12 19:17:05 +02:00
committed by GitHub
8 changed files with 213 additions and 2994 deletions

View File

@@ -19,33 +19,17 @@ set(WITH_WEBVIEW_DEFAULT OFF)
option(WITH_WEBVIEW "Build with WebView support for AAD login popup browser" ${WITH_WEBVIEW_DEFAULT})
if(WITH_WEBVIEW)
option(WITH_WEBVIEW_QT "Build with QtWebEngine support for AAD login broweser popup" OFF)
set(SRCS sdl_webview.hpp webview_impl.hpp sdl_webview.cpp)
set(LIBS winpr)
if(WITH_WEBVIEW_QT)
find_package(Qt5 COMPONENTS WebEngineWidgets REQUIRED)
include(FetchContent)
list(APPEND SRCS qt/webview_impl.cpp)
FetchContent_Declare(webview GIT_REPOSITORY https://github.com/akallabeth/webview GIT_TAG navigation-listener SYSTEM)
FetchContent_MakeAvailable(webview)
list(APPEND LIBS Qt5::WebEngineWidgets)
else()
list(APPEND SRCS wrapper/webview.h wrapper/webview_impl.cpp)
list(APPEND SRCS wrapper/webview_impl.cpp)
if(APPLE)
find_library(WEBKIT Webkit REQUIRED)
list(APPEND LIBS ${WEBKIT})
elseif(NOT WIN32)
find_package(PkgConfig REQUIRED)
pkg_check_modules(WEBVIEW_GTK webkit2gtk-4.1)
if(NOT WEBVIEW_GTK_FOUND)
pkg_check_modules(WEBVIEW_GTK webkit2gtk-4.0 REQUIRED)
endif()
include_directories(SYSTEM ${WEBVIEW_GTK_INCLUDE_DIRS})
list(APPEND LIBS ${WEBVIEW_GTK_LIBRARIES})
endif()
endif()
list(APPEND LIBS webview::core)
else()
set(SRCS dummy.cpp)
endif()
@@ -59,7 +43,3 @@ set_property(TARGET sdl-common-aad-view PROPERTY FOLDER "Client/Common")
target_include_directories(sdl-common-aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(sdl-common-aad-view PRIVATE ${LIBS})
target_compile_definitions(sdl-common-aad-view PUBLIC ${DEFINITIONS})
if(WITH_WEBVIEW AND NOT WITH_WEBVIEW_QT)
include(WebView2)
target_link_webview2("sdl-common-aad-view")
endif()

View File

@@ -1,105 +0,0 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Popup browser for AAD authentication
*
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QApplication>
#include <QWebEngineView>
#include <QWebEngineProfile>
#include <QWebEngineUrlScheme>
#include <QWebEngineUrlSchemeHandler>
#include <QWebEngineUrlRequestJob>
#include <string>
#include <cstdlib>
#include <cstdarg>
#include <winpr/string.h>
#include <winpr/assert.h>
#include <freerdp/log.h>
#include <freerdp/build-config.h>
#include "../webview_impl.hpp"
#define TAG CLIENT_TAG("sdl.webview")
class SchemeHandler : public QWebEngineUrlSchemeHandler
{
public:
explicit SchemeHandler(QObject* parent = nullptr) : QWebEngineUrlSchemeHandler(parent)
{
}
void requestStarted(QWebEngineUrlRequestJob* request) override
{
QUrl url = request->requestUrl();
int rc = -1;
for (auto& param : url.query().split('&'))
{
QStringList pair = param.split('=');
if (pair.size() != 2 || pair[0] != QLatin1String("code"))
continue;
auto qc = pair[1];
m_code = qc.toStdString();
rc = 0;
break;
}
qApp->exit(rc);
}
[[nodiscard]] std::string code() const
{
return m_code;
}
private:
std::string m_code{};
};
bool webview_impl_run(const std::string& title, const std::string& url, std::string& code)
{
int argc = 1;
const auto vendor = QLatin1String(FREERDP_VENDOR_STRING);
const auto product = QLatin1String(FREERDP_PRODUCT_STRING);
QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme("ms-appx-web"));
std::string wtitle = title;
char* argv[] = { wtitle.data() };
QCoreApplication::setOrganizationName(vendor);
QCoreApplication::setApplicationName(product);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
SchemeHandler handler;
QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("ms-appx-web", &handler);
QWebEngineView webview;
webview.load(QUrl(QString::fromStdString(url)));
webview.show();
if (app.exec() != 0)
return false;
auto val = handler.code();
if (val.empty())
return false;
code = val;
return !code.empty();
}

View File

@@ -61,14 +61,19 @@ static BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* s
WINPR_ASSERT(token);
WINPR_UNUSED(instance);
WINPR_UNUSED(instance->context);
auto client_id = from_settings(instance->context->settings, FreeRDP_GatewayAvdClientID);
auto context = instance->context;
WINPR_UNUSED(context);
auto settings = context->settings;
WINPR_ASSERT(settings);
auto client_id = from_settings(settings, FreeRDP_GatewayAvdClientID);
std::string redirect_uri = "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f" + client_id;
*token = nullptr;
auto ep = from_aad_wellknown(instance->context, AAD_WELLKNOWN_authorization_endpoint);
auto ep = from_aad_wellknown(context, AAD_WELLKNOWN_authorization_endpoint);
auto url = ep + "?client_id=" + client_id + "&response_type=code&scope=" + scope +
"&redirect_uri=" + redirect_uri;

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@
* limitations under the License.
*/
#include "webview.h"
#include <webview.h>
#include <cassert>
#include <string>
@@ -25,45 +25,137 @@
#include <map>
#include <regex>
#include <sstream>
#include <cctype>
#include <algorithm>
#include "../webview_impl.hpp"
static std::vector<std::string> split(const std::string& input, const std::string& regex)
{
// passing -1 as the submatch index parameter performs splitting
std::regex re(regex);
std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
std::sregex_token_iterator last;
return { first, last };
}
#include <winpr/string.h>
#include <freerdp/log.h>
static std::map<std::string, std::string> urlsplit(const std::string& url)
{
auto pos = url.find('?');
if (pos == std::string::npos)
return {};
auto surl = url.substr(pos);
auto args = split(surl, "&");
#define TAG FREERDP_TAG("client.SDL.common.aad")
std::map<std::string, std::string> argmap;
for (const auto& arg : args)
class fkt_arg
{
public:
fkt_arg(const std::string& url)
{
auto kv = split(arg, "=");
if (kv.size() == 2)
argmap.insert({ kv[0], kv[1] });
auto args = urlsplit(url);
auto redir = args.find("redirect_uri");
if (redir == args.end())
{
WLog_ERR(TAG, "[Webview] url %s does not contain a redirect_uri parameter, aborting.",
url.c_str());
}
else
{
_redirect_uri = from_url_encoded_str(redir->second);
}
}
return argmap;
}
static void fkt(const std::string& url, void* arg)
bool valid() const
{
return !_redirect_uri.empty();
}
bool getCode(std::string& c) const
{
c = _code;
return !c.empty();
}
bool handle(const std::string& uri) const
{
std::string duri = from_url_encoded_str(uri);
if (duri.length() < _redirect_uri.length())
return false;
auto rc = _strnicmp(duri.c_str(), _redirect_uri.c_str(), _redirect_uri.length());
return rc == 0;
}
bool parse(const std::string& uri)
{
_args = urlsplit(uri);
auto err = _args.find("error");
if (err != _args.end())
{
auto suberr = _args.find("error_subcode");
WLog_ERR(TAG, "[Webview] %s: %s, %s: %s", err->first.c_str(), err->second.c_str(),
suberr->first.c_str(), suberr->second.c_str());
return false;
}
auto val = _args.find("code");
if (val == _args.end())
{
WLog_ERR(TAG, "[Webview] no code parameter detected in redirect URI %s", uri.c_str());
return false;
}
_code = val->second;
return true;
}
protected:
static std::string from_url_encoded_str(const std::string& str)
{
std::string cxxstr;
auto cstr = winpr_str_url_decode(str.c_str(), str.length());
if (cstr)
{
cxxstr = std::string(cstr);
free(cstr);
}
return cxxstr;
}
static std::vector<std::string> split(const std::string& input, const std::string& regex)
{
// passing -1 as the submatch index parameter performs splitting
std::regex re(regex);
std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
std::sregex_token_iterator last;
return { first, last };
}
static std::map<std::string, std::string> urlsplit(const std::string& url)
{
auto pos = url.find('?');
if (pos == std::string::npos)
return {};
pos++; // skip '?'
auto surl = url.substr(pos);
auto args = split(surl, "&");
std::map<std::string, std::string> argmap;
for (const auto& arg : args)
{
auto kv = split(arg, "=");
if (kv.size() == 2)
argmap.insert({ kv[0], kv[1] });
}
return argmap;
}
private:
std::string _redirect_uri;
std::string _code;
std::map<std::string, std::string> _args;
};
static void fkt(webview_t webview, const char* uri, webview_navigation_event_t type, void* arg)
{
auto args = urlsplit(url);
auto val = args.find("code");
if (val == args.end())
assert(arg);
auto rcode = static_cast<fkt_arg*>(arg);
if (type != WEBVIEW_LOAD_FINISHED)
return;
assert(arg);
auto rcode = static_cast<std::string*>(arg);
*rcode = val->second;
if (!rcode->handle(uri))
return;
(void)rcode->parse(uri);
webview_terminate(webview);
}
bool webview_impl_run(const std::string& title, const std::string& url, std::string& code)
@@ -71,12 +163,15 @@ bool webview_impl_run(const std::string& title, const std::string& url, std::str
webview::webview w(false, nullptr);
w.set_title(title);
w.set_size(640, 480, WEBVIEW_HINT_NONE);
w.set_size(800, 600, WEBVIEW_HINT_NONE);
std::string scheme;
w.add_scheme_handler("ms-appx-web", fkt, &scheme);
w.add_navigate_listener(fkt, &code);
fkt_arg arg(url);
if (!arg.valid())
{
return false;
}
w.add_navigation_listener(fkt, &arg);
w.navigate(url);
w.run();
return !code.empty();
return arg.getCode(code);
}

View File

@@ -3,15 +3,18 @@ set(MODULE_PREFIX "TEST_SDL")
set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.cpp)
set(${MODULE_PREFIX}_TESTS TestSDLPrefs.cpp)
set(${MODULE_PREFIX}_TESTS TestSDLPrefs.cpp TestSDLWebview.cpp)
disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR})
create_test_sourcelist(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_DRIVER} ${${MODULE_PREFIX}_TESTS})
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../aad")
include_directories("${CMAKE_CURRENT_BINARY_DIR}/../aad")
add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
set(${MODULE_PREFIX}_LIBS freerdp winpr sdl-common-prefs)
set(${MODULE_PREFIX}_LIBS freerdp freerdp-client winpr sdl-common-prefs sdl-common-aad-view)
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})

View File

@@ -0,0 +1,30 @@
#include <iostream>
#include <fstream>
#include <memory>
#include <winpr/config.h>
#include <winpr/winpr.h>
#include <sdl_webview.hpp>
int TestSDLWebview(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char* argv[])
{
#if 0
RDP_CLIENT_ENTRY_POINTS entry = {};
entry.Version = RDP_CLIENT_INTERFACE_VERSION;
entry.Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
entry.ContextSize = sizeof(rdpContext);
std::shared_ptr<rdpContext> context(freerdp_client_context_new(&entry),
[](rdpContext* ptr) { freerdp_client_context_free(ptr); });
char* token = nullptr;
if (!sdl_webview_get_access_token(context->instance, ACCESS_TOKEN_TYPE_AAD, &token, 2, "scope",
"foobar"))
{
std::cerr << "test failed!" << std::endl;
return -1;
}
#endif
return 0;
}

View File

@@ -52,7 +52,7 @@ struct rdp_aad
#ifdef WITH_AAD
static BOOL aad_fetch_wellknown(rdpAad* aad);
static BOOL aad_fetch_wellknown(wLog* log, rdpContext* context);
static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** e, char** n);
static BOOL generate_pop_key(rdpAad* aad);
@@ -180,6 +180,20 @@ static INLINE const char* aad_auth_result_to_string(DWORD code)
return "Unknown error";
}
static BOOL ensure_wellknown(rdpContext* context)
{
if (context->rdp->wellknown)
return TRUE;
rdpAad* aad = context->rdp->aad;
if (!aad)
return FALSE;
if (!aad_fetch_wellknown(aad->log, context))
return FALSE;
return context->rdp->wellknown != NULL;
}
static BOOL aad_get_nonce(rdpAad* aad)
{
BOOL ret = FALSE;
@@ -194,6 +208,9 @@ static BOOL aad_get_nonce(rdpAad* aad)
rdpRdp* rdp = aad->rdpcontext->rdp;
WINPR_ASSERT(rdp);
if (!ensure_wellknown(aad->rdpcontext))
return FALSE;
WINPR_JSON* obj = WINPR_JSON_GetObjectItem(rdp->wellknown, "token_endpoint");
if (!obj)
{
@@ -292,7 +309,7 @@ int aad_client_begin(rdpAad* aad)
return -1;
}
if (!aad_fetch_wellknown(aad))
if (!aad_fetch_wellknown(aad->log, aad->rdpcontext))
return -1;
const BOOL arc = instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AAD, &aad->access_token,
@@ -756,12 +773,19 @@ int aad_client_begin(rdpAad* aad)
WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
return -1;
}
int aad_recv(rdpAad* aad, wStream* s)
{
WINPR_ASSERT(aad);
WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
return -1;
}
static BOOL ensure_wellknown(WINPR_ATTR_UNUSED rdpContext* context)
{
return FALSE;
}
#endif
rdpAad* aad_new(rdpContext* context, rdpTransport* transport)
@@ -855,26 +879,24 @@ cleanup:
return token;
}
BOOL aad_fetch_wellknown(rdpAad* aad)
BOOL aad_fetch_wellknown(wLog* log, rdpContext* context)
{
WINPR_ASSERT(aad);
WINPR_ASSERT(aad->rdpcontext);
WINPR_ASSERT(context);
rdpRdp* rdp = aad->rdpcontext->rdp;
rdpRdp* rdp = context->rdp;
WINPR_ASSERT(rdp);
if (rdp->wellknown)
return TRUE;
const char* base =
freerdp_settings_get_string(aad->rdpcontext->settings, FreeRDP_GatewayAzureActiveDirectory);
freerdp_settings_get_string(context->settings, FreeRDP_GatewayAzureActiveDirectory);
const BOOL useTenant =
freerdp_settings_get_bool(aad->rdpcontext->settings, FreeRDP_GatewayAvdUseTenantid);
freerdp_settings_get_bool(context->settings, FreeRDP_GatewayAvdUseTenantid);
const char* tenantid = "common";
if (useTenant)
tenantid =
freerdp_settings_get_string(aad->rdpcontext->settings, FreeRDP_GatewayAvdAadtenantid);
rdp->wellknown = freerdp_utils_aad_get_wellknown(aad->log, base, tenantid);
tenantid = freerdp_settings_get_string(context->settings, FreeRDP_GatewayAvdAadtenantid);
rdp->wellknown = freerdp_utils_aad_get_wellknown(log, base, tenantid);
return rdp->wellknown ? TRUE : FALSE;
}
@@ -889,7 +911,7 @@ const char* freerdp_utils_aad_get_wellknown_custom_string(rdpContext* context, c
WINPR_ASSERT(context);
WINPR_ASSERT(context->rdp);
if (!context->rdp->wellknown)
if (!ensure_wellknown(context))
return NULL;
WINPR_JSON* obj = WINPR_JSON_GetObjectItem(context->rdp->wellknown, which);
@@ -965,7 +987,7 @@ WINPR_JSON* freerdp_utils_aad_get_wellknown_custom_object(rdpContext* context, c
WINPR_ASSERT(context);
WINPR_ASSERT(context->rdp);
if (!context->rdp->wellknown)
if (!ensure_wellknown(context))
return NULL;
return WINPR_JSON_GetObjectItem(context->rdp->wellknown, which);