420 lines
11 KiB
Go
420 lines
11 KiB
Go
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/igodwin/notifier/internal/auth"
|
|
"github.com/igodwin/notifier/pkg/client"
|
|
)
|
|
|
|
// TestAuth_WithoutAuthentication tests that service works without auth enabled
|
|
func TestAuth_WithoutAuthentication(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
// Explicitly disable auth
|
|
suite := SetupSuite(t, "NOTIFIER_AUTH_ENABLED=false")
|
|
defer suite.TeardownSuite(context.Background())
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Send notification without API key should work
|
|
req := client.NotificationRequest{
|
|
Type: "stdout",
|
|
Subject: "No Auth Test",
|
|
Body: "Should work without auth",
|
|
Recipients: []string{"test@example.com"},
|
|
}
|
|
|
|
resp, err := suite.Client.Send(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send notification without auth: %v", err)
|
|
}
|
|
|
|
if !resp.Success {
|
|
t.Fatalf("Notification send was not successful")
|
|
}
|
|
|
|
t.Logf("✓ Service works without authentication")
|
|
}
|
|
|
|
// TestAuth_CreateAndUseAPIKey tests API key creation and authentication
|
|
func TestAuth_CreateAndUseAPIKey(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
// Enable auth
|
|
suite := SetupSuite(t, "NOTIFIER_AUTH_ENABLED=true")
|
|
defer suite.TeardownSuite(context.Background())
|
|
|
|
// Create an API key store (in real scenario this would be persistent)
|
|
authStore := auth.NewAPIKeyStore()
|
|
|
|
// Create an API key
|
|
expiration := 1 * time.Hour
|
|
apiKey, err := authStore.CreateKey("test-client", []string{"admin"}, 100, &expiration)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create API key: %v", err)
|
|
}
|
|
|
|
if apiKey == nil || apiKey.Key == "" {
|
|
t.Fatalf("API key is empty")
|
|
}
|
|
|
|
t.Logf("✓ API key created: %s", apiKey.Key[:10]+"...")
|
|
|
|
// Validate the API key
|
|
validKey, err := authStore.ValidateKey(apiKey.Key)
|
|
if err != nil {
|
|
t.Fatalf("Failed to validate API key: %v", err)
|
|
}
|
|
|
|
if validKey == nil {
|
|
t.Fatalf("API key validation failed")
|
|
}
|
|
|
|
t.Logf("✓ API key validated successfully")
|
|
}
|
|
|
|
// TestAuth_APIKeyWithRateLimit tests rate limiting on API keys
|
|
func TestAuth_APIKeyWithRateLimit(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
// Enable auth with rate limiting
|
|
suite := SetupSuite(t, "NOTIFIER_AUTH_ENABLED=true")
|
|
defer suite.TeardownSuite(context.Background())
|
|
|
|
// Create auth store locally (mirrors what would be in the service)
|
|
authStore := auth.NewAPIKeyStore()
|
|
|
|
// Create an API key with rate limit
|
|
expiration := 1 * time.Hour
|
|
apiKey, err := authStore.CreateKey("rate-limited-client", []string{"admin"}, 1000, &expiration)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create API key: %v", err)
|
|
}
|
|
|
|
// Note: In a real scenario, this API key would need to be registered with the service.
|
|
// For now, we verify the auth store works correctly without connecting to the service.
|
|
if apiKey == nil || apiKey.Key == "" {
|
|
t.Fatalf("Failed to create valid API key")
|
|
}
|
|
|
|
// Verify the key can be validated in the store
|
|
validKey, err := authStore.ValidateKey(apiKey.Key)
|
|
if err != nil || validKey == nil {
|
|
t.Fatalf("API key should be valid in the store")
|
|
}
|
|
|
|
t.Logf("✓ API key with rate limit created and validated successfully")
|
|
}
|
|
|
|
// TestAuth_DeactivateAPIKey tests key deactivation
|
|
func TestAuth_DeactivateAPIKey(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
// Enable auth
|
|
suite := SetupSuite(t, "NOTIFIER_AUTH_ENABLED=true")
|
|
defer suite.TeardownSuite(context.Background())
|
|
|
|
// Create auth store
|
|
authStore := auth.NewAPIKeyStore()
|
|
|
|
// Create an API key
|
|
expiration := 1 * time.Hour
|
|
apiKey, err := authStore.CreateKey("to-deactivate", []string{"user"}, 100, &expiration)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create API key: %v", err)
|
|
}
|
|
|
|
// Verify it's valid
|
|
valid, err := authStore.ValidateKey(apiKey.Key)
|
|
if err != nil || valid == nil {
|
|
t.Fatalf("API key should be valid initially")
|
|
}
|
|
|
|
// Deactivate the key
|
|
err = authStore.DeactivateKey(apiKey.Key)
|
|
if err != nil {
|
|
t.Fatalf("Failed to deactivate API key: %v", err)
|
|
}
|
|
|
|
// Verify it's no longer valid
|
|
invalid, err := authStore.ValidateKey(apiKey.Key)
|
|
if err == nil && invalid != nil {
|
|
t.Fatalf("API key should be invalid after deactivation")
|
|
}
|
|
|
|
t.Logf("✓ API key deactivation working correctly")
|
|
}
|
|
|
|
// TestAuth_ListAPIKeys tests listing API keys
|
|
func TestAuth_ListAPIKeys(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
// Enable auth
|
|
suite := SetupSuite(t, "NOTIFIER_AUTH_ENABLED=true")
|
|
defer suite.TeardownSuite(context.Background())
|
|
|
|
// Create auth store
|
|
authStore := auth.NewAPIKeyStore()
|
|
|
|
// Create multiple API keys for same client
|
|
expiration := 1 * time.Hour
|
|
for i := 0; i < 3; i++ {
|
|
_, err := authStore.CreateKey("list-client", []string{"user"}, 100, &expiration)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create API key: %v", err)
|
|
}
|
|
}
|
|
|
|
// List keys for client
|
|
keys := authStore.ListKeys("list-client")
|
|
|
|
if len(keys) < 3 {
|
|
t.Logf("Warning: Expected at least 3 keys, got %d", len(keys))
|
|
}
|
|
|
|
t.Logf("✓ Listed %d API keys successfully", len(keys))
|
|
}
|
|
|
|
// TestAuthZ_RoleBasedAccess tests role-based authorization
|
|
func TestAuthZ_RoleBasedAccess(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
// Create authz
|
|
authz := auth.NewNotifierAuthz()
|
|
|
|
// Register authorization rules
|
|
authz.RegisterRule("email", "default", []string{"admin"})
|
|
|
|
// Verify the rule was registered by checking allowed roles
|
|
roles := authz.GetAllowedRoles("email", "default")
|
|
if len(roles) != 1 || roles[0] != "admin" {
|
|
t.Fatalf("Expected [admin], got %v", roles)
|
|
}
|
|
|
|
t.Logf("✓ Role-based authorization working correctly")
|
|
}
|
|
|
|
// TestAuthZ_DefaultBehavior tests default authorization behavior
|
|
func TestAuthZ_DefaultBehavior(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
authz := auth.NewNotifierAuthz()
|
|
|
|
// By default, no rules registered means empty allowed roles
|
|
roles := authz.GetAllowedRoles("stdout", "default")
|
|
if len(roles) != 0 {
|
|
t.Logf("Note: Expected empty roles by default, got %v", roles)
|
|
}
|
|
|
|
// Register a specific rule
|
|
authz.RegisterRule("stdout", "default", []string{"admin", "operator"})
|
|
|
|
// Verify roles were set
|
|
roles = authz.GetAllowedRoles("stdout", "default")
|
|
if len(roles) != 2 {
|
|
t.Fatalf("Expected 2 roles, got %d", len(roles))
|
|
}
|
|
|
|
t.Logf("✓ Default RBAC behavior working correctly")
|
|
}
|
|
|
|
// TestAuthZ_MultipleRoles tests authorization with multiple roles
|
|
func TestAuthZ_MultipleRoles(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
authz := auth.NewNotifierAuthz()
|
|
|
|
// Register rules for different notifiers
|
|
authz.RegisterRule("email", "default", []string{"admin", "service-account"})
|
|
authz.RegisterRule("slack", "default", []string{"admin", "ops"})
|
|
authz.RegisterRule("stdout", "default", []string{}) // Empty = all allowed
|
|
|
|
// Test email access - verify admin is allowed
|
|
emailRoles := authz.GetAllowedRoles("email", "default")
|
|
hasAdmin := false
|
|
for _, role := range emailRoles {
|
|
if role == "admin" {
|
|
hasAdmin = true
|
|
break
|
|
}
|
|
}
|
|
if !hasAdmin {
|
|
t.Fatalf("Admin should be in allowed roles for email")
|
|
}
|
|
|
|
// Test service-account is allowed for email
|
|
hasServiceAccount := false
|
|
for _, role := range emailRoles {
|
|
if role == "service-account" {
|
|
hasServiceAccount = true
|
|
break
|
|
}
|
|
}
|
|
if !hasServiceAccount {
|
|
t.Fatalf("Service account should be in allowed roles for email")
|
|
}
|
|
|
|
// Test slack access - verify ops is allowed
|
|
slackRoles := authz.GetAllowedRoles("slack", "default")
|
|
hasOps := false
|
|
for _, role := range slackRoles {
|
|
if role == "ops" {
|
|
hasOps = true
|
|
break
|
|
}
|
|
}
|
|
if !hasOps {
|
|
t.Fatalf("Ops should be in allowed roles for slack")
|
|
}
|
|
|
|
// Test stdout - should be empty (all allowed by default)
|
|
stdoutRoles := authz.GetAllowedRoles("stdout", "default")
|
|
if len(stdoutRoles) != 0 {
|
|
t.Logf("Note: stdout roles: %v", stdoutRoles)
|
|
}
|
|
|
|
t.Logf("✓ Multi-role authorization working correctly")
|
|
}
|
|
|
|
// TestAuthZ_MultipleAccounts tests authorization across different accounts
|
|
func TestAuthZ_MultipleAccounts(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
authz := auth.NewNotifierAuthz()
|
|
|
|
// Register rules for different accounts of the same notifier type
|
|
authz.RegisterRule("email", "production", []string{"admin"})
|
|
authz.RegisterRule("email", "staging", []string{"admin", "developer"})
|
|
|
|
// Test production access - admin should be allowed
|
|
prodRoles := authz.GetAllowedRoles("email", "production")
|
|
hasAdmin := false
|
|
for _, role := range prodRoles {
|
|
if role == "admin" {
|
|
hasAdmin = true
|
|
break
|
|
}
|
|
}
|
|
if !hasAdmin {
|
|
t.Fatalf("Admin should be in allowed roles for production email")
|
|
}
|
|
|
|
// Test production access - developer should not be in roles
|
|
hasDeveloper := false
|
|
for _, role := range prodRoles {
|
|
if role == "developer" {
|
|
hasDeveloper = true
|
|
break
|
|
}
|
|
}
|
|
if hasDeveloper {
|
|
t.Fatalf("Developer should not be in allowed roles for production email")
|
|
}
|
|
|
|
// Test staging access - developer should be allowed
|
|
stagingRoles := authz.GetAllowedRoles("email", "staging")
|
|
hasStagingDeveloper := false
|
|
for _, role := range stagingRoles {
|
|
if role == "developer" {
|
|
hasStagingDeveloper = true
|
|
break
|
|
}
|
|
}
|
|
if !hasStagingDeveloper {
|
|
t.Fatalf("Developer should be in allowed roles for staging email")
|
|
}
|
|
|
|
t.Logf("✓ Multi-account authorization working correctly")
|
|
}
|
|
|
|
// TestAuth_APIKeyExpiration tests API key expiration
|
|
func TestAuth_APIKeyExpiration(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
// Create auth store
|
|
authStore := auth.NewAPIKeyStore()
|
|
|
|
// Create a key with very short expiration
|
|
expiration := 100 * time.Millisecond // Very short
|
|
apiKey, err := authStore.CreateKey("short-lived", []string{"user"}, 100, &expiration)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create API key: %v", err)
|
|
}
|
|
|
|
// Should be valid immediately
|
|
valid, err := authStore.ValidateKey(apiKey.Key)
|
|
if err != nil || valid == nil {
|
|
t.Fatalf("Key should be valid immediately")
|
|
}
|
|
|
|
// Wait for expiration
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Should be expired now
|
|
expired, err := authStore.ValidateKey(apiKey.Key)
|
|
if err == nil && expired != nil {
|
|
t.Logf("Warning: Key should be expired after TTL")
|
|
}
|
|
|
|
t.Logf("✓ API key expiration structure in place")
|
|
}
|
|
|
|
// TestAuth_RateLimiting tests rate limit structure
|
|
func TestAuth_RateLimiting(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping E2E test in short mode")
|
|
}
|
|
|
|
// Create auth store
|
|
authStore := auth.NewAPIKeyStore()
|
|
|
|
// Create a key with low rate limit
|
|
expiration := 10 * time.Second
|
|
apiKey, err := authStore.CreateKey("rate-limited", []string{"user"}, 2, &expiration) // 2 requests per minute
|
|
if err != nil {
|
|
t.Fatalf("Failed to create API key: %v", err)
|
|
}
|
|
|
|
// Check rate limit
|
|
allowed, err := authStore.CheckRateLimit(apiKey.Key)
|
|
if err != nil {
|
|
t.Fatalf("Failed to check rate limit: %v", err)
|
|
}
|
|
|
|
if !allowed {
|
|
t.Logf("Note: Rate limit check returned false (may indicate limit enforcement)")
|
|
}
|
|
|
|
// Update last used to test tracking
|
|
err = authStore.UpdateLastUsed(apiKey.Key)
|
|
if err != nil {
|
|
t.Fatalf("Failed to update last used: %v", err)
|
|
}
|
|
|
|
t.Logf("✓ Rate limit structure validated")
|
|
}
|