diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index 7a060f7667a..ff2da30d551 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -631,6 +631,10 @@ def get_msal_token(self, scopes, data): authority = posixpath.join(self.cli_ctx.cloud.endpoints.active_directory, tenant) app = ClientApplication(_CLIENT_ID, authority=authority) result = app.acquire_token_by_refresh_token(refresh_token, scopes, data=data) + + if 'error' in result: + from azure.cli.core.adal_authentication import aad_error_handler + aad_error_handler(result) return username, result["access_token"] def get_refresh_token(self, resource=None, @@ -674,17 +678,22 @@ def get_raw_token(self, resource=None, subscription=None, tenant=None): creds = self._get_token_from_cloud_shell(resource) else: tenant_dest = tenant if tenant else account[_TENANT_ID] - if user_type == _USER: - # User - creds = self._creds_cache.retrieve_token_for_user(username_or_sp_id, - tenant_dest, resource) - else: - # Service Principal - use_cert_sn_issuer = bool(account[_USER_ENTITY].get(_SERVICE_PRINCIPAL_CERT_SN_ISSUER_AUTH)) - creds = self._creds_cache.retrieve_token_for_service_principal(username_or_sp_id, - resource, - tenant_dest, - use_cert_sn_issuer) + import adal + try: + if user_type == _USER: + # User + creds = self._creds_cache.retrieve_token_for_user(username_or_sp_id, + tenant_dest, resource) + else: + # Service Principal + use_cert_sn_issuer = bool(account[_USER_ENTITY].get(_SERVICE_PRINCIPAL_CERT_SN_ISSUER_AUTH)) + creds = self._creds_cache.retrieve_token_for_service_principal(username_or_sp_id, + resource, + tenant_dest, + use_cert_sn_issuer) + except adal.AdalError as ex: + from azure.cli.core.adal_authentication import adal_error_handler + adal_error_handler(ex) return (creds, None if tenant else str(account[_SUBSCRIPTION_ID]), str(tenant if tenant else account[_TENANT_ID])) diff --git a/src/azure-cli-core/azure/cli/core/adal_authentication.py b/src/azure-cli-core/azure/cli/core/adal_authentication.py index 1ba6d504928..f14276d1d2f 100644 --- a/src/azure-cli-core/azure/cli/core/adal_authentication.py +++ b/src/azure-cli-core/azure/cli/core/adal_authentication.py @@ -43,24 +43,9 @@ def _get_token(self, sdk_resource=None): AdalAuthentication._log_hostname() raise err except adal.AdalError as err: - # pylint: disable=no-member if in_cloud_console(): AdalAuthentication._log_hostname() - - err = (getattr(err, 'error_response', None) or {}).get('error_description') or str(err) - if 'AADSTS70008' in err: # all errors starting with 70008 should be creds expiration related - raise CLIError("Credentials have expired due to inactivity. {}".format( - "Please run 'az login'" if not in_cloud_console() else '')) - if 'AADSTS50079' in err: - raise CLIError("Configuration of your account was changed. {}".format( - "Please run 'az login'" if not in_cloud_console() else '')) - if 'AADSTS50173' in err: - raise CLIError("The credential data used by CLI has been expired because you might have changed or " - "reset the password. {}".format( - "Please clear browser's cookies and run 'az login'" - if not in_cloud_console() else '')) - - raise CLIError(err) + adal_error_handler(err) except requests.exceptions.SSLError as err: from .util import SSLERROR_TEMPLATE raise CLIError(SSLERROR_TEMPLATE.format(str(err))) @@ -246,3 +231,29 @@ def _timestamp(dt): # https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking # https://williambert.online/2011/07/how-to-unit-testing-in-django-with-mocking-and-patching/ return dt.timestamp() + + +def aad_error_handler(error: dict): + """ Handle the error from AAD server returned by ADAL or MSAL. """ + login_message = ("To re-authenticate, please {}. If the problem persists, " + "please contact your tenant administrator." + .format("refresh Azure Portal" if in_cloud_console() else "run `az login`")) + + # https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes + # Search for an error code at https://login.microsoftonline.com/error + msg = error.get('error_description') + + from azure.cli.core.azclierror import AuthenticationError + raise AuthenticationError(msg, login_message) + + +def adal_error_handler(err: adal.AdalError): + """ Handle AdalError. """ + try: + aad_error_handler(err.error_response) + except AttributeError: + # In case of AdalError created as + # AdalError('More than one token matches the criteria. The result is ambiguous.') + # https://github.com/Azure/azure-cli/issues/15320 + from azure.cli.core.azclierror import UnknownError + raise UnknownError(str(err), recommendation="Please run `az account clear`, then `az login`.") diff --git a/src/azure-cli-core/azure/cli/core/azclierror.py b/src/azure-cli-core/azure/cli/core/azclierror.py index 246e5919a57..5749d000847 100644 --- a/src/azure-cli-core/azure/cli/core/azclierror.py +++ b/src/azure-cli-core/azure/cli/core/azclierror.py @@ -260,4 +260,8 @@ class RecommendationError(ClientError): """ The client error raised by `az next`. It is needed in `az next` to skip error records. """ pass + +class AuthenticationError(ServiceError): + """ Raised when AAD authentication fails. """ + # endregion