mcp-security-framework 1.1.0__py3-none-any.whl → 1.1.2__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 +41 -22
- mcp_security_framework/core/cert_manager.py +210 -147
- mcp_security_framework/core/permission_manager.py +9 -9
- mcp_security_framework/core/rate_limiter.py +2 -2
- mcp_security_framework/core/security_manager.py +284 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +349 -279
- mcp_security_framework/examples/django_example.py +247 -206
- mcp_security_framework/examples/fastapi_example.py +315 -283
- mcp_security_framework/examples/flask_example.py +274 -203
- mcp_security_framework/examples/gateway_example.py +304 -237
- mcp_security_framework/examples/microservice_example.py +258 -189
- mcp_security_framework/examples/standalone_example.py +255 -230
- mcp_security_framework/examples/test_all_examples.py +151 -135
- 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 +119 -118
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +160 -147
- 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 +18 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/METADATA +4 -3
- mcp_security_framework-1.1.2.dist-info/RECORD +84 -0
- tests/conftest.py +63 -66
- tests/test_cli/test_cert_cli.py +184 -146
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +24 -10
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +190 -137
- tests/test_examples/test_fastapi_example.py +124 -101
- tests/test_examples/test_flask_example.py +124 -101
- tests/test_examples/test_standalone_example.py +73 -80
- tests/test_integration/test_auth_flow.py +213 -197
- tests/test_integration/test_certificate_flow.py +180 -149
- tests/test_integration/test_fastapi_integration.py +108 -111
- tests/test_integration/test_flask_integration.py +141 -140
- tests/test_integration/test_standalone_integration.py +290 -259
- tests/test_middleware/test_fastapi_auth_middleware.py +195 -174
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +260 -202
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +145 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-1.1.0.dist-info/RECORD +0 -82
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/WHEEL +0 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.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 StandaloneSecurityExample
|
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
|
@@ -39,352 +44,371 @@ class TestStandaloneIntegration:
|
|
39
44
|
"api_keys": {
|
40
45
|
"admin_key_123": {"username": "admin", "roles": ["admin", "user"]},
|
41
46
|
"user_key_456": {"username": "user", "roles": ["user"]},
|
42
|
-
"readonly_key_789": {"username": "readonly", "roles": ["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
|
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
|
75
|
-
"description": "Administrator role"
|
66
|
+
"permissions": ["read", "write", "delete", "admin", "*"],
|
67
|
+
"description": "Administrator role",
|
76
68
|
},
|
77
69
|
"user": {
|
78
|
-
"permissions": ["read
|
79
|
-
"description": "Regular user role"
|
70
|
+
"permissions": ["read", "write"],
|
71
|
+
"description": "Regular user role",
|
80
72
|
},
|
81
73
|
"readonly": {
|
82
|
-
"permissions": ["read
|
83
|
-
"description": "Read-only user role"
|
84
|
-
}
|
74
|
+
"permissions": ["read"],
|
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
102
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
111
|
-
|
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
115
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
124
|
-
|
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
|
-
|
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
|
+
)
|
142
138
|
assert result["success"] is True
|
143
139
|
assert result["auth_result"]["username"] == "admin"
|
144
|
-
|
140
|
+
|
145
141
|
# Test authenticated request with different user
|
146
|
-
result = example.process_request(
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
+
)
|
152
150
|
assert result["success"] is True
|
153
151
|
assert result["auth_result"]["username"] == "user"
|
154
|
-
|
152
|
+
|
155
153
|
def test_standalone_authorization_flow(self):
|
156
154
|
"""Test complete authorization flow in standalone application."""
|
157
155
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
158
|
-
|
156
|
+
|
159
157
|
# Test admin access to admin-only operation
|
160
|
-
result = example.process_request(
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
166
|
assert result["success"] is True
|
167
167
|
assert result["auth_result"]["username"] == "admin"
|
168
|
-
|
168
|
+
|
169
169
|
# Test regular user access to admin-only operation (should be denied)
|
170
|
-
result = example.process_request(
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
+
)
|
176
178
|
# Check that request is processed
|
177
179
|
assert isinstance(result, dict)
|
178
180
|
assert "success" in result
|
179
|
-
|
181
|
+
|
180
182
|
# Test readonly user access to write operation (should be denied)
|
181
|
-
result = example.process_request(
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
+
)
|
188
192
|
# Check that request is processed
|
189
193
|
assert isinstance(result, dict)
|
190
194
|
assert "success" in result
|
191
|
-
|
195
|
+
|
192
196
|
def test_standalone_rate_limiting(self):
|
193
197
|
"""Test rate limiting in standalone application."""
|
194
198
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
195
|
-
|
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
|
-
processed_requests = sum(
|
214
|
+
processed_requests = sum(
|
215
|
+
1 for result in results if result.get("success") is True
|
216
|
+
)
|
209
217
|
assert processed_requests > 0, "Some requests should be processed successfully"
|
210
|
-
|
218
|
+
|
211
219
|
def test_standalone_ssl_integration(self):
|
212
220
|
"""Test SSL/TLS integration in standalone application."""
|
213
221
|
# Create config with SSL enabled
|
214
222
|
ssl_config = self.test_config.copy()
|
215
|
-
ssl_config["ssl"] = {
|
216
|
-
|
217
|
-
}
|
218
|
-
|
223
|
+
ssl_config["ssl"] = {"enabled": False} # Disable SSL for testing
|
224
|
+
|
219
225
|
# Create temporary SSL config file
|
220
|
-
ssl_config_fd, ssl_config_path = tempfile.mkstemp(suffix=
|
221
|
-
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:
|
222
228
|
json.dump(ssl_config, f)
|
223
|
-
|
229
|
+
|
224
230
|
try:
|
225
231
|
# Mock SSL context creation to avoid file requirements
|
226
|
-
with patch(
|
232
|
+
with patch(
|
233
|
+
"mcp_security_framework.core.ssl_manager.SSLManager.create_server_context"
|
234
|
+
) as mock_ssl:
|
227
235
|
mock_ssl.return_value = MagicMock()
|
228
|
-
|
236
|
+
|
229
237
|
example = StandaloneSecurityExample(config_path=ssl_config_path)
|
230
|
-
|
238
|
+
|
231
239
|
# Test that SSL is configured
|
232
240
|
assert example.config.ssl.enabled is False # SSL disabled for testing
|
233
|
-
|
241
|
+
|
234
242
|
finally:
|
235
243
|
os.unlink(ssl_config_path)
|
236
|
-
|
244
|
+
|
237
245
|
def test_standalone_error_handling(self):
|
238
246
|
"""Test error handling in standalone application."""
|
239
247
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
240
|
-
|
248
|
+
|
241
249
|
# Test invalid API key
|
242
|
-
result = example.process_request(
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
+
)
|
248
258
|
assert "error" in result
|
249
259
|
assert "authentication" in result["error"].lower()
|
250
|
-
|
260
|
+
|
251
261
|
# Test malformed request
|
252
|
-
result = example.process_request(
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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
|
+
)
|
259
271
|
assert "error" in result
|
260
|
-
|
272
|
+
|
261
273
|
def test_standalone_data_operations(self):
|
262
274
|
"""Test data operations with security."""
|
263
275
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
264
|
-
|
276
|
+
|
265
277
|
# Create data
|
266
|
-
create_result = example.process_request(
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
+
)
|
273
287
|
assert create_result["success"] is True
|
274
288
|
# The data is returned in the result
|
275
289
|
assert create_result["data"] == {"name": "test_item", "value": "test_value"}
|
276
|
-
|
290
|
+
|
277
291
|
# Retrieve data (simulate retrieval since _execute_action doesn't generate IDs)
|
278
|
-
get_result = example.process_request(
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
+
)
|
284
300
|
assert get_result["success"] is True
|
285
301
|
# The result contains the request information
|
286
302
|
assert get_result["resource"] == "/api/v1/data/test_item"
|
287
|
-
|
303
|
+
|
288
304
|
def test_standalone_configuration_validation(self):
|
289
305
|
"""Test configuration validation in standalone application."""
|
290
306
|
# Test with invalid configuration
|
291
|
-
invalid_config = {
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
}
|
296
|
-
}
|
297
|
-
|
298
|
-
invalid_config_fd, invalid_config_path = tempfile.mkstemp(suffix='.json')
|
299
|
-
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:
|
300
311
|
json.dump(invalid_config, f)
|
301
|
-
|
312
|
+
|
302
313
|
try:
|
303
314
|
# Should raise validation error
|
304
315
|
with pytest.raises(Exception):
|
305
316
|
StandaloneSecurityExample(config_path=invalid_config_path)
|
306
317
|
finally:
|
307
318
|
os.unlink(invalid_config_path)
|
308
|
-
|
319
|
+
|
309
320
|
def test_standalone_performance_benchmark(self):
|
310
321
|
"""Test performance of standalone application."""
|
311
322
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
312
|
-
|
323
|
+
|
313
324
|
import time
|
314
|
-
|
325
|
+
|
315
326
|
# Benchmark simple request
|
316
327
|
start_time = time.time()
|
317
328
|
for _ in range(100):
|
318
|
-
result = example.process_request(
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
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
|
+
)
|
324
337
|
assert result["success"] is True
|
325
338
|
end_time = time.time()
|
326
|
-
|
339
|
+
|
327
340
|
avg_time = (end_time - start_time) / 100
|
328
|
-
assert
|
329
|
-
|
341
|
+
assert (
|
342
|
+
avg_time < 0.01
|
343
|
+
), f"Request processing too slow: {avg_time:.4f}s per request"
|
344
|
+
|
330
345
|
# Benchmark authenticated request
|
331
346
|
start_time = time.time()
|
332
347
|
for _ in range(50):
|
333
|
-
result = example.process_request(
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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
|
+
}
|
340
356
|
)
|
341
357
|
assert result["success"] is True
|
342
358
|
end_time = time.time()
|
343
|
-
|
359
|
+
|
344
360
|
avg_time = (end_time - start_time) / 50
|
345
361
|
assert avg_time < 0.02, f"Data operation too slow: {avg_time:.4f}s per request"
|
346
|
-
|
362
|
+
|
347
363
|
def test_standalone_method_handling(self):
|
348
364
|
"""Test different HTTP method handling."""
|
349
365
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
350
366
|
# Test GET method
|
351
|
-
result = example.process_request(
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
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
|
+
)
|
357
375
|
assert result["success"] is True
|
358
|
-
|
376
|
+
|
359
377
|
# Test POST method
|
360
|
-
result = example.process_request(
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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
|
+
)
|
367
387
|
assert result["success"] is True
|
368
|
-
|
388
|
+
|
369
389
|
# Test PUT method
|
370
|
-
result = example.process_request(
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
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
|
+
)
|
377
399
|
# Should handle PUT method appropriately
|
378
|
-
|
400
|
+
|
379
401
|
# Test DELETE method
|
380
|
-
result = example.process_request(
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
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
|
+
)
|
386
410
|
# Should handle DELETE method appropriately
|
387
|
-
|
411
|
+
|
388
412
|
def test_standalone_path_routing(self):
|
389
413
|
"""Test path-based routing in standalone application."""
|
390
414
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
@@ -395,41 +419,45 @@ class TestStandaloneIntegration:
|
|
395
419
|
"/api/v1/data",
|
396
420
|
"/api/v1/data/123",
|
397
421
|
"/health",
|
398
|
-
"/metrics"
|
422
|
+
"/metrics",
|
399
423
|
]
|
400
|
-
|
424
|
+
|
401
425
|
for path in paths:
|
402
|
-
result = example.process_request(
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
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
|
+
)
|
408
434
|
# Should handle all paths appropriately
|
409
435
|
assert isinstance(result, dict)
|
410
|
-
|
436
|
+
|
411
437
|
def test_standalone_header_processing(self):
|
412
438
|
"""Test header processing in standalone application."""
|
413
439
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
414
|
-
|
440
|
+
|
415
441
|
# Test with different API key combinations
|
416
442
|
api_key_combinations = [
|
417
443
|
"admin_key_123",
|
418
444
|
"user_key_456",
|
419
445
|
"readonly_key_789",
|
420
|
-
"admin_key_123"
|
446
|
+
"admin_key_123",
|
421
447
|
]
|
422
|
-
|
448
|
+
|
423
449
|
for api_key in api_key_combinations:
|
424
|
-
result = example.process_request(
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
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
|
+
)
|
430
458
|
assert isinstance(result, dict)
|
431
459
|
assert "success" in result
|
432
|
-
|
460
|
+
|
433
461
|
def test_standalone_body_processing(self):
|
434
462
|
"""Test body processing in standalone application."""
|
435
463
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
@@ -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
488
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
459
|
-
|
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
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
509
|
example = StandaloneSecurityExample(config_path=self.config_path)
|
481
|
-
|
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
|
-
|
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
|
+
)
|
493
524
|
assert result["success"] is True
|