Milestone 5: deliver embedded RDP sessions and lifecycle hardening
This commit is contained in:
55
third_party/FreeRDP/client/SDL/common/aad/CMakeLists.txt
vendored
Normal file
55
third_party/FreeRDP/client/SDL/common/aad/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP SDL Client
|
||||
#
|
||||
# Copyright 2024 Armin Novak <anovak@thincast.com>
|
||||
# Copyright 2024 Thincast Technologies GmbH
|
||||
#
|
||||
# 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.
|
||||
|
||||
option(WITH_WEBVIEW "Build with WebView support for AAD login popup browser" OFF)
|
||||
if(WITH_WEBVIEW)
|
||||
set(SRCS sdl_webview.hpp webview_impl.hpp sdl_webview.cpp)
|
||||
set(LIBS winpr)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
set(FETCHCONTENT_SOURCE_DIR_WEBVIEW "${CMAKE_CURRENT_SOURCE_DIR}/../../../../external/webview")
|
||||
if(IS_DIRECTORY "${FETCHCONTENT_SOURCE_DIR_WEBVIEW}")
|
||||
message("Using existing source from ${FETCHCONTENT_SOURCE_DIR_WEBVIEW}")
|
||||
else()
|
||||
unset(FETCHCONTENT_SOURCE_DIR_WEBVIEW)
|
||||
endif()
|
||||
|
||||
set(WEBVIEW_BUILD_DOCS OFF CACHE INTERNAL "fetchcontent default")
|
||||
set(WEBVIEW_BUILD_SHARED_LIBRARY OFF CACHE INTERNAL "fetchcontent default")
|
||||
set(WEBVIEW_BUILD_STATIC_LIBRARY ON CACHE INTERNAL "fetchcontent default")
|
||||
set(WEBVIEW_BUILD_TESTS OFF CACHE INTERNAL "fetchcontent default")
|
||||
set(WEBVIEW_BUILD_EXAMPLES OFF CACHE INTERNAL "fetchcontent default")
|
||||
FetchContent_Declare(webview GIT_REPOSITORY https://github.com/akallabeth/webview GIT_TAG navigation-listener SYSTEM)
|
||||
FetchContent_MakeAvailable(webview)
|
||||
|
||||
list(APPEND SRCS webview_impl.cpp)
|
||||
|
||||
list(APPEND LIBS webview::core)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
add_library(sdl-common-aad-view STATIC ${SRCS})
|
||||
set_property(TARGET sdl-common-aad-view PROPERTY FOLDER "Client/SDL/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})
|
||||
else()
|
||||
add_library(sdl-common-aad-view STATIC dummy.cpp)
|
||||
endif()
|
||||
0
third_party/FreeRDP/client/SDL/common/aad/dummy.cpp
vendored
Normal file
0
third_party/FreeRDP/client/SDL/common/aad/dummy.cpp
vendored
Normal file
155
third_party/FreeRDP/client/SDL/common/aad/sdl_webview.cpp
vendored
Normal file
155
third_party/FreeRDP/client/SDL/common/aad/sdl_webview.cpp
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 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 <string>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#include <winpr/string.h>
|
||||
#include <freerdp/log.h>
|
||||
#include <freerdp/utils/aad.h>
|
||||
|
||||
#include "sdl_webview.hpp"
|
||||
#include "webview_impl.hpp"
|
||||
|
||||
#define TAG CLIENT_TAG("SDL.webview")
|
||||
|
||||
static std::string from_settings(const rdpSettings* settings, FreeRDP_Settings_Keys_String id)
|
||||
{
|
||||
auto val = freerdp_settings_get_string(settings, id);
|
||||
if (!val)
|
||||
{
|
||||
WLog_WARN(TAG, "Settings key %s is nullptr", freerdp_settings_get_name_for_key(id));
|
||||
return "";
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static std::string from_aad_wellknown(rdpContext* context, AAD_WELLKNOWN_VALUES which)
|
||||
{
|
||||
auto val = freerdp_utils_aad_get_wellknown_string(context, which);
|
||||
|
||||
if (!val)
|
||||
{
|
||||
WLog_WARN(TAG, "[wellknown] key %s is nullptr",
|
||||
freerdp_utils_aad_wellknwon_value_name(which));
|
||||
return "";
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope,
|
||||
const char* req_cnf, char** token)
|
||||
{
|
||||
WINPR_ASSERT(instance);
|
||||
WINPR_ASSERT(scope);
|
||||
WINPR_ASSERT(req_cnf);
|
||||
WINPR_ASSERT(token);
|
||||
|
||||
WINPR_UNUSED(instance);
|
||||
|
||||
auto context = instance->context;
|
||||
WINPR_UNUSED(context);
|
||||
|
||||
auto settings = context->settings;
|
||||
WINPR_ASSERT(settings);
|
||||
|
||||
std::shared_ptr<char> request(freerdp_client_get_aad_url((rdpClientContext*)instance->context,
|
||||
FREERDP_CLIENT_AAD_AUTH_REQUEST,
|
||||
scope),
|
||||
free);
|
||||
const std::string title = "FreeRDP WebView - AAD access token";
|
||||
std::string code;
|
||||
auto rc = webview_impl_run(title, request.get(), code);
|
||||
if (!rc || code.empty())
|
||||
return FALSE;
|
||||
|
||||
std::shared_ptr<char> token_request(
|
||||
freerdp_client_get_aad_url((rdpClientContext*)instance->context,
|
||||
FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope, code.c_str(), req_cnf),
|
||||
free);
|
||||
return client_common_get_access_token(instance, token_request.get(), token);
|
||||
}
|
||||
|
||||
static BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token)
|
||||
{
|
||||
WINPR_ASSERT(token);
|
||||
WINPR_ASSERT(instance);
|
||||
WINPR_ASSERT(instance->context);
|
||||
|
||||
std::shared_ptr<char> request(freerdp_client_get_aad_url((rdpClientContext*)instance->context,
|
||||
FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST),
|
||||
free);
|
||||
|
||||
const std::string title = "FreeRDP WebView - AVD access token";
|
||||
std::string code;
|
||||
auto rc = webview_impl_run(title, request.get(), code);
|
||||
if (!rc || code.empty())
|
||||
return FALSE;
|
||||
|
||||
std::shared_ptr<char> token_request(
|
||||
freerdp_client_get_aad_url((rdpClientContext*)instance->context,
|
||||
FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST, code.c_str()),
|
||||
free);
|
||||
return client_common_get_access_token(instance, token_request.get(), token);
|
||||
}
|
||||
|
||||
BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
|
||||
size_t count, ...)
|
||||
{
|
||||
WINPR_ASSERT(instance);
|
||||
WINPR_ASSERT(token);
|
||||
switch (tokenType)
|
||||
{
|
||||
case ACCESS_TOKEN_TYPE_AAD:
|
||||
{
|
||||
if (count < 2)
|
||||
{
|
||||
WLog_ERR(TAG,
|
||||
"ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
|
||||
", aborting",
|
||||
count);
|
||||
return FALSE;
|
||||
}
|
||||
else if (count > 2)
|
||||
WLog_WARN(TAG,
|
||||
"ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
|
||||
", ignoring",
|
||||
count);
|
||||
va_list ap = {};
|
||||
va_start(ap, count);
|
||||
const char* scope = va_arg(ap, const char*);
|
||||
const char* req_cnf = va_arg(ap, const char*);
|
||||
const BOOL rc = sdl_webview_get_rdsaad_access_token(instance, scope, req_cnf, token);
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
case ACCESS_TOKEN_TYPE_AVD:
|
||||
if (count != 0)
|
||||
WLog_WARN(TAG,
|
||||
"ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
|
||||
", ignoring",
|
||||
count);
|
||||
return sdl_webview_get_avd_access_token(instance, token);
|
||||
default:
|
||||
WLog_ERR(TAG, "Unexpected value for AccessTokenType [%" PRIuz "], aborting", tokenType);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
34
third_party/FreeRDP/client/SDL/common/aad/sdl_webview.hpp
vendored
Normal file
34
third_party/FreeRDP/client/SDL/common/aad/sdl_webview.hpp
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
[[nodiscard]] BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType,
|
||||
char** token, size_t count, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
179
third_party/FreeRDP/client/SDL/common/aad/webview_impl.cpp
vendored
Normal file
179
third_party/FreeRDP/client/SDL/common/aad/webview_impl.cpp
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* 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 <webview.h>
|
||||
|
||||
#include "webview_impl.hpp"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <freerdp/log.h>
|
||||
#include <winpr/string.h>
|
||||
|
||||
#define TAG FREERDP_TAG("client.SDL.common.aad")
|
||||
|
||||
class fkt_arg
|
||||
{
|
||||
public:
|
||||
fkt_arg(const std::string& url)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
assert(arg);
|
||||
auto rcode = static_cast<fkt_arg*>(arg);
|
||||
|
||||
if (type != WEBVIEW_LOAD_FINISHED)
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
webview::webview w(false, nullptr);
|
||||
|
||||
w.set_title(title);
|
||||
w.set_size(800, 600, WEBVIEW_HINT_NONE);
|
||||
|
||||
fkt_arg arg(url);
|
||||
if (!arg.valid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
w.add_navigation_listener(fkt, &arg);
|
||||
w.navigate(url);
|
||||
w.run();
|
||||
return arg.getCode(code);
|
||||
}
|
||||
25
third_party/FreeRDP/client/SDL/common/aad/webview_impl.hpp
vendored
Normal file
25
third_party/FreeRDP/client/SDL/common/aad/webview_impl.hpp
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
[[nodiscard]] bool webview_impl_run(const std::string& title, const std::string& url,
|
||||
std::string& code);
|
||||
Reference in New Issue
Block a user