Home Assistant Unofficial Reference 2024.12.1
application_credentials.py
Go to the documentation of this file.
1 """application_credentials platform the fitbit integration.
2 
3 See https://dev.fitbit.com/build/reference/web-api/authorization/ for additional
4 details on Fitbit authorization.
5 """
6 
7 import base64
8 from http import HTTPStatus
9 import logging
10 from typing import Any, cast
11 
12 import aiohttp
13 
15  AuthImplementation,
16  AuthorizationServer,
17  ClientCredential,
18 )
19 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers import config_entry_oauth2_flow
21 from homeassistant.helpers.aiohttp_client import async_get_clientsession
22 
23 from .const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
24 from .exceptions import FitbitApiException, FitbitAuthException
25 
26 _LOGGER = logging.getLogger(__name__)
27 
28 
30  """Local OAuth2 implementation for Fitbit.
31 
32  This implementation is needed to send the client id and secret as a Basic
33  Authorization header.
34  """
35 
36  async def async_resolve_external_data(self, external_data: dict[str, Any]) -> dict:
37  """Resolve the authorization code to tokens."""
38  return await self._post(
39  {
40  "grant_type": "authorization_code",
41  "code": external_data["code"],
42  "redirect_uri": external_data["state"]["redirect_uri"],
43  }
44  )
45 
46  async def _token_request(self, data: dict) -> dict:
47  """Make a token request."""
48  return await self._post(
49  {
50  **data,
51  CONF_CLIENT_ID: self.client_id,
52  CONF_CLIENT_SECRET: self.client_secret,
53  }
54  )
55 
56  async def _post(self, data: dict[str, Any]) -> dict[str, Any]:
57  session = async_get_clientsession(self.hass)
58  try:
59  resp = await session.post(self.token_url, data=data, headers=self._headers)
60  resp.raise_for_status()
61  except aiohttp.ClientResponseError as err:
62  if _LOGGER.isEnabledFor(logging.DEBUG):
63  try:
64  error_body = await resp.text()
65  except aiohttp.ClientError:
66  error_body = ""
67  _LOGGER.debug(
68  "Client response error status=%s, body=%s", err.status, error_body
69  )
70  if err.status == HTTPStatus.UNAUTHORIZED:
71  raise FitbitAuthException(f"Unauthorized error: {err}") from err
72  if err.status == HTTPStatus.BAD_REQUEST:
73  raise FitbitAuthException(f"Bad Request error: {err}") from err
74  raise FitbitApiException(f"Server error response: {err}") from err
75  except aiohttp.ClientError as err:
76  raise FitbitApiException(f"Client connection error: {err}") from err
77  return cast(dict, await resp.json())
78 
79  @property
80  def _headers(self) -> dict[str, str]:
81  """Build necessary authorization headers."""
82  basic_auth = base64.b64encode(
83  f"{self.client_id}:{self.client_secret}".encode()
84  ).decode()
85  return {"Authorization": f"Basic {basic_auth}"}
86 
87 
89  hass: HomeAssistant, auth_domain: str, credential: ClientCredential
90 ) -> config_entry_oauth2_flow.AbstractOAuth2Implementation:
91  """Return a custom auth implementation."""
93  hass,
94  auth_domain,
95  credential,
97  authorize_url=OAUTH2_AUTHORIZE,
98  token_url=OAUTH2_TOKEN,
99  ),
100  )
config_entry_oauth2_flow.AbstractOAuth2Implementation async_get_auth_implementation(HomeAssistant hass, str auth_domain, ClientCredential credential)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)