Merge remote-tracking branch 'origin/dev' into lirc
commit
262d95b7b1
homeassistant
components
frontend/www_static
media_player
tests/components
|
@ -4,7 +4,6 @@ from __future__ import print_function
|
|||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
|
@ -334,29 +333,6 @@ def try_to_restart():
|
|||
except AssertionError:
|
||||
sys.stderr.write("Failed to count non-daemonic threads.\n")
|
||||
|
||||
# Send terminate signal to all processes in our process group which
|
||||
# should be any children that have not themselves changed the process
|
||||
# group id. Don't bother if couldn't even call setpgid.
|
||||
if hasattr(os, 'setpgid'):
|
||||
sys.stderr.write("Signalling child processes to terminate...\n")
|
||||
os.kill(0, signal.SIGTERM)
|
||||
|
||||
# wait for child processes to terminate
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
if os.waitpid(0, os.WNOHANG) == (0, 0):
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
elif os.name == 'nt':
|
||||
# Maybe one of the following will work, but how do we indicate which
|
||||
# processes are our children if there is no process group?
|
||||
# os.kill(0, signal.CTRL_C_EVENT)
|
||||
# os.kill(0, signal.CTRL_BREAK_EVENT)
|
||||
pass
|
||||
|
||||
# Try to not leave behind open filedescriptors with the emphasis on try.
|
||||
try:
|
||||
max_fd = os.sysconf("SC_OPEN_MAX")
|
||||
|
@ -408,13 +384,6 @@ def main():
|
|||
if args.pid_file:
|
||||
write_pid(args.pid_file)
|
||||
|
||||
# Create new process group if we can
|
||||
if hasattr(os, 'setpgid'):
|
||||
try:
|
||||
os.setpgid(0, 0)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
exit_code = setup_and_run_hass(config_dir, args)
|
||||
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
||||
try_to_restart()
|
||||
|
|
|
@ -113,14 +113,13 @@ class APIEventStream(HomeAssistantView):
|
|||
|
||||
while True:
|
||||
try:
|
||||
# Somehow our queue.get takes too long to
|
||||
# be notified of arrival of object. Probably
|
||||
# Somehow our queue.get sometimes takes too long to
|
||||
# be notified of arrival of data. Probably
|
||||
# because of our spawning on hub in other thread
|
||||
# hack. Because current goal is to get this out,
|
||||
# We just timeout every second because it will
|
||||
# return right away if qsize() > 0.
|
||||
# So yes, we're basically polling :(
|
||||
# socket.io anyone?
|
||||
payload = to_write.get(timeout=1)
|
||||
|
||||
if payload is stop_obj:
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
{
|
||||
"src": "/static/favicon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/favicon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.helpers.entity import split_entity_id
|
|||
import homeassistant.util.dt as dt_util
|
||||
|
||||
DOMAIN = "http"
|
||||
REQUIREMENTS = ("eventlet==0.18.4", "static3==0.7.0", "Werkzeug==0.11.5",)
|
||||
REQUIREMENTS = ("eventlet==0.19.0", "static3==0.7.0", "Werkzeug==0.11.5",)
|
||||
|
||||
CONF_API_PASSWORD = "api_password"
|
||||
CONF_SERVER_HOST = "server_host"
|
||||
|
@ -31,8 +31,29 @@ _FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HideSensitiveFilter(logging.Filter):
|
||||
"""Filter API password calls."""
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, hass):
|
||||
"""Initialize sensitive data filter."""
|
||||
super().__init__()
|
||||
self.hass = hass
|
||||
|
||||
def filter(self, record):
|
||||
"""Hide sensitive data in messages."""
|
||||
if self.hass.wsgi.api_password is None:
|
||||
return True
|
||||
|
||||
record.msg = record.msg.replace(self.hass.wsgi.api_password, '*******')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the HTTP API and debug interface."""
|
||||
_LOGGER.addFilter(HideSensitiveFilter(hass))
|
||||
|
||||
conf = config.get(DOMAIN, {})
|
||||
|
||||
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
|
||||
|
@ -202,7 +223,7 @@ class HomeAssistantWSGI(object):
|
|||
"""Register a redirect with the server.
|
||||
|
||||
If given this must be either a string or callable. In case of a
|
||||
callable it’s called with the url adapter that triggered the match and
|
||||
callable it's called with the url adapter that triggered the match and
|
||||
the values of the URL as keyword arguments and has to return the target
|
||||
for the redirect, otherwise it has to be a string with placeholders in
|
||||
rule syntax.
|
||||
|
@ -243,9 +264,9 @@ class HomeAssistantWSGI(object):
|
|||
|
||||
sock = eventlet.listen((self.server_host, self.server_port))
|
||||
if self.ssl_certificate:
|
||||
eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
|
||||
keyfile=self.ssl_key, server_side=True)
|
||||
wsgi.server(sock, self)
|
||||
sock = eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
|
||||
keyfile=self.ssl_key, server_side=True)
|
||||
wsgi.server(sock, self, log=_LOGGER)
|
||||
|
||||
def dispatch_request(self, request):
|
||||
"""Handle incoming request."""
|
||||
|
@ -318,9 +339,7 @@ class HomeAssistantView(object):
|
|||
|
||||
def handle_request(self, request, **values):
|
||||
"""Handle request to url."""
|
||||
from werkzeug.exceptions import (
|
||||
MethodNotAllowed, Unauthorized, BadRequest,
|
||||
)
|
||||
from werkzeug.exceptions import MethodNotAllowed, Unauthorized
|
||||
|
||||
try:
|
||||
handler = getattr(self, request.method.lower())
|
||||
|
@ -342,18 +361,6 @@ class HomeAssistantView(object):
|
|||
self.hass.wsgi.api_password):
|
||||
authenticated = True
|
||||
|
||||
else:
|
||||
# Do we still want to support passing it in as post data?
|
||||
try:
|
||||
json_data = request.json
|
||||
if (json_data is not None and
|
||||
hmac.compare_digest(
|
||||
json_data.get(DATA_API_PASSWORD, ''),
|
||||
self.hass.wsgi.api_password)):
|
||||
authenticated = True
|
||||
except BadRequest:
|
||||
pass
|
||||
|
||||
if self.requires_auth and not authenticated:
|
||||
raise Unauthorized()
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ from homeassistant.helpers.entity import ToggleEntity
|
|||
from homeassistant.loader import get_component
|
||||
|
||||
DOMAIN = "isy994"
|
||||
REQUIREMENTS = ['PyISY==1.0.5']
|
||||
REQUIREMENTS = ['PyISY==1.0.6']
|
||||
DISCOVER_LIGHTS = "isy994.lights"
|
||||
DISCOVER_SWITCHES = "isy994.switches"
|
||||
DISCOVER_SENSORS = "isy994.sensors"
|
||||
|
|
|
@ -37,7 +37,8 @@ PLATFORM_SCHEMA = vol.Schema({
|
|||
vol.Required(CONF_PLATFORM): "lg_netcast",
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_ACCESS_TOKEN): vol.All(cv.string, vol.Length(max=6)),
|
||||
vol.Optional(CONF_ACCESS_TOKEN, default=None):
|
||||
vol.All(cv.string, vol.Length(max=6)),
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -136,11 +136,12 @@ class GoogleTravelTimeSensor(Entity):
|
|||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
try:
|
||||
res = self._matrix['rows'][0]['elements'][0]['duration']['value']
|
||||
return round(res/60)
|
||||
except KeyError:
|
||||
return None
|
||||
_data = self._matrix['rows'][0]['elements'][0]
|
||||
if 'duration_in_traffic' in _data:
|
||||
return round(_data['duration_in_traffic']['value']/60)
|
||||
if 'duration' in _data:
|
||||
return round(_data['duration']['value']/60)
|
||||
return None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -175,9 +176,15 @@ class GoogleTravelTimeSensor(Entity):
|
|||
atime = options_copy.get('arrival_time')
|
||||
if dtime is not None and ':' in dtime:
|
||||
options_copy['departure_time'] = convert_time_to_utc(dtime)
|
||||
elif dtime is not None:
|
||||
options_copy['departure_time'] = dtime
|
||||
else:
|
||||
options_copy['departure_time'] = 'now'
|
||||
|
||||
if atime is not None and ':' in atime:
|
||||
options_copy['arrival_time'] = convert_time_to_utc(atime)
|
||||
elif atime is not None:
|
||||
options_copy['arrival_time'] = atime
|
||||
|
||||
self._matrix = self._client.distance_matrix(self._origin,
|
||||
self._destination,
|
||||
|
|
|
@ -93,7 +93,11 @@ class OctoPrintSensor(Entity):
|
|||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
sensor_unit = self.unit_of_measurement
|
||||
if sensor_unit == TEMP_CELSIUS or sensor_unit == "%":
|
||||
return round(self._state, 2)
|
||||
else:
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.util import dt as dt_util
|
|||
from homeassistant.util import location as location_util
|
||||
from homeassistant.const import CONF_ELEVATION
|
||||
|
||||
REQUIREMENTS = ['astral==1.0']
|
||||
REQUIREMENTS = ['astral==1.1']
|
||||
DOMAIN = "sun"
|
||||
ENTITY_ID = "sun.sun"
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ voluptuous==0.8.9
|
|||
webcolors==1.5
|
||||
|
||||
# homeassistant.components.isy994
|
||||
PyISY==1.0.5
|
||||
PyISY==1.0.6
|
||||
|
||||
# homeassistant.components.arduino
|
||||
PyMata==2.12
|
||||
|
@ -30,7 +30,7 @@ Werkzeug==0.11.5
|
|||
apcaccess==0.0.4
|
||||
|
||||
# homeassistant.components.sun
|
||||
astral==1.0
|
||||
astral==1.1
|
||||
|
||||
# homeassistant.components.light.blinksticklight
|
||||
blinkstick==1.1.7
|
||||
|
@ -57,7 +57,7 @@ dweepy==0.2.0
|
|||
eliqonline==1.0.12
|
||||
|
||||
# homeassistant.components.http
|
||||
eventlet==0.18.4
|
||||
eventlet==0.19.0
|
||||
|
||||
# homeassistant.components.thermostat.honeywell
|
||||
evohomeclient==0.2.5
|
||||
|
|
|
@ -4,5 +4,6 @@ coveralls>=1.1
|
|||
pytest>=2.9.1
|
||||
pytest-cov>=2.2.0
|
||||
pytest-timeout>=1.0.0
|
||||
pytest-capturelog>=0.7
|
||||
betamax==0.5.1
|
||||
pydocstyle>=1.0.0
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""The tests for the Home Assistant HTTP component."""
|
||||
"""The tests for the Home Assistant API component."""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
# from contextlib import closing
|
||||
import json
|
||||
|
@ -66,28 +66,6 @@ class TestAPI(unittest.TestCase):
|
|||
"""Stop everything that was started."""
|
||||
hass.pool.block_till_done()
|
||||
|
||||
# TODO move back to http component and test with use_auth.
|
||||
def test_access_denied_without_password(self):
|
||||
"""Test access without password."""
|
||||
req = requests.get(_url(const.URL_API))
|
||||
|
||||
self.assertEqual(401, req.status_code)
|
||||
|
||||
def test_access_denied_with_wrong_password(self):
|
||||
"""Test ascces with wrong password."""
|
||||
req = requests.get(
|
||||
_url(const.URL_API),
|
||||
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
|
||||
|
||||
self.assertEqual(401, req.status_code)
|
||||
|
||||
def test_access_with_password_in_url(self):
|
||||
"""Test access with password in URL."""
|
||||
req = requests.get(
|
||||
"{}?api_password={}".format(_url(const.URL_API), API_PASSWORD))
|
||||
|
||||
self.assertEqual(200, req.status_code)
|
||||
|
||||
def test_api_list_state_entities(self):
|
||||
"""Test if the debug interface allows us to list state entities."""
|
||||
req = requests.get(_url(const.URL_API_STATES),
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
"""The tests for the Home Assistant HTTP component."""
|
||||
# pylint: disable=protected-access,too-many-public-methods
|
||||
import logging
|
||||
|
||||
import eventlet
|
||||
import requests
|
||||
|
||||
from homeassistant import bootstrap, const
|
||||
import homeassistant.components.http as http
|
||||
|
||||
from tests.common import get_test_instance_port, get_test_home_assistant
|
||||
|
||||
API_PASSWORD = "test1234"
|
||||
SERVER_PORT = get_test_instance_port()
|
||||
HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
|
||||
HA_HEADERS = {
|
||||
const.HTTP_HEADER_HA_AUTH: API_PASSWORD,
|
||||
const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON,
|
||||
}
|
||||
|
||||
hass = None
|
||||
|
||||
|
||||
def _url(path=""):
|
||||
"""Helper method to generate URLs."""
|
||||
return HTTP_BASE_URL + path
|
||||
|
||||
|
||||
def setUpModule(): # pylint: disable=invalid-name
|
||||
"""Initialize a Home Assistant server."""
|
||||
global hass
|
||||
|
||||
hass = get_test_home_assistant()
|
||||
|
||||
hass.bus.listen('test_event', lambda _: _)
|
||||
hass.states.set('test.test', 'a_state')
|
||||
|
||||
bootstrap.setup_component(
|
||||
hass, http.DOMAIN,
|
||||
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
|
||||
http.CONF_SERVER_PORT: SERVER_PORT}})
|
||||
|
||||
bootstrap.setup_component(hass, 'api')
|
||||
|
||||
hass.start()
|
||||
|
||||
eventlet.sleep(0.05)
|
||||
|
||||
|
||||
def tearDownModule(): # pylint: disable=invalid-name
|
||||
"""Stop the Home Assistant server."""
|
||||
hass.stop()
|
||||
|
||||
|
||||
class TestHttp:
|
||||
"""Test HTTP component."""
|
||||
|
||||
def test_access_denied_without_password(self):
|
||||
"""Test access without password."""
|
||||
req = requests.get(_url(const.URL_API))
|
||||
|
||||
assert req.status_code == 401
|
||||
|
||||
def test_access_denied_with_wrong_password_in_header(self):
|
||||
"""Test ascces with wrong password."""
|
||||
req = requests.get(
|
||||
_url(const.URL_API),
|
||||
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
|
||||
|
||||
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
|
||||
caplog.setLevel(logging.WARNING,
|
||||
logger='requests.packages.urllib3.connectionpool')
|
||||
|
||||
req = requests.get(
|
||||
_url(const.URL_API),
|
||||
headers={const.HTTP_HEADER_HA_AUTH: API_PASSWORD})
|
||||
|
||||
assert req.status_code == 200
|
||||
|
||||
logs = caplog.text()
|
||||
|
||||
assert const.URL_API in logs
|
||||
assert API_PASSWORD not in logs
|
||||
|
||||
def test_access_denied_with_wrong_password_in_url(self):
|
||||
"""Test ascces with wrong password."""
|
||||
req = requests.get(_url(const.URL_API),
|
||||
params={'api_password': 'wrongpassword'})
|
||||
|
||||
assert req.status_code == 401
|
||||
|
||||
def test_access_with_password_in_url(self, caplog):
|
||||
"""Test access with password in URL."""
|
||||
# Hide logging from requests package that we use to test logging
|
||||
caplog.setLevel(logging.WARNING,
|
||||
logger='requests.packages.urllib3.connectionpool')
|
||||
|
||||
req = requests.get(_url(const.URL_API),
|
||||
params={'api_password': API_PASSWORD})
|
||||
|
||||
assert req.status_code == 200
|
||||
|
||||
logs = caplog.text()
|
||||
|
||||
assert const.URL_API in logs
|
||||
assert API_PASSWORD not in logs
|
Loading…
Reference in New Issue