test_security_permissions.py•46.7 kB
"""Comprehensive tests for security.permissions module."""
import os
import stat
import tempfile
from pathlib import Path
import pytest
from mnemex.security.permissions import (
DIR_PERMISSIONS,
FILE_PERMISSIONS,
check_permissions,
ensure_secure_storage,
secure_config_file,
secure_directory,
secure_file,
)
IS_POSIX = os.name != "nt"
class TestSecureFile:
"""Tests for secure_file function."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_set_permissions_on_existing_file(self):
"""Test setting permissions on an existing file."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("test content")
secure_file(test_file)
file_stat = test_file.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == FILE_PERMISSIONS
assert actual_perms == 0o600
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_create_file_with_create_if_missing_true(self):
"""Test creating a file with create_if_missing=True."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "new_file.txt"
assert not test_file.exists()
secure_file(test_file, create_if_missing=True)
assert test_file.exists()
file_stat = test_file.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == FILE_PERMISSIONS
def test_file_not_found_when_create_if_missing_false(self):
"""Test FileNotFoundError when file doesn't exist and create_if_missing=False."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "nonexistent.txt"
with pytest.raises(FileNotFoundError, match="File not found"):
secure_file(test_file, create_if_missing=False)
def test_value_error_when_path_is_directory(self):
"""Test ValueError when path is a directory not a file."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "directory"
test_dir.mkdir()
with pytest.raises(ValueError, match="Path is not a file"):
secure_file(test_dir)
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_custom_permissions(self):
"""Test setting custom permissions (not just default)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
custom_perms = 0o640
secure_file(test_file, permissions=custom_perms)
file_stat = test_file.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == custom_perms
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_verify_actual_permissions_after_setting(self):
"""Test verifying actual file permissions after setting."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o777)
initial_perms = stat.S_IMODE(test_file.stat().st_mode)
assert initial_perms == 0o777
secure_file(test_file)
final_perms = stat.S_IMODE(test_file.stat().st_mode)
assert final_perms == FILE_PERMISSIONS
assert final_perms == 0o600
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_secure_file_with_string_path(self):
"""Test secure_file accepts string paths."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
secure_file(str(test_file))
file_stat = test_file.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == FILE_PERMISSIONS
class TestSecureDirectory:
"""Tests for secure_directory function."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_set_permissions_on_existing_directory(self):
"""Test setting permissions on an existing directory."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
secure_directory(test_dir)
dir_stat = test_dir.stat()
actual_perms = stat.S_IMODE(dir_stat.st_mode)
assert actual_perms == DIR_PERMISSIONS
assert actual_perms == 0o700
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_create_directory_with_create_if_missing_true(self):
"""Test creating a directory with create_if_missing=True."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "new_dir"
assert not test_dir.exists()
secure_directory(test_dir, create_if_missing=True)
assert test_dir.exists()
dir_stat = test_dir.stat()
actual_perms = stat.S_IMODE(dir_stat.st_mode)
assert actual_perms == DIR_PERMISSIONS
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_create_nested_directory_with_create_if_missing_true(self):
"""Test creating nested directories with create_if_missing=True."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "level1" / "level2" / "level3"
assert not test_dir.exists()
secure_directory(test_dir, create_if_missing=True)
assert test_dir.exists()
dir_stat = test_dir.stat()
actual_perms = stat.S_IMODE(dir_stat.st_mode)
assert actual_perms == DIR_PERMISSIONS
def test_directory_not_found_when_create_if_missing_false(self):
"""Test FileNotFoundError when directory doesn't exist and create_if_missing=False."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "nonexistent"
with pytest.raises(FileNotFoundError, match="Directory not found"):
secure_directory(test_dir, create_if_missing=False)
def test_value_error_when_path_is_file(self):
"""Test ValueError when path is a file not a directory."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "file.txt"
test_file.write_text("content")
with pytest.raises(ValueError, match="Path is not a directory"):
secure_directory(test_file)
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_custom_permissions(self):
"""Test setting custom permissions on a directory."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
custom_perms = 0o750
secure_directory(test_dir, permissions=custom_perms)
dir_stat = test_dir.stat()
actual_perms = stat.S_IMODE(dir_stat.st_mode)
assert actual_perms == custom_perms
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_recursive_mode_secures_subdirectories(self):
"""Test recursive mode secures all subdirectories."""
with tempfile.TemporaryDirectory() as tmpdir:
base_dir = Path(tmpdir) / "base"
base_dir.mkdir()
sub1 = base_dir / "sub1"
sub1.mkdir()
sub2 = base_dir / "sub2"
sub2.mkdir()
nested = sub1 / "nested"
nested.mkdir()
os.chmod(sub1, 0o755)
os.chmod(sub2, 0o755)
os.chmod(nested, 0o755)
secure_directory(base_dir, recursive=True)
assert stat.S_IMODE(base_dir.stat().st_mode) == DIR_PERMISSIONS
assert stat.S_IMODE(sub1.stat().st_mode) == DIR_PERMISSIONS
assert stat.S_IMODE(sub2.stat().st_mode) == DIR_PERMISSIONS
assert stat.S_IMODE(nested.stat().st_mode) == DIR_PERMISSIONS
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_recursive_mode_does_not_affect_files(self):
"""Test recursive mode doesn't modify file permissions."""
with tempfile.TemporaryDirectory() as tmpdir:
base_dir = Path(tmpdir) / "base"
base_dir.mkdir()
test_file = base_dir / "file.txt"
test_file.write_text("content")
os.chmod(test_file, 0o644)
secure_directory(base_dir, recursive=True)
assert stat.S_IMODE(test_file.stat().st_mode) == 0o644
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_verify_actual_permissions_after_setting(self):
"""Test verifying actual directory permissions after setting."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
os.chmod(test_dir, 0o777)
initial_perms = stat.S_IMODE(test_dir.stat().st_mode)
assert initial_perms == 0o777
secure_directory(test_dir)
final_perms = stat.S_IMODE(test_dir.stat().st_mode)
assert final_perms == DIR_PERMISSIONS
assert final_perms == 0o700
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_secure_directory_with_string_path(self):
"""Test secure_directory accepts string paths."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
secure_directory(str(test_dir))
dir_stat = test_dir.stat()
actual_perms = stat.S_IMODE(dir_stat.st_mode)
assert actual_perms == DIR_PERMISSIONS
class TestCheckPermissionsAsIsSecureFile:
"""Tests for check_permissions as is_secure_file validator."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_secure_permissions_0o600(self):
"""Test files with secure permissions (0o600)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "secure.txt"
test_file.write_text("content")
os.chmod(test_file, 0o600)
result = check_permissions(test_file)
assert result["is_secure"] is True
assert result["current"] == "0o600"
assert result["expected"] == "0o600"
assert result["world_readable"] is False
assert result["group_readable"] is False
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_insecure_permissions_0o644(self):
"""Test files with insecure permissions (0o644)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "insecure.txt"
test_file.write_text("content")
os.chmod(test_file, 0o644)
result = check_permissions(test_file)
assert result["is_secure"] is False
assert result["current"] == "0o644"
assert result["world_readable"] is True
assert result["group_readable"] is True
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_insecure_permissions_0o666(self):
"""Test files with insecure permissions (0o666)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "insecure.txt"
test_file.write_text("content")
os.chmod(test_file, 0o666)
result = check_permissions(test_file)
assert result["is_secure"] is False
assert result["current"] == "0o666"
assert result["world_readable"] is True
assert result["world_writable"] is True
assert result["group_readable"] is True
assert result["group_writable"] is True
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_insecure_permissions_0o777(self):
"""Test files with insecure permissions (0o777)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "insecure.txt"
test_file.write_text("content")
os.chmod(test_file, 0o777)
result = check_permissions(test_file)
assert result["is_secure"] is False
assert result["current"] == "0o777"
assert result["world_readable"] is True
assert result["world_writable"] is True
def test_nonexistent_file_raises_error(self):
"""Test non-existent files raise FileNotFoundError."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "nonexistent.txt"
with pytest.raises(FileNotFoundError, match="Path not found"):
check_permissions(test_file)
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_directory_detected_as_insecure_for_file_check(self):
"""Test directories are detected as insecure when expecting file permissions."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "directory"
test_dir.mkdir()
os.chmod(test_dir, 0o700)
result = check_permissions(test_dir, expected_permissions=FILE_PERMISSIONS)
assert result["is_secure"] is False
assert result["expected"] == "0o600"
class TestCheckPermissionsAsIsSecureDirectory:
"""Tests for check_permissions as is_secure_directory validator."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_directory_with_secure_permissions_0o700(self):
"""Test directories with secure permissions (0o700)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "secure_dir"
test_dir.mkdir()
os.chmod(test_dir, 0o700)
result = check_permissions(test_dir)
assert result["is_secure"] is True
assert result["current"] == "0o700"
assert result["expected"] == "0o700"
assert result["world_readable"] is False
assert result["group_readable"] is False
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_directory_with_insecure_permissions_0o755(self):
"""Test directories with insecure permissions (0o755)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "insecure_dir"
test_dir.mkdir()
os.chmod(test_dir, 0o755)
result = check_permissions(test_dir)
assert result["is_secure"] is False
assert result["current"] == "0o755"
assert result["world_readable"] is True
assert result["group_readable"] is True
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_directory_with_insecure_permissions_0o777(self):
"""Test directories with insecure permissions (0o777)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "insecure_dir"
test_dir.mkdir()
os.chmod(test_dir, 0o777)
result = check_permissions(test_dir)
assert result["is_secure"] is False
assert result["current"] == "0o777"
assert result["world_readable"] is True
assert result["world_writable"] is True
def test_nonexistent_directory_raises_error(self):
"""Test non-existent directories raise FileNotFoundError."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "nonexistent"
with pytest.raises(FileNotFoundError, match="Path not found"):
check_permissions(test_dir)
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_detected_as_insecure_for_directory_check(self):
"""Test files are detected as insecure when expecting directory permissions."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "file.txt"
test_file.write_text("content")
os.chmod(test_file, 0o600)
result = check_permissions(test_file, expected_permissions=DIR_PERMISSIONS)
assert result["is_secure"] is False
assert result["expected"] == "0o700"
class TestCheckPermissionsGroupAndOtherAccess:
"""Tests for check_permissions detecting group and other access."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_group_read_access(self):
"""Test files with group read access."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o640)
result = check_permissions(test_file)
assert result["group_readable"] is True
assert result["group_writable"] is False
assert result["world_readable"] is False
assert result["is_secure"] is False
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_group_write_access(self):
"""Test files with group write access."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o620)
result = check_permissions(test_file)
assert result["group_readable"] is False
assert result["group_writable"] is True
assert result["is_secure"] is False
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_other_read_access(self):
"""Test files with other (world) read access."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o604)
result = check_permissions(test_file)
assert result["world_readable"] is True
assert result["world_writable"] is False
assert result["group_readable"] is False
assert result["is_secure"] is False
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_other_write_access(self):
"""Test files with other (world) write access."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o602)
result = check_permissions(test_file)
assert result["world_readable"] is False
assert result["world_writable"] is True
assert result["is_secure"] is False
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_file_with_only_owner_access(self):
"""Test files with only owner access (secure)."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o600)
result = check_permissions(test_file)
assert result["group_readable"] is False
assert result["group_writable"] is False
assert result["world_readable"] is False
assert result["world_writable"] is False
assert result["is_secure"] is True
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_directory_with_group_and_other_access(self):
"""Test directories with both group and other access."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
os.chmod(test_dir, 0o755)
result = check_permissions(test_dir)
assert result["group_readable"] is True
assert result["world_readable"] is True
assert result["is_secure"] is False
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_various_permission_combinations(self):
"""Test various permission combinations."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
test_cases = [
(0o700, False, False, False, False),
(0o750, True, False, False, False),
(0o705, False, False, True, False),
(0o777, True, True, True, True),
(0o644, True, False, True, False),
(0o664, True, True, True, False),
]
for perms, grp_r, grp_w, wld_r, wld_w in test_cases:
os.chmod(test_file, perms)
result = check_permissions(test_file)
assert result["group_readable"] == grp_r, f"Failed for {oct(perms)}"
assert result["group_writable"] == grp_w, f"Failed for {oct(perms)}"
assert result["world_readable"] == wld_r, f"Failed for {oct(perms)}"
assert result["world_writable"] == wld_w, f"Failed for {oct(perms)}"
class TestSecureConfigFile:
"""Tests for secure_config_file function."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_secure_existing_config_file(self):
"""Test securing an existing config file."""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / ".env"
config_file.write_text("API_KEY=secret")
secure_config_file(config_file)
file_stat = config_file.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == 0o600
def test_config_file_not_found(self):
"""Test FileNotFoundError when config file doesn't exist."""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / "nonexistent.env"
with pytest.raises(FileNotFoundError, match="Config file not found"):
secure_config_file(config_file)
def test_config_path_is_directory_raises_error(self):
"""Test ValueError when config path is a directory."""
with tempfile.TemporaryDirectory() as tmpdir:
config_dir = Path(tmpdir) / "config"
config_dir.mkdir()
with pytest.raises(ValueError, match="Config path is not a file"):
secure_config_file(config_dir)
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_secure_config_file_with_string_path(self):
"""Test secure_config_file accepts string paths."""
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / ".env"
config_file.write_text("SECRET=value")
secure_config_file(str(config_file))
file_stat = config_file.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == 0o600
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_secure_config_file_with_tilde_expansion(self):
"""Test secure_config_file handles tilde expansion."""
import tempfile
with tempfile.NamedTemporaryFile(mode="w", suffix=".env", delete=False) as tf:
tf.write("TEST=value")
temp_path = Path(tf.name)
try:
secure_config_file(temp_path)
file_stat = temp_path.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == 0o600
finally:
temp_path.unlink()
class TestEnsureSecureStorage:
"""Tests for ensure_secure_storage function."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_secure_storage_directory_and_files(self):
"""Test securing storage directory and all contained files."""
with tempfile.TemporaryDirectory() as tmpdir:
storage_dir = Path(tmpdir) / "storage"
storage_dir.mkdir()
file1 = storage_dir / "mem1.jsonl"
file1.write_text("data1")
file2 = storage_dir / "mem2.jsonl"
file2.write_text("data2")
os.chmod(file1, 0o644)
os.chmod(file2, 0o644)
stats = ensure_secure_storage(storage_dir)
assert stats["files"] == 2
assert stats["directories"] == 1
assert stats["errors"] == 0
assert stat.S_IMODE(storage_dir.stat().st_mode) == DIR_PERMISSIONS
assert stat.S_IMODE(file1.stat().st_mode) == FILE_PERMISSIONS
assert stat.S_IMODE(file2.stat().st_mode) == FILE_PERMISSIONS
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_secure_storage_with_subdirectories(self):
"""Test securing storage with nested subdirectories."""
with tempfile.TemporaryDirectory() as tmpdir:
storage_dir = Path(tmpdir) / "storage"
storage_dir.mkdir()
subdir1 = storage_dir / "sub1"
subdir1.mkdir()
subdir2 = storage_dir / "sub2"
subdir2.mkdir()
nested = subdir1 / "nested"
nested.mkdir()
file1 = storage_dir / "file1.txt"
file1.write_text("data")
file2 = subdir1 / "file2.txt"
file2.write_text("data")
file3 = nested / "file3.txt"
file3.write_text("data")
stats = ensure_secure_storage(storage_dir)
assert stats["files"] == 3
assert stats["directories"] >= 3
assert stats["errors"] == 0
assert stat.S_IMODE(storage_dir.stat().st_mode) == DIR_PERMISSIONS
assert stat.S_IMODE(subdir1.stat().st_mode) == DIR_PERMISSIONS
assert stat.S_IMODE(subdir2.stat().st_mode) == DIR_PERMISSIONS
assert stat.S_IMODE(nested.stat().st_mode) == DIR_PERMISSIONS
assert stat.S_IMODE(file1.stat().st_mode) == FILE_PERMISSIONS
assert stat.S_IMODE(file2.stat().st_mode) == FILE_PERMISSIONS
assert stat.S_IMODE(file3.stat().st_mode) == FILE_PERMISSIONS
def test_storage_path_not_found(self):
"""Test FileNotFoundError when storage path doesn't exist."""
with tempfile.TemporaryDirectory() as tmpdir:
storage_dir = Path(tmpdir) / "nonexistent"
with pytest.raises(FileNotFoundError, match="Storage path not found"):
ensure_secure_storage(storage_dir)
def test_storage_path_is_file_raises_error(self):
"""Test ValueError when storage path is a file."""
with tempfile.TemporaryDirectory() as tmpdir:
storage_file = Path(tmpdir) / "file.txt"
storage_file.write_text("content")
with pytest.raises(ValueError, match="Storage path is not a directory"):
ensure_secure_storage(storage_file)
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_custom_file_and_directory_permissions(self):
"""Test ensure_secure_storage with custom permissions."""
with tempfile.TemporaryDirectory() as tmpdir:
storage_dir = Path(tmpdir) / "storage"
storage_dir.mkdir()
file1 = storage_dir / "file.txt"
file1.write_text("data")
custom_file_perms = 0o640
custom_dir_perms = 0o750
stats = ensure_secure_storage(
storage_dir, file_permissions=custom_file_perms, dir_permissions=custom_dir_perms
)
assert stats["files"] == 1
assert stats["directories"] == 1
assert stat.S_IMODE(storage_dir.stat().st_mode) == custom_dir_perms
assert stat.S_IMODE(file1.stat().st_mode) == custom_file_perms
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_empty_storage_directory(self):
"""Test securing an empty storage directory."""
with tempfile.TemporaryDirectory() as tmpdir:
storage_dir = Path(tmpdir) / "storage"
storage_dir.mkdir()
stats = ensure_secure_storage(storage_dir)
assert stats["files"] == 0
assert stats["directories"] == 1
assert stats["errors"] == 0
assert stat.S_IMODE(storage_dir.stat().st_mode) == DIR_PERMISSIONS
class TestCheckPermissions:
"""Tests for check_permissions function."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_check_permissions_secure_file(self):
"""Test check_permissions on a secure file."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "secure.txt"
test_file.write_text("content")
os.chmod(test_file, 0o600)
result = check_permissions(test_file)
assert result["current"] == "0o600"
assert result["expected"] == "0o600"
assert result["is_secure"] is True
assert "secure" in result["recommendation"].lower()
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_check_permissions_insecure_file(self):
"""Test check_permissions on an insecure file."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "insecure.txt"
test_file.write_text("content")
os.chmod(test_file, 0o644)
result = check_permissions(test_file)
assert result["current"] == "0o644"
assert result["is_secure"] is False
assert "0o600" in result["recommendation"]
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_check_permissions_with_custom_expected_permissions(self):
"""Test check_permissions with custom expected permissions."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o640)
result = check_permissions(test_file, expected_permissions=0o640)
assert result["current"] == "0o640"
assert result["expected"] == "0o640"
assert result["is_secure"] is True
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_check_permissions_auto_detects_directory(self):
"""Test check_permissions auto-detects directory type."""
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
os.chmod(test_dir, 0o700)
result = check_permissions(test_dir)
assert result["expected"] == "0o700"
assert result["is_secure"] is True
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_check_permissions_returns_all_fields(self):
"""Test check_permissions returns all expected fields."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o644)
result = check_permissions(test_file)
required_fields = [
"current",
"expected",
"is_secure",
"world_readable",
"world_writable",
"group_readable",
"group_writable",
"recommendation",
]
for field in required_fields:
assert field in result
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_check_permissions_with_string_path(self):
"""Test check_permissions accepts string paths."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o600)
result = check_permissions(str(test_file))
assert result["is_secure"] is True
def test_check_permissions_nonexistent_path(self):
"""Test check_permissions raises error for nonexistent path."""
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "nonexistent.txt"
with pytest.raises(FileNotFoundError, match="Path not found"):
check_permissions(test_file)
class TestMain:
"""Tests for the main() CLI function."""
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_main_check_secure_file(self, monkeypatch, capsys):
"""Test main with --check flag on a secure file."""
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "secure.txt"
test_file.write_text("content")
os.chmod(test_file, 0o600)
monkeypatch.setattr("sys.argv", ["prog", str(test_file), "--check"])
result = main()
assert result == 0
captured = capsys.readouterr()
assert "Secure: True" in captured.out
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_main_check_insecure_file(self, monkeypatch, capsys):
"""Test main with --check flag on an insecure file."""
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "insecure.txt"
test_file.write_text("content")
os.chmod(test_file, 0o644)
monkeypatch.setattr("sys.argv", ["prog", str(test_file), "--check"])
result = main()
assert result == 1
captured = capsys.readouterr()
assert "Secure: False" in captured.out
assert "⚠️" in captured.out
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_main_secure_config_file(self, monkeypatch, capsys):
"""Test main with --config flag."""
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / ".env"
config_file.write_text("API_KEY=secret")
monkeypatch.setattr("sys.argv", ["prog", str(config_file), "--config"])
result = main()
assert result == 0
captured = capsys.readouterr()
assert "Secured config file" in captured.out
file_stat = config_file.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == 0o600
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_main_secure_single_file(self, monkeypatch, capsys):
"""Test main securing a single file."""
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
os.chmod(test_file, 0o644)
monkeypatch.setattr("sys.argv", ["prog", str(test_file)])
result = main()
assert result == 0
captured = capsys.readouterr()
assert "Secured file" in captured.out
file_stat = test_file.stat()
actual_perms = stat.S_IMODE(file_stat.st_mode)
assert actual_perms == 0o600
@pytest.mark.skipif(not IS_POSIX, reason="Unix permissions not applicable on Windows")
def test_main_secure_directory_non_recursive(self, monkeypatch, capsys):
"""Test main securing a directory without --recursive."""
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
os.chmod(test_dir, 0o755)
monkeypatch.setattr("sys.argv", ["prog", str(test_dir)])
result = main()
assert result == 0
captured = capsys.readouterr()
assert "Secured directory" in captured.out
dir_stat = test_dir.stat()
actual_perms = stat.S_IMODE(dir_stat.st_mode)
assert actual_perms == 0o700
def test_main_secure_directory_recursive(self, monkeypatch, capsys):
"""Test main securing a directory with --recursive."""
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
subdir = test_dir / "subdir"
subdir.mkdir()
file1 = test_dir / "file1.txt"
file1.write_text("data")
monkeypatch.setattr("sys.argv", ["prog", str(test_dir), "--recursive"])
result = main()
assert result == 0
captured = capsys.readouterr()
assert "Secured directory" in captured.out
assert "Files secured:" in captured.out
assert "Directories secured:" in captured.out
def test_main_recursive_with_errors(self, monkeypatch, capsys):
"""Test main with --recursive when there are permission errors."""
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
file1 = test_dir / "file1.txt"
file1.write_text("data")
monkeypatch.setattr("sys.argv", ["prog", str(test_dir), "--recursive"])
result = main()
if result == 1:
captured = capsys.readouterr()
assert "Errors:" in captured.out
else:
assert result == 0
def test_main_nonexistent_path(self, monkeypatch, capsys):
"""Test main with a nonexistent path."""
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
nonexistent = Path(tmpdir) / "nonexistent"
monkeypatch.setattr("sys.argv", ["prog", str(nonexistent)])
result = main()
assert result == 1
captured = capsys.readouterr()
assert "Error" in captured.err or "Error" in captured.out
def test_main_exception_handling(self, monkeypatch, capsys):
"""Test main exception handling."""
from mnemex.security.permissions import main
monkeypatch.setattr("sys.argv", ["prog", "/nonexistent/path/that/does/not/exist"])
result = main()
assert result == 1
class TestPermissionErrors:
"""Tests for PermissionError exception handling."""
def test_secure_file_permission_error(self, monkeypatch):
"""Test PermissionError when securing a file."""
import os
with tempfile.TemporaryDirectory() as tmpdir:
test_file = Path(tmpdir) / "test.txt"
test_file.write_text("content")
original_chmod = os.chmod
def mock_chmod(path, mode):
if str(path) == str(test_file):
raise PermissionError("Permission denied")
return original_chmod(path, mode)
monkeypatch.setattr(os, "chmod", mock_chmod)
with pytest.raises(PermissionError, match="Unable to set permissions"):
secure_file(test_file)
def test_secure_directory_permission_error(self, monkeypatch):
"""Test PermissionError when securing a directory."""
import os
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
original_chmod = os.chmod
def mock_chmod(path, mode):
if str(path) == str(test_dir):
raise PermissionError("Permission denied")
return original_chmod(path, mode)
monkeypatch.setattr(os, "chmod", mock_chmod)
with pytest.raises(PermissionError, match="Unable to set permissions"):
secure_directory(test_dir)
def test_secure_directory_recursive_subdirectory_permission_error(self, monkeypatch, capsys):
"""Test PermissionError warning on subdirectory in recursive mode."""
import os
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
subdir = test_dir / "subdir"
subdir.mkdir()
original_chmod = os.chmod
def mock_chmod(path, mode):
if str(path) == str(subdir):
raise PermissionError("Permission denied")
return original_chmod(path, mode)
monkeypatch.setattr(os, "chmod", mock_chmod)
secure_directory(test_dir, recursive=True)
captured = capsys.readouterr()
assert "Warning: Unable to secure subdirectory" in captured.out
@pytest.mark.skipif(
not IS_POSIX or os.uname().sysname == "Darwin",
reason="Permission error behavior differs on macOS/Windows",
)
def test_secure_config_file_permission_error(self, monkeypatch):
"""Test PermissionError when securing a config file."""
import os
with tempfile.TemporaryDirectory() as tmpdir:
config_file = Path(tmpdir) / ".env"
config_file.write_text("SECRET=value")
original_chmod = os.chmod
def mock_chmod(path, mode):
if str(path) == str(config_file):
raise PermissionError("Permission denied")
return original_chmod(path, mode)
monkeypatch.setattr(os, "chmod", mock_chmod)
with pytest.raises(PermissionError, match="Unable to secure config file"):
secure_config_file(config_file)
@pytest.mark.skipif(
not IS_POSIX or os.uname().sysname == "Darwin",
reason="Permission error behavior differs on macOS/Windows",
)
def test_ensure_secure_storage_directory_permission_error(self, monkeypatch, capsys):
"""Test PermissionError on storage directory in ensure_secure_storage."""
import os
with tempfile.TemporaryDirectory() as tmpdir:
storage_dir = Path(tmpdir) / "storage"
storage_dir.mkdir()
original_chmod = os.chmod
def mock_chmod(path, mode):
if str(path) == str(storage_dir):
raise PermissionError("Permission denied")
return original_chmod(path, mode)
monkeypatch.setattr(os, "chmod", mock_chmod)
stats = ensure_secure_storage(storage_dir)
assert stats["errors"] > 0
captured = capsys.readouterr()
assert "Warning: Unable to secure storage directory" in captured.out
@pytest.mark.skipif(
not IS_POSIX or os.uname().sysname == "Darwin",
reason="Permission error behavior differs on macOS/Windows",
)
def test_ensure_secure_storage_file_permission_error(self, monkeypatch, capsys):
"""Test PermissionError on files in ensure_secure_storage."""
import os
with tempfile.TemporaryDirectory() as tmpdir:
storage_dir = Path(tmpdir) / "storage"
storage_dir.mkdir()
test_file = storage_dir / "file.txt"
test_file.write_text("data")
original_chmod = os.chmod
call_count = [0]
def mock_chmod(path, mode):
call_count[0] += 1
if call_count[0] > 1 and str(path) == str(test_file):
raise PermissionError("Permission denied")
return original_chmod(path, mode)
monkeypatch.setattr(os, "chmod", mock_chmod)
stats = ensure_secure_storage(storage_dir)
assert stats["errors"] > 0
captured = capsys.readouterr()
assert "Warning: Unable to secure" in captured.out
def test_main_with_storage_errors_returns_1(self, monkeypatch, capsys):
"""Test main returns 1 when ensure_secure_storage has errors."""
import os
from mnemex.security.permissions import main
with tempfile.TemporaryDirectory() as tmpdir:
test_dir = Path(tmpdir) / "test_dir"
test_dir.mkdir()
test_file = test_dir / "file.txt"
test_file.write_text("data")
original_chmod = os.chmod
call_count = [0]
def mock_chmod(path, mode):
call_count[0] += 1
if call_count[0] > 1:
raise PermissionError("Permission denied")
return original_chmod(path, mode)
monkeypatch.setattr(os, "chmod", mock_chmod)
monkeypatch.setattr("sys.argv", ["prog", str(test_dir), "--recursive"])
result = main()
assert result == 1
captured = capsys.readouterr()
assert "Errors:" in captured.out