Remove legacy_api_password auth provider (#119976)

This commit is contained in:
Robert Resch 2024-06-19 22:46:30 +02:00 committed by GitHub
parent 7d14b9c5c8
commit bae008b0e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 14 additions and 285 deletions

View File

@ -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,
)

View File

@ -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."
}
}
}

View File

@ -132,7 +132,6 @@ _TEST_FIXTURES: dict[str, list[str] | str] = {
"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",

View File

@ -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"
)

View File

@ -12,9 +12,6 @@ import voluptuous as vol
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:

View File

@ -15,9 +15,7 @@ import yarl
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."""

View File

@ -11,9 +11,7 @@ from unittest.mock import Mock, patch
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": {}})

View File

@ -6,9 +6,7 @@ import aiohttp
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_connected(hass):
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", {})

View File

@ -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(

View File

@ -43,7 +43,7 @@ from . import patch_time # noqa: F401, isort:skip
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."""

View File

@ -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"