diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index dcdb200d932a..e8e247545922 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -370,8 +370,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: message_expiry_interval=message_expiry_interval, ) - hass.services.async_register( - DOMAIN, SERVICE_PUBLISH, async_publish_service, schema=MQTT_PUBLISH_SCHEMA + async_register_admin_service( + hass, DOMAIN, SERVICE_PUBLISH, async_publish_service, MQTT_PUBLISH_SCHEMA ) async def async_dump_service(call: ServiceCall) -> None: @@ -395,7 +395,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ev.async_call_later(hass, call.data["duration"], finish_dump) - hass.services.async_register( + async_register_admin_service( + hass, DOMAIN, SERVICE_DUMP, async_dump_service, diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index f0190c69773c..5493d93a44f4 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -37,7 +37,12 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import HomeAssistantError, ServiceValidationError +from homeassistant.exceptions import ( + HomeAssistantError, + ServiceValidationError, + Unauthorized, + UnknownUser, +) from homeassistant.helpers import ( device_registry as dr, entity_registry as er, @@ -56,6 +61,7 @@ from tests.common import ( MockEntity, MockEntityPlatform, MockMqttReasonCode, + MockUser, async_capture_events, async_fire_mqtt_message, async_fire_time_changed, @@ -1113,6 +1119,77 @@ async def test_dump_service( assert writes[0][1][0] == ["bla/1,test1\n", "bla/2,test2\n"] +ADMIN_SERVICE_CALLS = [ + pytest.param( + mqtt.SERVICE_PUBLISH, + {mqtt.ATTR_TOPIC: "test/topic", mqtt.ATTR_PAYLOAD: "payload"}, + id="publish", + ), + pytest.param(mqtt.SERVICE_DUMP, {"topic": "bla/#", "duration": 3}, id="dump"), +] + + +@pytest.mark.parametrize(("service", "service_data"), ADMIN_SERVICE_CALLS) +async def test_admin_service_as_admin( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + hass_admin_user: MockUser, + service: str, + service_data: dict[str, Any], +) -> None: + """Test an admin user can call the action.""" + await mqtt_mock_entry() + + await hass.services.async_call( + mqtt.DOMAIN, + service, + service_data, + blocking=True, + context=ha.Context(user_id=hass_admin_user.id), + ) + + +@pytest.mark.parametrize(("service", "service_data"), ADMIN_SERVICE_CALLS) +async def test_admin_service_as_non_admin( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + hass_read_only_user: MockUser, + service: str, + service_data: dict[str, Any], +) -> None: + """Test a non-admin user cannot call the action.""" + await mqtt_mock_entry() + + with pytest.raises(Unauthorized): + await hass.services.async_call( + mqtt.DOMAIN, + service, + service_data, + blocking=True, + context=ha.Context(user_id=hass_read_only_user.id), + ) + + +@pytest.mark.parametrize(("service", "service_data"), ADMIN_SERVICE_CALLS) +async def test_admin_service_as_unknown_user( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + service: str, + service_data: dict[str, Any], +) -> None: + """Test a user that no longer exists cannot call the action.""" + await mqtt_mock_entry() + + with pytest.raises(UnknownUser): + await hass.services.async_call( + mqtt.DOMAIN, + service, + service_data, + blocking=True, + context=ha.Context(user_id="i-am-not-a-user"), + ) + + async def test_mqtt_ws_remove_discovered_device( hass: HomeAssistant, device_registry: dr.DeviceRegistry,