Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Pluggable auth modules for Home Assistant."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import types
7 from typing import Any
8 
9 import voluptuous as vol
10 from voluptuous.humanize import humanize_error
11 
12 from homeassistant import data_entry_flow, requirements
13 from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
14 from homeassistant.core import HomeAssistant
15 from homeassistant.data_entry_flow import FlowResult
16 from homeassistant.exceptions import HomeAssistantError
17 from homeassistant.helpers.importlib import async_import_module
18 from homeassistant.util.decorator import Registry
19 from homeassistant.util.hass_dict import HassKey
20 
21 MULTI_FACTOR_AUTH_MODULES: Registry[str, type[MultiFactorAuthModule]] = Registry()
22 
23 MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema(
24  {
25  vol.Required(CONF_TYPE): str,
26  vol.Optional(CONF_NAME): str,
27  # Specify ID if you have two mfa auth module for same type.
28  vol.Optional(CONF_ID): str,
29  },
30  extra=vol.ALLOW_EXTRA,
31 )
32 
33 DATA_REQS: HassKey[set[str]] = HassKey("mfa_auth_module_reqs_processed")
34 
35 _LOGGER = logging.getLogger(__name__)
36 
37 
39  """Multi-factor Auth Module of validation function."""
40 
41  DEFAULT_TITLE = "Unnamed auth module"
42  MAX_RETRY_TIME = 3
43 
44  def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None:
45  """Initialize an auth module."""
46  self.hasshass = hass
47  self.configconfig = config
48 
49  @property
50  def id(self) -> str:
51  """Return id of the auth module.
52 
53  Default is same as type
54  """
55  return self.configconfig.get(CONF_ID, self.typetype) # type: ignore[no-any-return]
56 
57  @property
58  def type(self) -> str:
59  """Return type of the module."""
60  return self.configconfig[CONF_TYPE] # type: ignore[no-any-return]
61 
62  @property
63  def name(self) -> str:
64  """Return the name of the auth module."""
65  return self.configconfig.get(CONF_NAME, self.DEFAULT_TITLEDEFAULT_TITLE) # type: ignore[no-any-return]
66 
67  # Implement by extending class
68 
69  @property
70  def input_schema(self) -> vol.Schema:
71  """Return a voluptuous schema to define mfa auth module's input."""
72  raise NotImplementedError
73 
74  async def async_setup_flow(self, user_id: str) -> SetupFlow:
75  """Return a data entry flow handler for setup module.
76 
77  Mfa module should extend SetupFlow
78  """
79  raise NotImplementedError
80 
81  async def async_setup_user(self, user_id: str, setup_data: Any) -> Any:
82  """Set up user for mfa auth module."""
83  raise NotImplementedError
84 
85  async def async_depose_user(self, user_id: str) -> None:
86  """Remove user from mfa module."""
87  raise NotImplementedError
88 
89  async def async_is_user_setup(self, user_id: str) -> bool:
90  """Return whether user is setup."""
91  raise NotImplementedError
92 
93  async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool:
94  """Return True if validation passed."""
95  raise NotImplementedError
96 
97 
99  """Handler for the setup flow."""
100 
101  def __init__(
102  self, auth_module: MultiFactorAuthModule, setup_schema: vol.Schema, user_id: str
103  ) -> None:
104  """Initialize the setup flow."""
105  self._auth_module_auth_module = auth_module
106  self._setup_schema_setup_schema = setup_schema
107  self._user_id_user_id = user_id
108 
109  async def async_step_init(
110  self, user_input: dict[str, str] | None = None
111  ) -> FlowResult:
112  """Handle the first step of setup flow.
113 
114  Return self.async_show_form(step_id='init') if user_input is None.
115  Return self.async_create_entry(data={'result': result}) if finish.
116  """
117  errors: dict[str, str] = {}
118 
119  if user_input:
120  result = await self._auth_module_auth_module.async_setup_user(self._user_id_user_id, user_input)
121  return self.async_create_entryasync_create_entry(data={"result": result})
122 
123  return self.async_show_formasync_show_form(
124  step_id="init", data_schema=self._setup_schema_setup_schema, errors=errors
125  )
126 
127 
129  hass: HomeAssistant, config: dict[str, Any]
130 ) -> MultiFactorAuthModule:
131  """Initialize an auth module from a config."""
132  module_name: str = config[CONF_TYPE]
133  module = await _load_mfa_module(hass, module_name)
134 
135  try:
136  config = module.CONFIG_SCHEMA(config)
137  except vol.Invalid as err:
138  _LOGGER.error(
139  "Invalid configuration for multi-factor module %s: %s",
140  module_name,
141  humanize_error(config, err),
142  )
143  raise
144 
145  return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config)
146 
147 
148 async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType:
149  """Load an mfa auth module."""
150  module_path = f"homeassistant.auth.mfa_modules.{module_name}"
151 
152  try:
153  module = await async_import_module(hass, module_path)
154  except ImportError as err:
155  _LOGGER.error("Unable to load mfa module %s: %s", module_name, err)
156  raise HomeAssistantError(
157  f"Unable to load mfa module {module_name}: {err}"
158  ) from err
159 
160  if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"):
161  return module
162 
163  processed = hass.data.get(DATA_REQS)
164  if processed and module_name in processed:
165  return module
166 
167  processed = hass.data[DATA_REQS] = set()
168 
169  await requirements.async_process_requirements(
170  hass, module_path, module.REQUIREMENTS
171  )
172 
173  processed.add(module_name)
174  return module
SetupFlow async_setup_flow(self, str user_id)
Definition: __init__.py:74
bool async_validate(self, str user_id, dict[str, Any] user_input)
Definition: __init__.py:93
None __init__(self, HomeAssistant hass, dict[str, Any] config)
Definition: __init__.py:44
Any async_setup_user(self, str user_id, Any setup_data)
Definition: __init__.py:81
None __init__(self, MultiFactorAuthModule auth_module, vol.Schema setup_schema, str user_id)
Definition: __init__.py:103
FlowResult async_step_init(self, dict[str, str]|None user_input=None)
Definition: __init__.py:111
_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)
types.ModuleType _load_mfa_module(HomeAssistant hass, str module_name)
Definition: __init__.py:148
MultiFactorAuthModule auth_mfa_module_from_config(HomeAssistant hass, dict[str, Any] config)
Definition: __init__.py:130
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
str humanize_error(HomeAssistant hass, vol.Invalid validation_error, str domain, dict config, str|None link, int max_sub_error_length=MAX_VALIDATION_ERROR_ITEM_LENGTH)
Definition: config.py:520
ModuleType async_import_module(HomeAssistant hass, str name)
Definition: importlib.py:30