FreeRDP
Loading...
Searching...
No Matches
sso_mib_tokens.c
1/*
2 * SPDX-License-Identifier: Apache-2.0
3 * SPDX-FileCopyrightText: Copyright 2025 Siemens
4 */
5
6#include <sso-mib/sso-mib.h>
7#include <freerdp/crypto/crypto.h>
8#include <winpr/json.h>
9#include <winpr/string.h>
10
11#include "sso_mib_tokens.h"
12
13#include <freerdp/log.h>
14#define TAG CLIENT_TAG("common.sso")
15
16enum sso_mib_state
17{
18 SSO_MIB_STATE_INIT = 0,
19 SSO_MIB_STATE_FAILED = 1,
20 SSO_MIB_STATE_SUCCESS = 2,
21};
22
23struct MIBClientWrapper
24{
25 MIBClientApp* app;
26 enum sso_mib_state state;
27 pGetCommonAccessToken GetCommonAccessToken;
28};
29
30WINPR_ATTR_NODISCARD
31static BOOL sso_mib_get_avd_access_token(rdpClientContext* client_context, char** token)
32{
33 WINPR_ASSERT(client_context);
34 WINPR_ASSERT(client_context->mibClientWrapper);
35 WINPR_ASSERT(client_context->mibClientWrapper->app);
36 WINPR_ASSERT(token);
37
38 MIBAccount* account = nullptr;
39 GSList* scopes = nullptr;
40
41 BOOL rc = FALSE;
42 *token = nullptr;
43
44 account = mib_client_app_get_account_by_upn(client_context->mibClientWrapper->app, nullptr);
45 if (!account)
46 {
47 goto cleanup;
48 }
49
50 scopes = g_slist_append(scopes, g_strdup("https://www.wvd.microsoft.com/.default"));
51
52 MIBPrt* prt = mib_client_app_acquire_token_silent(client_context->mibClientWrapper->app,
53 account, scopes, nullptr, nullptr, nullptr);
54 if (prt)
55 {
56 const char* access_token = mib_prt_get_access_token(prt);
57 if (access_token)
58 {
59 *token = strdup(access_token);
60 }
61 g_object_unref(prt);
62 }
63
64 rc = TRUE && *token != nullptr;
65cleanup:
66 if (account)
67 g_object_unref(account);
68 g_slist_free_full(scopes, g_free);
69 return rc;
70}
71
72WINPR_ATTR_NODISCARD
73static BOOL sso_mib_get_rdsaad_access_token(rdpClientContext* client_context, const char* scope,
74 const char* req_cnf, char** token)
75{
76 WINPR_ASSERT(client_context);
77 WINPR_ASSERT(client_context->mibClientWrapper);
78 WINPR_ASSERT(client_context->mibClientWrapper->app);
79 WINPR_ASSERT(scope);
80 WINPR_ASSERT(token);
81 WINPR_ASSERT(req_cnf);
82
83 GSList* scopes = nullptr;
84 WINPR_JSON* json = nullptr;
85 MIBPopParams* params = nullptr;
86
87 BOOL rc = FALSE;
88 *token = nullptr;
89 BYTE* req_cnf_dec = nullptr;
90 size_t req_cnf_dec_len = 0;
91
92 scopes = g_slist_append(scopes, g_strdup(scope));
93
94 // Parse the "kid" element from req_cnf
95 crypto_base64_decode(req_cnf, strlen(req_cnf), &req_cnf_dec, &req_cnf_dec_len);
96 if (!req_cnf_dec)
97 {
98 goto cleanup;
99 }
100
101 json = WINPR_JSON_Parse((const char*)req_cnf_dec);
102 if (!json)
103 {
104 goto cleanup;
105 }
106 WINPR_JSON* prop = WINPR_JSON_GetObjectItemCaseSensitive(json, "kid");
107 if (!prop)
108 {
109 goto cleanup;
110 }
111 const char* kid = WINPR_JSON_GetStringValue(prop);
112 if (!kid)
113 {
114 goto cleanup;
115 }
116
117 params = mib_pop_params_new(MIB_AUTH_SCHEME_POP, MIB_REQUEST_METHOD_GET, "");
118 mib_pop_params_set_kid(params, kid);
119 MIBPrt* prt = mib_client_app_acquire_token_interactive(client_context->mibClientWrapper->app,
120 scopes, MIB_PROMPT_NONE, nullptr,
121 nullptr, nullptr, params);
122 if (prt)
123 {
124 const gchar* str = mib_prt_get_access_token(prt);
125 if (str)
126 *token = strdup(str);
127 rc = *token != nullptr;
128 g_object_unref(prt);
129 }
130
131cleanup:
132 if (params)
133 g_object_unref(params);
134 WINPR_JSON_Delete(json);
135 free(req_cnf_dec);
136 g_slist_free_full(scopes, g_free);
137 return rc;
138}
139
140WINPR_ATTR_NODISCARD
141static BOOL sso_mib_get_access_token(rdpContext* context, AccessTokenType tokenType, char** token,
142 size_t count, ...)
143{
144 BOOL rc = FALSE;
145 rdpClientContext* client_context = (rdpClientContext*)context;
146 WINPR_ASSERT(client_context);
147 WINPR_ASSERT(client_context->mibClientWrapper);
148
149 if (!client_context->mibClientWrapper->app)
150 {
151 const char* client_id =
152 freerdp_settings_get_string(context->settings, FreeRDP_GatewayAvdClientID);
153
154 /* Build the authority from the configured AAD host and tenant (see
155 * freerdp_utils_aad_get_wellknown) so broker SSO works against sovereign clouds
156 * (e.g. login.microsoftonline.us). Defaults resolve to MIB_AUTHORITY_COMMON. */
157 const char* base =
158 freerdp_settings_get_string(context->settings, FreeRDP_GatewayAzureActiveDirectory);
159 const BOOL useTenant =
160 freerdp_settings_get_bool(context->settings, FreeRDP_GatewayAvdUseTenantid);
161 const char* tenantid = "common";
162 if (useTenant)
163 tenantid =
164 freerdp_settings_get_string(context->settings, FreeRDP_GatewayAvdAadtenantid);
165
166 char* authority = nullptr;
167 if (base && tenantid)
168 {
169 size_t len = 0;
170 winpr_asprintf(&authority, &len, "https://%s/%s", base, tenantid);
171 if (!authority)
172 return FALSE;
173 }
174
175 client_context->mibClientWrapper->app = mib_public_client_app_new(
176 client_id, authority ? authority : MIB_AUTHORITY_COMMON, nullptr, nullptr);
177 free(authority);
178 }
179
180 if (!client_context->mibClientWrapper->app)
181 return FALSE;
182
183 const char* scope = nullptr;
184 const char* req_cnf = nullptr;
185
186 va_list ap = WINPR_C_ARRAY_INIT;
187 va_start(ap, count);
188
189 if (tokenType == ACCESS_TOKEN_TYPE_AAD)
190 {
191 scope = va_arg(ap, const char*);
192 req_cnf = va_arg(ap, const char*);
193 }
194
195 if ((client_context->mibClientWrapper->state == SSO_MIB_STATE_INIT) ||
196 (client_context->mibClientWrapper->state == SSO_MIB_STATE_SUCCESS))
197 {
198 switch (tokenType)
199 {
200 case ACCESS_TOKEN_TYPE_AVD:
201 {
202 rc = sso_mib_get_avd_access_token(client_context, token);
203 if (rc)
204 client_context->mibClientWrapper->state = SSO_MIB_STATE_SUCCESS;
205 else
206 {
207 WLog_WARN(TAG, "Getting AVD token from identity broker failed, falling back to "
208 "browser-based authentication.");
209 client_context->mibClientWrapper->state = SSO_MIB_STATE_FAILED;
210 }
211 }
212 break;
213 case ACCESS_TOKEN_TYPE_AAD:
214 {
215 // Setup scope without URL encoding for sso-mib
216 char* scope_copy = winpr_str_url_decode(scope, strlen(scope));
217 if (!scope_copy)
218 WLog_ERR(TAG, "Failed to decode scope");
219 else
220 {
221 rc =
222 sso_mib_get_rdsaad_access_token(client_context, scope_copy, req_cnf, token);
223 free(scope_copy);
224 if (rc)
225 client_context->mibClientWrapper->state = SSO_MIB_STATE_SUCCESS;
226 else
227 {
228 WLog_WARN(TAG,
229 "Getting RDS token from identity broker failed, falling back to "
230 "browser-based authentication.");
231 client_context->mibClientWrapper->state = SSO_MIB_STATE_FAILED;
232 }
233 }
234 }
235 break;
236 default:
237 break;
238 }
239 }
240 if (!rc && client_context->mibClientWrapper->GetCommonAccessToken)
241 rc = client_context->mibClientWrapper->GetCommonAccessToken(context, tokenType, token,
242 count, scope, req_cnf);
243 va_end(ap);
244
245 return rc;
246}
247
248MIBClientWrapper* sso_mib_new(rdpContext* context)
249{
250
251 MIBClientWrapper* mibClientWrapper = (MIBClientWrapper*)calloc(1, sizeof(MIBClientWrapper));
252 if (!mibClientWrapper)
253 return nullptr;
254
255 mibClientWrapper->GetCommonAccessToken = freerdp_get_common_access_token(context);
256 if (!freerdp_set_common_access_token(context, sso_mib_get_access_token))
257 {
258 sso_mib_free(mibClientWrapper);
259 return nullptr;
260 }
261 mibClientWrapper->state = SSO_MIB_STATE_INIT;
262 return mibClientWrapper;
263}
264
265void sso_mib_free(MIBClientWrapper* sso)
266{
267 if (!sso)
268 return;
269
270 if (sso->app)
271 g_object_unref(sso->app);
272
273 free(sso);
274}
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case sensitive matching.
Definition c-json.c:127
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition c-json.c:103
WINPR_ATTR_NODISCARD WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition c-json.c:142
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
Definition c-json.c:93
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.