diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index b7eaa4dfd60..3f488b0f9ff 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -16,18 +16,23 @@ from http import cookies from http.server import HTTPServer, SimpleHTTPRequestHandler from socketserver import ThreadingMixIn from urllib.parse import parse_qs, urlparse +import voluptuous as vol import homeassistant.bootstrap as bootstrap import homeassistant.core as ha import homeassistant.remote as rem import homeassistant.util as util import homeassistant.util.dt as date_util +import homeassistant.helpers.config_validation as cv from homeassistant.const import ( CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN, HTTP_HEADER_ACCEPT_ENCODING, HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_EXPIRES, - HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY, HTTP_METHOD_NOT_ALLOWED, + HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY, + HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, HTTP_METHOD_NOT_ALLOWED, HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY, + ALLOWED_CORS_HEADERS, SERVER_PORT, URL_ROOT, URL_API_EVENT_FORWARD) DOMAIN = "http" @@ -38,6 +43,7 @@ CONF_SERVER_PORT = "server_port" CONF_DEVELOPMENT = "development" CONF_SSL_CERTIFICATE = 'ssl_certificate' CONF_SSL_KEY = 'ssl_key' +CONF_CORS_ORIGINS = 'cors_allowed_origins' DATA_API_PASSWORD = 'api_password' @@ -48,6 +54,19 @@ SESSION_KEY = 'sessionId' _LOGGER = logging.getLogger(__name__) +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_API_PASSWORD): cv.string, + vol.Optional(CONF_SERVER_HOST): cv.string, + vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT): + vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), + 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 + }), +}, extra=vol.ALLOW_EXTRA) + def setup(hass, config): """Set up the HTTP API and debug interface.""" @@ -61,11 +80,12 @@ def setup(hass, config): development = str(conf.get(CONF_DEVELOPMENT, "")) == "1" ssl_certificate = conf.get(CONF_SSL_CERTIFICATE) ssl_key = conf.get(CONF_SSL_KEY) + cors_origins = conf.get(CONF_CORS_ORIGINS, []) try: server = HomeAssistantHTTPServer( (server_host, server_port), RequestHandler, hass, api_password, - development, ssl_certificate, ssl_key) + development, ssl_certificate, ssl_key, cors_origins) except OSError: # If address already in use _LOGGER.exception("Error setting up HTTP server") @@ -96,7 +116,8 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): # pylint: disable=too-many-arguments def __init__(self, server_address, request_handler_class, - hass, api_password, development, ssl_certificate, ssl_key): + hass, api_password, development, ssl_certificate, ssl_key, + cors_origins): """Initialize the server.""" super().__init__(server_address, request_handler_class) @@ -107,6 +128,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): self.paths = [] self.sessions = SessionStore() self.use_ssl = ssl_certificate is not None + self.cors_origins = cors_origins # We will lazy init this one if needed self.event_forwarder = None @@ -351,6 +373,16 @@ class RequestHandler(SimpleHTTPRequestHandler): self.send_header(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT_ENCODING) self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(content))) + + cors_check = (self.headers.get("Origin") in self.server.cors_origins) + + cors_headers = ", ".join(ALLOWED_CORS_HEADERS) + + if self.server.cors_origins and cors_check: + self.send_header(HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + self.headers.get("Origin")) + self.send_header(HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, + cors_headers) self.end_headers() if self.command == 'HEAD': diff --git a/homeassistant/const.py b/homeassistant/const.py index e0ca1fcfb1c..934c6aa3a7b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -229,6 +229,15 @@ HTTP_HEADER_VARY = "Vary" HTTP_HEADER_CONTENT_LENGTH = "Content-Length" HTTP_HEADER_CACHE_CONTROL = "Cache-Control" HTTP_HEADER_EXPIRES = "Expires" +HTTP_HEADER_ORIGIN = "Origin" +HTTP_HEADER_X_REQUESTED_WITH = "X-Requested-With" +HTTP_HEADER_ACCEPT = "Accept" +HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin" +HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers" + +ALLOWED_CORS_HEADERS = [HTTP_HEADER_ORIGIN, HTTP_HEADER_ACCEPT, + HTTP_HEADER_X_REQUESTED_WITH, HTTP_HEADER_CONTENT_TYPE, + HTTP_HEADER_HA_AUTH] CONTENT_TYPE_JSON = "application/json" CONTENT_TYPE_MULTIPART = 'multipart/x-mixed-replace; boundary={}'