From c96804954cb4b6ecad77005b9194f236a16b79f6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Apr 2019 01:22:51 -0700 Subject: [PATCH] Only allow admins to enable remote connection (#22609) * Only allow admins to enable remote connection * Protect WS API * Lint --- homeassistant/components/cloud/__init__.py | 9 ++++---- homeassistant/components/cloud/http_api.py | 2 ++ tests/components/cloud/test_init.py | 26 +++++++++++++++++++++- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 76a768385f8..fca5b292033 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -187,10 +187,11 @@ async def async_setup(hass, config): await cloud.remote.disconnect() await prefs.async_update(remote_enabled=False) - hass.services.async_register( - DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler) - hass.services.async_register( - DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler) + empty_schema = vol.Schema({}) + hass.helpers.service.async_register_admin_service( + DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler, empty_schema) + hass.helpers.service.async_register_admin_service( + DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler, empty_schema) await http_api.async_setup(hass) hass.async_create_task(hass.helpers.discovery.async_load_platform( diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 212bdfb4bf8..d997d98d06e 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -422,6 +422,7 @@ def _account_data(cloud): } +@websocket_api.require_admin @_require_cloud_login @websocket_api.async_response @_ws_handle_cloud_errors @@ -436,6 +437,7 @@ async def websocket_remote_connect(hass, connection, msg): connection.send_result(msg['id'], _account_data(cloud)) +@websocket_api.require_admin @_require_cloud_login @websocket_api.async_response @_ws_handle_cloud_errors diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 0de395c8bbc..ea611c29df1 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -1,6 +1,10 @@ """Test the cloud component.""" from unittest.mock import patch +import pytest + +from homeassistant.core import Context +from homeassistant.exceptions import Unauthorized from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import cloud from homeassistant.components.cloud.const import DOMAIN @@ -34,7 +38,7 @@ async def test_constructor_loads_info_from_config(hass): assert cl.relayer == 'test-relayer' -async def test_remote_services(hass, mock_cloud_fixture): +async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user): """Setup cloud component and test services.""" cloud = hass.data[DOMAIN] @@ -58,6 +62,26 @@ async def test_remote_services(hass, mock_cloud_fixture): assert mock_disconnect.called assert not cloud.client.remote_autostart + # Test admin access required + non_admin_context = Context(user_id=hass_read_only_user.id) + + with patch( + "hass_nabucasa.remote.RemoteUI.connect", return_value=mock_coro() + ) as mock_connect, pytest.raises(Unauthorized): + await hass.services.async_call(DOMAIN, "remote_connect", blocking=True, + context=non_admin_context) + + assert mock_connect.called is False + + with patch( + "hass_nabucasa.remote.RemoteUI.disconnect", return_value=mock_coro() + ) as mock_disconnect, pytest.raises(Unauthorized): + await hass.services.async_call( + DOMAIN, "remote_disconnect", blocking=True, + context=non_admin_context) + + assert mock_disconnect.called is False + async def test_startup_shutdown_events(hass, mock_cloud_fixture): """Test if the cloud will start on startup event."""