This commit is contained in:
2025-08-26 19:15:37 -04:00
parent 7ca61eb712
commit 86900b0bd4
16 changed files with 2099 additions and 8 deletions

491
internal/handlers/test.go Normal file
View File

@ -0,0 +1,491 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// TestHandler handles test endpoints for development
type TestHandler struct {
logger *zap.Logger
}
// NewTestHandler creates a new test handler
func NewTestHandler(logger *zap.Logger) *TestHandler {
return &TestHandler{
logger: logger,
}
}
// SSOTestPage serves the SSO manual test page
func (h *TestHandler) SSOTestPage(c *gin.Context) {
h.logger.Debug("Serving SSO test page")
html := `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KMS SSO Manual Testing</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
.container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #333; border-bottom: 3px solid #007acc; padding-bottom: 10px; }
h2 { color: #007acc; margin-top: 30px; }
.test-section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 5px; background: #fafafa; }
.btn { display: inline-block; padding: 12px 20px; margin: 10px 5px; background: #007acc; color: white; text-decoration: none; border-radius: 5px; font-weight: bold; }
.btn:hover { background: #005a9e; }
.btn-secondary { background: #28a745; }
.btn-warning { background: #ffc107; color: #333; }
.status { padding: 10px; margin: 10px 0; border-radius: 3px; }
.status.success { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
.status.error { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
.status.info { background: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; }
.code { background: #f8f9fa; padding: 15px; border-radius: 5px; font-family: monospace; margin: 10px 0; overflow-x: auto; }
.endpoint { margin: 10px 0; }
.endpoint strong { color: #007acc; }
pre { background: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0; }
.card { padding: 20px; border: 1px solid #ddd; border-radius: 5px; background: white; }
.user-info { background: #e7f3ff; padding: 10px; border-radius: 5px; margin: 10px 0; }
button { background: #007acc; color: white; border: none; padding: 12px 20px; border-radius: 5px; cursor: pointer; font-weight: bold; margin: 5px; }
button:hover { background: #005a9e; }
.test-result { margin: 10px 0; padding: 10px; border-radius: 3px; }
</style>
</head>
<body>
<div class="container">
<h1>🔐 KMS SSO Manual Testing Suite</h1>
<p><strong>Served from KMS API</strong> - No CORS issues!</p>
<div class="status info">
<strong>Environment Status:</strong> Local Development
<div id="services-status">Loading service status...</div>
</div>
<div class="grid">
<div class="card">
<h2>🎯 OAuth2/OIDC Testing (Keycloak)</h2>
<div class="user-info">
<strong>Test Users:</strong><br>
• admin@example.com / admin123 (Full access)<br>
• test@example.com / test123 (Limited access)<br>
• limited@example.com / limited123 (Read-only)
</div>
<div class="endpoint">
<strong>Admin Console:</strong><br>
<a href="http://localhost:8090" target="_blank" class="btn">Open Keycloak Admin</a>
<small>Login: admin / admin</small>
</div>
<div class="endpoint">
<strong>OAuth2 Authorization Flow:</strong><br>
<a href="http://localhost:8090/realms/kms/protocol/openid-connect/auth?client_id=kms-api&response_type=code&redirect_uri=http://localhost:3000/callback&scope=openid+email+profile&state=test123" target="_blank" class="btn">Test OAuth2 Login</a>
</div>
<div class="endpoint">
<strong>Discovery Document:</strong><br>
<button onclick="testOIDCDiscovery()">Test OIDC Discovery</button>
<div id="oidc-result"></div>
</div>
</div>
<div class="card">
<h2>📝 SAML Testing (SimpleSAMLphp)</h2>
<div class="user-info">
<strong>Test Users:</strong><br>
• user1 / user1pass<br>
• user2 / user2pass
</div>
<div class="endpoint">
<strong>Admin Console:</strong><br>
<a href="http://localhost:8091/simplesaml" target="_blank" class="btn">Open SAML Admin</a>
<small>Login: admin / secret</small>
</div>
<div class="endpoint">
<strong>SAML Metadata:</strong><br>
<button onclick="testSAMLMetadata()">Test SAML Metadata</button>
<div id="saml-result"></div>
</div>
<div class="endpoint">
<strong>Test Authentication:</strong><br>
<a href="http://localhost:8091/simplesaml/module.php/core/authenticate.php?as=example-userpass" target="_blank" class="btn">Test SAML Login</a>
</div>
</div>
</div>
<div class="test-section">
<h2>🚀 KMS API Testing</h2>
<div class="endpoint">
<strong>Frontend Application:</strong><br>
<a href="http://localhost:3000" target="_blank" class="btn">Open KMS Frontend</a>
</div>
<div class="endpoint">
<strong>API Health Check:</strong><br>
<button onclick="testHealth()">Check API Health</button>
<div id="health-result"></div>
</div>
<div class="endpoint">
<strong>Test API with Different Users:</strong><br>
<button onclick="testAPI('admin@example.com')">Test as Admin</button>
<button onclick="testAPI('test@example.com')">Test as User</button>
<button onclick="testAPI('limited@example.com')">Test as Limited</button>
<div id="api-result"></div>
</div>
<div class="endpoint">
<strong>Create Test Application:</strong><br>
<button onclick="createTestApp()">Create Test App</button>
<div id="create-app-result"></div>
</div>
</div>
<div class="test-section">
<h2>🔍 Permission System Testing</h2>
<button onclick="testPermissions()">Test Permission System</button>
<div id="permission-result"></div>
</div>
<div class="test-section">
<h2>📊 Current Implementation Status</h2>
<div class="status success">
<strong>✅ Working:</strong><br>
• Keycloak OAuth2/OIDC provider with test realm<br>
• SimpleSAMLphp SAML IdP with test users<br>
• KMS API with header authentication<br>
• Hierarchical permission system (25+ permissions)<br>
• Application and token management<br>
• Database with proper permission structure
</div>
<div class="status error">
<strong>❌ Missing:</strong><br>
• OAuth2 callback handler in KMS API<br>
• SAML assertion processing in KMS API<br>
• Frontend SSO login integration<br>
• Automatic permission mapping from SSO claims
</div>
<div class="status info">
<strong> Next Steps:</strong><br>
• Complete OAuth2 callback implementation<br>
• Add SAML response handling<br>
• Map SSO user attributes to KMS permissions<br>
• Add SSO login buttons to frontend
</div>
</div>
<div class="test-section">
<h2>🛠️ Development Commands</h2>
<div class="code">
<pre># Start SSO services
podman-compose -f docker-compose.yml -f docker-compose.sso.yml up -d
# Run automated tests
./test/quick_sso_test.sh
# Check service logs
podman-compose logs keycloak
podman-compose logs saml-idp
podman-compose logs api-service
# Reset to header auth mode
podman-compose up -d</pre>
</div>
</div>
</div>
<script>
// Test OIDC Discovery
async function testOIDCDiscovery() {
const resultDiv = document.getElementById('oidc-result');
resultDiv.innerHTML = '<div style="color: blue;">Testing OIDC discovery...</div>';
try {
const response = await fetch('http://localhost:8090/realms/kms/.well-known/openid-configuration');
const data = await response.json();
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724;">
<strong>✅ OIDC Discovery Successful!</strong><br>
<strong>Authorization Endpoint:</strong> ${data.authorization_endpoint}<br>
<strong>Token Endpoint:</strong> ${data.token_endpoint}<br>
<strong>UserInfo Endpoint:</strong> ${data.userinfo_endpoint}<br>
<details style="margin-top: 10px;">
<summary>View Full Response</summary>
<pre style="max-height: 200px; overflow-y: auto;">${JSON.stringify(data, null, 2)}</pre>
</details>
</div>
` + "`" + `;
} catch (error) {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ OIDC Discovery Failed</strong><br>
${error.message}
</div>
` + "`" + `;
}
}
// Test SAML Metadata
async function testSAMLMetadata() {
const resultDiv = document.getElementById('saml-result');
resultDiv.innerHTML = '<div style="color: blue;">Testing SAML metadata...</div>';
try {
const response = await fetch('http://localhost:8091/simplesaml/saml2/idp/metadata.php');
const text = await response.text();
if (text.includes('EntityDescriptor')) {
const entityId = text.match(/entityID="([^"]*)"/)?.[1] || 'Not found';
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724;">
<strong>✅ SAML Metadata Accessible!</strong><br>
<strong>Entity ID:</strong> ${entityId}<br>
<details style="margin-top: 10px;">
<summary>View Metadata</summary>
<pre style="max-height: 200px; overflow-y: auto;">${text}</pre>
</details>
</div>
` + "`" + `;
} else {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ Invalid SAML Metadata</strong><br>
Response does not contain EntityDescriptor
</div>
` + "`" + `;
}
} catch (error) {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ SAML Metadata Test Failed</strong><br>
${error.message}
</div>
` + "`" + `;
}
}
// Test API Health
async function testHealth() {
const resultDiv = document.getElementById('health-result');
resultDiv.innerHTML = '<div style="color: blue;">Checking API health...</div>';
try {
const response = await fetch('/health');
const text = await response.text();
if (text === 'healthy') {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724;">
<strong>✅ API is Healthy!</strong>
</div>
` + "`" + `;
} else {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ API Health Check Failed</strong><br>
Response: ${text}
</div>
` + "`" + `;
}
} catch (error) {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ API Health Check Error</strong><br>
${error.message}
</div>
` + "`" + `;
}
}
// Test API with different users (simulating SSO result)
async function testAPI(userEmail) {
const resultDiv = document.getElementById('api-result');
resultDiv.innerHTML = ` + "`" + `<div style="color: blue;">Testing API as ${userEmail}...</div>` + "`" + `;
try {
const response = await fetch('/api/applications', {
headers: {
'X-User-Email': userEmail,
'Accept': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724;">
<strong>✅ API Test Successful (${userEmail})!</strong><br>
Found ${data.count} applications<br>
<details style="margin-top: 10px;">
<summary>View Response</summary>
<pre style="max-height: 200px; overflow-y: auto;">${JSON.stringify(data, null, 2)}</pre>
</details>
</div>
` + "`" + `;
} else {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ API Test Failed (${userEmail})</strong><br>
Status: ${response.status} ${response.statusText}
</div>
` + "`" + `;
}
} catch (error) {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ API Test Error (${userEmail})</strong><br>
${error.message}
</div>
` + "`" + `;
}
}
// Create test application
async function createTestApp() {
const resultDiv = document.getElementById('create-app-result');
resultDiv.innerHTML = '<div style="color: blue;">Creating test application...</div>';
const testAppData = {
app_id: ` + "`" + `sso-test-${Date.now()}` + "`" + `,
app_link: "https://test.example.com",
type: ["static"],
callback_url: "https://test.example.com/callback",
token_prefix: "TEST",
token_renewal_duration: 604800000000000,
max_token_duration: 2592000000000000,
owner: {
type: "individual",
name: "SSO Test App",
owner: "admin@example.com"
}
};
try {
const response = await fetch('/api/applications', {
method: 'POST',
headers: {
'X-User-Email': 'admin@example.com',
'Content-Type': 'application/json'
},
body: JSON.stringify(testAppData)
});
if (response.ok) {
const data = await response.json();
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724;">
<strong>✅ Application Created Successfully!</strong><br>
<strong>App ID:</strong> ${data.app_id}<br>
<strong>HMAC Key:</strong> ${data.hmac_key.substring(0, 20)}...<br>
<details style="margin-top: 10px;">
<summary>View Full Response</summary>
<pre style="max-height: 200px; overflow-y: auto;">${JSON.stringify(data, null, 2)}</pre>
</details>
</div>
` + "`" + `;
} else {
const errorText = await response.text();
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ Application Creation Failed</strong><br>
Status: ${response.status} ${response.statusText}<br>
${errorText}
</div>
` + "`" + `;
}
} catch (error) {
resultDiv.innerHTML = ` + "`" + `
<div class="test-result" style="background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;">
<strong>❌ Application Creation Error</strong><br>
${error.message}
</div>
` + "`" + `;
}
}
// Test permission system
async function testPermissions() {
const resultDiv = document.getElementById('permission-result');
resultDiv.innerHTML = '<div style="color: blue;">Testing permission system...</div>';
// Test different permission levels by trying to access different endpoints
const tests = [
{ user: 'admin@example.com', description: 'Admin (full access)' },
{ user: 'test@example.com', description: 'Test user (limited access)' },
{ user: 'limited@example.com', description: 'Limited user (read-only)' }
];
let results = '<div class="test-result" style="background: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460;"><strong>Permission Test Results:</strong><br><br>';
for (const test of tests) {
try {
const response = await fetch('/api/applications', {
headers: {
'X-User-Email': test.user,
'Accept': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
results += ` + "`" + `<span style="color: #28a745;">✅ ${test.description}: Can access applications (${data.count} found)</span><br>` + "`" + `;
} else {
results += ` + "`" + `<span style="color: #dc3545;">❌ ${test.description}: Access denied (${response.status})</span><br>` + "`" + `;
}
} catch (error) {
results += ` + "`" + `<span style="color: #dc3545;">❌ ${test.description}: Error - ${error.message}</span><br>` + "`" + `;
}
}
results += '</div>';
resultDiv.innerHTML = results;
}
// Check service status on load
window.addEventListener('load', async function() {
const statusDiv = document.getElementById('services-status');
statusDiv.innerHTML = '<br>Checking services...';
const services = [
{ name: 'KMS API', test: () => fetch('/health') },
{ name: 'Keycloak', test: () => fetch('http://localhost:8090') },
{ name: 'SAML IdP', test: () => fetch('http://localhost:8091/simplesaml') }
];
let statusHtml = '<br>';
for (const service of services) {
try {
const response = await service.test();
if (response.ok) {
statusHtml += ` + "`" + `<span style="color: green;">✅ ${service.name}: Online</span><br>` + "`" + `;
} else {
statusHtml += ` + "`" + `<span style="color: orange;">⚠️ ${service.name}: Response ${response.status}</span><br>` + "`" + `;
}
} catch (error) {
statusHtml += ` + "`" + `<span style="color: red;">❌ ${service.name}: Not accessible</span><br>` + "`" + `;
}
}
statusDiv.innerHTML = statusHtml;
});
</script>
</body>
</html>`
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html))
}