|
|
|
|
@@ -5,8 +5,8 @@ import pytest
|
|
|
|
|
from aegis_gitea_mcp.auth import (
|
|
|
|
|
APIKeyValidator,
|
|
|
|
|
generate_api_key,
|
|
|
|
|
hash_api_key,
|
|
|
|
|
get_validator,
|
|
|
|
|
hash_api_key,
|
|
|
|
|
reset_validator,
|
|
|
|
|
)
|
|
|
|
|
from aegis_gitea_mcp.config import reset_settings
|
|
|
|
|
@@ -40,7 +40,7 @@ def validator(mock_env_with_key):
|
|
|
|
|
def test_generate_api_key():
|
|
|
|
|
"""Test API key generation."""
|
|
|
|
|
key = generate_api_key(length=64)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert len(key) == 64
|
|
|
|
|
assert all(c in "0123456789abcdef" for c in key)
|
|
|
|
|
|
|
|
|
|
@@ -48,7 +48,7 @@ def test_generate_api_key():
|
|
|
|
|
def test_generate_api_key_custom_length():
|
|
|
|
|
"""Test API key generation with custom length."""
|
|
|
|
|
key = generate_api_key(length=128)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert len(key) == 128
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -56,7 +56,7 @@ def test_hash_api_key():
|
|
|
|
|
"""Test API key hashing."""
|
|
|
|
|
key = "test-key-12345"
|
|
|
|
|
hashed = hash_api_key(key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert len(hashed) == 64 # SHA256 produces 64-char hex string
|
|
|
|
|
assert hashed == hash_api_key(key) # Deterministic
|
|
|
|
|
|
|
|
|
|
@@ -65,7 +65,7 @@ def test_validator_singleton():
|
|
|
|
|
"""Test that get_validator returns same instance."""
|
|
|
|
|
validator1 = get_validator()
|
|
|
|
|
validator2 = get_validator()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert validator1 is validator2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -73,10 +73,10 @@ def test_constant_time_compare(validator):
|
|
|
|
|
"""Test constant-time string comparison."""
|
|
|
|
|
# Same strings
|
|
|
|
|
assert validator._constant_time_compare("test", "test")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Different strings
|
|
|
|
|
assert not validator._constant_time_compare("test", "fail")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Different lengths
|
|
|
|
|
assert not validator._constant_time_compare("test", "testing")
|
|
|
|
|
|
|
|
|
|
@@ -86,19 +86,19 @@ def test_extract_bearer_token(validator):
|
|
|
|
|
# Valid bearer token
|
|
|
|
|
token = validator.extract_bearer_token("Bearer abc123")
|
|
|
|
|
assert token == "abc123"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# No header
|
|
|
|
|
token = validator.extract_bearer_token(None)
|
|
|
|
|
assert token is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Wrong format
|
|
|
|
|
token = validator.extract_bearer_token("abc123")
|
|
|
|
|
assert token is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Wrong scheme
|
|
|
|
|
token = validator.extract_bearer_token("Basic abc123")
|
|
|
|
|
assert token is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Too many parts
|
|
|
|
|
token = validator.extract_bearer_token("Bearer abc 123")
|
|
|
|
|
assert token is None
|
|
|
|
|
@@ -107,7 +107,7 @@ def test_extract_bearer_token(validator):
|
|
|
|
|
def test_validate_api_key_missing(validator):
|
|
|
|
|
"""Test validation with missing API key."""
|
|
|
|
|
is_valid, error = validator.validate_api_key(None, "127.0.0.1", "test-agent")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert not is_valid
|
|
|
|
|
assert "Authorization header missing" in error
|
|
|
|
|
|
|
|
|
|
@@ -115,7 +115,7 @@ def test_validate_api_key_missing(validator):
|
|
|
|
|
def test_validate_api_key_too_short(validator):
|
|
|
|
|
"""Test validation with too-short API key."""
|
|
|
|
|
is_valid, error = validator.validate_api_key("short", "127.0.0.1", "test-agent")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert not is_valid
|
|
|
|
|
assert "Invalid API key format" in error
|
|
|
|
|
|
|
|
|
|
@@ -123,7 +123,7 @@ def test_validate_api_key_too_short(validator):
|
|
|
|
|
def test_validate_api_key_invalid(validator, mock_env_with_key):
|
|
|
|
|
"""Test validation with invalid API key."""
|
|
|
|
|
is_valid, error = validator.validate_api_key("b" * 64, "127.0.0.1", "test-agent")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert not is_valid
|
|
|
|
|
assert "Invalid API key" in error
|
|
|
|
|
|
|
|
|
|
@@ -131,7 +131,7 @@ def test_validate_api_key_invalid(validator, mock_env_with_key):
|
|
|
|
|
def test_validate_api_key_valid(validator, mock_env_with_key):
|
|
|
|
|
"""Test validation with valid API key."""
|
|
|
|
|
is_valid, error = validator.validate_api_key("a" * 64, "127.0.0.1", "test-agent")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert is_valid
|
|
|
|
|
assert error is None
|
|
|
|
|
|
|
|
|
|
@@ -139,12 +139,12 @@ def test_validate_api_key_valid(validator, mock_env_with_key):
|
|
|
|
|
def test_rate_limiting(validator):
|
|
|
|
|
"""Test rate limiting after multiple failures."""
|
|
|
|
|
client_ip = "192.168.1.1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# First 5 attempts should be allowed
|
|
|
|
|
for i in range(5):
|
|
|
|
|
for _ in range(5):
|
|
|
|
|
is_valid, error = validator.validate_api_key("b" * 64, client_ip, "test-agent")
|
|
|
|
|
assert not is_valid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 6th attempt should be rate limited
|
|
|
|
|
is_valid, error = validator.validate_api_key("b" * 64, client_ip, "test-agent")
|
|
|
|
|
assert not is_valid
|
|
|
|
|
@@ -155,15 +155,15 @@ def test_rate_limiting_per_ip(validator):
|
|
|
|
|
"""Test that rate limiting is per IP address."""
|
|
|
|
|
ip1 = "192.168.1.1"
|
|
|
|
|
ip2 = "192.168.1.2"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Fail 5 times from IP1
|
|
|
|
|
for i in range(5):
|
|
|
|
|
for _ in range(5):
|
|
|
|
|
validator.validate_api_key("b" * 64, ip1, "test-agent")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# IP1 should be rate limited
|
|
|
|
|
is_valid, error = validator.validate_api_key("b" * 64, ip1, "test-agent")
|
|
|
|
|
assert "Too many failed" in error
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# IP2 should still work
|
|
|
|
|
is_valid, error = validator.validate_api_key("b" * 64, ip2, "test-agent")
|
|
|
|
|
assert "Invalid API key" in error # Wrong key, but not rate limited
|
|
|
|
|
@@ -175,9 +175,9 @@ def test_auth_disabled(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("GITEA_TOKEN", "test-token-12345")
|
|
|
|
|
monkeypatch.setenv("AUTH_ENABLED", "false")
|
|
|
|
|
monkeypatch.setenv("MCP_API_KEYS", "") # No keys needed when disabled
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
validator = APIKeyValidator()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Should allow access without key
|
|
|
|
|
is_valid, error = validator.validate_api_key(None, "127.0.0.1", "test-agent")
|
|
|
|
|
assert is_valid
|
|
|
|
|
@@ -190,19 +190,19 @@ def test_multiple_keys(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("GITEA_TOKEN", "test-token-12345")
|
|
|
|
|
monkeypatch.setenv("AUTH_ENABLED", "true")
|
|
|
|
|
monkeypatch.setenv("MCP_API_KEYS", f"{'a' * 64},{'b' * 64},{'c' * 64}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
validator = APIKeyValidator()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# All three keys should work
|
|
|
|
|
is_valid, _ = validator.validate_api_key("a" * 64, "127.0.0.1", "test-agent")
|
|
|
|
|
assert is_valid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
is_valid, _ = validator.validate_api_key("b" * 64, "127.0.0.1", "test-agent")
|
|
|
|
|
assert is_valid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
is_valid, _ = validator.validate_api_key("c" * 64, "127.0.0.1", "test-agent")
|
|
|
|
|
assert is_valid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Invalid key should fail
|
|
|
|
|
is_valid, _ = validator.validate_api_key("d" * 64, "127.0.0.1", "test-agent")
|
|
|
|
|
assert not is_valid
|
|
|
|
|
|