1 """Config flow to configure the iCloud integration."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
8 from typing
import TYPE_CHECKING, Any
10 from pyicloud
import PyiCloudService
11 from pyicloud.exceptions
import (
13 PyiCloudFailedLoginException,
14 PyiCloudNoDevicesException,
15 PyiCloudServiceNotActivatedException,
17 import voluptuous
as vol
24 CONF_GPS_ACCURACY_THRESHOLD,
27 DEFAULT_GPS_ACCURACY_THRESHOLD,
35 CONF_TRUSTED_DEVICE =
"trusted_device"
36 CONF_VERIFICATION_CODE =
"verification_code"
38 _LOGGER = logging.getLogger(__name__)
42 """Handle a iCloud config flow."""
47 """Initialize iCloud config flow."""
62 """Show the setup form to the user."""
64 if user_input
is None:
70 CONF_USERNAME, default=user_input.get(CONF_USERNAME,
"")
73 CONF_PASSWORD, default=user_input.get(CONF_PASSWORD,
"")
77 default=user_input.get(CONF_WITH_FAMILY, DEFAULT_WITH_FAMILY),
83 CONF_PASSWORD, default=user_input.get(CONF_PASSWORD,
"")
89 data_schema=vol.Schema(schema),
95 """Check if config is valid and create entry if so."""
96 self.
_password_password = user_input[CONF_PASSWORD]
98 extra_inputs = user_input
105 self.
_username_username = extra_inputs[CONF_USERNAME]
106 self.
_with_family_with_family = extra_inputs.get(CONF_WITH_FAMILY, DEFAULT_WITH_FAMILY)
107 self.
_max_interval_max_interval = extra_inputs.get(CONF_MAX_INTERVAL, DEFAULT_MAX_INTERVAL)
109 CONF_GPS_ACCURACY_THRESHOLD, DEFAULT_GPS_ACCURACY_THRESHOLD
118 self.
apiapi = await self.hass.async_add_executor_job(
122 Store(self.hass, STORAGE_VERSION, STORAGE_KEY).path,
127 except PyiCloudFailedLoginException
as error:
128 _LOGGER.error(
"Error logging into iCloud service: %s", error)
130 errors = {CONF_PASSWORD:
"invalid_auth"}
133 if self.
apiapi.requires_2fa:
136 if self.
apiapi.requires_2sa:
140 devices = await self.hass.async_add_executor_job(
141 getattr, self.
apiapi,
"devices"
144 raise PyiCloudNoDevicesException
145 except (PyiCloudServiceNotActivatedException, PyiCloudNoDevicesException):
146 _LOGGER.error(
"No device found in the iCloud account: %s", self.
_username_username)
159 if step_id ==
"user":
163 self.hass.config_entries.async_update_entry(entry, data=data)
164 await self.hass.config_entries.async_reload(entry.entry_id)
168 self, user_input: dict[str, Any] |
None =
None
169 ) -> ConfigFlowResult:
170 """Handle a flow initiated by the user."""
171 errors: dict[str, str] = {}
173 icloud_dir = Store[Any](self.hass, STORAGE_VERSION, STORAGE_KEY)
175 if not os.path.exists(icloud_dir.path):
176 await self.hass.async_add_executor_job(os.makedirs, icloud_dir.path)
178 if user_input
is None:
184 self, entry_data: Mapping[str, Any]
185 ) -> ConfigFlowResult:
186 """Initialise re-authentication."""
195 self, user_input: dict[str, Any] |
None =
None
196 ) -> ConfigFlowResult:
197 """Update password for a config entry that can't authenticate."""
198 if user_input
is None:
205 user_input: dict[str, Any] |
None =
None,
206 errors: dict[str, str] |
None =
None,
207 ) -> ConfigFlowResult:
208 """We need a trusted device."""
213 assert self.
apiapi
is not None
214 trusted_devices = await self.hass.async_add_executor_job(
215 getattr, self.
apiapi,
"trusted_devices"
217 trusted_devices_for_form = {}
218 for i, device
in enumerate(trusted_devices):
219 trusted_devices_for_form[i] = device.get(
220 "deviceName", f
"SMS to {device.get('phoneNumber')}"
223 if user_input
is None:
225 trusted_devices_for_form, errors
228 self.
_trusted_device_trusted_device = trusted_devices[
int(user_input[CONF_TRUSTED_DEVICE])]
230 if not await self.hass.async_add_executor_job(
233 _LOGGER.error(
"Failed to send verification code")
235 errors[CONF_TRUSTED_DEVICE] =
"send_verification_code"
238 trusted_devices_for_form, errors
244 self, trusted_devices, errors: dict[str, str] |
None =
None
245 ) -> ConfigFlowResult:
246 """Show the trusted_device form to the user."""
249 step_id=
"trusted_device",
250 data_schema=vol.Schema(
252 vol.Required(CONF_TRUSTED_DEVICE): vol.All(
253 vol.Coerce(int), vol.In(trusted_devices)
262 user_input: dict[str, Any] |
None =
None,
263 errors: dict[str, str] |
None =
None,
264 ) -> ConfigFlowResult:
265 """Ask the verification code to the user."""
269 if user_input
is None:
273 assert self.
apiapi
is not None
278 if self.
apiapi.requires_2fa:
279 if not await self.hass.async_add_executor_job(
282 raise PyiCloudException(
"The code you entered is not valid.")
283 elif not await self.hass.async_add_executor_job(
284 self.
apiapi.validate_verification_code,
288 raise PyiCloudException(
"The code you entered is not valid.")
289 except PyiCloudException
as error:
291 _LOGGER.error(
"Failed to verify verification code: %s", error)
294 errors[
"base"] =
"validate_verification_code"
296 if self.
apiapi.requires_2fa:
298 self.
apiapi = await self.hass.async_add_executor_job(
302 Store(self.hass, STORAGE_VERSION, STORAGE_KEY).path,
308 except PyiCloudFailedLoginException
as error_login:
309 _LOGGER.error(
"Error logging into iCloud service: %s", error_login)
311 errors = {CONF_PASSWORD:
"invalid_auth"}
327 self, errors: dict[str, str] |
None =
None
328 ) -> ConfigFlowResult:
329 """Show the verification_code form to the user."""
332 step_id=
"verification_code",
333 data_schema=vol.Schema({vol.Required(CONF_VERIFICATION_CODE): str}),
ConfigFlowResult _show_trusted_device_form(self, trusted_devices, dict[str, str]|None errors=None)
def _show_setup_form(self, user_input=None, errors=None, step_id="user")
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
def _validate_and_create_entry(self, user_input, step_id)
ConfigFlowResult _show_verification_code_form(self, dict[str, str]|None errors=None)
ConfigFlowResult async_step_trusted_device(self, dict[str, Any]|None user_input=None, dict[str, str]|None errors=None)
ConfigFlowResult async_step_verification_code(self, dict[str, Any]|None user_input=None, dict[str, str]|None errors=None)
_description_placeholders
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
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)
_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)