Add concept of allowed external URLs to config (#36988)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
cbb76be9d0
commit
7968cd650a
6 changed files with 66 additions and 7 deletions
|
@ -20,6 +20,7 @@ from homeassistant.const import (
|
|||
ATTR_ASSUMED_STATE,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
ATTR_HIDDEN,
|
||||
CONF_ALLOWLIST_EXTERNAL_URLS,
|
||||
CONF_AUTH_MFA_MODULES,
|
||||
CONF_AUTH_PROVIDERS,
|
||||
CONF_CUSTOMIZE,
|
||||
|
@ -185,6 +186,7 @@ CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend(
|
|||
vol.Optional(CONF_WHITELIST_EXTERNAL_DIRS): vol.All(
|
||||
cv.ensure_list, [vol.IsDir()] # pylint: disable=no-value-for-parameter
|
||||
),
|
||||
vol.Optional(CONF_ALLOWLIST_EXTERNAL_URLS): vol.All(cv.ensure_list, [cv.url]),
|
||||
vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA,
|
||||
vol.Optional(CONF_AUTH_PROVIDERS): vol.All(
|
||||
cv.ensure_list,
|
||||
|
@ -502,6 +504,14 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> Non
|
|||
if CONF_WHITELIST_EXTERNAL_DIRS in config:
|
||||
hac.whitelist_external_dirs.update(set(config[CONF_WHITELIST_EXTERNAL_DIRS]))
|
||||
|
||||
# Init whitelist external URL list – make sure to add / to every URL that doesn't
|
||||
# already have it so that we can properly test "path ownership"
|
||||
if CONF_ALLOWLIST_EXTERNAL_URLS in config:
|
||||
hac.allowlist_external_urls.update(
|
||||
url if url.endswith("/") else f"{url}/"
|
||||
for url in config[CONF_ALLOWLIST_EXTERNAL_URLS]
|
||||
)
|
||||
|
||||
# Customize
|
||||
cust_exact = dict(config[CONF_CUSTOMIZE])
|
||||
cust_domain = dict(config[CONF_CUSTOMIZE_DOMAIN])
|
||||
|
|
|
@ -32,13 +32,14 @@ CONF_ACCESS_TOKEN = "access_token"
|
|||
CONF_ADDRESS = "address"
|
||||
CONF_AFTER = "after"
|
||||
CONF_ALIAS = "alias"
|
||||
CONF_ALLOWLIST_EXTERNAL_URLS = "allowlist_external_urls"
|
||||
CONF_API_KEY = "api_key"
|
||||
CONF_API_VERSION = "api_version"
|
||||
CONF_ARMING_TIME = "arming_time"
|
||||
CONF_AT = "at"
|
||||
CONF_AUTHENTICATION = "authentication"
|
||||
CONF_AUTH_MFA_MODULES = "auth_mfa_modules"
|
||||
CONF_AUTH_PROVIDERS = "auth_providers"
|
||||
CONF_AUTHENTICATION = "authentication"
|
||||
CONF_BASE = "base"
|
||||
CONF_BEFORE = "before"
|
||||
CONF_BELOW = "below"
|
||||
|
@ -68,9 +69,9 @@ CONF_CUSTOMIZE_GLOB = "customize_glob"
|
|||
CONF_DELAY = "delay"
|
||||
CONF_DELAY_TIME = "delay_time"
|
||||
CONF_DEVICE = "device"
|
||||
CONF_DEVICES = "devices"
|
||||
CONF_DEVICE_CLASS = "device_class"
|
||||
CONF_DEVICE_ID = "device_id"
|
||||
CONF_DEVICES = "devices"
|
||||
CONF_DISARM_AFTER_TRIGGER = "disarm_after_trigger"
|
||||
CONF_DISCOVERY = "discovery"
|
||||
CONF_DISKS = "disks"
|
||||
|
@ -90,8 +91,8 @@ CONF_EVENT_DATA = "event_data"
|
|||
CONF_EVENT_DATA_TEMPLATE = "event_data_template"
|
||||
CONF_EXCLUDE = "exclude"
|
||||
CONF_EXTERNAL_URL = "external_url"
|
||||
CONF_FILE_PATH = "file_path"
|
||||
CONF_FILENAME = "filename"
|
||||
CONF_FILE_PATH = "file_path"
|
||||
CONF_FOR = "for"
|
||||
CONF_FORCE_UPDATE = "force_update"
|
||||
CONF_FRIENDLY_NAME = "friendly_name"
|
||||
|
@ -138,15 +139,15 @@ CONF_RADIUS = "radius"
|
|||
CONF_RECIPIENT = "recipient"
|
||||
CONF_REGION = "region"
|
||||
CONF_RESOURCE = "resource"
|
||||
CONF_RESOURCE_TEMPLATE = "resource_template"
|
||||
CONF_RESOURCES = "resources"
|
||||
CONF_RESOURCE_TEMPLATE = "resource_template"
|
||||
CONF_RGB = "rgb"
|
||||
CONF_ROOM = "room"
|
||||
CONF_SCAN_INTERVAL = "scan_interval"
|
||||
CONF_SCENE = "scene"
|
||||
CONF_SENDER = "sender"
|
||||
CONF_SENSOR_TYPE = "sensor_type"
|
||||
CONF_SENSORS = "sensors"
|
||||
CONF_SENSOR_TYPE = "sensor_type"
|
||||
CONF_SERVICE = "service"
|
||||
CONF_SERVICE_DATA = "data"
|
||||
CONF_SERVICE_TEMPLATE = "service_template"
|
||||
|
@ -159,8 +160,8 @@ CONF_STATE_TEMPLATE = "state_template"
|
|||
CONF_STRUCTURE = "structure"
|
||||
CONF_SWITCHES = "switches"
|
||||
CONF_TEMPERATURE_UNIT = "temperature_unit"
|
||||
CONF_TIME_ZONE = "time_zone"
|
||||
CONF_TIMEOUT = "timeout"
|
||||
CONF_TIME_ZONE = "time_zone"
|
||||
CONF_TOKEN = "token"
|
||||
CONF_TRIGGER_TIME = "trigger_time"
|
||||
CONF_TTL = "ttl"
|
||||
|
@ -174,9 +175,9 @@ CONF_VERIFY_SSL = "verify_ssl"
|
|||
CONF_WAIT_TEMPLATE = "wait_template"
|
||||
CONF_WEBHOOK_ID = "webhook_id"
|
||||
CONF_WEEKDAY = "weekday"
|
||||
CONF_WHITE_VALUE = "white_value"
|
||||
CONF_WHITELIST = "whitelist"
|
||||
CONF_WHITELIST_EXTERNAL_DIRS = "whitelist_external_dirs"
|
||||
CONF_WHITE_VALUE = "white_value"
|
||||
CONF_XY = "xy"
|
||||
CONF_ZONE = "zone"
|
||||
|
||||
|
|
|
@ -1332,6 +1332,9 @@ class Config:
|
|||
# List of allowed external dirs to access
|
||||
self.whitelist_external_dirs: Set[str] = set()
|
||||
|
||||
# List of allowed external URLs that integrations may use
|
||||
self.allowlist_external_urls: Set[str] = set()
|
||||
|
||||
# If Home Assistant is running in safe mode
|
||||
self.safe_mode: bool = False
|
||||
|
||||
|
@ -1353,6 +1356,16 @@ class Config:
|
|||
raise HomeAssistantError("config_dir is not set")
|
||||
return os.path.join(self.config_dir, *path)
|
||||
|
||||
def is_allowed_external_url(self, url: str) -> bool:
|
||||
"""Check if an external URL is allowed."""
|
||||
parsed_url = f"{str(yarl.URL(url))}/"
|
||||
|
||||
return any(
|
||||
allowed
|
||||
for allowed in self.allowlist_external_urls
|
||||
if parsed_url.startswith(allowed)
|
||||
)
|
||||
|
||||
def is_allowed_path(self, path: str) -> bool:
|
||||
"""Check if the path is valid for access from outside."""
|
||||
assert path is not None
|
||||
|
@ -1395,6 +1408,7 @@ class Config:
|
|||
"components": self.components,
|
||||
"config_dir": self.config_dir,
|
||||
"whitelist_external_dirs": self.whitelist_external_dirs,
|
||||
"allowlist_external_urls": self.allowlist_external_urls,
|
||||
"version": __version__,
|
||||
"config_source": self.config_source,
|
||||
"safe_mode": self.safe_mode,
|
||||
|
|
|
@ -212,6 +212,8 @@ async def test_api_get_config(hass, mock_api_client):
|
|||
result["components"] = set(result["components"])
|
||||
if "whitelist_external_dirs" in result:
|
||||
result["whitelist_external_dirs"] = set(result["whitelist_external_dirs"])
|
||||
if "allowlist_external_urls" in result:
|
||||
result["allowlist_external_urls"] = set(result["allowlist_external_urls"])
|
||||
|
||||
assert hass.config.as_dict() == result
|
||||
|
||||
|
|
|
@ -234,6 +234,10 @@ async def test_get_config(hass, websocket_client):
|
|||
msg["result"]["whitelist_external_dirs"] = set(
|
||||
msg["result"]["whitelist_external_dirs"]
|
||||
)
|
||||
if "allowlist_external_urls" in msg["result"]:
|
||||
msg["result"]["allowlist_external_urls"] = set(
|
||||
msg["result"]["allowlist_external_urls"]
|
||||
)
|
||||
|
||||
assert msg["result"] == hass.config.as_dict()
|
||||
|
||||
|
|
|
@ -915,6 +915,7 @@ class TestConfig(unittest.TestCase):
|
|||
"components": set(),
|
||||
"config_dir": "/test/ha-config",
|
||||
"whitelist_external_dirs": set(),
|
||||
"allowlist_external_urls": set(),
|
||||
"version": __version__,
|
||||
"config_source": "default",
|
||||
"safe_mode": False,
|
||||
|
@ -955,6 +956,33 @@ class TestConfig(unittest.TestCase):
|
|||
with pytest.raises(AssertionError):
|
||||
self.config.is_allowed_path(None)
|
||||
|
||||
def test_is_allowed_external_url(self):
|
||||
"""Test is_allowed_external_url method."""
|
||||
self.config.allowlist_external_urls = [
|
||||
"http://x.com/",
|
||||
"https://y.com/bla/",
|
||||
"https://z.com/images/1.jpg/",
|
||||
]
|
||||
|
||||
valid = [
|
||||
"http://x.com/1.jpg",
|
||||
"http://x.com",
|
||||
"https://y.com/bla/",
|
||||
"https://y.com/bla/2.png",
|
||||
"https://z.com/images/1.jpg",
|
||||
]
|
||||
for url in valid:
|
||||
assert self.config.is_allowed_external_url(url)
|
||||
|
||||
invalid = [
|
||||
"https://a.co",
|
||||
"https://y.com/bla_wrong",
|
||||
"https://y.com/bla/../image.jpg",
|
||||
"https://z.com/images",
|
||||
]
|
||||
for url in invalid:
|
||||
assert not self.config.is_allowed_external_url(url)
|
||||
|
||||
|
||||
async def test_event_on_update(hass):
|
||||
"""Test that event is fired on update."""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue