mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 17:04:04 -04:00
Validate username during onboarding before creating a new user (#182320)
This commit is contained in:
@@ -10,7 +10,7 @@ from aiohttp.web_exceptions import HTTPUnauthorized
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||
from homeassistant.auth.providers.homeassistant import HassAuthProvider
|
||||
from homeassistant.auth.providers.homeassistant import HassAuthProvider, InvalidUsername
|
||||
from homeassistant.components import person
|
||||
from homeassistant.components.auth import indieauth
|
||||
from homeassistant.components.http import KEY_HASS, KEY_HASS_REFRESH_TOKEN_ID
|
||||
@@ -199,10 +199,18 @@ class UserOnboardingView(_BaseOnboardingStepView):
|
||||
provider = _async_get_hass_provider(hass)
|
||||
await provider.async_initialize()
|
||||
|
||||
# Add the auth before creating the user, as it validates the
|
||||
# username, to avoid leaving an orphaned user behind on failure.
|
||||
try:
|
||||
await provider.async_add_auth(data["username"], data["password"])
|
||||
except InvalidUsername as err:
|
||||
return self.json_message(
|
||||
str(err), HTTPStatus.BAD_REQUEST, err.translation_key
|
||||
)
|
||||
|
||||
user = await hass.auth.async_create_user(
|
||||
data["name"], group_ids=[GROUP_ID_ADMIN]
|
||||
)
|
||||
await provider.async_add_auth(data["username"], data["password"])
|
||||
credentials = await provider.async_get_or_create_credentials(
|
||||
{"username": data["username"]}
|
||||
)
|
||||
|
||||
@@ -256,6 +256,57 @@ async def test_onboarding_user_invalid_name(
|
||||
assert resp.status == 400
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("username", "expected_code"),
|
||||
[
|
||||
pytest.param("Test-User", "username_not_normalized", id="uppercase"),
|
||||
pytest.param("test-user ", "username_not_normalized", id="whitespace"),
|
||||
pytest.param("existing-user", "username_already_exists", id="duplicate"),
|
||||
],
|
||||
)
|
||||
async def test_onboarding_user_invalid_username(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
username: str,
|
||||
expected_code: str,
|
||||
) -> None:
|
||||
"""Test a rejected username does not leave an orphaned user behind."""
|
||||
mock_storage(hass_storage, {"done": []})
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
provider = views._async_get_hass_provider(hass)
|
||||
await provider.async_initialize()
|
||||
await provider.async_add_auth("existing-user", "test-pass")
|
||||
|
||||
cur_users = len(await hass.auth.async_get_users())
|
||||
client = await hass_client_no_auth()
|
||||
|
||||
resp = await client.post(
|
||||
"/api/onboarding/users",
|
||||
json={
|
||||
"client_id": CLIENT_ID,
|
||||
"name": "Test Name",
|
||||
"username": username,
|
||||
"password": "test-pass",
|
||||
"language": "en",
|
||||
},
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||
body = await resp.json()
|
||||
assert body["code"] == expected_code
|
||||
# The rejected username is echoed back so the frontend can explain the failure
|
||||
assert username in body["message"]
|
||||
|
||||
# The step stays open so onboarding can be retried with another username,
|
||||
# and no user may be left behind from the rejected attempt.
|
||||
assert const.STEP_USER not in hass_storage[const.DOMAIN]["data"]["done"]
|
||||
assert len(await hass.auth.async_get_users()) == cur_users
|
||||
|
||||
|
||||
async def test_onboarding_user_race(
|
||||
hass: HomeAssistant,
|
||||
hass_storage: dict[str, Any],
|
||||
|
||||
Reference in New Issue
Block a user