Skip to content

Commit 399299c

Browse files
deosrccgtobi
authored andcommitted
Fix netatmo authentication when using cloud authentication credentials (home-assistant#104021)
* Fix netatmo authentication loop * Update unit tests * Move logic to determine api scopes * Add unit tests for new method * Use pyatmo scope list (#1) * Exclude scopes not working with cloud * Fix linting error --------- Co-authored-by: Tobias Sauerwein <[email protected]>
1 parent c241c2f commit 399299c

File tree

5 files changed

+54
-22
lines changed

5 files changed

+54
-22
lines changed

homeassistant/components/netatmo/__init__.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import aiohttp
1010
import pyatmo
11-
from pyatmo.const import ALL_SCOPES as NETATMO_SCOPES
1211
import voluptuous as vol
1312

1413
from homeassistant.components import cloud
@@ -143,7 +142,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
143142
try:
144143
await session.async_ensure_token_valid()
145144
except aiohttp.ClientResponseError as ex:
146-
_LOGGER.debug("API error: %s (%s)", ex.status, ex.message)
145+
_LOGGER.warning("API error: %s (%s)", ex.status, ex.message)
147146
if ex.status in (
148147
HTTPStatus.BAD_REQUEST,
149148
HTTPStatus.UNAUTHORIZED,
@@ -152,19 +151,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
152151
raise ConfigEntryAuthFailed("Token not valid, trigger renewal") from ex
153152
raise ConfigEntryNotReady from ex
154153

155-
if entry.data["auth_implementation"] == cloud.DOMAIN:
156-
required_scopes = {
157-
scope
158-
for scope in NETATMO_SCOPES
159-
if scope not in ("access_doorbell", "read_doorbell")
160-
}
161-
else:
162-
required_scopes = set(NETATMO_SCOPES)
163-
164-
if not (set(session.token["scope"]) & required_scopes):
165-
_LOGGER.debug(
154+
required_scopes = api.get_api_scopes(entry.data["auth_implementation"])
155+
if not (set(session.token["scope"]) & set(required_scopes)):
156+
_LOGGER.warning(
166157
"Session is missing scopes: %s",
167-
required_scopes - set(session.token["scope"]),
158+
set(required_scopes) - set(session.token["scope"]),
168159
)
169160
raise ConfigEntryAuthFailed("Token scope not valid, trigger renewal")
170161

homeassistant/components/netatmo/api.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
"""API for Netatmo bound to HASS OAuth."""
2+
from collections.abc import Iterable
23
from typing import cast
34

45
from aiohttp import ClientSession
56
import pyatmo
67

8+
from homeassistant.components import cloud
79
from homeassistant.helpers import config_entry_oauth2_flow
810

11+
from .const import API_SCOPES_EXCLUDED_FROM_CLOUD
12+
13+
14+
def get_api_scopes(auth_implementation: str) -> Iterable[str]:
15+
"""Return the Netatmo API scopes based on the auth implementation."""
16+
17+
if auth_implementation == cloud.DOMAIN:
18+
return set(
19+
{
20+
scope
21+
for scope in pyatmo.const.ALL_SCOPES
22+
if scope not in API_SCOPES_EXCLUDED_FROM_CLOUD
23+
}
24+
)
25+
return sorted(pyatmo.const.ALL_SCOPES)
26+
927

1028
class AsyncConfigEntryNetatmoAuth(pyatmo.AbstractAsyncAuth):
1129
"""Provide Netatmo authentication tied to an OAuth2 based config entry."""

homeassistant/components/netatmo/config_flow.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from typing import Any
77
import uuid
88

9-
from pyatmo.const import ALL_SCOPES
109
import voluptuous as vol
1110

1211
from homeassistant import config_entries
@@ -15,6 +14,7 @@
1514
from homeassistant.data_entry_flow import FlowResult
1615
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
1716

17+
from .api import get_api_scopes
1818
from .const import (
1919
CONF_AREA_NAME,
2020
CONF_LAT_NE,
@@ -53,13 +53,7 @@ def logger(self) -> logging.Logger:
5353
@property
5454
def extra_authorize_data(self) -> dict:
5555
"""Extra data that needs to be appended to the authorize url."""
56-
exclude = []
57-
if self.flow_impl.name == "Home Assistant Cloud":
58-
exclude = ["access_doorbell", "read_doorbell"]
59-
60-
scopes = [scope for scope in ALL_SCOPES if scope not in exclude]
61-
scopes.sort()
62-
56+
scopes = get_api_scopes(self.flow_impl.domain)
6357
return {"scope": " ".join(scopes)}
6458

6559
async def async_step_user(self, user_input: dict | None = None) -> FlowResult:

homeassistant/components/netatmo/const.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@
3030
DATA_HANDLER = "netatmo_data_handler"
3131
SIGNAL_NAME = "signal_name"
3232

33+
API_SCOPES_EXCLUDED_FROM_CLOUD = [
34+
"access_doorbell",
35+
"read_doorbell",
36+
"read_mhs1",
37+
"write_mhs1",
38+
]
39+
3340
NETATMO_CREATE_BATTERY = "netatmo_create_battery"
3441
NETATMO_CREATE_CAMERA = "netatmo_create_camera"
3542
NETATMO_CREATE_CAMERA_LIGHT = "netatmo_create_camera_light"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""The tests for the Netatmo api."""
2+
3+
from pyatmo.const import ALL_SCOPES
4+
5+
from homeassistant.components import cloud
6+
from homeassistant.components.netatmo import api
7+
from homeassistant.components.netatmo.const import API_SCOPES_EXCLUDED_FROM_CLOUD
8+
9+
10+
async def test_get_api_scopes_cloud() -> None:
11+
"""Test method to get API scopes when using cloud auth implementation."""
12+
result = api.get_api_scopes(cloud.DOMAIN)
13+
14+
for scope in API_SCOPES_EXCLUDED_FROM_CLOUD:
15+
assert scope not in result
16+
17+
18+
async def test_get_api_scopes_other() -> None:
19+
"""Test method to get API scopes when using cloud auth implementation."""
20+
result = api.get_api_scopes("netatmo_239846i2f0j2")
21+
22+
assert sorted(ALL_SCOPES) == result

0 commit comments

Comments
 (0)