Set unique_id in myuplink config entry (#132672)
parent
74eddce3d3
commit
8d72443fd6
|
@ -3,8 +3,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
import jwt
|
||||
from myuplink import MyUplinkAPI, get_manufacturer, get_model, get_system_name
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -22,6 +24,8 @@ from .api import AsyncConfigEntryAuth
|
|||
from .const import DOMAIN, OAUTH2_SCOPES
|
||||
from .coordinator import MyUplinkDataCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.NUMBER,
|
||||
|
@ -109,3 +113,27 @@ async def async_remove_config_entry_device(
|
|||
return not device_entry.identifiers.intersection(
|
||||
(DOMAIN, device_id) for device_id in myuplink_data.data.devices
|
||||
)
|
||||
|
||||
|
||||
async def async_migrate_entry(
|
||||
hass: HomeAssistant, config_entry: MyUplinkConfigEntry
|
||||
) -> bool:
|
||||
"""Migrate old entry."""
|
||||
|
||||
# Use sub(ject) from access_token as unique_id
|
||||
if config_entry.version == 1 and config_entry.minor_version == 1:
|
||||
token = jwt.decode(
|
||||
config_entry.data["token"]["access_token"],
|
||||
options={"verify_signature": False},
|
||||
)
|
||||
uid = token["sub"]
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry, unique_id=uid, minor_version=2
|
||||
)
|
||||
_LOGGER.info(
|
||||
"Migration to version %s.%s successful",
|
||||
config_entry.version,
|
||||
config_entry.minor_version,
|
||||
)
|
||||
|
||||
return True
|
||||
|
|
|
@ -4,6 +4,8 @@ from collections.abc import Mapping
|
|||
import logging
|
||||
from typing import Any
|
||||
|
||||
import jwt
|
||||
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
|
||||
|
@ -15,6 +17,8 @@ class OAuth2FlowHandler(
|
|||
):
|
||||
"""Config flow to handle myUplink OAuth2 authentication."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
DOMAIN = DOMAIN
|
||||
|
||||
@property
|
||||
|
@ -46,8 +50,17 @@ class OAuth2FlowHandler(
|
|||
|
||||
async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult:
|
||||
"""Create or update the config entry."""
|
||||
|
||||
token = jwt.decode(
|
||||
data["token"]["access_token"], options={"verify_signature": False}
|
||||
)
|
||||
uid = token["sub"]
|
||||
await self.async_set_unique_id(uid)
|
||||
|
||||
if self.source == SOURCE_REAUTH:
|
||||
self._abort_if_unique_id_mismatch(reason="account_mismatch")
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(), data=data
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
return await super().async_oauth_create_entry(data)
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"account_mismatch": "The used account does not match the original account",
|
||||
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]"
|
||||
},
|
||||
"create_entry": {
|
||||
|
|
|
@ -15,10 +15,11 @@ from homeassistant.components.application_credentials import (
|
|||
)
|
||||
from homeassistant.components.myuplink.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from .const import CLIENT_ID, CLIENT_SECRET
|
||||
from .const import CLIENT_ID, CLIENT_SECRET, UNIQUE_ID
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
|
@ -33,7 +34,7 @@ def mock_expires_at() -> float:
|
|||
def mock_config_entry(hass: HomeAssistant, expires_at: float) -> MockConfigEntry:
|
||||
"""Return the default mocked config entry."""
|
||||
config_entry = MockConfigEntry(
|
||||
version=1,
|
||||
minor_version=2,
|
||||
domain=DOMAIN,
|
||||
title="myUplink test",
|
||||
data={
|
||||
|
@ -48,6 +49,7 @@ def mock_config_entry(hass: HomeAssistant, expires_at: float) -> MockConfigEntry
|
|||
},
|
||||
},
|
||||
entry_id="myuplink_test",
|
||||
unique_id=UNIQUE_ID,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
return config_entry
|
||||
|
@ -189,3 +191,21 @@ async def setup_platform(
|
|||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def access_token(hass: HomeAssistant) -> str:
|
||||
"""Return a valid access token."""
|
||||
return config_entry_oauth2_flow._encode_jwt(
|
||||
hass,
|
||||
{
|
||||
"sub": UNIQUE_ID,
|
||||
"aud": [],
|
||||
"scp": [
|
||||
"WRITESYSTEM",
|
||||
"READSYSTEM",
|
||||
"offline_access",
|
||||
],
|
||||
"ou_code": "NA",
|
||||
},
|
||||
)
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
|
||||
CLIENT_ID = "12345"
|
||||
CLIENT_SECRET = "67890"
|
||||
UNIQUE_ID = "uid"
|
||||
|
|
|
@ -29,6 +29,7 @@ async def test_full_flow(
|
|||
hass: HomeAssistant,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
access_token: str,
|
||||
setup_credentials,
|
||||
) -> None:
|
||||
"""Check full flow."""
|
||||
|
@ -59,7 +60,7 @@ async def test_full_flow(
|
|||
OAUTH2_TOKEN,
|
||||
json={
|
||||
"refresh_token": "mock-refresh-token",
|
||||
"access_token": "mock-access-token",
|
||||
"access_token": access_token,
|
||||
"type": "Bearer",
|
||||
"expires_in": 60,
|
||||
},
|
||||
|
@ -81,6 +82,7 @@ async def test_flow_reauth(
|
|||
aioclient_mock: AiohttpClientMocker,
|
||||
setup_credentials: None,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
access_token: str,
|
||||
expires_at: float,
|
||||
) -> None:
|
||||
"""Test reauth step."""
|
||||
|
@ -89,7 +91,7 @@ async def test_flow_reauth(
|
|||
OLD_SCOPE_TOKEN = {
|
||||
"auth_implementation": DOMAIN,
|
||||
"token": {
|
||||
"access_token": "Fake_token",
|
||||
"access_token": access_token,
|
||||
"scope": OLD_SCOPE,
|
||||
"expires_in": 86399,
|
||||
"refresh_token": "3012bc9f-7a65-4240-b817-9154ffdcc30f",
|
||||
|
@ -137,7 +139,7 @@ async def test_flow_reauth(
|
|||
OAUTH2_TOKEN,
|
||||
json={
|
||||
"refresh_token": "updated-refresh-token",
|
||||
"access_token": "updated-access-token",
|
||||
"access_token": access_token,
|
||||
"type": "Bearer",
|
||||
"expires_in": "60",
|
||||
"scope": CURRENT_SCOPE,
|
||||
|
|
|
@ -12,6 +12,7 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from . import setup_integration
|
||||
from .const import UNIQUE_ID
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
@ -92,7 +93,40 @@ async def test_devices_multiple_created_count(
|
|||
mock_myuplink_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that multiple device are created."""
|
||||
"""Test that multiple devices are created."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert len(device_registry.devices) == 2
|
||||
|
||||
|
||||
async def test_migrate_config_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_myuplink_client: MagicMock,
|
||||
expires_at: float,
|
||||
access_token: str,
|
||||
) -> None:
|
||||
"""Test migration of config entry."""
|
||||
mock_entry_v1_1 = MockConfigEntry(
|
||||
version=1,
|
||||
minor_version=1,
|
||||
domain=DOMAIN,
|
||||
title="myUplink test",
|
||||
data={
|
||||
"auth_implementation": DOMAIN,
|
||||
"token": {
|
||||
"access_token": access_token,
|
||||
"scope": "WRITESYSTEM READSYSTEM offline_access",
|
||||
"expires_in": 86399,
|
||||
"refresh_token": "3012bc9f-7a65-4240-b817-9154ffdcc30f",
|
||||
"token_type": "Bearer",
|
||||
"expires_at": expires_at,
|
||||
},
|
||||
},
|
||||
entry_id="myuplink_test",
|
||||
)
|
||||
|
||||
await setup_integration(hass, mock_entry_v1_1)
|
||||
assert mock_entry_v1_1.version == 1
|
||||
assert mock_entry_v1_1.minor_version == 2
|
||||
assert mock_entry_v1_1.unique_id == UNIQUE_ID
|
||||
|
|
Loading…
Reference in New Issue