mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 19:57: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 (
|
||||
HueAllGroupsStateView,
|
||||
HueAllLightsStateView,
|
||||
HueConfigView,
|
||||
HueFullStateView,
|
||||
HueGroupView,
|
||||
HueOneLightChangeView,
|
||||
@ -119,6 +120,7 @@ async def async_setup(hass, yaml_config):
|
||||
HueAllGroupsStateView(config).register(app, app.router)
|
||||
HueGroupView(config).register(app, app.router)
|
||||
HueFullStateView(config).register(app, app.router)
|
||||
HueConfigView(config).register(app, app.router)
|
||||
|
||||
upnp_listener = UPNPResponderThread(
|
||||
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_MAX = 500
|
||||
|
||||
HUE_API_USERNAME = "12345678901234567890"
|
||||
HUE_API_USERNAME = "nouser"
|
||||
UNAUTHORIZED_USER = [
|
||||
{"error": {"address": "/", "description": "unauthorized user", "type": "1"}}
|
||||
]
|
||||
@ -226,14 +226,47 @@ class HueFullStateView(HomeAssistantView):
|
||||
"config": {
|
||||
"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 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):
|
||||
"""Handle requests for getting info about a single entity."""
|
||||
|
||||
|
@ -42,7 +42,7 @@ class DescriptionXmlView(HomeAssistantView):
|
||||
<modelName>Philips hue bridge 2015</modelName>
|
||||
<modelNumber>BSB002</modelNumber>
|
||||
<modelURL>http://www.meethue.com</modelURL>
|
||||
<serialNumber>1234</serialNumber>
|
||||
<serialNumber>001788FFFE23BFC2</serialNumber>
|
||||
<UDN>uuid:2f402f80-da50-11e1-9b23-001788255acc</UDN>
|
||||
</device>
|
||||
</root>
|
||||
@ -77,10 +77,10 @@ class UPNPResponderThread(threading.Thread):
|
||||
CACHE-CONTROL: max-age=60
|
||||
EXT:
|
||||
LOCATION: http://{advertise_ip}:{advertise_port}/description.xml
|
||||
SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/0.1
|
||||
hue-bridgeid: 1234
|
||||
ST: urn:schemas-upnp-org:device:basic:1
|
||||
USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1
|
||||
SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0
|
||||
hue-bridgeid: 001788FFFE23BFC2
|
||||
ST: upnp:rootdevice
|
||||
USN: uuid:2f402f80-da50-11e1-9b23-00178829d301::upnp:rootdevice
|
||||
|
||||
"""
|
||||
|
||||
|
@ -26,6 +26,7 @@ from homeassistant.components.emulated_hue.hue_api import (
|
||||
HUE_API_USERNAME,
|
||||
HueAllGroupsStateView,
|
||||
HueAllLightsStateView,
|
||||
HueConfigView,
|
||||
HueFullStateView,
|
||||
HueOneLightChangeView,
|
||||
HueOneLightStateView,
|
||||
@ -191,6 +192,7 @@ def hue_client(loop, hass_hue, aiohttp_client):
|
||||
HueOneLightChangeView(config).register(web_app, web_app.router)
|
||||
HueAllGroupsStateView(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))
|
||||
|
||||
@ -330,7 +332,7 @@ async def test_discover_full_state(hue_client):
|
||||
|
||||
# Make sure array is correct size
|
||||
assert len(result_json) == 2
|
||||
assert len(config_json) == 4
|
||||
assert len(config_json) == 6
|
||||
assert len(lights_json) >= 1
|
||||
|
||||
# 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 "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"]
|
||||
@ -351,6 +357,49 @@ async def test_discover_full_state(hue_client):
|
||||
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_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):
|
||||
"""Test the getting of light state."""
|
||||
@ -905,6 +954,7 @@ async def test_external_ip_blocked(hue_client):
|
||||
getUrls = [
|
||||
"/api/username/groups",
|
||||
"/api/username",
|
||||
"/api/username/config",
|
||||
"/api/username/lights",
|
||||
"/api/username/lights/light.ceiling_lights",
|
||||
]
|
||||
@ -925,3 +975,17 @@ async def test_external_ip_blocked(hue_client):
|
||||
for putUrl in putUrls:
|
||||
result = await hue_client.put(putUrl)
|
||||
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
|
||||
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
|
||||
self.fail("description.xml is not valid XML!")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user