commit
bac48aa9d2
|
@ -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
|
||||
|
||||
|
|
|
@ -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\
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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__)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue