|
14 | 14 | from .oauth2cli import Client, JwtAssertionCreator
|
15 | 15 | from .oauth2cli.oidc import decode_part
|
16 | 16 | from .authority import Authority, WORLD_WIDE
|
17 |
| -from .mex import send_request as mex_send_request |
| 17 | +from .mex import send_request as mex_send_request, send_request_iwa as mex_send_request_iwa |
18 | 18 | from .wstrust_request import send_request as wst_send_request
|
19 | 19 | from .wstrust_response import *
|
20 | 20 | from .token_cache import TokenCache, _get_username, _GRANT_TYPE_BROKER
|
@@ -172,6 +172,7 @@ class ClientApplication(object):
|
172 | 172 | ACQUIRE_TOKEN_FOR_CLIENT_ID = "730"
|
173 | 173 | ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE_ID = "832"
|
174 | 174 | ACQUIRE_TOKEN_INTERACTIVE = "169"
|
| 175 | + ACQUIRE_TOKEN_INTEGRATED_WINDOWS_AUTH_ID = "870" |
175 | 176 | GET_ACCOUNTS_ID = "902"
|
176 | 177 | REMOVE_ACCOUNT_ID = "903"
|
177 | 178 |
|
@@ -2114,6 +2115,78 @@ def acquire_token_by_device_flow(self, flow, claims_challenge=None, **kwargs):
|
2114 | 2115 | telemetry_context.update_telemetry(response)
|
2115 | 2116 | return response
|
2116 | 2117 |
|
| 2118 | + def acquire_token_integrated_windows_auth(self, username, scopes="openid", **kwargs): |
| 2119 | + """Gets a token for a given resource via Integrated Windows Authentication (IWA). |
| 2120 | +
|
| 2121 | + :param str username: Typically a UPN in the form of an email address. |
| 2122 | + :param str scopes: Scopes requested to access a protected API (a resource). |
| 2123 | +
|
| 2124 | + :return: A dict representing the json response from AAD: |
| 2125 | +
|
| 2126 | + - A successful response would contain "access_token" key, |
| 2127 | + - an error response would contain "error" and usually "error_description". |
| 2128 | + """ |
| 2129 | + telemetry_context = self._build_telemetry_context( |
| 2130 | + self.ACQUIRE_TOKEN_INTEGRATED_WINDOWS_AUTH_ID) |
| 2131 | + headers = telemetry_context.generate_headers() |
| 2132 | + user_realm_result = self.authority.user_realm_discovery( |
| 2133 | + username, |
| 2134 | + correlation_id=headers[msal.telemetry.CLIENT_REQUEST_ID] |
| 2135 | + ) |
| 2136 | + if user_realm_result.get("account_type") != "Federated": |
| 2137 | + raise ValueError("Server returned an unknown account type: %s" % user_realm_result.get("account_type")) |
| 2138 | + response = _clean_up(self._acquire_token_by_iwa_federated(user_realm_result, username, scopes, **kwargs)) |
| 2139 | + if response is None: # Either ADFS or not federated |
| 2140 | + raise ValueError("Integrated Windows Authentication failed for this user: %s", username) |
| 2141 | + if "access_token" in response: |
| 2142 | + response[self._TOKEN_SOURCE] = self._TOKEN_SOURCE_IDP |
| 2143 | + telemetry_context.update_telemetry(response) |
| 2144 | + return response |
| 2145 | + |
| 2146 | + def _acquire_token_by_iwa_federated( |
| 2147 | + self, user_realm_result, username, scopes="openid", **kwargs): |
| 2148 | + wstrust_endpoint = {} |
| 2149 | + if user_realm_result.get("federation_metadata_url"): |
| 2150 | + mex_endpoint = user_realm_result.get("federation_metadata_url") |
| 2151 | + logger.debug( |
| 2152 | + "Attempting mex at: %(mex_endpoint)s", |
| 2153 | + {"mex_endpoint": mex_endpoint}) |
| 2154 | + wstrust_endpoint = mex_send_request_iwa(mex_endpoint, self.http_client) |
| 2155 | + if wstrust_endpoint is None: |
| 2156 | + raise ValueError("Unable to find wstrust endpoint from MEX. " |
| 2157 | + "This typically happens when attempting MSA accounts. " |
| 2158 | + "More details available here. " |
| 2159 | + "https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki/Username-Password-Authentication") |
| 2160 | + logger.debug("wstrust_endpoint = %s", wstrust_endpoint) |
| 2161 | + wstrust_result = wst_send_request( |
| 2162 | + None, None, |
| 2163 | + user_realm_result.get("cloud_audience_urn", "urn:federation:MicrosoftOnline"), |
| 2164 | + wstrust_endpoint.get("address", |
| 2165 | + # Fallback to an AAD supplied endpoint |
| 2166 | + user_realm_result.get("federation_active_auth_url")), |
| 2167 | + wstrust_endpoint.get("action"), self.http_client) |
| 2168 | + if not ("token" in wstrust_result and "type" in wstrust_result): |
| 2169 | + raise RuntimeError("Unsuccessful RSTR. %s" % wstrust_result) |
| 2170 | + GRANT_TYPE_SAML1_1 = 'urn:ietf:params:oauth:grant-type:saml1_1-bearer' |
| 2171 | + grant_type = { |
| 2172 | + SAML_TOKEN_TYPE_V1: GRANT_TYPE_SAML1_1, |
| 2173 | + SAML_TOKEN_TYPE_V2: self.client.GRANT_TYPE_SAML2, |
| 2174 | + WSS_SAML_TOKEN_PROFILE_V1_1: GRANT_TYPE_SAML1_1, |
| 2175 | + WSS_SAML_TOKEN_PROFILE_V2: self.client.GRANT_TYPE_SAML2 |
| 2176 | + }.get(wstrust_result.get("type")) |
| 2177 | + if not grant_type: |
| 2178 | + raise RuntimeError( |
| 2179 | + "RSTR returned unknown token type: %s", wstrust_result.get("type")) |
| 2180 | + self.client.grant_assertion_encoders.setdefault( # Register a non-standard type |
| 2181 | + grant_type, self.client.encode_saml_assertion) |
| 2182 | + return self.client.obtain_token_by_assertion( |
| 2183 | + wstrust_result["token"], grant_type, scope=scopes, |
| 2184 | + on_obtaining_tokens=lambda event: self.token_cache.add(dict( |
| 2185 | + event, |
| 2186 | + environment=self.authority.instance, |
| 2187 | + username=username, # Useful in case IDT contains no such info |
| 2188 | + )), |
| 2189 | + **kwargs) |
2117 | 2190 |
|
2118 | 2191 | class ConfidentialClientApplication(ClientApplication): # server-side web app
|
2119 | 2192 | """Same as :func:`ClientApplication.__init__`,
|
|
0 commit comments