Merge pull request #706 from balloob/dev

0.9.1
This commit is contained in:
Paulus Schoutsen 2015-12-06 21:18:57 -08:00
commit 2c157e6885
10 changed files with 61 additions and 34 deletions

View File

@ -52,7 +52,7 @@ omit =
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/ifttt.py
homeassistant/components/influx.py
homeassistant/components/influxdb.py
homeassistant/components/keyboard.py
homeassistant/components/light/hue.py
homeassistant/components/light/mqtt.py

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "8470cd10f28b20eae9022fa4c8f40c1b"
VERSION = "d07b7ed1734ae3f2472f9ae88e0c3dea"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit feb776ec89d6872dad2203b352cc6d652c46356d
Subproject commit 044a6d9810b6aa662b82af0f67deb662725ad4cb

View File

@ -6,16 +6,17 @@ This module provides an API and a HTTP interface for debug purposes.
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api/
"""
import json
import threading
import logging
import time
import gzip
import os
from datetime import timedelta
from http.server import SimpleHTTPRequestHandler, HTTPServer
import gzip
from http import cookies
from http.server import SimpleHTTPRequestHandler, HTTPServer
import json
import logging
import os
from socketserver import ThreadingMixIn
import ssl
import threading
import time
from urllib.parse import urlparse, parse_qs
import homeassistant.core as ha
@ -36,6 +37,8 @@ CONF_API_PASSWORD = "api_password"
CONF_SERVER_HOST = "server_host"
CONF_SERVER_PORT = "server_port"
CONF_DEVELOPMENT = "development"
CONF_SSL_CERTIFICATE = 'ssl_certificate'
CONF_SSL_KEY = 'ssl_key'
DATA_API_PASSWORD = 'api_password'
@ -57,11 +60,13 @@ def setup(hass, config):
server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
ssl_key = conf.get(CONF_SSL_KEY)
try:
server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password,
development)
development, ssl_certificate, ssl_key)
except OSError:
# If address already in use
_LOGGER.exception("Error setting up HTTP server")
@ -73,7 +78,8 @@ def setup(hass, config):
threading.Thread(target=server.start, daemon=True).start())
hass.http = server
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port)
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
ssl_certificate is not None)
return True
@ -88,7 +94,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
# pylint: disable=too-many-arguments
def __init__(self, server_address, request_handler_class,
hass, api_password, development):
hass, api_password, development, ssl_certificate, ssl_key):
super().__init__(server_address, request_handler_class)
self.server_address = server_address
@ -97,6 +103,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.development = development
self.paths = []
self.sessions = SessionStore()
self.use_ssl = ssl_certificate is not None
# We will lazy init this one if needed
self.event_forwarder = None
@ -104,6 +111,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
if development:
_LOGGER.info("running http in development mode")
if ssl_certificate is not None:
wrap_kwargs = {'certfile': ssl_certificate}
if ssl_key is not None:
wrap_kwargs['keyfile'] = ssl_key
self.socket = ssl.wrap_socket(self.socket, **wrap_kwargs)
def start(self):
""" Starts the HTTP server. """
def stop_http(event):
@ -112,8 +125,11 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
protocol = 'https' if self.use_ssl else 'http'
_LOGGER.info(
"Starting web interface at http://%s:%d", *self.server_address)
"Starting web interface at %s://%s:%d",
protocol, self.server_address[0], self.server_address[1])
# 31-1-2015: Refactored frontend/api components out of this component
# To prevent stuff from breaking, load the two extracted components
@ -186,17 +202,12 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return
if self.server.api_password is None:
self.authenticated = True
elif HTTP_HEADER_HA_AUTH in self.headers:
api_password = self.headers.get(HTTP_HEADER_HA_AUTH)
if not api_password and DATA_API_PASSWORD in data:
api_password = data[DATA_API_PASSWORD]
self.authenticated = api_password == self.server.api_password
else:
self.authenticated = self.verify_session()
self.authenticated = (self.server.api_password is None
or self.headers.get(HTTP_HEADER_HA_AUTH) ==
self.server.api_password
or data.get(DATA_API_PASSWORD) ==
self.server.api_password
or self.verify_session())
if '_METHOD' in data:
method = data.pop('_METHOD')

View File

@ -1,7 +1,7 @@
# coding: utf-8
""" Constants used by Home Assistant components. """
__version__ = "0.9.0"
__version__ = "0.9.1"
# Can be used to specify a catch all when registering state or event listeners.
MATCH_ALL = '*'

View File

@ -51,11 +51,14 @@ class API(object):
""" Object to pass around Home Assistant API location and credentials. """
# pylint: disable=too-few-public-methods
def __init__(self, host, api_password=None, port=None):
def __init__(self, host, api_password=None, port=None, use_ssl=False):
self.host = host
self.port = port or SERVER_PORT
self.api_password = api_password
self.base_url = "http://{}:{}".format(host, self.port)
if use_ssl:
self.base_url = "https://{}:{}".format(host, self.port)
else:
self.base_url = "http://{}:{}".format(host, self.port)
self.status = None
self._headers = {}

View File

@ -26,7 +26,7 @@ https://github.com/nkgilley/python-ecobee-api/archive/92a2f330cbaf601d0618456fdd
# homeassistant.components.ifttt
pyfttt==0.3
# homeassistant.components.influx
# homeassistant.components.influxdb
influxdb==2.10.0
# homeassistant.components.isy994

View File

@ -66,18 +66,31 @@ class TestAPI(unittest.TestCase):
# TODO move back to http component and test with use_auth.
def test_access_denied_without_password(self):
req = requests.get(
_url(const.URL_API_STATES_ENTITY.format("test")))
req = requests.get(_url(const.URL_API))
self.assertEqual(401, req.status_code)
def test_access_denied_with_wrong_password(self):
req = requests.get(
_url(const.URL_API_STATES_ENTITY.format("test")),
_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):
req = requests.get(
"{}?api_password={}".format(_url(const.URL_API), API_PASSWORD))
self.assertEqual(200, req.status_code)
def test_access_via_session(self):
session = requests.Session()
req = session.get(_url(const.URL_API), headers=HA_HEADERS)
self.assertEqual(200, req.status_code)
req = session.get(_url(const.URL_API))
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),