Home Assistant Unofficial Reference 2024.12.1
decorators.py
Go to the documentation of this file.
1 """Decorators for the Websocket API."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from functools import wraps
7 from typing import TYPE_CHECKING, Any
8 
9 import voluptuous as vol
10 
11 from homeassistant.const import HASSIO_USER_NAME
12 from homeassistant.core import HomeAssistant, callback
13 from homeassistant.exceptions import Unauthorized
14 from homeassistant.helpers.typing import VolDictType
15 
16 from . import const, messages
17 from .connection import ActiveConnection
18 
19 
21  func: const.AsyncWebSocketCommandHandler,
22  hass: HomeAssistant,
23  connection: ActiveConnection,
24  msg: dict[str, Any],
25 ) -> None:
26  """Create a response and handle exception."""
27  try:
28  await func(hass, connection, msg)
29  except Exception as err: # noqa: BLE001
30  connection.async_handle_exception(msg, err)
31 
32 
34  func: const.AsyncWebSocketCommandHandler,
35 ) -> const.WebSocketCommandHandler:
36  """Decorate an async function to handle WebSocket API messages."""
37  task_name = f"websocket_api.async:{func.__name__}"
38 
39  @callback
40  @wraps(func)
41  def schedule_handler(
42  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
43  ) -> None:
44  """Schedule the handler."""
45  # As the webserver is now started before the start
46  # event we do not want to block for websocket responders
47  hass.async_create_background_task(
48  _handle_async_response(func, hass, connection, msg),
49  task_name,
50  eager_start=True,
51  )
52 
53  return schedule_handler
54 
55 
56 def require_admin(func: const.WebSocketCommandHandler) -> const.WebSocketCommandHandler:
57  """Websocket decorator to require user to be an admin."""
58 
59  @wraps(func)
60  def with_admin(
61  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
62  ) -> None:
63  """Check admin and call function."""
64  user = connection.user
65 
66  if user is None or not user.is_admin:
67  raise Unauthorized
68 
69  func(hass, connection, msg)
70 
71  return with_admin
72 
73 
75  only_owner: bool = False,
76  only_system_user: bool = False,
77  allow_system_user: bool = True,
78  only_active_user: bool = True,
79  only_inactive_user: bool = False,
80  only_supervisor: bool = False,
81 ) -> Callable[[const.WebSocketCommandHandler], const.WebSocketCommandHandler]:
82  """Decorate function validating login user exist in current WS connection.
83 
84  Will write out error message if not authenticated.
85  """
86 
87  def validator(func: const.WebSocketCommandHandler) -> const.WebSocketCommandHandler:
88  """Decorate func."""
89 
90  @wraps(func)
91  def check_current_user(
92  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
93  ) -> None:
94  """Check current user."""
95 
96  def output_error(message_id: str, message: str) -> None:
97  """Output error message."""
98  connection.send_message(
99  messages.error_message(msg["id"], message_id, message)
100  )
101 
102  if only_owner and not connection.user.is_owner:
103  output_error("only_owner", "Only allowed as owner")
104  return None
105 
106  if only_system_user and not connection.user.system_generated:
107  output_error("only_system_user", "Only allowed as system user")
108  return None
109 
110  if not allow_system_user and connection.user.system_generated:
111  output_error("not_system_user", "Not allowed as system user")
112  return None
113 
114  if only_active_user and not connection.user.is_active:
115  output_error("only_active_user", "Only allowed as active user")
116  return None
117 
118  if only_inactive_user and connection.user.is_active:
119  output_error("only_inactive_user", "Not allowed as active user")
120  return None
121 
122  if only_supervisor and connection.user.name != HASSIO_USER_NAME:
123  output_error("only_supervisor", "Only allowed as Supervisor")
124  return None
125 
126  return func(hass, connection, msg)
127 
128  return check_current_user
129 
130  return validator
131 
132 
134  schema: VolDictType | vol.All,
135 ) -> Callable[[const.WebSocketCommandHandler], const.WebSocketCommandHandler]:
136  """Tag a function as a websocket command.
137 
138  The schema must be either a dictionary where the keys are voluptuous markers, or
139  a voluptuous.All schema where the first item is a voluptuous Mapping schema.
140  """
141  if is_dict := isinstance(schema, dict):
142  command = schema["type"]
143  else:
144  command = schema.validators[0].schema["type"]
145 
146  def decorate(func: const.WebSocketCommandHandler) -> const.WebSocketCommandHandler:
147  """Decorate ws command function."""
148  if is_dict and len(schema) == 1: # type: ignore[arg-type] # type only empty schema
149  func._ws_schema = False # type: ignore[attr-defined] # noqa: SLF001
150  elif is_dict:
151  func._ws_schema = messages.BASE_COMMAND_MESSAGE_SCHEMA.extend(schema) # type: ignore[attr-defined] # noqa: SLF001
152  else:
153  if TYPE_CHECKING:
154  assert not isinstance(schema, dict)
155  extended_schema = vol.All(
156  schema.validators[0].extend(
157  messages.BASE_COMMAND_MESSAGE_SCHEMA.schema
158  ),
159  *schema.validators[1:],
160  )
161  func._ws_schema = extended_schema # type: ignore[attr-defined] # noqa: SLF001
162  func._ws_command = command # type: ignore[attr-defined] # noqa: SLF001
163  return func
164 
165  return decorate
const .WebSocketCommandHandler require_admin(const .WebSocketCommandHandler func)
Definition: decorators.py:56
Callable[[const .WebSocketCommandHandler], const .WebSocketCommandHandler] websocket_command(VolDictType|vol.All schema)
Definition: decorators.py:135
None _handle_async_response(const .AsyncWebSocketCommandHandler func, HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: decorators.py:25
Callable[[const .WebSocketCommandHandler], const .WebSocketCommandHandler] ws_require_user(bool only_owner=False, bool only_system_user=False, bool allow_system_user=True, bool only_active_user=True, bool only_inactive_user=False, bool only_supervisor=False)
Definition: decorators.py:81
const .WebSocketCommandHandler async_response(const .AsyncWebSocketCommandHandler func)
Definition: decorators.py:35