From da0a65be87d9138132c95efdf7258b7f086041ec Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 23 Jul 2026 17:15:51 -0400 Subject: [PATCH] Fix for ProxmoxVE compoenent failing authentication with realms containg upper case letters. (#176742) --- .../components/proxmoxve/__init__.py | 4 +- homeassistant/components/proxmoxve/common.py | 2 +- tests/components/proxmoxve/conftest.py | 8 +- .../components/proxmoxve/test_config_flow.py | 6 +- tests/components/proxmoxve/test_init.py | 208 ++++++++++++------ 5 files changed, 149 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/proxmoxve/__init__.py b/homeassistant/components/proxmoxve/__init__.py index 2f969fae5bed..666394485c39 100644 --- a/homeassistant/components/proxmoxve/__init__.py +++ b/homeassistant/components/proxmoxve/__init__.py @@ -193,9 +193,9 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ProxmoxConfigEntry) -> if "@" in data.get(CONF_USERNAME, ""): username, realm = data[CONF_USERNAME].split("@", 1) data[CONF_USERNAME] = username - data[CONF_REALM] = realm.lower() + data[CONF_REALM] = realm - realm = data[CONF_REALM].lower() + realm = data[CONF_REALM] # If the realm is one of the base providers, # set the provider to match the realm. diff --git a/homeassistant/components/proxmoxve/common.py b/homeassistant/components/proxmoxve/common.py index 73dee442372a..a32a38734e9d 100644 --- a/homeassistant/components/proxmoxve/common.py +++ b/homeassistant/components/proxmoxve/common.py @@ -16,7 +16,7 @@ def sanitize_config_entry(input_data: Mapping[str, Any]) -> dict[str, Any]: realm = provider.lower() if provider == AUTH_OTHER: - realm = data[CONF_REALM].lower() + realm = data[CONF_REALM] data[CONF_REALM] = realm data[CONF_USERNAME] = f"{username}@{realm}" diff --git a/tests/components/proxmoxve/conftest.py b/tests/components/proxmoxve/conftest.py index 1decc74ac46c..9ac0b312633b 100644 --- a/tests/components/proxmoxve/conftest.py +++ b/tests/components/proxmoxve/conftest.py @@ -64,8 +64,8 @@ MOCK_TEST_TOKEN_CONFIG = { MOCK_TEST_OTHER_CONFIG = { **MOCK_TEST_CONFIG, CONF_AUTH_METHOD: "other", - CONF_REALM: "test_realm", - CONF_USERNAME: "test_user@test_realm", + CONF_REALM: "Test_Realm", + CONF_USERNAME: "test_user@Test_Realm", } MOCK_TEST_TOKEN_OTHER_CONFIG = { @@ -74,8 +74,8 @@ MOCK_TEST_TOKEN_OTHER_CONFIG = { CONF_TOKEN_ID: "test_token_id", CONF_TOKEN_SECRET: "test_token_secret", CONF_AUTH_METHOD: "other", - CONF_REALM: "test_realm", - CONF_USERNAME: "test_user@test_realm", + CONF_REALM: "Test_Realm", + CONF_USERNAME: "test_user@Test_Realm", } diff --git a/tests/components/proxmoxve/test_config_flow.py b/tests/components/proxmoxve/test_config_flow.py index c6814ca95717..aaf4aa0dd5ec 100644 --- a/tests/components/proxmoxve/test_config_flow.py +++ b/tests/components/proxmoxve/test_config_flow.py @@ -75,7 +75,7 @@ MOCK_USER_STEP_OTHER = { MOCK_USER_AUTH_STEP_OTHER = { **MOCK_USER_AUTH_STEP_PASSWORD, - CONF_REALM: "test_realm", + CONF_REALM: "Test_Realm", } # Other authentication method with realm and token @@ -86,7 +86,7 @@ MOCK_USER_STEP_OTHER_TOKEN = { MOCK_USER_AUTH_STEP_OTHER_TOKEN = { **MOCK_USER_AUTH_STEP_TOKEN, - CONF_REALM: "test_realm", + CONF_REALM: "Test_Realm", } MOCK_USER_SETUP = {CONF_NODES: ["pve1"]} @@ -724,7 +724,7 @@ async def test_full_flow_reauth_token_other( result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ - CONF_REALM: "test_realm", + CONF_REALM: "Test_Realm", CONF_TOKEN_ID: "test_token_id", CONF_TOKEN_SECRET: "new_token_secret", }, diff --git a/tests/components/proxmoxve/test_init.py b/tests/components/proxmoxve/test_init.py index 83c6a0a5139e..68ccca4f5916 100644 --- a/tests/components/proxmoxve/test_init.py +++ b/tests/components/proxmoxve/test_init.py @@ -9,6 +9,7 @@ import requests from requests.exceptions import ConnectTimeout, SSLError from homeassistant.components.proxmoxve.const import ( + AUTH_OTHER, AUTH_PAM, CONF_AUTH_METHOD, CONF_CONTAINERS, @@ -165,25 +166,56 @@ async def test_setup_exceptions( assert mock_config_entry.state is expected_state +@pytest.mark.parametrize( + ("mock_config_entry", "expected_auth_method", "expected_realm"), + [ + ( + MockConfigEntry( + domain=DOMAIN, + version=1, + unique_id="1", + data={ + CONF_HOST: "http://test_host", + CONF_PORT: 8006, + CONF_REALM: "pam", + CONF_USERNAME: "test_user@pam", + CONF_PASSWORD: "test_password", + CONF_VERIFY_SSL: True, + }, + ), + AUTH_PAM, + "pam", + ), + ( + MockConfigEntry( + domain=DOMAIN, + version=1, + unique_id="1", + data={ + CONF_HOST: "http://test_host", + CONF_PORT: 8006, + CONF_REALM: "Test_Realm", + CONF_USERNAME: "test_user@Test_Realm", + CONF_PASSWORD: "test_password", + CONF_VERIFY_SSL: True, + }, + ), + AUTH_OTHER, + "Test_Realm", + ), + ], +) async def test_migration_v1_to_v3( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, + mock_config_entry: MockConfigEntry, + expected_auth_method: str, + expected_realm: str, ) -> None: - """Test migration from version 1.""" - entry = MockConfigEntry( - domain=DOMAIN, - version=1, - unique_id="1", - data={ - CONF_HOST: "http://test_host", - CONF_PORT: 8006, - CONF_REALM: "pam", - CONF_USERNAME: "test_user@pam", - CONF_PASSWORD: "test_password", - CONF_VERIFY_SSL: True, - }, - ) + """Test migration from version 1 to 3.""" + entry = mock_config_entry + entry.add_to_hass(hass) assert entry.version == 1 @@ -227,6 +259,8 @@ async def test_migration_v1_to_v3( await hass.async_block_till_done() assert entry.version == 3 + assert entry.data[CONF_AUTH_METHOD] == expected_auth_method + assert entry.data[CONF_REALM] == expected_realm vm_entity_after = entity_registry.async_get(vm_entity.entity_id) container_entity_after = entity_registry.async_get(container_entity.entity_id) @@ -235,6 +269,97 @@ async def test_migration_v1_to_v3( assert container_entity_after.unique_id == f"{entry.entry_id}_200_status" +@pytest.mark.parametrize( + ("mock_config_entry", "expected_auth_method", "expected_realm"), + [ + ( + MockConfigEntry( + domain=DOMAIN, + version=2, + unique_id="1", + data={ + CONF_HOST: "http://test_host", + CONF_PORT: 8006, + CONF_REALM: "pam", + CONF_USERNAME: "test_user@pam", + CONF_PASSWORD: "test_password", + CONF_VERIFY_SSL: True, + }, + ), + AUTH_PAM, + "pam", + ), + ( + MockConfigEntry( + domain=DOMAIN, + version=2, + unique_id="1", + data={ + CONF_HOST: "http://test_host", + CONF_PORT: 8006, + CONF_REALM: "Test_Realm", + CONF_USERNAME: "test_user@Test_Realm", + CONF_PASSWORD: "test_password", + CONF_VERIFY_SSL: True, + }, + ), + AUTH_OTHER, + "Test_Realm", + ), + ( + MockConfigEntry( + domain=DOMAIN, + version=2, + unique_id="1", + data={ + CONF_HOST: "http://test_host", + CONF_PORT: 8006, + CONF_USERNAME: "test_user@pam", + CONF_PASSWORD: "test_password", + CONF_VERIFY_SSL: True, + }, + ), + AUTH_PAM, + "pam", + ), + ( + MockConfigEntry( + domain=DOMAIN, + version=2, + unique_id="1", + data={ + CONF_HOST: "http://test_host", + CONF_PORT: 8006, + CONF_USERNAME: "test_user@Test_Realm", + CONF_PASSWORD: "test_password", + CONF_VERIFY_SSL: True, + }, + ), + AUTH_OTHER, + "Test_Realm", + ), + ], +) +async def test_migration_v2_to_v3( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + expected_auth_method: str, + expected_realm: str, +) -> None: + """Test migration from version 2 to 3.""" + entry = mock_config_entry + + entry.add_to_hass(hass) + assert entry.version == 2 + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.version == 3 + assert entry.data[CONF_AUTH_METHOD] == expected_auth_method + assert entry.data[CONF_REALM] == expected_realm + + async def test_offline_node( hass: HomeAssistant, mock_proxmox_client: MagicMock, @@ -253,61 +378,6 @@ async def test_offline_node( assert state.state == STATE_OFF -async def test_migration_v2_to_v3( - hass: HomeAssistant, -) -> None: - """Test migration from version 2 to 3.""" - entry = MockConfigEntry( - domain=DOMAIN, - version=2, - unique_id="1", - data={ - CONF_HOST: "http://test_host", - CONF_PORT: 8006, - CONF_REALM: "pam", - CONF_USERNAME: "test_user@pam", - CONF_PASSWORD: "test_password", - CONF_VERIFY_SSL: True, - }, - ) - entry.add_to_hass(hass) - assert entry.version == 2 - - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - assert entry.version == 3 - assert entry.data[CONF_AUTH_METHOD] == AUTH_PAM - assert entry.data[CONF_REALM] == AUTH_PAM - - -async def test_migration_v2_to_v3_without_realm( - hass: HomeAssistant, -) -> None: - """Test migration from version 2 to 3.""" - entry = MockConfigEntry( - domain=DOMAIN, - version=2, - unique_id="1", - data={ - CONF_HOST: "http://test_host", - CONF_PORT: 8006, - CONF_USERNAME: "test_user@pam", - CONF_PASSWORD: "test_password", - CONF_VERIFY_SSL: True, - }, - ) - entry.add_to_hass(hass) - assert entry.version == 2 - - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - assert entry.version == 3 - assert entry.data[CONF_AUTH_METHOD] == AUTH_PAM - assert entry.data[CONF_REALM] == AUTH_PAM - - async def test_new_vm_creates_entity( hass: HomeAssistant, mock_proxmox_client: MagicMock,