From 5d2b1466fc4febf1fb415ce637227593cdd98e6b Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Fri, 1 Nov 2024 13:53:55 -0700 Subject: [PATCH 1/8] Broker on mac support --- .../azure-identity-broker/CHANGELOG.md | 4 +- .../azure/identity/broker/_browser.py | 127 +++++++++++++----- .../azure/identity/broker/_version.py | 2 +- 3 files changed, 101 insertions(+), 32 deletions(-) diff --git a/sdk/identity/azure-identity-broker/CHANGELOG.md b/sdk/identity/azure-identity-broker/CHANGELOG.md index adc46c7706e29..df14ad525424f 100644 --- a/sdk/identity/azure-identity-broker/CHANGELOG.md +++ b/sdk/identity/azure-identity-broker/CHANGELOG.md @@ -1,9 +1,11 @@ # Release History -## 1.2.1 (Unreleased) +## 1.3.0b1 (Unreleased) ### Features Added +- Added broker on MacOs support. + ### Breaking Changes ### Bugs Fixed 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 affe1f3291ea7..ba66cc8e6cfef 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 @@ -86,49 +87,78 @@ 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") + return result + 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 as ex: # 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 - return result + elif "error_description" in result: + if within_dac.get(): + raise CredentialUnavailableError(message=result["error_description"]) + raise ClientAuthenticationError(message=result.get("error_description")) def _get_app(self, **kwargs: Any) -> msal.ClientApplication: tenant_id = resolve_tenant( @@ -160,6 +190,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"))) + + if tenant_id not in client_applications_map: + 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=True, + enable_broker_on_mac=False, enable_pii_log=self._enable_support_logging, ) 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 6c16839fae268..638952e836792 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" From 67c4abc21ea04f12d65502520e01be0c2d8d2a0b Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Fri, 1 Nov 2024 16:14:28 -0700 Subject: [PATCH 2/8] update --- .../azure-identity-broker/azure/identity/broker/_browser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ba66cc8e6cfef..2268969ccf42a 100644 --- a/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py +++ b/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py @@ -127,7 +127,6 @@ def _request_token(self, *scopes: str, **kwargs: Any) -> Dict: if within_dac.get(): raise CredentialUnavailableError(message="Failed to authenticate user") raise ClientAuthenticationError(message="Failed to authenticate user") - return result else: try: result = app.acquire_token_interactive( @@ -141,7 +140,7 @@ def _request_token(self, *scopes: str, **kwargs: Any) -> Dict: enable_msa_passthrough=self._enable_msa_passthrough, auth_scheme=auth_scheme, ) - except Exception as ex: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except app = self._disable_broker_on_app(**kwargs) result = app.acquire_token_interactive( scopes=scopes, @@ -159,6 +158,7 @@ def _request_token(self, *scopes: str, **kwargs: Any) -> Dict: if within_dac.get(): raise CredentialUnavailableError(message=result["error_description"]) raise ClientAuthenticationError(message=result.get("error_description")) + return result def _get_app(self, **kwargs: Any) -> msal.ClientApplication: tenant_id = resolve_tenant( From 3b998f5830820c54c3a4b04198312f65ac62bb58 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Fri, 1 Nov 2024 17:25:12 -0700 Subject: [PATCH 3/8] update --- .../azure/identity/broker/_browser.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 2268969ccf42a..50f4ab3d85bb4 100644 --- a/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py +++ b/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py @@ -154,10 +154,14 @@ def _request_token(self, *scopes: str, **kwargs: Any) -> Dict: ) if "access_token" in result: return result - elif "error_description" in result: + if "error_description" in result: if within_dac.get(): - raise CredentialUnavailableError(message=result["error_description"]) - raise ClientAuthenticationError(message=result.get("error_description")) + raise CredentialUnavailableError( + message=result["error_description"] + ) # pylint: disable=raise-missing-from + raise ClientAuthenticationError( + message=result.get("error_description") + ) # pylint: disable=raise-missing-from return result def _get_app(self, **kwargs: Any) -> msal.ClientApplication: From d87d56d05b31bea4ebc82f5b319128fd880d4fb8 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Mon, 4 Nov 2024 06:54:36 -0800 Subject: [PATCH 4/8] update --- .../azure/identity/broker/_browser.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 50f4ab3d85bb4..99338f5cf101a 100644 --- a/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py +++ b/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py @@ -156,12 +156,10 @@ def _request_token(self, *scopes: str, **kwargs: Any) -> Dict: return result if "error_description" in result: if within_dac.get(): - raise CredentialUnavailableError( - message=result["error_description"] - ) # pylint: disable=raise-missing-from - raise ClientAuthenticationError( - message=result.get("error_description") - ) # pylint: disable=raise-missing-from + # 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: From dc7679d2f37af587221a9f12a8fdfe4424287976 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Mon, 4 Nov 2024 11:18:38 -0800 Subject: [PATCH 5/8] update --- .../azure-identity-broker/CHANGELOG.md | 2 +- .../azure/identity/broker/_browser.py | 27 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/sdk/identity/azure-identity-broker/CHANGELOG.md b/sdk/identity/azure-identity-broker/CHANGELOG.md index df14ad525424f..828267068e0d7 100644 --- a/sdk/identity/azure-identity-broker/CHANGELOG.md +++ b/sdk/identity/azure-identity-broker/CHANGELOG.md @@ -4,7 +4,7 @@ ### Features Added -- Added broker on MacOs support. +- Added broker on macOS support. ### Breaking Changes 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 99338f5cf101a..cf79d1ee28c58 100644 --- a/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py +++ b/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py @@ -217,19 +217,18 @@ def _disable_broker_on_app(self, **kwargs: Any) -> msal.ClientApplication: if not token_cache: token_cache = self._initialize_cache(is_cae=bool(kwargs.get("enable_cae"))) - if tenant_id not in client_applications_map: - 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=True, - enable_broker_on_mac=False, - enable_pii_log=self._enable_support_logging, - ) + 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] From ed4b380791daa817851e1eef1302bef92dde9d6e Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Mon, 4 Nov 2024 13:23:17 -0800 Subject: [PATCH 6/8] update --- sdk/identity/azure-identity-broker/CHANGELOG.md | 8 +------- .../azure/identity/broker/_browser.py | 6 ++++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/sdk/identity/azure-identity-broker/CHANGELOG.md b/sdk/identity/azure-identity-broker/CHANGELOG.md index 828267068e0d7..22646cbf4b684 100644 --- a/sdk/identity/azure-identity-broker/CHANGELOG.md +++ b/sdk/identity/azure-identity-broker/CHANGELOG.md @@ -1,17 +1,11 @@ # Release History -## 1.3.0b1 (Unreleased) +## 1.3.0b1 (2024-11-05) ### Features Added - Added broker on macOS support. -### Breaking Changes - -### Bugs Fixed - -### Other Changes - ## 1.2.0 (2024-10-08) ### Other Changes 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 cf79d1ee28c58..118366f8f2bba 100644 --- a/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py +++ b/sdk/identity/azure-identity-broker/azure/identity/broker/_browser.py @@ -33,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. From c745c75addc2f9e2f6f30c160e21f6ab04bdfe04 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Mon, 4 Nov 2024 15:28:20 -0800 Subject: [PATCH 7/8] update readme --- sdk/identity/azure-identity-broker/README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sdk/identity/azure-identity-broker/README.md b/sdk/identity/azure-identity-broker/README.md index 5fc069418f235..7b61b6f3343a6 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 From 26513a425b95bf627f22bd7603221a1b75b39e83 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Mon, 4 Nov 2024 16:55:58 -0800 Subject: [PATCH 8/8] update --- .vscode/cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/cspell.json b/.vscode/cspell.json index f1e127fcbc049..0894638b67906 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -316,6 +316,7 @@ "mktime", "mlindex", "msal", + "msauth", "msrest", "msrestazure", "MSSQL",