147 lines
5.4 KiB
Python
Executable file
147 lines
5.4 KiB
Python
Executable file
"""
|
|
Security Module Tests
|
|
Tests for HMAC authentication and signature generation
|
|
"""
|
|
import pytest
|
|
import time
|
|
import hmac
|
|
import hashlib
|
|
|
|
|
|
class TestSignatureGeneration:
|
|
"""Tests for HMAC signature generation"""
|
|
|
|
def test_generate_signature_get_request(self, test_settings):
|
|
"""Should generate valid signature for GET requests"""
|
|
from security import generate_signature
|
|
|
|
timestamp = str(int(time.time()))
|
|
path = "/api/catalog"
|
|
method = "GET"
|
|
|
|
signature = generate_signature(timestamp, path, method)
|
|
|
|
assert signature is not None
|
|
assert len(signature) == 64 # SHA256 hex digest length
|
|
|
|
def test_generate_signature_post_request(self, test_settings):
|
|
"""Should generate valid signature for POST requests with body"""
|
|
from security import generate_signature
|
|
|
|
timestamp = str(int(time.time()))
|
|
path = "/api/extract"
|
|
method = "POST"
|
|
body = b'{"url": "https://example.com/video"}'
|
|
|
|
signature = generate_signature(timestamp, path, method, body)
|
|
|
|
assert signature is not None
|
|
assert len(signature) == 64
|
|
|
|
def test_different_timestamps_produce_different_signatures(self, test_settings):
|
|
"""Different timestamps should produce different signatures"""
|
|
from security import generate_signature
|
|
|
|
sig1 = generate_signature("1000000000", "/api/test", "GET")
|
|
sig2 = generate_signature("1000000001", "/api/test", "GET")
|
|
|
|
assert sig1 != sig2
|
|
|
|
def test_different_paths_produce_different_signatures(self, test_settings):
|
|
"""Different paths should produce different signatures"""
|
|
from security import generate_signature
|
|
|
|
timestamp = str(int(time.time()))
|
|
|
|
sig1 = generate_signature(timestamp, "/api/path1", "GET")
|
|
sig2 = generate_signature(timestamp, "/api/path2", "GET")
|
|
|
|
assert sig1 != sig2
|
|
|
|
def test_different_methods_produce_different_signatures(self, test_settings):
|
|
"""Different HTTP methods should produce different signatures"""
|
|
from security import generate_signature
|
|
|
|
timestamp = str(int(time.time()))
|
|
path = "/api/test"
|
|
|
|
sig_get = generate_signature(timestamp, path, "GET")
|
|
sig_post = generate_signature(timestamp, path, "POST")
|
|
|
|
assert sig_get != sig_post
|
|
|
|
def test_body_affects_signature(self, test_settings):
|
|
"""Request body should affect the signature"""
|
|
from security import generate_signature
|
|
|
|
timestamp = str(int(time.time()))
|
|
path = "/api/test"
|
|
method = "POST"
|
|
|
|
sig_empty = generate_signature(timestamp, path, method, b"")
|
|
sig_with_body = generate_signature(timestamp, path, method, b'{"key": "value"}')
|
|
|
|
assert sig_empty != sig_with_body
|
|
|
|
|
|
class TestSignatureVerification:
|
|
"""Tests for signature verification logic"""
|
|
|
|
def test_valid_signature_passes(self, test_client, auth_headers):
|
|
"""Valid signature should pass authentication"""
|
|
headers = auth_headers("/api/health", "GET")
|
|
|
|
# Health doesn't require auth, but this tests the header generation
|
|
response = test_client.get("/api/health", headers=headers)
|
|
|
|
assert response.status_code == 200
|
|
|
|
def test_missing_headers_rejected(self, test_client):
|
|
"""Requests without auth headers should be rejected on protected endpoints"""
|
|
response = test_client.get("/api/admin/version")
|
|
|
|
assert response.status_code == 401
|
|
assert "Authentication headers missing" in response.json()["detail"]
|
|
|
|
def test_invalid_signature_rejected(self, test_client):
|
|
"""Invalid signature should be rejected"""
|
|
headers = {
|
|
"X-Timestamp": str(int(time.time())),
|
|
"X-Signature": "invalid_signature_here"
|
|
}
|
|
|
|
response = test_client.get("/api/admin/version", headers=headers)
|
|
|
|
assert response.status_code == 401
|
|
assert "Invalid signature" in response.json()["detail"]
|
|
|
|
def test_expired_timestamp_rejected(self, test_client, test_settings):
|
|
"""Expired timestamps should be rejected"""
|
|
from security import generate_signature
|
|
|
|
# Timestamp from 10 minutes ago (beyond 5 min window)
|
|
old_timestamp = str(int(time.time()) - 600)
|
|
path = "/api/admin/version"
|
|
signature = generate_signature(old_timestamp, path, "GET")
|
|
|
|
headers = {
|
|
"X-Timestamp": old_timestamp,
|
|
"X-Signature": signature
|
|
}
|
|
|
|
response = test_client.get(path, headers=headers)
|
|
|
|
assert response.status_code == 401
|
|
assert "expired" in response.json()["detail"].lower()
|
|
|
|
def test_invalid_timestamp_format_rejected(self, test_client):
|
|
"""Non-numeric timestamps should be rejected"""
|
|
headers = {
|
|
"X-Timestamp": "not-a-number",
|
|
"X-Signature": "some_signature"
|
|
}
|
|
|
|
response = test_client.get("/api/admin/version", headers=headers)
|
|
|
|
assert response.status_code == 401
|
|
assert "Invalid timestamp" in response.json()["detail"]
|