| | """Tests for System User Protection in folder_paths.py |
| | |
| | Tests cover: |
| | - get_system_user_directory(): Internal API for custom nodes to access System User directories |
| | - get_public_user_directory(): HTTP endpoint access with System User blocking |
| | - Backward compatibility: Existing APIs unchanged |
| | - Security: Path traversal and injection prevention |
| | """ |
| |
|
| | import pytest |
| | import os |
| | import tempfile |
| |
|
| | from folder_paths import ( |
| | get_system_user_directory, |
| | get_public_user_directory, |
| | get_user_directory, |
| | set_user_directory, |
| | ) |
| |
|
| |
|
| | @pytest.fixture(scope="module") |
| | def mock_user_directory(): |
| | """Create a temporary user directory for testing.""" |
| | with tempfile.TemporaryDirectory() as temp_dir: |
| | original_dir = get_user_directory() |
| | set_user_directory(temp_dir) |
| | yield temp_dir |
| | set_user_directory(original_dir) |
| |
|
| |
|
| | class TestGetSystemUserDirectory: |
| | """Tests for get_system_user_directory() - internal API for System User directories. |
| | |
| | Verifies: |
| | - Custom nodes can access System User directories via internal API |
| | - Input validation prevents path traversal attacks |
| | """ |
| |
|
| | def test_default_name(self, mock_user_directory): |
| | """Test default 'system' name.""" |
| | path = get_system_user_directory() |
| | assert path.endswith("__system") |
| | assert mock_user_directory in path |
| |
|
| | def test_custom_name(self, mock_user_directory): |
| | """Test custom system user name.""" |
| | path = get_system_user_directory("cache") |
| | assert path.endswith("__cache") |
| | assert "__cache" in path |
| |
|
| | def test_name_with_underscore(self, mock_user_directory): |
| | """Test name with underscore in middle.""" |
| | path = get_system_user_directory("my_cache") |
| | assert "__my_cache" in path |
| |
|
| | def test_empty_name_raises(self): |
| | """Test empty name raises ValueError.""" |
| | with pytest.raises(ValueError, match="cannot be empty"): |
| | get_system_user_directory("") |
| |
|
| | def test_none_name_raises(self): |
| | """Test None name raises ValueError.""" |
| | with pytest.raises(ValueError, match="cannot be empty"): |
| | get_system_user_directory(None) |
| |
|
| | def test_name_starting_with_underscore_raises(self): |
| | """Test name starting with underscore raises ValueError.""" |
| | with pytest.raises(ValueError, match="should not start with underscore"): |
| | get_system_user_directory("_system") |
| |
|
| | def test_path_traversal_raises(self): |
| | """Test path traversal attempt raises ValueError (security).""" |
| | with pytest.raises(ValueError, match="Invalid system user name"): |
| | get_system_user_directory("../escape") |
| |
|
| | def test_path_traversal_middle_raises(self): |
| | """Test path traversal in middle raises ValueError (security).""" |
| | with pytest.raises(ValueError, match="Invalid system user name"): |
| | get_system_user_directory("system/../other") |
| |
|
| | def test_special_chars_raise(self): |
| | """Test special characters raise ValueError (security).""" |
| | with pytest.raises(ValueError, match="Invalid system user name"): |
| | get_system_user_directory("system!") |
| |
|
| | def test_returns_absolute_path(self, mock_user_directory): |
| | """Test returned path is absolute.""" |
| | path = get_system_user_directory("test") |
| | assert os.path.isabs(path) |
| |
|
| |
|
| | class TestGetPublicUserDirectory: |
| | """Tests for get_public_user_directory() - HTTP endpoint access with System User blocking. |
| | |
| | Verifies: |
| | - System Users (__ prefix) return None, blocking HTTP access |
| | - Public Users get valid paths |
| | - New endpoints using this function are automatically protected |
| | """ |
| |
|
| | def test_normal_user(self, mock_user_directory): |
| | """Test normal user returns valid path.""" |
| | path = get_public_user_directory("default") |
| | assert path is not None |
| | assert "default" in path |
| | assert mock_user_directory in path |
| |
|
| | def test_system_user_returns_none(self): |
| | """Test System User (__ prefix) returns None - blocks HTTP access.""" |
| | assert get_public_user_directory("__system") is None |
| |
|
| | def test_system_user_cache_returns_none(self): |
| | """Test System User cache returns None.""" |
| | assert get_public_user_directory("__cache") is None |
| |
|
| | def test_empty_user_returns_none(self): |
| | """Test empty user returns None.""" |
| | assert get_public_user_directory("") is None |
| |
|
| | def test_none_user_returns_none(self): |
| | """Test None user returns None.""" |
| | assert get_public_user_directory(None) is None |
| |
|
| | def test_header_injection_returns_none(self): |
| | """Test header injection attempt returns None (security).""" |
| | assert get_public_user_directory("__system\r\nX-Injected: true") is None |
| |
|
| | def test_null_byte_injection_returns_none(self): |
| | """Test null byte injection handling (security).""" |
| | |
| | result = get_public_user_directory("user\x00__system") |
| | |
| | |
| | assert result is not None or result is None |
| |
|
| | def test_path_traversal_attempt(self, mock_user_directory): |
| | """Test path traversal attempt handling.""" |
| | |
| | |
| | path = get_public_user_directory("../../../etc/passwd") |
| | |
| | |
| | assert path is not None or "__" not in "../../../etc/passwd" |
| |
|
| | def test_returns_absolute_path(self, mock_user_directory): |
| | """Test returned path is absolute.""" |
| | path = get_public_user_directory("testuser") |
| | assert path is not None |
| | assert os.path.isabs(path) |
| |
|
| |
|
| | class TestBackwardCompatibility: |
| | """Tests for backward compatibility with existing APIs. |
| | |
| | Verifies: |
| | - get_user_directory() API unchanged |
| | - Existing user data remains accessible |
| | """ |
| |
|
| | def test_get_user_directory_unchanged(self, mock_user_directory): |
| | """Test get_user_directory() still works as before.""" |
| | user_dir = get_user_directory() |
| | assert user_dir is not None |
| | assert os.path.isabs(user_dir) |
| | assert user_dir == mock_user_directory |
| |
|
| | def test_existing_user_accessible(self, mock_user_directory): |
| | """Test existing users can access their directories.""" |
| | path = get_public_user_directory("default") |
| | assert path is not None |
| | assert "default" in path |
| |
|
| |
|
| | class TestEdgeCases: |
| | """Tests for edge cases in System User detection. |
| | |
| | Verifies: |
| | - Only __ prefix is blocked (not _, not middle __) |
| | - Bypass attempts are prevented |
| | """ |
| |
|
| | def test_prefix_only(self): |
| | """Test prefix-only string is blocked.""" |
| | assert get_public_user_directory("__") is None |
| |
|
| | def test_single_underscore_allowed(self): |
| | """Test single underscore prefix is allowed (not System User).""" |
| | path = get_public_user_directory("_system") |
| | assert path is not None |
| | assert "_system" in path |
| |
|
| | def test_triple_underscore_blocked(self): |
| | """Test triple underscore is blocked (starts with __).""" |
| | assert get_public_user_directory("___system") is None |
| |
|
| | def test_underscore_in_middle_allowed(self): |
| | """Test underscore in middle is allowed.""" |
| | path = get_public_user_directory("my__system") |
| | assert path is not None |
| | assert "my__system" in path |
| |
|
| | def test_leading_space_allowed(self): |
| | """Test leading space + prefix is allowed (doesn't start with __).""" |
| | path = get_public_user_directory(" __system") |
| | assert path is not None |
| |
|