Fixes for known bugs in v1.0.0-rc1 (#241)

* Fix #238 for same-site cookies

* Redirect in Python + bump to rc2
This commit is contained in:
Christiaan Goossens
2026-04-14 09:43:58 +02:00
committed by GitHub
parent c7672f65d9
commit baf3ac6b5a
10 changed files with 190 additions and 101 deletions

View File

@@ -93,7 +93,7 @@ async def test_provider_cookie_header_sets_secure_when_requested(hass: HomeAssis
provider = hass.auth.get_auth_providers(DOMAIN)[0]
cookie_header = provider.get_cookie_header("state-id", secure=True)["set-cookie"]
assert "SameSite=Strict" in cookie_header
assert "SameSite=Lax" in cookie_header
assert "HttpOnly" in cookie_header
assert "Secure" in cookie_header
@@ -342,5 +342,36 @@ async def test_login_with_invalid_cookie_aborts(hass: HomeAssistant):
result = await flow.async_step_init({})
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "oidc_cookie_invalid"
@pytest.mark.asyncio
async def test_login_with_no_cookie_aborts(hass: HomeAssistant):
"""Missing cookie should fail closed."""
await setup(
hass,
{
CLIENT_ID: "dummy",
DISCOVERY_URL: MockOIDCServer.get_discovery_url(),
FEATURES: {
FEATURES_AUTOMATIC_PERSON_CREATION: False,
FEATURES_AUTOMATIC_USER_LINKING: False,
},
},
True,
)
provider = hass.auth.get_auth_providers(DOMAIN)[0]
flow = await provider.async_login_flow({})
fake_request = SimpleNamespace(cookies={}, remote="127.0.0.1")
with patch(
"custom_components.auth_oidc.provider.http.current_request"
) as current_request:
current_request.get.return_value = fake_request
result = await flow.async_step_init({})
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "no_oidc_cookie_found"

View File

@@ -2,6 +2,7 @@
import base64
import os
from urllib.parse import parse_qs, quote, unquote, urlparse
from unittest.mock import AsyncMock, MagicMock, patch
from auth_oidc.config.const import DISCOVERY_URL, CLIENT_ID
import pytest
@@ -45,6 +46,22 @@ async def setup(
assert result
async def setup_mock_authorize_route(hass: HomeAssistant) -> None:
"""Register a mock /auth/authorize page so frontend injection can hook into it."""
await async_setup_component(hass, HTTP_DOMAIN, {})
mock_html_path = os.path.join(os.path.dirname(__file__), "mocks", "auth_page.html")
await hass.http.async_register_static_paths(
[
StaticPathConfig(
"/auth/authorize",
mock_html_path,
cache_headers=False,
)
]
)
@pytest.mark.asyncio
async def test_welcome_page_registration(hass: HomeAssistant, hass_client):
"""Test that welcome page is present."""
@@ -87,7 +104,7 @@ async def test_welcome_rejects_invalid_encoded_redirect_uri(
@pytest.mark.asyncio
async def test_welcome_sets_strict_state_cookie_flags(hass: HomeAssistant, hass_client):
async def test_welcome_sets_secure_state_cookie_flags(hass: HomeAssistant, hass_client):
"""Welcome should set secure cookie flags for the OIDC state cookie."""
await setup(hass)
@@ -105,7 +122,7 @@ async def test_welcome_sets_strict_state_cookie_flags(hass: HomeAssistant, hass_
set_cookie = resp.headers.get("Set-Cookie", "")
assert "Path=/auth/" in set_cookie
assert "SameSite=Strict" in set_cookie
assert "SameSite=Lax" in set_cookie
assert "HttpOnly" in set_cookie
assert "Max-Age=300" in set_cookie
@@ -557,25 +574,14 @@ async def test_frontend_injection(hass: HomeAssistant, hass_client):
"""Test that frontend injection works."""
# Because there is no frontend in the test setup,
# we'll have to fake /auth/authorize for the changes to register
await async_setup_component(hass, HTTP_DOMAIN, {})
mock_html_path = os.path.join(os.path.dirname(__file__), "mocks", "auth_page.html")
await hass.http.async_register_static_paths(
[
StaticPathConfig(
"/auth/authorize",
mock_html_path,
cache_headers=False,
)
]
)
# we'll have to fake /auth/authorize for the changes to register.
await setup_mock_authorize_route(hass)
await setup(hass)
client = await hass_client()
resp = await client.get("/auth/authorize", allow_redirects=False)
assert resp.status == 200
assert resp.status == 200 # 200 because there is no redirect_uri
text = await resp.text()
assert "<script src='/auth/oidc/static/injection.js" in text
@@ -606,7 +612,7 @@ async def test_frontend_injection_logs_and_returns_when_route_handler_is_unexpec
return iter([FakeRoute()])
with patch.object(hass.http.app.router, "resources", return_value=[FakeResource()]):
await frontend_injection(hass)
await frontend_injection(hass, force_https=False)
assert "Unexpected route handler type" in caplog.text
assert (
@@ -625,6 +631,61 @@ async def test_injected_auth_page_inject_logs_errors(hass: HomeAssistant, caplog
"custom_components.auth_oidc.endpoints.injected_auth_page.frontend_injection",
side_effect=RuntimeError("boom"),
):
await OIDCInjectedAuthPage.inject(hass)
await OIDCInjectedAuthPage.inject(hass, force_https=False)
assert "Failed to inject OIDC auth page: boom" in caplog.text
@pytest.mark.asyncio
async def test_injected_auth_page_redirects_to_welcome_when_not_skipped(
hass: HomeAssistant, hass_client
):
"""Injected auth page should redirect into OIDC when skip flags are absent."""
await setup_mock_authorize_route(hass)
await setup(hass)
client = await hass_client()
encoded_redirect_uri = quote(create_redirect_uri(WEB_CLIENT_ID), safe="")
resp = await client.get(
f"/auth/authorize?redirect_uri={encoded_redirect_uri}",
allow_redirects=False,
)
assert resp.status == 302
location = resp.headers["Location"]
parsed_location = urlparse(location)
assert parsed_location.path == "/auth/oidc/welcome"
query = parse_qs(parsed_location.query)
assert "redirect_uri" in query
original_url = base64.b64decode(unquote(query["redirect_uri"][0]), validate=True)
original_url = original_url.decode("utf-8")
assert "/auth/authorize?redirect_uri=" in original_url
@pytest.mark.asyncio
@pytest.mark.parametrize(
"request_target",
[
"/auth/authorize?skip_oidc_redirect=true",
"/auth/authorize?redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fauthorize%3Fskip_oidc_redirect%3Dtrue",
],
)
async def test_injected_auth_page_returns_original_html_when_skipped(
hass: HomeAssistant,
hass_client,
request_target: str,
):
"""Injected auth page should render HTML when redirect suppression is requested."""
await setup_mock_authorize_route(hass)
await setup(hass)
client = await hass_client()
response = await client.get(request_target, allow_redirects=False)
assert response.status == 200
assert "<script src='/auth/oidc/static/injection.js" in await response.text()