> ## 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.

# Permissions System

> Fine-grained access control in Home Assistant

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:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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`

```python theme={null}
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
```

Policies are dictionaries with category keys. Currently, only the `entities` category is supported:

```python theme={null}
{
    "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`

```python theme={null}
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:

```python theme={null}
"entity_ids": {
    "light.bedroom": {"read": True, "control": True},
    "climate.living_room": {"read": True},
}
```

#### Domains

Match all entities in a domain:

```python theme={null}
"domains": {
    "light": {"read": True, "control": True},
    "switch": {"read": True},
}
```

#### Areas

Match all entities in an area:

```python theme={null}
"areas": {
    "bedroom": {"read": True, "control": True},
    "garage": {"read": True},
}
```

#### Labels

Match all entities with a label:

```python theme={null}
"labels": {
    "personal": {"read": True, "control": True},
    "secure": {"read": True},
}
```

#### All Entities

Grant access to all entities:

```python theme={null}
"all": {"read": True}
```

## Permission Lookup

Location: `homeassistant/auth/permissions/models.py:14`

```python theme={null}
@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:

```python theme={null}
ADMIN_POLICY: PolicyType = {
    CAT_ENTITIES: {
        "all": {
            "read": True,
            "control": True,
            "edit": True,
        }
    }
}
```

### User Policy

Read and control access to all entities:

```python theme={null}
USER_POLICY: PolicyType = {
    CAT_ENTITIES: {
        "all": {
            "read": True,
            "control": True,
            "edit": False,
        }
    }
}
```

### Read-Only Policy

Read-only access to all entities:

```python theme={null}
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:

```python theme={null}
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:

1. Start with empty policy (no access)
2. For each group policy, merge permissions
3. Use OR logic: if any policy grants a permission, it's granted
4. 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:

```python theme={null}
{
    "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:

```python theme={null}
@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

```python theme={null}
# 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

```python theme={null}
# 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

```python theme={null}
if user.is_owner:
    # Owner has full access to everything
    pass
```

### Admin Check

Location: `homeassistant/auth/models.py:92`

```python theme={null}
@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:

```python theme={null}
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:

1. Checks direct entity ID matches first (fastest)
2. Checks domain matches
3. Looks up entity in registry for area/label matching
4. 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

```python theme={null}
# 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

```python theme={null}
# 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()

```python theme={null}
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:

```python theme={null}
# 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:

```python theme={null}
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:

```python theme={null}
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

1. **Use groups** instead of per-user policies
2. **Start restrictive** and grant permissions as needed
3. **Invalidate cache** after permission changes:
   ```python theme={null}
   user.invalidate_cache()
   ```
4. **Check permissions** before sensitive operations
5. **Use owner check** sparingly - prefer granular permissions
6. **Test policy changes** thoroughly before deployment
7. **Document custom policies** for maintainability
8. **Use area/label selectors** for logical grouping
9. **Avoid excessive entity\_ids** - use domains when possible
10. **Consider local\_only** for untrusted users

## Performance Considerations

* 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

```python theme={null}
# 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
)
```

## Related Components

* [Authentication Overview](/api/auth/overview) - Core auth concepts
* [Authentication Providers](/api/auth/providers) - User authentication
* [WebSocket API](/api/websocket) - Permission-aware API
