diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index dd07e137e5e..5f3836252df 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -143,6 +143,7 @@ from homeassistant.auth.models import ( User, ) from homeassistant.components import websocket_api +from homeassistant.components.http import KEY_HASS from homeassistant.components.http.auth import ( async_sign_path, async_user_not_allowed_do_auth, @@ -209,7 +210,7 @@ class RevokeTokenView(HomeAssistantView): async def post(self, request: web.Request) -> web.Response: """Revoke a token.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] data = cast(MultiDictProxy[str], await request.post()) # OAuth 2.0 Token Revocation [RFC7009] @@ -243,7 +244,7 @@ class TokenView(HomeAssistantView): @log_invalid_auth async def post(self, request: web.Request) -> web.Response: """Grant a token.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] data = cast(MultiDictProxy[str], await request.post()) grant_type = data.get("grant_type") @@ -415,7 +416,7 @@ class LinkUserView(HomeAssistantView): @RequestDataValidator(vol.Schema({"code": str, "client_id": str})) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Link a user.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] user: User = request["hass_user"] credentials = self._retrieve_credentials(data["client_id"], data["code"]) diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 12b1893bc9d..8051e871776 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -81,6 +81,7 @@ from homeassistant import data_entry_flow from homeassistant.auth import AuthManagerFlowManager, InvalidAuthError from homeassistant.auth.models import AuthFlowResult, Credentials from homeassistant.components import onboarding +from homeassistant.components.http import KEY_HASS from homeassistant.components.http.auth import async_user_not_allowed_do_auth from homeassistant.components.http.ban import ( log_invalid_auth, @@ -144,7 +145,7 @@ class AuthProvidersView(HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Get available auth providers.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] if not onboarding.async_is_user_onboarded(hass): return self.json_message( message="Onboarding not finished", @@ -255,7 +256,7 @@ class LoginFlowBaseView(HomeAssistantView): await process_wrong_login(request) return self.json(_prepare_result_json(result)) - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] if not await indieauth.verify_redirect_uri( hass, client_id, result["context"]["redirect_uri"] diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 6f60112c9ba..d053e238e6e 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -34,6 +34,7 @@ from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv from homeassistant.helpers.http import ( KEY_AUTHENTICATED, # noqa: F401 + KEY_HASS, HomeAssistantView, current_request, ) @@ -47,7 +48,7 @@ from homeassistant.util.json import json_loads from .auth import async_setup_auth from .ban import setup_bans -from .const import KEY_HASS, KEY_HASS_REFRESH_TOKEN_ID, KEY_HASS_USER # noqa: F401 +from .const import KEY_HASS_REFRESH_TOKEN_ID, KEY_HASS_USER # noqa: F401 from .cors import setup_cors from .decorators import require_admin # noqa: F401 from .forwarded import async_setup_forwarded @@ -323,6 +324,7 @@ class HomeAssistantHTTP: ) -> None: """Initialize the server.""" self.app[KEY_HASS] = self.hass + self.app["hass"] = self.hass # For backwards compatibility # Order matters, security filters middleware needs to go first, # forwarded middleware needs to go second. diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 0b720b078b9..bec11d6e5ff 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -21,6 +21,7 @@ from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.util import dt as dt_util, yaml +from .const import KEY_HASS from .view import HomeAssistantView _HassViewT = TypeVar("_HassViewT", bound=HomeAssistantView) @@ -105,7 +106,7 @@ async def process_wrong_login(request: Request) -> None: Increase failed login attempts counter for remote IP address. Add ip ban entry if failed login attempts exceeds threshold. """ - hass = request.app["hass"] + hass = request.app[KEY_HASS] remote_addr = ip_address(request.remote) # type: ignore[arg-type] remote_host = request.remote diff --git a/homeassistant/components/http/const.py b/homeassistant/components/http/const.py index 090e5234aeb..3181b043b88 100644 --- a/homeassistant/components/http/const.py +++ b/homeassistant/components/http/const.py @@ -1,8 +1,7 @@ """HTTP specific constants.""" from typing import Final -from homeassistant.helpers.http import KEY_AUTHENTICATED # noqa: F401 +from homeassistant.helpers.http import KEY_AUTHENTICATED, KEY_HASS # noqa: F401 -KEY_HASS: Final = "hass" KEY_HASS_USER: Final = "hass_user" KEY_HASS_REFRESH_TOKEN_ID: Final = "hass_refresh_token_id" diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index e6e773d4c0c..57f3f22866e 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -12,8 +12,6 @@ from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound from aiohttp.web_urldispatcher import StaticResource from lru import LRU -from homeassistant.core import HomeAssistant - from .const import KEY_HASS CACHE_TIME: Final = 31 * 86400 # = 1 month @@ -48,7 +46,7 @@ class CachingStaticResource(StaticResource): rel_url = request.match_info["filename"] key = (rel_url, self._directory) if (filepath_content_type := PATH_CACHE.get(key)) is None: - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] try: filepath = await hass.async_add_executor_job(_get_file_path, *key) except (ValueError, FileNotFoundError) as error: diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py index 63ff173a3a0..6c92917f0bc 100644 --- a/homeassistant/helpers/http.py +++ b/homeassistant/helpers/http.py @@ -10,7 +10,7 @@ from typing import Any, Final from aiohttp import web from aiohttp.typedefs import LooseHeaders -from aiohttp.web import Request +from aiohttp.web import AppKey, Request from aiohttp.web_exceptions import ( HTTPBadRequest, HTTPInternalServerError, @@ -30,6 +30,7 @@ _LOGGER = logging.getLogger(__name__) KEY_AUTHENTICATED: Final = "ha_authenticated" +KEY_HASS: AppKey[HomeAssistant] = AppKey("hass") current_request: ContextVar[Request | None] = ContextVar( "current_request", default=None diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py index ab56dca5580..a54a697240a 100644 --- a/tests/components/http/test_auth.py +++ b/tests/components/http/test_auth.py @@ -17,6 +17,7 @@ from homeassistant.auth.providers.legacy_api_password import ( LegacyApiPasswordAuthProvider, ) from homeassistant.components import websocket_api +from homeassistant.components.http import KEY_HASS from homeassistant.components.http.auth import ( CONTENT_USER_NAME, DATA_SIGN_SECRET, @@ -78,7 +79,7 @@ async def get_legacy_user(auth): def app(hass): """Fixture to set up a web.Application.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass app.router.add_get("/", mock_handler) async_setup_forwarded(app, True, []) return app @@ -88,7 +89,7 @@ def app(hass): def app2(hass): """Fixture to set up a web.Application without real_ip middleware.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass app.router.add_get("/", mock_handler) return app diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index e38a9c97071..26301cf5b79 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -10,7 +10,7 @@ from aiohttp.web_middlewares import middleware import pytest import homeassistant.components.http as http -from homeassistant.components.http import KEY_AUTHENTICATED +from homeassistant.components.http import KEY_AUTHENTICATED, KEY_HASS from homeassistant.components.http.ban import ( IP_BANS_FILE, KEY_BAN_MANAGER, @@ -58,7 +58,7 @@ async def test_access_from_banned_ip( ) -> None: """Test accessing to server from banned IP. Both trusted and not.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -87,7 +87,7 @@ async def test_access_from_banned_ip_with_partially_broken_yaml_file( still load the bans. """ app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -118,7 +118,7 @@ async def test_no_ip_bans_file( ) -> None: """Test no ip bans file.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -138,7 +138,7 @@ async def test_failure_loading_ip_bans_file( ) -> None: """Test failure loading ip bans file.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -160,7 +160,7 @@ async def test_ip_ban_manager_never_started( ) -> None: """Test we handle the ip ban manager not being started.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -199,7 +199,7 @@ async def test_access_from_supervisor_ip( ) -> None: """Test accessing to server from supervisor IP.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass async def unauth_handler(request): """Return a mock web response.""" @@ -270,7 +270,7 @@ async def test_ip_bans_file_creation( ) -> None: """Testing if banned IP file created.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass async def unauth_handler(request): """Return a mock web response.""" @@ -326,7 +326,7 @@ async def test_failed_login_attempts_counter( ) -> None: """Testing if failed login attempts counter increased.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass async def auth_handler(request): """Return 200 status code.""" @@ -398,7 +398,7 @@ async def test_single_ban_file_entry( ) -> None: """Test that only one item is added to ban file.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass async def unauth_handler(request): """Return a mock web response.""" diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 97e39811cd8..a1be00e3a2b 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -14,6 +14,7 @@ from homeassistant.auth.providers.legacy_api_password import ( ) import homeassistant.components.http as http from homeassistant.core import HomeAssistant +from homeassistant.helpers.http import KEY_HASS from homeassistant.helpers.network import NoURLAvailableError from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -97,6 +98,15 @@ async def test_registering_view_while_running( hass.http.register_view(TestView) +async def test_homeassistant_assigned_to_app(hass: HomeAssistant) -> None: + """Test HomeAssistant instance is assigned to HomeAssistantApp.""" + assert await async_setup_component(hass, "api", {"http": {}}) + await hass.async_start() + assert hass.http.app[KEY_HASS] == hass + assert hass.http.app["hass"] == hass # For backwards compatibility + await hass.async_stop() + + async def test_not_log_password( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator,