mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Improve emulated_hue compatibility with newer systems (#35148)
* Make emulated hue detectable by Busch-Jaeger free@home SysAP * Emulated hue: Remove unnecessary host line from UPnP response * Test that external IPs are blocked for config route * Add another test for unauthorized users * Change hue username to nouser nouser seems to be used by the official Hue Bridge v1 Android app and is used by other projects as well Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
73fb57fd32
commit
c5379a0f35
@ -14,6 +14,7 @@ from homeassistant.util.json import load_json, save_json
|
|||||||
from .hue_api import (
|
from .hue_api import (
|
||||||
HueAllGroupsStateView,
|
HueAllGroupsStateView,
|
||||||
HueAllLightsStateView,
|
HueAllLightsStateView,
|
||||||
|
HueConfigView,
|
||||||
HueFullStateView,
|
HueFullStateView,
|
||||||
HueGroupView,
|
HueGroupView,
|
||||||
HueOneLightChangeView,
|
HueOneLightChangeView,
|
||||||
@ -119,6 +120,7 @@ async def async_setup(hass, yaml_config):
|
|||||||
HueAllGroupsStateView(config).register(app, app.router)
|
HueAllGroupsStateView(config).register(app, app.router)
|
||||||
HueGroupView(config).register(app, app.router)
|
HueGroupView(config).register(app, app.router)
|
||||||
HueFullStateView(config).register(app, app.router)
|
HueFullStateView(config).register(app, app.router)
|
||||||
|
HueConfigView(config).register(app, app.router)
|
||||||
|
|
||||||
upnp_listener = UPNPResponderThread(
|
upnp_listener = UPNPResponderThread(
|
||||||
config.host_ip_addr,
|
config.host_ip_addr,
|
||||||
|
@ -89,7 +89,7 @@ HUE_API_STATE_SAT_MAX = 254
|
|||||||
HUE_API_STATE_CT_MIN = 153 # Color temp
|
HUE_API_STATE_CT_MIN = 153 # Color temp
|
||||||
HUE_API_STATE_CT_MAX = 500
|
HUE_API_STATE_CT_MAX = 500
|
||||||
|
|
||||||
HUE_API_USERNAME = "12345678901234567890"
|
HUE_API_USERNAME = "nouser"
|
||||||
UNAUTHORIZED_USER = [
|
UNAUTHORIZED_USER = [
|
||||||
{"error": {"address": "/", "description": "unauthorized user", "type": "1"}}
|
{"error": {"address": "/", "description": "unauthorized user", "type": "1"}}
|
||||||
]
|
]
|
||||||
@ -226,14 +226,47 @@ class HueFullStateView(HomeAssistantView):
|
|||||||
"config": {
|
"config": {
|
||||||
"mac": "00:00:00:00:00:00",
|
"mac": "00:00:00:00:00:00",
|
||||||
"swversion": "01003542",
|
"swversion": "01003542",
|
||||||
|
"apiversion": "1.17.0",
|
||||||
"whitelist": {HUE_API_USERNAME: {"name": "HASS BRIDGE"}},
|
"whitelist": {HUE_API_USERNAME: {"name": "HASS BRIDGE"}},
|
||||||
"ipaddress": f"{self.config.advertise_ip}:{self.config.advertise_port}",
|
"ipaddress": f"{self.config.advertise_ip}:{self.config.advertise_port}",
|
||||||
|
"linkbutton": True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.json(json_response)
|
return self.json(json_response)
|
||||||
|
|
||||||
|
|
||||||
|
class HueConfigView(HomeAssistantView):
|
||||||
|
"""Return config view of emulated hue."""
|
||||||
|
|
||||||
|
url = "/api/{username}/config"
|
||||||
|
name = "emulated_hue:username:config"
|
||||||
|
requires_auth = False
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
"""Initialize the instance of the view."""
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
@core.callback
|
||||||
|
def get(self, request, username):
|
||||||
|
"""Process a request to get the configuration."""
|
||||||
|
if not is_local(request[KEY_REAL_IP]):
|
||||||
|
return self.json_message("only local IPs allowed", HTTP_UNAUTHORIZED)
|
||||||
|
if username != HUE_API_USERNAME:
|
||||||
|
return self.json(UNAUTHORIZED_USER)
|
||||||
|
|
||||||
|
json_response = {
|
||||||
|
"mac": "00:00:00:00:00:00",
|
||||||
|
"swversion": "01003542",
|
||||||
|
"apiversion": "1.17.0",
|
||||||
|
"whitelist": {HUE_API_USERNAME: {"name": "HASS BRIDGE"}},
|
||||||
|
"ipaddress": f"{self.config.advertise_ip}:{self.config.advertise_port}",
|
||||||
|
"linkbutton": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.json(json_response)
|
||||||
|
|
||||||
|
|
||||||
class HueOneLightStateView(HomeAssistantView):
|
class HueOneLightStateView(HomeAssistantView):
|
||||||
"""Handle requests for getting info about a single entity."""
|
"""Handle requests for getting info about a single entity."""
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class DescriptionXmlView(HomeAssistantView):
|
|||||||
<modelName>Philips hue bridge 2015</modelName>
|
<modelName>Philips hue bridge 2015</modelName>
|
||||||
<modelNumber>BSB002</modelNumber>
|
<modelNumber>BSB002</modelNumber>
|
||||||
<modelURL>http://www.meethue.com</modelURL>
|
<modelURL>http://www.meethue.com</modelURL>
|
||||||
<serialNumber>1234</serialNumber>
|
<serialNumber>001788FFFE23BFC2</serialNumber>
|
||||||
<UDN>uuid:2f402f80-da50-11e1-9b23-001788255acc</UDN>
|
<UDN>uuid:2f402f80-da50-11e1-9b23-001788255acc</UDN>
|
||||||
</device>
|
</device>
|
||||||
</root>
|
</root>
|
||||||
@ -77,10 +77,10 @@ class UPNPResponderThread(threading.Thread):
|
|||||||
CACHE-CONTROL: max-age=60
|
CACHE-CONTROL: max-age=60
|
||||||
EXT:
|
EXT:
|
||||||
LOCATION: http://{advertise_ip}:{advertise_port}/description.xml
|
LOCATION: http://{advertise_ip}:{advertise_port}/description.xml
|
||||||
SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/0.1
|
SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0
|
||||||
hue-bridgeid: 1234
|
hue-bridgeid: 001788FFFE23BFC2
|
||||||
ST: urn:schemas-upnp-org:device:basic:1
|
ST: upnp:rootdevice
|
||||||
USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
|
USN: uuid:2f402f80-da50-11e1-9b23-00178829d301::upnp:rootdevice
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ from homeassistant.components.emulated_hue.hue_api import (
|
|||||||
HUE_API_USERNAME,
|
HUE_API_USERNAME,
|
||||||
HueAllGroupsStateView,
|
HueAllGroupsStateView,
|
||||||
HueAllLightsStateView,
|
HueAllLightsStateView,
|
||||||
|
HueConfigView,
|
||||||
HueFullStateView,
|
HueFullStateView,
|
||||||
HueOneLightChangeView,
|
HueOneLightChangeView,
|
||||||
HueOneLightStateView,
|
HueOneLightStateView,
|
||||||
@ -191,6 +192,7 @@ def hue_client(loop, hass_hue, aiohttp_client):
|
|||||||
HueOneLightChangeView(config).register(web_app, web_app.router)
|
HueOneLightChangeView(config).register(web_app, web_app.router)
|
||||||
HueAllGroupsStateView(config).register(web_app, web_app.router)
|
HueAllGroupsStateView(config).register(web_app, web_app.router)
|
||||||
HueFullStateView(config).register(web_app, web_app.router)
|
HueFullStateView(config).register(web_app, web_app.router)
|
||||||
|
HueConfigView(config).register(web_app, web_app.router)
|
||||||
|
|
||||||
return loop.run_until_complete(aiohttp_client(web_app))
|
return loop.run_until_complete(aiohttp_client(web_app))
|
||||||
|
|
||||||
@ -330,7 +332,7 @@ async def test_discover_full_state(hue_client):
|
|||||||
|
|
||||||
# Make sure array is correct size
|
# Make sure array is correct size
|
||||||
assert len(result_json) == 2
|
assert len(result_json) == 2
|
||||||
assert len(config_json) == 4
|
assert len(config_json) == 6
|
||||||
assert len(lights_json) >= 1
|
assert len(lights_json) >= 1
|
||||||
|
|
||||||
# Make sure the config wrapper added to the config is there
|
# Make sure the config wrapper added to the config is there
|
||||||
@ -341,6 +343,10 @@ async def test_discover_full_state(hue_client):
|
|||||||
assert "swversion" in config_json
|
assert "swversion" in config_json
|
||||||
assert "01003542" in config_json["swversion"]
|
assert "01003542" in config_json["swversion"]
|
||||||
|
|
||||||
|
# Make sure the api version is correct
|
||||||
|
assert "apiversion" in config_json
|
||||||
|
assert "1.17.0" in config_json["apiversion"]
|
||||||
|
|
||||||
# Make sure the correct username in config
|
# Make sure the correct username in config
|
||||||
assert "whitelist" in config_json
|
assert "whitelist" in config_json
|
||||||
assert HUE_API_USERNAME in config_json["whitelist"]
|
assert HUE_API_USERNAME in config_json["whitelist"]
|
||||||
@ -351,6 +357,49 @@ async def test_discover_full_state(hue_client):
|
|||||||
assert "ipaddress" in config_json
|
assert "ipaddress" in config_json
|
||||||
assert "127.0.0.1:8300" in config_json["ipaddress"]
|
assert "127.0.0.1:8300" in config_json["ipaddress"]
|
||||||
|
|
||||||
|
# Make sure the device announces a link button
|
||||||
|
assert "linkbutton" in config_json
|
||||||
|
assert config_json["linkbutton"] is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discover_config(hue_client):
|
||||||
|
"""Test the discovery of configuration."""
|
||||||
|
result = await hue_client.get(f"/api/{HUE_API_USERNAME}/config")
|
||||||
|
|
||||||
|
assert result.status == 200
|
||||||
|
assert "application/json" in result.headers["content-type"]
|
||||||
|
|
||||||
|
config_json = await result.json()
|
||||||
|
|
||||||
|
# Make sure array is correct size
|
||||||
|
assert len(config_json) == 6
|
||||||
|
|
||||||
|
# Make sure the config wrapper added to the config is there
|
||||||
|
assert "mac" in config_json
|
||||||
|
assert "00:00:00:00:00:00" in config_json["mac"]
|
||||||
|
|
||||||
|
# Make sure the correct version in config
|
||||||
|
assert "swversion" in config_json
|
||||||
|
assert "01003542" in config_json["swversion"]
|
||||||
|
|
||||||
|
# Make sure the api version is correct
|
||||||
|
assert "apiversion" in config_json
|
||||||
|
assert "1.17.0" in config_json["apiversion"]
|
||||||
|
|
||||||
|
# Make sure the correct username in config
|
||||||
|
assert "whitelist" in config_json
|
||||||
|
assert HUE_API_USERNAME in config_json["whitelist"]
|
||||||
|
assert "name" in config_json["whitelist"][HUE_API_USERNAME]
|
||||||
|
assert "HASS BRIDGE" in config_json["whitelist"][HUE_API_USERNAME]["name"]
|
||||||
|
|
||||||
|
# Make sure the correct ip in config
|
||||||
|
assert "ipaddress" in config_json
|
||||||
|
assert "127.0.0.1:8300" in config_json["ipaddress"]
|
||||||
|
|
||||||
|
# Make sure the device announces a link button
|
||||||
|
assert "linkbutton" in config_json
|
||||||
|
assert config_json["linkbutton"] is True
|
||||||
|
|
||||||
|
|
||||||
async def test_get_light_state(hass_hue, hue_client):
|
async def test_get_light_state(hass_hue, hue_client):
|
||||||
"""Test the getting of light state."""
|
"""Test the getting of light state."""
|
||||||
@ -905,6 +954,7 @@ async def test_external_ip_blocked(hue_client):
|
|||||||
getUrls = [
|
getUrls = [
|
||||||
"/api/username/groups",
|
"/api/username/groups",
|
||||||
"/api/username",
|
"/api/username",
|
||||||
|
"/api/username/config",
|
||||||
"/api/username/lights",
|
"/api/username/lights",
|
||||||
"/api/username/lights/light.ceiling_lights",
|
"/api/username/lights/light.ceiling_lights",
|
||||||
]
|
]
|
||||||
@ -925,3 +975,17 @@ async def test_external_ip_blocked(hue_client):
|
|||||||
for putUrl in putUrls:
|
for putUrl in putUrls:
|
||||||
result = await hue_client.put(putUrl)
|
result = await hue_client.put(putUrl)
|
||||||
assert result.status == HTTP_UNAUTHORIZED
|
assert result.status == HTTP_UNAUTHORIZED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unauthorized_user_blocked(hue_client):
|
||||||
|
"""Test unauthorized_user blocked."""
|
||||||
|
getUrls = [
|
||||||
|
"/api/wronguser",
|
||||||
|
"/api/wronguser/config",
|
||||||
|
]
|
||||||
|
for getUrl in getUrls:
|
||||||
|
result = await hue_client.get(getUrl)
|
||||||
|
assert result.status == HTTP_OK
|
||||||
|
|
||||||
|
result_json = await result.json()
|
||||||
|
assert result_json[0]["error"]["description"] == "unauthorized user"
|
||||||
|
@ -57,7 +57,9 @@ class TestEmulatedHue(unittest.TestCase):
|
|||||||
|
|
||||||
# Make sure the XML is parsable
|
# Make sure the XML is parsable
|
||||||
try:
|
try:
|
||||||
ET.fromstring(result.text)
|
root = ET.fromstring(result.text)
|
||||||
|
ns = {"s": "urn:schemas-upnp-org:device-1-0"}
|
||||||
|
assert root.find("./s:device/s:serialNumber", ns).text == "001788FFFE23BFC2"
|
||||||
except: # noqa: E722 pylint: disable=bare-except
|
except: # noqa: E722 pylint: disable=bare-except
|
||||||
self.fail("description.xml is not valid XML!")
|
self.fail("description.xml is not valid XML!")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user