diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index d2ce63f9490..e36eb6800fa 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -284,6 +284,7 @@ class AuthManager: self, user: models.User, name: Optional[str] = None, + is_active: Optional[bool] = None, group_ids: Optional[List[str]] = None, ) -> None: """Update a user.""" @@ -294,6 +295,12 @@ class AuthManager: kwargs["group_ids"] = group_ids await self._store.async_update_user(user, **kwargs) + if is_active is not None: + if is_active is True: + await self.async_activate_user(user) + else: + await self.async_deactivate_user(user) + async def async_activate_user(self, user: models.User) -> None: """Activate a user.""" await self._store.async_activate_user(user) diff --git a/homeassistant/components/config/auth.py b/homeassistant/components/config/auth.py index 440ac05cef4..c1d43a5d4a9 100644 --- a/homeassistant/components/config/auth.py +++ b/homeassistant/components/config/auth.py @@ -86,6 +86,7 @@ async def websocket_create(hass, connection, msg): vol.Required("type"): "config/auth/update", vol.Required("user_id"): str, vol.Optional("name"): str, + vol.Optional("is_active"): bool, vol.Optional("group_ids"): [str], } ) @@ -111,6 +112,16 @@ async def websocket_update(hass, connection, msg): ) return + if user.is_owner and msg["is_active"] is False: + connection.send_message( + websocket_api.error_message( + msg["id"], + "cannot_deactivate_owner", + "Unable to deactivate owner.", + ) + ) + return + msg.pop("type") msg_id = msg.pop("id") diff --git a/tests/components/config/test_auth.py b/tests/components/config/test_auth.py index e7e3c67e5d8..2d3cfe54f5a 100644 --- a/tests/components/config/test_auth.py +++ b/tests/components/config/test_auth.py @@ -285,3 +285,76 @@ async def test_update_system_generated(hass, hass_ws_client): assert not result["success"], result assert result["error"]["code"] == "cannot_modify_system_generated" assert user.name == "Test user" + + +async def test_deactivate(hass, hass_ws_client): + """Test deactivation and reactivation of regular user.""" + client = await hass_ws_client(hass) + + user = await hass.auth.async_create_user("Test user") + assert user.is_active is True + + await client.send_json( + { + "id": 5, + "type": "config/auth/update", + "user_id": user.id, + "name": "Updated name", + "is_active": False, + } + ) + + result = await client.receive_json() + assert result["success"], result + data_user = result["result"]["user"] + assert data_user["is_active"] is False + + await client.send_json( + { + "id": 6, + "type": "config/auth/update", + "user_id": user.id, + "name": "Updated name", + "is_active": True, + } + ) + + result = await client.receive_json() + assert result["success"], result + data_user = result["result"]["user"] + assert data_user["is_active"] is True + + +async def test_deactivate_owner(hass, hass_ws_client): + """Test that owner cannot be deactivated.""" + user = MockUser(id="abc", name="Test Owner", is_owner=True).add_to_hass(hass) + + assert user.is_active is True + assert user.is_owner is True + + client = await hass_ws_client(hass) + await client.send_json( + {"id": 5, "type": "config/auth/update", "user_id": user.id, "is_active": False} + ) + + result = await client.receive_json() + assert not result["success"], result + assert result["error"]["code"] == "cannot_deactivate_owner" + + +async def test_deactivate_system_generated(hass, hass_ws_client): + """Test that owner cannot be deactivated.""" + client = await hass_ws_client(hass) + + user = await hass.auth.async_create_system_user("Test user") + assert user.is_active is True + assert user.system_generated is True + assert user.is_owner is False + + await client.send_json( + {"id": 5, "type": "config/auth/update", "user_id": user.id, "is_active": False} + ) + + result = await client.receive_json() + assert not result["success"], result + assert result["error"]["code"] == "cannot_modify_system_generated"