precedence_test.go raw

   1  package policy
   2  
   3  import (
   4  	"testing"
   5  
   6  	"next.orly.dev/pkg/lol/chk"
   7  	"next.orly.dev/pkg/nostr/encoders/hex"
   8  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
   9  )
  10  
  11  // TestPolicyPrecedenceRules verifies the correct evaluation order and precedence
  12  // of different policy fields, clarifying the exact behavior after fixes.
  13  //
  14  // Evaluation Order (as fixed):
  15  // 1. Universal constraints (size, tags, timestamps)
  16  // 2. Explicit denials (highest priority)
  17  // 3. Privileged access (ONLY if no allow lists)
  18  // 4. Exclusive allow lists (authoritative when present)
  19  // 5. Privileged final check
  20  // 6. Default policy
  21  func TestPolicyPrecedenceRules(t *testing.T) {
  22  	// Generate test keypairs
  23  	aliceSigner := p8k.MustNew()
  24  	if err := aliceSigner.Generate(); chk.E(err) {
  25  		t.Fatalf("Failed to generate alice signer: %v", err)
  26  	}
  27  	alicePubkey := aliceSigner.Pub()
  28  
  29  	bobSigner := p8k.MustNew()
  30  	if err := bobSigner.Generate(); chk.E(err) {
  31  		t.Fatalf("Failed to generate bob signer: %v", err)
  32  	}
  33  	bobPubkey := bobSigner.Pub()
  34  
  35  	charlieSigner := p8k.MustNew()
  36  	if err := charlieSigner.Generate(); chk.E(err) {
  37  		t.Fatalf("Failed to generate charlie signer: %v", err)
  38  	}
  39  	charliePubkey := charlieSigner.Pub()
  40  
  41  	// ===================================================================
  42  	// Test 1: Deny List Has Highest Priority
  43  	// ===================================================================
  44  	t.Run("Deny List Overrides Everything", func(t *testing.T) {
  45  		policy := &P{
  46  			DefaultPolicy: "allow",
  47  			rules: map[int]Rule{
  48  				100: {
  49  					Description: "Deny overrides allow and privileged",
  50  					AccessControl: AccessControl{
  51  						WriteAllow: []string{hex.Enc(alicePubkey)}, // Alice in allow list
  52  						WriteDeny:  []string{hex.Enc(alicePubkey)}, // But also in deny list
  53  					},
  54  					Constraints: Constraints{
  55  						Privileged: true, // And it's privileged
  56  					},
  57  				},
  58  			},
  59  		}
  60  
  61  		// Alice creates an event (she's author, in allow list, but also in deny list)
  62  		event := createTestEvent(t, aliceSigner, "test", 100)
  63  
  64  		// Should be DENIED because deny list has highest priority
  65  		allowed, err := policy.CheckPolicy("write", event, alicePubkey, "127.0.0.1")
  66  		if err != nil {
  67  			t.Fatalf("Unexpected error: %v", err)
  68  		}
  69  		if allowed {
  70  			t.Error("FAIL: User in deny list should be denied even if in allow list and privileged")
  71  		} else {
  72  			t.Log("PASS: Deny list correctly overrides allow list and privileged")
  73  		}
  74  	})
  75  
  76  	// ===================================================================
  77  	// Test 2: Allow List OR Privileged (Either grants access)
  78  	// ===================================================================
  79  	t.Run("Allow List OR Privileged Access", func(t *testing.T) {
  80  		policy := &P{
  81  			DefaultPolicy: "allow",
  82  			rules: map[int]Rule{
  83  				200: {
  84  					Description: "Privileged with allow list",
  85  					AccessControl: AccessControl{
  86  						ReadAllow: []string{hex.Enc(bobPubkey)}, // Only Bob in allow list
  87  					},
  88  					Constraints: Constraints{
  89  						Privileged: true,
  90  					},
  91  				},
  92  			},
  93  		}
  94  
  95  		// Alice creates event
  96  		event := createTestEvent(t, aliceSigner, "secret", 200)
  97  
  98  		// Test 2a: Alice is author (privileged) but NOT in allow list - should be ALLOWED (OR logic)
  99  		allowed, err := policy.CheckPolicy("read", event, alicePubkey, "127.0.0.1")
 100  		if err != nil {
 101  			t.Fatalf("Unexpected error: %v", err)
 102  		}
 103  		if !allowed {
 104  			t.Error("FAIL: Author should be allowed via privileged (OR logic)")
 105  		} else {
 106  			t.Log("PASS: Author allowed via privileged despite not in allow list (OR logic)")
 107  		}
 108  
 109  		// Test 2b: Bob is in allow list - should be ALLOWED
 110  		allowed, err = policy.CheckPolicy("read", event, bobPubkey, "127.0.0.1")
 111  		if err != nil {
 112  			t.Fatalf("Unexpected error: %v", err)
 113  		}
 114  		if !allowed {
 115  			t.Error("FAIL: User in allow list should be allowed")
 116  		} else {
 117  			t.Log("PASS: User in allow list correctly allowed")
 118  		}
 119  
 120  		// Test 2c: Charlie in p-tag but not in allow list - should be ALLOWED (OR logic)
 121  		addPTag(event, charliePubkey)
 122  		allowed, err = policy.CheckPolicy("read", event, charliePubkey, "127.0.0.1")
 123  		if err != nil {
 124  			t.Fatalf("Unexpected error: %v", err)
 125  		}
 126  		if !allowed {
 127  			t.Error("FAIL: User in p-tag should be allowed via privileged (OR logic)")
 128  		} else {
 129  			t.Log("PASS: User in p-tag allowed via privileged despite not in allow list (OR logic)")
 130  		}
 131  	})
 132  
 133  	// ===================================================================
 134  	// Test 3: Privileged Without Allow List Grants Access
 135  	// ===================================================================
 136  	t.Run("Privileged Grants Access When No Allow List", func(t *testing.T) {
 137  		policy := &P{
 138  			DefaultPolicy: "deny", // Default deny to make test clearer
 139  			rules: map[int]Rule{
 140  				300: {
 141  					Description: "Privileged without allow list",
 142  					Constraints: Constraints{
 143  						Privileged: true,
 144  					},
 145  					// NO ReadAllow or WriteAllow specified
 146  				},
 147  			},
 148  		}
 149  
 150  		// Alice creates event with Bob in p-tag
 151  		event := createTestEvent(t, aliceSigner, "message", 300)
 152  		addPTag(event, bobPubkey)
 153  
 154  		// Test 3a: Alice (author) should be ALLOWED (privileged, no allow list)
 155  		allowed, err := policy.CheckPolicy("read", event, alicePubkey, "127.0.0.1")
 156  		if err != nil {
 157  			t.Fatalf("Unexpected error: %v", err)
 158  		}
 159  
 160  
 161  		if !allowed {
 162  			t.Error("FAIL: Author should be allowed when privileged and no allow list")
 163  		} else {
 164  			t.Log("PASS: Privileged correctly grants access to author when no allow list")
 165  		}
 166  
 167  		// Test 3b: Bob (in p-tag) should be ALLOWED (privileged, no allow list)
 168  		allowed, err = policy.CheckPolicy("read", event, bobPubkey, "127.0.0.1")
 169  		if err != nil {
 170  			t.Fatalf("Unexpected error: %v", err)
 171  		}
 172  		if !allowed {
 173  			t.Error("FAIL: P-tagged user should be allowed when privileged and no allow list")
 174  		} else {
 175  			t.Log("PASS: Privileged correctly grants access to p-tagged user when no allow list")
 176  		}
 177  
 178  		// Test 3c: Charlie (not involved) should be DENIED
 179  		allowed, err = policy.CheckPolicy("read", event, charliePubkey, "127.0.0.1")
 180  		if err != nil {
 181  			t.Fatalf("Unexpected error: %v", err)
 182  		}
 183  		if allowed {
 184  			t.Error("FAIL: Non-involved user should be denied for privileged event")
 185  		} else {
 186  			t.Log("PASS: Privileged correctly denies non-involved user")
 187  		}
 188  	})
 189  
 190  	// ===================================================================
 191  	// Test 4: Allow List Without Privileged Is Exclusive
 192  	// ===================================================================
 193  	t.Run("Allow List Exclusive Without Privileged", func(t *testing.T) {
 194  		policy := &P{
 195  			DefaultPolicy: "allow", // Even with allow default
 196  			rules: map[int]Rule{
 197  				400: {
 198  					Description: "Allow list only",
 199  					AccessControl: AccessControl{
 200  						WriteAllow: []string{hex.Enc(alicePubkey)}, // Only Alice
 201  					},
 202  					// NO Privileged flag
 203  				},
 204  			},
 205  		}
 206  
 207  		// Test 4a: Alice should be ALLOWED (in allow list)
 208  		aliceEvent := createTestEvent(t, aliceSigner, "alice msg", 400)
 209  		allowed, err := policy.CheckPolicy("write", aliceEvent, alicePubkey, "127.0.0.1")
 210  		if err != nil {
 211  			t.Fatalf("Unexpected error: %v", err)
 212  		}
 213  		if !allowed {
 214  			t.Error("FAIL: User in allow list should be allowed")
 215  		} else {
 216  			t.Log("PASS: Allow list correctly allows listed user")
 217  		}
 218  
 219  		// Test 4b: Bob should be DENIED (not in allow list, even with allow default)
 220  		bobEvent := createTestEvent(t, bobSigner, "bob msg", 400)
 221  		allowed, err = policy.CheckPolicy("write", bobEvent, bobPubkey, "127.0.0.1")
 222  		if err != nil {
 223  			t.Fatalf("Unexpected error: %v", err)
 224  		}
 225  		if allowed {
 226  			t.Error("FAIL: User not in allow list should be denied despite allow default")
 227  		} else {
 228  			t.Log("PASS: Allow list correctly excludes non-listed user")
 229  		}
 230  	})
 231  
 232  	// ===================================================================
 233  	// Test 5: Complex Precedence Chain
 234  	// ===================================================================
 235  	t.Run("Complex Precedence Chain", func(t *testing.T) {
 236  		policy := &P{
 237  			DefaultPolicy: "allow",
 238  			rules: map[int]Rule{
 239  				500: {
 240  					Description: "Complex rules",
 241  					AccessControl: AccessControl{
 242  						WriteAllow: []string{hex.Enc(alicePubkey), hex.Enc(bobPubkey)},
 243  						WriteDeny:  []string{hex.Enc(bobPubkey)}, // Bob denied despite being in allow
 244  					},
 245  					Constraints: Constraints{
 246  						Privileged: true,
 247  					},
 248  				},
 249  			},
 250  		}
 251  
 252  		// Test 5a: Alice in allow, not in deny - ALLOWED
 253  		aliceEvent := createTestEvent(t, aliceSigner, "alice", 500)
 254  		allowed, err := policy.CheckPolicy("write", aliceEvent, alicePubkey, "127.0.0.1")
 255  		if err != nil {
 256  			t.Fatalf("Unexpected error: %v", err)
 257  		}
 258  		if !allowed {
 259  			t.Error("FAIL: Alice should be allowed (in allow, not in deny)")
 260  		} else {
 261  			t.Log("PASS: User in allow and not in deny is allowed")
 262  		}
 263  
 264  		// Test 5b: Bob in allow AND deny - DENIED (deny wins)
 265  		bobEvent := createTestEvent(t, bobSigner, "bob", 500)
 266  		allowed, err = policy.CheckPolicy("write", bobEvent, bobPubkey, "127.0.0.1")
 267  		if err != nil {
 268  			t.Fatalf("Unexpected error: %v", err)
 269  		}
 270  		if allowed {
 271  			t.Error("FAIL: Bob should be denied (deny list overrides allow list)")
 272  		} else {
 273  			t.Log("PASS: Deny list correctly overrides allow list")
 274  		}
 275  
 276  		// Test 5c: Charlie not in allow - DENIED (even though he's author of his event)
 277  		charlieEvent := createTestEvent(t, charlieSigner, "charlie", 500)
 278  		allowed, err = policy.CheckPolicy("write", charlieEvent, charliePubkey, "127.0.0.1")
 279  		if err != nil {
 280  			t.Fatalf("Unexpected error: %v", err)
 281  		}
 282  		if allowed {
 283  			t.Error("FAIL: Charlie should be denied (not in allow list)")
 284  		} else {
 285  			t.Log("PASS: Allow list correctly excludes non-listed privileged author")
 286  		}
 287  	})
 288  
 289  	// ===================================================================
 290  	// Test 6: Default Policy Application
 291  	// ===================================================================
 292  	t.Run("Default Policy Only When No Rules", func(t *testing.T) {
 293  		// Test 6a: With allow default and no rules
 294  		policyAllow := &P{
 295  			DefaultPolicy: "allow",
 296  			rules: map[int]Rule{
 297  				// No rule for kind 600
 298  			},
 299  		}
 300  
 301  		event := createTestEvent(t, aliceSigner, "test", 600)
 302  		allowed, err := policyAllow.CheckPolicy("write", event, alicePubkey, "127.0.0.1")
 303  		if err != nil {
 304  			t.Fatalf("Unexpected error: %v", err)
 305  		}
 306  		if !allowed {
 307  			t.Error("FAIL: Default allow should permit when no rules")
 308  		} else {
 309  			t.Log("PASS: Default allow correctly applied when no rules")
 310  		}
 311  
 312  		// Test 6b: With deny default and no rules
 313  		policyDeny := &P{
 314  			DefaultPolicy: "deny",
 315  			rules: map[int]Rule{
 316  				// No rule for kind 600
 317  			},
 318  		}
 319  
 320  		allowed, err = policyDeny.CheckPolicy("write", event, alicePubkey, "127.0.0.1")
 321  		if err != nil {
 322  			t.Fatalf("Unexpected error: %v", err)
 323  		}
 324  		if allowed {
 325  			t.Error("FAIL: Default deny should block when no rules")
 326  		} else {
 327  			t.Log("PASS: Default deny correctly applied when no rules")
 328  		}
 329  
 330  		// Test 6c: Default does NOT apply when allow list exists
 331  		policyWithRule := &P{
 332  			DefaultPolicy: "allow", // Allow default
 333  			rules: map[int]Rule{
 334  				700: {
 335  					AccessControl: AccessControl{
 336  						WriteAllow: []string{hex.Enc(bobPubkey)}, // Only Bob
 337  					},
 338  				},
 339  			},
 340  		}
 341  
 342  		eventKind700 := createTestEvent(t, aliceSigner, "alice", 700)
 343  		allowed, err = policyWithRule.CheckPolicy("write", eventKind700, alicePubkey, "127.0.0.1")
 344  		if err != nil {
 345  			t.Fatalf("Unexpected error: %v", err)
 346  		}
 347  		if allowed {
 348  			t.Error("FAIL: Default allow should NOT override exclusive allow list")
 349  		} else {
 350  			t.Log("PASS: Allow list correctly overrides default policy")
 351  		}
 352  	})
 353  }
 354