core/homeassistant/auth/providers/insecure_example.py

127 lines
3.8 KiB
Python
Raw Normal View History

"""Example auth provider."""
2021-03-17 20:46:07 +00:00
from __future__ import annotations
from collections.abc import Mapping
import hmac
from typing import Any, cast
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from ..models import Credentials, UserMeta
2023-06-08 21:43:56 +00:00
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
2018-07-13 09:43:08 +00:00
2019-07-31 19:25:30 +00:00
USER_SCHEMA = vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
vol.Optional("name"): str,
}
)
2019-07-31 19:25:30 +00:00
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend(
{vol.Required("users"): [USER_SCHEMA]}, extra=vol.PREVENT_EXTRA
)
class InvalidAuthError(HomeAssistantError):
"""Raised when submitting invalid authentication."""
2019-07-31 19:25:30 +00:00
@AUTH_PROVIDERS.register("insecure_example")
2018-07-13 09:43:08 +00:00
class ExampleAuthProvider(AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords."""
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login."""
return ExampleLoginFlow(self)
@callback
def async_validate_login(self, username: str, password: str) -> None:
"""Validate a username and password."""
user = None
# Compare all users to avoid timing attacks.
2019-07-31 19:25:30 +00:00
for usr in self.config["users"]:
if hmac.compare_digest(
username.encode("utf-8"), usr["username"].encode("utf-8")
):
user = usr
if user is None:
# Do one more compare to make timing the same as if user was found.
2019-07-31 19:25:30 +00:00
hmac.compare_digest(password.encode("utf-8"), password.encode("utf-8"))
raise InvalidAuthError
2019-07-31 19:25:30 +00:00
if not hmac.compare_digest(
user["password"].encode("utf-8"), password.encode("utf-8")
):
raise InvalidAuthError
async def async_get_or_create_credentials(
self, flow_result: Mapping[str, str]
2019-07-31 19:25:30 +00:00
) -> Credentials:
"""Get credentials based on the flow result."""
2019-07-31 19:25:30 +00:00
username = flow_result["username"]
for credential in await self.async_credentials():
2019-07-31 19:25:30 +00:00
if credential.data["username"] == username:
return credential
# Create new credentials.
2019-07-31 19:25:30 +00:00
return self.async_create_credentials({"username": username})
async def async_user_meta_for_credentials(
2019-07-31 19:25:30 +00:00
self, credentials: Credentials
) -> UserMeta:
"""Return extra user metadata for credentials.
Will be used to populate info when creating a new user.
"""
2019-07-31 19:25:30 +00:00
username = credentials.data["username"]
name = None
2019-07-31 19:25:30 +00:00
for user in self.config["users"]:
if user["username"] == username:
name = user.get("name")
break
return UserMeta(name=name, is_active=True)
class ExampleLoginFlow(LoginFlow):
"""Handler for the login flow."""
async def async_step_init(
2021-03-17 20:46:07 +00:00
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the step of the form."""
errors = None
if user_input is not None:
try:
2019-07-31 19:25:30 +00:00
cast(ExampleAuthProvider, self._auth_provider).async_validate_login(
user_input["username"], user_input["password"]
)
except InvalidAuthError:
errors = {"base": "invalid_auth"}
if not errors:
2019-07-31 19:25:30 +00:00
user_input.pop("password")
return await self.async_finish(user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
)