diff --git a/homeassistant/components/emulated_hue.py b/homeassistant/components/emulated_hue.py index d39a1602ec2..1b2581075bc 100755 --- a/homeassistant/components/emulated_hue.py +++ b/homeassistant/components/emulated_hue.py @@ -74,7 +74,8 @@ def setup(hass, yaml_config): api_password=None, ssl_certificate=None, ssl_key=None, - cors_origins=[] + cors_origins=[], + approved_ips=[] ) server.register_view(DescriptionXmlView(hass, config)) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 4c46aa25524..c3a8ccd85f6 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -28,6 +28,7 @@ DOMAIN = 'http' REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11') CONF_API_PASSWORD = 'api_password' +CONF_APPROVED_IPS = 'approved_ips' CONF_SERVER_HOST = 'server_host' CONF_SERVER_PORT = 'server_port' CONF_DEVELOPMENT = 'development' @@ -71,7 +72,8 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_DEVELOPMENT): cv.string, vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile, vol.Optional(CONF_SSL_KEY): cv.isfile, - vol.Optional(CONF_CORS_ORIGINS): cv.ensure_list + vol.Optional(CONF_CORS_ORIGINS): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_APPROVED_IPS): vol.All(cv.ensure_list, [cv.string]) }), }, extra=vol.ALLOW_EXTRA) @@ -108,6 +110,7 @@ def setup(hass, config): ssl_certificate = conf.get(CONF_SSL_CERTIFICATE) ssl_key = conf.get(CONF_SSL_KEY) cors_origins = conf.get(CONF_CORS_ORIGINS, []) + approved_ips = conf.get(CONF_APPROVED_IPS, []) server = HomeAssistantWSGI( hass, @@ -117,7 +120,8 @@ def setup(hass, config): api_password=api_password, ssl_certificate=ssl_certificate, ssl_key=ssl_key, - cors_origins=cors_origins + cors_origins=cors_origins, + approved_ips=approved_ips ) def start_wsgi_server(event): @@ -249,7 +253,8 @@ class HomeAssistantWSGI(object): # pylint: disable=too-many-arguments def __init__(self, hass, development, api_password, ssl_certificate, - ssl_key, server_host, server_port, cors_origins): + ssl_key, server_host, server_port, cors_origins, + approved_ips): """Initilalize the WSGI Home Assistant server.""" from werkzeug.wrappers import Response @@ -268,6 +273,7 @@ class HomeAssistantWSGI(object): self.server_host = server_host self.server_port = server_port self.cors_origins = cors_origins + self.approved_ips = approved_ips self.event_forwarder = None self.server = None @@ -468,6 +474,9 @@ class HomeAssistantView(object): if self.hass.wsgi.api_password is None: authenticated = True + elif request.remote_addr in self.hass.wsgi.approved_ips: + authenticated = True + elif hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''), self.hass.wsgi.api_password): # A valid auth header has been set diff --git a/tests/components/test_http.py b/tests/components/test_http.py index ef491a91b36..e4e0fafd7c7 100644 --- a/tests/components/test_http.py +++ b/tests/components/test_http.py @@ -72,6 +72,15 @@ class TestHttp: assert req.status_code == 401 + def test_access_denied_with_ip_no_in_approved_ips(self, caplog): + """Test access deniend with ip not in approved ip.""" + hass.wsgi.approved_ips = ['134.4.56.1'] + + req = requests.get(_url(const.URL_API), + params={'api_password': ''}) + + assert req.status_code == 401 + def test_access_with_password_in_header(self, caplog): """Test access with password in URL.""" # Hide logging from requests package that we use to test logging @@ -112,6 +121,15 @@ class TestHttp: # assert const.URL_API in logs assert API_PASSWORD not in logs + def test_access_with_ip_in_approved_ips(self, caplog): + """Test access with approved ip.""" + hass.wsgi.approved_ips = ['127.0.0.1', '134.4.56.1'] + + req = requests.get(_url(const.URL_API), + params={'api_password': ''}) + + assert req.status_code == 200 + def test_cors_allowed_with_password_in_url(self): """Test cross origin resource sharing with password in url.""" req = requests.get(_url(const.URL_API),