mcp-security-framework 1.2.3__py3-none-any.whl → 1.2.4__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/core/permission_manager.py +26 -6
- mcp_security_framework/schemas/config.py +13 -2
- {mcp_security_framework-1.2.3.dist-info → mcp_security_framework-1.2.4.dist-info}/METADATA +2 -2
- {mcp_security_framework-1.2.3.dist-info → mcp_security_framework-1.2.4.dist-info}/RECORD +9 -7
- tests/test_core/test_permission_manager_null_roles.py +265 -0
- tests/test_schemas/test_config_null_roles.py +252 -0
- {mcp_security_framework-1.2.3.dist-info → mcp_security_framework-1.2.4.dist-info}/WHEEL +0 -0
- {mcp_security_framework-1.2.3.dist-info → mcp_security_framework-1.2.4.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-1.2.3.dist-info → mcp_security_framework-1.2.4.dist-info}/top_level.txt +0 -0
@@ -26,11 +26,10 @@ License: MIT
|
|
26
26
|
import json
|
27
27
|
import logging
|
28
28
|
from pathlib import Path
|
29
|
-
from typing import Dict, List,
|
29
|
+
from typing import Dict, List, Set
|
30
30
|
|
31
31
|
from ..schemas.config import PermissionConfig
|
32
32
|
from ..schemas.models import ValidationResult, ValidationStatus
|
33
|
-
from ..utils.validation_utils import validate_configuration_file
|
34
33
|
|
35
34
|
|
36
35
|
class PermissionManager:
|
@@ -227,10 +226,14 @@ class PermissionManager:
|
|
227
226
|
if not user_roles:
|
228
227
|
return set()
|
229
228
|
|
230
|
-
# Validate that all roles exist
|
231
|
-
|
232
|
-
|
233
|
-
|
229
|
+
# Validate that all roles exist (only if roles are loaded)
|
230
|
+
if self._roles: # Only validate if roles are loaded
|
231
|
+
invalid_roles = [role for role in user_roles if role not in self._roles]
|
232
|
+
if invalid_roles:
|
233
|
+
raise RoleNotFoundError(f"Invalid roles: {invalid_roles}")
|
234
|
+
else:
|
235
|
+
# If no roles are loaded, return empty set for any role
|
236
|
+
return set()
|
234
237
|
|
235
238
|
# Create cache key
|
236
239
|
cache_key = tuple(sorted(user_roles))
|
@@ -269,9 +272,15 @@ class PermissionManager:
|
|
269
272
|
RoleNotFoundError: When either role is not found
|
270
273
|
"""
|
271
274
|
if child_role not in self._roles:
|
275
|
+
# If no roles are loaded, return False
|
276
|
+
if not self._roles:
|
277
|
+
return False
|
272
278
|
raise RoleNotFoundError(f"Child role not found: {child_role}")
|
273
279
|
|
274
280
|
if parent_role not in self._roles:
|
281
|
+
# If no roles are loaded, return False
|
282
|
+
if not self._roles:
|
283
|
+
return False
|
275
284
|
raise RoleNotFoundError(f"Parent role not found: {parent_role}")
|
276
285
|
|
277
286
|
if child_role == parent_role:
|
@@ -473,6 +482,9 @@ class PermissionManager:
|
|
473
482
|
RoleNotFoundError: When role is not found in configuration
|
474
483
|
"""
|
475
484
|
if role not in self._roles:
|
485
|
+
# If no roles are loaded, return empty list
|
486
|
+
if not self._roles:
|
487
|
+
return []
|
476
488
|
raise RoleNotFoundError(f"Role not found: {role}")
|
477
489
|
|
478
490
|
return self._roles[role].get("permissions", []).copy()
|
@@ -563,6 +575,14 @@ class PermissionManager:
|
|
563
575
|
def _load_roles_configuration(self) -> None:
|
564
576
|
"""Load roles configuration from file."""
|
565
577
|
try:
|
578
|
+
# Handle null or empty roles_file
|
579
|
+
if not self.config.roles_file:
|
580
|
+
self.logger.warning(
|
581
|
+
"No roles file specified, using empty roles configuration"
|
582
|
+
)
|
583
|
+
self._roles = {}
|
584
|
+
return
|
585
|
+
|
566
586
|
roles_file = Path(self.config.roles_file)
|
567
587
|
|
568
588
|
if not roles_file.exists():
|
@@ -271,7 +271,8 @@ class CertificateConfig(BaseModel):
|
|
271
271
|
default=False, description="Whether certificate management is enabled"
|
272
272
|
)
|
273
273
|
ca_creation_mode: bool = Field(
|
274
|
-
default=False,
|
274
|
+
default=False,
|
275
|
+
description="Whether we are in CA creation mode (bypasses CA path validation)",
|
275
276
|
)
|
276
277
|
ca_cert_path: Optional[str] = Field(
|
277
278
|
default=None, description="Path to CA certificate"
|
@@ -390,7 +391,17 @@ class PermissionConfig(BaseModel):
|
|
390
391
|
@classmethod
|
391
392
|
def validate_roles_file(cls, v):
|
392
393
|
"""Validate roles file path."""
|
393
|
-
|
394
|
+
# Allow None, empty string, null values, or whitespace-only strings
|
395
|
+
if (
|
396
|
+
v is None
|
397
|
+
or v == ""
|
398
|
+
or v == "null"
|
399
|
+
or (isinstance(v, str) and v.strip() == "")
|
400
|
+
):
|
401
|
+
return None
|
402
|
+
|
403
|
+
# If a path is provided, check if it exists
|
404
|
+
if v and not Path(v).exists():
|
394
405
|
raise ValueError(f"Roles file does not exist: {v}")
|
395
406
|
return v
|
396
407
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-security-framework
|
3
|
-
Version: 1.2.
|
4
|
-
Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Requires cryptography>=42.0.0 for certificate operations.
|
3
|
+
Version: 1.2.4
|
4
|
+
Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Fixed MCP Proxy Adapter JSON-RPC authentication issue with null roles_file. Requires cryptography>=42.0.0 for certificate operations.
|
5
5
|
Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
6
6
|
Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
7
7
|
License: MIT
|
@@ -6,7 +6,7 @@ mcp_security_framework/cli/security_cli.py,sha256=Thine_Zzfesz7j29y2k_XZFYUK5YSr
|
|
6
6
|
mcp_security_framework/core/__init__.py,sha256=LiX8_M5qWiTXccJFjSLxup9emhklp-poq57SvznsKEg,1729
|
7
7
|
mcp_security_framework/core/auth_manager.py,sha256=GqGAW83Qg1_z2HJ0-FEVTmlli_DBSOPOap2jJMEU1_k,39882
|
8
8
|
mcp_security_framework/core/cert_manager.py,sha256=F3rWpqi-YZtaCt3g-KpoqJ1WY22TGaVLEkacKKTrHxw,89094
|
9
|
-
mcp_security_framework/core/permission_manager.py,sha256=
|
9
|
+
mcp_security_framework/core/permission_manager.py,sha256=P6ENqC6sCH4ig_DBfMGALsw-ooRrXJGvQjPyLZrKo9k,27228
|
10
10
|
mcp_security_framework/core/rate_limiter.py,sha256=6qjVBxK2YHouSxQuCcbr0PBpRqA5toQss_Ce178RElY,20682
|
11
11
|
mcp_security_framework/core/security_manager.py,sha256=mAF-5znqxin-MSSgXISB7t1kTkqHltEqGzzmlLAhRGs,37766
|
12
12
|
mcp_security_framework/core/ssl_manager.py,sha256=SXuN5PMTAnMNz04CEKzHbxRKjzF-VqvS-QCFhV-wFeo,29133
|
@@ -29,7 +29,7 @@ mcp_security_framework/middleware/mtls_middleware.py,sha256=WSyWIk1fCN96hkofODKj
|
|
29
29
|
mcp_security_framework/middleware/rate_limit_middleware.py,sha256=deCwwigI0Pt7pBUnk2jDurI9ZyjujWTsexEWWndXm3g,13177
|
30
30
|
mcp_security_framework/middleware/security_middleware.py,sha256=PQ251Fr2UrYVPgGfhXq6QJyqK2tRk0WCIg9_FBvfVkg,16844
|
31
31
|
mcp_security_framework/schemas/__init__.py,sha256=lefkbRlbj2ICfasSj51MQ04o3z1YycnbnknSJCFfXbU,2590
|
32
|
-
mcp_security_framework/schemas/config.py,sha256=
|
32
|
+
mcp_security_framework/schemas/config.py,sha256=AQEQ8260ZwP9QpTdmpQ9EbU13P7TEyPAA5B48TWssp8,27687
|
33
33
|
mcp_security_framework/schemas/models.py,sha256=Izjy3I55zjMVLsVZpXZ0M4aK3SCks9sC2U1cbxrXYeI,28439
|
34
34
|
mcp_security_framework/schemas/responses.py,sha256=nVXaqF5GTSprXTa_wiUEu38nvSw9WAXtKViAJNbO-Xg,23206
|
35
35
|
mcp_security_framework/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -47,6 +47,7 @@ tests/test_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
47
|
tests/test_core/test_auth_manager.py,sha256=7Z2DLfJLqKtiwX5Q-lR85hN6NxHbE2Q_FT7IsoyKPQk,22568
|
48
48
|
tests/test_core/test_cert_manager.py,sha256=AVaJlWrWTFMO4MqxzVx39QAwB5zbYhVHQwdYUgNNplk,38091
|
49
49
|
tests/test_core/test_permission_manager.py,sha256=0XeghWXZqVpKyyRuhuDu1dkLUSwuZaFWkRQxQhkkFVI,14966
|
50
|
+
tests/test_core/test_permission_manager_null_roles.py,sha256=ajYFpkWgJqtamOU1eI3HlO0WiMdDpPB8br2rp2VjxCY,10403
|
50
51
|
tests/test_core/test_rate_limiter.py,sha256=YzzlhlxZm-A7YGMiIV8LXDA0zmb_6uRF9GRx9s21Q0U,22544
|
51
52
|
tests/test_core/test_security_manager.py,sha256=C5uPFALAkitmHbi-L8xF1OyfOmVHQSq1g-PLkwl_LDU,35007
|
52
53
|
tests/test_core/test_ssl_manager.py,sha256=Vm_Nw4SoVro_iwPPc_uD9CwzXpVBkGyVH7EqDtHawvU,20362
|
@@ -69,6 +70,7 @@ tests/test_middleware/test_flask_middleware.py,sha256=JqWr5MknE6AvnUUf2Cr0ME6l_w
|
|
69
70
|
tests/test_middleware/test_security_middleware.py,sha256=J69rVgsnohQp2ucUnGRyWCWZxt6RF2tQ9vQNLFlDXEg,19199
|
70
71
|
tests/test_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
72
|
tests/test_schemas/test_config.py,sha256=m0TKYPXKC2QdkVmUc7UPEF3yOANL5Ee1v93DZswSMvk,31347
|
73
|
+
tests/test_schemas/test_config_null_roles.py,sha256=xF7d1-moagxWp3TSNERMVrp6g9vzRqR-t9mMgfzK0xI,9491
|
72
74
|
tests/test_schemas/test_models.py,sha256=bBeZOPqveuVJuEi_BTVWdVsdj08JXJTEFwvBM4eFRVU,34311
|
73
75
|
tests/test_schemas/test_responses.py,sha256=ZSbO7A3ThPBovTXO8PFF-2ONWAjJx2dMOoV2lQIfd8s,40774
|
74
76
|
tests/test_schemas/test_serialization.py,sha256=jCugAyrdD6Mw1U7Kxni9oTukarZmMMl6KUcl6cq_NTk,18599
|
@@ -78,8 +80,8 @@ tests/test_utils/test_crypto_utils.py,sha256=yEb4hzG6-irj2DPoXY0DUboJfbeR87ussgT
|
|
78
80
|
tests/test_utils/test_datetime_compat.py,sha256=n8S4X5HN-_ejSNpgymDXRyZkmxhnyxwwjxFPdX23I40,5656
|
79
81
|
tests/test_utils/test_unitid_compat.py,sha256=MWh03A4FwzQyZa20PKHEWz4W03YtARwBOd_1JbABznQ,25544
|
80
82
|
tests/test_utils/test_validation_utils.py,sha256=lus_wHJ2WyVnBGQ28S7dSv78uWcCIuLhn5uflJw-uGw,18569
|
81
|
-
mcp_security_framework-1.2.
|
82
|
-
mcp_security_framework-1.2.
|
83
|
-
mcp_security_framework-1.2.
|
84
|
-
mcp_security_framework-1.2.
|
85
|
-
mcp_security_framework-1.2.
|
83
|
+
mcp_security_framework-1.2.4.dist-info/METADATA,sha256=j3FYSacZFO8OwGfkn-B6om8z1ZcATSmUZrGxnIZLDCk,11847
|
84
|
+
mcp_security_framework-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
85
|
+
mcp_security_framework-1.2.4.dist-info/entry_points.txt,sha256=qBh92fVDmd1m2f3xeW0hTu3Ksg8QfGJyV8UEkdA2itg,142
|
86
|
+
mcp_security_framework-1.2.4.dist-info/top_level.txt,sha256=ifUiGrTDcD574MXSOoAN2rp2wpUvWlb4jD9LTUgDWCA,29
|
87
|
+
mcp_security_framework-1.2.4.dist-info/RECORD,,
|
@@ -0,0 +1,265 @@
|
|
1
|
+
"""
|
2
|
+
Tests for PermissionManager with null roles_file
|
3
|
+
|
4
|
+
This module contains tests for the PermissionManager when roles_file
|
5
|
+
is set to null or None, ensuring graceful handling of this configuration.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import pytest
|
12
|
+
from unittest.mock import patch
|
13
|
+
from mcp_security_framework.core.permission_manager import PermissionManager
|
14
|
+
from mcp_security_framework.schemas.config import PermissionConfig
|
15
|
+
from mcp_security_framework.schemas.models import ValidationResult, ValidationStatus
|
16
|
+
|
17
|
+
|
18
|
+
class TestPermissionManagerNullRoles:
|
19
|
+
"""Test suite for PermissionManager with null roles_file."""
|
20
|
+
|
21
|
+
def test_permission_manager_with_null_roles_file(self):
|
22
|
+
"""Test PermissionManager initialization with null roles_file."""
|
23
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
24
|
+
|
25
|
+
# Should not raise an exception
|
26
|
+
perm_manager = PermissionManager(config)
|
27
|
+
|
28
|
+
# Should have empty roles
|
29
|
+
assert perm_manager._roles == {}
|
30
|
+
|
31
|
+
# Should be able to validate access with default role
|
32
|
+
result = perm_manager.validate_access(["guest"], ["read:public"])
|
33
|
+
assert isinstance(result, ValidationResult)
|
34
|
+
|
35
|
+
def test_permission_manager_with_empty_roles_file(self):
|
36
|
+
"""Test PermissionManager initialization with empty roles_file."""
|
37
|
+
config = PermissionConfig(enabled=True, roles_file="", default_role="guest")
|
38
|
+
|
39
|
+
# Should not raise an exception
|
40
|
+
perm_manager = PermissionManager(config)
|
41
|
+
|
42
|
+
# Should have empty roles
|
43
|
+
assert perm_manager._roles == {}
|
44
|
+
|
45
|
+
def test_permission_manager_with_string_null_roles_file(self):
|
46
|
+
"""Test PermissionManager initialization with string 'null' roles_file."""
|
47
|
+
config = PermissionConfig(enabled=True, roles_file="null", default_role="guest")
|
48
|
+
|
49
|
+
# Should not raise an exception
|
50
|
+
perm_manager = PermissionManager(config)
|
51
|
+
|
52
|
+
# Should have empty roles
|
53
|
+
assert perm_manager._roles == {}
|
54
|
+
|
55
|
+
def test_permission_manager_validation_with_empty_roles(self):
|
56
|
+
"""Test permission validation when no roles are loaded."""
|
57
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
58
|
+
|
59
|
+
perm_manager = PermissionManager(config)
|
60
|
+
|
61
|
+
# Test validation with guest role
|
62
|
+
result = perm_manager.validate_access(["guest"], ["read:public"])
|
63
|
+
assert isinstance(result, ValidationResult)
|
64
|
+
|
65
|
+
# Test validation with unknown role
|
66
|
+
result = perm_manager.validate_access(["unknown"], ["read:public"])
|
67
|
+
assert isinstance(result, ValidationResult)
|
68
|
+
assert result.status == ValidationStatus.INVALID
|
69
|
+
|
70
|
+
def test_permission_manager_get_effective_permissions_empty_roles(self):
|
71
|
+
"""Test getting effective permissions when no roles are loaded."""
|
72
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
73
|
+
|
74
|
+
perm_manager = PermissionManager(config)
|
75
|
+
|
76
|
+
# Should return empty set for any role
|
77
|
+
permissions = perm_manager.get_effective_permissions(["guest"])
|
78
|
+
assert permissions == set()
|
79
|
+
|
80
|
+
permissions = perm_manager.get_effective_permissions(["admin"])
|
81
|
+
assert permissions == set()
|
82
|
+
|
83
|
+
def test_permission_manager_export_roles_config_empty(self):
|
84
|
+
"""Test exporting roles configuration when no roles are loaded."""
|
85
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
86
|
+
|
87
|
+
perm_manager = PermissionManager(config)
|
88
|
+
|
89
|
+
# Should export empty configuration
|
90
|
+
exported = perm_manager.export_roles_config()
|
91
|
+
assert "roles" in exported
|
92
|
+
assert exported["roles"] == {}
|
93
|
+
assert "permissions" in exported
|
94
|
+
assert exported["permissions"] == {}
|
95
|
+
|
96
|
+
def test_permission_manager_cache_operations_empty_roles(self):
|
97
|
+
"""Test cache operations when no roles are loaded."""
|
98
|
+
config = PermissionConfig(
|
99
|
+
enabled=True,
|
100
|
+
roles_file=None,
|
101
|
+
default_role="guest",
|
102
|
+
permission_cache_enabled=True,
|
103
|
+
)
|
104
|
+
|
105
|
+
perm_manager = PermissionManager(config)
|
106
|
+
|
107
|
+
# Cache operations should work without errors
|
108
|
+
perm_manager.clear_cache()
|
109
|
+
perm_manager.clear_permission_cache()
|
110
|
+
|
111
|
+
# Cache should be empty
|
112
|
+
assert len(perm_manager._permission_cache) == 0
|
113
|
+
|
114
|
+
def test_permission_manager_role_operations_empty_roles(self):
|
115
|
+
"""Test role operations when no roles are loaded."""
|
116
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
117
|
+
|
118
|
+
perm_manager = PermissionManager(config)
|
119
|
+
|
120
|
+
# Role operations should work without errors
|
121
|
+
roles = perm_manager.get_all_roles()
|
122
|
+
assert roles == []
|
123
|
+
|
124
|
+
# Getting role permissions should work (returns empty for unknown roles)
|
125
|
+
permissions = perm_manager.get_role_permissions("test_role")
|
126
|
+
assert permissions == []
|
127
|
+
|
128
|
+
def test_permission_manager_hierarchy_operations_empty_roles(self):
|
129
|
+
"""Test hierarchy operations when no roles are loaded."""
|
130
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
131
|
+
|
132
|
+
perm_manager = PermissionManager(config)
|
133
|
+
|
134
|
+
# Hierarchy operations should work without errors
|
135
|
+
hierarchy = perm_manager.get_role_hierarchy()
|
136
|
+
assert hierarchy == {}
|
137
|
+
|
138
|
+
# Checking hierarchy should work
|
139
|
+
is_hierarchy = perm_manager.check_role_hierarchy("child", "parent")
|
140
|
+
assert is_hierarchy is False
|
141
|
+
|
142
|
+
def test_permission_manager_permission_operations_empty_roles(self):
|
143
|
+
"""Test permission operations when no roles are loaded."""
|
144
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
145
|
+
|
146
|
+
perm_manager = PermissionManager(config)
|
147
|
+
|
148
|
+
# Permission operations should work without errors
|
149
|
+
# Note: get_all_permissions doesn't exist, so we test role permissions instead
|
150
|
+
permissions = perm_manager.get_role_permissions("guest")
|
151
|
+
assert permissions == []
|
152
|
+
|
153
|
+
def test_permission_manager_validation_with_wildcards_empty_roles(self):
|
154
|
+
"""Test wildcard permission validation when no roles are loaded."""
|
155
|
+
config = PermissionConfig(
|
156
|
+
enabled=True,
|
157
|
+
roles_file=None,
|
158
|
+
default_role="guest",
|
159
|
+
wildcard_permissions=True,
|
160
|
+
)
|
161
|
+
|
162
|
+
perm_manager = PermissionManager(config)
|
163
|
+
|
164
|
+
# Wildcard validation should work without errors
|
165
|
+
result = perm_manager.validate_access(["guest"], ["*"])
|
166
|
+
assert isinstance(result, ValidationResult)
|
167
|
+
|
168
|
+
result = perm_manager.validate_access(["guest"], ["read:*"])
|
169
|
+
assert isinstance(result, ValidationResult)
|
170
|
+
|
171
|
+
def test_permission_manager_strict_mode_empty_roles(self):
|
172
|
+
"""Test strict mode validation when no roles are loaded."""
|
173
|
+
config = PermissionConfig(
|
174
|
+
enabled=True, roles_file=None, default_role="guest", strict_mode=True
|
175
|
+
)
|
176
|
+
|
177
|
+
perm_manager = PermissionManager(config)
|
178
|
+
|
179
|
+
# Strict mode validation should work without errors
|
180
|
+
result = perm_manager.validate_access(["guest"], ["read:public"])
|
181
|
+
assert isinstance(result, ValidationResult)
|
182
|
+
|
183
|
+
result = perm_manager.validate_access(["unknown"], ["read:public"])
|
184
|
+
assert isinstance(result, ValidationResult)
|
185
|
+
assert result.status == ValidationStatus.INVALID
|
186
|
+
|
187
|
+
def test_permission_manager_cache_ttl_empty_roles(self):
|
188
|
+
"""Test cache TTL operations when no roles are loaded."""
|
189
|
+
config = PermissionConfig(
|
190
|
+
enabled=True,
|
191
|
+
roles_file=None,
|
192
|
+
default_role="guest",
|
193
|
+
permission_cache_enabled=True,
|
194
|
+
permission_cache_ttl=300,
|
195
|
+
)
|
196
|
+
|
197
|
+
perm_manager = PermissionManager(config)
|
198
|
+
|
199
|
+
# Cache TTL operations should work without errors
|
200
|
+
assert perm_manager._cache_enabled is True
|
201
|
+
|
202
|
+
# Cache should be empty but functional
|
203
|
+
permissions = perm_manager.get_effective_permissions(["guest"])
|
204
|
+
assert permissions == set()
|
205
|
+
|
206
|
+
def test_permission_manager_error_handling_empty_roles(self):
|
207
|
+
"""Test error handling when no roles are loaded."""
|
208
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
209
|
+
|
210
|
+
perm_manager = PermissionManager(config)
|
211
|
+
|
212
|
+
# Error handling should work without errors
|
213
|
+
try:
|
214
|
+
perm_manager.validate_access([], ["read:public"])
|
215
|
+
except Exception as e:
|
216
|
+
pytest.fail(f"validate_access raised an exception: {e}")
|
217
|
+
|
218
|
+
try:
|
219
|
+
perm_manager.validate_access(["guest"], [])
|
220
|
+
except Exception as e:
|
221
|
+
pytest.fail(f"validate_access raised an exception: {e}")
|
222
|
+
|
223
|
+
def test_permission_manager_logging_empty_roles(self):
|
224
|
+
"""Test logging when no roles are loaded."""
|
225
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
226
|
+
|
227
|
+
# Should not raise an exception during initialization
|
228
|
+
with patch(
|
229
|
+
"mcp_security_framework.core.permission_manager.logging.getLogger"
|
230
|
+
) as mock_logger:
|
231
|
+
mock_logger_instance = mock_logger.return_value
|
232
|
+
perm_manager = PermissionManager(config)
|
233
|
+
|
234
|
+
# Should log warning about empty roles configuration
|
235
|
+
mock_logger_instance.warning.assert_called_with(
|
236
|
+
"No roles file specified, using empty roles configuration"
|
237
|
+
)
|
238
|
+
|
239
|
+
def test_permission_manager_integration_empty_roles(self):
|
240
|
+
"""Test full integration when no roles are loaded."""
|
241
|
+
config = PermissionConfig(
|
242
|
+
enabled=True,
|
243
|
+
roles_file=None,
|
244
|
+
default_role="guest",
|
245
|
+
permission_cache_enabled=True,
|
246
|
+
wildcard_permissions=True,
|
247
|
+
strict_mode=True,
|
248
|
+
)
|
249
|
+
|
250
|
+
# Should initialize without errors
|
251
|
+
perm_manager = PermissionManager(config)
|
252
|
+
|
253
|
+
# All operations should work
|
254
|
+
assert perm_manager._roles == {}
|
255
|
+
assert perm_manager._hierarchy == {}
|
256
|
+
assert perm_manager._permission_cache == {}
|
257
|
+
assert perm_manager._cache_enabled is True
|
258
|
+
|
259
|
+
# Validation should work
|
260
|
+
result = perm_manager.validate_access(["guest"], ["read:public"])
|
261
|
+
assert isinstance(result, ValidationResult)
|
262
|
+
|
263
|
+
# Cache operations should work
|
264
|
+
perm_manager.clear_cache()
|
265
|
+
assert len(perm_manager._permission_cache) == 0
|
@@ -0,0 +1,252 @@
|
|
1
|
+
"""
|
2
|
+
Tests for PermissionConfig with null roles_file
|
3
|
+
|
4
|
+
This module contains tests for the PermissionConfig validation
|
5
|
+
when roles_file is set to null, None, or empty values.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import pytest
|
12
|
+
from mcp_security_framework.schemas.config import PermissionConfig
|
13
|
+
|
14
|
+
|
15
|
+
class TestPermissionConfigNullRoles:
|
16
|
+
"""Test suite for PermissionConfig with null roles_file."""
|
17
|
+
|
18
|
+
def test_permission_config_with_none_roles_file(self):
|
19
|
+
"""Test PermissionConfig with None roles_file."""
|
20
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
21
|
+
|
22
|
+
assert config.roles_file is None
|
23
|
+
assert config.enabled is True
|
24
|
+
assert config.default_role == "guest"
|
25
|
+
|
26
|
+
def test_permission_config_with_empty_roles_file(self):
|
27
|
+
"""Test PermissionConfig with empty string roles_file."""
|
28
|
+
config = PermissionConfig(enabled=True, roles_file="", default_role="guest")
|
29
|
+
|
30
|
+
assert config.roles_file is None
|
31
|
+
assert config.enabled is True
|
32
|
+
assert config.default_role == "guest"
|
33
|
+
|
34
|
+
def test_permission_config_with_string_null_roles_file(self):
|
35
|
+
"""Test PermissionConfig with string 'null' roles_file."""
|
36
|
+
config = PermissionConfig(enabled=True, roles_file="null", default_role="guest")
|
37
|
+
|
38
|
+
assert config.roles_file is None
|
39
|
+
assert config.enabled is True
|
40
|
+
assert config.default_role == "guest"
|
41
|
+
|
42
|
+
def test_permission_config_with_valid_roles_file(self):
|
43
|
+
"""Test PermissionConfig with valid roles_file."""
|
44
|
+
# Create a temporary file for testing
|
45
|
+
import tempfile
|
46
|
+
import os
|
47
|
+
|
48
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
49
|
+
f.write('{"roles": {}}')
|
50
|
+
temp_file = f.name
|
51
|
+
|
52
|
+
try:
|
53
|
+
config = PermissionConfig(
|
54
|
+
enabled=True, roles_file=temp_file, default_role="guest"
|
55
|
+
)
|
56
|
+
|
57
|
+
assert config.roles_file == temp_file
|
58
|
+
assert config.enabled is True
|
59
|
+
assert config.default_role == "guest"
|
60
|
+
finally:
|
61
|
+
os.unlink(temp_file)
|
62
|
+
|
63
|
+
def test_permission_config_with_nonexistent_roles_file(self):
|
64
|
+
"""Test PermissionConfig with nonexistent roles_file."""
|
65
|
+
with pytest.raises(ValueError, match="Roles file does not exist"):
|
66
|
+
PermissionConfig(
|
67
|
+
enabled=True,
|
68
|
+
roles_file="/nonexistent/path/roles.json",
|
69
|
+
default_role="guest",
|
70
|
+
)
|
71
|
+
|
72
|
+
def test_permission_config_default_values(self):
|
73
|
+
"""Test PermissionConfig default values."""
|
74
|
+
config = PermissionConfig()
|
75
|
+
|
76
|
+
assert config.enabled is True
|
77
|
+
assert config.roles_file is None
|
78
|
+
assert config.default_role == "guest"
|
79
|
+
assert config.admin_role == "admin"
|
80
|
+
assert config.role_hierarchy == {}
|
81
|
+
assert config.permission_cache_enabled is True
|
82
|
+
assert config.permission_cache_ttl == 300
|
83
|
+
assert config.wildcard_permissions is False
|
84
|
+
assert config.strict_mode is True
|
85
|
+
assert config.roles is None
|
86
|
+
|
87
|
+
def test_permission_config_with_all_null_values(self):
|
88
|
+
"""Test PermissionConfig with all null-like values."""
|
89
|
+
config = PermissionConfig(
|
90
|
+
enabled=True,
|
91
|
+
roles_file=None,
|
92
|
+
default_role="guest",
|
93
|
+
admin_role="admin",
|
94
|
+
role_hierarchy={},
|
95
|
+
permission_cache_enabled=True,
|
96
|
+
permission_cache_ttl=300,
|
97
|
+
wildcard_permissions=False,
|
98
|
+
strict_mode=True,
|
99
|
+
roles=None,
|
100
|
+
)
|
101
|
+
|
102
|
+
assert config.roles_file is None
|
103
|
+
assert config.roles is None
|
104
|
+
assert config.role_hierarchy == {}
|
105
|
+
|
106
|
+
def test_permission_config_validation_edge_cases(self):
|
107
|
+
"""Test PermissionConfig validation with edge cases."""
|
108
|
+
# Test with whitespace-only string
|
109
|
+
config = PermissionConfig(enabled=True, roles_file=" ", default_role="guest")
|
110
|
+
|
111
|
+
# Should be treated as empty and converted to None
|
112
|
+
assert config.roles_file is None
|
113
|
+
|
114
|
+
def test_permission_config_with_roles_dict(self):
|
115
|
+
"""Test PermissionConfig with inline roles dictionary."""
|
116
|
+
config = PermissionConfig(
|
117
|
+
enabled=True,
|
118
|
+
roles_file=None,
|
119
|
+
default_role="guest",
|
120
|
+
roles={
|
121
|
+
"admin": ["*"],
|
122
|
+
"user": ["read:*", "write:own"],
|
123
|
+
"guest": ["read:public"],
|
124
|
+
},
|
125
|
+
)
|
126
|
+
|
127
|
+
assert config.roles_file is None
|
128
|
+
assert config.roles is not None
|
129
|
+
assert "admin" in config.roles
|
130
|
+
assert "user" in config.roles
|
131
|
+
assert "guest" in config.roles
|
132
|
+
|
133
|
+
def test_permission_config_serialization(self):
|
134
|
+
"""Test PermissionConfig serialization with null values."""
|
135
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
136
|
+
|
137
|
+
# Should serialize without errors
|
138
|
+
config_dict = config.model_dump()
|
139
|
+
assert "roles_file" in config_dict
|
140
|
+
assert config_dict["roles_file"] is None
|
141
|
+
|
142
|
+
# Should deserialize without errors
|
143
|
+
config_dict["roles_file"] = None
|
144
|
+
new_config = PermissionConfig(**config_dict)
|
145
|
+
assert new_config.roles_file is None
|
146
|
+
|
147
|
+
def test_permission_config_json_serialization(self):
|
148
|
+
"""Test PermissionConfig JSON serialization with null values."""
|
149
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
150
|
+
|
151
|
+
# Should serialize to JSON without errors
|
152
|
+
json_str = config.model_dump_json()
|
153
|
+
assert "roles_file" in json_str
|
154
|
+
assert "null" in json_str
|
155
|
+
|
156
|
+
# Should deserialize from JSON without errors
|
157
|
+
import json
|
158
|
+
|
159
|
+
config_dict = json.loads(json_str)
|
160
|
+
assert config_dict["roles_file"] is None
|
161
|
+
|
162
|
+
def test_permission_config_validation_chain(self):
|
163
|
+
"""Test PermissionConfig validation chain with null values."""
|
164
|
+
# Test that validation works with null values
|
165
|
+
config = PermissionConfig(
|
166
|
+
enabled=True,
|
167
|
+
roles_file=None,
|
168
|
+
default_role="guest",
|
169
|
+
admin_role="admin",
|
170
|
+
role_hierarchy={},
|
171
|
+
permission_cache_enabled=True,
|
172
|
+
permission_cache_ttl=300,
|
173
|
+
wildcard_permissions=False,
|
174
|
+
strict_mode=True,
|
175
|
+
roles=None,
|
176
|
+
)
|
177
|
+
|
178
|
+
# All validations should pass
|
179
|
+
assert config.enabled is True
|
180
|
+
assert config.roles_file is None
|
181
|
+
assert config.default_role == "guest"
|
182
|
+
assert config.admin_role == "admin"
|
183
|
+
assert config.role_hierarchy == {}
|
184
|
+
assert config.permission_cache_enabled is True
|
185
|
+
assert config.permission_cache_ttl == 300
|
186
|
+
assert config.wildcard_permissions is False
|
187
|
+
assert config.strict_mode is True
|
188
|
+
assert config.roles is None
|
189
|
+
|
190
|
+
def test_permission_config_field_validation(self):
|
191
|
+
"""Test PermissionConfig field validation with null values."""
|
192
|
+
# Test that all fields can be set to null/None where appropriate
|
193
|
+
config = PermissionConfig(
|
194
|
+
enabled=True,
|
195
|
+
roles_file=None, # Can be None
|
196
|
+
default_role="guest", # Cannot be None
|
197
|
+
admin_role="admin", # Cannot be None
|
198
|
+
role_hierarchy={}, # Cannot be None, but can be empty
|
199
|
+
permission_cache_enabled=True, # Cannot be None
|
200
|
+
permission_cache_ttl=300, # Cannot be None
|
201
|
+
wildcard_permissions=False, # Cannot be None
|
202
|
+
strict_mode=True, # Cannot be None
|
203
|
+
roles=None, # Can be None
|
204
|
+
)
|
205
|
+
|
206
|
+
# All fields should be valid
|
207
|
+
assert config.roles_file is None
|
208
|
+
assert config.roles is None
|
209
|
+
assert config.role_hierarchy == {}
|
210
|
+
assert config.enabled is True
|
211
|
+
assert config.default_role == "guest"
|
212
|
+
assert config.admin_role == "admin"
|
213
|
+
assert config.permission_cache_enabled is True
|
214
|
+
assert config.permission_cache_ttl == 300
|
215
|
+
assert config.wildcard_permissions is False
|
216
|
+
assert config.strict_mode is True
|
217
|
+
|
218
|
+
def test_permission_config_equality(self):
|
219
|
+
"""Test PermissionConfig equality with null values."""
|
220
|
+
config1 = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
221
|
+
|
222
|
+
config2 = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
223
|
+
|
224
|
+
# Should be equal
|
225
|
+
assert config1 == config2
|
226
|
+
|
227
|
+
# Test with different null representations
|
228
|
+
config3 = PermissionConfig(enabled=True, roles_file="", default_role="guest")
|
229
|
+
|
230
|
+
# Should be equal (empty string converted to None)
|
231
|
+
assert config1 == config3
|
232
|
+
|
233
|
+
def test_permission_config_copy(self):
|
234
|
+
"""Test PermissionConfig copy with null values."""
|
235
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
236
|
+
|
237
|
+
# Should copy without errors
|
238
|
+
config_copy = config.model_copy()
|
239
|
+
assert config_copy == config
|
240
|
+
assert config_copy.roles_file is None
|
241
|
+
assert config_copy.default_role == "guest"
|
242
|
+
|
243
|
+
def test_permission_config_mutability(self):
|
244
|
+
"""Test PermissionConfig mutability with null values."""
|
245
|
+
config = PermissionConfig(enabled=True, roles_file=None, default_role="guest")
|
246
|
+
|
247
|
+
# Pydantic models are mutable by default
|
248
|
+
config.roles_file = "new_value"
|
249
|
+
assert config.roles_file == "new_value"
|
250
|
+
|
251
|
+
config.default_role = "new_role"
|
252
|
+
assert config.default_role == "new_role"
|
File without changes
|
{mcp_security_framework-1.2.3.dist-info → mcp_security_framework-1.2.4.dist-info}/entry_points.txt
RENAMED
File without changes
|
{mcp_security_framework-1.2.3.dist-info → mcp_security_framework-1.2.4.dist-info}/top_level.txt
RENAMED
File without changes
|