/* * SPDX-License-Identifier: Apache-2.0 * SPDX-FileCopyrightText: Copyright 2025 Siemens */ #include #include #include #include "sso_mib_tokens.h" #include #define TAG CLIENT_TAG("common.sso") enum sso_mib_state { SSO_MIB_STATE_INIT = 0, SSO_MIB_STATE_FAILED = 1, SSO_MIB_STATE_SUCCESS = 2, }; struct MIBClientWrapper { MIBClientApp* app; enum sso_mib_state state; pGetCommonAccessToken GetCommonAccessToken; }; static BOOL sso_mib_get_avd_access_token(rdpClientContext* client_context, char** token) { WINPR_ASSERT(client_context); WINPR_ASSERT(client_context->mibClientWrapper); WINPR_ASSERT(client_context->mibClientWrapper->app); WINPR_ASSERT(token); MIBAccount* account = nullptr; GSList* scopes = nullptr; BOOL rc = FALSE; *token = nullptr; account = mib_client_app_get_account_by_upn(client_context->mibClientWrapper->app, nullptr); if (!account) { goto cleanup; } scopes = g_slist_append(scopes, g_strdup("https://www.wvd.microsoft.com/.default")); MIBPrt* prt = mib_client_app_acquire_token_silent(client_context->mibClientWrapper->app, account, scopes, nullptr, nullptr, nullptr); if (prt) { const char* access_token = mib_prt_get_access_token(prt); if (access_token) { *token = strdup(access_token); } g_object_unref(prt); } rc = TRUE && *token != nullptr; cleanup: if (account) g_object_unref(account); g_slist_free_full(scopes, g_free); return rc; } static BOOL sso_mib_get_rdsaad_access_token(rdpClientContext* client_context, const char* scope, const char* req_cnf, char** token) { WINPR_ASSERT(client_context); WINPR_ASSERT(client_context->mibClientWrapper); WINPR_ASSERT(client_context->mibClientWrapper->app); WINPR_ASSERT(scope); WINPR_ASSERT(token); WINPR_ASSERT(req_cnf); GSList* scopes = nullptr; WINPR_JSON* json = nullptr; MIBPopParams* params = nullptr; BOOL rc = FALSE; *token = nullptr; BYTE* req_cnf_dec = nullptr; size_t req_cnf_dec_len = 0; scopes = g_slist_append(scopes, g_strdup(scope)); // Parse the "kid" element from req_cnf crypto_base64_decode(req_cnf, strlen(req_cnf) + 1, &req_cnf_dec, &req_cnf_dec_len); if (!req_cnf_dec) { goto cleanup; } json = WINPR_JSON_Parse((const char*)req_cnf_dec); if (!json) { goto cleanup; } WINPR_JSON* prop = WINPR_JSON_GetObjectItemCaseSensitive(json, "kid"); if (!prop) { goto cleanup; } const char* kid = WINPR_JSON_GetStringValue(prop); if (!kid) { goto cleanup; } params = mib_pop_params_new(MIB_AUTH_SCHEME_POP, MIB_REQUEST_METHOD_GET, ""); mib_pop_params_set_kid(params, kid); MIBPrt* prt = mib_client_app_acquire_token_interactive(client_context->mibClientWrapper->app, scopes, MIB_PROMPT_NONE, nullptr, nullptr, nullptr, params); if (prt) { *token = strdup(mib_prt_get_access_token(prt)); rc = TRUE; g_object_unref(prt); } cleanup: if (params) g_object_unref(params); WINPR_JSON_Delete(json); free(req_cnf_dec); g_slist_free_full(scopes, g_free); return rc; } static BOOL sso_mib_get_access_token(rdpContext* context, AccessTokenType tokenType, char** token, size_t count, ...) { BOOL rc = FALSE; rdpClientContext* client_context = (rdpClientContext*)context; WINPR_ASSERT(client_context); WINPR_ASSERT(client_context->mibClientWrapper); if (!client_context->mibClientWrapper->app) { const char* client_id = freerdp_settings_get_string(context->settings, FreeRDP_GatewayAvdClientID); client_context->mibClientWrapper->app = mib_public_client_app_new(client_id, MIB_AUTHORITY_COMMON, nullptr, nullptr); } if (!client_context->mibClientWrapper->app) return FALSE; const char* scope = nullptr; const char* req_cnf = nullptr; va_list ap = WINPR_C_ARRAY_INIT; va_start(ap, count); if (tokenType == ACCESS_TOKEN_TYPE_AAD) { scope = va_arg(ap, const char*); req_cnf = va_arg(ap, const char*); } if ((client_context->mibClientWrapper->state == SSO_MIB_STATE_INIT) || (client_context->mibClientWrapper->state == SSO_MIB_STATE_SUCCESS)) { switch (tokenType) { case ACCESS_TOKEN_TYPE_AVD: { rc = sso_mib_get_avd_access_token(client_context, token); if (rc) client_context->mibClientWrapper->state = SSO_MIB_STATE_SUCCESS; else { WLog_WARN(TAG, "Getting AVD token from identity broker failed, falling back to " "browser-based authentication."); client_context->mibClientWrapper->state = SSO_MIB_STATE_FAILED; } } break; case ACCESS_TOKEN_TYPE_AAD: { // Setup scope without URL encoding for sso-mib char* scope_copy = winpr_str_url_decode(scope, strlen(scope)); if (!scope_copy) WLog_ERR(TAG, "Failed to decode scope"); else { rc = sso_mib_get_rdsaad_access_token(client_context, scope_copy, req_cnf, token); free(scope_copy); if (rc) client_context->mibClientWrapper->state = SSO_MIB_STATE_SUCCESS; else { WLog_WARN(TAG, "Getting RDS token from identity broker failed, falling back to " "browser-based authentication."); client_context->mibClientWrapper->state = SSO_MIB_STATE_FAILED; } } } break; default: break; } } if (!rc && client_context->mibClientWrapper->GetCommonAccessToken) rc = client_context->mibClientWrapper->GetCommonAccessToken(context, tokenType, token, count, scope, req_cnf); va_end(ap); return rc; } MIBClientWrapper* sso_mib_new(rdpContext* context) { MIBClientWrapper* mibClientWrapper = (MIBClientWrapper*)calloc(1, sizeof(MIBClientWrapper)); if (!mibClientWrapper) return nullptr; mibClientWrapper->GetCommonAccessToken = freerdp_get_common_access_token(context); if (!freerdp_set_common_access_token(context, sso_mib_get_access_token)) { sso_mib_free(mibClientWrapper); return nullptr; } mibClientWrapper->state = SSO_MIB_STATE_INIT; return mibClientWrapper; } void sso_mib_free(MIBClientWrapper* sso) { if (!sso) return; if (sso->app) g_object_unref(sso->app); free(sso); }