Admin allowlist storage seeded with the setup user (Feature 03, task 4)
- admin_allowlist.py: list_admin_ids / set_admin_ids (atomic sync of the allowlist inside one transaction under the connection lock; refuses empty targets so the last admin can never be removed and the allowlist can never be emptied — fail closed) and seed_setup_admin (seeds ONLY an empty allowlist: stored entry-data copy first — disaster recovery for a lost database — else the HA user who completed the config flow from the entry context; non-empty lists are never resurrected). - __init__.py: both setup paths (first setup and the reopened-record path) seed after migrations; a failed seed closes the database and re-raises through the existing error handling. - options_flow.py: the admin picker is a multi-select of EXISTING HA users (hass.auth.async_get_users), defaulted to the current database allowlist, membership-checked at the schema level and backed up on the raw-input path; empty selections are refused (no_admins), unknown ids rejected (invalid_admin); the selection is applied to the DB when the runtime record is reachable and always stored on the entry as the recovery copy. Submissions without the key (legacy raw payloads) keep the three-key behaviour untouched. - const.CONF_ADMIN_USER_IDS; strings + translations for the field and the two errors; conftest entries gain a context dict (HA's entry context carries the flow user). - Tests: 20 allowlist business tests (round-trips, refusal matrix, seed precedence and narrowed-list persistence), 5 picker flow tests, 3 setup-seeding lifecycle tests. Suite: 901 passed.
This commit is contained in:
@@ -13,10 +13,11 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .const import CONF_ADMIN_USER_IDS, DOMAIN, LOGGER
|
||||
from .db import NestQuestDatabase
|
||||
from .migrations import apply_migrations
|
||||
from .store import async_get_db_path
|
||||
from .admin_allowlist import seed_setup_admin
|
||||
|
||||
_LOGGER = LOGGER
|
||||
|
||||
@@ -245,6 +246,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
db = await _async_open_database(
|
||||
hass, await async_get_db_path(hass)
|
||||
)
|
||||
context = getattr(entry, "context", None)
|
||||
context_user_id = (
|
||||
context.get("user_id")
|
||||
if isinstance(context, dict)
|
||||
else None
|
||||
)
|
||||
await seed_setup_admin(
|
||||
db,
|
||||
stored_admin_ids=entry.data.get(CONF_ADMIN_USER_IDS),
|
||||
context_user_id=context_user_id,
|
||||
)
|
||||
existing.database = db
|
||||
entry.runtime_data = existing
|
||||
return True
|
||||
@@ -257,6 +269,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
database = await _async_open_database(hass, await async_get_db_path(hass))
|
||||
try:
|
||||
# Seed the admin allowlist on first setup so the owner is never
|
||||
# locked out: an empty database takes the config entry's stored
|
||||
# admin copy (disaster recovery), else the HA user who
|
||||
# completed the config flow. Non-empty allowlists are never
|
||||
# touched (fail-closed narrowing is deliberate).
|
||||
context = getattr(entry, "context", None)
|
||||
context_user_id = (
|
||||
context.get("user_id")
|
||||
if isinstance(context, dict)
|
||||
else None
|
||||
)
|
||||
await seed_setup_admin(
|
||||
database,
|
||||
stored_admin_ids=entry.data.get(CONF_ADMIN_USER_IDS),
|
||||
context_user_id=context_user_id,
|
||||
)
|
||||
remove_update_listener = entry.add_update_listener(_async_update_listener)
|
||||
except BaseException:
|
||||
await database.close()
|
||||
|
||||
Reference in New Issue
Block a user