2018-05-01 16:20:41 +00:00
|
|
|
"""Example auth provider."""
|
|
|
|
from collections import OrderedDict
|
|
|
|
import hmac
|
2018-08-21 18:03:38 +00:00
|
|
|
from typing import Any, Dict, Optional, cast
|
2018-05-01 16:20:41 +00:00
|
|
|
|
|
|
|
import voluptuous as vol
|
|
|
|
|
2018-05-10 18:09:22 +00:00
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2018-05-01 16:20:41 +00:00
|
|
|
from homeassistant.core import callback
|
|
|
|
|
2018-08-21 18:03:38 +00:00
|
|
|
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
|
2018-08-16 20:25:41 +00:00
|
|
|
from ..models import Credentials, UserMeta
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-05-01 16:20:41 +00:00
|
|
|
|
|
|
|
USER_SCHEMA = vol.Schema({
|
|
|
|
vol.Required('username'): str,
|
|
|
|
vol.Required('password'): str,
|
|
|
|
vol.Optional('name'): str,
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
|
2018-05-01 16:20:41 +00:00
|
|
|
vol.Required('users'): [USER_SCHEMA]
|
|
|
|
}, extra=vol.PREVENT_EXTRA)
|
|
|
|
|
|
|
|
|
2018-05-10 18:09:22 +00:00
|
|
|
class InvalidAuthError(HomeAssistantError):
|
|
|
|
"""Raised when submitting invalid authentication."""
|
|
|
|
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
@AUTH_PROVIDERS.register('insecure_example')
|
|
|
|
class ExampleAuthProvider(AuthProvider):
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Example auth provider based on hardcoded usernames and passwords."""
|
|
|
|
|
2018-08-21 18:03:38 +00:00
|
|
|
async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow:
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Return a flow to login."""
|
2018-08-21 18:03:38 +00:00
|
|
|
return ExampleLoginFlow(self)
|
2018-05-01 16:20:41 +00:00
|
|
|
|
|
|
|
@callback
|
2018-08-16 20:25:41 +00:00
|
|
|
def async_validate_login(self, username: str, password: str) -> None:
|
2018-08-24 08:28:43 +00:00
|
|
|
"""Validate a username and password."""
|
2018-05-01 16:20:41 +00:00
|
|
|
user = None
|
|
|
|
|
|
|
|
# Compare all users to avoid timing attacks.
|
|
|
|
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.
|
|
|
|
hmac.compare_digest(password.encode('utf-8'),
|
|
|
|
password.encode('utf-8'))
|
2018-05-10 18:09:22 +00:00
|
|
|
raise InvalidAuthError
|
2018-05-01 16:20:41 +00:00
|
|
|
|
|
|
|
if not hmac.compare_digest(user['password'].encode('utf-8'),
|
|
|
|
password.encode('utf-8')):
|
2018-05-10 18:09:22 +00:00
|
|
|
raise InvalidAuthError
|
2018-05-01 16:20:41 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_get_or_create_credentials(
|
|
|
|
self, flow_result: Dict[str, str]) -> Credentials:
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Get credentials based on the flow result."""
|
|
|
|
username = flow_result['username']
|
|
|
|
|
|
|
|
for credential in await self.async_credentials():
|
|
|
|
if credential.data['username'] == username:
|
|
|
|
return credential
|
|
|
|
|
|
|
|
# Create new credentials.
|
|
|
|
return self.async_create_credentials({
|
|
|
|
'username': username
|
|
|
|
})
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_user_meta_for_credentials(
|
|
|
|
self, credentials: Credentials) -> UserMeta:
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Return extra user metadata for credentials.
|
|
|
|
|
|
|
|
Will be used to populate info when creating a new user.
|
|
|
|
"""
|
|
|
|
username = credentials.data['username']
|
2018-08-16 20:25:41 +00:00
|
|
|
name = None
|
2018-05-01 16:20:41 +00:00
|
|
|
|
|
|
|
for user in self.config['users']:
|
|
|
|
if user['username'] == username:
|
2018-08-16 20:25:41 +00:00
|
|
|
name = user.get('name')
|
2018-07-19 20:10:36 +00:00
|
|
|
break
|
2018-05-01 16:20:41 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
return UserMeta(name=name, is_active=True)
|
2018-05-01 16:20:41 +00:00
|
|
|
|
|
|
|
|
2018-08-21 18:03:38 +00:00
|
|
|
class ExampleLoginFlow(LoginFlow):
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Handler for the login flow."""
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_step_init(
|
2018-08-17 18:22:49 +00:00
|
|
|
self, user_input: Optional[Dict[str, str]] = None) \
|
|
|
|
-> Dict[str, Any]:
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Handle the step of the form."""
|
|
|
|
errors = {}
|
|
|
|
|
|
|
|
if user_input is not None:
|
|
|
|
try:
|
2018-08-21 18:03:38 +00:00
|
|
|
cast(ExampleAuthProvider, self._auth_provider)\
|
|
|
|
.async_validate_login(user_input['username'],
|
|
|
|
user_input['password'])
|
2018-05-10 18:09:22 +00:00
|
|
|
except InvalidAuthError:
|
2018-05-01 16:20:41 +00:00
|
|
|
errors['base'] = 'invalid_auth'
|
|
|
|
|
|
|
|
if not errors:
|
2018-08-21 18:03:38 +00:00
|
|
|
user_input.pop('password')
|
|
|
|
return await self.async_finish(user_input)
|
2018-05-01 16:20:41 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
schema = OrderedDict() # type: Dict[str, type]
|
2018-05-01 16:20:41 +00:00
|
|
|
schema['username'] = str
|
|
|
|
schema['password'] = str
|
|
|
|
|
|
|
|
return self.async_show_form(
|
|
|
|
step_id='init',
|
|
|
|
data_schema=vol.Schema(schema),
|
|
|
|
errors=errors,
|
|
|
|
)
|