Add url validators

This commit is contained in:
Paulus Schoutsen 2016-05-14 21:18:46 -07:00
parent 4d7555957c
commit 5aa0158761
5 changed files with 92 additions and 31 deletions

View File

@ -214,7 +214,7 @@ class APIStatesView(HomeAssistantView):
class APIEntityStateView(HomeAssistantView): class APIEntityStateView(HomeAssistantView):
"""View to handle EntityState requests.""" """View to handle EntityState requests."""
url = "/api/states/<entity_id>" url = "/api/states/<entity(exist=False):entity_id>"
name = "api:entity-state" name = "api:entity-state"
def get(self, request, entity_id): def get(self, request, entity_id):

View File

@ -147,7 +147,7 @@ class CameraView(HomeAssistantView):
class CameraImageView(CameraView): class CameraImageView(CameraView):
"""Camera view to serve an image.""" """Camera view to serve an image."""
url = "/api/camera_proxy/<entity_id>" url = "/api/camera_proxy/<entity(domain=camera):entity_id>"
name = "api:camera:image" name = "api:camera:image"
def get(self, request, entity_id): def get(self, request, entity_id):
@ -168,7 +168,7 @@ class CameraImageView(CameraView):
class CameraMjpegStream(CameraView): class CameraMjpegStream(CameraView):
"""Camera View to serve an MJPEG stream.""" """Camera View to serve an MJPEG stream."""
url = "/api/camera_proxy_stream/<entity_id>" url = "/api/camera_proxy_stream/<entity(domain=camera):entity_id>"
name = "api:camera:stream" name = "api:camera:stream"
def get(self, request, entity_id): def get(self, request, entity_id):

View File

@ -165,7 +165,7 @@ def setup(hass, config):
class Last5StatesView(HomeAssistantView): class Last5StatesView(HomeAssistantView):
"""Handle last 5 state view requests.""" """Handle last 5 state view requests."""
url = '/api/history/entity/<entity_id>/recent_states' url = '/api/history/entity/<entity:entity_id>/recent_states'
name = 'api:history:entity-recent-states' name = 'api:history:entity-recent-states'
def get(self, request, entity_id): def get(self, request, entity_id):
@ -178,7 +178,7 @@ class HistoryPeriodView(HomeAssistantView):
url = '/api/history/period' url = '/api/history/period'
name = 'api:history:entity-recent-states' name = 'api:history:entity-recent-states'
extra_urls = ['/api/history/period/<date>'] extra_urls = ['/api/history/period/<date:date>']
def get(self, request, date=None): def get(self, request, date=None):
"""Return history over a period of time.""" """Return history over a period of time."""

View File

@ -10,6 +10,8 @@ import homeassistant.core as ha
import homeassistant.remote as rem import homeassistant.remote as rem
from homeassistant import util from homeassistant import util
from homeassistant.const import SERVER_PORT, HTTP_HEADER_HA_AUTH from homeassistant.const import SERVER_PORT, HTTP_HEADER_HA_AUTH
from homeassistant.helpers.entity import valid_entity_id, split_entity_id
import homeassistant.util.dt as dt_util
DOMAIN = "http" DOMAIN = "http"
REQUIREMENTS = ("eventlet==0.18.4", "static3==0.6.1", "Werkzeug==0.11.5",) REQUIREMENTS = ("eventlet==0.18.4", "static3==0.6.1", "Werkzeug==0.11.5",)
@ -77,20 +79,13 @@ def setup(hass, config):
# return app(environ, start_response) # return app(environ, start_response)
class HomeAssistantWSGI(object): def request_class():
"""WSGI server for Home Assistant.""" """Generate request class.
# pylint: disable=too-many-instance-attributes, too-many-locals Done in method because of imports."""
# pylint: disable=too-many-arguments
def __init__(self, hass, development, api_password, ssl_certificate,
ssl_key, server_host, server_port):
"""Initilalize the WSGI Home Assistant server."""
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.wrappers import BaseRequest, AcceptMixin from werkzeug.wrappers import BaseRequest, AcceptMixin
from werkzeug.routing import Map
from werkzeug.utils import cached_property from werkzeug.utils import cached_property
from werkzeug.wrappers import Response
class Request(BaseRequest, AcceptMixin): class Request(BaseRequest, AcceptMixin):
"""Base class for incoming requests.""" """Base class for incoming requests."""
@ -108,11 +103,77 @@ class HomeAssistantWSGI(object):
except (TypeError, ValueError): except (TypeError, ValueError):
raise BadRequest('Unable to read JSON request') raise BadRequest('Unable to read JSON request')
return Request
def routing_map(hass):
"""Generate empty routing map with HA validators."""
from werkzeug.routing import Map, BaseConverter, ValidationError
class EntityValidator(BaseConverter):
"""Validate entity_id in urls."""
regex = r"(\w+)\.(\w+)"
def __init__(self, url_map, exist=True, domain=None):
"""Initilalize entity validator."""
super().__init__(url_map)
self._exist = exist
self._domain = domain
def to_python(self, value):
"""Validate entity id."""
if self._exist and hass.states.get(value) is None:
raise ValidationError()
if self._domain is not None and \
split_entity_id(value)[0] != self._domain:
raise ValidationError()
return value
def to_url(self, value):
"""Convert entity_id for a url."""
return value
class DateValidator(BaseConverter):
"""Validate dates in urls."""
regex = r'\d{4}-(0[1-9])|(1[012])-((0[1-9])|([12]\d)|(3[01]))'
def to_python(self, value):
"""Validate and convert date."""
parsed = dt_util.parse_date(value)
if value is None:
raise ValidationError()
return parsed
def to_url(self, value):
"""Convert date to url value."""
return value.isoformat()
return Map(converters={
'entity': EntityValidator,
'date': DateValidator,
})
class HomeAssistantWSGI(object):
"""WSGI server for Home Assistant."""
# pylint: disable=too-many-instance-attributes, too-many-locals
# pylint: disable=too-many-arguments
def __init__(self, hass, development, api_password, ssl_certificate,
ssl_key, server_host, server_port):
"""Initilalize the WSGI Home Assistant server."""
from werkzeug.wrappers import Response
Response.mimetype = 'text/html' Response.mimetype = 'text/html'
# pylint: disable=invalid-name # pylint: disable=invalid-name
self.Request = Request self.Request = request_class()
self.url_map = Map() self.url_map = routing_map(hass)
self.views = {} self.views = {}
self.hass = hass self.hass = hass
self.extra_apps = {} self.extra_apps = {}
@ -340,13 +401,13 @@ class HomeAssistantView(object):
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
if isinstance(fil, str): if isinstance(fil, str):
if mimetype is None:
mimetype = mimetypes.guess_type(fil)[0]
try: try:
fil = open(fil) fil = open(fil)
except IOError: except IOError:
raise NotFound() raise NotFound()
if mimetype is None:
mimetype = mimetypes.guess_type(fil)[0]
return self.Response(wrap_file(request.environ, fil), return self.Response(wrap_file(request.environ, fil),
mimetype=mimetype, direct_passthrough=True) mimetype=mimetype, direct_passthrough=True)

View File

@ -89,7 +89,7 @@ class LogbookView(HomeAssistantView):
url = '/api/logbook' url = '/api/logbook'
name = 'api:logbook' name = 'api:logbook'
extra_urls = ['/api/logbook/<date>'] extra_urls = ['/api/logbook/<date:date>']
def get(self, request, date=None): def get(self, request, date=None):
"""Retrieve logbook entries.""" """Retrieve logbook entries."""