Add default headers to webserver responses (#97784)
* Add default headers to webserver responses * Set default server header * Fix other testspull/97462/head^2
parent
3df71eca45
commit
369a484a78
|
@ -53,6 +53,7 @@ from .const import ( # noqa: F401
|
|||
)
|
||||
from .cors import setup_cors
|
||||
from .forwarded import async_setup_forwarded
|
||||
from .headers import setup_headers
|
||||
from .request_context import current_request, setup_request_context
|
||||
from .security_filter import setup_security_filter
|
||||
from .static import CACHE_HEADERS, CachingStaticResource
|
||||
|
@ -69,6 +70,7 @@ CONF_SSL_PEER_CERTIFICATE: Final = "ssl_peer_certificate"
|
|||
CONF_SSL_KEY: Final = "ssl_key"
|
||||
CONF_CORS_ORIGINS: Final = "cors_allowed_origins"
|
||||
CONF_USE_X_FORWARDED_FOR: Final = "use_x_forwarded_for"
|
||||
CONF_USE_X_FRAME_OPTIONS: Final = "use_x_frame_options"
|
||||
CONF_TRUSTED_PROXIES: Final = "trusted_proxies"
|
||||
CONF_LOGIN_ATTEMPTS_THRESHOLD: Final = "login_attempts_threshold"
|
||||
CONF_IP_BAN_ENABLED: Final = "ip_ban_enabled"
|
||||
|
@ -118,6 +120,7 @@ HTTP_SCHEMA: Final = vol.All(
|
|||
vol.Optional(CONF_SSL_PROFILE, default=SSL_MODERN): vol.In(
|
||||
[SSL_INTERMEDIATE, SSL_MODERN]
|
||||
),
|
||||
vol.Optional(CONF_USE_X_FRAME_OPTIONS, default=True): cv.boolean,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
@ -136,6 +139,7 @@ class ConfData(TypedDict, total=False):
|
|||
ssl_key: str
|
||||
cors_allowed_origins: list[str]
|
||||
use_x_forwarded_for: bool
|
||||
use_x_frame_options: bool
|
||||
trusted_proxies: list[IPv4Network | IPv6Network]
|
||||
login_attempts_threshold: int
|
||||
ip_ban_enabled: bool
|
||||
|
@ -180,6 +184,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
ssl_key = conf.get(CONF_SSL_KEY)
|
||||
cors_origins = conf[CONF_CORS_ORIGINS]
|
||||
use_x_forwarded_for = conf.get(CONF_USE_X_FORWARDED_FOR, False)
|
||||
use_x_frame_options = conf[CONF_USE_X_FRAME_OPTIONS]
|
||||
trusted_proxies = conf.get(CONF_TRUSTED_PROXIES) or []
|
||||
is_ban_enabled = conf[CONF_IP_BAN_ENABLED]
|
||||
login_threshold = conf[CONF_LOGIN_ATTEMPTS_THRESHOLD]
|
||||
|
@ -200,6 +205,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
use_x_forwarded_for=use_x_forwarded_for,
|
||||
login_threshold=login_threshold,
|
||||
is_ban_enabled=is_ban_enabled,
|
||||
use_x_frame_options=use_x_frame_options,
|
||||
)
|
||||
|
||||
async def stop_server(event: Event) -> None:
|
||||
|
@ -331,6 +337,7 @@ class HomeAssistantHTTP:
|
|||
use_x_forwarded_for: bool,
|
||||
login_threshold: int,
|
||||
is_ban_enabled: bool,
|
||||
use_x_frame_options: bool,
|
||||
) -> None:
|
||||
"""Initialize the server."""
|
||||
self.app[KEY_HASS] = self.hass
|
||||
|
@ -348,6 +355,7 @@ class HomeAssistantHTTP:
|
|||
|
||||
await async_setup_auth(self.hass, self.app)
|
||||
|
||||
setup_headers(self.app, use_x_frame_options)
|
||||
setup_cors(self.app, cors_origins)
|
||||
|
||||
if self.ssl_certificate:
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
"""Middleware that helps with the control of headers in our responses."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from aiohttp.web import Application, Request, StreamResponse, middleware
|
||||
|
||||
from homeassistant.core import callback
|
||||
|
||||
|
||||
@callback
|
||||
def setup_headers(app: Application, use_x_frame_options: bool) -> None:
|
||||
"""Create headers middleware for the app."""
|
||||
|
||||
@middleware
|
||||
async def headers_middleware(
|
||||
request: Request, handler: Callable[[Request], Awaitable[StreamResponse]]
|
||||
) -> StreamResponse:
|
||||
"""Process request and add headers to the responses."""
|
||||
response = await handler(request)
|
||||
response.headers["Referrer-Policy"] = "no-referrer"
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
|
||||
# Set an empty server header, to prevent aiohttp of setting one.
|
||||
response.headers["Server"] = ""
|
||||
|
||||
if use_x_frame_options:
|
||||
response.headers["X-Frame-Options"] = "SAMEORIGIN"
|
||||
|
||||
return response
|
||||
|
||||
app.middlewares.append(headers_middleware)
|
|
@ -0,0 +1,44 @@
|
|||
"""Test headers middleware."""
|
||||
from http import HTTPStatus
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from homeassistant.components.http.headers import setup_headers
|
||||
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
async def mock_handler(request):
|
||||
"""Return OK."""
|
||||
return web.Response(text="OK")
|
||||
|
||||
|
||||
async def test_headers_added(aiohttp_client: ClientSessionGenerator) -> None:
|
||||
"""Test that headers are being added on each request."""
|
||||
app = web.Application()
|
||||
app.router.add_get("/", mock_handler)
|
||||
|
||||
setup_headers(app, use_x_frame_options=True)
|
||||
|
||||
mock_api_client = await aiohttp_client(app)
|
||||
resp = await mock_api_client.get("/")
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
assert resp.headers["Referrer-Policy"] == "no-referrer"
|
||||
assert resp.headers["Server"] == ""
|
||||
assert resp.headers["X-Content-Type-Options"] == "nosniff"
|
||||
assert resp.headers["X-Frame-Options"] == "SAMEORIGIN"
|
||||
|
||||
|
||||
async def test_allow_framing(aiohttp_client: ClientSessionGenerator) -> None:
|
||||
"""Test that we allow framing when disabled."""
|
||||
app = web.Application()
|
||||
app.router.add_get("/", mock_handler)
|
||||
|
||||
setup_headers(app, use_x_frame_options=False)
|
||||
|
||||
mock_api_client = await aiohttp_client(app)
|
||||
resp = await mock_api_client.get("/")
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
assert "X-Frame-Options" not in resp.headers
|
|
@ -117,6 +117,7 @@ def test_secrets(mock_is_file, event_loop, mock_hass_config_yaml: None) -> None:
|
|||
"login_attempts_threshold": -1,
|
||||
"server_port": 8123,
|
||||
"ssl_profile": "modern",
|
||||
"use_x_frame_options": True,
|
||||
}
|
||||
assert res["secret_cache"] == {
|
||||
get_test_config_dir("secrets.yaml"): {"http_pw": "http://google.com"}
|
||||
|
|
Loading…
Reference in New Issue