test_validations.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import pytest
  2. from unittest.mock import patch
  3. from pathlib import Path
  4. from validations import is_allowed_path, RESTRICTED, RESTRICTED_IN
  5. def mock_resolve(self):
  6. # Don't modify paths that are from RESTRICTED list initialization
  7. if str(self) in [str(p) for p in RESTRICTED]:
  8. return self
  9. # For symlinks that point to restricted paths, return the target path
  10. # without stripping /private/
  11. if str(self).endswith("symlink_restricted"):
  12. return Path("/home") # Return the actual restricted target
  13. # For other paths, strip /private/ if present
  14. return Path(str(self).removeprefix("/private/"))
  15. @pytest.mark.parametrize(
  16. "test_path, expected",
  17. [
  18. # Non-restricted path (should be valid)
  19. ("/tmp/somefile", True),
  20. # Exactly /mnt (restricted_in)
  21. ("/mnt", False),
  22. # Exactly / (restricted_in)
  23. ("/", False),
  24. # Subdirectory inside /mnt/.ix-apps (restricted)
  25. ("/mnt/.ix-apps/something", False),
  26. # A path that is a restricted directory exactly
  27. ("/home", False),
  28. ("/var/log", False),
  29. ("/mnt/.ix-apps", False),
  30. ("/data", False),
  31. # Subdirectory inside e.g. /data
  32. ("/data/subdir", False),
  33. # Not an obviously restricted path
  34. ("/usr/local/share", True),
  35. # Another system path likely not in restricted list
  36. ("/opt/myapp", True),
  37. ],
  38. )
  39. @patch.object(Path, "resolve", mock_resolve)
  40. def test_is_allowed_path_direct(test_path, expected):
  41. """Test direct paths against the is_allowed_path function."""
  42. assert is_allowed_path(test_path) == expected
  43. @patch.object(Path, "resolve", mock_resolve)
  44. def test_is_allowed_path_ix_volume():
  45. """Test that IX volumes are not allowed"""
  46. assert is_allowed_path("/mnt/.ix-apps/something", True)
  47. @patch.object(Path, "resolve", mock_resolve)
  48. def test_is_allowed_path_symlink(tmp_path):
  49. """
  50. Test that a symlink pointing to a restricted directory is detected as invalid,
  51. and a symlink pointing to an allowed directory is valid.
  52. """
  53. # Create a real (allowed) directory and a restricted directory in a temp location
  54. allowed_dir = tmp_path / "allowed_dir"
  55. allowed_dir.mkdir()
  56. restricted_dir = tmp_path / "restricted_dir"
  57. restricted_dir.mkdir()
  58. # We will simulate that "restricted_dir" is actually a symlink link pointing to e.g. "/var/log"
  59. # or we create a subdir to match the restricted pattern.
  60. # For demonstration, let's just patch it to a path in the restricted list.
  61. real_restricted_path = Path("/home") # This is one of the restricted directories
  62. # Create symlinks to test
  63. symlink_allowed = tmp_path / "symlink_allowed"
  64. symlink_restricted = tmp_path / "symlink_restricted"
  65. # Point the symlinks
  66. symlink_allowed.symlink_to(allowed_dir)
  67. symlink_restricted.symlink_to(real_restricted_path)
  68. assert is_allowed_path(str(symlink_allowed)) is True
  69. assert is_allowed_path(str(symlink_restricted)) is False
  70. def test_is_allowed_path_nested_symlink(tmp_path):
  71. """
  72. Test that even a nested symlink that eventually resolves into restricted
  73. directories is seen as invalid.
  74. """
  75. # e.g., Create 2 symlinks that chain to /root
  76. link1 = tmp_path / "link1"
  77. link2 = tmp_path / "link2"
  78. # link2 -> /root
  79. link2.symlink_to(Path("/root"))
  80. # link1 -> link2
  81. link1.symlink_to(link2)
  82. assert is_allowed_path(str(link1)) is False
  83. def test_is_allowed_path_nonexistent(tmp_path):
  84. """
  85. Test a path that does not exist at all. The code calls .resolve() which will
  86. give the absolute path, but if it's not restricted, it should still be valid.
  87. """
  88. nonexistent = tmp_path / "this_does_not_exist"
  89. assert is_allowed_path(str(nonexistent)) is True
  90. @pytest.mark.parametrize(
  91. "test_path",
  92. list(RESTRICTED),
  93. )
  94. @patch.object(Path, "resolve", mock_resolve)
  95. def test_is_allowed_path_restricted_list(test_path):
  96. """Test that all items in the RESTRICTED list are invalid."""
  97. assert is_allowed_path(test_path) is False
  98. @pytest.mark.parametrize(
  99. "test_path",
  100. list(RESTRICTED_IN),
  101. )
  102. def test_is_allowed_path_restricted_in_list(test_path):
  103. """
  104. Test that items in RESTRICTED_IN are invalid.
  105. """
  106. assert is_allowed_path(test_path) is False