Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow to configure the SimpliSafe component."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from typing import Any, NamedTuple
7 
8 from simplipy import API
9 from simplipy.errors import InvalidCredentialsError, SimplipyError
10 from simplipy.util.auth import (
11  get_auth0_code_challenge,
12  get_auth0_code_verifier,
13  get_auth_url,
14 )
15 import voluptuous as vol
16 
17 from homeassistant.config_entries import (
18  ConfigEntry,
19  ConfigFlow,
20  ConfigFlowResult,
21  OptionsFlow,
22 )
23 from homeassistant.const import CONF_CODE, CONF_TOKEN, CONF_URL, CONF_USERNAME
24 from homeassistant.core import callback
25 from homeassistant.helpers import aiohttp_client, config_validation as cv
26 
27 from .const import DOMAIN, LOGGER
28 
29 CONF_AUTH_CODE = "auth_code"
30 
31 STEP_USER_SCHEMA = vol.Schema(
32  {
33  vol.Required(CONF_AUTH_CODE): cv.string,
34  }
35 )
36 
37 
38 class SimpliSafeOAuthValues(NamedTuple):
39  """Define a named tuple to handle SimpliSafe OAuth strings."""
40 
41  auth_url: str
42  code_verifier: str
43 
44 
45 @callback
46 def async_get_simplisafe_oauth_values() -> SimpliSafeOAuthValues:
47  """Get a SimpliSafe OAuth code verifier and auth URL."""
48  code_verifier = get_auth0_code_verifier()
49  code_challenge = get_auth0_code_challenge(code_verifier)
50  auth_url = get_auth_url(code_challenge)
51  return SimpliSafeOAuthValues(auth_url, code_verifier)
52 
53 
54 class SimpliSafeFlowHandler(ConfigFlow, domain=DOMAIN):
55  """Handle a SimpliSafe config flow."""
56 
57  VERSION = 1
58 
59  def __init__(self) -> None:
60  """Initialize the config flow."""
61  self._oauth_values: SimpliSafeOAuthValues = async_get_simplisafe_oauth_values()
62  self._reauth_reauth: bool = False
63 
64  @staticmethod
65  @callback
67  config_entry: ConfigEntry,
68  ) -> SimpliSafeOptionsFlowHandler:
69  """Define the config flow to handle options."""
71 
72  async def async_step_reauth(
73  self, entry_data: Mapping[str, Any]
74  ) -> ConfigFlowResult:
75  """Handle configuration by re-auth."""
76  self._reauth_reauth = True
77  return await self.async_step_userasync_step_userasync_step_user()
78 
79  async def async_step_user(
80  self, user_input: dict[str, Any] | None = None
81  ) -> ConfigFlowResult:
82  """Handle the start of the config flow."""
83  if user_input is None:
84  return self.async_show_formasync_show_formasync_show_form(
85  step_id="user",
86  data_schema=STEP_USER_SCHEMA,
87  description_placeholders={CONF_URL: self._oauth_values.auth_url},
88  )
89 
90  auth_code = user_input[CONF_AUTH_CODE]
91 
92  if auth_code.startswith("="):
93  # Sometimes, users may include the "=" from the URL query param; in that
94  # case, strip it off and proceed:
95  LOGGER.debug('Stripping "=" from the start of the authorization code')
96  auth_code = auth_code[1:]
97 
98  if len(auth_code) != 45:
99  # SimpliSafe authorization codes are 45 characters in length; if the user
100  # provides something different, stop them here:
101  return self.async_show_formasync_show_formasync_show_form(
102  step_id="user",
103  data_schema=STEP_USER_SCHEMA,
104  errors={CONF_AUTH_CODE: "invalid_auth_code_length"},
105  description_placeholders={CONF_URL: self._oauth_values.auth_url},
106  )
107 
108  errors = {}
109  session = aiohttp_client.async_get_clientsession(self.hass)
110  try:
111  simplisafe = await API.async_from_auth(
112  auth_code,
113  self._oauth_values.code_verifier,
114  session=session,
115  )
116  except InvalidCredentialsError:
117  errors = {CONF_AUTH_CODE: "invalid_auth"}
118  except SimplipyError as err:
119  LOGGER.error("Unknown error while logging into SimpliSafe: %s", err)
120  errors = {"base": "unknown"}
121 
122  if errors:
123  return self.async_show_formasync_show_formasync_show_form(
124  step_id="user",
125  data_schema=STEP_USER_SCHEMA,
126  errors=errors,
127  description_placeholders={CONF_URL: self._oauth_values.auth_url},
128  )
129 
130  simplisafe_user_id = str(simplisafe.user_id)
131  data = {CONF_USERNAME: simplisafe_user_id, CONF_TOKEN: simplisafe.refresh_token}
132 
133  if self._reauth_reauth:
134  existing_entry = await self.async_set_unique_idasync_set_unique_id(simplisafe_user_id)
135  if not existing_entry:
136  # If we don't have an entry that matches this user ID, the user logged
137  # in with different credentials:
138  return self.async_abortasync_abortasync_abort(reason="wrong_account")
139 
140  self.hass.config_entries.async_update_entry(
141  existing_entry, unique_id=simplisafe_user_id, data=data
142  )
143  self.hass.async_create_task(
144  self.hass.config_entries.async_reload(existing_entry.entry_id)
145  )
146  return self.async_abortasync_abortasync_abort(reason="reauth_successful")
147 
148  await self.async_set_unique_idasync_set_unique_id(simplisafe_user_id)
149  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
150  return self.async_create_entryasync_create_entryasync_create_entry(title=simplisafe_user_id, data=data)
151 
152 
154  """Handle a SimpliSafe options flow."""
155 
156  async def async_step_init(
157  self, user_input: dict[str, Any] | None = None
158  ) -> ConfigFlowResult:
159  """Manage the options."""
160  if user_input is not None:
161  return self.async_create_entryasync_create_entry(data=user_input)
162 
163  return self.async_show_formasync_show_form(
164  step_id="init",
165  data_schema=vol.Schema(
166  {
167  vol.Optional(
168  CONF_CODE,
169  description={
170  "suggested_value": self.config_entryconfig_entryconfig_entry.options.get(CONF_CODE)
171  },
172  ): str
173  }
174  ),
175  )
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
Definition: config_flow.py:74
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:81
SimpliSafeOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:68
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:158
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
None config_entry(self, ConfigEntry value)
str
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
SimpliSafeOAuthValues async_get_simplisafe_oauth_values()
Definition: config_flow.py:46