Cache emulated_hue local ip check (#102020)

This commit is contained in:
J. Nick Koston 2023-10-14 16:19:10 -10:00 committed by GitHub
parent 7b2aa3a369
commit 547c38a515
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 28 additions and 9 deletions

View file

@ -121,6 +121,12 @@ DIMMABLE_SUPPORT_FEATURES = (
) )
@lru_cache(maxsize=32)
def _remote_is_allowed(address: str) -> bool:
"""Check if remote address is allowed."""
return is_local(ip_address(address))
class HueUnauthorizedUser(HomeAssistantView): class HueUnauthorizedUser(HomeAssistantView):
"""Handle requests to find the emulated hue bridge.""" """Handle requests to find the emulated hue bridge."""
@ -145,7 +151,7 @@ class HueUsernameView(HomeAssistantView):
async def post(self, request: web.Request) -> web.Response: async def post(self, request: web.Request) -> web.Response:
"""Handle a POST request.""" """Handle a POST request."""
assert request.remote is not None assert request.remote is not None
if not is_local(ip_address(request.remote)): if not _remote_is_allowed(request.remote):
return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED) return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED)
try: try:
@ -174,7 +180,7 @@ class HueAllGroupsStateView(HomeAssistantView):
def get(self, request: web.Request, username: str) -> web.Response: def get(self, request: web.Request, username: str) -> web.Response:
"""Process a request to make the Brilliant Lightpad work.""" """Process a request to make the Brilliant Lightpad work."""
assert request.remote is not None assert request.remote is not None
if not is_local(ip_address(request.remote)): if not _remote_is_allowed(request.remote):
return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED) return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED)
return self.json({}) return self.json({})
@ -195,7 +201,7 @@ class HueGroupView(HomeAssistantView):
def put(self, request: web.Request, username: str) -> web.Response: def put(self, request: web.Request, username: str) -> web.Response:
"""Process a request to make the Logitech Pop working.""" """Process a request to make the Logitech Pop working."""
assert request.remote is not None assert request.remote is not None
if not is_local(ip_address(request.remote)): if not _remote_is_allowed(request.remote):
return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED) return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED)
return self.json( return self.json(
@ -226,7 +232,7 @@ class HueAllLightsStateView(HomeAssistantView):
def get(self, request: web.Request, username: str) -> web.Response: def get(self, request: web.Request, username: str) -> web.Response:
"""Process a request to get the list of available lights.""" """Process a request to get the list of available lights."""
assert request.remote is not None assert request.remote is not None
if not is_local(ip_address(request.remote)): if not _remote_is_allowed(request.remote):
return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED) return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED)
return self.json(create_list_of_entities(self.config, request)) return self.json(create_list_of_entities(self.config, request))
@ -247,7 +253,7 @@ class HueFullStateView(HomeAssistantView):
def get(self, request: web.Request, username: str) -> web.Response: def get(self, request: web.Request, username: str) -> web.Response:
"""Process a request to get the list of available lights.""" """Process a request to get the list of available lights."""
assert request.remote is not None assert request.remote is not None
if not is_local(ip_address(request.remote)): if not _remote_is_allowed(request.remote):
return self.json_message("only local IPs allowed", HTTPStatus.UNAUTHORIZED) return self.json_message("only local IPs allowed", HTTPStatus.UNAUTHORIZED)
if username != HUE_API_USERNAME: if username != HUE_API_USERNAME:
return self.json(UNAUTHORIZED_USER) return self.json(UNAUTHORIZED_USER)
@ -276,7 +282,7 @@ class HueConfigView(HomeAssistantView):
def get(self, request: web.Request, username: str = "") -> web.Response: def get(self, request: web.Request, username: str = "") -> web.Response:
"""Process a request to get the configuration.""" """Process a request to get the configuration."""
assert request.remote is not None assert request.remote is not None
if not is_local(ip_address(request.remote)): if not _remote_is_allowed(request.remote):
return self.json_message("only local IPs allowed", HTTPStatus.UNAUTHORIZED) return self.json_message("only local IPs allowed", HTTPStatus.UNAUTHORIZED)
json_response = create_config_model(self.config, request) json_response = create_config_model(self.config, request)
@ -299,7 +305,7 @@ class HueOneLightStateView(HomeAssistantView):
def get(self, request: web.Request, username: str, entity_id: str) -> web.Response: def get(self, request: web.Request, username: str, entity_id: str) -> web.Response:
"""Process a request to get the state of an individual light.""" """Process a request to get the state of an individual light."""
assert request.remote is not None assert request.remote is not None
if not is_local(ip_address(request.remote)): if not _remote_is_allowed(request.remote):
return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED) return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED)
hass: core.HomeAssistant = request.app["hass"] hass: core.HomeAssistant = request.app["hass"]
@ -341,7 +347,7 @@ class HueOneLightChangeView(HomeAssistantView):
) -> web.Response: ) -> web.Response:
"""Process a request to set the state of an individual light.""" """Process a request to set the state of an individual light."""
assert request.remote is not None assert request.remote is not None
if not is_local(ip_address(request.remote)): if not _remote_is_allowed(request.remote):
return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED) return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED)
config = self.config config = self.config

View file

@ -36,9 +36,11 @@ from homeassistant.components.emulated_hue.hue_api import (
HueAllLightsStateView, HueAllLightsStateView,
HueConfigView, HueConfigView,
HueFullStateView, HueFullStateView,
HueGroupView,
HueOneLightChangeView, HueOneLightChangeView,
HueOneLightStateView, HueOneLightStateView,
HueUsernameView, HueUsernameView,
_remote_is_allowed,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -232,6 +234,7 @@ def _mock_hue_endpoints(
HueOneLightStateView(config).register(hass, web_app, web_app.router) HueOneLightStateView(config).register(hass, web_app, web_app.router)
HueOneLightChangeView(config).register(hass, web_app, web_app.router) HueOneLightChangeView(config).register(hass, web_app, web_app.router)
HueAllGroupsStateView(config).register(hass, web_app, web_app.router) HueAllGroupsStateView(config).register(hass, web_app, web_app.router)
HueGroupView(config).register(hass, web_app, web_app.router)
HueFullStateView(config).register(hass, web_app, web_app.router) HueFullStateView(config).register(hass, web_app, web_app.router)
HueConfigView(config).register(hass, web_app, web_app.router) HueConfigView(config).register(hass, web_app, web_app.router)
@ -1327,23 +1330,33 @@ async def test_external_ip_blocked(hue_client) -> None:
"/api/username/lights/light.ceiling_lights", "/api/username/lights/light.ceiling_lights",
] ]
postUrls = ["/api"] postUrls = ["/api"]
putUrls = ["/api/username/lights/light.ceiling_lights/state"] putUrls = [
"/api/username/lights/light.ceiling_lights/state",
"/api/username/groups/0/action",
]
with patch( with patch(
"homeassistant.components.emulated_hue.hue_api.ip_address", "homeassistant.components.emulated_hue.hue_api.ip_address",
return_value=ip_address("45.45.45.45"), return_value=ip_address("45.45.45.45"),
): ):
for getUrl in getUrls: for getUrl in getUrls:
_remote_is_allowed.cache_clear()
result = await hue_client.get(getUrl) result = await hue_client.get(getUrl)
assert result.status == HTTPStatus.UNAUTHORIZED assert result.status == HTTPStatus.UNAUTHORIZED
for postUrl in postUrls: for postUrl in postUrls:
_remote_is_allowed.cache_clear()
result = await hue_client.post(postUrl) result = await hue_client.post(postUrl)
assert result.status == HTTPStatus.UNAUTHORIZED assert result.status == HTTPStatus.UNAUTHORIZED
for putUrl in putUrls: for putUrl in putUrls:
_remote_is_allowed.cache_clear()
result = await hue_client.put(putUrl) result = await hue_client.put(putUrl)
assert result.status == HTTPStatus.UNAUTHORIZED assert result.status == HTTPStatus.UNAUTHORIZED
# We are patching inside of a cache so be sure to clear it
# so that the next test is not affected
_remote_is_allowed.cache_clear()
async def test_unauthorized_user_blocked(hue_client) -> None: async def test_unauthorized_user_blocked(hue_client) -> None:
"""Test unauthorized_user blocked.""" """Test unauthorized_user blocked."""