From bae008b0e2d70de15b9417a8a66e163100249957 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 19 Jun 2024 22:46:30 +0200 Subject: [PATCH] Remove legacy_api_password auth provider (#119976) --- .../auth/providers/legacy_api_password.py | 123 ------------------ homeassistant/components/auth/strings.json | 6 - pylint/plugins/hass_enforce_type_hints.py | 1 - .../providers/test_legacy_api_password.py | 90 ------------- tests/components/api/test_init.py | 19 --- tests/components/http/test_auth.py | 20 +-- tests/components/http/test_init.py | 6 +- tests/components/websocket_api/test_auth.py | 8 +- tests/components/websocket_api/test_sensor.py | 6 +- tests/conftest.py | 16 +-- tests/test_config.py | 4 +- 11 files changed, 14 insertions(+), 285 deletions(-) delete mode 100644 homeassistant/auth/providers/legacy_api_password.py delete mode 100644 tests/auth/providers/test_legacy_api_password.py diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py deleted file mode 100644 index f04490a354ef66..00000000000000 --- a/homeassistant/auth/providers/legacy_api_password.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Support Legacy API password auth provider. - -It will be removed when auth system production ready -""" - -from __future__ import annotations - -from collections.abc import Mapping -import hmac -from typing import Any, cast - -import voluptuous as vol - -from homeassistant.core import async_get_hass, callback -from homeassistant.exceptions import HomeAssistantError -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue - -from ..models import AuthFlowResult, Credentials, UserMeta -from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow - -AUTH_PROVIDER_TYPE = "legacy_api_password" -CONF_API_PASSWORD = "api_password" - -_CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( - {vol.Required(CONF_API_PASSWORD): cv.string}, extra=vol.PREVENT_EXTRA -) - - -def _create_repair_and_validate(config: dict[str, Any]) -> dict[str, Any]: - async_create_issue( - async_get_hass(), - "auth", - "deprecated_legacy_api_password", - breaks_in_ha_version="2024.6.0", - is_fixable=False, - severity=IssueSeverity.WARNING, - translation_key="deprecated_legacy_api_password", - ) - - return _CONFIG_SCHEMA(config) # type: ignore[no-any-return] - - -CONFIG_SCHEMA = _create_repair_and_validate - - -LEGACY_USER_NAME = "Legacy API password user" - - -class InvalidAuthError(HomeAssistantError): - """Raised when submitting invalid authentication.""" - - -@AUTH_PROVIDERS.register(AUTH_PROVIDER_TYPE) -class LegacyApiPasswordAuthProvider(AuthProvider): - """An auth provider support legacy api_password.""" - - DEFAULT_TITLE = "Legacy API Password" - - @property - def api_password(self) -> str: - """Return api_password.""" - return str(self.config[CONF_API_PASSWORD]) - - async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow: - """Return a flow to login.""" - return LegacyLoginFlow(self) - - @callback - def async_validate_login(self, password: str) -> None: - """Validate password.""" - api_password = str(self.config[CONF_API_PASSWORD]) - - if not hmac.compare_digest( - api_password.encode("utf-8"), password.encode("utf-8") - ): - raise InvalidAuthError - - async def async_get_or_create_credentials( - self, flow_result: Mapping[str, str] - ) -> Credentials: - """Return credentials for this login.""" - credentials = await self.async_credentials() - if credentials: - return credentials[0] - - return self.async_create_credentials({}) - - async def async_user_meta_for_credentials( - self, credentials: Credentials - ) -> UserMeta: - """Return info for the user. - - Will be used to populate info when creating a new user. - """ - return UserMeta(name=LEGACY_USER_NAME, is_active=True) - - -class LegacyLoginFlow(LoginFlow): - """Handler for the login flow.""" - - async def async_step_init( - self, user_input: dict[str, str] | None = None - ) -> AuthFlowResult: - """Handle the step of the form.""" - errors = {} - - if user_input is not None: - try: - cast( - LegacyApiPasswordAuthProvider, self._auth_provider - ).async_validate_login(user_input["password"]) - except InvalidAuthError: - errors["base"] = "invalid_auth" - - if not errors: - return await self.async_finish({}) - - return self.async_show_form( - step_id="init", - data_schema=vol.Schema({vol.Required("password"): str}), - errors=errors, - ) diff --git a/homeassistant/components/auth/strings.json b/homeassistant/components/auth/strings.json index 0dd3ee64cdf70d..d386bb7a48889f 100644 --- a/homeassistant/components/auth/strings.json +++ b/homeassistant/components/auth/strings.json @@ -31,11 +31,5 @@ "invalid_code": "Invalid code, please try again." } } - }, - "issues": { - "deprecated_legacy_api_password": { - "title": "The legacy API password is deprecated", - "description": "The legacy API password authentication provider is deprecated and will be removed. Please remove it from your YAML configuration and use the default Home Assistant authentication provider instead." - } } } diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index feda93fc7faef8..6dd19d96d01729 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -132,7 +132,6 @@ class ClassTypeHintMatch: "hass_ws_client": "WebSocketGenerator", "init_tts_cache_dir_side_effect": "Any", "issue_registry": "IssueRegistry", - "legacy_auth": "LegacyApiPasswordAuthProvider", "local_auth": "HassAuthProvider", "mock_async_zeroconf": "MagicMock", "mock_bleak_scanner_start": "MagicMock", diff --git a/tests/auth/providers/test_legacy_api_password.py b/tests/auth/providers/test_legacy_api_password.py deleted file mode 100644 index a9ef03fd27bd3a..00000000000000 --- a/tests/auth/providers/test_legacy_api_password.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Tests for the legacy_api_password auth provider.""" - -import pytest - -from homeassistant import auth, data_entry_flow -from homeassistant.auth import auth_store -from homeassistant.auth.providers import legacy_api_password -from homeassistant.core import HomeAssistant -import homeassistant.helpers.issue_registry as ir -from homeassistant.setup import async_setup_component - -from tests.common import ensure_auth_manager_loaded - -CONFIG = {"type": "legacy_api_password", "api_password": "test-password"} - - -@pytest.fixture -async def store(hass): - """Mock store.""" - store = auth_store.AuthStore(hass) - await store.async_load() - return store - - -@pytest.fixture -def provider(hass, store): - """Mock provider.""" - return legacy_api_password.LegacyApiPasswordAuthProvider(hass, store, CONFIG) - - -@pytest.fixture -def manager(hass, store, provider): - """Mock manager.""" - return auth.AuthManager(hass, store, {(provider.type, provider.id): provider}, {}) - - -async def test_create_new_credential(manager, provider) -> None: - """Test that we create a new credential.""" - credentials = await provider.async_get_or_create_credentials({}) - assert credentials.is_new is True - - user = await manager.async_get_or_create_user(credentials) - assert user.name == legacy_api_password.LEGACY_USER_NAME - assert user.is_active - - -async def test_only_one_credentials(manager, provider) -> None: - """Call create twice will return same credential.""" - credentials = await provider.async_get_or_create_credentials({}) - await manager.async_get_or_create_user(credentials) - credentials2 = await provider.async_get_or_create_credentials({}) - assert credentials2.id == credentials.id - assert credentials2.is_new is False - - -async def test_verify_login(hass: HomeAssistant, provider) -> None: - """Test login using legacy api password auth provider.""" - provider.async_validate_login("test-password") - with pytest.raises(legacy_api_password.InvalidAuthError): - provider.async_validate_login("invalid-password") - - -async def test_login_flow_works(hass: HomeAssistant, manager) -> None: - """Test wrong config.""" - result = await manager.login_flow.async_init(handler=("legacy_api_password", None)) - assert result["type"] == data_entry_flow.FlowResultType.FORM - - result = await manager.login_flow.async_configure( - flow_id=result["flow_id"], user_input={"password": "not-hello"} - ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["errors"]["base"] == "invalid_auth" - - result = await manager.login_flow.async_configure( - flow_id=result["flow_id"], user_input={"password": "test-password"} - ) - assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - - -async def test_create_repair_issue( - hass: HomeAssistant, issue_registry: ir.IssueRegistry -) -> None: - """Test legacy api password auth provider creates a reapir issue.""" - hass.auth = await auth.auth_manager_from_config(hass, [CONFIG], []) - ensure_auth_manager_loaded(hass.auth) - await async_setup_component(hass, "auth", {}) - - assert issue_registry.async_get_issue( - domain="auth", issue_id="deprecated_legacy_api_password" - ) diff --git a/tests/components/api/test_init.py b/tests/components/api/test_init.py index 5443d48452f5df..a1453315dbff0f 100644 --- a/tests/components/api/test_init.py +++ b/tests/components/api/test_init.py @@ -12,9 +12,6 @@ from homeassistant import const from homeassistant.auth.models import Credentials -from homeassistant.auth.providers.legacy_api_password import ( - LegacyApiPasswordAuthProvider, -) from homeassistant.bootstrap import DATA_LOGGING import homeassistant.core as ha from homeassistant.core import HomeAssistant @@ -731,22 +728,6 @@ async def test_rendering_template_admin( assert resp.status == HTTPStatus.UNAUTHORIZED -async def test_rendering_template_legacy_user( - hass: HomeAssistant, - mock_api_client: TestClient, - aiohttp_client: ClientSessionGenerator, - legacy_auth: LegacyApiPasswordAuthProvider, -) -> None: - """Test rendering a template with legacy API password.""" - hass.states.async_set("sensor.temperature", 10) - client = await aiohttp_client(hass.http.app) - resp = await client.post( - const.URL_API_TEMPLATE, - json={"template": "{{ states.sensor.temperature.state }}"}, - ) - assert resp.status == HTTPStatus.UNAUTHORIZED - - async def test_api_call_service_not_found( hass: HomeAssistant, mock_api_client: TestClient ) -> None: diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py index aa6ed64ff57bd2..20dfe0a371092b 100644 --- a/tests/components/http/test_auth.py +++ b/tests/components/http/test_auth.py @@ -15,9 +15,7 @@ from homeassistant.auth.const import GROUP_ID_READ_ONLY from homeassistant.auth.models import User from homeassistant.auth.providers import trusted_networks -from homeassistant.auth.providers.legacy_api_password import ( - LegacyApiPasswordAuthProvider, -) +from homeassistant.auth.providers.homeassistant import HassAuthProvider from homeassistant.components import websocket_api from homeassistant.components.http import KEY_HASS from homeassistant.components.http.auth import ( @@ -76,14 +74,6 @@ async def mock_handler(request): return web.json_response(data={"user_id": user_id}) -async def get_legacy_user(auth): - """Get the user in legacy_api_password auth provider.""" - provider = auth.get_auth_provider("legacy_api_password", None) - return await auth.async_get_or_create_user( - await provider.async_get_or_create_credentials({}) - ) - - @pytest.fixture def app(hass): """Fixture to set up a web.Application.""" @@ -126,7 +116,7 @@ async def test_auth_middleware_loaded_by_default(hass: HomeAssistant) -> None: async def test_cant_access_with_password_in_header( app, aiohttp_client: ClientSessionGenerator, - legacy_auth: LegacyApiPasswordAuthProvider, + local_auth: HassAuthProvider, hass: HomeAssistant, ) -> None: """Test access with password in header.""" @@ -143,7 +133,7 @@ async def test_cant_access_with_password_in_header( async def test_cant_access_with_password_in_query( app, aiohttp_client: ClientSessionGenerator, - legacy_auth: LegacyApiPasswordAuthProvider, + local_auth: HassAuthProvider, hass: HomeAssistant, ) -> None: """Test access with password in URL.""" @@ -164,7 +154,7 @@ async def test_basic_auth_does_not_work( app, aiohttp_client: ClientSessionGenerator, hass: HomeAssistant, - legacy_auth: LegacyApiPasswordAuthProvider, + local_auth: HassAuthProvider, ) -> None: """Test access with basic authentication.""" await async_setup_auth(hass, app) @@ -278,7 +268,7 @@ async def test_auth_active_access_with_trusted_ip( async def test_auth_legacy_support_api_password_cannot_access( app, aiohttp_client: ClientSessionGenerator, - legacy_auth: LegacyApiPasswordAuthProvider, + local_auth: HassAuthProvider, hass: HomeAssistant, ) -> None: """Test access using api_password if auth.support_legacy.""" diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 995be3954d9d4e..7a9fb329fcdb55 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -11,9 +11,7 @@ import pytest -from homeassistant.auth.providers.legacy_api_password import ( - LegacyApiPasswordAuthProvider, -) +from homeassistant.auth.providers.homeassistant import HassAuthProvider from homeassistant.components import http from homeassistant.core import HomeAssistant from homeassistant.helpers.http import KEY_HASS @@ -115,7 +113,7 @@ async def test_not_log_password( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, caplog: pytest.LogCaptureFixture, - legacy_auth: LegacyApiPasswordAuthProvider, + local_auth: HassAuthProvider, ) -> None: """Test access with password doesn't get logged.""" assert await async_setup_component(hass, "api", {"http": {}}) diff --git a/tests/components/websocket_api/test_auth.py b/tests/components/websocket_api/test_auth.py index 595dc7dcc32f8b..62298098adcd55 100644 --- a/tests/components/websocket_api/test_auth.py +++ b/tests/components/websocket_api/test_auth.py @@ -6,9 +6,7 @@ from aiohttp import WSMsgType import pytest -from homeassistant.auth.providers.legacy_api_password import ( - LegacyApiPasswordAuthProvider, -) +from homeassistant.auth.providers.homeassistant import HassAuthProvider from homeassistant.components.websocket_api.auth import ( TYPE_AUTH, TYPE_AUTH_INVALID, @@ -51,7 +49,7 @@ def track_disconnected(): async def test_auth_events( hass: HomeAssistant, no_auth_websocket_client, - legacy_auth: LegacyApiPasswordAuthProvider, + local_auth: HassAuthProvider, hass_access_token: str, track_connected, ) -> None: @@ -174,7 +172,7 @@ async def test_auth_active_with_password_not_allow( async def test_auth_legacy_support_with_password( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, - legacy_auth: LegacyApiPasswordAuthProvider, + local_auth: HassAuthProvider, ) -> None: """Test authenticating with a token.""" assert await async_setup_component(hass, "websocket_api", {}) diff --git a/tests/components/websocket_api/test_sensor.py b/tests/components/websocket_api/test_sensor.py index 72b39b3935467a..3af02dc8f2bbe3 100644 --- a/tests/components/websocket_api/test_sensor.py +++ b/tests/components/websocket_api/test_sensor.py @@ -1,8 +1,6 @@ """Test cases for the API stream sensor.""" -from homeassistant.auth.providers.legacy_api_password import ( - LegacyApiPasswordAuthProvider, -) +from homeassistant.auth.providers.homeassistant import HassAuthProvider from homeassistant.bootstrap import async_setup_component from homeassistant.components.websocket_api.auth import TYPE_AUTH_REQUIRED from homeassistant.components.websocket_api.http import URL @@ -17,7 +15,7 @@ async def test_websocket_api( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, hass_access_token: str, - legacy_auth: LegacyApiPasswordAuthProvider, + local_auth: HassAuthProvider, ) -> None: """Test API streams.""" await async_setup_component( diff --git a/tests/conftest.py b/tests/conftest.py index b2b0eb3487c916..14e6f97d7c4a36 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -43,7 +43,7 @@ from homeassistant import core as ha, loader, runner from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY from homeassistant.auth.models import Credentials -from homeassistant.auth.providers import homeassistant, legacy_api_password +from homeassistant.auth.providers import homeassistant from homeassistant.components.device_tracker.legacy import Device from homeassistant.components.websocket_api.auth import ( TYPE_AUTH, @@ -751,20 +751,6 @@ async def hass_supervisor_access_token( return hass.auth.async_create_access_token(refresh_token) -@pytest.fixture -def legacy_auth( - hass: HomeAssistant, -) -> legacy_api_password.LegacyApiPasswordAuthProvider: - """Load legacy API password provider.""" - prv = legacy_api_password.LegacyApiPasswordAuthProvider( - hass, - hass.auth._store, - {"type": "legacy_api_password", "api_password": "test-password"}, - ) - hass.auth._providers[(prv.type, prv.id)] = prv - return prv - - @pytest.fixture async def local_auth(hass: HomeAssistant) -> homeassistant.HassAuthProvider: """Load local auth provider.""" diff --git a/tests/test_config.py b/tests/test_config.py index a30498b422ade2..51c72472a4f3f2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1335,7 +1335,6 @@ async def test_auth_provider_config(hass: HomeAssistant) -> None: "time_zone": "GMT", CONF_AUTH_PROVIDERS: [ {"type": "homeassistant"}, - {"type": "legacy_api_password", "api_password": "some-pass"}, ], CONF_AUTH_MFA_MODULES: [{"type": "totp"}, {"type": "totp", "id": "second"}], } @@ -1343,9 +1342,8 @@ async def test_auth_provider_config(hass: HomeAssistant) -> None: del hass.auth await config_util.async_process_ha_core_config(hass, core_config) - assert len(hass.auth.auth_providers) == 2 + assert len(hass.auth.auth_providers) == 1 assert hass.auth.auth_providers[0].type == "homeassistant" - assert hass.auth.auth_providers[1].type == "legacy_api_password" assert len(hass.auth.auth_mfa_modules) == 2 assert hass.auth.auth_mfa_modules[0].id == "totp" assert hass.auth.auth_mfa_modules[1].id == "second"