Allow non-admins to listen to certain events (#22137)
This commit is contained in:
parent
c020b7c47d
commit
29131a655d
3 changed files with 125 additions and 10 deletions
|
@ -1,7 +1,9 @@
|
|||
"""Commands part of Websocket API."""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import MATCH_ALL, EVENT_TIME_CHANGED
|
||||
from homeassistant.auth.permissions.const import POLICY_READ
|
||||
from homeassistant.const import (
|
||||
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED)
|
||||
from homeassistant.core import callback, DOMAIN as HASS_DOMAIN
|
||||
from homeassistant.exceptions import Unauthorized, ServiceNotFound, \
|
||||
HomeAssistantError
|
||||
|
@ -42,20 +44,37 @@ def handle_subscribe_events(hass, connection, msg):
|
|||
|
||||
Async friendly.
|
||||
"""
|
||||
if not connection.user.is_admin:
|
||||
from .permissions import SUBSCRIBE_WHITELIST
|
||||
|
||||
event_type = msg['event_type']
|
||||
|
||||
if (event_type not in SUBSCRIBE_WHITELIST and
|
||||
not connection.user.is_admin):
|
||||
raise Unauthorized
|
||||
|
||||
async def forward_events(event):
|
||||
"""Forward events to websocket."""
|
||||
if event.event_type == EVENT_TIME_CHANGED:
|
||||
return
|
||||
if event_type == EVENT_STATE_CHANGED:
|
||||
@callback
|
||||
def forward_events(event):
|
||||
"""Forward state changed events to websocket."""
|
||||
if not connection.user.permissions.check_entity(
|
||||
event.data['entity_id'], POLICY_READ):
|
||||
return
|
||||
|
||||
connection.send_message(messages.event_message(
|
||||
msg['id'], event.as_dict()
|
||||
))
|
||||
connection.send_message(messages.event_message(msg['id'], event))
|
||||
|
||||
else:
|
||||
@callback
|
||||
def forward_events(event):
|
||||
"""Forward events to websocket."""
|
||||
if event.event_type == EVENT_TIME_CHANGED:
|
||||
return
|
||||
|
||||
connection.send_message(messages.event_message(
|
||||
msg['id'], event.as_dict()
|
||||
))
|
||||
|
||||
connection.subscriptions[msg['id']] = hass.bus.async_listen(
|
||||
msg['event_type'], forward_events)
|
||||
event_type, forward_events)
|
||||
|
||||
connection.send_message(messages.result_message(msg['id']))
|
||||
|
||||
|
|
23
homeassistant/components/websocket_api/permissions.py
Normal file
23
homeassistant/components/websocket_api/permissions.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
"""Permission constants for the websocket API.
|
||||
|
||||
Separate file to avoid circular imports.
|
||||
"""
|
||||
from homeassistant.const import (
|
||||
EVENT_COMPONENT_LOADED,
|
||||
EVENT_SERVICE_REGISTERED,
|
||||
EVENT_SERVICE_REMOVED,
|
||||
EVENT_STATE_CHANGED,
|
||||
EVENT_THEMES_UPDATED)
|
||||
from homeassistant.components.persistent_notification import (
|
||||
EVENT_PERSISTENT_NOTIFICATIONS_UPDATED)
|
||||
|
||||
# These are events that do not contain any sensitive data
|
||||
# Except for state_changed, which is handled accordingly.
|
||||
SUBSCRIBE_WHITELIST = {
|
||||
EVENT_COMPONENT_LOADED,
|
||||
EVENT_PERSISTENT_NOTIFICATIONS_UPDATED,
|
||||
EVENT_SERVICE_REGISTERED,
|
||||
EVENT_SERVICE_REMOVED,
|
||||
EVENT_STATE_CHANGED,
|
||||
EVENT_THEMES_UPDATED,
|
||||
}
|
|
@ -333,3 +333,76 @@ async def test_get_states_not_allows_nan(hass, websocket_client):
|
|||
msg = await websocket_client.receive_json()
|
||||
assert not msg['success']
|
||||
assert msg['error']['code'] == const.ERR_UNKNOWN_ERROR
|
||||
|
||||
|
||||
async def test_subscribe_unsubscribe_events_whitelist(
|
||||
hass, websocket_client, hass_admin_user):
|
||||
"""Test subscribe/unsubscribe events on whitelist."""
|
||||
hass_admin_user.groups = []
|
||||
|
||||
await websocket_client.send_json({
|
||||
'id': 5,
|
||||
'type': 'subscribe_events',
|
||||
'event_type': 'not-in-whitelist'
|
||||
})
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg['id'] == 5
|
||||
assert msg['type'] == const.TYPE_RESULT
|
||||
assert not msg['success']
|
||||
assert msg['error']['code'] == 'unauthorized'
|
||||
|
||||
await websocket_client.send_json({
|
||||
'id': 6,
|
||||
'type': 'subscribe_events',
|
||||
'event_type': 'themes_updated'
|
||||
})
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg['id'] == 6
|
||||
assert msg['type'] == const.TYPE_RESULT
|
||||
assert msg['success']
|
||||
|
||||
hass.bus.async_fire('themes_updated')
|
||||
|
||||
with timeout(3, loop=hass.loop):
|
||||
msg = await websocket_client.receive_json()
|
||||
|
||||
assert msg['id'] == 6
|
||||
assert msg['type'] == 'event'
|
||||
event = msg['event']
|
||||
assert event['event_type'] == 'themes_updated'
|
||||
assert event['origin'] == 'LOCAL'
|
||||
|
||||
|
||||
async def test_subscribe_unsubscribe_events_state_changed(
|
||||
hass, websocket_client, hass_admin_user):
|
||||
"""Test subscribe/unsubscribe state_changed events."""
|
||||
hass_admin_user.groups = []
|
||||
hass_admin_user.mock_policy({
|
||||
'entities': {
|
||||
'entity_ids': {
|
||||
'light.permitted': True
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await websocket_client.send_json({
|
||||
'id': 7,
|
||||
'type': 'subscribe_events',
|
||||
'event_type': 'state_changed'
|
||||
})
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg['id'] == 7
|
||||
assert msg['type'] == const.TYPE_RESULT
|
||||
assert msg['success']
|
||||
|
||||
hass.states.async_set('light.not_permitted', 'on')
|
||||
hass.states.async_set('light.permitted', 'on')
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg['id'] == 7
|
||||
assert msg['type'] == 'event'
|
||||
assert msg['event']['event_type'] == 'state_changed'
|
||||
assert msg['event']['data']['entity_id'] == 'light.permitted'
|
||||
|
|
Loading…
Add table
Reference in a new issue