From 3e411935bbe07ebe0e7a9f5323734448486d75d7 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Sun, 2 Oct 2022 03:11:54 +0200 Subject: [PATCH] Fix Netatmo scope issue with HA cloud (#79437) Co-authored-by: Paulus Schoutsen --- homeassistant/components/netatmo/__init__.py | 14 ++++++++-- .../components/netatmo/config_flow.py | 9 ++++++- tests/components/netatmo/test_camera.py | 26 +++++++++---------- tests/components/netatmo/test_climate.py | 24 ++++++++--------- tests/components/netatmo/test_cover.py | 2 +- tests/components/netatmo/test_init.py | 2 +- tests/components/netatmo/test_light.py | 6 ++--- tests/components/netatmo/test_select.py | 2 +- tests/components/netatmo/test_sensor.py | 8 +++--- tests/components/netatmo/test_switch.py | 2 +- 10 files changed, 56 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index 65b321d25aa..eb0e93c4b38 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -137,9 +137,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryAuthFailed("Token not valid, trigger renewal") from ex raise ConfigEntryNotReady from ex - if sorted(session.token["scope"]) != sorted(NETATMO_SCOPES): + if entry.data["auth_implementation"] == cloud.DOMAIN: + required_scopes = { + scope + for scope in NETATMO_SCOPES + if scope not in ("access_doorbell", "read_doorbell") + } + else: + required_scopes = set(NETATMO_SCOPES) + + if not (set(session.token["scope"]) & required_scopes): _LOGGER.debug( - "Scope is invalid: %s != %s", session.token["scope"], NETATMO_SCOPES + "Session is missing scopes: %s", + required_scopes - set(session.token["scope"]), ) raise ConfigEntryAuthFailed("Token scope not valid, trigger renewal") diff --git a/homeassistant/components/netatmo/config_flow.py b/homeassistant/components/netatmo/config_flow.py index 99fa195b118..acd8965d013 100644 --- a/homeassistant/components/netatmo/config_flow.py +++ b/homeassistant/components/netatmo/config_flow.py @@ -54,7 +54,14 @@ class NetatmoFlowHandler( @property def extra_authorize_data(self) -> dict: """Extra data that needs to be appended to the authorize url.""" - return {"scope": " ".join(ALL_SCOPES)} + exclude = [] + if self.flow_impl.name == "Home Assistant Cloud": + exclude = ["access_doorbell", "read_doorbell"] + + scopes = [scope for scope in ALL_SCOPES if scope not in exclude] + scopes.sort() + + return {"scope": " ".join(scopes)} async def async_step_user(self, user_input: dict | None = None) -> FlowResult: """Handle a flow start.""" diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index ea39497ce58..027b0907d50 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -25,7 +25,7 @@ from tests.common import async_capture_events, async_fire_time_changed async def test_setup_component_with_webhook(hass, config_entry, netatmo_auth): """Test setup with webhook.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -132,7 +132,7 @@ IMAGE_BYTES_FROM_STREAM = b"test stream image bytes" async def test_camera_image_local(hass, config_entry, requests_mock, netatmo_auth): """Test retrieval or local camera image.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -158,7 +158,7 @@ async def test_camera_image_local(hass, config_entry, requests_mock, netatmo_aut async def test_camera_image_vpn(hass, config_entry, requests_mock, netatmo_auth): """Test retrieval of remote camera image.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -182,7 +182,7 @@ async def test_camera_image_vpn(hass, config_entry, requests_mock, netatmo_auth) async def test_service_set_person_away(hass, config_entry, netatmo_auth): """Test service to set person as away.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -219,7 +219,7 @@ async def test_service_set_person_away(hass, config_entry, netatmo_auth): async def test_service_set_person_away_invalid_person(hass, config_entry, netatmo_auth): """Test service to set invalid person as away.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -247,7 +247,7 @@ async def test_service_set_persons_home_invalid_person( ): """Test service to set invalid persons as home.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -273,7 +273,7 @@ async def test_service_set_persons_home_invalid_person( async def test_service_set_persons_home(hass, config_entry, netatmo_auth): """Test service to set persons as home.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -297,7 +297,7 @@ async def test_service_set_persons_home(hass, config_entry, netatmo_auth): async def test_service_set_camera_light(hass, config_entry, netatmo_auth): """Test service to set the outdoor camera light mode.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -327,7 +327,7 @@ async def test_service_set_camera_light(hass, config_entry, netatmo_auth): async def test_service_set_camera_light_invalid_type(hass, config_entry, netatmo_auth): """Test service to set the indoor camera light mode.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -377,7 +377,7 @@ async def test_camera_reconnect_webhook(hass, config_entry): mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() mock_webhook.return_value = "https://example.com" - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -412,7 +412,7 @@ async def test_camera_reconnect_webhook(hass, config_entry): async def test_webhook_person_event(hass, config_entry, netatmo_auth): """Test that person events are handled.""" with selected_platforms(["camera"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -469,7 +469,7 @@ async def test_setup_component_no_devices(hass, config_entry): mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert fake_post_hits == 9 @@ -508,7 +508,7 @@ async def test_camera_image_raises_exception(hass, config_entry, requests_mock): mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() camera_entity_indoor = "camera.hall" diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py index cc23dc887bd..d37bab929e1 100644 --- a/tests/components/netatmo/test_climate.py +++ b/tests/components/netatmo/test_climate.py @@ -27,7 +27,7 @@ from .common import selected_platforms, simulate_webhook async def test_webhook_event_handling_thermostats(hass, config_entry, netatmo_auth): """Test service and webhook event handling with thermostats.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -204,7 +204,7 @@ async def test_service_preset_mode_frost_guard_thermostat( ): """Test service with frost guard preset for thermostats.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -277,7 +277,7 @@ async def test_service_preset_mode_frost_guard_thermostat( async def test_service_preset_modes_thermostat(hass, config_entry, netatmo_auth): """Test service with preset modes for thermostats.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -356,7 +356,7 @@ async def test_service_preset_modes_thermostat(hass, config_entry, netatmo_auth) async def test_webhook_event_handling_no_data(hass, config_entry, netatmo_auth): """Test service and webhook event handling with erroneous data.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -405,7 +405,7 @@ async def test_webhook_event_handling_no_data(hass, config_entry, netatmo_auth): async def test_service_schedule_thermostats(hass, config_entry, caplog, netatmo_auth): """Test service for selecting Netatmo schedule with thermostats.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -458,7 +458,7 @@ async def test_service_preset_mode_already_boost_valves( ): """Test service with boost preset for valves when already in boost mode.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -536,7 +536,7 @@ async def test_service_preset_mode_already_boost_valves( async def test_service_preset_mode_boost_valves(hass, config_entry, netatmo_auth): """Test service with boost preset for valves.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -586,7 +586,7 @@ async def test_service_preset_mode_boost_valves(hass, config_entry, netatmo_auth async def test_service_preset_mode_invalid(hass, config_entry, caplog, netatmo_auth): """Test service with invalid preset.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -604,7 +604,7 @@ async def test_service_preset_mode_invalid(hass, config_entry, caplog, netatmo_a async def test_valves_service_turn_off(hass, config_entry, netatmo_auth): """Test service turn off for valves.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -654,7 +654,7 @@ async def test_valves_service_turn_off(hass, config_entry, netatmo_auth): async def test_valves_service_turn_on(hass, config_entry, netatmo_auth): """Test service turn on for valves.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -699,7 +699,7 @@ async def test_valves_service_turn_on(hass, config_entry, netatmo_auth): async def test_webhook_home_id_mismatch(hass, config_entry, netatmo_auth): """Test service turn on for valves.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -737,7 +737,7 @@ async def test_webhook_home_id_mismatch(hass, config_entry, netatmo_auth): async def test_webhook_set_point(hass, config_entry, netatmo_auth): """Test service turn on for valves.""" with selected_platforms(["climate"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/netatmo/test_cover.py b/tests/components/netatmo/test_cover.py index c0f34f25b24..6a5709ebf8f 100644 --- a/tests/components/netatmo/test_cover.py +++ b/tests/components/netatmo/test_cover.py @@ -17,7 +17,7 @@ from .common import selected_platforms async def test_cover_setup_and_services(hass, config_entry, netatmo_auth): """Test setup and services.""" with selected_platforms(["cover"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index 373d7e19765..187a89afeb6 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -121,7 +121,7 @@ async def test_setup_component_with_config(hass, config_entry): async def test_setup_component_with_webhook(hass, config_entry, netatmo_auth): """Test setup and teardown of the netatmo component with webhook registration.""" with selected_platforms(["camera", "climate", "light", "sensor"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/netatmo/test_light.py b/tests/components/netatmo/test_light.py index ced24c738e3..b1a5270745c 100644 --- a/tests/components/netatmo/test_light.py +++ b/tests/components/netatmo/test_light.py @@ -17,7 +17,7 @@ from tests.test_util.aiohttp import AiohttpClientMockResponse async def test_camera_light_setup_and_services(hass, config_entry, netatmo_auth): """Test camera ligiht setup and services.""" with selected_platforms(["light"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -108,7 +108,7 @@ async def test_setup_component_no_devices(hass, config_entry): mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() # Fake webhook activation @@ -126,7 +126,7 @@ async def test_setup_component_no_devices(hass, config_entry): async def test_light_setup_and_services(hass, config_entry, netatmo_auth): """Test setup and services.""" with selected_platforms(["light"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/netatmo/test_select.py b/tests/components/netatmo/test_select.py index ea8e88ce8de..8053b8bdac7 100644 --- a/tests/components/netatmo/test_select.py +++ b/tests/components/netatmo/test_select.py @@ -14,7 +14,7 @@ from .common import selected_platforms, simulate_webhook async def test_select_schedule_thermostats(hass, config_entry, caplog, netatmo_auth): """Test service for selecting Netatmo schedule with thermostats.""" with selected_platforms(["climate", "select"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/netatmo/test_sensor.py b/tests/components/netatmo/test_sensor.py index 99e76389c13..d3ea8fb8167 100644 --- a/tests/components/netatmo/test_sensor.py +++ b/tests/components/netatmo/test_sensor.py @@ -12,7 +12,7 @@ from .common import TEST_TIME, selected_platforms async def test_weather_sensor(hass, config_entry, netatmo_auth): """Test weather sensor setup.""" with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -27,7 +27,7 @@ async def test_weather_sensor(hass, config_entry, netatmo_auth): async def test_public_weather_sensor(hass, config_entry, netatmo_auth): """Test public weather sensor setup.""" with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -182,7 +182,7 @@ async def test_weather_sensor_enabling( suggested_object_id=name, disabled_by=None, ) - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -195,7 +195,7 @@ async def test_climate_battery_sensor(hass, config_entry, netatmo_auth): with patch("time.time", return_value=TEST_TIME), selected_platforms( ["sensor", "climate"] ): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/netatmo/test_switch.py b/tests/components/netatmo/test_switch.py index dc11ac22746..c6f9c9c514e 100644 --- a/tests/components/netatmo/test_switch.py +++ b/tests/components/netatmo/test_switch.py @@ -14,7 +14,7 @@ from .common import selected_platforms async def test_switch_setup_and_services(hass, config_entry, netatmo_auth): """Test setup and services.""" with selected_platforms(["switch"]): - await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done()