This commit is contained in:
2026-01-30 23:31:00 -06:00
commit a39095b3de
2665 changed files with 263970 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
"""OpenID / OAuth2 login component for Home Assistant."""
from __future__ import annotations
import asyncio
from http import HTTPStatus
import logging
import os
from pathlib import Path
import hass_frontend
import voluptuous as vol
from homeassistant.components.http import StaticPathConfig
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import (
CONF_AUTHORIZE_URL,
CONF_BLOCK_LOGIN,
CONF_CONFIGURE_URL,
CONF_CREATE_USER,
CONF_OPENID_TEXT,
CONF_SCOPE,
CONF_TOKEN_URL,
CONF_USER_INFO_URL,
CONF_USERNAME_FIELD,
DOMAIN,
)
from .http_helper import override_authorize_login_flow, override_authorize_route
from .views import OpenIDAuthorizeView, OpenIDCallbackView
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_CLIENT_SECRET): cv.string,
vol.Optional(CONF_AUTHORIZE_URL): cv.url,
vol.Optional(CONF_TOKEN_URL): cv.url,
vol.Optional(CONF_USER_INFO_URL): cv.url,
vol.Optional(CONF_CONFIGURE_URL): cv.url,
vol.Optional(CONF_SCOPE, default="openid profile email"): cv.string,
vol.Optional(
CONF_USERNAME_FIELD, default="preferred_username"
): cv.string,
vol.Optional(CONF_CREATE_USER, default=False): cv.boolean,
vol.Optional(CONF_BLOCK_LOGIN, default=False): cv.boolean,
vol.Optional(
CONF_OPENID_TEXT, default="OpenID / OAuth2 Authentication"
): cv.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the OpenID component."""
if DOMAIN not in config:
_LOGGER.error("Missing '%s' section in configuration.yaml", DOMAIN)
return False
hass.data[DOMAIN] = config[DOMAIN]
hass.data.setdefault("_openid_state", {})
if CONF_CONFIGURE_URL in hass.data[DOMAIN]:
try:
await fetch_urls(hass, config[DOMAIN][CONF_CONFIGURE_URL])
except Exception as e: # noqa: BLE001
_LOGGER.error("Failed to fetch OpenID configuration: %s", e)
return False
# Preload HTML templates
authorize_path = hass_frontend.where() / "authorize.html"
authorize_template = await asyncio.to_thread(
authorize_path.read_text, encoding="utf-8"
)
token_path = Path(__file__).parent / "token.html"
token_template = await asyncio.to_thread(token_path.read_text, encoding="utf-8")
hass.data[DOMAIN]["authorize_template"] = authorize_template
hass.data[DOMAIN]["token_template"] = token_template
# Serve the custom frontend JS that hooks into the login dialog
await hass.http.async_register_static_paths(
[
StaticPathConfig(
"/openid/authorize.js",
os.path.join(os.path.dirname(__file__), "authorize.js"),
cache_headers=True,
)
]
)
# Register routes
hass.http.register_view(OpenIDAuthorizeView(hass))
hass.http.register_view(OpenIDCallbackView(hass))
# Patch /auth/authorize to inject our JS file.
override_authorize_route(hass)
# Patch the login flow to include additional OpenID data.
override_authorize_login_flow(hass)
return True
async def fetch_urls(hass: HomeAssistant, configure_url: str) -> None:
"""Fetch the OpenID URLs from the IdP's configuration endpoint."""
session = aiohttp_client.async_get_clientsession(hass, verify_ssl=False)
try:
_LOGGER.debug("Fetching OpenID configuration from %s", configure_url)
async with session.get(configure_url) as resp:
if resp.status != HTTPStatus.OK:
raise RuntimeError(f"Configuration endpoint returned {resp.status}") # noqa: TRY301
config_data = await resp.json()
# Update the configuration with fetched URLs
hass.data[DOMAIN][CONF_AUTHORIZE_URL] = config_data.get(
"authorization_endpoint"
)
hass.data[DOMAIN][CONF_TOKEN_URL] = config_data.get("token_endpoint")
hass.data[DOMAIN][CONF_USER_INFO_URL] = config_data.get("userinfo_endpoint")
_LOGGER.info("OpenID configuration loaded successfully")
except Exception as e: # noqa: BLE001
_LOGGER.error("Failed to fetch OpenID configuration: %s", e)