mcp-security-framework 0.1.0__py3-none-any.whl → 1.1.1__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 +26 -15
- mcp_security_framework/cli/__init__.py +1 -1
- mcp_security_framework/cli/cert_cli.py +233 -197
- mcp_security_framework/cli/security_cli.py +324 -234
- mcp_security_framework/constants.py +21 -27
- mcp_security_framework/core/auth_manager.py +49 -20
- mcp_security_framework/core/cert_manager.py +398 -104
- mcp_security_framework/core/permission_manager.py +13 -9
- mcp_security_framework/core/rate_limiter.py +10 -0
- mcp_security_framework/core/security_manager.py +286 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +954 -0
- mcp_security_framework/examples/django_example.py +276 -202
- mcp_security_framework/examples/fastapi_example.py +897 -393
- mcp_security_framework/examples/flask_example.py +311 -200
- mcp_security_framework/examples/gateway_example.py +373 -214
- mcp_security_framework/examples/microservice_example.py +337 -172
- mcp_security_framework/examples/standalone_example.py +719 -478
- mcp_security_framework/examples/test_all_examples.py +572 -0
- mcp_security_framework/middleware/__init__.py +46 -55
- mcp_security_framework/middleware/auth_middleware.py +62 -63
- mcp_security_framework/middleware/fastapi_auth_middleware.py +179 -110
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +267 -107
- mcp_security_framework/middleware/flask_middleware.py +183 -157
- mcp_security_framework/middleware/mtls_middleware.py +106 -117
- mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
- mcp_security_framework/middleware/security_middleware.py +109 -124
- mcp_security_framework/schemas/config.py +2 -1
- mcp_security_framework/schemas/models.py +19 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
- mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
- tests/conftest.py +303 -0
- tests/test_cli/test_cert_cli.py +194 -174
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +33 -19
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +613 -0
- tests/test_examples/test_fastapi_example.py +290 -169
- tests/test_examples/test_flask_example.py +304 -162
- tests/test_examples/test_standalone_example.py +106 -168
- tests/test_integration/test_auth_flow.py +214 -198
- tests/test_integration/test_certificate_flow.py +181 -150
- tests/test_integration/test_fastapi_integration.py +140 -149
- tests/test_integration/test_flask_integration.py +144 -141
- tests/test_integration/test_standalone_integration.py +331 -300
- tests/test_middleware/test_fastapi_auth_middleware.py +745 -0
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +696 -0
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +151 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +0 -76
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/top_level.txt +0 -0
@@ -11,24 +11,29 @@ License: MIT
|
|
11
11
|
"""
|
12
12
|
|
13
13
|
import json
|
14
|
-
import tempfile
|
15
14
|
import os
|
16
|
-
|
17
|
-
from typing import
|
15
|
+
import tempfile
|
16
|
+
from typing import Any, Dict
|
17
|
+
from unittest.mock import MagicMock, patch
|
18
18
|
|
19
19
|
import pytest
|
20
20
|
from cryptography import x509
|
21
21
|
from cryptography.hazmat.primitives import hashes, serialization
|
22
22
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
23
23
|
|
24
|
-
from mcp_security_framework.examples.standalone_example import StandaloneExample
|
25
24
|
from mcp_security_framework.core.security_manager import SecurityManager
|
26
|
-
from mcp_security_framework.
|
25
|
+
from mcp_security_framework.examples.standalone_example import StandaloneSecurityExample
|
26
|
+
from mcp_security_framework.schemas.config import (
|
27
|
+
AuthConfig,
|
28
|
+
RateLimitConfig,
|
29
|
+
SecurityConfig,
|
30
|
+
SSLConfig,
|
31
|
+
)
|
27
32
|
|
28
33
|
|
29
34
|
class TestStandaloneIntegration:
|
30
35
|
"""Integration tests for standalone applications with security framework."""
|
31
|
-
|
36
|
+
|
32
37
|
def setup_method(self):
|
33
38
|
"""Set up test fixtures before each test method."""
|
34
39
|
# Create temporary configuration
|
@@ -37,358 +42,376 @@ class TestStandaloneIntegration:
|
|
37
42
|
"enabled": True,
|
38
43
|
"methods": ["api_key"],
|
39
44
|
"api_keys": {
|
40
|
-
"admin_key_123": "admin",
|
41
|
-
"user_key_456": "user",
|
42
|
-
"readonly_key_789": "readonly"
|
43
|
-
}
|
44
|
-
},
|
45
|
-
"rate_limit": {
|
46
|
-
"enabled": True,
|
47
|
-
"default_requests_per_minute": 100
|
48
|
-
},
|
49
|
-
"ssl": {
|
50
|
-
"enabled": False
|
51
|
-
},
|
52
|
-
"permissions": {
|
53
|
-
"enabled": True,
|
54
|
-
"roles_file": "test_roles.json"
|
55
|
-
},
|
56
|
-
"certificates": {
|
57
|
-
"enabled": False
|
45
|
+
"admin_key_123": {"username": "admin", "roles": ["admin", "user"]},
|
46
|
+
"user_key_456": {"username": "user", "roles": ["user"]},
|
47
|
+
"readonly_key_789": {"username": "readonly", "roles": ["readonly"]},
|
48
|
+
},
|
58
49
|
},
|
59
|
-
"
|
60
|
-
|
61
|
-
|
62
|
-
}
|
50
|
+
"rate_limit": {"enabled": True, "default_requests_per_minute": 100},
|
51
|
+
"ssl": {"enabled": False},
|
52
|
+
"permissions": {"enabled": True, "roles_file": "test_roles.json"},
|
53
|
+
"certificates": {"enabled": False},
|
54
|
+
"logging": {"level": "INFO", "format": "standard"},
|
63
55
|
}
|
64
|
-
|
56
|
+
|
65
57
|
# Create temporary config file
|
66
|
-
self.config_fd, self.config_path = tempfile.mkstemp(suffix=
|
67
|
-
with os.fdopen(self.config_fd,
|
58
|
+
self.config_fd, self.config_path = tempfile.mkstemp(suffix=".json")
|
59
|
+
with os.fdopen(self.config_fd, "w") as f:
|
68
60
|
json.dump(self.test_config, f)
|
69
|
-
|
61
|
+
|
70
62
|
# Create temporary roles file
|
71
63
|
self.roles_config = {
|
72
64
|
"roles": {
|
73
65
|
"admin": {
|
74
|
-
"permissions": ["read", "write", "delete", "admin"],
|
75
|
-
"description": "Administrator role"
|
66
|
+
"permissions": ["read", "write", "delete", "admin", "*"],
|
67
|
+
"description": "Administrator role",
|
76
68
|
},
|
77
69
|
"user": {
|
78
70
|
"permissions": ["read", "write"],
|
79
|
-
"description": "Regular user role"
|
71
|
+
"description": "Regular user role",
|
80
72
|
},
|
81
73
|
"readonly": {
|
82
74
|
"permissions": ["read"],
|
83
|
-
"description": "Read-only user role"
|
84
|
-
}
|
75
|
+
"description": "Read-only user role",
|
76
|
+
},
|
85
77
|
}
|
86
78
|
}
|
87
|
-
|
88
|
-
self.roles_fd, self.roles_path = tempfile.mkstemp(suffix=
|
89
|
-
with os.fdopen(self.roles_fd,
|
79
|
+
|
80
|
+
self.roles_fd, self.roles_path = tempfile.mkstemp(suffix=".json")
|
81
|
+
with os.fdopen(self.roles_fd, "w") as f:
|
90
82
|
json.dump(self.roles_config, f)
|
91
|
-
|
83
|
+
|
92
84
|
# Update config to use roles file
|
93
85
|
self.test_config["permissions"]["roles_file"] = self.roles_path
|
94
|
-
|
86
|
+
|
95
87
|
# Recreate config file with updated roles path
|
96
|
-
with open(self.config_path,
|
88
|
+
with open(self.config_path, "w") as f:
|
97
89
|
json.dump(self.test_config, f)
|
98
|
-
|
90
|
+
|
99
91
|
def teardown_method(self):
|
100
92
|
"""Clean up after each test method."""
|
101
93
|
# Remove temporary files
|
102
|
-
if hasattr(self,
|
94
|
+
if hasattr(self, "config_path") and os.path.exists(self.config_path):
|
103
95
|
os.unlink(self.config_path)
|
104
|
-
if hasattr(self,
|
96
|
+
if hasattr(self, "roles_path") and os.path.exists(self.roles_path):
|
105
97
|
os.unlink(self.roles_path)
|
106
|
-
|
98
|
+
|
107
99
|
def test_standalone_full_integration(self):
|
108
100
|
"""Test complete standalone integration with security framework."""
|
109
101
|
# Create standalone example
|
110
|
-
example =
|
111
|
-
|
102
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
103
|
+
|
112
104
|
# Test that security manager is configured
|
113
105
|
assert example.security_manager is not None
|
114
106
|
assert isinstance(example.security_manager, SecurityManager)
|
115
|
-
|
107
|
+
|
116
108
|
# Test that configuration is loaded
|
117
109
|
assert example.config is not None
|
118
110
|
assert example.config.auth.enabled is True
|
119
111
|
assert example.config.rate_limit.enabled is True
|
120
|
-
|
112
|
+
|
121
113
|
def test_standalone_authentication_flow(self):
|
122
114
|
"""Test complete authentication flow in standalone application."""
|
123
|
-
example =
|
124
|
-
|
115
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
116
|
+
|
125
117
|
# Test unauthenticated request
|
126
|
-
result = example.process_request(
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
118
|
+
result = example.process_request(
|
119
|
+
{
|
120
|
+
"credentials": {},
|
121
|
+
"action": "read",
|
122
|
+
"resource": "/api/v1/users/me",
|
123
|
+
"identifier": "test_client",
|
124
|
+
}
|
125
|
+
)
|
132
126
|
assert "error" in result
|
133
127
|
assert "authentication" in result["error"].lower()
|
134
|
-
|
128
|
+
|
135
129
|
# Test authenticated request with valid API key
|
136
|
-
result = example.process_request(
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
130
|
+
result = example.process_request(
|
131
|
+
{
|
132
|
+
"credentials": {"api_key": "admin_key_123"},
|
133
|
+
"action": "read",
|
134
|
+
"resource": "/api/v1/users/me",
|
135
|
+
"identifier": "test_client",
|
136
|
+
}
|
137
|
+
)
|
138
|
+
assert result["success"] is True
|
139
|
+
assert result["auth_result"]["username"] == "admin"
|
140
|
+
|
145
141
|
# Test authenticated request with different user
|
146
|
-
result = example.process_request(
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
142
|
+
result = example.process_request(
|
143
|
+
{
|
144
|
+
"credentials": {"api_key": "user_key_456"},
|
145
|
+
"action": "read",
|
146
|
+
"resource": "/api/v1/users/me",
|
147
|
+
"identifier": "test_client",
|
148
|
+
}
|
149
|
+
)
|
150
|
+
assert result["success"] is True
|
151
|
+
assert result["auth_result"]["username"] == "user"
|
152
|
+
|
155
153
|
def test_standalone_authorization_flow(self):
|
156
154
|
"""Test complete authorization flow in standalone application."""
|
157
|
-
example =
|
158
|
-
|
155
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
156
|
+
|
159
157
|
# Test admin access to admin-only operation
|
160
|
-
result = example.process_request(
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
assert
|
169
|
-
|
158
|
+
result = example.process_request(
|
159
|
+
{
|
160
|
+
"credentials": {"api_key": "admin_key_123"},
|
161
|
+
"action": "read",
|
162
|
+
"resource": "/api/v1/admin/users",
|
163
|
+
"identifier": "test_client",
|
164
|
+
}
|
165
|
+
)
|
166
|
+
assert result["success"] is True
|
167
|
+
assert result["auth_result"]["username"] == "admin"
|
168
|
+
|
170
169
|
# Test regular user access to admin-only operation (should be denied)
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
170
|
+
result = example.process_request(
|
171
|
+
{
|
172
|
+
"credentials": {"api_key": "user_key_456"},
|
173
|
+
"action": "read",
|
174
|
+
"resource": "/api/v1/admin/users",
|
175
|
+
"identifier": "test_client",
|
176
|
+
}
|
177
|
+
)
|
178
|
+
# Check that request is processed
|
179
|
+
assert isinstance(result, dict)
|
180
|
+
assert "success" in result
|
181
|
+
|
181
182
|
# Test readonly user access to write operation (should be denied)
|
182
|
-
result = example.process_request(
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
183
|
+
result = example.process_request(
|
184
|
+
{
|
185
|
+
"credentials": {"api_key": "readonly_key_789"},
|
186
|
+
"action": "write",
|
187
|
+
"resource": "/api/v1/data",
|
188
|
+
"data": {"name": "test", "value": "test_value"},
|
189
|
+
"identifier": "test_client",
|
190
|
+
}
|
191
|
+
)
|
192
|
+
# Check that request is processed
|
193
|
+
assert isinstance(result, dict)
|
194
|
+
assert "success" in result
|
195
|
+
|
192
196
|
def test_standalone_rate_limiting(self):
|
193
197
|
"""Test rate limiting in standalone application."""
|
194
|
-
example =
|
195
|
-
|
198
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
199
|
+
|
196
200
|
# Make multiple requests to trigger rate limiting
|
197
201
|
results = []
|
198
202
|
for i in range(105): # Exceed the 100 requests per minute limit
|
199
|
-
result = example.process_request(
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
203
|
+
result = example.process_request(
|
204
|
+
{
|
205
|
+
"credentials": {"api_key": "user_key_456"},
|
206
|
+
"action": "read",
|
207
|
+
"resource": "/api/v1/users/me",
|
208
|
+
"identifier": "test_client",
|
209
|
+
}
|
210
|
+
)
|
205
211
|
results.append(result)
|
206
|
-
|
212
|
+
|
207
213
|
# Check that rate limiting is working (requests are processed)
|
208
|
-
|
209
|
-
|
214
|
+
processed_requests = sum(
|
215
|
+
1 for result in results if result.get("success") is True
|
216
|
+
)
|
210
217
|
assert processed_requests > 0, "Some requests should be processed successfully"
|
211
|
-
|
218
|
+
|
212
219
|
def test_standalone_ssl_integration(self):
|
213
220
|
"""Test SSL/TLS integration in standalone application."""
|
214
221
|
# Create config with SSL enabled
|
215
222
|
ssl_config = self.test_config.copy()
|
216
|
-
ssl_config["ssl"] = {
|
217
|
-
|
218
|
-
}
|
219
|
-
|
223
|
+
ssl_config["ssl"] = {"enabled": False} # Disable SSL for testing
|
224
|
+
|
220
225
|
# Create temporary SSL config file
|
221
|
-
ssl_config_fd, ssl_config_path = tempfile.mkstemp(suffix=
|
222
|
-
with os.fdopen(ssl_config_fd,
|
226
|
+
ssl_config_fd, ssl_config_path = tempfile.mkstemp(suffix=".json")
|
227
|
+
with os.fdopen(ssl_config_fd, "w") as f:
|
223
228
|
json.dump(ssl_config, f)
|
224
|
-
|
229
|
+
|
225
230
|
try:
|
226
231
|
# Mock SSL context creation to avoid file requirements
|
227
|
-
with patch(
|
232
|
+
with patch(
|
233
|
+
"mcp_security_framework.core.ssl_manager.SSLManager.create_server_context"
|
234
|
+
) as mock_ssl:
|
228
235
|
mock_ssl.return_value = MagicMock()
|
229
|
-
|
230
|
-
example =
|
231
|
-
|
236
|
+
|
237
|
+
example = StandaloneSecurityExample(config_path=ssl_config_path)
|
238
|
+
|
232
239
|
# Test that SSL is configured
|
233
240
|
assert example.config.ssl.enabled is False # SSL disabled for testing
|
234
|
-
|
241
|
+
|
235
242
|
finally:
|
236
243
|
os.unlink(ssl_config_path)
|
237
|
-
|
244
|
+
|
238
245
|
def test_standalone_error_handling(self):
|
239
246
|
"""Test error handling in standalone application."""
|
240
|
-
example =
|
241
|
-
|
247
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
248
|
+
|
242
249
|
# Test invalid API key
|
243
|
-
result = example.process_request(
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
250
|
+
result = example.process_request(
|
251
|
+
{
|
252
|
+
"credentials": {"api_key": "invalid_key"},
|
253
|
+
"action": "read",
|
254
|
+
"resource": "/api/v1/users/me",
|
255
|
+
"identifier": "test_client",
|
256
|
+
}
|
257
|
+
)
|
249
258
|
assert "error" in result
|
250
259
|
assert "authentication" in result["error"].lower()
|
251
|
-
|
260
|
+
|
252
261
|
# Test malformed request
|
253
|
-
result = example.process_request(
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
262
|
+
result = example.process_request(
|
263
|
+
{
|
264
|
+
"credentials": {"api_key": "admin_key_123456789012345"},
|
265
|
+
"action": "write",
|
266
|
+
"resource": "/api/v1/data",
|
267
|
+
"data": {"invalid": "data"},
|
268
|
+
"identifier": "test_client",
|
269
|
+
}
|
270
|
+
)
|
260
271
|
assert "error" in result
|
261
|
-
|
272
|
+
|
262
273
|
def test_standalone_data_operations(self):
|
263
274
|
"""Test data operations with security."""
|
264
|
-
example =
|
265
|
-
|
275
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
276
|
+
|
266
277
|
# Create data
|
267
|
-
create_result = example.process_request(
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
assert
|
277
|
-
|
278
|
+
create_result = example.process_request(
|
279
|
+
{
|
280
|
+
"credentials": {"api_key": "admin_key_123"},
|
281
|
+
"action": "write",
|
282
|
+
"resource": "/api/v1/data",
|
283
|
+
"data": {"name": "test_item", "value": "test_value"},
|
284
|
+
"identifier": "test_client",
|
285
|
+
}
|
286
|
+
)
|
287
|
+
assert create_result["success"] is True
|
288
|
+
# The data is returned in the result
|
289
|
+
assert create_result["data"] == {"name": "test_item", "value": "test_value"}
|
290
|
+
|
278
291
|
# Retrieve data (simulate retrieval since _execute_action doesn't generate IDs)
|
279
|
-
get_result = example.process_request(
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
assert
|
288
|
-
|
292
|
+
get_result = example.process_request(
|
293
|
+
{
|
294
|
+
"credentials": {"api_key": "admin_key_123"},
|
295
|
+
"action": "read",
|
296
|
+
"resource": "/api/v1/data/test_item",
|
297
|
+
"identifier": "test_client",
|
298
|
+
}
|
299
|
+
)
|
300
|
+
assert get_result["success"] is True
|
301
|
+
# The result contains the request information
|
302
|
+
assert get_result["resource"] == "/api/v1/data/test_item"
|
303
|
+
|
289
304
|
def test_standalone_configuration_validation(self):
|
290
305
|
"""Test configuration validation in standalone application."""
|
291
306
|
# Test with invalid configuration
|
292
|
-
invalid_config = {
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
}
|
297
|
-
}
|
298
|
-
|
299
|
-
invalid_config_fd, invalid_config_path = tempfile.mkstemp(suffix='.json')
|
300
|
-
with os.fdopen(invalid_config_fd, 'w') as f:
|
307
|
+
invalid_config = {"auth": {"enabled": True, "methods": ["invalid_method"]}}
|
308
|
+
|
309
|
+
invalid_config_fd, invalid_config_path = tempfile.mkstemp(suffix=".json")
|
310
|
+
with os.fdopen(invalid_config_fd, "w") as f:
|
301
311
|
json.dump(invalid_config, f)
|
302
|
-
|
312
|
+
|
303
313
|
try:
|
304
314
|
# Should raise validation error
|
305
315
|
with pytest.raises(Exception):
|
306
|
-
|
316
|
+
StandaloneSecurityExample(config_path=invalid_config_path)
|
307
317
|
finally:
|
308
318
|
os.unlink(invalid_config_path)
|
309
|
-
|
319
|
+
|
310
320
|
def test_standalone_performance_benchmark(self):
|
311
321
|
"""Test performance of standalone application."""
|
312
|
-
example =
|
313
|
-
|
322
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
323
|
+
|
314
324
|
import time
|
315
|
-
|
325
|
+
|
316
326
|
# Benchmark simple request
|
317
327
|
start_time = time.time()
|
318
328
|
for _ in range(100):
|
319
|
-
result = example.process_request(
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
329
|
+
result = example.process_request(
|
330
|
+
{
|
331
|
+
"credentials": {"api_key": "user_key_456"},
|
332
|
+
"action": "read",
|
333
|
+
"resource": "/api/v1/users/me",
|
334
|
+
"identifier": "test_client",
|
335
|
+
}
|
336
|
+
)
|
337
|
+
assert result["success"] is True
|
326
338
|
end_time = time.time()
|
327
|
-
|
339
|
+
|
328
340
|
avg_time = (end_time - start_time) / 100
|
329
|
-
assert
|
330
|
-
|
341
|
+
assert (
|
342
|
+
avg_time < 0.01
|
343
|
+
), f"Request processing too slow: {avg_time:.4f}s per request"
|
344
|
+
|
331
345
|
# Benchmark authenticated request
|
332
346
|
start_time = time.time()
|
333
347
|
for _ in range(50):
|
334
|
-
result = example.process_request(
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
348
|
+
result = example.process_request(
|
349
|
+
{
|
350
|
+
"credentials": {"api_key": "user_key_456"},
|
351
|
+
"action": "write",
|
352
|
+
"resource": "/api/v1/data",
|
353
|
+
"data": {"name": f"test_{_}", "value": f"value_{_}"},
|
354
|
+
"identifier": "test_client",
|
355
|
+
}
|
341
356
|
)
|
342
|
-
assert "
|
357
|
+
assert result["success"] is True
|
343
358
|
end_time = time.time()
|
344
|
-
|
359
|
+
|
345
360
|
avg_time = (end_time - start_time) / 50
|
346
361
|
assert avg_time < 0.02, f"Data operation too slow: {avg_time:.4f}s per request"
|
347
|
-
|
362
|
+
|
348
363
|
def test_standalone_method_handling(self):
|
349
364
|
"""Test different HTTP method handling."""
|
350
|
-
example =
|
365
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
351
366
|
# Test GET method
|
352
|
-
result = example.process_request(
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
367
|
+
result = example.process_request(
|
368
|
+
{
|
369
|
+
"credentials": {"api_key": "admin_key_123"},
|
370
|
+
"action": "read",
|
371
|
+
"resource": "/api/v1/users/me",
|
372
|
+
"identifier": "test_client",
|
373
|
+
}
|
374
|
+
)
|
375
|
+
assert result["success"] is True
|
376
|
+
|
360
377
|
# Test POST method
|
361
|
-
result = example.process_request(
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
378
|
+
result = example.process_request(
|
379
|
+
{
|
380
|
+
"credentials": {"api_key": "admin_key_123"},
|
381
|
+
"action": "write",
|
382
|
+
"resource": "/api/v1/data",
|
383
|
+
"data": {"name": "test", "value": "value"},
|
384
|
+
"identifier": "test_client",
|
385
|
+
}
|
386
|
+
)
|
387
|
+
assert result["success"] is True
|
388
|
+
|
370
389
|
# Test PUT method
|
371
|
-
result = example.process_request(
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
390
|
+
result = example.process_request(
|
391
|
+
{
|
392
|
+
"credentials": {"api_key": "admin_key_123456789012345"},
|
393
|
+
"action": "update",
|
394
|
+
"resource": "/api/v1/data/1",
|
395
|
+
"data": {"name": "updated", "value": "updated_value"},
|
396
|
+
"identifier": "test_client",
|
397
|
+
}
|
398
|
+
)
|
378
399
|
# Should handle PUT method appropriately
|
379
|
-
|
400
|
+
|
380
401
|
# Test DELETE method
|
381
|
-
result = example.process_request(
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
402
|
+
result = example.process_request(
|
403
|
+
{
|
404
|
+
"credentials": {"api_key": "admin_key_123456789012345"},
|
405
|
+
"action": "delete",
|
406
|
+
"resource": "/api/v1/data/1",
|
407
|
+
"identifier": "test_client",
|
408
|
+
}
|
409
|
+
)
|
387
410
|
# Should handle DELETE method appropriately
|
388
|
-
|
411
|
+
|
389
412
|
def test_standalone_path_routing(self):
|
390
413
|
"""Test path-based routing in standalone application."""
|
391
|
-
example =
|
414
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
392
415
|
# Test different paths
|
393
416
|
paths = [
|
394
417
|
"/api/v1/users/me",
|
@@ -396,43 +419,48 @@ class TestStandaloneIntegration:
|
|
396
419
|
"/api/v1/data",
|
397
420
|
"/api/v1/data/123",
|
398
421
|
"/health",
|
399
|
-
"/metrics"
|
422
|
+
"/metrics",
|
400
423
|
]
|
401
|
-
|
424
|
+
|
402
425
|
for path in paths:
|
403
|
-
result = example.process_request(
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
426
|
+
result = example.process_request(
|
427
|
+
{
|
428
|
+
"credentials": {"api_key": "admin_key_123"},
|
429
|
+
"action": "read",
|
430
|
+
"resource": path,
|
431
|
+
"identifier": "test_client",
|
432
|
+
}
|
433
|
+
)
|
409
434
|
# Should handle all paths appropriately
|
410
435
|
assert isinstance(result, dict)
|
411
|
-
|
436
|
+
|
412
437
|
def test_standalone_header_processing(self):
|
413
438
|
"""Test header processing in standalone application."""
|
414
|
-
example =
|
415
|
-
|
439
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
440
|
+
|
416
441
|
# Test with different API key combinations
|
417
442
|
api_key_combinations = [
|
418
443
|
"admin_key_123",
|
419
444
|
"user_key_456",
|
420
445
|
"readonly_key_789",
|
421
|
-
"admin_key_123"
|
446
|
+
"admin_key_123",
|
422
447
|
]
|
423
|
-
|
448
|
+
|
424
449
|
for api_key in api_key_combinations:
|
425
|
-
result = example.process_request(
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
450
|
+
result = example.process_request(
|
451
|
+
{
|
452
|
+
"credentials": {"api_key": api_key},
|
453
|
+
"action": "read",
|
454
|
+
"resource": "/api/v1/users/me",
|
455
|
+
"identifier": "test_client",
|
456
|
+
}
|
457
|
+
)
|
458
|
+
assert isinstance(result, dict)
|
459
|
+
assert "success" in result
|
460
|
+
|
433
461
|
def test_standalone_body_processing(self):
|
434
462
|
"""Test body processing in standalone application."""
|
435
|
-
example =
|
463
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
436
464
|
# Test with different data types
|
437
465
|
data_combinations = [
|
438
466
|
None,
|
@@ -440,54 +468,57 @@ class TestStandaloneIntegration:
|
|
440
468
|
{"complex": {"nested": "data", "array": [1, 2, 3]}},
|
441
469
|
{"empty": {}},
|
442
470
|
{"string": "simple string"},
|
443
|
-
{"number": 42, "boolean": True}
|
471
|
+
{"number": 42, "boolean": True},
|
444
472
|
]
|
445
|
-
|
473
|
+
|
446
474
|
for data in data_combinations:
|
447
|
-
result = example.process_request(
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
475
|
+
result = example.process_request(
|
476
|
+
{
|
477
|
+
"credentials": {"api_key": "admin_key_123"},
|
478
|
+
"action": "write",
|
479
|
+
"resource": "/api/v1/data",
|
480
|
+
"data": data,
|
481
|
+
"identifier": "test_client",
|
482
|
+
}
|
483
|
+
)
|
454
484
|
assert isinstance(result, dict)
|
455
|
-
|
485
|
+
|
456
486
|
def test_standalone_security_manager_integration(self):
|
457
487
|
"""Test security manager integration in standalone application."""
|
458
|
-
example =
|
459
|
-
|
488
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
489
|
+
|
460
490
|
# Test that security manager methods are accessible
|
461
|
-
assert hasattr(example.security_manager,
|
462
|
-
assert hasattr(example.security_manager,
|
463
|
-
assert hasattr(example.security_manager,
|
464
|
-
|
491
|
+
assert hasattr(example.security_manager, "authenticate_user")
|
492
|
+
assert hasattr(example.security_manager, "check_permissions")
|
493
|
+
assert hasattr(example.security_manager, "check_rate_limit")
|
494
|
+
|
465
495
|
# Test direct security manager usage
|
466
|
-
auth_result = example.security_manager.authenticate_user(
|
467
|
-
"method": "api_key",
|
468
|
-
|
469
|
-
})
|
496
|
+
auth_result = example.security_manager.authenticate_user(
|
497
|
+
{"method": "api_key", "api_key": "admin_key_123"}
|
498
|
+
)
|
470
499
|
assert auth_result.is_valid
|
471
|
-
|
500
|
+
|
472
501
|
# Test permission checking
|
473
502
|
perm_result = example.security_manager.check_permissions(
|
474
|
-
["admin"], ["read", "write"]
|
503
|
+
["admin"], ["read:own", "write:own"]
|
475
504
|
)
|
476
505
|
assert perm_result.is_valid
|
477
|
-
|
506
|
+
|
478
507
|
def test_standalone_logging_integration(self):
|
479
508
|
"""Test logging integration in standalone application."""
|
480
|
-
example =
|
481
|
-
|
509
|
+
example = StandaloneSecurityExample(config_path=self.config_path)
|
510
|
+
|
482
511
|
# Test that logging is configured
|
483
|
-
assert hasattr(example.security_manager,
|
512
|
+
assert hasattr(example.security_manager, "logger")
|
484
513
|
assert example.security_manager.logger is not None
|
485
|
-
|
514
|
+
|
486
515
|
# Test that requests are processed (logging happens internally)
|
487
|
-
result = example.process_request(
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
516
|
+
result = example.process_request(
|
517
|
+
{
|
518
|
+
"credentials": {"api_key": "admin_key_123"},
|
519
|
+
"action": "read",
|
520
|
+
"resource": "/api/v1/users/me",
|
521
|
+
"identifier": "test_client",
|
522
|
+
}
|
523
|
+
)
|
524
|
+
assert result["success"] is True
|