|
11 | 11 | from .oauth2cli import Client, JwtAssertionCreator |
12 | 12 | from .oauth2cli.oidc import decode_part |
13 | 13 | from .authority import Authority, WORLD_WIDE |
14 | | -from .mex import send_request as mex_send_request |
| 14 | +from .mex import send_request as mex_send_request, send_request_iwa as mex_send_request_iwa |
15 | 15 | from .wstrust_request import send_request as wst_send_request |
16 | 16 | from .wstrust_response import * |
17 | 17 | from .token_cache import TokenCache, _get_username, _GRANT_TYPE_BROKER |
@@ -222,6 +222,7 @@ class ClientApplication(object): |
222 | 222 | ACQUIRE_TOKEN_FOR_CLIENT_ID = "730" |
223 | 223 | ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE_ID = "832" |
224 | 224 | ACQUIRE_TOKEN_INTERACTIVE = "169" |
| 225 | + ACQUIRE_TOKEN_INTEGRATED_WINDOWS_AUTH_ID = "870" |
225 | 226 | GET_ACCOUNTS_ID = "902" |
226 | 227 | REMOVE_ACCOUNT_ID = "903" |
227 | 228 |
|
@@ -2334,6 +2335,78 @@ def acquire_token_by_device_flow(self, flow, claims_challenge=None, **kwargs): |
2334 | 2335 | telemetry_context.update_telemetry(response) |
2335 | 2336 | return response |
2336 | 2337 |
|
| 2338 | + def acquire_token_integrated_windows_auth(self, username, scopes="openid", **kwargs): |
| 2339 | + """Gets a token for a given resource via Integrated Windows Authentication (IWA). |
| 2340 | +
|
| 2341 | + :param str username: Typically a UPN in the form of an email address. |
| 2342 | + :param str scopes: Scopes requested to access a protected API (a resource). |
| 2343 | +
|
| 2344 | + :return: A dict representing the json response from AAD: |
| 2345 | +
|
| 2346 | + - A successful response would contain "access_token" key, |
| 2347 | + - an error response would contain "error" and usually "error_description". |
| 2348 | + """ |
| 2349 | + telemetry_context = self._build_telemetry_context( |
| 2350 | + self.ACQUIRE_TOKEN_INTEGRATED_WINDOWS_AUTH_ID) |
| 2351 | + headers = telemetry_context.generate_headers() |
| 2352 | + user_realm_result = self.authority.user_realm_discovery( |
| 2353 | + username, |
| 2354 | + correlation_id=headers[msal.telemetry.CLIENT_REQUEST_ID] |
| 2355 | + ) |
| 2356 | + if user_realm_result.get("account_type") != "Federated": |
| 2357 | + raise ValueError("Server returned an unknown account type: %s" % user_realm_result.get("account_type")) |
| 2358 | + response = _clean_up(self._acquire_token_by_iwa_federated(user_realm_result, username, scopes, **kwargs)) |
| 2359 | + if response is None: # Either ADFS or not federated |
| 2360 | + raise ValueError("Integrated Windows Authentication failed for this user: %s", username) |
| 2361 | + if "access_token" in response: |
| 2362 | + response[self._TOKEN_SOURCE] = self._TOKEN_SOURCE_IDP |
| 2363 | + telemetry_context.update_telemetry(response) |
| 2364 | + return response |
| 2365 | + |
| 2366 | + def _acquire_token_by_iwa_federated( |
| 2367 | + self, user_realm_result, username, scopes="openid", **kwargs): |
| 2368 | + wstrust_endpoint = {} |
| 2369 | + if user_realm_result.get("federation_metadata_url"): |
| 2370 | + mex_endpoint = user_realm_result.get("federation_metadata_url") |
| 2371 | + logger.debug( |
| 2372 | + "Attempting mex at: %(mex_endpoint)s", |
| 2373 | + {"mex_endpoint": mex_endpoint}) |
| 2374 | + wstrust_endpoint = mex_send_request_iwa(mex_endpoint, self.http_client) |
| 2375 | + if wstrust_endpoint is None: |
| 2376 | + raise ValueError("Unable to find wstrust endpoint from MEX. " |
| 2377 | + "This typically happens when attempting MSA accounts. " |
| 2378 | + "More details available here. " |
| 2379 | + "https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki/Username-Password-Authentication") |
| 2380 | + logger.debug("wstrust_endpoint = %s", wstrust_endpoint) |
| 2381 | + wstrust_result = wst_send_request( |
| 2382 | + None, None, |
| 2383 | + user_realm_result.get("cloud_audience_urn", "urn:federation:MicrosoftOnline"), |
| 2384 | + wstrust_endpoint.get("address", |
| 2385 | + # Fallback to an AAD supplied endpoint |
| 2386 | + user_realm_result.get("federation_active_auth_url")), |
| 2387 | + wstrust_endpoint.get("action"), self.http_client) |
| 2388 | + if not ("token" in wstrust_result and "type" in wstrust_result): |
| 2389 | + raise RuntimeError("Unsuccessful RSTR. %s" % wstrust_result) |
| 2390 | + GRANT_TYPE_SAML1_1 = 'urn:ietf:params:oauth:grant-type:saml1_1-bearer' |
| 2391 | + grant_type = { |
| 2392 | + SAML_TOKEN_TYPE_V1: GRANT_TYPE_SAML1_1, |
| 2393 | + SAML_TOKEN_TYPE_V2: self.client.GRANT_TYPE_SAML2, |
| 2394 | + WSS_SAML_TOKEN_PROFILE_V1_1: GRANT_TYPE_SAML1_1, |
| 2395 | + WSS_SAML_TOKEN_PROFILE_V2: self.client.GRANT_TYPE_SAML2 |
| 2396 | + }.get(wstrust_result.get("type")) |
| 2397 | + if not grant_type: |
| 2398 | + raise RuntimeError( |
| 2399 | + "RSTR returned unknown token type: %s", wstrust_result.get("type")) |
| 2400 | + self.client.grant_assertion_encoders.setdefault( # Register a non-standard type |
| 2401 | + grant_type, self.client.encode_saml_assertion) |
| 2402 | + return self.client.obtain_token_by_assertion( |
| 2403 | + wstrust_result["token"], grant_type, scope=scopes, |
| 2404 | + on_obtaining_tokens=lambda event: self.token_cache.add(dict( |
| 2405 | + event, |
| 2406 | + environment=self.authority.instance, |
| 2407 | + username=username, # Useful in case IDT contains no such info |
| 2408 | + )), |
| 2409 | + **kwargs) |
2337 | 2410 |
|
2338 | 2411 | class ConfidentialClientApplication(ClientApplication): # server-side web app |
2339 | 2412 | """Same as :func:`ClientApplication.__init__`, |
|
0 commit comments