Skip to content

Commit

Permalink
Token tweaks (home-assistant#5599)
Browse files Browse the repository at this point in the history
* Base status code on auth when entity not found

* Also allow previous camera token

* Fix tests

* Address comments
  • Loading branch information
balloob authored Jan 28, 2017
1 parent e1412a2 commit b0d07a4
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 13 deletions.
41 changes: 30 additions & 11 deletions homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@
https://home-assistant.io/components/camera/
"""
import asyncio
import collections
from datetime import timedelta
import logging
import hashlib
from random import SystemRandom

import aiohttp
from aiohttp import web
import async_timeout

from homeassistant.core import callback
from homeassistant.const import ATTR_ENTITY_PICTURE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED
from homeassistant.helpers.event import async_track_time_interval

_LOGGER = logging.getLogger(__name__)

Expand All @@ -35,6 +39,9 @@

ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'

TOKEN_CHANGE_INTERVAL = timedelta(minutes=5)
_RND = SystemRandom()


@asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10):
Expand Down Expand Up @@ -80,6 +87,15 @@ def async_setup(hass, config):
hass.http.register_view(CameraMjpegStream(component.entities))

yield from component.async_setup(config)

@callback
def update_tokens(time):
"""Update tokens of the entities."""
for entity in component.entities.values():
entity.async_update_token()
hass.async_add_job(entity.async_update_ha_state())

async_track_time_interval(hass, update_tokens, TOKEN_CHANGE_INTERVAL)
return True


Expand All @@ -89,13 +105,8 @@ class Camera(Entity):
def __init__(self):
"""Initialize a camera."""
self.is_streaming = False
self._access_token = hashlib.sha256(
str.encode(str(id(self)))).hexdigest()

@property
def access_token(self):
"""Access token for this camera."""
return self._access_token
self.access_tokens = collections.deque([], 2)
self.async_update_token()

@property
def should_poll(self):
Expand All @@ -105,7 +116,7 @@ def should_poll(self):
@property
def entity_picture(self):
"""Return a link to the camera feed as entity picture."""
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_token)
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_tokens[-1])

@property
def is_recording(self):
Expand Down Expand Up @@ -196,7 +207,7 @@ def state(self):
def state_attributes(self):
"""Camera state attributes."""
attr = {
'access_token': self.access_token,
'access_token': self.access_tokens[-1],
}

if self.model:
Expand All @@ -207,6 +218,13 @@ def state_attributes(self):

return attr

@callback
def async_update_token(self):
"""Update the used token."""
self.access_tokens.append(
hashlib.sha256(
_RND.getrandbits(256).to_bytes(32, 'little')).hexdigest())


class CameraView(HomeAssistantView):
"""Base CameraView."""
Expand All @@ -223,10 +241,11 @@ def get(self, request, entity_id):
camera = self.entities.get(entity_id)

if camera is None:
return web.Response(status=404)
status = 404 if request[KEY_AUTHENTICATED] else 401
return web.Response(status=status)

authenticated = (request[KEY_AUTHENTICATED] or
request.GET.get('token') == camera.access_token)
request.GET.get('token') in camera.access_tokens)

if not authenticated:
return web.Response(status=401)
Expand Down
12 changes: 10 additions & 2 deletions homeassistant/components/media_player/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import hashlib
import logging
import os
from random import SystemRandom

from aiohttp import web
import async_timeout
Expand All @@ -32,6 +33,7 @@
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)

_LOGGER = logging.getLogger(__name__)
_RND = SystemRandom()

DOMAIN = 'media_player'
DEPENDENCIES = ['http']
Expand Down Expand Up @@ -389,6 +391,8 @@ def async_service_handler(service):
class MediaPlayerDevice(Entity):
"""ABC for media player devices."""

_access_token = None

# pylint: disable=no-self-use
# Implement these for your media player
@property
Expand All @@ -399,7 +403,10 @@ def state(self):
@property
def access_token(self):
"""Access token for this media player."""
return str(id(self))
if self._access_token is None:
self._access_token = hashlib.sha256(
_RND.getrandbits(256).to_bytes(32, 'little')).hexdigest()
return self._access_token

@property
def volume_level(self):
Expand Down Expand Up @@ -895,7 +902,8 @@ def get(self, request, entity_id):
"""Start a get request."""
player = self.entities.get(entity_id)
if player is None:
return web.Response(status=404)
status = 404 if request[KEY_AUTHENTICATED] else 401
return web.Response(status=status)

authenticated = (request[KEY_AUTHENTICATED] or
request.GET.get('token') == player.access_token)
Expand Down

0 comments on commit b0d07a4

Please sign in to comment.