Enforce per-entity permissions in calendar HTTP and WS APIs#169235
Enforce per-entity permissions in calendar HTTP and WS APIs#169235
Conversation
The calendar HTTP views and WebSocket commands accepted user-supplied entity_ids without consulting the caller's entity policy. Mirror the canonical filter pattern from APIStatesView and _async_get_allowed_states so non-admin users with restrictive policies can no longer read or mutate calendars they do not have access to.
|
Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
There was a problem hiding this comment.
Pull request overview
This PR tightens access control for the Calendar HTTP views and WebSocket commands by enforcing per-entity permission policies so users can only read/control calendars allowed by their entity policy.
Changes:
- Enforce
POLICY_READfor calendar HTTP event retrieval and WS event subscriptions. - Enforce
POLICY_CONTROLfor calendar WS event create/update/delete operations. - Add tests validating unauthorized access is rejected and list/event responses are filtered appropriately.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
homeassistant/components/calendar/__init__.py |
Adds entity-policy permission checks to calendar HTTP and WS endpoints. |
tests/components/calendar/test_init.py |
Adds regression tests for permission enforcement across HTTP and WS handlers. |
| """Subscribe to calendar event updates.""" | ||
| entity_id: str = msg["entity_id"] | ||
|
|
||
| if not connection.user.permissions.check_entity(entity_id, POLICY_READ): | ||
| raise Unauthorized(entity_id=entity_id) | ||
|
|
There was a problem hiding this comment.
Extend the permission enforcement to account for policy changes after the subscription is created (currently it’s only checked at subscribe time), e.g., re-check before sending each event update and stop/unsubscribe if access is revoked.
There was a problem hiding this comment.
Sounds good. Although maybe all subscriptions should be stopped centrally if access rights are changed?
There was a problem hiding this comment.
Yeah, that's something we should consider for a future audit task. Probably disconnect all users connection when their permissions change.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
MartinHjelmare
left a comment
There was a problem hiding this comment.
Looks good! But note this bot review:
https://github.com/home-assistant/core/pull/169235/changes#r3144449900
|
Test failure is unrelated flaky test. |
Proposed change
The calendar HTTP views and WebSocket commands accepted user-supplied
entity_idvalues without consulting the caller's entity policy, so a non-admin user with a restrictive policy could still read or mutate calendars they should not have access to.This change mirrors the canonical filter pattern used by
APIStatesView/APIEntityStateView(homeassistant/components/api/__init__.py) and_async_get_allowed_states/_forward_entity_changes(homeassistant/components/websocket_api/commands.py):CalendarEventView.get— raiseUnauthorized(entity_id=…)when the user lacksPOLICY_READ.CalendarListView.get— filter the returned list bycheck_entity(POLICY_READ).calendar/event/create,calendar/event/delete,calendar/event/update— raiseUnauthorizedwhen the user lacksPOLICY_CONTROL.calendar/event/subscribe— raiseUnauthorizedwhen the user lacksPOLICY_READ.Tests cover each handler with a non-admin user whose policy permits only specific calendar entities.
When adding a new admin in the UI we tell users:
This PR is part of that audit.
Type of change
Additional information
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests: