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,582 @@
|
|
1
|
+
"""
|
2
|
+
Tests for Authentication Manager Module
|
3
|
+
|
4
|
+
This module contains comprehensive tests for the AuthManager class,
|
5
|
+
covering all authentication methods and edge cases.
|
6
|
+
|
7
|
+
Test Coverage:
|
8
|
+
- API key authentication
|
9
|
+
- JWT token authentication and creation
|
10
|
+
- Certificate-based authentication
|
11
|
+
- Error handling and edge cases
|
12
|
+
- Configuration validation
|
13
|
+
- Integration with PermissionManager
|
14
|
+
|
15
|
+
Author: MCP Security Team
|
16
|
+
Version: 1.0.0
|
17
|
+
License: MIT
|
18
|
+
"""
|
19
|
+
|
20
|
+
from datetime import datetime, timedelta, timezone
|
21
|
+
from unittest.mock import MagicMock, Mock, patch
|
22
|
+
|
23
|
+
import pytest
|
24
|
+
|
25
|
+
from mcp_security_framework.core.auth_manager import (
|
26
|
+
AuthenticationConfigurationError,
|
27
|
+
AuthenticationError,
|
28
|
+
AuthManager,
|
29
|
+
CertificateValidationError,
|
30
|
+
JWTValidationError,
|
31
|
+
)
|
32
|
+
from mcp_security_framework.core.permission_manager import PermissionManager
|
33
|
+
from mcp_security_framework.schemas.config import AuthConfig
|
34
|
+
from mcp_security_framework.schemas.models import AuthResult, AuthStatus
|
35
|
+
|
36
|
+
|
37
|
+
class TestAuthManager:
|
38
|
+
"""Test suite for AuthManager class."""
|
39
|
+
|
40
|
+
def setup_method(self):
|
41
|
+
"""Set up test fixtures before each test method."""
|
42
|
+
# Create mock permission manager
|
43
|
+
self.mock_permission_manager = Mock(spec=PermissionManager)
|
44
|
+
# Configure mock to return default roles
|
45
|
+
self.mock_permission_manager.get_user_roles.return_value = ["user"]
|
46
|
+
|
47
|
+
# Create test configuration
|
48
|
+
self.auth_config = AuthConfig(
|
49
|
+
api_keys={"test_api_key_123": "test_user"},
|
50
|
+
jwt_secret="test_jwt_secret_key_32_chars_long",
|
51
|
+
jwt_expiry_hours=1,
|
52
|
+
ca_cert_file=None,
|
53
|
+
)
|
54
|
+
|
55
|
+
# Create AuthManager instance
|
56
|
+
self.auth_manager = AuthManager(self.auth_config, self.mock_permission_manager)
|
57
|
+
|
58
|
+
def teardown_method(self):
|
59
|
+
"""Clean up after each test method."""
|
60
|
+
# Clear caches
|
61
|
+
self.auth_manager.clear_token_cache()
|
62
|
+
self.auth_manager.clear_session_store()
|
63
|
+
|
64
|
+
def test_init_success(self):
|
65
|
+
"""Test successful AuthManager initialization."""
|
66
|
+
assert self.auth_manager.config == self.auth_config
|
67
|
+
assert self.auth_manager.permission_manager == self.mock_permission_manager
|
68
|
+
assert self.auth_manager._api_keys == {"test_user": "test_api_key_123"}
|
69
|
+
assert self.auth_manager._jwt_secret == "test_jwt_secret_key_32_chars_long"
|
70
|
+
assert isinstance(self.auth_manager._token_cache, dict)
|
71
|
+
assert isinstance(self.auth_manager._session_store, dict)
|
72
|
+
|
73
|
+
def test_init_without_config(self):
|
74
|
+
"""Test AuthManager initialization without config."""
|
75
|
+
with pytest.raises(AuthenticationConfigurationError) as exc_info:
|
76
|
+
AuthManager(None, self.mock_permission_manager)
|
77
|
+
assert "Authentication configuration is required" in str(exc_info.value)
|
78
|
+
|
79
|
+
def test_init_without_permission_manager(self):
|
80
|
+
"""Test AuthManager initialization without permission manager."""
|
81
|
+
with pytest.raises(AuthenticationConfigurationError) as exc_info:
|
82
|
+
AuthManager(self.auth_config, None)
|
83
|
+
assert "Permission manager is required" in str(exc_info.value)
|
84
|
+
|
85
|
+
def test_init_with_short_jwt_secret(self):
|
86
|
+
"""Test AuthManager initialization with short JWT secret."""
|
87
|
+
short_config = AuthConfig(
|
88
|
+
api_keys={"test_api_key_123": "test_user"},
|
89
|
+
jwt_secret="short",
|
90
|
+
jwt_expiry_hours=1,
|
91
|
+
)
|
92
|
+
|
93
|
+
with pytest.raises(AuthenticationConfigurationError) as exc_info:
|
94
|
+
AuthManager(short_config, self.mock_permission_manager)
|
95
|
+
assert "JWT secret must be at least 16 characters" in str(exc_info.value)
|
96
|
+
|
97
|
+
def test_init_with_generated_jwt_secret(self):
|
98
|
+
"""Test AuthManager initialization with auto-generated JWT secret."""
|
99
|
+
config_without_secret = AuthConfig(
|
100
|
+
api_keys={"test_api_key_123": "test_user"},
|
101
|
+
jwt_secret=None,
|
102
|
+
jwt_expiry_hours=1,
|
103
|
+
)
|
104
|
+
|
105
|
+
auth_manager = AuthManager(config_without_secret, self.mock_permission_manager)
|
106
|
+
assert len(auth_manager._jwt_secret) >= 32
|
107
|
+
|
108
|
+
def test_authenticate_api_key_success(self):
|
109
|
+
"""Test successful API key authentication."""
|
110
|
+
# Mock user roles
|
111
|
+
with patch.object(
|
112
|
+
self.auth_manager, "_get_user_roles", return_value=["user", "admin"]
|
113
|
+
):
|
114
|
+
result = self.auth_manager.authenticate_api_key("test_api_key_123")
|
115
|
+
|
116
|
+
assert result.is_valid is True
|
117
|
+
assert result.status == AuthStatus.SUCCESS
|
118
|
+
assert result.username == "test_user"
|
119
|
+
assert result.roles == ["user", "admin"]
|
120
|
+
assert result.auth_method == "api_key"
|
121
|
+
assert result.error_code is None
|
122
|
+
assert result.error_message is None
|
123
|
+
assert result.auth_timestamp is not None
|
124
|
+
|
125
|
+
def test_authenticate_api_key_empty_key(self):
|
126
|
+
"""Test API key authentication with empty key."""
|
127
|
+
result = self.auth_manager.authenticate_api_key("")
|
128
|
+
|
129
|
+
assert result.is_valid is False
|
130
|
+
assert result.status == AuthStatus.INVALID
|
131
|
+
assert result.auth_method == "api_key"
|
132
|
+
assert result.error_code == -32001
|
133
|
+
assert "API key is required" in result.error_message
|
134
|
+
|
135
|
+
def test_authenticate_api_key_none_key(self):
|
136
|
+
"""Test API key authentication with None key."""
|
137
|
+
result = self.auth_manager.authenticate_api_key(None)
|
138
|
+
|
139
|
+
assert result.is_valid is False
|
140
|
+
assert result.status == AuthStatus.INVALID
|
141
|
+
assert result.auth_method == "api_key"
|
142
|
+
assert result.error_code == -32001
|
143
|
+
assert "API key is required" in result.error_message
|
144
|
+
|
145
|
+
def test_authenticate_api_key_invalid_format(self):
|
146
|
+
"""Test API key authentication with invalid format."""
|
147
|
+
with patch(
|
148
|
+
"mcp_security_framework.utils.crypto_utils.validate_api_key_format",
|
149
|
+
return_value=False,
|
150
|
+
):
|
151
|
+
result = self.auth_manager.authenticate_api_key("short")
|
152
|
+
|
153
|
+
assert result.is_valid is False
|
154
|
+
assert result.status == AuthStatus.INVALID
|
155
|
+
assert result.auth_method == "api_key"
|
156
|
+
assert result.error_code == -32002
|
157
|
+
assert "Invalid API key format" in result.error_message
|
158
|
+
|
159
|
+
def test_authenticate_api_key_not_found(self):
|
160
|
+
"""Test API key authentication with non-existent key."""
|
161
|
+
result = self.auth_manager.authenticate_api_key("non_existent_key")
|
162
|
+
|
163
|
+
assert result.is_valid is False
|
164
|
+
assert result.status == AuthStatus.INVALID
|
165
|
+
assert result.auth_method == "api_key"
|
166
|
+
assert result.error_code == -32003
|
167
|
+
assert "Invalid API key" in result.error_message
|
168
|
+
|
169
|
+
def test_authenticate_api_key_roles_error(self):
|
170
|
+
"""Test API key authentication when role retrieval fails."""
|
171
|
+
with patch.object(
|
172
|
+
self.auth_manager, "_get_user_roles", side_effect=Exception("Role error")
|
173
|
+
):
|
174
|
+
result = self.auth_manager.authenticate_api_key("test_api_key_123")
|
175
|
+
|
176
|
+
assert result.is_valid is False
|
177
|
+
assert result.status == AuthStatus.FAILED
|
178
|
+
assert result.auth_method == "api_key"
|
179
|
+
assert result.error_code == -32004
|
180
|
+
assert "Failed to retrieve user roles" in result.error_message
|
181
|
+
|
182
|
+
def test_authenticate_jwt_token_success(self):
|
183
|
+
"""Test successful JWT token authentication."""
|
184
|
+
# Create a valid JWT token
|
185
|
+
user_data = {"username": "test_user", "roles": ["user", "admin"]}
|
186
|
+
token = self.auth_manager.create_jwt_token(user_data)
|
187
|
+
|
188
|
+
# Authenticate with the token
|
189
|
+
result = self.auth_manager.authenticate_jwt_token(token)
|
190
|
+
|
191
|
+
assert result.is_valid is True
|
192
|
+
assert result.status == AuthStatus.SUCCESS
|
193
|
+
assert result.username == "test_user"
|
194
|
+
assert result.roles == ["user", "admin"]
|
195
|
+
assert result.auth_method == "jwt"
|
196
|
+
assert result.error_code is None
|
197
|
+
assert result.error_message is None
|
198
|
+
assert result.auth_timestamp is not None
|
199
|
+
assert result.token_expiry is not None
|
200
|
+
|
201
|
+
def test_authenticate_jwt_token_empty_token(self):
|
202
|
+
"""Test JWT authentication with empty token."""
|
203
|
+
result = self.auth_manager.authenticate_jwt_token("")
|
204
|
+
|
205
|
+
assert result.is_valid is False
|
206
|
+
assert result.status == AuthStatus.INVALID
|
207
|
+
assert result.auth_method == "jwt"
|
208
|
+
assert result.error_code == -32001
|
209
|
+
assert "JWT token is required" in result.error_message
|
210
|
+
|
211
|
+
def test_authenticate_jwt_token_none_token(self):
|
212
|
+
"""Test JWT authentication with None token."""
|
213
|
+
result = self.auth_manager.authenticate_jwt_token(None)
|
214
|
+
|
215
|
+
assert result.is_valid is False
|
216
|
+
assert result.status == AuthStatus.INVALID
|
217
|
+
assert result.auth_method == "jwt"
|
218
|
+
assert result.error_code == -32001
|
219
|
+
assert "JWT token is required" in result.error_message
|
220
|
+
|
221
|
+
def test_authenticate_jwt_token_invalid_token(self):
|
222
|
+
"""Test JWT authentication with invalid token."""
|
223
|
+
result = self.auth_manager.authenticate_jwt_token("invalid.jwt.token")
|
224
|
+
|
225
|
+
assert result.is_valid is False
|
226
|
+
assert result.status == AuthStatus.INVALID
|
227
|
+
assert result.auth_method == "jwt"
|
228
|
+
assert result.error_code == -32003
|
229
|
+
assert "Invalid JWT token" in result.error_message
|
230
|
+
|
231
|
+
def test_authenticate_jwt_token_expired_token(self):
|
232
|
+
"""Test JWT authentication with expired token."""
|
233
|
+
# Create an expired token
|
234
|
+
expired_payload = {
|
235
|
+
"username": "test_user",
|
236
|
+
"roles": ["user"],
|
237
|
+
"iat": int((datetime.now(timezone.utc) - timedelta(hours=2)).timestamp()),
|
238
|
+
"exp": int((datetime.now(timezone.utc) - timedelta(hours=1)).timestamp()),
|
239
|
+
}
|
240
|
+
|
241
|
+
import jwt
|
242
|
+
|
243
|
+
expired_token = jwt.encode(
|
244
|
+
expired_payload, self.auth_manager._jwt_secret, algorithm="HS256"
|
245
|
+
)
|
246
|
+
|
247
|
+
result = self.auth_manager.authenticate_jwt_token(expired_token)
|
248
|
+
|
249
|
+
assert result.is_valid is False
|
250
|
+
assert result.status == AuthStatus.EXPIRED
|
251
|
+
assert result.auth_method == "jwt"
|
252
|
+
assert result.error_code == -32002
|
253
|
+
assert "JWT token has expired" in result.error_message
|
254
|
+
|
255
|
+
def test_authenticate_jwt_token_missing_username(self):
|
256
|
+
"""Test JWT authentication with token missing username."""
|
257
|
+
# Create token without username
|
258
|
+
payload = {
|
259
|
+
"roles": ["user"],
|
260
|
+
"iat": int(datetime.now(timezone.utc).timestamp()),
|
261
|
+
"exp": int((datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()),
|
262
|
+
}
|
263
|
+
|
264
|
+
import jwt
|
265
|
+
|
266
|
+
token = jwt.encode(payload, self.auth_manager._jwt_secret, algorithm="HS256")
|
267
|
+
|
268
|
+
result = self.auth_manager.authenticate_jwt_token(token)
|
269
|
+
|
270
|
+
assert result.is_valid is False
|
271
|
+
assert result.status == AuthStatus.INVALID
|
272
|
+
assert result.auth_method == "jwt"
|
273
|
+
assert result.error_code == -32004
|
274
|
+
assert "JWT token missing username" in result.error_message
|
275
|
+
|
276
|
+
def test_authenticate_jwt_token_cached_result(self):
|
277
|
+
"""Test JWT authentication with cached result."""
|
278
|
+
# Create a valid token
|
279
|
+
user_data = {"username": "test_user", "roles": ["user"]}
|
280
|
+
token = self.auth_manager.create_jwt_token(user_data)
|
281
|
+
|
282
|
+
# First authentication
|
283
|
+
result1 = self.auth_manager.authenticate_jwt_token(token)
|
284
|
+
|
285
|
+
# Second authentication (should use cache)
|
286
|
+
result2 = self.auth_manager.authenticate_jwt_token(token)
|
287
|
+
|
288
|
+
assert result1.is_valid is True
|
289
|
+
assert result2.is_valid is True
|
290
|
+
assert result1.username == result2.username
|
291
|
+
assert result1.roles == result2.roles
|
292
|
+
|
293
|
+
def test_authenticate_certificate_success(self):
|
294
|
+
"""Test successful certificate authentication."""
|
295
|
+
# Test with invalid certificate format (should fail gracefully)
|
296
|
+
cert_pem = "invalid_certificate_data"
|
297
|
+
|
298
|
+
result = self.auth_manager.authenticate_certificate(cert_pem)
|
299
|
+
|
300
|
+
assert result.is_valid is False
|
301
|
+
assert result.status == AuthStatus.INVALID
|
302
|
+
assert result.auth_method == "certificate"
|
303
|
+
assert result.error_code == -32002
|
304
|
+
assert "Invalid certificate format" in result.error_message
|
305
|
+
|
306
|
+
def test_authenticate_certificate_empty_cert(self):
|
307
|
+
"""Test certificate authentication with empty certificate."""
|
308
|
+
result = self.auth_manager.authenticate_certificate("")
|
309
|
+
|
310
|
+
assert result.is_valid is False
|
311
|
+
assert result.status == AuthStatus.INVALID
|
312
|
+
assert result.auth_method == "certificate"
|
313
|
+
assert result.error_code == -32001
|
314
|
+
assert "Certificate is required" in result.error_message
|
315
|
+
|
316
|
+
def test_authenticate_certificate_invalid_format(self):
|
317
|
+
"""Test certificate authentication with invalid format."""
|
318
|
+
result = self.auth_manager.authenticate_certificate("invalid_certificate")
|
319
|
+
|
320
|
+
assert result.is_valid is False
|
321
|
+
assert result.status == AuthStatus.INVALID
|
322
|
+
assert result.auth_method == "certificate"
|
323
|
+
assert result.error_code == -32002
|
324
|
+
assert "Invalid certificate format" in result.error_message
|
325
|
+
|
326
|
+
def test_authenticate_certificate_missing_username(self):
|
327
|
+
"""Test certificate authentication with missing username."""
|
328
|
+
# Test with invalid certificate format (should fail before username check)
|
329
|
+
cert_pem = "invalid_certificate_data"
|
330
|
+
|
331
|
+
result = self.auth_manager.authenticate_certificate(cert_pem)
|
332
|
+
|
333
|
+
assert result.is_valid is False
|
334
|
+
assert result.status == AuthStatus.INVALID
|
335
|
+
assert result.auth_method == "certificate"
|
336
|
+
assert result.error_code == -32002
|
337
|
+
assert "Invalid certificate format" in result.error_message
|
338
|
+
|
339
|
+
def test_create_jwt_token_success(self):
|
340
|
+
"""Test successful JWT token creation."""
|
341
|
+
user_data = {
|
342
|
+
"username": "test_user",
|
343
|
+
"roles": ["user", "admin"],
|
344
|
+
"permissions": ["read:users", "write:posts"],
|
345
|
+
}
|
346
|
+
|
347
|
+
token = self.auth_manager.create_jwt_token(user_data)
|
348
|
+
|
349
|
+
assert isinstance(token, str)
|
350
|
+
assert len(token) > 0
|
351
|
+
|
352
|
+
# Verify token can be decoded
|
353
|
+
import jwt
|
354
|
+
|
355
|
+
payload = jwt.decode(
|
356
|
+
token,
|
357
|
+
self.auth_manager._jwt_secret,
|
358
|
+
algorithms=["HS256"],
|
359
|
+
options={"verify_aud": False},
|
360
|
+
)
|
361
|
+
|
362
|
+
assert payload["username"] == "test_user"
|
363
|
+
assert payload["roles"] == ["user", "admin"]
|
364
|
+
assert payload["permissions"] == ["read:users", "write:posts"]
|
365
|
+
assert "iat" in payload
|
366
|
+
assert "exp" in payload
|
367
|
+
assert "sub" in payload
|
368
|
+
assert "iss" in payload
|
369
|
+
|
370
|
+
def test_create_jwt_token_with_additional_data(self):
|
371
|
+
"""Test JWT token creation with additional user data."""
|
372
|
+
user_data = {
|
373
|
+
"username": "test_user",
|
374
|
+
"roles": ["user"],
|
375
|
+
"additional_data": {"email": "test@example.com", "department": "IT"},
|
376
|
+
}
|
377
|
+
|
378
|
+
token = self.auth_manager.create_jwt_token(user_data)
|
379
|
+
|
380
|
+
# Verify token can be decoded
|
381
|
+
import jwt
|
382
|
+
|
383
|
+
payload = jwt.decode(
|
384
|
+
token,
|
385
|
+
self.auth_manager._jwt_secret,
|
386
|
+
algorithms=["HS256"],
|
387
|
+
options={"verify_aud": False},
|
388
|
+
)
|
389
|
+
|
390
|
+
assert payload["username"] == "test_user"
|
391
|
+
assert payload["user_data"]["email"] == "test@example.com"
|
392
|
+
assert payload["user_data"]["department"] == "IT"
|
393
|
+
|
394
|
+
def test_create_jwt_token_invalid_user_data(self):
|
395
|
+
"""Test JWT token creation with invalid user data."""
|
396
|
+
with pytest.raises(JWTValidationError) as exc_info:
|
397
|
+
self.auth_manager.create_jwt_token(None)
|
398
|
+
assert "User data must be provided" in str(exc_info.value)
|
399
|
+
|
400
|
+
with pytest.raises(JWTValidationError) as exc_info:
|
401
|
+
self.auth_manager.create_jwt_token({})
|
402
|
+
assert "User data dictionary cannot be empty" in str(exc_info.value)
|
403
|
+
|
404
|
+
with pytest.raises(JWTValidationError) as exc_info:
|
405
|
+
self.auth_manager.create_jwt_token({"roles": ["user"]})
|
406
|
+
assert "Username is required in user data" in str(exc_info.value)
|
407
|
+
|
408
|
+
def test_validate_jwt_token_valid(self):
|
409
|
+
"""Test JWT token validation with valid token."""
|
410
|
+
user_data = {"username": "test_user", "roles": ["user"]}
|
411
|
+
token = self.auth_manager.create_jwt_token(user_data)
|
412
|
+
|
413
|
+
is_valid = self.auth_manager.validate_jwt_token(token)
|
414
|
+
assert is_valid is True
|
415
|
+
|
416
|
+
def test_validate_jwt_token_invalid(self):
|
417
|
+
"""Test JWT token validation with invalid token."""
|
418
|
+
is_valid = self.auth_manager.validate_jwt_token("invalid.token")
|
419
|
+
assert is_valid is False
|
420
|
+
|
421
|
+
is_valid = self.auth_manager.validate_jwt_token("")
|
422
|
+
assert is_valid is False
|
423
|
+
|
424
|
+
is_valid = self.auth_manager.validate_jwt_token(None)
|
425
|
+
assert is_valid is False
|
426
|
+
|
427
|
+
def test_add_api_key_success(self):
|
428
|
+
"""Test successful API key addition."""
|
429
|
+
success = self.auth_manager.add_api_key("new_user", "new_api_key_456789")
|
430
|
+
|
431
|
+
assert success is True
|
432
|
+
assert "new_user" in self.auth_manager._api_keys
|
433
|
+
assert self.auth_manager._api_keys["new_user"] == "new_api_key_456789"
|
434
|
+
|
435
|
+
def test_add_api_key_invalid_input(self):
|
436
|
+
"""Test API key addition with invalid input."""
|
437
|
+
success = self.auth_manager.add_api_key("", "valid_key")
|
438
|
+
assert success is False
|
439
|
+
|
440
|
+
success = self.auth_manager.add_api_key("user", "")
|
441
|
+
assert success is False
|
442
|
+
|
443
|
+
success = self.auth_manager.add_api_key(None, "valid_key")
|
444
|
+
assert success is False
|
445
|
+
|
446
|
+
def test_add_api_key_invalid_format(self):
|
447
|
+
"""Test API key addition with invalid format."""
|
448
|
+
with patch(
|
449
|
+
"mcp_security_framework.core.auth_manager.validate_api_key_format",
|
450
|
+
return_value=False,
|
451
|
+
):
|
452
|
+
success = self.auth_manager.add_api_key("user", "invalid_key")
|
453
|
+
assert success is False
|
454
|
+
|
455
|
+
def test_remove_api_key_success(self):
|
456
|
+
"""Test successful API key removal."""
|
457
|
+
# Add a key first
|
458
|
+
self.auth_manager.add_api_key("temp_user", "temp_key_123456789")
|
459
|
+
assert "temp_user" in self.auth_manager._api_keys
|
460
|
+
|
461
|
+
# Remove the key
|
462
|
+
success = self.auth_manager.remove_api_key("temp_user")
|
463
|
+
assert success is True
|
464
|
+
assert "temp_user" not in self.auth_manager._api_keys
|
465
|
+
|
466
|
+
def test_remove_api_key_not_found(self):
|
467
|
+
"""Test API key removal for non-existent user."""
|
468
|
+
success = self.auth_manager.remove_api_key("non_existent_user")
|
469
|
+
assert success is False
|
470
|
+
|
471
|
+
def test_clear_token_cache(self):
|
472
|
+
"""Test token cache clearing."""
|
473
|
+
# Add a token to cache
|
474
|
+
user_data = {"username": "test_user", "roles": ["user"]}
|
475
|
+
token = self.auth_manager.create_jwt_token(user_data)
|
476
|
+
self.auth_manager.authenticate_jwt_token(token)
|
477
|
+
|
478
|
+
assert len(self.auth_manager._token_cache) > 0
|
479
|
+
|
480
|
+
# Clear cache
|
481
|
+
self.auth_manager.clear_token_cache()
|
482
|
+
assert len(self.auth_manager._token_cache) == 0
|
483
|
+
|
484
|
+
def test_clear_session_store(self):
|
485
|
+
"""Test session store clearing."""
|
486
|
+
# Add a session
|
487
|
+
self.auth_manager._session_store["test_session"] = {"user": "test"}
|
488
|
+
|
489
|
+
assert len(self.auth_manager._session_store) > 0
|
490
|
+
|
491
|
+
# Clear session store
|
492
|
+
self.auth_manager.clear_session_store()
|
493
|
+
assert len(self.auth_manager._session_store) == 0
|
494
|
+
|
495
|
+
def test_get_user_roles(self):
|
496
|
+
"""Test user roles retrieval."""
|
497
|
+
roles = self.auth_manager._get_user_roles("test_user")
|
498
|
+
assert isinstance(roles, list)
|
499
|
+
assert "user" in roles
|
500
|
+
|
501
|
+
def test_extract_username_from_certificate(self):
|
502
|
+
"""Test username extraction from certificate."""
|
503
|
+
from cryptography import x509
|
504
|
+
from cryptography.x509.oid import NameOID
|
505
|
+
|
506
|
+
# Create a mock certificate
|
507
|
+
mock_cert = Mock(spec=x509.Certificate)
|
508
|
+
mock_cn = Mock()
|
509
|
+
mock_cn.value = "test_user"
|
510
|
+
mock_cert.subject.get_attributes_for_oid.return_value = [mock_cn]
|
511
|
+
|
512
|
+
username = self.auth_manager._extract_username_from_certificate(mock_cert)
|
513
|
+
assert username == "test_user"
|
514
|
+
|
515
|
+
def test_is_token_expired(self):
|
516
|
+
"""Test token expiration check."""
|
517
|
+
# Create a non-expired result
|
518
|
+
non_expired_result = AuthResult(
|
519
|
+
is_valid=True,
|
520
|
+
status=AuthStatus.SUCCESS,
|
521
|
+
username="test_user",
|
522
|
+
roles=["user"],
|
523
|
+
auth_method="jwt",
|
524
|
+
token_expiry=datetime.now(timezone.utc) + timedelta(hours=1),
|
525
|
+
)
|
526
|
+
|
527
|
+
assert self.auth_manager._is_token_expired(non_expired_result) is False
|
528
|
+
|
529
|
+
# Create an expired result
|
530
|
+
expired_result = AuthResult(
|
531
|
+
is_valid=True,
|
532
|
+
status=AuthStatus.SUCCESS,
|
533
|
+
username="test_user",
|
534
|
+
roles=["user"],
|
535
|
+
auth_method="jwt",
|
536
|
+
token_expiry=datetime.now(timezone.utc) - timedelta(hours=1),
|
537
|
+
)
|
538
|
+
|
539
|
+
assert self.auth_manager._is_token_expired(expired_result) is True
|
540
|
+
|
541
|
+
# Test with no expiration
|
542
|
+
no_expiry_result = AuthResult(
|
543
|
+
is_valid=True,
|
544
|
+
status=AuthStatus.SUCCESS,
|
545
|
+
username="test_user",
|
546
|
+
roles=["user"],
|
547
|
+
auth_method="jwt",
|
548
|
+
)
|
549
|
+
|
550
|
+
assert self.auth_manager._is_token_expired(no_expiry_result) is False
|
551
|
+
|
552
|
+
|
553
|
+
class TestAuthManagerExceptions:
|
554
|
+
"""Test suite for AuthManager exception classes."""
|
555
|
+
|
556
|
+
def test_authentication_configuration_error(self):
|
557
|
+
"""Test AuthenticationConfigurationError."""
|
558
|
+
error = AuthenticationConfigurationError("Test error", -32001)
|
559
|
+
assert error.message == "Test error"
|
560
|
+
assert error.error_code == -32001
|
561
|
+
assert str(error) == "Test error"
|
562
|
+
|
563
|
+
def test_authentication_error(self):
|
564
|
+
"""Test AuthenticationError."""
|
565
|
+
error = AuthenticationError("Auth failed", -32002)
|
566
|
+
assert error.message == "Auth failed"
|
567
|
+
assert error.error_code == -32002
|
568
|
+
assert str(error) == "Auth failed"
|
569
|
+
|
570
|
+
def test_jwt_validation_error(self):
|
571
|
+
"""Test JWTValidationError."""
|
572
|
+
error = JWTValidationError("JWT invalid", -32003)
|
573
|
+
assert error.message == "JWT invalid"
|
574
|
+
assert error.error_code == -32003
|
575
|
+
assert str(error) == "JWT invalid"
|
576
|
+
|
577
|
+
def test_certificate_validation_error(self):
|
578
|
+
"""Test CertificateValidationError."""
|
579
|
+
error = CertificateValidationError("Cert invalid", -32004)
|
580
|
+
assert error.message == "Cert invalid"
|
581
|
+
assert error.error_code == -32004
|
582
|
+
assert str(error) == "Cert invalid"
|