diff --git a/homeassistant/components/config/auth.py b/homeassistant/components/config/auth.py index ec83918e9f0..625dbefbbb3 100644 --- a/homeassistant/components/config/auth.py +++ b/homeassistant/components/config/auth.py @@ -39,7 +39,7 @@ async def async_setup(hass): return True -@websocket_api.require_owner +@websocket_api.require_admin @websocket_api.async_response async def websocket_list(hass, connection, msg): """Return a list of users.""" @@ -49,7 +49,7 @@ async def websocket_list(hass, connection, msg): websocket_api.result_message(msg['id'], result)) -@websocket_api.require_owner +@websocket_api.require_admin @websocket_api.async_response async def websocket_delete(hass, connection, msg): """Delete a user.""" @@ -72,7 +72,7 @@ async def websocket_delete(hass, connection, msg): websocket_api.result_message(msg['id'])) -@websocket_api.require_owner +@websocket_api.require_admin @websocket_api.async_response async def websocket_create(hass, connection, msg): """Create a user.""" diff --git a/homeassistant/components/config/auth_provider_homeassistant.py b/homeassistant/components/config/auth_provider_homeassistant.py index 3495a959f49..5455277aa78 100644 --- a/homeassistant/components/config/auth_provider_homeassistant.py +++ b/homeassistant/components/config/auth_provider_homeassistant.py @@ -3,7 +3,6 @@ import voluptuous as vol from homeassistant.auth.providers import homeassistant as auth_ha from homeassistant.components import websocket_api -from homeassistant.components.websocket_api.decorators import require_owner WS_TYPE_CREATE = 'config/auth_provider/homeassistant/create' @@ -54,7 +53,7 @@ def _get_provider(hass): raise RuntimeError('Provider not found') -@require_owner +@websocket_api.require_admin @websocket_api.async_response async def websocket_create(hass, connection, msg): """Create credentials and attach to a user.""" @@ -91,7 +90,7 @@ async def websocket_create(hass, connection, msg): connection.send_message(websocket_api.result_message(msg['id'])) -@require_owner +@websocket_api.require_admin @websocket_api.async_response async def websocket_delete(hass, connection, msg): """Delete username and related credential.""" @@ -123,6 +122,7 @@ async def websocket_delete(hass, connection, msg): websocket_api.result_message(msg['id'])) +@websocket_api.require_admin @websocket_api.async_response async def websocket_change_password(hass, connection, msg): """Change user password.""" diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py index 90c802423ce..9c67af820f4 100644 --- a/homeassistant/components/websocket_api/__init__.py +++ b/homeassistant/components/websocket_api/__init__.py @@ -20,7 +20,7 @@ BASE_COMMAND_MESSAGE_SCHEMA = messages.BASE_COMMAND_MESSAGE_SCHEMA error_message = messages.error_message result_message = messages.result_message async_response = decorators.async_response -require_owner = decorators.require_owner +require_admin = decorators.require_admin ws_require_user = decorators.ws_require_user # pylint: enable=invalid-name diff --git a/homeassistant/components/websocket_api/decorators.py b/homeassistant/components/websocket_api/decorators.py index 34250202a5e..d91b884541d 100644 --- a/homeassistant/components/websocket_api/decorators.py +++ b/homeassistant/components/websocket_api/decorators.py @@ -3,6 +3,7 @@ from functools import wraps import logging from homeassistant.core import callback +from homeassistant.exceptions import Unauthorized from . import messages @@ -30,21 +31,19 @@ def async_response(func): return schedule_handler -def require_owner(func): - """Websocket decorator to require user to be an owner.""" +def require_admin(func): + """Websocket decorator to require user to be an admin.""" @wraps(func) - def with_owner(hass, connection, msg): - """Check owner and call function.""" + def with_admin(hass, connection, msg): + """Check admin and call function.""" user = connection.user - if user is None or not user.is_owner: - connection.send_message(messages.error_message( - msg['id'], 'unauthorized', 'This command is for owners only.')) - return + if user is None or not user.is_admin: + raise Unauthorized() func(hass, connection, msg) - return with_owner + return with_admin def ws_require_user( diff --git a/tests/components/config/test_auth.py b/tests/components/config/test_auth.py index b5e0a8c9197..5cc7b4bd82e 100644 --- a/tests/components/config/test_auth.py +++ b/tests/components/config/test_auth.py @@ -13,9 +13,10 @@ def setup_config(hass, aiohttp_client): hass.loop.run_until_complete(auth_config.async_setup(hass)) -async def test_list_requires_owner(hass, hass_ws_client, hass_access_token): +async def test_list_requires_admin(hass, hass_ws_client, + hass_read_only_access_token): """Test get users requires auth.""" - client = await hass_ws_client(hass, hass_access_token) + client = await hass_ws_client(hass, hass_read_only_access_token) await client.send_json({ 'id': 5, @@ -109,9 +110,10 @@ async def test_list(hass, hass_ws_client, hass_admin_user): } -async def test_delete_requires_owner(hass, hass_ws_client, hass_access_token): +async def test_delete_requires_admin(hass, hass_ws_client, + hass_read_only_access_token): """Test delete command requires an owner.""" - client = await hass_ws_client(hass, hass_access_token) + client = await hass_ws_client(hass, hass_read_only_access_token) await client.send_json({ 'id': 5, @@ -139,15 +141,12 @@ async def test_delete_unable_self_account(hass, hass_ws_client, result = await client.receive_json() assert not result['success'], result - assert result['error']['code'] == 'unauthorized' + assert result['error']['code'] == 'no_delete_self' async def test_delete_unknown_user(hass, hass_ws_client, hass_access_token): """Test we cannot delete an unknown user.""" client = await hass_ws_client(hass, hass_access_token) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True await client.send_json({ 'id': 5, @@ -163,9 +162,6 @@ async def test_delete_unknown_user(hass, hass_ws_client, hass_access_token): async def test_delete(hass, hass_ws_client, hass_access_token): """Test delete command works.""" client = await hass_ws_client(hass, hass_access_token) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True test_user = MockUser( id='efg', ).add_to_hass(hass) @@ -186,9 +182,6 @@ async def test_delete(hass, hass_ws_client, hass_access_token): async def test_create(hass, hass_ws_client, hass_access_token): """Test create command works.""" client = await hass_ws_client(hass, hass_access_token) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True assert len(await hass.auth.async_get_users()) == 1 @@ -210,9 +203,10 @@ async def test_create(hass, hass_ws_client, hass_access_token): assert not user.system_generated -async def test_create_requires_owner(hass, hass_ws_client, hass_access_token): +async def test_create_requires_admin(hass, hass_ws_client, + hass_read_only_access_token): """Test create command requires an owner.""" - client = await hass_ws_client(hass, hass_access_token) + client = await hass_ws_client(hass, hass_read_only_access_token) await client.send_json({ 'id': 5, diff --git a/tests/components/config/test_auth_provider_homeassistant.py b/tests/components/config/test_auth_provider_homeassistant.py index a374083c2ab..a4c4c5a3c5a 100644 --- a/tests/components/config/test_auth_provider_homeassistant.py +++ b/tests/components/config/test_auth_provider_homeassistant.py @@ -22,9 +22,6 @@ async def test_create_auth_system_generated_user(hass, hass_access_token, """Test we can't add auth to system generated users.""" system_user = MockUser(system_generated=True).add_to_hass(hass) client = await hass_ws_client(hass, hass_access_token) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True await client.send_json({ 'id': 5, @@ -49,9 +46,6 @@ async def test_create_auth_unknown_user(hass_ws_client, hass, hass_access_token): """Test create pointing at unknown user.""" client = await hass_ws_client(hass, hass_access_token) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True await client.send_json({ 'id': 5, @@ -67,10 +61,10 @@ async def test_create_auth_unknown_user(hass_ws_client, hass, assert result['error']['code'] == 'not_found' -async def test_create_auth_requires_owner(hass, hass_ws_client, - hass_access_token): - """Test create requires owner to call API.""" - client = await hass_ws_client(hass, hass_access_token) +async def test_create_auth_requires_admin(hass, hass_ws_client, + hass_read_only_access_token): + """Test create requires admin to call API.""" + client = await hass_ws_client(hass, hass_read_only_access_token) await client.send_json({ 'id': 5, @@ -90,9 +84,6 @@ async def test_create_auth(hass, hass_ws_client, hass_access_token, """Test create auth command works.""" client = await hass_ws_client(hass, hass_access_token) user = MockUser().add_to_hass(hass) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True assert len(user.credentials) == 0 @@ -123,9 +114,6 @@ async def test_create_auth_duplicate_username(hass, hass_ws_client, """Test we can't create auth with a duplicate username.""" client = await hass_ws_client(hass, hass_access_token) user = MockUser().add_to_hass(hass) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True hass_storage[prov_ha.STORAGE_KEY] = { 'version': 1, @@ -153,9 +141,6 @@ async def test_delete_removes_just_auth(hass_ws_client, hass, hass_storage, hass_access_token): """Test deleting an auth without being connected to a user.""" client = await hass_ws_client(hass, hass_access_token) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True hass_storage[prov_ha.STORAGE_KEY] = { 'version': 1, @@ -181,9 +166,6 @@ async def test_delete_removes_credential(hass, hass_ws_client, hass_access_token, hass_storage): """Test deleting auth that is connected to a user.""" client = await hass_ws_client(hass, hass_access_token) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True user = MockUser().add_to_hass(hass) user.credentials.append( @@ -210,9 +192,10 @@ async def test_delete_removes_credential(hass, hass_ws_client, assert len(hass_storage[prov_ha.STORAGE_KEY]['data']['users']) == 0 -async def test_delete_requires_owner(hass, hass_ws_client, hass_access_token): - """Test delete requires owner.""" - client = await hass_ws_client(hass, hass_access_token) +async def test_delete_requires_admin(hass, hass_ws_client, + hass_read_only_access_token): + """Test delete requires admin.""" + client = await hass_ws_client(hass, hass_read_only_access_token) await client.send_json({ 'id': 5, @@ -228,9 +211,6 @@ async def test_delete_requires_owner(hass, hass_ws_client, hass_access_token): async def test_delete_unknown_auth(hass, hass_ws_client, hass_access_token): """Test trying to delete an unknown auth username.""" client = await hass_ws_client(hass, hass_access_token) - refresh_token = await hass.auth.async_validate_access_token( - hass_access_token) - refresh_token.user.is_owner = True await client.send_json({ 'id': 5, diff --git a/tests/conftest.py b/tests/conftest.py index 82ae596fb48..528ad195195 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -142,7 +142,7 @@ def hass_access_token(hass, hass_admin_user): """Return an access token to access Home Assistant.""" refresh_token = hass.loop.run_until_complete( hass.auth.async_create_refresh_token(hass_admin_user, CLIENT_ID)) - yield hass.auth.async_create_access_token(refresh_token) + return hass.auth.async_create_access_token(refresh_token) @pytest.fixture @@ -167,6 +167,14 @@ def hass_read_only_user(hass, local_auth): return MockUser(groups=[read_only_group]).add_to_hass(hass) +@pytest.fixture +def hass_read_only_access_token(hass, hass_read_only_user): + """Return a Home Assistant read only user.""" + refresh_token = hass.loop.run_until_complete( + hass.auth.async_create_refresh_token(hass_read_only_user, CLIENT_ID)) + return hass.auth.async_create_access_token(refresh_token) + + @pytest.fixture def legacy_auth(hass): """Load legacy API password provider."""