2016-11-25 21:04:06 +00:00
|
|
|
"""Authentication for HTTP component."""
|
2018-03-09 01:51:49 +00:00
|
|
|
|
2017-09-28 07:49:35 +00:00
|
|
|
import base64
|
2016-11-25 21:04:06 +00:00
|
|
|
import hmac
|
|
|
|
import logging
|
|
|
|
|
2017-09-28 07:49:35 +00:00
|
|
|
from aiohttp import hdrs
|
2017-11-06 02:42:31 +00:00
|
|
|
from aiohttp.web import middleware
|
2017-09-28 07:49:35 +00:00
|
|
|
|
2018-02-15 21:06:14 +00:00
|
|
|
from homeassistant.core import callback
|
2016-11-25 21:04:06 +00:00
|
|
|
from homeassistant.const import HTTP_HEADER_HA_AUTH
|
2018-02-15 21:06:14 +00:00
|
|
|
from .const import KEY_AUTHENTICATED, KEY_REAL_IP
|
2016-11-25 21:04:06 +00:00
|
|
|
|
|
|
|
DATA_API_PASSWORD = 'api_password'
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2018-02-15 21:06:14 +00:00
|
|
|
@callback
|
|
|
|
def setup_auth(app, trusted_networks, api_password):
|
|
|
|
"""Create auth middleware for the app."""
|
|
|
|
@middleware
|
2018-03-09 01:51:49 +00:00
|
|
|
async def auth_middleware(request, handler):
|
2018-02-15 21:06:14 +00:00
|
|
|
"""Authenticate as middleware."""
|
|
|
|
# If no password set, just always set authenticated=True
|
|
|
|
if api_password is None:
|
|
|
|
request[KEY_AUTHENTICATED] = True
|
2018-03-09 01:51:49 +00:00
|
|
|
return await handler(request)
|
2018-02-15 21:06:14 +00:00
|
|
|
|
|
|
|
# Check authentication
|
|
|
|
authenticated = False
|
|
|
|
|
|
|
|
if (HTTP_HEADER_HA_AUTH in request.headers and
|
|
|
|
hmac.compare_digest(
|
2018-05-01 16:20:41 +00:00
|
|
|
api_password.encode('utf-8'),
|
|
|
|
request.headers[HTTP_HEADER_HA_AUTH].encode('utf-8'))):
|
2018-02-15 21:06:14 +00:00
|
|
|
# A valid auth header has been set
|
|
|
|
authenticated = True
|
|
|
|
|
|
|
|
elif (DATA_API_PASSWORD in request.query and
|
2018-05-01 16:20:41 +00:00
|
|
|
hmac.compare_digest(
|
|
|
|
api_password.encode('utf-8'),
|
|
|
|
request.query[DATA_API_PASSWORD].encode('utf-8'))):
|
2018-02-15 21:06:14 +00:00
|
|
|
authenticated = True
|
|
|
|
|
|
|
|
elif (hdrs.AUTHORIZATION in request.headers and
|
2018-05-01 16:20:41 +00:00
|
|
|
await async_validate_auth_header(api_password, request)):
|
2018-02-15 21:06:14 +00:00
|
|
|
authenticated = True
|
|
|
|
|
|
|
|
elif _is_trusted_ip(request, trusted_networks):
|
|
|
|
authenticated = True
|
|
|
|
|
|
|
|
request[KEY_AUTHENTICATED] = authenticated
|
2018-03-09 01:51:49 +00:00
|
|
|
return await handler(request)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2018-03-09 01:51:49 +00:00
|
|
|
async def auth_startup(app):
|
2018-02-15 21:06:14 +00:00
|
|
|
"""Initialize auth middleware when app starts up."""
|
|
|
|
app.middlewares.append(auth_middleware)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2018-02-15 21:06:14 +00:00
|
|
|
app.on_startup.append(auth_startup)
|
2017-09-28 07:49:35 +00:00
|
|
|
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2018-02-15 21:06:14 +00:00
|
|
|
def _is_trusted_ip(request, trusted_networks):
|
2016-11-25 21:04:06 +00:00
|
|
|
"""Test if request is from a trusted ip."""
|
2018-02-15 21:06:14 +00:00
|
|
|
ip_addr = request[KEY_REAL_IP]
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2018-02-15 21:06:14 +00:00
|
|
|
return any(
|
2016-11-25 21:04:06 +00:00
|
|
|
ip_addr in trusted_network for trusted_network
|
2018-02-15 21:06:14 +00:00
|
|
|
in trusted_networks)
|
2016-11-27 02:23:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
def validate_password(request, api_password):
|
|
|
|
"""Test if password is valid."""
|
2017-04-30 05:04:49 +00:00
|
|
|
return hmac.compare_digest(
|
2018-05-01 16:20:41 +00:00
|
|
|
api_password.encode('utf-8'),
|
|
|
|
request.app['hass'].http.api_password.encode('utf-8'))
|
2017-09-28 07:49:35 +00:00
|
|
|
|
|
|
|
|
2018-05-01 16:20:41 +00:00
|
|
|
async def async_validate_auth_header(api_password, request):
|
2017-09-28 07:49:35 +00:00
|
|
|
"""Test an authorization header if valid password."""
|
|
|
|
if hdrs.AUTHORIZATION not in request.headers:
|
|
|
|
return False
|
|
|
|
|
2018-05-01 16:20:41 +00:00
|
|
|
auth_type, auth_val = request.headers.get(hdrs.AUTHORIZATION).split(' ', 1)
|
2017-09-28 07:49:35 +00:00
|
|
|
|
2018-05-01 16:20:41 +00:00
|
|
|
if auth_type == 'Basic':
|
|
|
|
decoded = base64.b64decode(auth_val).decode('utf-8')
|
|
|
|
try:
|
|
|
|
username, password = decoded.split(':', 1)
|
|
|
|
except ValueError:
|
|
|
|
# If no ':' in decoded
|
|
|
|
return False
|
|
|
|
|
|
|
|
if username != 'homeassistant':
|
|
|
|
return False
|
2017-09-28 07:49:35 +00:00
|
|
|
|
2018-05-01 16:20:41 +00:00
|
|
|
return hmac.compare_digest(api_password.encode('utf-8'),
|
|
|
|
password.encode('utf-8'))
|
|
|
|
|
|
|
|
if auth_type != 'Bearer':
|
|
|
|
return False
|
2017-09-28 07:49:35 +00:00
|
|
|
|
2018-05-01 16:20:41 +00:00
|
|
|
hass = request.app['hass']
|
|
|
|
access_token = hass.auth.async_get_access_token(auth_val)
|
|
|
|
if access_token is None:
|
2017-09-28 07:49:35 +00:00
|
|
|
return False
|
|
|
|
|
2018-05-01 16:20:41 +00:00
|
|
|
request['hass_user'] = access_token.refresh_token.user
|
|
|
|
return True
|