diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py
index da6e7acab40..5dbd52d09b1 100644
--- a/homeassistant/components/emulated_hue/__init__.py
+++ b/homeassistant/components/emulated_hue/__init__.py
@@ -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,
diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py
index d7830b7b699..069a4b60d0c 100644
--- a/homeassistant/components/emulated_hue/hue_api.py
+++ b/homeassistant/components/emulated_hue/hue_api.py
@@ -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."""
diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py
index c10fb3b826b..f0fe392f865 100644
--- a/homeassistant/components/emulated_hue/upnp.py
+++ b/homeassistant/components/emulated_hue/upnp.py
@@ -42,7 +42,7 @@ class DescriptionXmlView(HomeAssistantView):
Philips hue bridge 2015
BSB002
http://www.meethue.com
-1234
+001788FFFE23BFC2
uuid:2f402f80-da50-11e1-9b23-001788255acc
@@ -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
"""
diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py
index 2171bac8c3f..fa97cd2f417 100644
--- a/tests/components/emulated_hue/test_hue_api.py
+++ b/tests/components/emulated_hue/test_hue_api.py
@@ -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"
diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py
index 110ecf868e6..32859ca00c1 100644
--- a/tests/components/emulated_hue/test_upnp.py
+++ b/tests/components/emulated_hue/test_upnp.py
@@ -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!")