Fix Netatmo scope issue with HA cloud (#79437)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
pull/79448/head^2
Tobias Sauerwein 2022-10-02 03:11:54 +02:00 committed by GitHub
parent ebc2a751d2
commit 3e411935bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 56 additions and 39 deletions

View File

@ -137,9 +137,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryAuthFailed("Token not valid, trigger renewal") from ex raise ConfigEntryAuthFailed("Token not valid, trigger renewal") from ex
raise ConfigEntryNotReady 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( _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") raise ConfigEntryAuthFailed("Token scope not valid, trigger renewal")

View File

@ -54,7 +54,14 @@ class NetatmoFlowHandler(
@property @property
def extra_authorize_data(self) -> dict: def extra_authorize_data(self) -> dict:
"""Extra data that needs to be appended to the authorize url.""" """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: async def async_step_user(self, user_input: dict | None = None) -> FlowResult:
"""Handle a flow start.""" """Handle a flow start."""

View File

@ -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): async def test_setup_component_with_webhook(hass, config_entry, netatmo_auth):
"""Test setup with webhook.""" """Test setup with webhook."""
with selected_platforms(["camera"]): 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() 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): async def test_camera_image_local(hass, config_entry, requests_mock, netatmo_auth):
"""Test retrieval or local camera image.""" """Test retrieval or local camera image."""
with selected_platforms(["camera"]): 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() 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): async def test_camera_image_vpn(hass, config_entry, requests_mock, netatmo_auth):
"""Test retrieval of remote camera image.""" """Test retrieval of remote camera image."""
with selected_platforms(["camera"]): 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() 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): async def test_service_set_person_away(hass, config_entry, netatmo_auth):
"""Test service to set person as away.""" """Test service to set person as away."""
with selected_platforms(["camera"]): 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() 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): async def test_service_set_person_away_invalid_person(hass, config_entry, netatmo_auth):
"""Test service to set invalid person as away.""" """Test service to set invalid person as away."""
with selected_platforms(["camera"]): 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() 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.""" """Test service to set invalid persons as home."""
with selected_platforms(["camera"]): 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() 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): async def test_service_set_persons_home(hass, config_entry, netatmo_auth):
"""Test service to set persons as home.""" """Test service to set persons as home."""
with selected_platforms(["camera"]): 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() 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): async def test_service_set_camera_light(hass, config_entry, netatmo_auth):
"""Test service to set the outdoor camera light mode.""" """Test service to set the outdoor camera light mode."""
with selected_platforms(["camera"]): 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() 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): async def test_service_set_camera_light_invalid_type(hass, config_entry, netatmo_auth):
"""Test service to set the indoor camera light mode.""" """Test service to set the indoor camera light mode."""
with selected_platforms(["camera"]): 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() 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_addwebhook.side_effect = AsyncMock()
mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock()
mock_webhook.return_value = "https://example.com" 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() 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): async def test_webhook_person_event(hass, config_entry, netatmo_auth):
"""Test that person events are handled.""" """Test that person events are handled."""
with selected_platforms(["camera"]): 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() 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_addwebhook.side_effect = AsyncMock()
mock_auth.return_value.async_dropwebhook.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() await hass.async_block_till_done()
assert fake_post_hits == 9 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_addwebhook.side_effect = AsyncMock()
mock_auth.return_value.async_dropwebhook.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() await hass.async_block_till_done()
camera_entity_indoor = "camera.hall" camera_entity_indoor = "camera.hall"

View File

@ -27,7 +27,7 @@ from .common import selected_platforms, simulate_webhook
async def test_webhook_event_handling_thermostats(hass, config_entry, netatmo_auth): async def test_webhook_event_handling_thermostats(hass, config_entry, netatmo_auth):
"""Test service and webhook event handling with thermostats.""" """Test service and webhook event handling with thermostats."""
with selected_platforms(["climate"]): 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() 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.""" """Test service with frost guard preset for thermostats."""
with selected_platforms(["climate"]): 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() 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): async def test_service_preset_modes_thermostat(hass, config_entry, netatmo_auth):
"""Test service with preset modes for thermostats.""" """Test service with preset modes for thermostats."""
with selected_platforms(["climate"]): 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() 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): async def test_webhook_event_handling_no_data(hass, config_entry, netatmo_auth):
"""Test service and webhook event handling with erroneous data.""" """Test service and webhook event handling with erroneous data."""
with selected_platforms(["climate"]): 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() 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): async def test_service_schedule_thermostats(hass, config_entry, caplog, netatmo_auth):
"""Test service for selecting Netatmo schedule with thermostats.""" """Test service for selecting Netatmo schedule with thermostats."""
with selected_platforms(["climate"]): 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() 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.""" """Test service with boost preset for valves when already in boost mode."""
with selected_platforms(["climate"]): 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() 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): async def test_service_preset_mode_boost_valves(hass, config_entry, netatmo_auth):
"""Test service with boost preset for valves.""" """Test service with boost preset for valves."""
with selected_platforms(["climate"]): 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() 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): async def test_service_preset_mode_invalid(hass, config_entry, caplog, netatmo_auth):
"""Test service with invalid preset.""" """Test service with invalid preset."""
with selected_platforms(["climate"]): 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() 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): async def test_valves_service_turn_off(hass, config_entry, netatmo_auth):
"""Test service turn off for valves.""" """Test service turn off for valves."""
with selected_platforms(["climate"]): 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() 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): async def test_valves_service_turn_on(hass, config_entry, netatmo_auth):
"""Test service turn on for valves.""" """Test service turn on for valves."""
with selected_platforms(["climate"]): 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() 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): async def test_webhook_home_id_mismatch(hass, config_entry, netatmo_auth):
"""Test service turn on for valves.""" """Test service turn on for valves."""
with selected_platforms(["climate"]): 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() 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): async def test_webhook_set_point(hass, config_entry, netatmo_auth):
"""Test service turn on for valves.""" """Test service turn on for valves."""
with selected_platforms(["climate"]): 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() await hass.async_block_till_done()

View File

@ -17,7 +17,7 @@ from .common import selected_platforms
async def test_cover_setup_and_services(hass, config_entry, netatmo_auth): async def test_cover_setup_and_services(hass, config_entry, netatmo_auth):
"""Test setup and services.""" """Test setup and services."""
with selected_platforms(["cover"]): 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() await hass.async_block_till_done()

View File

@ -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): async def test_setup_component_with_webhook(hass, config_entry, netatmo_auth):
"""Test setup and teardown of the netatmo component with webhook registration.""" """Test setup and teardown of the netatmo component with webhook registration."""
with selected_platforms(["camera", "climate", "light", "sensor"]): 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() await hass.async_block_till_done()

View File

@ -17,7 +17,7 @@ from tests.test_util.aiohttp import AiohttpClientMockResponse
async def test_camera_light_setup_and_services(hass, config_entry, netatmo_auth): async def test_camera_light_setup_and_services(hass, config_entry, netatmo_auth):
"""Test camera ligiht setup and services.""" """Test camera ligiht setup and services."""
with selected_platforms(["light"]): 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() 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_addwebhook.side_effect = AsyncMock()
mock_auth.return_value.async_dropwebhook.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() await hass.async_block_till_done()
# Fake webhook activation # 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): async def test_light_setup_and_services(hass, config_entry, netatmo_auth):
"""Test setup and services.""" """Test setup and services."""
with selected_platforms(["light"]): 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() await hass.async_block_till_done()

View File

@ -14,7 +14,7 @@ from .common import selected_platforms, simulate_webhook
async def test_select_schedule_thermostats(hass, config_entry, caplog, netatmo_auth): async def test_select_schedule_thermostats(hass, config_entry, caplog, netatmo_auth):
"""Test service for selecting Netatmo schedule with thermostats.""" """Test service for selecting Netatmo schedule with thermostats."""
with selected_platforms(["climate", "select"]): 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() await hass.async_block_till_done()

View File

@ -12,7 +12,7 @@ from .common import TEST_TIME, selected_platforms
async def test_weather_sensor(hass, config_entry, netatmo_auth): async def test_weather_sensor(hass, config_entry, netatmo_auth):
"""Test weather sensor setup.""" """Test weather sensor setup."""
with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): 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() 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): async def test_public_weather_sensor(hass, config_entry, netatmo_auth):
"""Test public weather sensor setup.""" """Test public weather sensor setup."""
with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): 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() await hass.async_block_till_done()
@ -182,7 +182,7 @@ async def test_weather_sensor_enabling(
suggested_object_id=name, suggested_object_id=name,
disabled_by=None, 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() 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( with patch("time.time", return_value=TEST_TIME), selected_platforms(
["sensor", "climate"] ["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() await hass.async_block_till_done()

View File

@ -14,7 +14,7 @@ from .common import selected_platforms
async def test_switch_setup_and_services(hass, config_entry, netatmo_auth): async def test_switch_setup_and_services(hass, config_entry, netatmo_auth):
"""Test setup and services.""" """Test setup and services."""
with selected_platforms(["switch"]): 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() await hass.async_block_till_done()