Merge pull request #18857 from home-assistant/rc

0.83.2
pull/18947/head 0.83.2
Paulus Schoutsen 2018-11-30 20:09:29 +01:00 committed by GitHub
commit bac48aa9d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 90 additions and 54 deletions

View File

@ -45,6 +45,7 @@ def setup_auth(app, trusted_networks, use_auth,
support_legacy=False, api_password=None):
"""Create auth middleware for the app."""
old_auth_warning = set()
legacy_auth = (not use_auth or support_legacy) and api_password
@middleware
async def auth_middleware(request, handler):
@ -60,7 +61,6 @@ def setup_auth(app, trusted_networks, use_auth,
request.path, request[KEY_REAL_IP])
old_auth_warning.add(request.path)
legacy_auth = (not use_auth or support_legacy) and api_password
if (hdrs.AUTHORIZATION in request.headers and
await async_validate_auth_header(
request, api_password if legacy_auth else None)):
@ -91,6 +91,11 @@ def setup_auth(app, trusted_networks, use_auth,
app['hass'])
elif _is_trusted_ip(request, trusted_networks):
users = await app['hass'].auth.async_get_users()
for user in users:
if user.is_owner:
request['hass_user'] = user
break
authenticated = True
elif not use_auth and api_password is None:
@ -136,8 +141,9 @@ async def async_validate_auth_header(request, api_password=None):
# If no space in authorization header
return False
if auth_type == 'Bearer':
hass = request.app['hass']
if auth_type == 'Bearer':
refresh_token = await hass.auth.async_validate_access_token(auth_val)
if refresh_token is None:
return False
@ -157,8 +163,12 @@ async def async_validate_auth_header(request, api_password=None):
if username != 'homeassistant':
return False
return hmac.compare_digest(api_password.encode('utf-8'),
password.encode('utf-8'))
if not hmac.compare_digest(api_password.encode('utf-8'),
password.encode('utf-8')):
return False
request['hass_user'] = await legacy_api_password.async_get_user(hass)
return True
return False

View File

@ -65,7 +65,8 @@ class FibaroLight(FibaroDevice, Light):
self._update_lock = asyncio.Lock()
if 'levelChange' in fibaro_device.interfaces:
self._supported_flags |= SUPPORT_BRIGHTNESS
if 'color' in fibaro_device.properties:
if 'color' in fibaro_device.properties and \
'setColor' in fibaro_device.actions:
self._supported_flags |= SUPPORT_COLOR
if 'setW' in fibaro_device.actions:
self._supported_flags |= SUPPORT_WHITE_VALUE
@ -168,7 +169,9 @@ class FibaroLight(FibaroDevice, Light):
if self._supported_flags & SUPPORT_BRIGHTNESS:
self._brightness = float(self.fibaro_device.properties.value)
# Color handling
if self._supported_flags & SUPPORT_COLOR:
if self._supported_flags & SUPPORT_COLOR and \
'color' in self.fibaro_device.properties and \
',' in self.fibaro_device.properties.color:
# Fibaro communicates the color as an 'R, G, B, W' string
rgbw_s = self.fibaro_device.properties.color
if rgbw_s == '0,0,0,0' and\

View File

@ -208,11 +208,8 @@ async def async_start(hass: HomeAssistantType, discovery_topic, hass_config,
if value[-1] == TOPIC_BASE and key.endswith('_topic'):
payload[key] = "{}{}".format(value[:-1], base)
# If present, unique_id is used as the discovered object id. Otherwise,
# if present, the node_id will be included in the discovered object id
discovery_id = payload.get(
'unique_id', ' '.join(
(node_id, object_id)) if node_id else object_id)
# If present, the node_id will be included in the discovered object id
discovery_id = ' '.join((node_id, object_id)) if node_id else object_id
discovery_hash = (component, discovery_id)
if payload:

View File

@ -17,7 +17,7 @@ import yarl
from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider
from homeassistant.helpers.aiohttp_client import async_get_clientsession
REQUIREMENTS = ['gTTS-token==1.1.2']
REQUIREMENTS = ['gTTS-token==1.1.3']
_LOGGER = logging.getLogger(__name__)

View File

@ -2,7 +2,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 83
PATCH_VERSION = '1'
PATCH_VERSION = '2'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3)

View File

@ -406,7 +406,7 @@ freesms==0.1.2
fritzhome==1.0.4
# homeassistant.components.tts.google
gTTS-token==1.1.2
gTTS-token==1.1.3
# homeassistant.components.sensor.gearbest
gearbest_parser==1.0.7

View File

@ -72,7 +72,7 @@ feedparser==5.2.1
foobot_async==0.3.1
# homeassistant.components.tts.google
gTTS-token==1.1.2
gTTS-token==1.1.3
# homeassistant.components.geo_location.geo_json_events
# homeassistant.components.geo_location.nsw_rural_fire_service_feed

View File

@ -333,39 +333,6 @@ async def test_discovery_update_binary_sensor(hass, mqtt_mock, caplog):
assert state is None
async def test_discovery_unique_id(hass, mqtt_mock, caplog):
"""Test unique id option only creates one sensor per unique_id."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
await async_start(hass, 'homeassistant', {}, entry)
data1 = (
'{ "name": "Beer",'
' "state_topic": "test_topic",'
' "unique_id": "TOTALLY_UNIQUE" }'
)
data2 = (
'{ "name": "Milk",'
' "state_topic": "test_topic",'
' "unique_id": "TOTALLY_DIFFERENT" }'
)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
data1)
await hass.async_block_till_done()
state = hass.states.get('binary_sensor.beer')
assert state is not None
assert state.name == 'Beer'
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
data2)
await hass.async_block_till_done()
await hass.async_block_till_done()
state = hass.states.get('binary_sensor.beer')
assert state is not None
assert state.name == 'Beer'
state = hass.states.get('binary_sensor.milk')
assert state is not None
assert state.name == 'Milk'
async def test_entity_device_info_with_identifier(hass, mqtt_mock):
"""Test MQTT binary sensor device registry integration."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)

View File

@ -88,6 +88,12 @@ def hass_access_token(hass, hass_admin_user):
yield hass.auth.async_create_access_token(refresh_token)
@pytest.fixture
def hass_owner_user(hass, local_auth):
"""Return a Home Assistant admin user."""
return MockUser(is_owner=True).add_to_hass(hass)
@pytest.fixture
def hass_admin_user(hass, local_auth):
"""Return a Home Assistant admin user."""

View File

@ -7,6 +7,7 @@ import pytest
from aiohttp import BasicAuth, web
from aiohttp.web_exceptions import HTTPUnauthorized
from homeassistant.auth.providers import legacy_api_password
from homeassistant.components.http.auth import setup_auth, async_sign_path
from homeassistant.components.http.const import KEY_AUTHENTICATED
from homeassistant.components.http.real_ip import setup_real_ip
@ -84,29 +85,40 @@ async def test_access_without_password(app, aiohttp_client):
async def test_access_with_password_in_header(app, aiohttp_client,
legacy_auth):
legacy_auth, hass):
"""Test access with password in header."""
setup_auth(app, [], False, api_password=API_PASSWORD)
client = await aiohttp_client(app)
user = await legacy_api_password.async_get_user(hass)
req = await client.get(
'/', headers={HTTP_HEADER_HA_AUTH: API_PASSWORD})
assert req.status == 200
assert await req.json() == {
'refresh_token_id': None,
'user_id': user.id,
}
req = await client.get(
'/', headers={HTTP_HEADER_HA_AUTH: 'wrong-pass'})
assert req.status == 401
async def test_access_with_password_in_query(app, aiohttp_client, legacy_auth):
async def test_access_with_password_in_query(app, aiohttp_client, legacy_auth,
hass):
"""Test access with password in URL."""
setup_auth(app, [], False, api_password=API_PASSWORD)
client = await aiohttp_client(app)
user = await legacy_api_password.async_get_user(hass)
resp = await client.get('/', params={
'api_password': API_PASSWORD
})
assert resp.status == 200
assert await resp.json() == {
'refresh_token_id': None,
'user_id': user.id,
}
resp = await client.get('/')
assert resp.status == 401
@ -117,15 +129,20 @@ async def test_access_with_password_in_query(app, aiohttp_client, legacy_auth):
assert resp.status == 401
async def test_basic_auth_works(app, aiohttp_client):
async def test_basic_auth_works(app, aiohttp_client, hass, legacy_auth):
"""Test access with basic authentication."""
setup_auth(app, [], False, api_password=API_PASSWORD)
client = await aiohttp_client(app)
user = await legacy_api_password.async_get_user(hass)
req = await client.get(
'/',
auth=BasicAuth('homeassistant', API_PASSWORD))
assert req.status == 200
assert await req.json() == {
'refresh_token_id': None,
'user_id': user.id,
}
req = await client.get(
'/',
@ -145,7 +162,7 @@ async def test_basic_auth_works(app, aiohttp_client):
assert req.status == 401
async def test_access_with_trusted_ip(app2, aiohttp_client):
async def test_access_with_trusted_ip(app2, aiohttp_client, hass_owner_user):
"""Test access with an untrusted ip address."""
setup_auth(app2, TRUSTED_NETWORKS, False, api_password='some-pass')
@ -163,6 +180,10 @@ async def test_access_with_trusted_ip(app2, aiohttp_client):
resp = await client.get('/')
assert resp.status == 200, \
"{} should be trusted".format(remote_addr)
assert await resp.json() == {
'refresh_token_id': None,
'user_id': hass_owner_user.id,
}
async def test_auth_active_access_with_access_token_in_header(
@ -171,18 +192,32 @@ async def test_auth_active_access_with_access_token_in_header(
token = hass_access_token
setup_auth(app, [], True, api_password=None)
client = await aiohttp_client(app)
refresh_token = await hass.auth.async_validate_access_token(
hass_access_token)
req = await client.get(
'/', headers={'Authorization': 'Bearer {}'.format(token)})
assert req.status == 200
assert await req.json() == {
'refresh_token_id': refresh_token.id,
'user_id': refresh_token.user.id,
}
req = await client.get(
'/', headers={'AUTHORIZATION': 'Bearer {}'.format(token)})
assert req.status == 200
assert await req.json() == {
'refresh_token_id': refresh_token.id,
'user_id': refresh_token.user.id,
}
req = await client.get(
'/', headers={'authorization': 'Bearer {}'.format(token)})
assert req.status == 200
assert await req.json() == {
'refresh_token_id': refresh_token.id,
'user_id': refresh_token.user.id,
}
req = await client.get(
'/', headers={'Authorization': token})
@ -200,7 +235,8 @@ async def test_auth_active_access_with_access_token_in_header(
assert req.status == 401
async def test_auth_active_access_with_trusted_ip(app2, aiohttp_client):
async def test_auth_active_access_with_trusted_ip(app2, aiohttp_client,
hass_owner_user):
"""Test access with an untrusted ip address."""
setup_auth(app2, TRUSTED_NETWORKS, True, api_password=None)
@ -218,6 +254,10 @@ async def test_auth_active_access_with_trusted_ip(app2, aiohttp_client):
resp = await client.get('/')
assert resp.status == 200, \
"{} should be trusted".format(remote_addr)
assert await resp.json() == {
'refresh_token_id': None,
'user_id': hass_owner_user.id,
}
async def test_auth_active_blocked_api_password_access(
@ -242,24 +282,37 @@ async def test_auth_active_blocked_api_password_access(
async def test_auth_legacy_support_api_password_access(
app, aiohttp_client, legacy_auth):
app, aiohttp_client, legacy_auth, hass):
"""Test access using api_password if auth.support_legacy."""
setup_auth(app, [], True, support_legacy=True, api_password=API_PASSWORD)
client = await aiohttp_client(app)
user = await legacy_api_password.async_get_user(hass)
req = await client.get(
'/', headers={HTTP_HEADER_HA_AUTH: API_PASSWORD})
assert req.status == 200
assert await req.json() == {
'refresh_token_id': None,
'user_id': user.id,
}
resp = await client.get('/', params={
'api_password': API_PASSWORD
})
assert resp.status == 200
assert await resp.json() == {
'refresh_token_id': None,
'user_id': user.id,
}
req = await client.get(
'/',
auth=BasicAuth('homeassistant', API_PASSWORD))
assert req.status == 200
assert await req.json() == {
'refresh_token_id': None,
'user_id': user.id,
}
async def test_auth_access_signed_path(