Example #1
0
    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
Example #2
0
    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)
Example #3
0
    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
Example #5
0
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
Example #6
0
    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)
Example #7
0
    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
Example #8
0
    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)
Example #12
0
    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
Example #13
0
    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
Example #15
0
    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
Example #16
0
    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"]))
Example #17
0
    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"]))
Example #18
0
    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, {})
Example #19
0
    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
Example #20
0
    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)
Example #21
0
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))
Example #22
0
    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
Example #23
0
 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)
Example #24
0
    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
Example #26
0
 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)
Example #27
0
    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, {})
Example #29
0
 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"]))