mcp-security-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,395 @@
|
|
1
|
+
"""
|
2
|
+
Tests for Permission Manager Module
|
3
|
+
|
4
|
+
This module contains comprehensive tests for the PermissionManager class,
|
5
|
+
including role hierarchy management, permission validation, and caching.
|
6
|
+
|
7
|
+
Test Coverage:
|
8
|
+
- PermissionManager initialization
|
9
|
+
- Role and permission validation
|
10
|
+
- Role hierarchy management
|
11
|
+
- Permission caching
|
12
|
+
- CRUD operations for roles and permissions
|
13
|
+
- Error handling and edge cases
|
14
|
+
|
15
|
+
Author: MCP Security Team
|
16
|
+
Version: 1.0.0
|
17
|
+
License: MIT
|
18
|
+
"""
|
19
|
+
|
20
|
+
import json
|
21
|
+
import tempfile
|
22
|
+
from pathlib import Path
|
23
|
+
from unittest.mock import mock_open, patch
|
24
|
+
|
25
|
+
import pytest
|
26
|
+
|
27
|
+
from mcp_security_framework.core.permission_manager import (
|
28
|
+
PermissionConfigurationError,
|
29
|
+
PermissionManager,
|
30
|
+
PermissionValidationError,
|
31
|
+
RoleNotFoundError,
|
32
|
+
)
|
33
|
+
from mcp_security_framework.schemas.config import PermissionConfig
|
34
|
+
from mcp_security_framework.schemas.models import ValidationResult
|
35
|
+
|
36
|
+
|
37
|
+
class TestPermissionManager:
|
38
|
+
"""Test suite for PermissionManager class."""
|
39
|
+
|
40
|
+
def setup_method(self):
|
41
|
+
"""Set up test fixtures before each test method."""
|
42
|
+
# Create temporary roles configuration
|
43
|
+
self.roles_config = {
|
44
|
+
"admin": {
|
45
|
+
"permissions": ["read:*", "write:*", "delete:*", "admin:*"],
|
46
|
+
"inherits": [],
|
47
|
+
},
|
48
|
+
"moderator": {
|
49
|
+
"permissions": ["read:*", "write:posts", "moderate:comments"],
|
50
|
+
"inherits": ["user"],
|
51
|
+
},
|
52
|
+
"user": {
|
53
|
+
"permissions": ["read:posts", "write:posts", "read:comments"],
|
54
|
+
"inherits": [],
|
55
|
+
},
|
56
|
+
"guest": {"permissions": ["read:posts"], "inherits": []},
|
57
|
+
}
|
58
|
+
|
59
|
+
# Create temporary file
|
60
|
+
self.temp_file = tempfile.NamedTemporaryFile(
|
61
|
+
mode="w", suffix=".json", delete=False
|
62
|
+
)
|
63
|
+
json.dump(self.roles_config, self.temp_file)
|
64
|
+
self.temp_file.close()
|
65
|
+
|
66
|
+
# Create config
|
67
|
+
self.config = PermissionConfig(
|
68
|
+
roles_file=self.temp_file.name, permission_cache_enabled=True
|
69
|
+
)
|
70
|
+
|
71
|
+
# Create permission manager
|
72
|
+
self.perm_manager = PermissionManager(self.config)
|
73
|
+
|
74
|
+
def teardown_method(self):
|
75
|
+
"""Clean up after each test method."""
|
76
|
+
# Remove temporary file
|
77
|
+
Path(self.temp_file.name).unlink(missing_ok=True)
|
78
|
+
|
79
|
+
def test_permission_manager_initialization(self):
|
80
|
+
"""Test PermissionManager initialization."""
|
81
|
+
assert self.perm_manager.config == self.config
|
82
|
+
assert self.perm_manager._cache_enabled is True
|
83
|
+
assert len(self.perm_manager._roles) == 4
|
84
|
+
assert "admin" in self.perm_manager._roles
|
85
|
+
assert "moderator" in self.perm_manager._roles
|
86
|
+
assert "user" in self.perm_manager._roles
|
87
|
+
assert "guest" in self.perm_manager._roles
|
88
|
+
|
89
|
+
def test_permission_manager_initialization_invalid_file(self):
|
90
|
+
"""Test PermissionManager initialization with invalid file."""
|
91
|
+
# Create config without roles_file to bypass pydantic validation
|
92
|
+
config = PermissionConfig(permission_cache_enabled=True)
|
93
|
+
config.roles_file = "nonexistent.json"
|
94
|
+
|
95
|
+
with pytest.raises(PermissionConfigurationError):
|
96
|
+
PermissionManager(config)
|
97
|
+
|
98
|
+
def test_permission_manager_initialization_invalid_json(self):
|
99
|
+
"""Test PermissionManager initialization with invalid JSON."""
|
100
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
101
|
+
f.write("invalid json content")
|
102
|
+
temp_file = f.name
|
103
|
+
|
104
|
+
config = PermissionConfig(roles_file=temp_file, permission_cache_enabled=True)
|
105
|
+
|
106
|
+
with pytest.raises(PermissionConfigurationError):
|
107
|
+
PermissionManager(config)
|
108
|
+
|
109
|
+
Path(temp_file).unlink()
|
110
|
+
|
111
|
+
def test_validate_access_success(self):
|
112
|
+
"""Test successful access validation."""
|
113
|
+
result = self.perm_manager.validate_access(["admin"], ["read:*", "write:*"])
|
114
|
+
|
115
|
+
assert result.is_valid is True
|
116
|
+
assert result.status.value == "valid"
|
117
|
+
|
118
|
+
def test_validate_access_with_inheritance(self):
|
119
|
+
"""Test access validation with role inheritance."""
|
120
|
+
result = self.perm_manager.validate_access(
|
121
|
+
["moderator"], ["read:*", "write:posts", "moderate:comments"]
|
122
|
+
)
|
123
|
+
|
124
|
+
assert result.is_valid is True
|
125
|
+
assert result.status.value == "valid"
|
126
|
+
|
127
|
+
def test_validate_access_denied(self):
|
128
|
+
"""Test access validation when access is denied."""
|
129
|
+
result = self.perm_manager.validate_access(
|
130
|
+
["guest"], ["write:posts", "delete:posts"]
|
131
|
+
)
|
132
|
+
|
133
|
+
assert result.is_valid is False
|
134
|
+
assert result.status.value == "invalid"
|
135
|
+
assert result.error_code == -32003
|
136
|
+
assert "Missing permissions" in result.error_message
|
137
|
+
|
138
|
+
def test_validate_access_no_roles(self):
|
139
|
+
"""Test access validation with no user roles."""
|
140
|
+
result = self.perm_manager.validate_access([], ["read:posts"])
|
141
|
+
|
142
|
+
assert result.is_valid is False
|
143
|
+
assert result.status.value == "invalid"
|
144
|
+
assert result.error_code == -32001
|
145
|
+
assert result.error_message == "No user roles provided"
|
146
|
+
|
147
|
+
def test_validate_access_no_permissions(self):
|
148
|
+
"""Test access validation with no required permissions."""
|
149
|
+
result = self.perm_manager.validate_access(["admin"], [])
|
150
|
+
|
151
|
+
assert result.is_valid is True
|
152
|
+
assert result.status.value == "valid"
|
153
|
+
|
154
|
+
def test_validate_access_invalid_role(self):
|
155
|
+
"""Test access validation with invalid role."""
|
156
|
+
result = self.perm_manager.validate_access(["invalid_role"], ["read:posts"])
|
157
|
+
|
158
|
+
assert result.is_valid is False
|
159
|
+
assert result.status.value == "invalid"
|
160
|
+
assert result.error_code == -32002
|
161
|
+
assert "Invalid roles" in result.error_message
|
162
|
+
|
163
|
+
def test_get_effective_permissions(self):
|
164
|
+
"""Test getting effective permissions for user roles."""
|
165
|
+
permissions = self.perm_manager.get_effective_permissions(["admin"])
|
166
|
+
|
167
|
+
assert "read:*" in permissions
|
168
|
+
assert "write:*" in permissions
|
169
|
+
assert "delete:*" in permissions
|
170
|
+
assert "admin:*" in permissions
|
171
|
+
|
172
|
+
def test_get_effective_permissions_with_inheritance(self):
|
173
|
+
"""Test getting effective permissions with role inheritance."""
|
174
|
+
permissions = self.perm_manager.get_effective_permissions(["moderator"])
|
175
|
+
|
176
|
+
# Direct permissions
|
177
|
+
assert "read:*" in permissions
|
178
|
+
assert "write:posts" in permissions
|
179
|
+
assert "moderate:comments" in permissions
|
180
|
+
|
181
|
+
# Inherited permissions from user role
|
182
|
+
assert "read:posts" in permissions
|
183
|
+
assert "read:comments" in permissions
|
184
|
+
|
185
|
+
def test_get_effective_permissions_multiple_roles(self):
|
186
|
+
"""Test getting effective permissions for multiple roles."""
|
187
|
+
permissions = self.perm_manager.get_effective_permissions(["user", "guest"])
|
188
|
+
|
189
|
+
# User permissions
|
190
|
+
assert "read:posts" in permissions
|
191
|
+
assert "write:posts" in permissions
|
192
|
+
assert "read:comments" in permissions
|
193
|
+
|
194
|
+
# Guest permissions
|
195
|
+
assert "read:posts" in permissions # Duplicate, but should be in set
|
196
|
+
|
197
|
+
def test_get_effective_permissions_invalid_role(self):
|
198
|
+
"""Test getting effective permissions with invalid role."""
|
199
|
+
with pytest.raises(RoleNotFoundError):
|
200
|
+
self.perm_manager.get_effective_permissions(["invalid_role"])
|
201
|
+
|
202
|
+
def test_check_role_hierarchy(self):
|
203
|
+
"""Test role hierarchy checking."""
|
204
|
+
# Direct inheritance
|
205
|
+
assert self.perm_manager.check_role_hierarchy("moderator", "user") is True
|
206
|
+
|
207
|
+
# Self inheritance
|
208
|
+
assert self.perm_manager.check_role_hierarchy("admin", "admin") is True
|
209
|
+
|
210
|
+
# No inheritance
|
211
|
+
assert self.perm_manager.check_role_hierarchy("user", "admin") is False
|
212
|
+
assert self.perm_manager.check_role_hierarchy("guest", "moderator") is False
|
213
|
+
|
214
|
+
def test_check_role_hierarchy_invalid_roles(self):
|
215
|
+
"""Test role hierarchy checking with invalid roles."""
|
216
|
+
with pytest.raises(RoleNotFoundError):
|
217
|
+
self.perm_manager.check_role_hierarchy("invalid_role", "admin")
|
218
|
+
|
219
|
+
with pytest.raises(RoleNotFoundError):
|
220
|
+
self.perm_manager.check_role_hierarchy("admin", "invalid_role")
|
221
|
+
|
222
|
+
def test_add_role_permission(self):
|
223
|
+
"""Test adding permission to role."""
|
224
|
+
success = self.perm_manager.add_role_permission("user", "new:permission")
|
225
|
+
|
226
|
+
assert success is True
|
227
|
+
assert "new:permission" in self.perm_manager._roles["user"]["permissions"]
|
228
|
+
|
229
|
+
def test_add_role_permission_already_exists(self):
|
230
|
+
"""Test adding permission that already exists."""
|
231
|
+
# Add permission first time
|
232
|
+
success1 = self.perm_manager.add_role_permission("user", "test:permission")
|
233
|
+
assert success1 is True
|
234
|
+
|
235
|
+
# Try to add same permission again
|
236
|
+
success2 = self.perm_manager.add_role_permission("user", "test:permission")
|
237
|
+
assert success2 is False
|
238
|
+
|
239
|
+
def test_add_role_permission_invalid_role(self):
|
240
|
+
"""Test adding permission to invalid role."""
|
241
|
+
with pytest.raises(RoleNotFoundError):
|
242
|
+
self.perm_manager.add_role_permission("invalid_role", "test:permission")
|
243
|
+
|
244
|
+
def test_remove_role_permission(self):
|
245
|
+
"""Test removing permission from role."""
|
246
|
+
# Add permission first
|
247
|
+
self.perm_manager.add_role_permission("user", "test:permission")
|
248
|
+
|
249
|
+
# Remove permission
|
250
|
+
success = self.perm_manager.remove_role_permission("user", "test:permission")
|
251
|
+
|
252
|
+
assert success is True
|
253
|
+
assert "test:permission" not in self.perm_manager._roles["user"]["permissions"]
|
254
|
+
|
255
|
+
def test_remove_role_permission_not_exists(self):
|
256
|
+
"""Test removing permission that doesn't exist."""
|
257
|
+
success = self.perm_manager.remove_role_permission(
|
258
|
+
"user", "nonexistent:permission"
|
259
|
+
)
|
260
|
+
assert success is False
|
261
|
+
|
262
|
+
def test_remove_role_permission_invalid_role(self):
|
263
|
+
"""Test removing permission from invalid role."""
|
264
|
+
with pytest.raises(RoleNotFoundError):
|
265
|
+
self.perm_manager.remove_role_permission("invalid_role", "test:permission")
|
266
|
+
|
267
|
+
def test_reload_roles_configuration(self):
|
268
|
+
"""Test reloading roles configuration."""
|
269
|
+
# Modify the temporary file
|
270
|
+
new_config = {"new_role": {"permissions": ["new:permission"], "inherits": []}}
|
271
|
+
|
272
|
+
with open(self.temp_file.name, "w") as f:
|
273
|
+
json.dump(new_config, f)
|
274
|
+
|
275
|
+
# Reload configuration
|
276
|
+
success = self.perm_manager.reload_roles_configuration()
|
277
|
+
|
278
|
+
assert success is True
|
279
|
+
assert "new_role" in self.perm_manager._roles
|
280
|
+
assert "new:permission" in self.perm_manager._roles["new_role"]["permissions"]
|
281
|
+
assert "admin" not in self.perm_manager._roles # Old roles should be gone
|
282
|
+
|
283
|
+
def test_get_role_permissions(self):
|
284
|
+
"""Test getting direct permissions for a role."""
|
285
|
+
permissions = self.perm_manager.get_role_permissions("admin")
|
286
|
+
|
287
|
+
assert "read:*" in permissions
|
288
|
+
assert "write:*" in permissions
|
289
|
+
assert "delete:*" in permissions
|
290
|
+
assert "admin:*" in permissions
|
291
|
+
|
292
|
+
def test_get_role_permissions_invalid_role(self):
|
293
|
+
"""Test getting permissions for invalid role."""
|
294
|
+
with pytest.raises(RoleNotFoundError):
|
295
|
+
self.perm_manager.get_role_permissions("invalid_role")
|
296
|
+
|
297
|
+
def test_get_all_roles(self):
|
298
|
+
"""Test getting all available roles."""
|
299
|
+
roles = self.perm_manager.get_all_roles()
|
300
|
+
|
301
|
+
assert "admin" in roles
|
302
|
+
assert "moderator" in roles
|
303
|
+
assert "user" in roles
|
304
|
+
assert "guest" in roles
|
305
|
+
assert len(roles) == 4
|
306
|
+
|
307
|
+
def test_get_role_hierarchy(self):
|
308
|
+
"""Test getting role hierarchy."""
|
309
|
+
hierarchy = self.perm_manager.get_role_hierarchy()
|
310
|
+
|
311
|
+
assert hierarchy["admin"] == []
|
312
|
+
assert hierarchy["moderator"] == ["user"]
|
313
|
+
assert hierarchy["user"] == []
|
314
|
+
assert hierarchy["guest"] == []
|
315
|
+
|
316
|
+
def test_clear_cache(self):
|
317
|
+
"""Test clearing permission cache."""
|
318
|
+
# Get permissions to populate cache
|
319
|
+
self.perm_manager.get_effective_permissions(["admin"])
|
320
|
+
|
321
|
+
# Clear cache
|
322
|
+
self.perm_manager.clear_cache()
|
323
|
+
|
324
|
+
assert len(self.perm_manager._permission_cache) == 0
|
325
|
+
|
326
|
+
def test_permission_caching(self):
|
327
|
+
"""Test permission caching functionality."""
|
328
|
+
# First call should populate cache
|
329
|
+
permissions1 = self.perm_manager.get_effective_permissions(["admin"])
|
330
|
+
|
331
|
+
# Second call should use cache
|
332
|
+
permissions2 = self.perm_manager.get_effective_permissions(["admin"])
|
333
|
+
|
334
|
+
assert permissions1 == permissions2
|
335
|
+
assert len(self.perm_manager._permission_cache) > 0
|
336
|
+
|
337
|
+
def test_wildcard_permission_matching(self):
|
338
|
+
"""Test wildcard permission matching."""
|
339
|
+
# Test action wildcard
|
340
|
+
result = self.perm_manager.validate_access(["user"], ["*:posts"])
|
341
|
+
assert result.is_valid is True
|
342
|
+
assert result.status.value == "valid"
|
343
|
+
|
344
|
+
# Test resource wildcard
|
345
|
+
result = self.perm_manager.validate_access(["admin"], ["read:*"])
|
346
|
+
assert result.is_valid is True
|
347
|
+
assert result.status.value == "valid"
|
348
|
+
|
349
|
+
# Test no match
|
350
|
+
result = self.perm_manager.validate_access(["guest"], ["write:*"])
|
351
|
+
assert result.is_valid is False
|
352
|
+
assert result.status.value == "invalid"
|
353
|
+
|
354
|
+
def test_permission_manager_with_cache_disabled(self):
|
355
|
+
"""Test PermissionManager with caching disabled."""
|
356
|
+
config = PermissionConfig(
|
357
|
+
roles_file=self.temp_file.name, permission_cache_enabled=False
|
358
|
+
)
|
359
|
+
|
360
|
+
perm_manager = PermissionManager(config)
|
361
|
+
|
362
|
+
# Get permissions multiple times
|
363
|
+
permissions1 = perm_manager.get_effective_permissions(["admin"])
|
364
|
+
permissions2 = perm_manager.get_effective_permissions(["admin"])
|
365
|
+
|
366
|
+
assert permissions1 == permissions2
|
367
|
+
assert len(perm_manager._permission_cache) == 0 # No caching
|
368
|
+
|
369
|
+
|
370
|
+
class TestPermissionManagerErrors:
|
371
|
+
"""Test suite for PermissionManager error handling."""
|
372
|
+
|
373
|
+
def test_permission_configuration_error(self):
|
374
|
+
"""Test PermissionConfigurationError."""
|
375
|
+
error = PermissionConfigurationError("Test error", error_code=-32001)
|
376
|
+
|
377
|
+
assert error.message == "Test error"
|
378
|
+
assert error.error_code == -32001
|
379
|
+
assert str(error) == "Test error"
|
380
|
+
|
381
|
+
def test_role_not_found_error(self):
|
382
|
+
"""Test RoleNotFoundError."""
|
383
|
+
error = RoleNotFoundError("Role not found", error_code=-32002)
|
384
|
+
|
385
|
+
assert error.message == "Role not found"
|
386
|
+
assert error.error_code == -32002
|
387
|
+
assert str(error) == "Role not found"
|
388
|
+
|
389
|
+
def test_permission_validation_error(self):
|
390
|
+
"""Test PermissionValidationError."""
|
391
|
+
error = PermissionValidationError("Validation failed", error_code=-32003)
|
392
|
+
|
393
|
+
assert error.message == "Validation failed"
|
394
|
+
assert error.error_code == -32003
|
395
|
+
assert str(error) == "Validation failed"
|