diff --git a/.vscode/cspell.json b/.vscode/cspell.json index f1e127fcbc04..0894638b6790 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -316,6 +316,7 @@ "mktime", "mlindex", "msal", + "msauth", "msrest", "msrestazure", "MSSQL", diff --git a/sdk/identity/azure-identity-broker/CHANGELOG.md b/sdk/identity/azure-identity-broker/CHANGELOG.md index adc46c7706e2..22646cbf4b68 100644 --- a/sdk/identity/azure-identity-broker/CHANGELOG.md +++ b/sdk/identity/azure-identity-broker/CHANGELOG.md @@ -1,14 +1,10 @@ # Release History -## 1.2.1 (Unreleased) +## 1.3.0b1 (2024-11-05) ### Features Added -### Breaking Changes - -### Bugs Fixed - -### Other Changes +- Added broker on macOS support. ## 1.2.0 (2024-10-08) diff --git a/sdk/identity/azure-identity-broker/README.md b/sdk/identity/azure-identity-broker/README.md index 5fc069418f23..7b61b6f3343a 100644 --- a/sdk/identity/azure-identity-broker/README.md +++ b/sdk/identity/azure-identity-broker/README.md @@ -4,7 +4,9 @@ This package extends the [Azure Identity][azure_identity] library by providing supplemental credentials for authenticating via an authentication broker. -An authentication broker is an application that runs on a user’s machine that manages the authentication handshakes and token maintenance for connected accounts. Currently, only the Windows authentication broker, Web Account Manager (WAM), is supported. +An authentication broker is an application that runs on a user’s machine that manages the authentication handshakes and token maintenance for connected accounts. Currently, only the following brokers are supported: +- Web Account Manager (WAM) on Windows +- Company Portal on macOS [Source code][source_code] | [Package (PyPI)][azure_identity_broker] | [API reference documentation][ref_docs] | [Microsoft Entra ID documentation][entra_id] @@ -28,15 +30,16 @@ When authenticating interactively via `InteractiveBrowserBrokerCredential`, a pa ## Microsoft account (MSA) passthrough -Microsoft accounts (MSA) are personal accounts created by users to access Microsoft services. MSA passthrough is a legacy configuration which enables users to get tokens to resources which normally don't accept MSA logins. This feature is only available to first-party applications. Users authenticating with an application that is configured to use MSA passthrough can set `enable_msa_passthrough` to `True` inside `InteractiveBrowserBrokerCredential` to allow these personal accounts to be listed by WAM. +Microsoft accounts (MSA) are personal accounts created by users to access Microsoft services. MSA passthrough is a legacy configuration which enables users to get tokens to resources which normally don't accept MSA logins. This feature is only available to first-party applications. Users authenticating with an application that is configured to use MSA passthrough can set `enable_msa_passthrough` to `True` inside `InteractiveBrowserBrokerCredential` to allow these personal accounts to be listed by broker. ## Redirect URIs -Microsoft Entra applications rely on redirect URIs to determine where to send the authentication response after a user has logged in. To enable brokered authentication through WAM, a redirect URI matching the following pattern should be registered to the application: +Microsoft Entra applications rely on redirect URIs to determine where to send the authentication response after a user has logged in. To enable brokered authentication through broker, a redirect URI matching the following pattern should be registered to the application: -``` -ms-appx-web://Microsoft.AAD.BrokerPlugin/{client_id} -``` +* ``ms-appx-web://Microsoft.AAD.BrokerPlugin/your_client_id`` +if your app is expected to run on Windows 10+ +* ``msauth.com.msauth.unsignedapp://auth`` +if your app is expected to run on Mac ## Examples diff --git a/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py b/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py index affe1f3291ea..118366f8f2bb 100644 --- a/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py +++ b/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. # ------------------------------------ import socket +import sys from typing import Dict, Any, Mapping, Union import msal @@ -32,8 +33,10 @@ class PopTokenRequestOptions(TokenRequestOptions): class InteractiveBrowserBrokerCredential(_InteractiveBrowserCredential): """Uses an authentication broker to interactively sign in a user. - Currently, only the Windows authentication broker, Web Account Manager (WAM), is supported. Users on macOS and Linux - will be authenticated through a browser. + Currently, only the following brokers are supported: + - Web Account Manager (WAM) on Windows + - Company Portal on macOS + Users on Linux will be authenticated through the browser. :func:`~get_token` opens a browser to a login URL provided by Microsoft Entra ID and authenticates a user there with the authorization code flow, using PKCE (Proof Key for Code Exchange) internally to protect the code. @@ -86,48 +89,79 @@ def _request_token(self, *scopes: str, **kwargs: Any) -> Dict: auth_scheme = msal.PopAuthScheme( http_method=pop["resource_request_method"], url=pop["resource_request_url"], nonce=pop["nonce"] ) - - if self._use_default_broker_account: + if sys.platform.startswith("win"): + if self._use_default_broker_account: + try: + result = app.acquire_token_interactive( + scopes=scopes, + login_hint=self._login_hint, + claims_challenge=claims, + timeout=self._timeout, + prompt=msal.Prompt.NONE, + port=port, + parent_window_handle=self._parent_window_handle, + enable_msa_passthrough=self._enable_msa_passthrough, + auth_scheme=auth_scheme, + ) + if "access_token" in result: + return result + except socket.error: + pass + try: + result = app.acquire_token_interactive( + scopes=scopes, + login_hint=self._login_hint, + claims_challenge=claims, + timeout=self._timeout, + prompt="select_account", + port=port, + parent_window_handle=self._parent_window_handle, + enable_msa_passthrough=self._enable_msa_passthrough, + auth_scheme=auth_scheme, + ) + except socket.error as ex: + raise CredentialUnavailableError(message="Couldn't start an HTTP server.") from ex + if "access_token" not in result and "error_description" in result: + if within_dac.get(): + raise CredentialUnavailableError(message=result["error_description"]) + raise ClientAuthenticationError(message=result.get("error_description")) + if "access_token" not in result: + if within_dac.get(): + raise CredentialUnavailableError(message="Failed to authenticate user") + raise ClientAuthenticationError(message="Failed to authenticate user") + else: try: result = app.acquire_token_interactive( scopes=scopes, login_hint=self._login_hint, claims_challenge=claims, timeout=self._timeout, - prompt=msal.Prompt.NONE, + prompt="select_account", port=port, parent_window_handle=self._parent_window_handle, enable_msa_passthrough=self._enable_msa_passthrough, auth_scheme=auth_scheme, ) + except Exception: # pylint: disable=broad-except + app = self._disable_broker_on_app(**kwargs) + result = app.acquire_token_interactive( + scopes=scopes, + login_hint=self._login_hint, + claims_challenge=claims, + timeout=self._timeout, + prompt="select_account", + port=port, + parent_window_handle=self._parent_window_handle, + enable_msa_passthrough=self._enable_msa_passthrough, + ) if "access_token" in result: return result - except socket.error: - pass - try: - result = app.acquire_token_interactive( - scopes=scopes, - login_hint=self._login_hint, - claims_challenge=claims, - timeout=self._timeout, - prompt="select_account", - port=port, - parent_window_handle=self._parent_window_handle, - enable_msa_passthrough=self._enable_msa_passthrough, - auth_scheme=auth_scheme, - ) - except socket.error as ex: - raise CredentialUnavailableError(message="Couldn't start an HTTP server.") from ex - if "access_token" not in result and "error_description" in result: - if within_dac.get(): - raise CredentialUnavailableError(message=result["error_description"]) - raise ClientAuthenticationError(message=result.get("error_description")) - if "access_token" not in result: - if within_dac.get(): - raise CredentialUnavailableError(message="Failed to authenticate user") - raise ClientAuthenticationError(message="Failed to authenticate user") - - # base class will raise for other errors + if "error_description" in result: + if within_dac.get(): + # pylint: disable=raise-missing-from + raise CredentialUnavailableError(message=result["error_description"]) + # pylint: disable=raise-missing-from + raise ClientAuthenticationError(message=result.get("error_description")) return result def _get_app(self, **kwargs: Any) -> msal.ClientApplication: @@ -160,7 +194,43 @@ def _get_app(self, **kwargs: Any) -> msal.ClientApplication: http_client=self._client, instance_discovery=self._instance_discovery, enable_broker_on_windows=True, + enable_broker_on_mac=True, enable_pii_log=self._enable_support_logging, ) return client_applications_map[tenant_id] + + def _disable_broker_on_app(self, **kwargs: Any) -> msal.ClientApplication: + tenant_id = resolve_tenant( + self._tenant_id, additionally_allowed_tenants=self._additionally_allowed_tenants, **kwargs + ) + + client_applications_map = self._client_applications + capabilities = None + token_cache = self._cache + + app_class = msal.PublicClientApplication + + if kwargs.get("enable_cae"): + client_applications_map = self._cae_client_applications + capabilities = ["CP1"] + token_cache = self._cae_cache + + if not token_cache: + token_cache = self._initialize_cache(is_cae=bool(kwargs.get("enable_cae"))) + + client_applications_map[tenant_id] = app_class( + client_id=self._client_id, + client_credential=self._client_credential, + client_capabilities=capabilities, + authority="{}/{}".format(self._authority, tenant_id), + azure_region=self._regional_authority, + token_cache=token_cache, + http_client=self._client, + instance_discovery=self._instance_discovery, + enable_broker_on_windows=False, + enable_broker_on_mac=False, + enable_pii_log=self._enable_support_logging, + ) + + return client_applications_map[tenant_id] diff --git a/sdk/identity/azure-identity-broker/azure/identity/broker/_version.py b/sdk/identity/azure-identity-broker/azure/identity/broker/_version.py index 6c16839fae26..638952e83679 100644 --- a/sdk/identity/azure-identity-broker/azure/identity/broker/_version.py +++ b/sdk/identity/azure-identity-broker/azure/identity/broker/_version.py @@ -2,4 +2,4 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -VERSION = "1.2.1" +VERSION = "1.3.0b1"