comprehensive_test.go raw

   1  package policy
   2  
   3  import (
   4  	"context"
   5  	"encoding/json"
   6  	"os"
   7  	"path/filepath"
   8  	"testing"
   9  
  10  	"next.orly.dev/pkg/lol/chk"
  11  	"next.orly.dev/pkg/nostr/encoders/hex"
  12  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  13  )
  14  
  15  // TestPolicyDefinitionOfDone tests all requirements from the GitHub issue
  16  // Issue: https://git.nostrdev.com/mleku/next.orly.dev/issues/5
  17  //
  18  // Requirements:
  19  // 1. Configure relay to accept only certain kind events
  20  // 2. Scenario A: Only certain users should be allowed to write events
  21  // 3. Scenario B: Only certain users should be allowed to read events
  22  // 4. Scenario C: Only users involved in events should be able to read the events (privileged)
  23  // 5. Scenario D: Scripting option for complex validation
  24  func TestPolicyDefinitionOfDone(t *testing.T) {
  25  	// Generate test keypairs
  26  	allowedSigner := p8k.MustNew()
  27  	if err := allowedSigner.Generate(); chk.E(err) {
  28  		t.Fatalf("Failed to generate allowed signer: %v", err)
  29  	}
  30  	allowedPubkey := allowedSigner.Pub()
  31  	allowedPubkeyHex := hex.Enc(allowedPubkey)
  32  
  33  	unauthorizedSigner := p8k.MustNew()
  34  	if err := unauthorizedSigner.Generate(); chk.E(err) {
  35  		t.Fatalf("Failed to generate unauthorized signer: %v", err)
  36  	}
  37  	unauthorizedPubkey := unauthorizedSigner.Pub()
  38  	unauthorizedPubkeyHex := hex.Enc(unauthorizedPubkey)
  39  
  40  	thirdPartySigner := p8k.MustNew()
  41  	if err := thirdPartySigner.Generate(); chk.E(err) {
  42  		t.Fatalf("Failed to generate third party signer: %v", err)
  43  	}
  44  	thirdPartyPubkey := thirdPartySigner.Pub()
  45  
  46  	t.Logf("Allowed pubkey: %s", allowedPubkeyHex)
  47  	t.Logf("Unauthorized pubkey: %s", unauthorizedPubkeyHex)
  48  
  49  	// ===================================================================
  50  	// Requirement 1: Configure relay to accept only certain kind events
  51  	// ===================================================================
  52  	t.Run("Requirement 1: Kind Whitelist", func(t *testing.T) {
  53  		// Create policy with kind whitelist
  54  		policyJSON := map[string]interface{}{
  55  			"kind": map[string]interface{}{
  56  				"whitelist": []int{1, 3, 4}, // Only allow kinds 1, 3, 4
  57  			},
  58  		}
  59  
  60  		policyBytes, err := json.Marshal(policyJSON)
  61  		if err != nil {
  62  			t.Fatalf("Failed to marshal policy: %v", err)
  63  		}
  64  
  65  		policy, err := New(policyBytes)
  66  		if err != nil {
  67  			t.Fatalf("Failed to create policy: %v", err)
  68  		}
  69  
  70  		// Test: Kind 1 should be allowed (in whitelist)
  71  		event1 := createTestEvent(t, allowedSigner, "kind 1 event", 1)
  72  		allowed, err := policy.CheckPolicy("write", event1, allowedPubkey, "127.0.0.1")
  73  		if err != nil {
  74  			t.Errorf("Unexpected error: %v", err)
  75  		}
  76  		if !allowed {
  77  			t.Error("FAIL: Kind 1 should be allowed (in whitelist)")
  78  		} else {
  79  			t.Log("PASS: Kind 1 is allowed (in whitelist)")
  80  		}
  81  
  82  		// Test: Kind 5 should be denied (not in whitelist)
  83  		event5 := createTestEvent(t, allowedSigner, "kind 5 event", 5)
  84  		allowed, err = policy.CheckPolicy("write", event5, allowedPubkey, "127.0.0.1")
  85  		if err != nil {
  86  			t.Errorf("Unexpected error: %v", err)
  87  		}
  88  		if allowed {
  89  			t.Error("FAIL: Kind 5 should be denied (not in whitelist)")
  90  		} else {
  91  			t.Log("PASS: Kind 5 is denied (not in whitelist)")
  92  		}
  93  
  94  		// Test: Kind 3 should be allowed (in whitelist)
  95  		event3 := createTestEvent(t, allowedSigner, "kind 3 event", 3)
  96  		allowed, err = policy.CheckPolicy("write", event3, allowedPubkey, "127.0.0.1")
  97  		if err != nil {
  98  			t.Errorf("Unexpected error: %v", err)
  99  		}
 100  		if !allowed {
 101  			t.Error("FAIL: Kind 3 should be allowed (in whitelist)")
 102  		} else {
 103  			t.Log("PASS: Kind 3 is allowed (in whitelist)")
 104  		}
 105  	})
 106  
 107  	// ===================================================================
 108  	// Requirement 2: Scenario A - Only certain users can write events
 109  	// ===================================================================
 110  	t.Run("Scenario A: Per-Kind Write Access Control", func(t *testing.T) {
 111  		// Create policy with write_allow for kind 10
 112  		policyJSON := map[string]interface{}{
 113  			"rules": map[string]interface{}{
 114  				"10": map[string]interface{}{
 115  					"description": "Only allowed user can write kind 10",
 116  					"write_allow": []string{allowedPubkeyHex},
 117  				},
 118  			},
 119  		}
 120  
 121  		policyBytes, err := json.Marshal(policyJSON)
 122  		if err != nil {
 123  			t.Fatalf("Failed to marshal policy: %v", err)
 124  		}
 125  
 126  		policy, err := New(policyBytes)
 127  		if err != nil {
 128  			t.Fatalf("Failed to create policy: %v", err)
 129  		}
 130  
 131  		// Test: Allowed user can write kind 10
 132  		event10Allowed := createTestEvent(t, allowedSigner, "kind 10 from allowed user", 10)
 133  		allowed, err := policy.CheckPolicy("write", event10Allowed, allowedPubkey, "127.0.0.1")
 134  		if err != nil {
 135  			t.Errorf("Unexpected error: %v", err)
 136  		}
 137  		if !allowed {
 138  			t.Error("FAIL: Allowed user should be able to write kind 10")
 139  		} else {
 140  			t.Log("PASS: Allowed user can write kind 10")
 141  		}
 142  
 143  		// Test: Unauthorized user cannot write kind 10
 144  		event10Unauthorized := createTestEvent(t, unauthorizedSigner, "kind 10 from unauthorized user", 10)
 145  		allowed, err = policy.CheckPolicy("write", event10Unauthorized, unauthorizedPubkey, "127.0.0.1")
 146  		if err != nil {
 147  			t.Errorf("Unexpected error: %v", err)
 148  		}
 149  		if allowed {
 150  			t.Error("FAIL: Unauthorized user should NOT be able to write kind 10")
 151  		} else {
 152  			t.Log("PASS: Unauthorized user cannot write kind 10")
 153  		}
 154  	})
 155  
 156  	// ===================================================================
 157  	// Requirement 3: Scenario B - Only certain users can read events
 158  	// ===================================================================
 159  	t.Run("Scenario B: Per-Kind Read Access Control", func(t *testing.T) {
 160  		// Create policy with read_allow for kind 20
 161  		policyJSON := map[string]interface{}{
 162  			"rules": map[string]interface{}{
 163  				"20": map[string]interface{}{
 164  					"description": "Only allowed user can read kind 20",
 165  					"read_allow":  []string{allowedPubkeyHex},
 166  				},
 167  			},
 168  		}
 169  
 170  		policyBytes, err := json.Marshal(policyJSON)
 171  		if err != nil {
 172  			t.Fatalf("Failed to marshal policy: %v", err)
 173  		}
 174  
 175  		policy, err := New(policyBytes)
 176  		if err != nil {
 177  			t.Fatalf("Failed to create policy: %v", err)
 178  		}
 179  
 180  		// Create a kind 20 event (doesn't matter who wrote it)
 181  		event20 := createTestEvent(t, allowedSigner, "kind 20 event", 20)
 182  
 183  		// Test: Allowed user can read kind 20
 184  		allowed, err := policy.CheckPolicy("read", event20, allowedPubkey, "127.0.0.1")
 185  		if err != nil {
 186  			t.Errorf("Unexpected error: %v", err)
 187  		}
 188  		if !allowed {
 189  			t.Error("FAIL: Allowed user should be able to read kind 20")
 190  		} else {
 191  			t.Log("PASS: Allowed user can read kind 20")
 192  		}
 193  
 194  		// Test: Unauthorized user cannot read kind 20
 195  		allowed, err = policy.CheckPolicy("read", event20, unauthorizedPubkey, "127.0.0.1")
 196  		if err != nil {
 197  			t.Errorf("Unexpected error: %v", err)
 198  		}
 199  		if allowed {
 200  			t.Error("FAIL: Unauthorized user should NOT be able to read kind 20")
 201  		} else {
 202  			t.Log("PASS: Unauthorized user cannot read kind 20")
 203  		}
 204  
 205  		// Test: Unauthenticated user cannot read kind 20
 206  		allowed, err = policy.CheckPolicy("read", event20, nil, "127.0.0.1")
 207  		if err != nil {
 208  			t.Errorf("Unexpected error: %v", err)
 209  		}
 210  		if allowed {
 211  			t.Error("FAIL: Unauthenticated user should NOT be able to read kind 20")
 212  		} else {
 213  			t.Log("PASS: Unauthenticated user cannot read kind 20")
 214  		}
 215  	})
 216  
 217  	// ===================================================================
 218  	// Requirement 4: Scenario C - Only users involved in events can read (privileged)
 219  	// ===================================================================
 220  	t.Run("Scenario C: Privileged Events - Only Parties Involved", func(t *testing.T) {
 221  		// Create policy with privileged flag for kind 30
 222  		policyJSON := map[string]interface{}{
 223  			"rules": map[string]interface{}{
 224  				"30": map[string]interface{}{
 225  					"description": "Privileged - only parties involved can read",
 226  					"privileged":  true,
 227  				},
 228  			},
 229  		}
 230  
 231  		policyBytes, err := json.Marshal(policyJSON)
 232  		if err != nil {
 233  			t.Fatalf("Failed to marshal policy: %v", err)
 234  		}
 235  
 236  		policy, err := New(policyBytes)
 237  		if err != nil {
 238  			t.Fatalf("Failed to create policy: %v", err)
 239  		}
 240  
 241  		// Test 1: Author can read their own event
 242  		event30Author := createTestEvent(t, allowedSigner, "kind 30 authored by allowed user", 30)
 243  		allowed, err := policy.CheckPolicy("read", event30Author, allowedPubkey, "127.0.0.1")
 244  		if err != nil {
 245  			t.Errorf("Unexpected error: %v", err)
 246  		}
 247  		if !allowed {
 248  			t.Error("FAIL: Author should be able to read their own privileged event")
 249  		} else {
 250  			t.Log("PASS: Author can read their own privileged event")
 251  		}
 252  
 253  		// Test 2: User in p-tag can read the event
 254  		event30WithPTag := createTestEvent(t, allowedSigner, "kind 30 with unauthorized in p-tag", 30)
 255  		addPTag(event30WithPTag, unauthorizedPubkey) // Add unauthorized user to p-tag
 256  		allowed, err = policy.CheckPolicy("read", event30WithPTag, unauthorizedPubkey, "127.0.0.1")
 257  		if err != nil {
 258  			t.Errorf("Unexpected error: %v", err)
 259  		}
 260  		if !allowed {
 261  			t.Error("FAIL: User in p-tag should be able to read privileged event")
 262  		} else {
 263  			t.Log("PASS: User in p-tag can read privileged event")
 264  		}
 265  
 266  		// Test 3: Third party (not author, not in p-tag) cannot read
 267  		event30NoAccess := createTestEvent(t, allowedSigner, "kind 30 for allowed only", 30)
 268  		allowed, err = policy.CheckPolicy("read", event30NoAccess, thirdPartyPubkey, "127.0.0.1")
 269  		if err != nil {
 270  			t.Errorf("Unexpected error: %v", err)
 271  		}
 272  		if allowed {
 273  			t.Error("FAIL: Third party should NOT be able to read privileged event")
 274  		} else {
 275  			t.Log("PASS: Third party cannot read privileged event")
 276  		}
 277  
 278  		// Test 4: Unauthenticated user cannot read privileged event
 279  		allowed, err = policy.CheckPolicy("read", event30NoAccess, nil, "127.0.0.1")
 280  		if err != nil {
 281  			t.Errorf("Unexpected error: %v", err)
 282  		}
 283  		if allowed {
 284  			t.Error("FAIL: Unauthenticated user should NOT be able to read privileged event")
 285  		} else {
 286  			t.Log("PASS: Unauthenticated user cannot read privileged event")
 287  		}
 288  	})
 289  
 290  	// ===================================================================
 291  	// Requirement 5: Scenario D - Scripting support
 292  	// ===================================================================
 293  	t.Run("Scenario D: Scripting Support", func(t *testing.T) {
 294  		// Create temporary directory for test
 295  		tempDir := t.TempDir()
 296  		scriptPath := filepath.Join(tempDir, "test-policy.sh")
 297  
 298  		// Create a simple test script that accepts events with content "accept"
 299  		scriptContent := `#!/bin/bash
 300  while IFS= read -r line; do
 301    if echo "$line" | grep -q '"content":"accept"'; then
 302      echo '{"id":"test","action":"accept","msg":"accepted by script"}'
 303    else
 304      echo '{"id":"test","action":"reject","msg":"rejected by script"}'
 305    fi
 306  done
 307  `
 308  		if err := os.WriteFile(scriptPath, []byte(scriptContent), 0755); err != nil {
 309  			t.Fatalf("Failed to write test script: %v", err)
 310  		}
 311  
 312  		// Create policy with script
 313  		policyJSON := map[string]interface{}{
 314  			"rules": map[string]interface{}{
 315  				"40": map[string]interface{}{
 316  					"description": "Script-based validation",
 317  					"script":      scriptPath,
 318  				},
 319  			},
 320  		}
 321  
 322  		policyBytes, err := json.Marshal(policyJSON)
 323  		if err != nil {
 324  			t.Fatalf("Failed to marshal policy: %v", err)
 325  		}
 326  
 327  		policy, err := New(policyBytes)
 328  		if err != nil {
 329  			t.Fatalf("Failed to create policy: %v", err)
 330  		}
 331  
 332  		// Initialize policy manager
 333  		ctx, cancel := context.WithCancel(context.Background())
 334  		defer cancel()
 335  		policy.manager = &PolicyManager{
 336  			ctx:        ctx,
 337  			cancel:     cancel,
 338  			configDir:  tempDir,
 339  			scriptPath: scriptPath,
 340  			enabled:    true,
 341  			runners:    make(map[string]*ScriptRunner),
 342  		}
 343  
 344  		// Test: Event with "accept" content should be accepted
 345  		eventAccept := createTestEvent(t, allowedSigner, "accept", 40)
 346  		allowed, err := policy.CheckPolicy("write", eventAccept, allowedPubkey, "127.0.0.1")
 347  		if err != nil {
 348  			t.Logf("Script check failed (expected if script not running): %v", err)
 349  			t.Log("SKIP: Script execution requires policy manager to be fully running")
 350  		} else if !allowed {
 351  			t.Log("INFO: Script rejected event (may be expected if script not running)")
 352  		} else {
 353  			t.Log("PASS: Script accepted event with 'accept' content")
 354  		}
 355  
 356  		// Note: Full script testing requires the policy manager to be running,
 357  		// which is tested in policy_integration_test.go
 358  		t.Log("INFO: Full script validation tested in integration tests")
 359  	})
 360  
 361  	// ===================================================================
 362  	// Combined Scenarios
 363  	// ===================================================================
 364  	t.Run("Combined: Kind Whitelist + Write Access + Privileged", func(t *testing.T) {
 365  		// Create comprehensive policy
 366  		policyJSON := map[string]interface{}{
 367  			"kind": map[string]interface{}{
 368  				"whitelist": []int{50, 51}, // Only kinds 50 and 51
 369  			},
 370  			"rules": map[string]interface{}{
 371  				"50": map[string]interface{}{
 372  					"description": "Write-restricted kind",
 373  					"write_allow": []string{allowedPubkeyHex},
 374  				},
 375  				"51": map[string]interface{}{
 376  					"description": "Privileged kind",
 377  					"privileged":  true,
 378  				},
 379  			},
 380  		}
 381  
 382  		policyBytes, err := json.Marshal(policyJSON)
 383  		if err != nil {
 384  			t.Fatalf("Failed to marshal policy: %v", err)
 385  		}
 386  
 387  		policy, err := New(policyBytes)
 388  		if err != nil {
 389  			t.Fatalf("Failed to create policy: %v", err)
 390  		}
 391  
 392  		// Test 1: Kind 50 with allowed user should pass
 393  		event50Allowed := createTestEvent(t, allowedSigner, "kind 50 allowed", 50)
 394  		allowed, err := policy.CheckPolicy("write", event50Allowed, allowedPubkey, "127.0.0.1")
 395  		if err != nil {
 396  			t.Errorf("Unexpected error: %v", err)
 397  		}
 398  		if !allowed {
 399  			t.Error("FAIL: Kind 50 with allowed user should pass")
 400  		} else {
 401  			t.Log("PASS: Kind 50 with allowed user passes")
 402  		}
 403  
 404  		// Test 2: Kind 50 with unauthorized user should fail
 405  		event50Unauthorized := createTestEvent(t, unauthorizedSigner, "kind 50 unauthorized", 50)
 406  		allowed, err = policy.CheckPolicy("write", event50Unauthorized, unauthorizedPubkey, "127.0.0.1")
 407  		if err != nil {
 408  			t.Errorf("Unexpected error: %v", err)
 409  		}
 410  		if allowed {
 411  			t.Error("FAIL: Kind 50 with unauthorized user should fail")
 412  		} else {
 413  			t.Log("PASS: Kind 50 with unauthorized user fails")
 414  		}
 415  
 416  		// Test 3: Kind 100 (not in whitelist) should fail regardless of user
 417  		event100 := createTestEvent(t, allowedSigner, "kind 100 not in whitelist", 100)
 418  		allowed, err = policy.CheckPolicy("write", event100, allowedPubkey, "127.0.0.1")
 419  		if err != nil {
 420  			t.Errorf("Unexpected error: %v", err)
 421  		}
 422  		if allowed {
 423  			t.Error("FAIL: Kind 100 (not in whitelist) should fail")
 424  		} else {
 425  			t.Log("PASS: Kind 100 (not in whitelist) fails")
 426  		}
 427  
 428  		// Test 4: Kind 51 (privileged) - author can write
 429  		event51Author := createTestEvent(t, allowedSigner, "kind 51 by author", 51)
 430  		allowed, err = policy.CheckPolicy("write", event51Author, allowedPubkey, "127.0.0.1")
 431  		if err != nil {
 432  			t.Errorf("Unexpected error: %v", err)
 433  		}
 434  		if !allowed {
 435  			t.Error("FAIL: Author should be able to write their own privileged event")
 436  		} else {
 437  			t.Log("PASS: Author can write their own privileged event")
 438  		}
 439  
 440  		// Test 5: Kind 51 (privileged) - third party cannot read
 441  		allowed, err = policy.CheckPolicy("read", event51Author, thirdPartyPubkey, "127.0.0.1")
 442  		if err != nil {
 443  			t.Errorf("Unexpected error: %v", err)
 444  		}
 445  		if allowed {
 446  			t.Error("FAIL: Third party should NOT be able to read privileged event")
 447  		} else {
 448  			t.Log("PASS: Third party cannot read privileged event")
 449  		}
 450  	})
 451  }
 452  
 453  // TestDefaultPolicy tests the default_policy configuration
 454  func TestDefaultPolicy(t *testing.T) {
 455  	allowedSigner := p8k.MustNew()
 456  	if err := allowedSigner.Generate(); chk.E(err) {
 457  		t.Fatalf("Failed to generate signer: %v", err)
 458  	}
 459  
 460  	t.Run("default policy allow", func(t *testing.T) {
 461  		policyJSON := map[string]interface{}{
 462  			"default_policy": "allow",
 463  		}
 464  
 465  		policyBytes, err := json.Marshal(policyJSON)
 466  		if err != nil {
 467  			t.Fatalf("Failed to marshal policy: %v", err)
 468  		}
 469  
 470  		policy, err := New(policyBytes)
 471  		if err != nil {
 472  			t.Fatalf("Failed to create policy: %v", err)
 473  		}
 474  
 475  		// Event without specific rule should be allowed
 476  		event := createTestEvent(t, allowedSigner, "test event", 999)
 477  		allowed, err := policy.CheckPolicy("write", event, allowedSigner.Pub(), "127.0.0.1")
 478  		if err != nil {
 479  			t.Errorf("Unexpected error: %v", err)
 480  		}
 481  		if !allowed {
 482  			t.Error("FAIL: Event should be allowed with default_policy=allow")
 483  		} else {
 484  			t.Log("PASS: Event allowed with default_policy=allow")
 485  		}
 486  	})
 487  
 488  	t.Run("default policy deny", func(t *testing.T) {
 489  		policyJSON := map[string]interface{}{
 490  			"default_policy": "deny",
 491  		}
 492  
 493  		policyBytes, err := json.Marshal(policyJSON)
 494  		if err != nil {
 495  			t.Fatalf("Failed to marshal policy: %v", err)
 496  		}
 497  
 498  		policy, err := New(policyBytes)
 499  		if err != nil {
 500  			t.Fatalf("Failed to create policy: %v", err)
 501  		}
 502  
 503  		// Event without specific rule should be denied
 504  		event := createTestEvent(t, allowedSigner, "test event", 999)
 505  		allowed, err := policy.CheckPolicy("write", event, allowedSigner.Pub(), "127.0.0.1")
 506  		if err != nil {
 507  			t.Errorf("Unexpected error: %v", err)
 508  		}
 509  		if allowed {
 510  			t.Error("FAIL: Event should be denied with default_policy=deny")
 511  		} else {
 512  			t.Log("PASS: Event denied with default_policy=deny")
 513  		}
 514  	})
 515  }
 516