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