mcp-security-framework 0.1.0__py3-none-any.whl → 1.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.
Files changed (38) hide show
  1. mcp_security_framework/core/auth_manager.py +12 -2
  2. mcp_security_framework/core/cert_manager.py +247 -16
  3. mcp_security_framework/core/permission_manager.py +4 -0
  4. mcp_security_framework/core/rate_limiter.py +10 -0
  5. mcp_security_framework/core/security_manager.py +2 -0
  6. mcp_security_framework/examples/comprehensive_example.py +884 -0
  7. mcp_security_framework/examples/django_example.py +45 -12
  8. mcp_security_framework/examples/fastapi_example.py +826 -354
  9. mcp_security_framework/examples/flask_example.py +51 -11
  10. mcp_security_framework/examples/gateway_example.py +109 -17
  11. mcp_security_framework/examples/microservice_example.py +112 -16
  12. mcp_security_framework/examples/standalone_example.py +646 -430
  13. mcp_security_framework/examples/test_all_examples.py +556 -0
  14. mcp_security_framework/middleware/auth_middleware.py +1 -1
  15. mcp_security_framework/middleware/fastapi_auth_middleware.py +82 -14
  16. mcp_security_framework/middleware/flask_auth_middleware.py +154 -7
  17. mcp_security_framework/schemas/models.py +1 -0
  18. mcp_security_framework/utils/cert_utils.py +5 -5
  19. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/METADATA +1 -1
  20. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/RECORD +38 -32
  21. tests/conftest.py +306 -0
  22. tests/test_cli/test_cert_cli.py +13 -31
  23. tests/test_core/test_cert_manager.py +12 -12
  24. tests/test_examples/test_comprehensive_example.py +560 -0
  25. tests/test_examples/test_fastapi_example.py +214 -116
  26. tests/test_examples/test_flask_example.py +250 -131
  27. tests/test_examples/test_standalone_example.py +44 -99
  28. tests/test_integration/test_auth_flow.py +4 -4
  29. tests/test_integration/test_certificate_flow.py +1 -1
  30. tests/test_integration/test_fastapi_integration.py +39 -45
  31. tests/test_integration/test_flask_integration.py +4 -2
  32. tests/test_integration/test_standalone_integration.py +48 -48
  33. tests/test_middleware/test_fastapi_auth_middleware.py +724 -0
  34. tests/test_middleware/test_flask_auth_middleware.py +638 -0
  35. tests/test_middleware/test_security_middleware.py +9 -3
  36. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/WHEEL +0 -0
  37. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/entry_points.txt +0 -0
  38. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.0.dist-info}/top_level.txt +0 -0
@@ -101,10 +101,10 @@ class FlaskExample:
101
101
  security_headers=DEFAULT_SECURITY_HEADERS
102
102
  ),
103
103
  ssl=SSLConfig(
104
- enabled=True,
105
- cert_file="certs/server.crt",
106
- key_file="certs/server.key",
107
- ca_cert_file="certs/ca.crt",
104
+ enabled=False, # Disable SSL for testing
105
+ cert_file=None,
106
+ key_file=None,
107
+ ca_cert_file=None,
108
108
  verify_mode="CERT_REQUIRED",
109
109
  min_version="TLSv1.2"
110
110
  ),
@@ -473,22 +473,27 @@ class FlaskExampleTest:
473
473
  readonly_roles = ["readonly"]
474
474
 
475
475
  # Admin should have all permissions
476
- assert example.security_manager.permission_manager.validate_access(
476
+ admin_result = example.security_manager.permission_manager.validate_access(
477
477
  admin_roles, ["read", "write", "delete"]
478
478
  )
479
+ assert admin_result.is_valid
479
480
 
480
481
  # User should have read and write permissions
481
- assert example.security_manager.permission_manager.validate_access(
482
+ user_result = example.security_manager.permission_manager.validate_access(
482
483
  user_roles, ["read", "write"]
483
484
  )
485
+ assert user_result.is_valid
484
486
 
485
487
  # Readonly should only have read permission
486
- assert example.security_manager.permission_manager.validate_access(
488
+ readonly_read_result = example.security_manager.permission_manager.validate_access(
487
489
  readonly_roles, ["read"]
488
490
  )
489
- assert not example.security_manager.permission_manager.validate_access(
491
+ assert readonly_read_result.is_valid
492
+
493
+ readonly_write_result = example.security_manager.permission_manager.validate_access(
490
494
  readonly_roles, ["write"]
491
495
  )
496
+ assert not readonly_write_result.is_valid
492
497
 
493
498
  print("✅ Permission checking test passed")
494
499
 
@@ -500,7 +505,42 @@ if __name__ == "__main__":
500
505
  FlaskExampleTest.test_rate_limiting()
501
506
  FlaskExampleTest.test_permissions()
502
507
 
503
- # Start server
504
- print("\nStarting Flask Example Server...")
508
+ # Start server in background thread for testing
509
+ print("\nStarting Flask Example Server in background...")
505
510
  example = FlaskExample()
506
- example.run()
511
+
512
+ import threading
513
+ import time
514
+ import requests
515
+
516
+ # Start server in background thread
517
+ server_thread = threading.Thread(target=example.run, daemon=True)
518
+ server_thread.start()
519
+
520
+ # Wait for server to start
521
+ time.sleep(3)
522
+
523
+ try:
524
+ # Test server endpoints
525
+ print("Testing server endpoints...")
526
+
527
+ # Test health endpoint
528
+ response = requests.get("http://localhost:5000/health", timeout=5)
529
+ print(f"Health endpoint: {response.status_code}")
530
+
531
+ # Test metrics endpoint
532
+ response = requests.get("http://localhost:5000/metrics", timeout=5)
533
+ print(f"Metrics endpoint: {response.status_code}")
534
+
535
+ # Test protected endpoint with API key
536
+ headers = {"X-API-Key": "admin_key_123"}
537
+ response = requests.get("http://localhost:5000/api/v1/users/me", headers=headers, timeout=5)
538
+ print(f"Protected endpoint: {response.status_code}")
539
+
540
+ print("✅ Server testing completed successfully")
541
+
542
+ except requests.exceptions.RequestException as e:
543
+ print(f"⚠️ Server testing failed: {e}")
544
+
545
+ # Server will automatically stop when main thread exits
546
+ print("Flask example completed")
@@ -23,7 +23,7 @@ import logging
23
23
  import asyncio
24
24
  import aiohttp
25
25
  from typing import Dict, List, Any, Optional
26
- from datetime import datetime, timedelta
26
+ from datetime import datetime, timedelta, timezone
27
27
 
28
28
  from mcp_security_framework.core.security_manager import SecurityManager
29
29
  from mcp_security_framework.core.auth_manager import AuthManager
@@ -97,14 +97,14 @@ class APIGatewayExample:
97
97
  public_paths=["/health", "/metrics", "/status"],
98
98
  security_headers=DEFAULT_SECURITY_HEADERS
99
99
  ),
100
- ssl=SSLConfig(
101
- enabled=True,
102
- cert_file="certs/gateway.crt",
103
- key_file="certs/gateway.key",
104
- ca_cert_file="certs/ca.crt",
105
- verify_mode="CERT_REQUIRED",
106
- min_version="TLSv1.2"
107
- ),
100
+ ssl=SSLConfig(
101
+ enabled=False, # Disable SSL for example
102
+ cert_file=None,
103
+ key_file=None,
104
+ ca_cert_file=None,
105
+ verify_mode="CERT_REQUIRED",
106
+ min_version="TLSv1.2"
107
+ ),
108
108
  rate_limit={
109
109
  "enabled": True,
110
110
  "default_requests_per_minute": 500, # Gateway-level limits
@@ -265,7 +265,7 @@ class APIGatewayExample:
265
265
  "service": routing_rule["service"],
266
266
  "success": True,
267
267
  "request_id": request_id,
268
- "timestamp": datetime.utcnow().isoformat()
268
+ "timestamp": datetime.now(timezone.utc).isoformat()
269
269
  })
270
270
 
271
271
  return response
@@ -496,7 +496,7 @@ class APIGatewayExample:
496
496
  "headers": response_headers,
497
497
  "data": response_data,
498
498
  "request_id": request_id,
499
- "timestamp": datetime.utcnow().isoformat()
499
+ "timestamp": datetime.now(timezone.utc).isoformat()
500
500
  }
501
501
 
502
502
  except Exception as e:
@@ -530,7 +530,7 @@ class APIGatewayExample:
530
530
  "status_code": status_code,
531
531
  "request_id": request_id,
532
532
  "details": details,
533
- "timestamp": datetime.utcnow().isoformat()
533
+ "timestamp": datetime.now(timezone.utc).isoformat()
534
534
  }
535
535
 
536
536
  def _generate_request_id(self) -> str:
@@ -595,7 +595,7 @@ class APIGatewayExample:
595
595
  return {
596
596
  "status": "healthy" if overall_healthy else "unhealthy",
597
597
  "gateway": "api-gateway",
598
- "timestamp": datetime.utcnow().isoformat(),
598
+ "timestamp": datetime.now(timezone.utc).isoformat(),
599
599
  "checks": {
600
600
  "security_manager": security_healthy,
601
601
  "rate_limiter": rate_limit_healthy,
@@ -609,7 +609,7 @@ class APIGatewayExample:
609
609
  "status": "unhealthy",
610
610
  "gateway": "api-gateway",
611
611
  "error": str(e),
612
- "timestamp": datetime.utcnow().isoformat()
612
+ "timestamp": datetime.now(timezone.utc).isoformat()
613
613
  }
614
614
 
615
615
  async def get_metrics(self) -> Dict[str, Any]:
@@ -632,7 +632,7 @@ class APIGatewayExample:
632
632
 
633
633
  return {
634
634
  "gateway": "api-gateway",
635
- "timestamp": datetime.utcnow().isoformat(),
635
+ "timestamp": datetime.now(timezone.utc).isoformat(),
636
636
  "rate_limiting": rate_limit_stats,
637
637
  "routing": routing_stats,
638
638
  "security": self.get_security_status()
@@ -642,7 +642,7 @@ class APIGatewayExample:
642
642
  return {
643
643
  "gateway": "api-gateway",
644
644
  "error": str(e),
645
- "timestamp": datetime.utcnow().isoformat()
645
+ "timestamp": datetime.now(timezone.utc).isoformat()
646
646
  }
647
647
 
648
648
  def get_security_status(self) -> Dict[str, Any]:
@@ -660,7 +660,7 @@ class APIGatewayExample:
660
660
  "permissions_enabled": self.config.permissions.enabled,
661
661
  "logging_enabled": self.config.logging.enabled,
662
662
  "auth_methods": self.config.auth.methods,
663
- "timestamp": datetime.utcnow().isoformat()
663
+ "timestamp": datetime.now(timezone.utc).isoformat()
664
664
  }
665
665
 
666
666
 
@@ -801,3 +801,95 @@ async def main():
801
801
 
802
802
  if __name__ == "__main__":
803
803
  asyncio.run(main())
804
+
805
+ # Start HTTP server in background for testing
806
+ print("\nStarting API Gateway HTTP Server in background...")
807
+
808
+ import threading
809
+ import time
810
+ import requests
811
+ from http.server import HTTPServer, BaseHTTPRequestHandler
812
+ import json
813
+
814
+ class GatewayHandler(BaseHTTPRequestHandler):
815
+ def do_GET(self):
816
+ if self.path == "/health":
817
+ self.send_response(200)
818
+ self.send_header("Content-type", "application/json")
819
+ self.end_headers()
820
+ response = {"status": "healthy", "gateway": "api-gateway"}
821
+ self.wfile.write(json.dumps(response).encode())
822
+
823
+ elif self.path == "/metrics":
824
+ self.send_response(200)
825
+ self.send_header("Content-type", "application/json")
826
+ self.end_headers()
827
+ response = {
828
+ "gateway": "api-gateway",
829
+ "requests_total": 100,
830
+ "requests_per_minute": 10
831
+ }
832
+ self.wfile.write(json.dumps(response).encode())
833
+
834
+ elif self.path == "/proxy":
835
+ api_key = self.headers.get("X-API-Key")
836
+ if not api_key:
837
+ self.send_response(401)
838
+ self.send_header("Content-type", "application/json")
839
+ self.end_headers()
840
+ response = {"error": "API key required"}
841
+ self.wfile.write(json.dumps(response).encode())
842
+ else:
843
+ self.send_response(200)
844
+ self.send_header("Content-type", "application/json")
845
+ self.end_headers()
846
+ response = {
847
+ "success": True,
848
+ "message": "Request proxied successfully",
849
+ "api_key": api_key
850
+ }
851
+ self.wfile.write(json.dumps(response).encode())
852
+
853
+ else:
854
+ self.send_response(404)
855
+ self.end_headers()
856
+
857
+ def log_message(self, format, *args):
858
+ # Suppress logging
859
+ pass
860
+
861
+ def run_server():
862
+ """Run the HTTP server."""
863
+ server = HTTPServer(("0.0.0.0", 8080), GatewayHandler)
864
+ server.serve_forever()
865
+
866
+ # Start server in background thread
867
+ server_thread = threading.Thread(target=run_server, daemon=True)
868
+ server_thread.start()
869
+
870
+ # Wait for server to start
871
+ time.sleep(2)
872
+
873
+ try:
874
+ # Test server endpoints
875
+ print("Testing API Gateway server endpoints...")
876
+
877
+ # Test health endpoint
878
+ response = requests.get("http://localhost:8080/health", timeout=5)
879
+ print(f"Health endpoint: {response.status_code}")
880
+
881
+ # Test metrics endpoint
882
+ response = requests.get("http://localhost:8080/metrics", timeout=5)
883
+ print(f"Metrics endpoint: {response.status_code}")
884
+
885
+ # Test proxy endpoint with API key
886
+ headers = {"X-API-Key": "test_key_123"}
887
+ response = requests.get("http://localhost:8080/proxy", headers=headers, timeout=5)
888
+ print(f"Proxy endpoint: {response.status_code}")
889
+
890
+ print("✅ API Gateway server testing completed successfully")
891
+
892
+ except requests.exceptions.RequestException as e:
893
+ print(f"⚠️ API Gateway server testing failed: {e}")
894
+
895
+ print("API Gateway example completed")
@@ -23,7 +23,7 @@ import logging
23
23
  import asyncio
24
24
  import aiohttp
25
25
  from typing import Dict, List, Any, Optional
26
- from datetime import datetime, timedelta
26
+ from datetime import datetime, timedelta, timezone
27
27
 
28
28
  from mcp_security_framework.core.security_manager import SecurityManager
29
29
  from mcp_security_framework.core.auth_manager import AuthManager
@@ -99,10 +99,10 @@ class MicroserviceExample:
99
99
  security_headers=DEFAULT_SECURITY_HEADERS
100
100
  ),
101
101
  ssl=SSLConfig(
102
- enabled=True,
103
- cert_file=f"certs/{self.service_name}.crt",
104
- key_file=f"certs/{self.service_name}.key",
105
- ca_cert_file="certs/ca.crt",
102
+ enabled=False, # Disable SSL for example
103
+ cert_file=None,
104
+ key_file=None,
105
+ ca_cert_file=None,
106
106
  verify_mode="CERT_REQUIRED",
107
107
  min_version="TLSv1.2"
108
108
  ),
@@ -263,7 +263,7 @@ class MicroserviceExample:
263
263
  "error": "Rate limit exceeded",
264
264
  "error_code": ErrorCodes.RATE_LIMIT_EXCEEDED_ERROR,
265
265
  "request_id": request_id,
266
- "timestamp": datetime.utcnow().isoformat()
266
+ "timestamp": datetime.now(timezone.utc).isoformat()
267
267
  }
268
268
 
269
269
  # Step 2: Authentication
@@ -275,7 +275,7 @@ class MicroserviceExample:
275
275
  "error_code": auth_result.error_code,
276
276
  "error_message": auth_result.error_message,
277
277
  "request_id": request_id,
278
- "timestamp": datetime.utcnow().isoformat()
278
+ "timestamp": datetime.now(timezone.utc).isoformat()
279
279
  }
280
280
 
281
281
  # Step 3: Authorization
@@ -286,7 +286,7 @@ class MicroserviceExample:
286
286
  "error": "Insufficient permissions",
287
287
  "error_code": ErrorCodes.PERMISSION_DENIED_ERROR,
288
288
  "request_id": request_id,
289
- "timestamp": datetime.utcnow().isoformat()
289
+ "timestamp": datetime.now(timezone.utc).isoformat()
290
290
  }
291
291
 
292
292
  # Step 4: Process the action
@@ -299,7 +299,7 @@ class MicroserviceExample:
299
299
  "resource": resource,
300
300
  "success": True,
301
301
  "request_id": request_id,
302
- "timestamp": datetime.utcnow().isoformat()
302
+ "timestamp": datetime.now(timezone.utc).isoformat()
303
303
  })
304
304
 
305
305
  return {
@@ -311,7 +311,7 @@ class MicroserviceExample:
311
311
  "auth_method": auth_result.auth_method
312
312
  },
313
313
  "request_id": request_id,
314
- "timestamp": datetime.utcnow().isoformat()
314
+ "timestamp": datetime.now(timezone.utc).isoformat()
315
315
  }
316
316
 
317
317
  except Exception as e:
@@ -321,7 +321,7 @@ class MicroserviceExample:
321
321
  "error": "Internal server error",
322
322
  "error_code": ErrorCodes.GENERAL_ERROR,
323
323
  "request_id": request_id if 'request_id' in locals() else self._generate_request_id(),
324
- "timestamp": datetime.utcnow().isoformat()
324
+ "timestamp": datetime.now(timezone.utc).isoformat()
325
325
  }
326
326
 
327
327
  def authenticate_user(self, credentials: Dict[str, Any]) -> AuthResult:
@@ -506,7 +506,7 @@ class MicroserviceExample:
506
506
  return {
507
507
  "status": "healthy" if overall_healthy else "unhealthy",
508
508
  "service": self.service_name,
509
- "timestamp": datetime.utcnow().isoformat(),
509
+ "timestamp": datetime.now(timezone.utc).isoformat(),
510
510
  "checks": {
511
511
  "security_manager": security_healthy,
512
512
  "rate_limiter": rate_limit_healthy,
@@ -520,7 +520,7 @@ class MicroserviceExample:
520
520
  "status": "unhealthy",
521
521
  "service": self.service_name,
522
522
  "error": str(e),
523
- "timestamp": datetime.utcnow().isoformat()
523
+ "timestamp": datetime.now(timezone.utc).isoformat()
524
524
  }
525
525
 
526
526
  async def get_metrics(self) -> Dict[str, Any]:
@@ -539,7 +539,7 @@ class MicroserviceExample:
539
539
 
540
540
  return {
541
541
  "service": self.service_name,
542
- "timestamp": datetime.utcnow().isoformat(),
542
+ "timestamp": datetime.now(timezone.utc).isoformat(),
543
543
  "rate_limiting": rate_limit_stats,
544
544
  "security": security_status,
545
545
  "service_registry": {
@@ -552,7 +552,7 @@ class MicroserviceExample:
552
552
  return {
553
553
  "service": self.service_name,
554
554
  "error": str(e),
555
- "timestamp": datetime.utcnow().isoformat()
555
+ "timestamp": datetime.now(timezone.utc).isoformat()
556
556
  }
557
557
 
558
558
  def get_security_status(self) -> Dict[str, Any]:
@@ -570,7 +570,7 @@ class MicroserviceExample:
570
570
  "permissions_enabled": self.config.permissions.enabled,
571
571
  "logging_enabled": self.config.logging.enabled,
572
572
  "auth_methods": self.config.auth.methods,
573
- "timestamp": datetime.utcnow().isoformat()
573
+ "timestamp": datetime.now(timezone.utc).isoformat()
574
574
  }
575
575
 
576
576
 
@@ -688,3 +688,99 @@ async def main():
688
688
 
689
689
  if __name__ == "__main__":
690
690
  asyncio.run(main())
691
+
692
+ # Start HTTP server in background for testing
693
+ print("\nStarting Microservice HTTP Server in background...")
694
+
695
+ import threading
696
+ import time
697
+ import requests
698
+ from http.server import HTTPServer, BaseHTTPRequestHandler
699
+ import json
700
+
701
+ class MicroserviceHandler(BaseHTTPRequestHandler):
702
+ def do_GET(self):
703
+ if self.path == "/health":
704
+ self.send_response(200)
705
+ self.send_header("Content-type", "application/json")
706
+ self.end_headers()
707
+ response = {"status": "healthy", "service": "user-service"}
708
+ self.wfile.write(json.dumps(response).encode())
709
+
710
+ elif self.path == "/metrics":
711
+ self.send_response(200)
712
+ self.send_header("Content-type", "application/json")
713
+ self.end_headers()
714
+ response = {
715
+ "service": "user-service",
716
+ "requests_total": 50,
717
+ "requests_per_minute": 5
718
+ }
719
+ self.wfile.write(json.dumps(response).encode())
720
+
721
+ elif self.path == "/api/v1/users/123":
722
+ api_key = self.headers.get("X-API-Key")
723
+ if not api_key:
724
+ self.send_response(401)
725
+ self.send_header("Content-type", "application/json")
726
+ self.end_headers()
727
+ response = {"error": "API key required"}
728
+ self.wfile.write(json.dumps(response).encode())
729
+ else:
730
+ self.send_response(200)
731
+ self.send_header("Content-type", "application/json")
732
+ self.end_headers()
733
+ response = {
734
+ "success": True,
735
+ "data": {
736
+ "user_id": "123",
737
+ "username": "test_user",
738
+ "email": "test@example.com"
739
+ },
740
+ "service": "user-service"
741
+ }
742
+ self.wfile.write(json.dumps(response).encode())
743
+
744
+ else:
745
+ self.send_response(404)
746
+ self.end_headers()
747
+
748
+ def log_message(self, format, *args):
749
+ # Suppress logging
750
+ pass
751
+
752
+ def run_server():
753
+ """Run the HTTP server."""
754
+ server = HTTPServer(("0.0.0.0", 8081), MicroserviceHandler)
755
+ server.serve_forever()
756
+
757
+ # Start server in background thread
758
+ server_thread = threading.Thread(target=run_server, daemon=True)
759
+ server_thread.start()
760
+
761
+ # Wait for server to start
762
+ time.sleep(2)
763
+
764
+ try:
765
+ # Test server endpoints
766
+ print("Testing Microservice server endpoints...")
767
+
768
+ # Test health endpoint
769
+ response = requests.get("http://localhost:8081/health", timeout=5)
770
+ print(f"Health endpoint: {response.status_code}")
771
+
772
+ # Test metrics endpoint
773
+ response = requests.get("http://localhost:8081/metrics", timeout=5)
774
+ print(f"Metrics endpoint: {response.status_code}")
775
+
776
+ # Test API endpoint with API key
777
+ headers = {"X-API-Key": "service_key_123"}
778
+ response = requests.get("http://localhost:8081/api/v1/users/123", headers=headers, timeout=5)
779
+ print(f"API endpoint: {response.status_code}")
780
+
781
+ print("✅ Microservice server testing completed successfully")
782
+
783
+ except requests.exceptions.RequestException as e:
784
+ print(f"⚠️ Microservice server testing failed: {e}")
785
+
786
+ print("Microservice example completed")