def get_token(self, *scopes, **kwargs): # type: (*str, **Any) -> AccessToken """Request an access token for `scopes`. This method is called automatically by Azure SDK clients. Applications calling this method directly must also handle token caching because this credential doesn't cache the tokens it acquires. :param str scopes: desired scope for the access token. This credential allows only one scope per request. :keyword str tenant_id: optional tenant to include in the token request. If **allow_multitenant_authentication** is False, specifying a tenant with this argument may raise an exception. :rtype: :class:`azure.core.credentials.AccessToken` :raises ~azure.identity.CredentialUnavailableError: the credential was unable to invoke the Azure CLI. :raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked the Azure CLI but didn't receive an access token. """ resource = _scopes_to_resource(*scopes) command = COMMAND_LINE.format(resource) tenant = resolve_tenant("", self._allow_multitenant, **kwargs) if tenant: command += " --tenant " + tenant output = _run_command(command) token = parse_token(output) if not token: sanitized_output = sanitize_output(output) raise ClientAuthenticationError(message="Unexpected output from Azure CLI: '{}'".format(sanitized_output)) return token
async def get_token(self, *scopes: str, **kwargs: "Any") -> "AccessToken": """Asynchronously request a token from each credential, in order, returning the first token received. If no credential provides a token, raises :class:`azure.core.exceptions.ClientAuthenticationError` with an error message from each credential. .. note:: This method is called by Azure SDK clients. It isn't intended for use in application code. :param str scopes: desired scopes for the access token. This method requires at least one scope. :raises ~azure.core.exceptions.ClientAuthenticationError: no credential in the chain provided a token """ history = [] for credential in self.credentials: try: token = await credential.get_token(*scopes, **kwargs) self._successful_credential = credential return token except CredentialUnavailableError as ex: # credential didn't attempt authentication because it lacks required data or state -> continue history.append((credential, ex.message)) except Exception as ex: # pylint: disable=broad-except # credential failed to authenticate, or something unexpectedly raised -> break history.append((credential, str(ex))) break attempts = _get_error_message(history) message = self.__class__.__name__ + " failed to retrieve a token from the included credentials." + attempts raise ClientAuthenticationError(message=message)
async def get_token(self, *scopes, **kwargs): """Request an access token for `scopes`. .. note:: This method is called by Azure SDK clients. It isn't intended for use in application code. This credential won't cache tokens. Every call invokes the Azure CLI. :param str scopes: desired scope for the access token. This credential allows only one scope per request. :rtype: :class:`azure.core.credentials.AccessToken` :raises ~azure.identity.CredentialUnavailableError: the credential was unable to invoke the Azure CLI. :raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked the Azure CLI but didn't receive an access token. """ # only ProactorEventLoop supports subprocesses on Windows (and it isn't the default loop on Python < 3.8) if sys.platform.startswith("win") and not isinstance( asyncio.get_event_loop(), asyncio.ProactorEventLoop): return _SyncAzureCliCredential().get_token(*scopes, **kwargs) resource = _scopes_to_resource(*scopes) output = await _run_command(COMMAND_LINE.format(resource)) token = parse_token(output) if not token: sanitized_output = sanitize_output(output) raise ClientAuthenticationError( message="Unexpected output from Azure CLI: '{}'".format( sanitized_output)) return token
async def _refresh_token(self, *scopes): resource = scopes[0] if resource.endswith("/.default"): resource = resource[:-len("/.default")] params = { "api-version": "2018-02-01", "resource": resource, **self._identity_config } try: token = await self._client.request_token(scopes, method="GET", params=params) except HttpResponseError as ex: # 400 in response to a token request indicates managed identity is disabled, # or the identity with the specified client_id is not available if ex.status_code == 400: self._endpoint_available = False message = "ManagedIdentityCredential authentication unavailable. " if self._identity_config: message += "The requested identity has not been assigned to this resource." else: message += "No identity has been assigned to this resource." raise CredentialUnavailableError(message=message) from ex # any other error is unexpected raise ClientAuthenticationError(message=ex.message, response=ex.response) from None return token
def _run_command(command): if sys.platform.startswith("win"): args = ["cmd", "/c", command] else: args = ["/bin/sh", "-c", command] try: working_directory = get_safe_working_dir() kwargs = {"stderr": subprocess.STDOUT, "cwd": working_directory, "universal_newlines": True} if platform.python_version() >= "3.3": kwargs["timeout"] = 10 output = subprocess.check_output(args, **kwargs) return output, None except subprocess.CalledProcessError as ex: # non-zero return from shell if ex.returncode == 127 or ex.output.startswith("'az' is not recognized"): error = CredentialUnavailableError(message=CLI_NOT_FOUND) else: # return code is from the CLI -> propagate its output if ex.output: message = sanitize_output(ex.output) else: message = "Failed to invoke Azure CLI" error = ClientAuthenticationError(message=message) except OSError as ex: # failed to execute 'cmd' or '/bin/sh'; CLI may or may not be installed error = CredentialUnavailableError(message="Failed to execute '{}'".format(args[0])) except Exception as ex: # pylint:disable=broad-except error = ex return None, error
def _deserialize_and_cache_token(self, response, scopes, request_time): # type: (PipelineResponse, Iterable[str], int) -> AccessToken # ContentDecodePolicy sets this, and should have raised if it couldn't deserialize the response payload = response.context[ContentDecodePolicy.CONTEXT_NAME] if not payload or "access_token" not in payload or not ( "expires_in" in payload or "expires_on" in payload): if payload and "access_token" in payload: payload["access_token"] = "****" raise ClientAuthenticationError( message="Unexpected response '{}'".format(payload)) token = payload["access_token"] # AccessToken wants expires_on as an int expires_on = payload.get("expires_on") or int( payload["expires_in"]) + request_time try: expires_on = int(expires_on) except ValueError: # probably an App Service MSI response, convert it to epoch seconds try: t = self._parse_app_service_expires_on(expires_on) expires_on = calendar.timegm(t) except ValueError: # have a token but don't know when it expires -> treat it as single-use expires_on = request_time # now we have an int expires_on, ensure the cache entry gets it payload["expires_on"] = expires_on self._cache.add({"response": payload, "scope": scopes}) return AccessToken(token, expires_on)
def _batch_send( # pylint: disable=inconsistent-return-statements self, entities, # type: List[TableEntity] *reqs, # type: List[HttpRequest] **kwargs): # (...) -> List[HttpResponse] """Given a series of request, do a Storage batch call. """ # Pop it here, so requests doesn't feel bad about additional kwarg raise_on_any_failure = kwargs.pop("raise_on_any_failure", True) policies = [StorageHeadersPolicy()] changeset = HttpRequest('POST', None) changeset.set_multipart_mixed(*reqs, policies=policies, boundary="changeset_{}".format(uuid4())) request = self._client._client.post( # pylint: disable=protected-access url='https://{}/$batch'.format(self._primary_hostname), headers={ 'x-ms-version': self.api_version, 'DataServiceVersion': '3.0', 'MaxDataServiceVersion': '3.0;NetFx', } ) request.set_multipart_mixed(changeset, policies=policies, enforce_https=False, boundary="batch_{}".format(uuid4())) pipeline_response = self._pipeline.run(request, **kwargs) response = pipeline_response.http_response if response.status_code == 403: raise ClientAuthenticationError( message="There was an error authenticating with the service", response=response) if response.status_code == 404: raise ResourceNotFoundError( message="The resource could not be found", response=response) if response.status_code != 202: raise BatchErrorException( message="There is a failure in the batch operation.", response=response, parts=None) parts = response.parts() transaction_result = BatchTransactionResult(reqs, parts, entities) if raise_on_any_failure: if any(p for p in parts if not 200 <= p.status_code < 300): if any(p for p in parts if p.status_code == 404): raise ResourceNotFoundError( message="The resource could not be found", response=response) raise BatchErrorException( message="There is a failure in the batch operation.", response=response, parts=parts) return transaction_result
def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument # type: (*str, **Any) -> AccessToken """Request an access token for `scopes`. :param str scopes: desired scopes for the token :rtype: :class:`azure.core.credentials.AccessToken` :raises ~azure.core.exceptions.ClientAuthenticationError: """ if not self._endpoint: raise ClientAuthenticationError(message="MSI endpoint unavailable") if len(scopes) != 1: raise ValueError( "this credential supports only one scope per request") token = self._client.get_cached_token(scopes) if not token: resource = scopes[0] if resource.endswith("/.default"): resource = resource[:-len("/.default")] secret = os.environ.get(EnvironmentVariables.MSI_SECRET) if secret: # MSI_ENDPOINT and MSI_SECRET set -> App Service token = self._request_app_service_token(scopes=scopes, resource=resource, secret=secret) else: # only MSI_ENDPOINT set -> legacy-style MSI (Cloud Shell) token = self._request_legacy_token(scopes=scopes, resource=resource) return token
def get_token(self, *scopes, **kwargs): # type: (*str, **Any) -> AccessToken """Request an access token for `scopes`. The first time this method is called, the credential will redeem its authorization code. On subsequent calls the credential will return a cached access token or redeem a refresh token, if it acquired a refresh token upon redeeming the authorization code. .. note:: This method is called by Azure SDK clients. It isn't intended for use in application code. :param str scopes: desired scopes for the access token :rtype: :class:`azure.core.credentials.AccessToken` :raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The error's ``message`` attribute gives a reason. Any error response from Azure Active Directory is available as the error's ``response`` attribute. """ if self._authorization_code: token = self._client.obtain_token_by_authorization_code( code=self._authorization_code, redirect_uri=self._redirect_uri, scopes=scopes, **kwargs) self._authorization_code = None # auth codes are single-use return token token = self._client.get_cached_access_token( scopes) or self._redeem_refresh_token(scopes, **kwargs) if not token: raise ClientAuthenticationError( message= "No authorization code, cached access token, or refresh token available." ) return token
def get_token(self, *scopes, **kwargs): # pylint: disable=no-self-use # type: (*str, **Any) -> AccessToken """Request an access token for `scopes`. This method is called automatically by Azure SDK clients. Applications calling this method directly must also handle token caching because this credential doesn't cache the tokens it acquires. :param str scopes: desired scope for the access token. This credential allows only one scope per request. :keyword str tenant_id: optional tenant to include in the token request. :rtype: :class:`azure.core.credentials.AccessToken` :raises ~azure.identity.CredentialUnavailableError: the credential was unable to invoke the Azure CLI. :raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked the Azure CLI but didn't receive an access token. """ resource = _scopes_to_resource(*scopes) command = COMMAND_LINE.format(resource) tenant = resolve_tenant("", **kwargs) if tenant: command += " --tenant " + tenant output = _run_command(command) token = parse_token(output) if not token: sanitized_output = sanitize_output(output) raise ClientAuthenticationError( message="Unexpected output from Azure CLI: '{}'. \n" "To mitigate this issue, please refer to the troubleshooting guidelines here at " "https://aka.ms/azsdk/python/identity/azclicredential/troubleshoot." .format(sanitized_output)) return token
async def _run_command(command): if sys.platform.startswith("win"): args = ("cmd", "/c " + command) else: args = ("/bin/sh", "-c " + command) working_directory = get_safe_working_dir() try: proc = await asyncio.create_subprocess_exec( *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, cwd=working_directory) except OSError as ex: # failed to execute 'cmd' or '/bin/sh'; CLI may or may not be installed error = CredentialUnavailableError( message="Failed to execute '{}'".format(args[0])) raise error from ex stdout, _ = await asyncio.wait_for(proc.communicate(), 10) output = stdout.decode() if proc.returncode == 0: return output if proc.returncode == 127 or output.startswith("'az' is not recognized"): raise CredentialUnavailableError(CLI_NOT_FOUND) if "az login" in output or "az account set" in output: raise CredentialUnavailableError(message=NOT_LOGGED_IN) message = sanitize_output( output) if output else "Failed to invoke Azure CLI" raise ClientAuthenticationError(message=message)
def get_token(self, *scopes, **kwargs): # pylint:disable=no-self-use,unused-argument # type: (*str, **Any) -> AccessToken """Request an access token for `scopes`. .. note:: This method is called by Azure SDK clients. It isn't intended for use in application code. This credential won't cache tokens. Every call invokes the Azure CLI. :param str scopes: desired scope for the access token. This credential allows only one scope per request. :rtype: :class:`azure.core.credentials.AccessToken` :raises ~azure.identity.CredentialUnavailableError: the credential was unable to invoke the Azure CLI. :raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked the Azure CLI but didn't receive an access token. """ resource = _scopes_to_resource(*scopes) output, error = _run_command(COMMAND_LINE.format(resource)) if error: raise error token = parse_token(output) if not token: sanitized_output = sanitize_output(output) raise ClientAuthenticationError( message="Unexpected output from Azure CLI: '{}'".format( sanitized_output)) return token
def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument # type: (*str, **Any) -> AccessToken """Request a token from each chained credential, in order, returning the first token received. This method is called automatically by Azure SDK clients. :param str scopes: desired scopes for the access token. This method requires at least one scope. :raises ~azure.core.exceptions.ClientAuthenticationError: no credential in the chain provided a token """ history = [] # Suppress warnings from credentials in Azure.Identity azure_identity_logger = logging.getLogger("azure.identity") handler = logging.StreamHandler(stream=sys.stdout) handler.addFilter(filter_credential_warnings) azure_identity_logger.addHandler(handler) try: for credential in self.credentials: try: token = credential.get_token(*scopes, **kwargs) _LOGGER.info( "%s acquired a token from %s", self.__class__.__name__, credential.__class__.__name__, ) self._successful_credential = credential return token except CredentialUnavailableError as ex: # credential didn't attempt authentication because it lacks required data or state -> continue history.append((credential, ex.message)) _LOGGER.info( "%s - %s is unavailable", self.__class__.__name__, credential.__class__.__name__, ) except Exception as ex: # pylint: disable=broad-except # credential failed to authenticate, or something unexpectedly raised -> break history.append((credential, str(ex))) # instead of logging a warning, we just want to log an info # since other credentials might succeed _LOGGER.info( '%s.get_token failed: %s raised unexpected error "%s"', self.__class__.__name__, credential.__class__.__name__, ex, exc_info=_LOGGER.isEnabledFor(logging.DEBUG), ) # here we do NOT want break and will continue to try other credentials finally: # Re-enable warnings from credentials in Azure.Identity azure_identity_logger.removeHandler(handler) # if all attempts failed, only then we log a warning and raise an error attempts = _get_error_message(history) message = ( self.__class__.__name__ + " failed to retrieve a token from the included credentials." + attempts ) _LOGGER.warning(message) raise ClientAuthenticationError(message=message)
def get_token(self, *scopes, **kwargs): # type: (*str, **Any) -> AccessToken """ Request an access token for ``scopes``. The first time this method is called, the credential will redeem its authorization code. On subsequent calls the credential will return a cached access token or redeem a refresh token, if it acquired a refresh token upon redeeming the authorization code. :param str scopes: desired scopes for the access token :rtype: :class:`azure.core.credentials.AccessToken` :raises: :class:`azure.core.exceptions.ClientAuthenticationError` """ if self._authorization_code: token = self._client.obtain_token_by_authorization_code( code=self._authorization_code, redirect_uri=self._redirect_uri, scopes=scopes, **kwargs) self._authorization_code = None # auth codes are single-use return token token = self._client.get_cached_access_token( scopes) or self._redeem_refresh_token(scopes, **kwargs) if not token: raise ClientAuthenticationError( message= "No authorization code, cached access token, or refresh token available." ) return token
def _request_token(self, *scopes, **kwargs): # type: (*str, **Any) -> AccessToken if self._authorization_code: token = self._client.obtain_token_by_authorization_code( scopes=scopes, code=self._authorization_code, redirect_uri=self._redirect_uri, **kwargs) self._authorization_code = None # auth codes are single-use return token token = None for refresh_token in self._client.get_cached_refresh_tokens(scopes): if "secret" in refresh_token: token = self._client.obtain_token_by_refresh_token( scopes, refresh_token["secret"], **kwargs) if token: break if not token: raise ClientAuthenticationError( message= "No authorization code, cached access token, or refresh token available." ) return token
def get_token(self, *scopes, **kwargs): # type: (*str, **Any) -> AccessToken """Request an access token for `scopes`. .. note:: This method is called by Azure SDK clients. It isn't intended for use in application code. :param str scopes: desired scopes for the access token. This method requires at least one scope. :rtype: :class:`azure.core.credentials.AccessToken` :raises CredentialUnavailableError: the credential is unable to attempt authentication because it lacks required data, state, or platform support :raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The error's ``message`` attribute gives a reason. """ if not scopes: message = "'get_token' requires at least one scope" _LOGGER.warning("%s.get_token failed: %s", self.__class__.__name__, message) raise ValueError(message) allow_prompt = kwargs.pop("_allow_prompt", not self._disable_automatic_authentication) try: token = self._acquire_token_silent(*scopes, **kwargs) _LOGGER.info("%s.get_token succeeded", self.__class__.__name__) return token except Exception as ex: # pylint:disable=broad-except if not (isinstance(ex, AuthenticationRequiredError) and allow_prompt): _LOGGER.warning( "%s.get_token failed: %s", self.__class__.__name__, ex, exc_info=_LOGGER.isEnabledFor(logging.DEBUG), ) raise # silent authentication failed -> authenticate interactively now = int(time.time()) try: result = self._request_token(*scopes, **kwargs) if "access_token" not in result: message = "Authentication failed: {}".format( result.get("error_description") or result.get("error")) raise ClientAuthenticationError(message=message) # this may be the first authentication, or the user may have authenticated a different identity self._auth_record = _build_auth_record(result) except Exception as ex: # pylint:disable=broad-except _LOGGER.warning( "%s.get_token failed: %s", self.__class__.__name__, ex, exc_info=_LOGGER.isEnabledFor(logging.DEBUG), ) raise _LOGGER.info("%s.get_token succeeded", self.__class__.__name__) return AccessToken(result["access_token"], now + int(result["expires_in"]))
def _get_token_by_auth_code(self, scopes, **kwargs): # start an HTTP server on localhost to receive the redirect for port in range(8400, 9000): try: server = self._server_class(port, timeout=self._timeout) redirect_uri = "http://localhost:{}".format(port) break except socket.error: continue # keep looking for an open port if not redirect_uri: raise ClientAuthenticationError( message="Couldn't start an HTTP server on localhost") # get the url the user must visit to authenticate scopes = list(scopes) # type: ignore request_state = str(uuid.uuid4()) app = self._get_app() auth_url = app.get_authorization_request_url(scopes, redirect_uri=redirect_uri, state=request_state, **kwargs) # open browser to that url webbrowser.open(auth_url) # block until the server times out or receives the post-authentication redirect response = server.wait_for_redirect() if not response: raise ClientAuthenticationError( message= "Timed out after waiting {} seconds for the user to authenticate" .format(self._timeout)) # redeem the authorization code for a token code = self._parse_response(request_state, response) now = int(time.time()) result = app.acquire_token_by_authorization_code( code, scopes=scopes, redirect_uri=redirect_uri, **kwargs) if "access_token" not in result: raise ClientAuthenticationError( message="Authentication failed: {}".format( result.get("error_description"))) return AccessToken(result["access_token"], now + int(result["expires_in"]))
async def delete_chat_message( self, chat_thread_id: str, chat_message_id: str, **kwargs ) -> None: """Deletes a message. Deletes a message. :param chat_thread_id: The thread id to which the message was sent. :type chat_thread_id: str :param chat_message_id: The message id. :type chat_message_id: str :keyword callable cls: A custom type or function that will be passed the direct response :return: None, or the result of cls(response) :rtype: None :raises: ~azure.core.exceptions.HttpResponseError """ cls = kwargs.pop('cls', None) # type: ClsType[None] error_map = { 404: ResourceNotFoundError, 409: ResourceExistsError, 401: lambda response: ClientAuthenticationError(response=response, model=self._deserialize(_models.CommunicationErrorResponse, response)), 403: lambda response: HttpResponseError(response=response, model=self._deserialize(_models.CommunicationErrorResponse, response)), 429: lambda response: HttpResponseError(response=response, model=self._deserialize(_models.CommunicationErrorResponse, response)), 503: lambda response: HttpResponseError(response=response, model=self._deserialize(_models.CommunicationErrorResponse, response)), } error_map.update(kwargs.pop('error_map', {})) api_version = "2021-01-27-preview4" accept = "application/json" # Construct URL url = self.delete_chat_message.metadata['url'] # type: ignore path_format_arguments = { 'endpoint': self._serialize.url("self._config.endpoint", self._config.endpoint, 'str', skip_quote=True), 'chatThreadId': self._serialize.url("chat_thread_id", chat_thread_id, 'str'), 'chatMessageId': self._serialize.url("chat_message_id", chat_message_id, 'str'), } url = self._client.format_url(url, **path_format_arguments) # Construct parameters query_parameters = {} # type: Dict[str, Any] query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') # Construct headers header_parameters = {} # type: Dict[str, Any] header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') request = self._client.delete(url, query_parameters, header_parameters) pipeline_response = await self._client._pipeline.run(request, stream=False, **kwargs) response = pipeline_response.http_response if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) if cls: return cls(pipeline_response, None, {})
def _process_response(self, response, request_time): # type: (PipelineResponse, int) -> AccessToken self._last_refresh_time = request_time # no matter succeed or not, update the last refresh time content = ContentDecodePolicy.deserialize_from_http_generics( response.http_response) if response.http_request.body.get("grant_type") == "refresh_token": if content.get("error") == "invalid_grant": # the request's refresh token is invalid -> evict it from the cache cache_entries = self._cache.find( TokenCache.CredentialType.REFRESH_TOKEN, query={ "secret": response.http_request.body["refresh_token"] }, ) for invalid_token in cache_entries: self._cache.remove_rt(invalid_token) if "refresh_token" in content: # AAD returned a new refresh token -> update the cache entry cache_entries = self._cache.find( TokenCache.CredentialType.REFRESH_TOKEN, query={ "secret": response.http_request.body["refresh_token"] }, ) # If the old token is in multiple cache entries, the cache is in a state we don't # expect or know how to reason about, so we update nothing. if len(cache_entries) == 1: self._cache.update_rt(cache_entries[0], content["refresh_token"]) del content[ "refresh_token"] # prevent caching a redundant entry _raise_for_error(content) if "expires_on" in content: expires_on = int(content["expires_on"]) elif "expires_in" in content: expires_on = request_time + int(content["expires_in"]) else: _scrub_secrets(content) raise ClientAuthenticationError( message="Unexpected response from Azure Active Directory: {}". format(content)) token = AccessToken(content["access_token"], expires_on) # caching is the final step because 'add' mutates 'content' self._cache.add( event={ "response": content, "scope": response.http_request.body["scope"].split(), "client_id": self._client_id, }, now=request_time, ) return token
def _request_token(self, *scopes, **kwargs): # type: (*str, **Any) -> dict # start an HTTP server to receive the redirect server = None if self._parsed_url: try: redirect_uri = "http://{}:{}".format(self._parsed_url.hostname, self._parsed_url.port) server = self._server_class(self._parsed_url.hostname, self._parsed_url.port, timeout=self._timeout) except socket.error: raise CredentialUnavailableError( message="Couldn't start an HTTP server on " + redirect_uri) else: for port in range(8400, 9000): try: server = self._server_class("localhost", port, timeout=self._timeout) redirect_uri = "http://localhost:{}".format(port) break except socket.error: continue # keep looking for an open port if not server: raise CredentialUnavailableError( message="Couldn't start an HTTP server on localhost") # get the url the user must visit to authenticate scopes = list(scopes) # type: ignore claims = kwargs.get("claims") app = self._get_app() flow = app.initiate_auth_code_flow(scopes, redirect_uri=redirect_uri, prompt="select_account", claims_challenge=claims) if "auth_uri" not in flow: raise CredentialUnavailableError( "Failed to begin authentication flow") if not _open_browser(flow["auth_uri"]): raise CredentialUnavailableError( message="Failed to open a browser") # block until the server times out or receives the post-authentication redirect response = server.wait_for_redirect() if not response: raise ClientAuthenticationError( message= "Timed out after waiting {} seconds for the user to authenticate" .format(self._timeout)) # redeem the authorization code for a token return app.acquire_token_by_auth_code_flow(flow, response, scopes=scopes, claims_challenge=claims)
def parse_token(output): # type: (str) -> AccessToken for line in output.split(): if line.startswith("azsdk%"): _, token, expires_on = line.split("%") return AccessToken(token, int(expires_on)) raise ClientAuthenticationError(message='Unexpected output from Get-AzAccessToken: "{}"'.format(output))
def get_token(self, *scopes): # type (*str) -> AccessToken """ Request an access token for `scopes`. This credential won't cache the token. Each call begins a new authentication flow. :param str scopes: desired scopes for the token :rtype: :class:`azure.core.credentials.AccessToken` :raises: :class:`azure.core.exceptions.ClientAuthenticationError` """ # MSAL requires scopes be a list scopes = list(scopes) # type: ignore now = int(time.time()) app = self._get_app() flow = app.initiate_device_flow(scopes) if "error" in flow: raise ClientAuthenticationError( message="Couldn't begin authentication: {}".format( flow.get("error_description") or flow.get("error"))) if self._prompt_callback: self._prompt_callback(flow["verification_uri"], flow["user_code"], flow["expires_in"]) else: print(flow["message"]) if self._timeout is not None and self._timeout < flow["expires_in"]: deadline = now + self._timeout result = app.acquire_token_by_device_flow( flow, exit_condition=lambda flow: time.time() > deadline) else: result = app.acquire_token_by_device_flow(flow) if "access_token" not in result: if result.get("error") == "authorization_pending": message = "Timed out waiting for user to authenticate" else: message = "Authentication failed: {}".format( result.get("error_description") or result.get("error")) raise ClientAuthenticationError(message=message) token = AccessToken(result["access_token"], now + int(result["expires_in"])) return token
def wrapper(*args, **kwargs): try: return fn(*args, **kwargs) except ClientAuthenticationError: raise except Exception as ex: # pylint:disable=broad-except auth_error = ClientAuthenticationError(message="Authentication failed: {}".format(ex)) raise_from(auth_error, ex)
async def _request_token( self, *scopes: str, **kwargs: "Any") -> "AccessToken": # pylint:disable=unused-argument if self._endpoint_available is None: # Lacking another way to determine whether the IMDS endpoint is listening, # we send a request it would immediately reject (missing a required header), # setting a short timeout. try: await self._client.request_token(scopes, method="GET", connection_timeout=0.3, retry_total=0) self._endpoint_available = True except HttpResponseError: # received a response, choked on it self._endpoint_available = True except Exception: # pylint:disable=broad-except # if anything else was raised, assume the endpoint is unavailable self._endpoint_available = False _LOGGER.info("No response from the IMDS endpoint.") if not self._endpoint_available: message = "ManagedIdentityCredential authentication unavailable, no managed identity endpoint found." raise CredentialUnavailableError(message=message) if len(scopes) != 1: raise ValueError( "This credential requires exactly one scope per token request." ) resource = scopes[0] if resource.endswith("/.default"): resource = resource[:-len("/.default")] params = { "api-version": "2018-02-01", "resource": resource, **self._identity_config } try: token = await self._client.request_token(scopes, method="GET", params=params) except HttpResponseError as ex: # 400 in response to a token request indicates managed identity is disabled, # or the identity with the specified client_id is not available if ex.status_code == 400: self._endpoint_available = False message = "ManagedIdentityCredential authentication unavailable. " if self._identity_config: message += "The requested identity has not been assigned to this resource." else: message += "No identity has been assigned to this resource." raise CredentialUnavailableError(message=message) from ex # any other error is unexpected raise ClientAuthenticationError(message=ex.message, response=ex.response) from None return token
def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument # type: (*str, **Any) -> AccessToken """Request an access token for `scopes`. .. note:: This method is called by Azure SDK clients. It isn't intended for use in application code. :param str scopes: desired scope for the access token. This credential allows only one scope per request. :rtype: :class:`azure.core.credentials.AccessToken` :raises ~azure.identity.CredentialUnavailableError: the IMDS endpoint is unreachable """ if self._endpoint_available is None: # Lacking another way to determine whether the IMDS endpoint is listening, # we send a request it would immediately reject (missing a required header), # setting a short timeout. try: self._client.request_token(scopes, method="GET", connection_timeout=0.3, retry_total=0) self._endpoint_available = True except HttpResponseError: # received a response, choked on it self._endpoint_available = True except Exception: # pylint:disable=broad-except # if anything else was raised, assume the endpoint is unavailable self._endpoint_available = False if not self._endpoint_available: message = "ManagedIdentityCredential authentication unavailable, no managed identity endpoint found." raise CredentialUnavailableError(message=message) if len(scopes) != 1: raise ValueError("This credential requires exactly one scope per token request.") token = self._client.get_cached_token(scopes) if not token: resource = scopes[0] if resource.endswith("/.default"): resource = resource[: -len("/.default")] params = {"api-version": "2018-02-01", "resource": resource} if self._client_id: params["client_id"] = self._client_id try: token = self._client.request_token(scopes, method="GET", params=params) except HttpResponseError as ex: # 400 in response to a token request indicates managed identity is disabled, # or the identity with the specified client_id is not available if ex.status_code == 400: self._endpoint_available = False message = "ManagedIdentityCredential authentication unavailable. " if self._client_id: message += "The requested identity has not been assigned to this resource." else: message += "No identity has been assigned to this resource." six.raise_from(CredentialUnavailableError(message=message), ex) # any other error is unexpected six.raise_from(ClientAuthenticationError(message=ex.message, response=ex.response), None) return token
def _error_handler(self, status_code, content): # pylint:disable=no-self-use if status_code == 404: logging.warning(content) raise ResourceNotFoundError(content) if status_code == 401: logging.warning(content) raise ClientAuthenticationError(content) logging.warning(content) raise HttpResponseError(content)
def get_token(self, *scopes, **kwargs): try: return super(DefaultAzureCredential, self).get_token(*scopes, **kwargs) except ClientAuthenticationError as e: raise ClientAuthenticationError(message=""" {}\n\nPlease visit the Azure identity Python SDK docs at https://aka.ms/python-sdk-identity#defaultazurecredential to learn what options DefaultAzureCredential supports""".format(e.message))
def send_typing_notification( self, chat_thread_id, # type: str **kwargs # type: Any ): # type: (...) -> None """Posts a typing event to a thread, on behalf of a user. Posts a typing event to a thread, on behalf of a user. :param chat_thread_id: Id of the thread. :type chat_thread_id: str :keyword callable cls: A custom type or function that will be passed the direct response :return: None, or the result of cls(response) :rtype: None :raises: ~azure.core.exceptions.HttpResponseError """ cls = kwargs.pop('cls', None) # type: ClsType[None] error_map = { 404: ResourceNotFoundError, 409: ResourceExistsError, 401: lambda response: ClientAuthenticationError(response=response, model=self._deserialize(_models.CommunicationErrorResponse, response)), 403: lambda response: HttpResponseError(response=response, model=self._deserialize(_models.CommunicationErrorResponse, response)), 429: lambda response: HttpResponseError(response=response, model=self._deserialize(_models.CommunicationErrorResponse, response)), 503: lambda response: HttpResponseError(response=response, model=self._deserialize(_models.CommunicationErrorResponse, response)), } error_map.update(kwargs.pop('error_map', {})) api_version = "2020-11-01-preview3" accept = "application/json" # Construct URL url = self.send_typing_notification.metadata['url'] # type: ignore path_format_arguments = { 'endpoint': self._serialize.url("self._config.endpoint", self._config.endpoint, 'str', skip_quote=True), 'chatThreadId': self._serialize.url("chat_thread_id", chat_thread_id, 'str'), } url = self._client.format_url(url, **path_format_arguments) # Construct parameters query_parameters = {} # type: Dict[str, Any] query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') # Construct headers header_parameters = {} # type: Dict[str, Any] header_parameters['Accept'] = self._serialize.header("accept", accept, 'str') request = self._client.post(url, query_parameters, header_parameters) pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs) response = pipeline_response.http_response if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) if cls: return cls(pipeline_response, None, {})
async def wrapper(*args, **kwargs): try: result = await fn(*args, **kwargs) return result except ClientAuthenticationError: raise except Exception as ex: # pylint:disable=broad-except auth_error = ClientAuthenticationError( message="Authentication failed: {}".format(ex)) raise auth_error from ex
def _request_token(self, *scopes, **kwargs): # type: (*str, **Any) -> Optional[AccessToken] app = self._get_app(**kwargs) request_time = int(time.time()) result = app.acquire_token_for_client(list(scopes)) if "access_token" not in result: message = "Authentication failed: {}".format(result.get("error_description") or result.get("error")) raise ClientAuthenticationError(message=message) return AccessToken(result["access_token"], request_time + int(result["expires_in"]))