mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Replace api_password in Camera.Push (#16339)
* Use access_token and user provided token instead of api_password * address comments by @awarecan * new tests * add extra checks and test * lint * add comment
This commit is contained in:
parent
6b08e6e769
commit
20f6cb7cc7
@ -13,8 +13,10 @@ import voluptuous as vol
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\
|
||||
STATE_IDLE, STATE_RECORDING
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.http.view import HomeAssistantView
|
||||
from homeassistant.const import CONF_NAME, CONF_TIMEOUT, HTTP_BAD_REQUEST
|
||||
from homeassistant.components.http.view import KEY_AUTHENTICATED,\
|
||||
HomeAssistantView
|
||||
from homeassistant.const import CONF_NAME, CONF_TIMEOUT,\
|
||||
HTTP_NOT_FOUND, HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
import homeassistant.util.dt as dt_util
|
||||
@ -25,11 +27,13 @@ DEPENDENCIES = ['http']
|
||||
|
||||
CONF_BUFFER_SIZE = 'buffer'
|
||||
CONF_IMAGE_FIELD = 'field'
|
||||
CONF_TOKEN = 'token'
|
||||
|
||||
DEFAULT_NAME = "Push Camera"
|
||||
|
||||
ATTR_FILENAME = 'filename'
|
||||
ATTR_LAST_TRIP = 'last_trip'
|
||||
ATTR_TOKEN = 'token'
|
||||
|
||||
PUSH_CAMERA_DATA = 'push_camera'
|
||||
|
||||
@ -39,6 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_TIMEOUT, default=timedelta(seconds=5)): vol.All(
|
||||
cv.time_period, cv.positive_timedelta),
|
||||
vol.Optional(CONF_IMAGE_FIELD, default='image'): cv.string,
|
||||
vol.Optional(CONF_TOKEN): vol.All(cv.string, vol.Length(min=8)),
|
||||
})
|
||||
|
||||
|
||||
@ -50,7 +55,8 @@ async def async_setup_platform(hass, config, async_add_entities,
|
||||
|
||||
cameras = [PushCamera(config[CONF_NAME],
|
||||
config[CONF_BUFFER_SIZE],
|
||||
config[CONF_TIMEOUT])]
|
||||
config[CONF_TIMEOUT],
|
||||
config.get(CONF_TOKEN))]
|
||||
|
||||
hass.http.register_view(CameraPushReceiver(hass,
|
||||
config[CONF_IMAGE_FIELD]))
|
||||
@ -63,6 +69,7 @@ class CameraPushReceiver(HomeAssistantView):
|
||||
|
||||
url = "/api/camera_push/{entity_id}"
|
||||
name = 'api:camera_push:camera_entity'
|
||||
requires_auth = False
|
||||
|
||||
def __init__(self, hass, image_field):
|
||||
"""Initialize CameraPushReceiver with camera entity."""
|
||||
@ -75,8 +82,21 @@ class CameraPushReceiver(HomeAssistantView):
|
||||
|
||||
if _camera is None:
|
||||
_LOGGER.error("Unknown %s", entity_id)
|
||||
status = HTTP_NOT_FOUND if request[KEY_AUTHENTICATED]\
|
||||
else HTTP_UNAUTHORIZED
|
||||
return self.json_message('Unknown {}'.format(entity_id),
|
||||
HTTP_BAD_REQUEST)
|
||||
status)
|
||||
|
||||
# Supports HA authentication and token based
|
||||
# when token has been configured
|
||||
authenticated = (request[KEY_AUTHENTICATED] or
|
||||
(_camera.token is not None and
|
||||
request.query.get('token') == _camera.token))
|
||||
|
||||
if not authenticated:
|
||||
return self.json_message(
|
||||
'Invalid authorization credentials for {}'.format(entity_id),
|
||||
HTTP_UNAUTHORIZED)
|
||||
|
||||
try:
|
||||
data = await request.post()
|
||||
@ -95,7 +115,7 @@ class CameraPushReceiver(HomeAssistantView):
|
||||
class PushCamera(Camera):
|
||||
"""The representation of a Push camera."""
|
||||
|
||||
def __init__(self, name, buffer_size, timeout):
|
||||
def __init__(self, name, buffer_size, timeout, token):
|
||||
"""Initialize push camera component."""
|
||||
super().__init__()
|
||||
self._name = name
|
||||
@ -106,6 +126,7 @@ class PushCamera(Camera):
|
||||
self._timeout = timeout
|
||||
self.queue = deque([], buffer_size)
|
||||
self._current_image = None
|
||||
self.token = token
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
@ -168,5 +189,6 @@ class PushCamera(Camera):
|
||||
name: value for name, value in (
|
||||
(ATTR_LAST_TRIP, self._last_trip),
|
||||
(ATTR_FILENAME, self._filename),
|
||||
(ATTR_TOKEN, self.token),
|
||||
) if value is not None
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ from datetime import timedelta
|
||||
from homeassistant import core as ha
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
from tests.components.auth import async_setup_auth
|
||||
from homeassistant.components.http.auth import setup_auth
|
||||
|
||||
|
||||
async def test_bad_posting(aioclient_mock, hass, aiohttp_client):
|
||||
@ -15,19 +15,69 @@ async def test_bad_posting(aioclient_mock, hass, aiohttp_client):
|
||||
'camera': {
|
||||
'platform': 'push',
|
||||
'name': 'config_test',
|
||||
'token': '12345678'
|
||||
}})
|
||||
|
||||
client = await async_setup_auth(hass, aiohttp_client)
|
||||
client = await aiohttp_client(hass.http.app)
|
||||
|
||||
# missing file
|
||||
resp = await client.post('/api/camera_push/camera.config_test')
|
||||
assert resp.status == 400
|
||||
|
||||
files = {'image': io.BytesIO(b'fake')}
|
||||
|
||||
# wrong entity
|
||||
files = {'image': io.BytesIO(b'fake')}
|
||||
resp = await client.post('/api/camera_push/camera.wrong', data=files)
|
||||
assert resp.status == 400
|
||||
assert resp.status == 404
|
||||
|
||||
|
||||
async def test_cases_with_no_auth(aioclient_mock, hass, aiohttp_client):
|
||||
"""Test cases where aiohttp_client is not auth."""
|
||||
await async_setup_component(hass, 'camera', {
|
||||
'camera': {
|
||||
'platform': 'push',
|
||||
'name': 'config_test',
|
||||
'token': '12345678'
|
||||
}})
|
||||
|
||||
setup_auth(hass.http.app, [], True, api_password=None)
|
||||
client = await aiohttp_client(hass.http.app)
|
||||
|
||||
# wrong token
|
||||
files = {'image': io.BytesIO(b'fake')}
|
||||
resp = await client.post('/api/camera_push/camera.config_test?token=1234',
|
||||
data=files)
|
||||
assert resp.status == 401
|
||||
|
||||
# right token
|
||||
files = {'image': io.BytesIO(b'fake')}
|
||||
resp = await client.post(
|
||||
'/api/camera_push/camera.config_test?token=12345678',
|
||||
data=files)
|
||||
assert resp.status == 200
|
||||
|
||||
|
||||
async def test_no_auth_no_token(aioclient_mock, hass, aiohttp_client):
|
||||
"""Test cases where aiohttp_client is not auth."""
|
||||
await async_setup_component(hass, 'camera', {
|
||||
'camera': {
|
||||
'platform': 'push',
|
||||
'name': 'config_test',
|
||||
}})
|
||||
|
||||
setup_auth(hass.http.app, [], True, api_password=None)
|
||||
client = await aiohttp_client(hass.http.app)
|
||||
|
||||
# no token
|
||||
files = {'image': io.BytesIO(b'fake')}
|
||||
resp = await client.post('/api/camera_push/camera.config_test',
|
||||
data=files)
|
||||
assert resp.status == 401
|
||||
|
||||
# fake token
|
||||
files = {'image': io.BytesIO(b'fake')}
|
||||
resp = await client.post(
|
||||
'/api/camera_push/camera.config_test?token=12345678',
|
||||
data=files)
|
||||
assert resp.status == 401
|
||||
|
||||
|
||||
async def test_posting_url(hass, aiohttp_client):
|
||||
@ -36,6 +86,7 @@ async def test_posting_url(hass, aiohttp_client):
|
||||
'camera': {
|
||||
'platform': 'push',
|
||||
'name': 'config_test',
|
||||
'token': '12345678'
|
||||
}})
|
||||
|
||||
client = await aiohttp_client(hass.http.app)
|
||||
@ -46,7 +97,9 @@ async def test_posting_url(hass, aiohttp_client):
|
||||
assert camera_state.state == 'idle'
|
||||
|
||||
# post image
|
||||
resp = await client.post('/api/camera_push/camera.config_test', data=files)
|
||||
resp = await client.post(
|
||||
'/api/camera_push/camera.config_test?token=12345678',
|
||||
data=files)
|
||||
assert resp.status == 200
|
||||
|
||||
# state recording
|
||||
|
Loading…
x
Reference in New Issue
Block a user