From 9f1bf3a83f4f3b7592e8af03dc9681aae121dc01 Mon Sep 17 00:00:00 2001 From: Xiang Yan Date: Wed, 26 Jul 2023 16:54:06 -0700 Subject: [PATCH] Identity not use is chained (#31328) * not use is_chained * update --- .../azure/identity/_credentials/azd_cli.py | 12 +++---- .../azure/identity/_credentials/azure_cli.py | 12 +++---- .../identity/_credentials/azure_powershell.py | 10 +++--- .../azure/identity/_credentials/browser.py | 7 ++--- .../azure/identity/_credentials/default.py | 31 +++++++------------ .../azure/identity/_credentials/silent.py | 5 ++- .../azure/identity/_credentials/vscode.py | 5 ++- .../azure/identity/_internal/__init__.py | 2 ++ .../identity/_internal/shared_token_cache.py | 1 - .../azure/identity/_internal/utils.py | 1 + .../identity/aio/_credentials/azd_cli.py | 12 +++---- .../identity/aio/_credentials/azure_cli.py | 12 +++---- .../aio/_credentials/azure_powershell.py | 4 +-- .../identity/aio/_credentials/default.py | 18 +++++------ .../azure/identity/aio/_credentials/vscode.py | 9 +++++- .../tests/test_shared_cache_credential.py | 24 ++++++++++---- 16 files changed, 81 insertions(+), 84 deletions(-) diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/azd_cli.py b/sdk/identity/azure-identity/azure/identity/_credentials/azd_cli.py index cd1dd042ff53..9cf4f244fe2a 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/azd_cli.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/azd_cli.py @@ -16,7 +16,7 @@ from azure.core.exceptions import ClientAuthenticationError from .. import CredentialUnavailableError -from .._internal import resolve_tenant +from .._internal import resolve_tenant, within_dac from .._internal.decorators import log_get_token CLI_NOT_FOUND = ( @@ -75,11 +75,9 @@ def __init__( tenant_id: str = "", additionally_allowed_tenants: Optional[List[str]] = None, process_timeout: int = 10, - _is_chained: bool = False, ) -> None: self.tenant_id = tenant_id - self._is_chained = _is_chained self._additionally_allowed_tenants = additionally_allowed_tenants or [] self._process_timeout = process_timeout @@ -123,7 +121,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: ) if tenant: command += " --tenant-id " + tenant - output = _run_command(command, self._process_timeout, _is_chained=self._is_chained) + output = _run_command(command, self._process_timeout) token = parse_token(output) if not token: @@ -133,7 +131,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: f"To mitigate this issue, please refer to the troubleshooting guidelines here at " f"https://aka.ms/azsdk/python/identity/azdevclicredential/troubleshoot." ) - if self._is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=message) raise ClientAuthenticationError(message=message) @@ -189,7 +187,7 @@ def sanitize_output(output: str) -> str: return re.sub(r"\"token\": \"(.*?)(\"|$)", "****", output) -def _run_command(command: str, timeout: int, _is_chained: bool = False) -> str: +def _run_command(command: str, timeout: int) -> str: # Ensure executable exists in PATH first. This avoids a subprocess call that would fail anyway. if shutil.which(EXECUTABLE_NAME) is None: raise CredentialUnavailableError(message=CLI_NOT_FOUND) @@ -223,7 +221,7 @@ def _run_command(command: str, timeout: int, _is_chained: bool = False) -> str: message = sanitize_output(ex.stderr) else: message = "Failed to invoke Azure Developer CLI" - if _is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=message) from ex raise ClientAuthenticationError(message=message) from ex except OSError as ex: diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py b/sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py index e4535ce93217..530f5c1cd0c1 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py @@ -16,7 +16,7 @@ from azure.core.exceptions import ClientAuthenticationError from .. import CredentialUnavailableError -from .._internal import _scopes_to_resource, resolve_tenant +from .._internal import _scopes_to_resource, resolve_tenant, within_dac from .._internal.decorators import log_get_token @@ -53,11 +53,9 @@ def __init__( tenant_id: str = "", additionally_allowed_tenants: Optional[List[str]] = None, process_timeout: int = 10, - _is_chained: bool = False, ) -> None: self.tenant_id = tenant_id - self._is_chained = _is_chained self._additionally_allowed_tenants = additionally_allowed_tenants or [] self._process_timeout = process_timeout @@ -97,7 +95,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: ) if tenant: command += " --tenant " + tenant - output = _run_command(command, self._process_timeout, _is_chained=self._is_chained) + output = _run_command(command, self._process_timeout) token = parse_token(output) if not token: @@ -107,7 +105,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: f"To mitigate this issue, please refer to the troubleshooting guidelines here at " f"https://aka.ms/azsdk/python/identity/azclicredential/troubleshoot." ) - if self._is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=message) raise ClientAuthenticationError(message=message) @@ -165,7 +163,7 @@ def sanitize_output(output: str) -> str: return re.sub(r"\"accessToken\": \"(.*?)(\"|$)", "****", output) -def _run_command(command: str, timeout: int, _is_chained: bool = False) -> str: +def _run_command(command: str, timeout: int) -> str: # Ensure executable exists in PATH first. This avoids a subprocess call that would fail anyway. if shutil.which(EXECUTABLE_NAME) is None: raise CredentialUnavailableError(message=CLI_NOT_FOUND) @@ -198,7 +196,7 @@ def _run_command(command: str, timeout: int, _is_chained: bool = False) -> str: message = sanitize_output(ex.stderr) else: message = "Failed to invoke Azure CLI" - if _is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=message) from ex raise ClientAuthenticationError(message=message) from ex except OSError as ex: diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/azure_powershell.py b/sdk/identity/azure-identity/azure/identity/_credentials/azure_powershell.py index 3507f9f37526..d671a0ff3cc5 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/azure_powershell.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/azure_powershell.py @@ -13,7 +13,7 @@ from .azure_cli import get_safe_working_dir from .. import CredentialUnavailableError -from .._internal import _scopes_to_resource, resolve_tenant +from .._internal import _scopes_to_resource, resolve_tenant, within_dac from .._internal.decorators import log_get_token @@ -67,11 +67,9 @@ def __init__( tenant_id: str = "", additionally_allowed_tenants: Optional[List[str]] = None, process_timeout: int = 10, - _is_chained: bool = False ) -> None: self.tenant_id = tenant_id - self._is_chained = _is_chained self._additionally_allowed_tenants = additionally_allowed_tenants or [] self._process_timeout = process_timeout @@ -109,7 +107,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: ) command_line = get_command_line(scopes, tenant_id) output = run_command_line(command_line, self._process_timeout) - token = parse_token(output, _is_chained=self._is_chained) + token = parse_token(output) return token @@ -155,13 +153,13 @@ def start_process(args: List[str]) -> "subprocess.Popen": return proc -def parse_token(output: str, _is_chained: bool = False) -> AccessToken: +def parse_token(output: str) -> AccessToken: for line in output.split(): if line.startswith("azsdk%"): _, token, expires_on = line.split("%") return AccessToken(token, int(expires_on)) - if _is_chained: + if within_dac.get(): raise CredentialUnavailableError(message='Unexpected output from Get-AzAccessToken: "{}"'.format(output)) raise ClientAuthenticationError(message='Unexpected output from Get-AzAccessToken: "{}"'.format(output)) diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/browser.py b/sdk/identity/azure-identity/azure/identity/_credentials/browser.py index ad6cafb4c051..48cbbd174087 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/browser.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/browser.py @@ -14,7 +14,7 @@ from .. import CredentialUnavailableError from .._constants import DEVELOPER_SIGN_ON_CLIENT_ID -from .._internal import AuthCodeRedirectServer, InteractiveCredential, wrap_exceptions +from .._internal import AuthCodeRedirectServer, InteractiveCredential, wrap_exceptions, within_dac class InteractiveBrowserCredential(InteractiveCredential): @@ -78,7 +78,6 @@ def __init__(self, **kwargs: Any) -> None: else: self._parsed_url = None - self._is_chained = kwargs.pop("_is_chained", False) self._login_hint = kwargs.pop("login_hint", None) self._timeout = kwargs.pop("timeout", 300) self._server_class = kwargs.pop("_server_class", AuthCodeRedirectServer) @@ -148,11 +147,11 @@ def _request_token(self, *scopes: str, **kwargs: Any) -> Dict: 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 self._is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=result["error_description"]) raise ClientAuthenticationError(message=result.get("error_description")) if "access_token" not in result: - if self._is_chained: + if within_dac.get(): raise CredentialUnavailableError(message="Failed to authenticate user") raise ClientAuthenticationError(message="Failed to authenticate user") diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/default.py b/sdk/identity/azure-identity/azure/identity/_credentials/default.py index 5fe0d5c52333..8e188d028f33 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/default.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/default.py @@ -8,7 +8,7 @@ from azure.core.credentials import AccessToken from .._constants import EnvironmentVariables -from .._internal import get_default_authority, normalize_authority +from .._internal import get_default_authority, normalize_authority, within_dac from .azure_powershell import AzurePowerShellCredential from .browser import InteractiveBrowserCredential from .chained import ChainedTokenCredential @@ -170,37 +170,28 @@ def __init__(self, **kwargs: Any) -> None: # pylint: disable=too-many-statement try: # username and/or tenant_id are only required when the cache contains tokens for multiple identities shared_cache = SharedTokenCacheCredential( - username=shared_cache_username, - tenant_id=shared_cache_tenant_id, - authority=authority, - _is_chained=True, - **kwargs + username=shared_cache_username, tenant_id=shared_cache_tenant_id, authority=authority, **kwargs ) credentials.append(shared_cache) except Exception as ex: # pylint:disable=broad-except _LOGGER.info("Shared token cache is unavailable: '%s'", ex) if not exclude_visual_studio_code_credential: - credentials.append(VisualStudioCodeCredential(_is_chained=True, **vscode_args)) + credentials.append(VisualStudioCodeCredential(**vscode_args)) if not exclude_cli_credential: - credentials.append(AzureCliCredential(process_timeout=process_timeout, _is_chained=True)) + credentials.append(AzureCliCredential(process_timeout=process_timeout)) if not exclude_powershell_credential: - credentials.append(AzurePowerShellCredential(process_timeout=process_timeout, _is_chained=True)) + credentials.append(AzurePowerShellCredential(process_timeout=process_timeout)) if not exclude_developer_cli_credential: - credentials.append(AzureDeveloperCliCredential(process_timeout=process_timeout, _is_chained=True)) + credentials.append(AzureDeveloperCliCredential(process_timeout=process_timeout)) if not exclude_interactive_browser_credential: if interactive_browser_client_id: credentials.append( InteractiveBrowserCredential( - tenant_id=interactive_browser_tenant_id, - client_id=interactive_browser_client_id, - _is_chained=True, - **kwargs + tenant_id=interactive_browser_tenant_id, client_id=interactive_browser_client_id, **kwargs ) ) else: - credentials.append( - InteractiveBrowserCredential(tenant_id=interactive_browser_tenant_id, _is_chained=True, **kwargs) - ) + credentials.append(InteractiveBrowserCredential(tenant_id=interactive_browser_tenant_id, **kwargs)) super(DefaultAzureCredential, self).__init__(*credentials) @@ -226,5 +217,7 @@ def get_token(self, *scopes: str, **kwargs) -> AccessToken: "%s acquired a token from %s", self.__class__.__name__, self._successful_credential.__class__.__name__ ) return token - - return super(DefaultAzureCredential, self).get_token(*scopes, **kwargs) + within_dac.set(True) + token = super(DefaultAzureCredential, self).get_token(*scopes, **kwargs) + within_dac.set(False) + return token diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/silent.py b/sdk/identity/azure-identity/azure/identity/_credentials/silent.py index 7d872da38a41..833c7e21615c 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/silent.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/silent.py @@ -13,7 +13,7 @@ from azure.core.exceptions import ClientAuthenticationError from .. import CredentialUnavailableError -from .._internal import resolve_tenant, validate_tenant_id +from .._internal import resolve_tenant, validate_tenant_id, within_dac from .._internal.decorators import wrap_exceptions from .._internal.msal_client import MsalClient from .._internal.shared_token_cache import NO_TOKEN @@ -38,7 +38,6 @@ def __init__( # authenticate in the tenant that produced the record unless "tenant_id" specifies another self._tenant_id = tenant_id or self._auth_record.tenant_id validate_tenant_id(self._tenant_id) - self._is_chained = kwargs.pop("_is_chained", False) self._cache = kwargs.pop("_cache", None) self._cache_persistence_options = kwargs.pop("cache_persistence_options", None) self._client_applications: Dict[str, PublicClientApplication] = {} @@ -61,7 +60,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: self._initialize() if not self._cache: - if self._is_chained: + if within_dac.get(): raise CredentialUnavailableError(message="Shared token cache unavailable") raise ClientAuthenticationError(message="Shared token cache unavailable") diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/vscode.py b/sdk/identity/azure-identity/azure/identity/_credentials/vscode.py index 3ec26f496416..f73b7832e972 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/vscode.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/vscode.py @@ -11,7 +11,7 @@ from azure.core.exceptions import ClientAuthenticationError from .._exceptions import CredentialUnavailableError from .._constants import AzureAuthorityHosts, AZURE_VSCODE_CLIENT_ID, EnvironmentVariables -from .._internal import normalize_authority, validate_tenant_id +from .._internal import normalize_authority, validate_tenant_id, within_dac from .._internal.aad_client import AadClient, AadClientBase from .._internal.get_token_mixin import GetTokenMixin from .._internal.decorators import log_get_token @@ -29,7 +29,6 @@ def __init__(self, **kwargs: Any) -> None: super(_VSCodeCredentialBase, self).__init__() user_settings = get_user_settings() - self._is_chained = kwargs.pop("_is_chained", False) self._cloud = user_settings.get("azure.cloud", "AzureCloud") self._refresh_token = None self._unavailable_reason = "" @@ -162,7 +161,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: " to troubleshoot this issue." ) raise CredentialUnavailableError(message=error_message) - if self._is_chained: + if within_dac.get(): try: token = super(VisualStudioCodeCredential, self).get_token(*scopes, **kwargs) return token diff --git a/sdk/identity/azure-identity/azure/identity/_internal/__init__.py b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py index 0a9f27f2c6da..6bfab45e7dd5 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/__init__.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py @@ -14,6 +14,7 @@ resolve_tenant, validate_tenant_id, within_credential_chain, + within_dac, ) @@ -47,6 +48,7 @@ def _scopes_to_resource(*scopes) -> str: "normalize_authority", "resolve_tenant", "within_credential_chain", + "within_dac", "wrap_exceptions", "validate_tenant_id", ] diff --git a/sdk/identity/azure-identity/azure/identity/_internal/shared_token_cache.py b/sdk/identity/azure-identity/azure/identity/_internal/shared_token_cache.py index 07a69fbb8618..8aa60bf4f5de 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/shared_token_cache.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/shared_token_cache.py @@ -92,7 +92,6 @@ def __init__( self._authority = normalize_authority(authority) if authority else get_default_authority() environment = urlparse(self._authority).netloc self._environment_aliases = KNOWN_ALIASES.get(environment) or frozenset((environment,)) - self._is_chained = kwargs.pop("_is_chained", False) self._username = username self._tenant_id = tenant_id self._cache = kwargs.pop("_cache", None) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/utils.py b/sdk/identity/azure-identity/azure/identity/_internal/utils.py index 105d91e96dc3..0bb176e19d25 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/utils.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/utils.py @@ -13,6 +13,7 @@ from .._constants import EnvironmentVariables, KnownAuthorities within_credential_chain = ContextVar("within_credential_chain", default=False) +within_dac = ContextVar("within_dac", default=False) _LOGGER = logging.getLogger(__name__) diff --git a/sdk/identity/azure-identity/azure/identity/aio/_credentials/azd_cli.py b/sdk/identity/azure-identity/azure/identity/aio/_credentials/azd_cli.py index 5233c6d98636..b03ea546660e 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_credentials/azd_cli.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_credentials/azd_cli.py @@ -23,7 +23,7 @@ parse_token, sanitize_output, ) -from ..._internal import resolve_tenant +from ..._internal import resolve_tenant, within_dac class AzureDeveloperCliCredential(AsyncContextManager): @@ -72,11 +72,9 @@ def __init__( tenant_id: str = "", additionally_allowed_tenants: Optional[List[str]] = None, process_timeout: int = 10, - _is_chained: bool = False, ) -> None: self.tenant_id = tenant_id - self._is_chained = _is_chained self._additionally_allowed_tenants = additionally_allowed_tenants or [] self._process_timeout = process_timeout @@ -113,7 +111,7 @@ async def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: if tenant: command += " --tenant-id " + tenant - output = await _run_command(command, self._process_timeout, _is_chained=self._is_chained) + output = await _run_command(command, self._process_timeout) token = parse_token(output) if not token: @@ -123,7 +121,7 @@ async def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: f"To mitigate this issue, please refer to the troubleshooting guidelines here at " f"https://aka.ms/azsdk/python/identity/azdevclicredential/troubleshoot." ) - if self._is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=message) raise ClientAuthenticationError(message=message) @@ -133,7 +131,7 @@ async def close(self) -> None: """Calling this method is unnecessary""" -async def _run_command(command: str, timeout: int, _is_chained: bool = False) -> str: +async def _run_command(command: str, timeout: int) -> str: # Ensure executable exists in PATH first. This avoids a subprocess call that would fail anyway. if shutil.which(EXECUTABLE_NAME) is None: raise CredentialUnavailableError(message=CLI_NOT_FOUND) @@ -175,6 +173,6 @@ async def _run_command(command: str, timeout: int, _is_chained: bool = False) -> raise CredentialUnavailableError(message=NOT_LOGGED_IN) message = sanitize_output(stderr) if stderr else "Failed to invoke Azure Developer CLI" - if _is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=message) raise ClientAuthenticationError(message=message) diff --git a/sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_cli.py b/sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_cli.py index 665d0c2aee66..649ada95a965 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_cli.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_cli.py @@ -23,7 +23,7 @@ parse_token, sanitize_output, ) -from ..._internal import _scopes_to_resource, resolve_tenant +from ..._internal import _scopes_to_resource, resolve_tenant, within_dac class AzureCliCredential(AsyncContextManager): @@ -53,11 +53,9 @@ def __init__( tenant_id: str = "", additionally_allowed_tenants: Optional[List[str]] = None, process_timeout: int = 10, - _is_chained: bool = False, ) -> None: self.tenant_id = tenant_id - self._is_chained = _is_chained self._additionally_allowed_tenants = additionally_allowed_tenants or [] self._process_timeout = process_timeout @@ -91,7 +89,7 @@ async def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: if tenant: command += " --tenant " + tenant - output = await _run_command(command, self._process_timeout, _is_chained=self._is_chained) + output = await _run_command(command, self._process_timeout) token = parse_token(output) if not token: @@ -101,7 +99,7 @@ async def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: f"To mitigate this issue, please refer to the troubleshooting guidelines here at " f"https://aka.ms/azsdk/python/identity/azclicredential/troubleshoot." ) - if self._is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=message) raise ClientAuthenticationError(message=message) @@ -111,7 +109,7 @@ async def close(self) -> None: """Calling this method is unnecessary""" -async def _run_command(command: str, timeout: int, _is_chained: bool = False) -> str: +async def _run_command(command: str, timeout: int) -> str: # Ensure executable exists in PATH first. This avoids a subprocess call that would fail anyway. if shutil.which(EXECUTABLE_NAME) is None: raise CredentialUnavailableError(message=CLI_NOT_FOUND) @@ -153,6 +151,6 @@ async def _run_command(command: str, timeout: int, _is_chained: bool = False) -> raise CredentialUnavailableError(message=NOT_LOGGED_IN) message = sanitize_output(stderr) if stderr else "Failed to invoke Azure CLI" - if _is_chained: + if within_dac.get(): raise CredentialUnavailableError(message=message) raise ClientAuthenticationError(message=message) diff --git a/sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_powershell.py b/sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_powershell.py index 7eb45fee3ff0..c704c9614de6 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_powershell.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_powershell.py @@ -47,11 +47,9 @@ def __init__( tenant_id: str = "", additionally_allowed_tenants: Optional[List[str]] = None, process_timeout: int = 10, - _is_chained: bool = False ) -> None: self.tenant_id = tenant_id - self._is_chained = _is_chained self._additionally_allowed_tenants = additionally_allowed_tenants or [] self._process_timeout = process_timeout @@ -83,7 +81,7 @@ async def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: ) command_line = get_command_line(scopes, tenant_id) output = await run_command_line(command_line, self._process_timeout) - token = parse_token(output, _is_chained=self._is_chained) + token = parse_token(output) return token async def close(self) -> None: diff --git a/sdk/identity/azure-identity/azure/identity/aio/_credentials/default.py b/sdk/identity/azure-identity/azure/identity/aio/_credentials/default.py index 73a180c02dc1..bb539650d7db 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_credentials/default.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_credentials/default.py @@ -8,7 +8,7 @@ from azure.core.credentials import AccessToken from ..._constants import EnvironmentVariables -from ..._internal import get_default_authority, normalize_authority +from ..._internal import get_default_authority, normalize_authority, within_dac from .azure_cli import AzureCliCredential from .azd_cli import AzureDeveloperCliCredential from .azure_powershell import AzurePowerShellCredential @@ -160,19 +160,15 @@ def __init__(self, **kwargs: Any) -> None: try: # username and/or tenant_id are only required when the cache contains tokens for multiple identities shared_cache = SharedTokenCacheCredential( - username=shared_cache_username, - tenant_id=shared_cache_tenant_id, - authority=authority, - _is_chained=True, - **kwargs + username=shared_cache_username, tenant_id=shared_cache_tenant_id, authority=authority, **kwargs ) credentials.append(shared_cache) except Exception as ex: # pylint:disable=broad-except _LOGGER.info("Shared token cache is unavailable: '%s'", ex) if not exclude_visual_studio_code_credential: - credentials.append(VisualStudioCodeCredential(_is_chained=True, **vscode_args)) + credentials.append(VisualStudioCodeCredential(**vscode_args)) if not exclude_cli_credential: - credentials.append(AzureCliCredential(process_timeout=process_timeout, _is_chained=True)) + credentials.append(AzureCliCredential(process_timeout=process_timeout)) if not exclude_powershell_credential: credentials.append(AzurePowerShellCredential(process_timeout=process_timeout)) if not exclude_developer_cli_credential: @@ -197,5 +193,7 @@ async def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: """ if self._successful_credential: return await self._successful_credential.get_token(*scopes, **kwargs) - - return await super().get_token(*scopes, **kwargs) + within_dac.set(True) + token = await super().get_token(*scopes, **kwargs) + within_dac.set(False) + return token diff --git a/sdk/identity/azure-identity/azure/identity/aio/_credentials/vscode.py b/sdk/identity/azure-identity/azure/identity/aio/_credentials/vscode.py index c27ba10efaa2..ddd1d0e012ec 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_credentials/vscode.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_credentials/vscode.py @@ -5,12 +5,14 @@ from typing import cast, Optional, Any from azure.core.credentials import AccessToken +from azure.core.exceptions import ClientAuthenticationError from ..._exceptions import CredentialUnavailableError from .._internal import AsyncContextManager from .._internal.aad_client import AadClient from .._internal.get_token_mixin import GetTokenMixin from .._internal.decorators import log_get_token_async from ..._credentials.vscode import _VSCodeCredentialBase +from ..._internal import within_dac class VisualStudioCodeCredential(_VSCodeCredentialBase, AsyncContextManager, GetTokenMixin): @@ -69,7 +71,12 @@ async def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: raise CredentialUnavailableError(message=error_message) if not self._client: raise CredentialUnavailableError("Initialization failed") - + if within_dac.get(): + try: + token = await super().get_token(*scopes, **kwargs) + return token + except ClientAuthenticationError as ex: + raise CredentialUnavailableError(message=ex.message) from ex return await super().get_token(*scopes, **kwargs) async def _acquire_token_silently(self, *scopes: str, **kwargs: Any) -> Optional[AccessToken]: diff --git a/sdk/identity/azure-identity/tests/test_shared_cache_credential.py b/sdk/identity/azure-identity/tests/test_shared_cache_credential.py index 12437779ef79..8c279ec536be 100644 --- a/sdk/identity/azure-identity/tests/test_shared_cache_credential.py +++ b/sdk/identity/azure-identity/tests/test_shared_cache_credential.py @@ -19,16 +19,12 @@ NO_ACCOUNTS, NO_MATCHING_ACCOUNTS, ) -from azure.identity._internal import get_default_authority +from azure.identity._internal import get_default_authority, within_dac from azure.identity._internal.user_agent import USER_AGENT from msal import TokenCache import pytest from urllib.parse import urlparse - -try: - from unittest.mock import MagicMock, Mock, patch -except ImportError: # python < 3.3 - from mock import MagicMock, Mock, patch # type: ignore +from unittest.mock import MagicMock, Mock, patch from helpers import ( build_aad_response, @@ -830,6 +826,22 @@ def send(request, **_): assert kwargs["client_capabilities"] is None +def test_within_dac_error(): + def send(request, **_): + # expecting only the discovery requests triggered by creating an msal.PublicClientApplication + # because the cache is empty--the credential shouldn't send a token request + return get_discovery_response("https://localhost/tenant") + + record = AuthenticationRecord("tenant-id", "client_id", "authority", "home_account_id", "username") + transport = Mock(send=send) + credential = SharedTokenCacheCredential(transport=transport, authentication_record=record, _cache=TokenCache()) + within_dac.set(True) + with patch("azure.identity._credentials.silent.PublicClientApplication") as PublicClientApplication: + with pytest.raises(CredentialUnavailableError): # (cache is empty) + credential.get_token("scope") + within_dac.set(False) + + def test_claims_challenge(): """get_token should pass any claims challenge to MSAL token acquisition APIs"""