Compare commits

...

2 Commits

Author SHA1 Message Date
ludeeus
de7ef86f52 Disallow ' 2023-06-16 12:33:04 +00:00
ludeeus
6f614c91d7 Quote CIFS password to remove strict requirements 2023-06-15 16:57:53 +00:00
4 changed files with 44 additions and 13 deletions

View File

@@ -358,7 +358,8 @@ class CIFSMount(NetworkMount):
def options(self) -> list[str]: def options(self) -> list[str]:
"""Options to use to mount.""" """Options to use to mount."""
return ( return (
super().options + [f"username={self.username}", f"password={self.password}"] super().options
+ [f"username={self.username}", f"password='{self.password}'"]
if self.username if self.username
else [] else []
) )

View File

@@ -21,13 +21,12 @@ from .const import (
RE_MOUNT_NAME = re.compile(r"^\w+$") RE_MOUNT_NAME = re.compile(r"^\w+$")
RE_PATH_PART = re.compile(r"^[^\\\/]+") RE_PATH_PART = re.compile(r"^[^\\\/]+")
RE_MOUNT_OPTION = re.compile(r"^[^,=]+$") RE_MOUNT_OPTION = re.compile(r"^[^']+$")
VALIDATE_NAME = vol.Match(RE_MOUNT_NAME) VALIDATE_NAME = vol.Match(RE_MOUNT_NAME)
VALIDATE_SERVER = vol.Match(RE_PATH_PART) VALIDATE_SERVER = vol.Match(RE_PATH_PART)
VALIDATE_SHARE = vol.Match(RE_PATH_PART) VALIDATE_SHARE = vol.Match(RE_PATH_PART)
VALIDATE_USERNAME = vol.Match(RE_MOUNT_OPTION)
VALIDATE_PASSWORD = vol.Match(RE_MOUNT_OPTION)
_SCHEMA_BASE_MOUNT_CONFIG = vol.Schema( _SCHEMA_BASE_MOUNT_CONFIG = vol.Schema(
{ {
@@ -49,8 +48,8 @@ SCHEMA_MOUNT_CIFS = _SCHEMA_MOUNT_NETWORK.extend(
{ {
vol.Required(ATTR_TYPE): MountType.CIFS.value, vol.Required(ATTR_TYPE): MountType.CIFS.value,
vol.Required(ATTR_SHARE): VALIDATE_SHARE, vol.Required(ATTR_SHARE): VALIDATE_SHARE,
vol.Inclusive(ATTR_USERNAME, "basic_auth"): VALIDATE_USERNAME, vol.Inclusive(ATTR_USERNAME, "basic_auth"): vol.Match(RE_MOUNT_OPTION),
vol.Inclusive(ATTR_PASSWORD, "basic_auth"): VALIDATE_PASSWORD, vol.Inclusive(ATTR_PASSWORD, "basic_auth"): vol.Match(RE_MOUNT_OPTION),
} }
) )

View File

@@ -39,7 +39,7 @@ async def test_cifs_mount(
"server": "test.local", "server": "test.local",
"share": "camera", "share": "camera",
"username": "admin", "username": "admin",
"password": "password", "password": "p@assword!,=",
} }
mount: CIFSMount = Mount.from_dict(coresys, mount_data) mount: CIFSMount = Mount.from_dict(coresys, mount_data)
@@ -54,7 +54,7 @@ async def test_cifs_mount(
assert mount.what == "//test.local/camera" assert mount.what == "//test.local/camera"
assert mount.where == Path("/mnt/data/supervisor/mounts/test") assert mount.where == Path("/mnt/data/supervisor/mounts/test")
assert mount.local_where == tmp_supervisor_data / "mounts" / "test" assert mount.local_where == tmp_supervisor_data / "mounts" / "test"
assert mount.options == ["username=admin", "password=password"] assert mount.options == ["username=admin", "password='p@assword!,='"]
assert not mount.local_where.exists() assert not mount.local_where.exists()
assert mount.to_dict(skip_secrets=False) == mount_data assert mount.to_dict(skip_secrets=False) == mount_data
@@ -73,7 +73,7 @@ async def test_cifs_mount(
"mnt-data-supervisor-mounts-test.mount", "mnt-data-supervisor-mounts-test.mount",
"fail", "fail",
[ [
["Options", Variant("s", "username=admin,password=password")], ["Options", Variant("s", "username=admin,password='p@assword!,='")],
["Type", Variant("s", "cifs")], ["Type", Variant("s", "cifs")],
["Description", Variant("s", "Supervisor cifs mount: test")], ["Description", Variant("s", "Supervisor cifs mount: test")],
["What", Variant("s", "//test.local/camera")], ["What", Variant("s", "//test.local/camera")],

View File

@@ -1,5 +1,7 @@
"""Tests for mount manager validation.""" """Tests for mount manager validation."""
import re
import pytest import pytest
from voluptuous import Invalid from voluptuous import Invalid
@@ -15,6 +17,8 @@ async def test_valid_mounts():
"type": "cifs", "type": "cifs",
"server": "test.local", "server": "test.local",
"share": "test", "share": "test",
"username": "admin",
"password": "p@assword!,=",
} }
) )
@@ -77,12 +81,39 @@ async def test_invalid_cifs():
SCHEMA_MOUNT_CONFIG(base) SCHEMA_MOUNT_CONFIG(base)
# Path is for NFS # Path is for NFS
with pytest.raises(Invalid): with pytest.raises(
SCHEMA_MOUNT_CONFIG({"path": "backups"}) Invalid, match=re.escape("required key not provided @ data['share']")
):
SCHEMA_MOUNT_CONFIG({**base, "path": "backups"})
# Username and password must be together # Username and password must be together
with pytest.raises(Invalid): with pytest.raises(
SCHEMA_MOUNT_CONFIG({"username": "admin"}) Invalid,
match=re.escape(
"some but not all values in the same group of inclusion 'basic_auth' @ data[<basic_auth>]"
),
):
SCHEMA_MOUNT_CONFIG({**base, "share": "test", "username": "admin"})
# Username and password must be together
with pytest.raises(
Invalid,
match=re.escape(
"some but not all values in the same group of inclusion 'basic_auth' @ data[<basic_auth>]"
),
):
SCHEMA_MOUNT_CONFIG({**base, "share": "test", "password": "my=!pass"})
# Invalid password
with pytest.raises(
Invalid,
match=re.escape(
"does not match regular expression ^[^']+$ for dictionary value @ data['password']"
),
):
SCHEMA_MOUNT_CONFIG(
{**base, "share": "test", "username": "admin", "password": "my=!pa'ss,"}
)
async def test_invalid_nfs(): async def test_invalid_nfs():