File size: 5,774 Bytes
310260a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | """
Cross-User Isolation Security Tests
Comprehensive tests to ensure complete data isolation between users.
"""
import pytest
from src.tools.create_task import create_task_internal
from src.tools.list_tasks import list_tasks_internal
from src.tools.get_task import get_task_internal
from src.tools.mark_complete import mark_complete_internal
from src.tools.update_task import update_task_internal
from src.tools.delete_task import delete_task_internal
from tests.utils.task_helpers import create_test_task
@pytest.mark.security
@pytest.mark.asyncio
async def test_user1_cannot_access_user2_tasks_via_any_tool(mock_mcp_context, mock_mcp_context_user2, test_session):
"""
Test: User1 cannot access User2 tasks via any tool
Comprehensive test ensuring complete data isolation across all tools.
"""
# Setup: Create tasks for user 2
user2_task = create_test_task(test_session, mock_mcp_context_user2.user_id, title="User 2 Task")
# Test 1: User 1 cannot list user 2's tasks
list_result = await list_tasks_internal(ctx=mock_mcp_context)
assert list_result["total"] == 0
assert len(list_result["tasks"]) == 0
# Test 2: User 1 cannot get user 2's task by ID
get_result = await get_task_internal(ctx=mock_mcp_context, task_id=user2_task.id)
assert get_result["status"] == "error"
assert "not found" in get_result["error"].lower()
# Test 3: User 1 cannot mark user 2's task complete
mark_result = await mark_complete_internal(ctx=mock_mcp_context, task_id=user2_task.id)
assert mark_result["status"] == "error"
assert "not found" in mark_result["error"].lower()
# Test 4: User 1 cannot update user 2's task
update_result = await update_task_internal(
ctx=mock_mcp_context,
task_id=user2_task.id,
title="Hacked"
)
assert update_result["status"] == "error"
assert "not found" in update_result["error"].lower()
# Test 5: User 1 cannot delete user 2's task
delete_result = await delete_task_internal(ctx=mock_mcp_context, task_id=user2_task.id)
assert delete_result["status"] == "error"
assert "not found" in delete_result["error"].lower()
@pytest.mark.security
@pytest.mark.asyncio
async def test_user1_cannot_modify_user2_tasks_via_any_tool(mock_mcp_context, mock_mcp_context_user2, test_session):
"""
Test: User1 cannot modify User2 tasks via any tool
Verifies that all modification operations respect user boundaries.
"""
# Setup: Create task for user 2
user2_task = create_test_task(
test_session,
mock_mcp_context_user2.user_id,
title="Original Title",
description="Original Description",
completed=False
)
# Attempt 1: Mark complete
await mark_complete_internal(ctx=mock_mcp_context, task_id=user2_task.id)
# Attempt 2: Update
await update_task_internal(
ctx=mock_mcp_context,
task_id=user2_task.id,
title="Modified Title"
)
# Attempt 3: Delete
await delete_task_internal(ctx=mock_mcp_context, task_id=user2_task.id)
# Verify: User 2's task remains unchanged
from tests.utils.task_helpers import get_task_by_id
unchanged_task = get_task_by_id(test_session, user2_task.id)
assert unchanged_task is not None
assert unchanged_task.title == "Original Title"
assert unchanged_task.description == "Original Description"
assert unchanged_task.completed is False
@pytest.mark.security
@pytest.mark.asyncio
async def test_user1_cannot_delete_user2_tasks_via_any_tool(mock_mcp_context, mock_mcp_context_user2, test_session):
"""
Test: User1 cannot delete User2 tasks via any tool
Verifies that delete operations respect user boundaries.
"""
# Setup: Create multiple tasks for user 2
from tests.utils.task_helpers import create_multiple_tasks
user2_tasks = create_multiple_tasks(test_session, mock_mcp_context_user2.user_id, count=5)
# User 1 attempts to delete all user 2's tasks
for task in user2_tasks:
result = await delete_task_internal(ctx=mock_mcp_context, task_id=task.id)
assert result["status"] == "error"
# Verify: All user 2's tasks still exist
from tests.utils.task_helpers import count_tasks
user2_task_count = count_tasks(test_session, mock_mcp_context_user2.user_id)
assert user2_task_count == 5
@pytest.mark.security
@pytest.mark.asyncio
async def test_complete_isolation_between_three_users(mock_mcp_context, mock_mcp_context_user2, test_session):
"""
Test: Complete isolation between three users
Verifies data isolation works correctly with multiple users.
"""
# Create third user context
from src.tools.mcp_server import MCPContext
user3_context = MCPContext(user_id=3)
# Create tasks for all three users
from tests.utils.task_helpers import create_multiple_tasks
create_multiple_tasks(test_session, mock_mcp_context.user_id, count=3, title_prefix="User1")
create_multiple_tasks(test_session, mock_mcp_context_user2.user_id, count=4, title_prefix="User2")
create_multiple_tasks(test_session, user3_context.user_id, count=5, title_prefix="User3")
# Verify each user sees only their own tasks
result1 = await list_tasks_internal(ctx=mock_mcp_context)
assert result1["total"] == 3
result2 = await list_tasks_internal(ctx=mock_mcp_context_user2)
assert result2["total"] == 4
result3 = await list_tasks_internal(ctx=user3_context)
assert result3["total"] == 5
# Verify no cross-contamination
for task in result1["tasks"]:
assert "User1" in task["title"]
for task in result2["tasks"]:
assert "User2" in task["title"]
for task in result3["tasks"]:
assert "User3" in task["title"]
|