Add support for KNX IP-Secure routing (#82765)
* always use instance variable for new entry data - change `self._tunneling_config` to non-optional `self.new_entry_data` - always use self.new_entry_data in `finish_flow()` * support secure routing * amend current tests * use sync latency tolerance * test secure routing config flow * diagnostics redact backbone_key * test xknx library setup * check length of backbone_key * better readable key validation
This commit is contained in:
parent
d6e287f47a
commit
4517af509c
9 changed files with 422 additions and 88 deletions
|
@ -26,6 +26,9 @@ from homeassistant.components.knx.const import (
|
|||
CONF_KNX_RATE_LIMIT,
|
||||
CONF_KNX_ROUTE_BACK,
|
||||
CONF_KNX_ROUTING,
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY,
|
||||
CONF_KNX_ROUTING_SECURE,
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE,
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION,
|
||||
CONF_KNX_SECURE_USER_ID,
|
||||
CONF_KNX_SECURE_USER_PASSWORD,
|
||||
|
@ -197,6 +200,162 @@ async def test_routing_setup_advanced(hass: HomeAssistant) -> None:
|
|||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_routing_secure_manual_setup(hass: HomeAssistant) -> None:
|
||||
"""Test routing secure setup with manual key config."""
|
||||
with patch("xknx.io.gateway_scanner.GatewayScanner.scan") as gateways:
|
||||
gateways.return_value = []
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert not result["errors"]
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["step_id"] == "routing"
|
||||
assert result2["errors"] == {"base": "no_router_discovered"}
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP,
|
||||
CONF_KNX_MCAST_PORT: 3671,
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS: "0.0.123",
|
||||
CONF_KNX_ROUTING_SECURE: True,
|
||||
},
|
||||
)
|
||||
assert result3["type"] == FlowResultType.MENU
|
||||
assert result3["step_id"] == "secure_key_source"
|
||||
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
{"next_step_id": "secure_routing_manual"},
|
||||
)
|
||||
assert result4["type"] == FlowResultType.FORM
|
||||
assert result4["step_id"] == "secure_routing_manual"
|
||||
assert not result4["errors"]
|
||||
|
||||
result_invalid_key1 = await hass.config_entries.flow.async_configure(
|
||||
result4["flow_id"],
|
||||
{
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY: "xxaacc44bbaacc44bbaacc44bbaaccyy", # invalid hex string
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE: 2000,
|
||||
},
|
||||
)
|
||||
assert result_invalid_key1["type"] == FlowResultType.FORM
|
||||
assert result_invalid_key1["step_id"] == "secure_routing_manual"
|
||||
assert result_invalid_key1["errors"] == {"backbone_key": "invalid_backbone_key"}
|
||||
|
||||
result_invalid_key2 = await hass.config_entries.flow.async_configure(
|
||||
result4["flow_id"],
|
||||
{
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY: "bbaacc44bbaacc44", # invalid length
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE: 2000,
|
||||
},
|
||||
)
|
||||
assert result_invalid_key2["type"] == FlowResultType.FORM
|
||||
assert result_invalid_key2["step_id"] == "secure_routing_manual"
|
||||
assert result_invalid_key2["errors"] == {"backbone_key": "invalid_backbone_key"}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.knx.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
secure_routing_manual = await hass.config_entries.flow.async_configure(
|
||||
result_invalid_key2["flow_id"],
|
||||
{
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY: "bbaacc44bbaacc44bbaacc44bbaacc44",
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE: 2000,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert secure_routing_manual["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert secure_routing_manual["title"] == "Secure Routing as 0.0.123"
|
||||
assert secure_routing_manual["data"] == {
|
||||
**DEFAULT_ENTRY_DATA,
|
||||
CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING_SECURE,
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY: "bbaacc44bbaacc44bbaacc44bbaacc44",
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE: 2000,
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS: "0.0.123",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_routing_secure_keyfile(hass: HomeAssistant) -> None:
|
||||
"""Test routing secure setup with keyfile."""
|
||||
with patch("xknx.io.gateway_scanner.GatewayScanner.scan") as gateways:
|
||||
gateways.return_value = []
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert not result["errors"]
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["step_id"] == "routing"
|
||||
assert result2["errors"] == {"base": "no_router_discovered"}
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP,
|
||||
CONF_KNX_MCAST_PORT: 3671,
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS: "0.0.123",
|
||||
CONF_KNX_ROUTING_SECURE: True,
|
||||
},
|
||||
)
|
||||
assert result3["type"] == FlowResultType.MENU
|
||||
assert result3["step_id"] == "secure_key_source"
|
||||
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
{"next_step_id": "secure_knxkeys"},
|
||||
)
|
||||
assert result4["type"] == FlowResultType.FORM
|
||||
assert result4["step_id"] == "secure_knxkeys"
|
||||
assert not result4["errors"]
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.knx.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry, patch(
|
||||
"homeassistant.components.knx.config_flow.load_keyring", return_value=True
|
||||
):
|
||||
routing_secure_knxkeys = await hass.config_entries.flow.async_configure(
|
||||
result4["flow_id"],
|
||||
{
|
||||
CONF_KNX_KNXKEY_FILENAME: "testcase.knxkeys",
|
||||
CONF_KNX_KNXKEY_PASSWORD: "password",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert routing_secure_knxkeys["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert routing_secure_knxkeys["title"] == "Secure Routing as 0.0.123"
|
||||
assert routing_secure_knxkeys["data"] == {
|
||||
**DEFAULT_ENTRY_DATA,
|
||||
CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING_SECURE,
|
||||
CONF_KNX_KNXKEY_FILENAME: "knx/testcase.knxkeys",
|
||||
CONF_KNX_KNXKEY_PASSWORD: "password",
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY: None,
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE: None,
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS: "0.0.123",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"user_input,config_entry_data",
|
||||
[
|
||||
|
@ -506,7 +665,7 @@ async def test_form_with_automatic_connection_handling(hass: HomeAssistant) -> N
|
|||
|
||||
|
||||
async def _get_menu_step(hass: HomeAssistant) -> FlowResult:
|
||||
"""Return flow in secure_tunnellinn menu step."""
|
||||
"""Return flow in secure_tunnelling menu step."""
|
||||
gateway = _gateway_descriptor(
|
||||
"192.168.0.1",
|
||||
3675,
|
||||
|
@ -538,7 +697,7 @@ async def _get_menu_step(hass: HomeAssistant) -> FlowResult:
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result3["type"] == FlowResultType.MENU
|
||||
assert result3["step_id"] == "secure_tunneling"
|
||||
assert result3["step_id"] == "secure_key_source"
|
||||
return result3
|
||||
|
||||
|
||||
|
@ -588,7 +747,7 @@ async def test_get_secure_menu_step_manual_tunnelling(
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result3["type"] == FlowResultType.MENU
|
||||
assert result3["step_id"] == "secure_tunneling"
|
||||
assert result3["step_id"] == "secure_key_source"
|
||||
|
||||
|
||||
async def test_configure_secure_tunnel_manual(hass: HomeAssistant):
|
||||
|
@ -665,6 +824,8 @@ async def test_configure_secure_knxkeys(hass: HomeAssistant):
|
|||
CONF_KNX_CONNECTION_TYPE: CONF_KNX_TUNNELING_TCP_SECURE,
|
||||
CONF_KNX_KNXKEY_FILENAME: "knx/testcase.knxkeys",
|
||||
CONF_KNX_KNXKEY_PASSWORD: "password",
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY: None,
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE: None,
|
||||
CONF_HOST: "192.168.0.1",
|
||||
CONF_PORT: 3675,
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS: "0.0.240",
|
||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.components.knx.const import (
|
|||
CONF_KNX_MCAST_GRP,
|
||||
CONF_KNX_MCAST_PORT,
|
||||
CONF_KNX_RATE_LIMIT,
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY,
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION,
|
||||
CONF_KNX_SECURE_USER_PASSWORD,
|
||||
CONF_KNX_STATE_UPDATER,
|
||||
|
@ -107,6 +108,7 @@ async def test_diagnostic_redact(
|
|||
CONF_KNX_KNXKEY_PASSWORD: "password",
|
||||
CONF_KNX_SECURE_USER_PASSWORD: "user_password",
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION: "device_authentication",
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY: "bbaacc44bbaacc44bbaacc44bbaacc44",
|
||||
},
|
||||
)
|
||||
knx: KNXTestKit = KNXTestKit(hass, mock_config_entry)
|
||||
|
@ -128,6 +130,7 @@ async def test_diagnostic_redact(
|
|||
"knxkeys_password": "**REDACTED**",
|
||||
"user_password": "**REDACTED**",
|
||||
"device_authentication": "**REDACTED**",
|
||||
"backbone_key": "**REDACTED**",
|
||||
},
|
||||
"configuration_error": None,
|
||||
"configuration_yaml": None,
|
||||
|
|
|
@ -23,6 +23,9 @@ from homeassistant.components.knx.const import (
|
|||
CONF_KNX_RATE_LIMIT,
|
||||
CONF_KNX_ROUTE_BACK,
|
||||
CONF_KNX_ROUTING,
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY,
|
||||
CONF_KNX_ROUTING_SECURE,
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE,
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION,
|
||||
CONF_KNX_SECURE_USER_ID,
|
||||
CONF_KNX_SECURE_USER_PASSWORD,
|
||||
|
@ -167,6 +170,31 @@ from tests.common import MockConfigEntry
|
|||
threaded=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING_SECURE,
|
||||
CONF_KNX_LOCAL_IP: "192.168.1.1",
|
||||
CONF_KNX_RATE_LIMIT: CONF_KNX_DEFAULT_RATE_LIMIT,
|
||||
CONF_KNX_STATE_UPDATER: CONF_KNX_DEFAULT_STATE_UPDATER,
|
||||
CONF_KNX_MCAST_PORT: DEFAULT_MCAST_PORT,
|
||||
CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP,
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS: DEFAULT_ROUTING_IA,
|
||||
CONF_KNX_ROUTING_BACKBONE_KEY: "bbaacc44bbaacc44bbaacc44bbaacc44",
|
||||
CONF_KNX_ROUTING_SYNC_LATENCY_TOLERANCE: 2000,
|
||||
},
|
||||
ConnectionConfig(
|
||||
connection_type=ConnectionType.ROUTING_SECURE,
|
||||
individual_address=DEFAULT_ROUTING_IA,
|
||||
multicast_group=DEFAULT_MCAST_GRP,
|
||||
multicast_port=DEFAULT_MCAST_PORT,
|
||||
secure_config=SecureConfig(
|
||||
backbone_key="bbaacc44bbaacc44bbaacc44bbaacc44",
|
||||
latency_ms=2000,
|
||||
),
|
||||
local_ip="192.168.1.1",
|
||||
threaded=True,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_init_connection_handling(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue