policy_integration_test.go raw

   1  package policy
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"os"
   7  	"path/filepath"
   8  	"testing"
   9  	"time"
  10  
  11  	"next.orly.dev/pkg/lol/chk"
  12  	"next.orly.dev/pkg/nostr/encoders/event"
  13  	"next.orly.dev/pkg/nostr/encoders/hex"
  14  	"next.orly.dev/pkg/nostr/encoders/kind"
  15  	"next.orly.dev/pkg/nostr/encoders/tag"
  16  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  17  )
  18  
  19  // TestPolicyIntegration runs the relay with policy enabled and tests event filtering
  20  func TestPolicyIntegration(t *testing.T) {
  21  	if testing.Short() {
  22  		t.Skip("skipping integration test")
  23  	}
  24  
  25  	// Generate test keys
  26  	allowedSigner := p8k.MustNew()
  27  	if err := allowedSigner.Generate(); chk.E(err) {
  28  		t.Fatalf("Failed to generate allowed signer: %v", err)
  29  	}
  30  	allowedPubkeyHex := hex.Enc(allowedSigner.Pub())
  31  
  32  	unauthorizedSigner := p8k.MustNew()
  33  	if err := unauthorizedSigner.Generate(); chk.E(err) {
  34  		t.Fatalf("Failed to generate unauthorized signer: %v", err)
  35  	}
  36  
  37  	// Create temporary directory for policy config
  38  	tempDir := t.TempDir()
  39  	configDir := filepath.Join(tempDir, "ORLY_TEST")
  40  	if err := os.MkdirAll(configDir, 0755); chk.E(err) {
  41  		t.Fatalf("Failed to create config directory: %v", err)
  42  	}
  43  
  44  	// Create policy JSON with generated keys
  45  	policyJSON := map[string]interface{}{
  46  		"kind": map[string]interface{}{
  47  			"whitelist": []int{4678, 10306, 30520, 30919},
  48  		},
  49  		"rules": map[string]interface{}{
  50  			"4678": map[string]interface{}{
  51  				"description": "Zenotp message events",
  52  				"script":      filepath.Join(configDir, "validate4678.js"), // Won't exist, should fall back to default
  53  				"privileged":  true,
  54  			},
  55  			"10306": map[string]interface{}{
  56  				"description": "End user whitelist changes",
  57  				"read_allow":  []string{allowedPubkeyHex},
  58  				"privileged":  true,
  59  			},
  60  			"30520": map[string]interface{}{
  61  				"description": "Zenotp events",
  62  				"write_allow": []string{allowedPubkeyHex},
  63  				"privileged":  true,
  64  			},
  65  			"30919": map[string]interface{}{
  66  				"description": "Zenotp events",
  67  				"write_allow": []string{allowedPubkeyHex},
  68  				"privileged":  true,
  69  			},
  70  		},
  71  	}
  72  
  73  	policyJSONBytes, err := json.MarshalIndent(policyJSON, "", "  ")
  74  	if err != nil {
  75  		t.Fatalf("Failed to marshal policy JSON: %v", err)
  76  	}
  77  
  78  	policyPath := filepath.Join(configDir, "policy.json")
  79  	if err := os.WriteFile(policyPath, policyJSONBytes, 0644); chk.E(err) {
  80  		t.Fatalf("Failed to write policy file: %v", err)
  81  	}
  82  
  83  	// Create events with proper signatures
  84  	// Event 1: Kind 30520 with allowed pubkey (should be allowed)
  85  	event30520Allowed := event.New()
  86  	event30520Allowed.CreatedAt = time.Now().Unix()
  87  	event30520Allowed.Kind = kind.K{K: 30520}.K
  88  	event30520Allowed.Content = []byte("test event 30520")
  89  	event30520Allowed.Tags = tag.NewS()
  90  	addPTag(event30520Allowed, allowedSigner.Pub()) // Add p tag for privileged check
  91  	if err := event30520Allowed.Sign(allowedSigner); chk.E(err) {
  92  		t.Fatalf("Failed to sign event30520Allowed: %v", err)
  93  	}
  94  
  95  	// Event 2: Kind 30520 with unauthorized pubkey (should be denied)
  96  	event30520Unauthorized := event.New()
  97  	event30520Unauthorized.CreatedAt = time.Now().Unix()
  98  	event30520Unauthorized.Kind = kind.K{K: 30520}.K
  99  	event30520Unauthorized.Content = []byte("test event 30520 unauthorized")
 100  	event30520Unauthorized.Tags = tag.NewS()
 101  	if err := event30520Unauthorized.Sign(unauthorizedSigner); chk.E(err) {
 102  		t.Fatalf("Failed to sign event30520Unauthorized: %v", err)
 103  	}
 104  
 105  	// Event 3: Kind 10306 with allowed pubkey (should be readable by allowed user)
 106  	event10306Allowed := event.New()
 107  	event10306Allowed.CreatedAt = time.Now().Unix()
 108  	event10306Allowed.Kind = kind.K{K: 10306}.K
 109  	event10306Allowed.Content = []byte("test event 10306")
 110  	event10306Allowed.Tags = tag.NewS()
 111  	addPTag(event10306Allowed, allowedSigner.Pub()) // Add p tag for privileged check
 112  	if err := event10306Allowed.Sign(allowedSigner); chk.E(err) {
 113  		t.Fatalf("Failed to sign event10306Allowed: %v", err)
 114  	}
 115  
 116  	// Event 4: Kind 4678 with allowed pubkey (script-based, should fall back to default)
 117  	event4678Allowed := event.New()
 118  	event4678Allowed.CreatedAt = time.Now().Unix()
 119  	event4678Allowed.Kind = kind.K{K: 4678}.K
 120  	event4678Allowed.Content = []byte("test event 4678")
 121  	event4678Allowed.Tags = tag.NewS()
 122  	addPTag(event4678Allowed, allowedSigner.Pub()) // Add p tag for privileged check
 123  	if err := event4678Allowed.Sign(allowedSigner); chk.E(err) {
 124  		t.Fatalf("Failed to sign event4678Allowed: %v", err)
 125  	}
 126  
 127  	// Test policy loading
 128  	policy, err := New(policyJSONBytes)
 129  	if err != nil {
 130  		t.Fatalf("Failed to create policy: %v", err)
 131  	}
 132  
 133  	// Verify policy loaded correctly
 134  	if len(policy.rules) != 4 {
 135  		t.Errorf("Expected 4 rules, got %d", len(policy.rules))
 136  	}
 137  
 138  	// Test policy checks directly
 139  	t.Run("policy checks", func(t *testing.T) {
 140  		// Test 1: Event 30520 with allowed pubkey should be allowed
 141  		allowed, err := policy.CheckPolicy("write", event30520Allowed, allowedSigner.Pub(), "127.0.0.1")
 142  		if err != nil {
 143  			t.Errorf("Unexpected error: %v", err)
 144  		}
 145  		if !allowed {
 146  			t.Error("Expected event30520Allowed to be allowed")
 147  		}
 148  
 149  		// Test 2: Event 30520 with unauthorized pubkey should be denied
 150  		allowed, err = policy.CheckPolicy("write", event30520Unauthorized, unauthorizedSigner.Pub(), "127.0.0.1")
 151  		if err != nil {
 152  			t.Errorf("Unexpected error: %v", err)
 153  		}
 154  		if allowed {
 155  			t.Error("Expected event30520Unauthorized to be denied")
 156  		}
 157  
 158  		// Test 3: Event 10306 should be readable by allowed user
 159  		allowed, err = policy.CheckPolicy("read", event10306Allowed, allowedSigner.Pub(), "127.0.0.1")
 160  		if err != nil {
 161  			t.Errorf("Unexpected error: %v", err)
 162  		}
 163  		if !allowed {
 164  			t.Error("Expected event10306Allowed to be readable by allowed user")
 165  		}
 166  
 167  		// Test 4: Event 10306 should NOT be readable by unauthorized user
 168  		allowed, err = policy.CheckPolicy("read", event10306Allowed, unauthorizedSigner.Pub(), "127.0.0.1")
 169  		if err != nil {
 170  			t.Errorf("Unexpected error: %v", err)
 171  		}
 172  		if allowed {
 173  			t.Error("Expected event10306Allowed to be denied for unauthorized user")
 174  		}
 175  
 176  		// Test 5: Event 10306 should NOT be readable without authentication
 177  		allowed, err = policy.CheckPolicy("read", event10306Allowed, nil, "127.0.0.1")
 178  		if err != nil {
 179  			t.Errorf("Unexpected error: %v", err)
 180  		}
 181  		if allowed {
 182  			t.Error("Expected event10306Allowed to be denied without authentication (privileged)")
 183  		}
 184  
 185  		// Test 6: Event 30520 should NOT be writable without authentication
 186  		allowed, err = policy.CheckPolicy("write", event30520Allowed, nil, "127.0.0.1")
 187  		if err != nil {
 188  			t.Errorf("Unexpected error: %v", err)
 189  		}
 190  		if allowed {
 191  			t.Error("Expected event30520Allowed to be denied without authentication (privileged)")
 192  		}
 193  
 194  		// Test 7: Event 4678 should fall back to default policy (allow) when script not running
 195  		allowed, err = policy.CheckPolicy("write", event4678Allowed, allowedSigner.Pub(), "127.0.0.1")
 196  		if err != nil {
 197  			t.Errorf("Unexpected error: %v", err)
 198  		}
 199  		if !allowed {
 200  			t.Error("Expected event4678Allowed to be allowed when script not running (falls back to default)")
 201  		}
 202  
 203  		// Test 8: Event 4678 write should be allowed without authentication (privileged doesn't affect write)
 204  		allowed, err = policy.CheckPolicy("write", event4678Allowed, nil, "127.0.0.1")
 205  		if err != nil {
 206  			t.Errorf("Unexpected error: %v", err)
 207  		}
 208  		if !allowed {
 209  			t.Error("Expected event4678Allowed to be allowed without authentication (privileged doesn't affect write operations)")
 210  		}
 211  	})
 212  
 213  	// Test with relay simulation (checking log output)
 214  	t.Run("relay simulation", func(t *testing.T) {
 215  		// Note: We can't easily capture log output in tests, so we just verify
 216  		// that policy checks work correctly
 217  
 218  		// Simulate policy checks that would happen in relay
 219  		// First, publish events (simulate write checks)
 220  		checks := []struct {
 221  			name           string
 222  			event          *event.E
 223  			loggedInPubkey []byte
 224  			access         string
 225  			shouldAllow    bool
 226  			shouldLog      string // Expected log message substring, empty means no specific log expected
 227  		}{
 228  			{
 229  				name:           "write 30520 with allowed pubkey",
 230  				event:          event30520Allowed,
 231  				loggedInPubkey: allowedSigner.Pub(),
 232  				access:         "write",
 233  				shouldAllow:    true,
 234  			},
 235  			{
 236  				name:           "write 30520 with unauthorized pubkey",
 237  				event:          event30520Unauthorized,
 238  				loggedInPubkey: unauthorizedSigner.Pub(),
 239  				access:         "write",
 240  				shouldAllow:    false,
 241  			},
 242  			{
 243  				name:           "read 10306 with allowed pubkey",
 244  				event:          event10306Allowed,
 245  				loggedInPubkey: allowedSigner.Pub(),
 246  				access:         "read",
 247  				shouldAllow:    true,
 248  			},
 249  			{
 250  				name:           "read 10306 with unauthorized pubkey",
 251  				event:          event10306Allowed,
 252  				loggedInPubkey: unauthorizedSigner.Pub(),
 253  				access:         "read",
 254  				shouldAllow:    false,
 255  			},
 256  			{
 257  				name:           "read 10306 without authentication",
 258  				event:          event10306Allowed,
 259  				loggedInPubkey: nil,
 260  				access:         "read",
 261  				shouldAllow:    false,
 262  			},
 263  			{
 264  				name:           "write 30520 without authentication",
 265  				event:          event30520Allowed,
 266  				loggedInPubkey: nil,
 267  				access:         "write",
 268  				shouldAllow:    false,
 269  			},
 270  			{
 271  				name:           "write 4678 with allowed pubkey",
 272  				event:          event4678Allowed,
 273  				loggedInPubkey: allowedSigner.Pub(),
 274  				access:         "write",
 275  				shouldAllow:    true,
 276  				shouldLog:      "", // Should not log "policy rule is inactive" if script is not configured
 277  			},
 278  		}
 279  
 280  		for _, check := range checks {
 281  			t.Run(check.name, func(t *testing.T) {
 282  				allowed, err := policy.CheckPolicy(check.access, check.event, check.loggedInPubkey, "127.0.0.1")
 283  				if err != nil {
 284  					t.Errorf("Unexpected error: %v", err)
 285  					return
 286  				}
 287  				if allowed != check.shouldAllow {
 288  					t.Errorf("Expected allowed=%v, got %v", check.shouldAllow, allowed)
 289  				}
 290  			})
 291  		}
 292  	})
 293  
 294  	// Test event IDs are regenerated correctly after signing
 295  	t.Run("event ID regeneration", func(t *testing.T) {
 296  		// Create a new event, sign it, then verify ID is correct
 297  		testEvent := event.New()
 298  		testEvent.CreatedAt = time.Now().Unix()
 299  		testEvent.Kind = kind.K{K: 30520}.K
 300  		testEvent.Content = []byte("test content")
 301  		testEvent.Tags = tag.NewS()
 302  
 303  		// Sign the event
 304  		if err := testEvent.Sign(allowedSigner); chk.E(err) {
 305  			t.Fatalf("Failed to sign test event: %v", err)
 306  		}
 307  
 308  		// Verify event ID is correct (should be SHA256 of serialized event)
 309  		if len(testEvent.ID) != 32 {
 310  			t.Errorf("Expected event ID to be 32 bytes, got %d", len(testEvent.ID))
 311  		}
 312  
 313  		// Verify signature is correct
 314  		if len(testEvent.Sig) != 64 {
 315  			t.Errorf("Expected event signature to be 64 bytes, got %d", len(testEvent.Sig))
 316  		}
 317  
 318  		// Verify signature validates using event's Verify method
 319  		valid, err := testEvent.Verify()
 320  		if err != nil {
 321  			t.Errorf("Failed to verify signature: %v", err)
 322  		}
 323  		if !valid {
 324  			t.Error("Event signature verification failed")
 325  		}
 326  	})
 327  
 328  	// Test WebSocket client simulation (for future integration)
 329  	t.Run("websocket client simulation", func(t *testing.T) {
 330  		// This test simulates what would happen if we connected via WebSocket
 331  		// For now, we'll just verify the events can be serialized correctly
 332  
 333  		events := []*event.E{
 334  			event30520Allowed,
 335  			event30520Unauthorized,
 336  			event10306Allowed,
 337  			event4678Allowed,
 338  		}
 339  
 340  		for i, ev := range events {
 341  			t.Run(fmt.Sprintf("event_%d", i), func(t *testing.T) {
 342  				// Serialize event
 343  				serialized := ev.Serialize()
 344  				if len(serialized) == 0 {
 345  					t.Error("Event serialization returned empty")
 346  				}
 347  
 348  				// Verify event can be parsed back (simplified check)
 349  				if len(ev.ID) != 32 {
 350  					t.Errorf("Event ID length incorrect: %d", len(ev.ID))
 351  				}
 352  				if len(ev.Pubkey) != 32 {
 353  					t.Errorf("Event pubkey length incorrect: %d", len(ev.Pubkey))
 354  				}
 355  				if len(ev.Sig) != 64 {
 356  					t.Errorf("Event signature length incorrect: %d", len(ev.Sig))
 357  				}
 358  			})
 359  		}
 360  	})
 361  }
 362  
 363  // TestPolicyWithRelay creates a comprehensive test that simulates relay behavior
 364  func TestPolicyWithRelay(t *testing.T) {
 365  	if testing.Short() {
 366  		t.Skip("skipping integration test")
 367  	}
 368  
 369  	// Generate keys
 370  	allowedSigner := p8k.MustNew()
 371  	if err := allowedSigner.Generate(); chk.E(err) {
 372  		t.Fatalf("Failed to generate allowed signer: %v", err)
 373  	}
 374  	allowedPubkeyHex := hex.Enc(allowedSigner.Pub())
 375  
 376  	unauthorizedSigner := p8k.MustNew()
 377  	if err := unauthorizedSigner.Generate(); chk.E(err) {
 378  		t.Fatalf("Failed to generate unauthorized signer: %v", err)
 379  	}
 380  
 381  	// Create policy JSON
 382  	policyJSON := map[string]interface{}{
 383  		"kind": map[string]interface{}{
 384  			"whitelist": []int{4678, 10306, 30520, 30919},
 385  		},
 386  		"rules": map[string]interface{}{
 387  			"10306": map[string]interface{}{
 388  				"description": "End user whitelist changes",
 389  				"read_allow":  []string{allowedPubkeyHex},
 390  				"privileged":  true,
 391  			},
 392  			"30520": map[string]interface{}{
 393  				"description": "Zenotp events",
 394  				"write_allow": []string{allowedPubkeyHex},
 395  				"privileged":  true,
 396  			},
 397  			"30919": map[string]interface{}{
 398  				"description": "Zenotp events",
 399  				"write_allow": []string{allowedPubkeyHex},
 400  				"privileged":  true,
 401  			},
 402  		},
 403  	}
 404  
 405  	policyJSONBytes, err := json.Marshal(policyJSON)
 406  	if err != nil {
 407  		t.Fatalf("Failed to marshal policy JSON: %v", err)
 408  	}
 409  
 410  	policy, err := New(policyJSONBytes)
 411  	if err != nil {
 412  		t.Fatalf("Failed to create policy: %v", err)
 413  	}
 414  
 415  	// Create test event (kind 30520) with allowed pubkey
 416  	testEvent := event.New()
 417  	testEvent.CreatedAt = time.Now().Unix()
 418  	testEvent.Kind = kind.K{K: 30520}.K
 419  	testEvent.Content = []byte("test content")
 420  	testEvent.Tags = tag.NewS()
 421  	addPTag(testEvent, allowedSigner.Pub())
 422  	if err := testEvent.Sign(allowedSigner); chk.E(err) {
 423  		t.Fatalf("Failed to sign test event: %v", err)
 424  	}
 425  
 426  	// Test scenarios
 427  	scenarios := []struct {
 428  		name           string
 429  		loggedInPubkey []byte
 430  		expectedResult bool
 431  		description    string
 432  	}{
 433  		{
 434  			name:           "authenticated as allowed pubkey",
 435  			loggedInPubkey: allowedSigner.Pub(),
 436  			expectedResult: true,
 437  			description:    "Should allow when authenticated as allowed pubkey",
 438  		},
 439  		{
 440  			name:           "unauthenticated",
 441  			loggedInPubkey: nil,
 442  			expectedResult: false,
 443  			description:    "Should deny when not authenticated (privileged check)",
 444  		},
 445  		{
 446  			name:           "authenticated as different pubkey",
 447  			loggedInPubkey: unauthorizedSigner.Pub(),
 448  			expectedResult: false,
 449  			description:    "Should deny when authenticated as different pubkey",
 450  		},
 451  	}
 452  
 453  	for _, scenario := range scenarios {
 454  		t.Run(scenario.name, func(t *testing.T) {
 455  			allowed, err := policy.CheckPolicy("write", testEvent, scenario.loggedInPubkey, "127.0.0.1")
 456  			if err != nil {
 457  				t.Errorf("Unexpected error: %v", err)
 458  				return
 459  			}
 460  			if allowed != scenario.expectedResult {
 461  				t.Errorf("%s: Expected allowed=%v, got %v", scenario.description, scenario.expectedResult, allowed)
 462  			}
 463  		})
 464  	}
 465  
 466  	// Test read access for kind 10306
 467  	readEvent := event.New()
 468  	readEvent.CreatedAt = time.Now().Unix()
 469  	readEvent.Kind = kind.K{K: 10306}.K
 470  	readEvent.Content = []byte("test read event")
 471  	readEvent.Tags = tag.NewS()
 472  	addPTag(readEvent, allowedSigner.Pub())
 473  	if err := readEvent.Sign(allowedSigner); chk.E(err) {
 474  		t.Fatalf("Failed to sign read event: %v", err)
 475  	}
 476  
 477  	readScenarios := []struct {
 478  		name           string
 479  		loggedInPubkey []byte
 480  		expectedResult bool
 481  		description    string
 482  	}{
 483  		{
 484  			name:           "read authenticated as allowed pubkey",
 485  			loggedInPubkey: allowedSigner.Pub(),
 486  			expectedResult: true,
 487  			description:    "Should allow read when authenticated as allowed pubkey",
 488  		},
 489  		{
 490  			name:           "read unauthenticated",
 491  			loggedInPubkey: nil,
 492  			expectedResult: false,
 493  			description:    "Should deny read when not authenticated (privileged check)",
 494  		},
 495  		{
 496  			name:           "read authenticated as different pubkey",
 497  			loggedInPubkey: unauthorizedSigner.Pub(),
 498  			expectedResult: false,
 499  			description:    "Should deny read when authenticated as different pubkey",
 500  		},
 501  	}
 502  
 503  	for _, scenario := range readScenarios {
 504  		t.Run(scenario.name, func(t *testing.T) {
 505  			allowed, err := policy.CheckPolicy("read", readEvent, scenario.loggedInPubkey, "127.0.0.1")
 506  			if err != nil {
 507  				t.Errorf("Unexpected error: %v", err)
 508  				return
 509  			}
 510  			if allowed != scenario.expectedResult {
 511  				t.Errorf("%s: Expected allowed=%v, got %v", scenario.description, scenario.expectedResult, allowed)
 512  			}
 513  		})
 514  	}
 515  }
 516