Set unique id in aladdin connect config flow (#118798)

This commit is contained in:
Joost Lekkerkerker 2024-06-04 16:00:53 +02:00 committed by GitHub
parent 1eb13b48a2
commit 2ac5f8db06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 208 additions and 22 deletions

View File

@ -4,9 +4,10 @@ from collections.abc import Mapping
import logging import logging
from typing import Any from typing import Any
import voluptuous as vol import jwt
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.config_entries import ConfigEntry, ConfigFlowResult
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
from .const import DOMAIN from .const import DOMAIN
@ -35,20 +36,33 @@ class AladdinConnectOAuth2FlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN):
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Dialog that informs the user that reauth is required.""" """Dialog that informs the user that reauth is required."""
if user_input is None: if user_input is None:
return self.async_show_form( return self.async_show_form(step_id="reauth_confirm")
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return await self.async_step_user() return await self.async_step_user()
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
"""Create an oauth config entry or update existing entry for reauth.""" """Create an oauth config entry or update existing entry for reauth."""
if self.reauth_entry: token_payload = jwt.decode(
data[CONF_TOKEN][CONF_ACCESS_TOKEN], options={"verify_signature": False}
)
if not self.reauth_entry:
await self.async_set_unique_id(token_payload["sub"])
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=token_payload["username"],
data=data,
)
if self.reauth_entry.unique_id == token_payload["username"]:
return self.async_update_reload_and_abort( return self.async_update_reload_and_abort(
self.reauth_entry, self.reauth_entry,
data=data, data=data,
unique_id=token_payload["sub"],
) )
return await super().async_oauth_create_entry(data) if self.reauth_entry.unique_id == token_payload["sub"]:
return self.async_update_reload_and_abort(self.reauth_entry, data=data)
return self.async_abort(reason="wrong_account")
@property @property
def logger(self) -> logging.Logger: def logger(self) -> logging.Logger:

View File

@ -0,0 +1,31 @@
"""Test fixtures for the Aladdin Connect Garage Door integration."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.aladdin_connect import DOMAIN
from tests.common import MockConfigEntry
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.aladdin_connect.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return an Aladdin Connect config entry."""
return MockConfigEntry(
domain=DOMAIN,
data={},
title="test@test.com",
unique_id="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
version=2,
)

View File

@ -1,10 +1,9 @@
"""Test the Aladdin Connect Garage Door config flow.""" """Test the Aladdin Connect Garage Door config flow."""
from unittest.mock import patch from unittest.mock import AsyncMock
import pytest import pytest
from homeassistant import config_entries
from homeassistant.components.aladdin_connect.const import ( from homeassistant.components.aladdin_connect.const import (
DOMAIN, DOMAIN,
OAUTH2_AUTHORIZE, OAUTH2_AUTHORIZE,
@ -14,16 +13,25 @@ from homeassistant.components.application_credentials import (
ClientCredential, ClientCredential,
async_import_client_credential, async_import_client_credential,
) )
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER, ConfigFlowResult
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
CLIENT_ID = "1234" CLIENT_ID = "1234"
CLIENT_SECRET = "5678" CLIENT_SECRET = "5678"
EXAMPLE_TOKEN = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYWFhYWFhYS1iYmJiLWNjY2MtZGRk"
"ZC1lZWVlZWVlZWVlZWUiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW"
"1lIjoidGVzdEB0ZXN0LmNvbSJ9.CTU1YItIrUl8nSM3koJxlFJr5CjLghgc9gS6h45D8dE"
)
@pytest.fixture @pytest.fixture
async def setup_credentials(hass: HomeAssistant) -> None: async def setup_credentials(hass: HomeAssistant) -> None:
@ -36,17 +44,13 @@ async def setup_credentials(hass: HomeAssistant) -> None:
) )
@pytest.mark.usefixtures("current_request_with_host", "setup_credentials") async def _oauth_actions(
async def test_full_flow(
hass: HomeAssistant, hass: HomeAssistant,
result: ConfigFlowResult,
hass_client_no_auth: ClientSessionGenerator, hass_client_no_auth: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker, aioclient_mock: AiohttpClientMocker,
) -> None: ) -> None:
"""Check full flow.""" state = config_entry_oauth2_flow._encode_jwt(
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
state = config_entry_oauth2_flow._encode_jwt( # noqa: SLF001
hass, hass,
{ {
"flow_id": result["flow_id"], "flow_id": result["flow_id"],
@ -69,16 +73,153 @@ async def test_full_flow(
OAUTH2_TOKEN, OAUTH2_TOKEN,
json={ json={
"refresh_token": "mock-refresh-token", "refresh_token": "mock-refresh-token",
"access_token": "mock-access-token", "access_token": EXAMPLE_TOKEN,
"type": "Bearer", "type": "Bearer",
"expires_in": 60, "expires_in": 60,
}, },
) )
with patch(
"homeassistant.components.aladdin_connect.async_setup_entry", return_value=True async def test_full_flow(
) as mock_setup: hass: HomeAssistant,
await hass.config_entries.flow.async_configure(result["flow_id"]) hass_client_no_auth: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
current_request_with_host: None,
setup_credentials: None,
mock_setup_entry: AsyncMock,
) -> None:
"""Check full flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test@test.com"
assert result["data"]["token"]["access_token"] == EXAMPLE_TOKEN
assert result["data"]["token"]["refresh_token"] == "mock-refresh-token"
assert result["result"].unique_id == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_duplicate_entry(
hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
current_request_with_host: None,
setup_credentials: None,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test we abort with duplicate entry."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_reauth(
hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
current_request_with_host: None,
setup_credentials: None,
mock_config_entry: MockConfigEntry,
mock_setup_entry: AsyncMock,
) -> None:
"""Test reauthentication."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"entry_id": mock_config_entry.entry_id,
},
data=mock_config_entry.data,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
async def test_reauth_wrong_account(
hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
current_request_with_host: None,
setup_credentials: None,
mock_setup_entry: AsyncMock,
) -> None:
"""Test reauthentication with wrong account."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={},
title="test@test.com",
unique_id="aaaaaaaa-bbbb-ffff-dddd-eeeeeeeeeeee",
version=2,
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"entry_id": config_entry.entry_id,
},
data=config_entry.data,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "wrong_account"
async def test_reauth_old_account(
hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
current_request_with_host: None,
setup_credentials: None,
mock_setup_entry: AsyncMock,
) -> None:
"""Test reauthentication with old account."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={},
title="test@test.com",
unique_id="test@test.com",
version=2,
)
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"entry_id": config_entry.entry_id,
},
data=config_entry.data,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert config_entry.unique_id == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"