Documentation Index
Fetch the complete documentation index at: https://mintlify.com/home-assistant/core/llms.txt
Use this file to discover all available pages before exploring further.
The Home Assistant permissions system provides fine-grained access control for users based on their group memberships and policies. The system uses a policy-based approach where permissions are defined in policies attached to groups.
Architecture
Location: homeassistant/auth/permissions/__init__.py
The permissions system consists of several key components:
- Policies: Define what actions are allowed
- Groups: Collections of users with shared policies
- Permissions Objects: Evaluate access based on policies
- Permission Lookup: Provides entity and device metadata for policy evaluation
Permission Classes
AbstractPermissions
Location: homeassistant/auth/permissions/__init__.py:29
Base class for all permission implementations:
class AbstractPermissions:
"""Default permissions class."""
_cached_entity_func: Callable[[str, str], bool] | None = None
def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access."""
raise NotImplementedError
def access_all_entities(self, key: str) -> bool:
"""Check if we have a certain access to all entities."""
raise NotImplementedError
def check_entity(self, entity_id: str, key: str) -> bool:
"""Check if we can access entity."""
if (entity_func := self._cached_entity_func) is None:
entity_func = self._cached_entity_func = self._entity_func()
return entity_func(entity_id, key)
Access Keys:
"read": View entity state
"control": Change entity state
"edit": Modify entity configuration
OwnerPermissions
Location: homeassistant/auth/permissions/__init__.py:71
Special permissions object for owners with full access:
class _OwnerPermissions(AbstractPermissions):
"""Owner permissions."""
def access_all_entities(self, key: str) -> bool:
"""Check if we have a certain access to all entities."""
return True
def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access."""
return lambda entity_id, key: True
OwnerPermissions = _OwnerPermissions() # Singleton instance
Owners have unrestricted access to all resources.
PolicyPermissions
Location: homeassistant/auth/permissions/__init__.py:50
Policy-based permissions for regular users:
class PolicyPermissions(AbstractPermissions):
"""Handle permissions."""
def __init__(self, policy: PolicyType, perm_lookup: PermissionLookup):
self._policy = policy
self._perm_lookup = perm_lookup
def access_all_entities(self, key: str) -> bool:
"""Check if we have a certain access to all entities."""
return test_all(self._policy.get(CAT_ENTITIES), key)
def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access."""
return compile_entities(self._policy.get(CAT_ENTITIES), self._perm_lookup)
Evaluates permissions based on the merged policy from user’s groups.
Policy Structure
Location: homeassistant/auth/permissions/__init__.py:16
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
Policies are dictionaries with category keys. Currently, only the entities category is supported:
{
"entities": {
"entity_ids": {
"light.bedroom": {"read": true, "control": true},
"switch.kitchen": {"read": true}
},
"domains": {
"light": {"read": true, "control": true},
"switch": {"read": true}
},
"areas": {
"bedroom": {"read": true, "control": true}
},
"labels": {
"personal": {"read": true, "control": true}
},
"all": {"read": true} # Access to all entities
}
}
Entity Policy Schema
Location: homeassistant/auth/permissions/entities.py
ENTITY_POLICY_SCHEMA = vol.Schema({
vol.Optional("entity_ids"): {str: ENTITY_PERMISSIONS_SCHEMA},
vol.Optional("domains"): {str: ENTITY_PERMISSIONS_SCHEMA},
vol.Optional("areas"): {str: ENTITY_PERMISSIONS_SCHEMA},
vol.Optional("labels"): {str: ENTITY_PERMISSIONS_SCHEMA},
vol.Optional("all"): ENTITY_PERMISSIONS_SCHEMA,
})
ENTITY_PERMISSIONS_SCHEMA = vol.Schema({
vol.Optional("read"): bool,
vol.Optional("control"): bool,
vol.Optional("edit"): bool,
})
Policy Selectors
Policies can specify permissions using multiple selectors:
Entity IDs
Direct entity ID matching:
"entity_ids": {
"light.bedroom": {"read": True, "control": True},
"climate.living_room": {"read": True},
}
Domains
Match all entities in a domain:
"domains": {
"light": {"read": True, "control": True},
"switch": {"read": True},
}
Areas
Match all entities in an area:
"areas": {
"bedroom": {"read": True, "control": True},
"garage": {"read": True},
}
Labels
Match all entities with a label:
"labels": {
"personal": {"read": True, "control": True},
"secure": {"read": True},
}
All Entities
Grant access to all entities:
Permission Lookup
Location: homeassistant/auth/permissions/models.py:14
@attr.s(slots=True)
class PermissionLookup:
"""Class to hold data for permission lookups."""
entity_registry: er.EntityRegistry = attr.ib()
device_registry: dr.DeviceRegistry = attr.ib()
The PermissionLookup provides access to entity and device registries for resolving area and label memberships during policy evaluation.
System Policies
Location: homeassistant/auth/permissions/system_policies.py
Home Assistant defines several built-in system policies:
Admin Policy
Full access to all entities:
ADMIN_POLICY: PolicyType = {
CAT_ENTITIES: {
"all": {
"read": True,
"control": True,
"edit": True,
}
}
}
User Policy
Read and control access to all entities:
USER_POLICY: PolicyType = {
CAT_ENTITIES: {
"all": {
"read": True,
"control": True,
"edit": False,
}
}
}
Read-Only Policy
Read-only access to all entities:
READ_ONLY_POLICY: PolicyType = {
CAT_ENTITIES: {
"all": {
"read": True,
"control": False,
"edit": False,
}
}
}
Policy Merging
Location: homeassistant/auth/permissions/merge.py
When a user belongs to multiple groups, their policies are merged:
def merge_policies(policies: list[PolicyType]) -> PolicyType:
"""Merge multiple policies."""
# Policies are merged using OR logic
# If any policy grants access, access is granted
Merge strategy:
- Start with empty policy (no access)
- For each group policy, merge permissions
- Use OR logic: if any policy grants a permission, it’s granted
- More specific selectors take precedence
Example:
User in two groups:
- Group A:
{"domains": {"light": {"read": True}}}
- Group B:
{"entity_ids": {"light.bedroom": {"control": True}}}
Merged policy:
{
"domains": {"light": {"read": True}},
"entity_ids": {"light.bedroom": {"read": True, "control": True}}
}
User Permission Access
Location: homeassistant/auth/models.py:82
Users have a cached permissions property:
@cached_property
def permissions(self) -> perm_mdl.AbstractPermissions:
"""Return permissions object for user."""
if self.is_owner:
return perm_mdl.OwnerPermissions
return perm_mdl.PolicyPermissions(
perm_mdl.merge_policies([group.policy for group in self.groups]),
self.perm_lookup,
)
Important: Call user.invalidate_cache() after modifying groups or policies.
Checking Permissions
Check Entity Access
# Check if user can read an entity
if user.permissions.check_entity("light.bedroom", "read"):
# Show entity state
pass
# Check if user can control an entity
if user.permissions.check_entity("light.bedroom", "control"):
# Allow turning on/off
pass
# Check if user can edit an entity
if user.permissions.check_entity("light.bedroom", "edit"):
# Allow configuration changes
pass
Check All Entities Access
# Check if user has read access to all entities
if user.permissions.access_all_entities("read"):
# User can see all entity states
pass
# Check if user has control access to all entities
if user.permissions.access_all_entities("control"):
# User can control all entities
pass
Owner Check
if user.is_owner:
# Owner has full access to everything
pass
Admin Check
Location: homeassistant/auth/models.py:92
@cached_property
def is_admin(self) -> bool:
"""Return if user is part of the admin group."""
return self.is_owner or (
self.is_active and any(gr.id == GROUP_ID_ADMIN for gr in self.groups)
)
Policy Compilation
Location: homeassistant/auth/permissions/entities.py
For performance, policies are compiled into efficient evaluation functions:
def compile_entities(
policy: dict | None,
perm_lookup: PermissionLookup
) -> Callable[[str, str], bool]:
"""Compile an entity policy into a validation function."""
# Returns a function that efficiently checks entity access
# Uses compiled matchers for fast evaluation
The compiled function:
- Checks direct entity ID matches first (fastest)
- Checks domain matches
- Looks up entity in registry for area/label matching
- Falls back to “all” selector
Groups and Policies
Location: homeassistant/auth/auth_store.py
Built-in Groups
Administrators (GROUP_ID_ADMIN: system-admin):
- Policy: Full access (read, control, edit)
- For trusted users who manage the system
Users (GROUP_ID_USER: system-users):
- Policy: Read and control access
- For regular users
Read Only (GROUP_ID_READ_ONLY: system-read-only):
- Policy: Read-only access
- For monitoring/display purposes
Creating Custom Groups
# Create a custom group with limited access
group = models.Group(
name="Guest",
policy={
"entities": {
"domains": {
"light": {"read": True, "control": True}
},
"areas": {
"living_room": {"read": True, "control": True}
}
}
},
system_generated=False
)
Assigning Users to Groups
# When creating a user
user = await auth_manager.async_create_user(
name="Guest User",
group_ids=[guest_group.id]
)
# When updating a user
await auth_manager.async_update_user(
user,
group_ids=[guest_group.id, another_group.id]
)
Permission Utilities
Location: homeassistant/auth/permissions/util.py
test_all()
def test_all(policy: dict | None, key: str) -> bool:
"""Test if a policy grants access to all entities."""
if policy is None:
return False
all_perms = policy.get("all")
if all_perms is None:
return False
return all_perms.get(key, False)
WebSocket API Integration
The permissions system integrates with the WebSocket API to filter results:
# In a WebSocket command handler
@websocket_api.require_admin # Requires admin group
async def handle_command(hass, connection, msg):
pass
# Check permissions manually
@websocket_api.websocket_command({vol.Required("type"): "get_entity"})
async def handle_get_entity(hass, connection, msg):
entity_id = msg["entity_id"]
if not connection.user.permissions.check_entity(entity_id, "read"):
connection.send_error(
msg["id"],
"unauthorized",
"Insufficient permissions"
)
return
# Return entity data
HTTP API Integration
The HTTP API validates permissions using decorators:
from homeassistant.components.http import require_admin
class MyView(HomeAssistantView):
@require_admin
async def post(self, request):
# Only admins can access
pass
Local-Only Users
Location: homeassistant/auth/models.py:68
Users can be marked as local-only:
user = await auth_manager.async_create_user(
name="Local User",
local_only=True
)
Local-only users:
- Cannot authenticate via cloud connections
- Restricted to local network access
- Useful for guest accounts or limited access users
Best Practices
- Use groups instead of per-user policies
- Start restrictive and grant permissions as needed
- Invalidate cache after permission changes:
- Check permissions before sensitive operations
- Use owner check sparingly - prefer granular permissions
- Test policy changes thoroughly before deployment
- Document custom policies for maintainability
- Use area/label selectors for logical grouping
- Avoid excessive entity_ids - use domains when possible
- Consider local_only for untrusted users
- Permission checks are cached at the user level
- Policy compilation happens once per user session
- Entity function is cached after first use
- Direct entity ID lookups are fastest
- Registry lookups (area/label) are slower
Security Notes
- Owner status cannot be restricted by policies
- System-generated users have special permission handling
- Inactive users have no effective permissions
- Permission changes require cache invalidation
- Policies use OR logic (permissive merging)
Example: Custom Permission Policy
# Create a policy for a smart home guest
guest_policy = {
"entities": {
# Can read all entities
"all": {"read": True},
# Can control lights and media players
"domains": {
"light": {"control": True},
"media_player": {"control": True},
},
# Can control everything in guest bedroom
"areas": {
"guest_bedroom": {"control": True},
},
# Cannot control security devices
"entity_ids": {
"alarm_control_panel.home": {"read": True, "control": False},
"lock.front_door": {"read": True, "control": False},
},
}
}
# Create guest group
guest_group = models.Group(
name="Guests",
policy=guest_policy,
system_generated=False
)
# Assign user to guest group
user = await auth_manager.async_create_user(
name="Guest User",
group_ids=[guest_group.id],
local_only=True # Only local access
)