init
This commit is contained in:
135
custom_components/openid/__init__.py
Normal file
135
custom_components/openid/__init__.py
Normal 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)
|
||||
Reference in New Issue
Block a user