335 lines
11 KiB
Go
335 lines
11 KiB
Go
package policy
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"lol.mleku.dev/chk"
|
|
"git.mleku.dev/mleku/nostr/encoders/hex"
|
|
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
|
|
)
|
|
|
|
// TestPolicyPrecedenceRules verifies the correct evaluation order and precedence
|
|
// of different policy fields, clarifying the exact behavior after fixes.
|
|
//
|
|
// Evaluation Order (as fixed):
|
|
// 1. Universal constraints (size, tags, timestamps)
|
|
// 2. Explicit denials (highest priority)
|
|
// 3. Privileged access (ONLY if no allow lists)
|
|
// 4. Exclusive allow lists (authoritative when present)
|
|
// 5. Privileged final check
|
|
// 6. Default policy
|
|
func TestPolicyPrecedenceRules(t *testing.T) {
|
|
// Generate test keypairs
|
|
aliceSigner := p8k.MustNew()
|
|
if err := aliceSigner.Generate(); chk.E(err) {
|
|
t.Fatalf("Failed to generate alice signer: %v", err)
|
|
}
|
|
alicePubkey := aliceSigner.Pub()
|
|
|
|
bobSigner := p8k.MustNew()
|
|
if err := bobSigner.Generate(); chk.E(err) {
|
|
t.Fatalf("Failed to generate bob signer: %v", err)
|
|
}
|
|
bobPubkey := bobSigner.Pub()
|
|
|
|
charlieSigner := p8k.MustNew()
|
|
if err := charlieSigner.Generate(); chk.E(err) {
|
|
t.Fatalf("Failed to generate charlie signer: %v", err)
|
|
}
|
|
charliePubkey := charlieSigner.Pub()
|
|
|
|
// ===================================================================
|
|
// Test 1: Deny List Has Highest Priority
|
|
// ===================================================================
|
|
t.Run("Deny List Overrides Everything", func(t *testing.T) {
|
|
policy := &P{
|
|
DefaultPolicy: "allow",
|
|
Rules: map[int]Rule{
|
|
100: {
|
|
Description: "Deny overrides allow and privileged",
|
|
WriteAllow: []string{hex.Enc(alicePubkey)}, // Alice in allow list
|
|
WriteDeny: []string{hex.Enc(alicePubkey)}, // But also in deny list
|
|
Privileged: true, // And it's privileged
|
|
},
|
|
},
|
|
}
|
|
|
|
// Alice creates an event (she's author, in allow list, but also in deny list)
|
|
event := createTestEvent(t, aliceSigner, "test", 100)
|
|
|
|
// Should be DENIED because deny list has highest priority
|
|
allowed, err := policy.CheckPolicy("write", event, alicePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("FAIL: User in deny list should be denied even if in allow list and privileged")
|
|
} else {
|
|
t.Log("PASS: Deny list correctly overrides allow list and privileged")
|
|
}
|
|
})
|
|
|
|
// ===================================================================
|
|
// Test 2: Allow List OR Privileged (Either grants access)
|
|
// ===================================================================
|
|
t.Run("Allow List OR Privileged Access", func(t *testing.T) {
|
|
policy := &P{
|
|
DefaultPolicy: "allow",
|
|
Rules: map[int]Rule{
|
|
200: {
|
|
Description: "Privileged with allow list",
|
|
ReadAllow: []string{hex.Enc(bobPubkey)}, // Only Bob in allow list
|
|
Privileged: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Alice creates event
|
|
event := createTestEvent(t, aliceSigner, "secret", 200)
|
|
|
|
// Test 2a: Alice is author (privileged) but NOT in allow list - should be ALLOWED (OR logic)
|
|
allowed, err := policy.CheckPolicy("read", event, alicePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("FAIL: Author should be allowed via privileged (OR logic)")
|
|
} else {
|
|
t.Log("PASS: Author allowed via privileged despite not in allow list (OR logic)")
|
|
}
|
|
|
|
// Test 2b: Bob is in allow list - should be ALLOWED
|
|
allowed, err = policy.CheckPolicy("read", event, bobPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("FAIL: User in allow list should be allowed")
|
|
} else {
|
|
t.Log("PASS: User in allow list correctly allowed")
|
|
}
|
|
|
|
// Test 2c: Charlie in p-tag but not in allow list - should be ALLOWED (OR logic)
|
|
addPTag(event, charliePubkey)
|
|
allowed, err = policy.CheckPolicy("read", event, charliePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("FAIL: User in p-tag should be allowed via privileged (OR logic)")
|
|
} else {
|
|
t.Log("PASS: User in p-tag allowed via privileged despite not in allow list (OR logic)")
|
|
}
|
|
})
|
|
|
|
// ===================================================================
|
|
// Test 3: Privileged Without Allow List Grants Access
|
|
// ===================================================================
|
|
t.Run("Privileged Grants Access When No Allow List", func(t *testing.T) {
|
|
policy := &P{
|
|
DefaultPolicy: "deny", // Default deny to make test clearer
|
|
Rules: map[int]Rule{
|
|
300: {
|
|
Description: "Privileged without allow list",
|
|
Privileged: true,
|
|
// NO ReadAllow or WriteAllow specified
|
|
},
|
|
},
|
|
}
|
|
|
|
// Alice creates event with Bob in p-tag
|
|
event := createTestEvent(t, aliceSigner, "message", 300)
|
|
addPTag(event, bobPubkey)
|
|
|
|
// Test 3a: Alice (author) should be ALLOWED (privileged, no allow list)
|
|
allowed, err := policy.CheckPolicy("read", event, alicePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
|
|
if !allowed {
|
|
t.Error("FAIL: Author should be allowed when privileged and no allow list")
|
|
} else {
|
|
t.Log("PASS: Privileged correctly grants access to author when no allow list")
|
|
}
|
|
|
|
// Test 3b: Bob (in p-tag) should be ALLOWED (privileged, no allow list)
|
|
allowed, err = policy.CheckPolicy("read", event, bobPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("FAIL: P-tagged user should be allowed when privileged and no allow list")
|
|
} else {
|
|
t.Log("PASS: Privileged correctly grants access to p-tagged user when no allow list")
|
|
}
|
|
|
|
// Test 3c: Charlie (not involved) should be DENIED
|
|
allowed, err = policy.CheckPolicy("read", event, charliePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("FAIL: Non-involved user should be denied for privileged event")
|
|
} else {
|
|
t.Log("PASS: Privileged correctly denies non-involved user")
|
|
}
|
|
})
|
|
|
|
// ===================================================================
|
|
// Test 4: Allow List Without Privileged Is Exclusive
|
|
// ===================================================================
|
|
t.Run("Allow List Exclusive Without Privileged", func(t *testing.T) {
|
|
policy := &P{
|
|
DefaultPolicy: "allow", // Even with allow default
|
|
Rules: map[int]Rule{
|
|
400: {
|
|
Description: "Allow list only",
|
|
WriteAllow: []string{hex.Enc(alicePubkey)}, // Only Alice
|
|
// NO Privileged flag
|
|
},
|
|
},
|
|
}
|
|
|
|
// Test 4a: Alice should be ALLOWED (in allow list)
|
|
aliceEvent := createTestEvent(t, aliceSigner, "alice msg", 400)
|
|
allowed, err := policy.CheckPolicy("write", aliceEvent, alicePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("FAIL: User in allow list should be allowed")
|
|
} else {
|
|
t.Log("PASS: Allow list correctly allows listed user")
|
|
}
|
|
|
|
// Test 4b: Bob should be DENIED (not in allow list, even with allow default)
|
|
bobEvent := createTestEvent(t, bobSigner, "bob msg", 400)
|
|
allowed, err = policy.CheckPolicy("write", bobEvent, bobPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("FAIL: User not in allow list should be denied despite allow default")
|
|
} else {
|
|
t.Log("PASS: Allow list correctly excludes non-listed user")
|
|
}
|
|
})
|
|
|
|
// ===================================================================
|
|
// Test 5: Complex Precedence Chain
|
|
// ===================================================================
|
|
t.Run("Complex Precedence Chain", func(t *testing.T) {
|
|
policy := &P{
|
|
DefaultPolicy: "allow",
|
|
Rules: map[int]Rule{
|
|
500: {
|
|
Description: "Complex rules",
|
|
WriteAllow: []string{hex.Enc(alicePubkey), hex.Enc(bobPubkey)},
|
|
WriteDeny: []string{hex.Enc(bobPubkey)}, // Bob denied despite being in allow
|
|
Privileged: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Test 5a: Alice in allow, not in deny - ALLOWED
|
|
aliceEvent := createTestEvent(t, aliceSigner, "alice", 500)
|
|
allowed, err := policy.CheckPolicy("write", aliceEvent, alicePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("FAIL: Alice should be allowed (in allow, not in deny)")
|
|
} else {
|
|
t.Log("PASS: User in allow and not in deny is allowed")
|
|
}
|
|
|
|
// Test 5b: Bob in allow AND deny - DENIED (deny wins)
|
|
bobEvent := createTestEvent(t, bobSigner, "bob", 500)
|
|
allowed, err = policy.CheckPolicy("write", bobEvent, bobPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("FAIL: Bob should be denied (deny list overrides allow list)")
|
|
} else {
|
|
t.Log("PASS: Deny list correctly overrides allow list")
|
|
}
|
|
|
|
// Test 5c: Charlie not in allow - DENIED (even though he's author of his event)
|
|
charlieEvent := createTestEvent(t, charlieSigner, "charlie", 500)
|
|
allowed, err = policy.CheckPolicy("write", charlieEvent, charliePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("FAIL: Charlie should be denied (not in allow list)")
|
|
} else {
|
|
t.Log("PASS: Allow list correctly excludes non-listed privileged author")
|
|
}
|
|
})
|
|
|
|
// ===================================================================
|
|
// Test 6: Default Policy Application
|
|
// ===================================================================
|
|
t.Run("Default Policy Only When No Rules", func(t *testing.T) {
|
|
// Test 6a: With allow default and no rules
|
|
policyAllow := &P{
|
|
DefaultPolicy: "allow",
|
|
Rules: map[int]Rule{
|
|
// No rule for kind 600
|
|
},
|
|
}
|
|
|
|
event := createTestEvent(t, aliceSigner, "test", 600)
|
|
allowed, err := policyAllow.CheckPolicy("write", event, alicePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("FAIL: Default allow should permit when no rules")
|
|
} else {
|
|
t.Log("PASS: Default allow correctly applied when no rules")
|
|
}
|
|
|
|
// Test 6b: With deny default and no rules
|
|
policyDeny := &P{
|
|
DefaultPolicy: "deny",
|
|
Rules: map[int]Rule{
|
|
// No rule for kind 600
|
|
},
|
|
}
|
|
|
|
allowed, err = policyDeny.CheckPolicy("write", event, alicePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("FAIL: Default deny should block when no rules")
|
|
} else {
|
|
t.Log("PASS: Default deny correctly applied when no rules")
|
|
}
|
|
|
|
// Test 6c: Default does NOT apply when allow list exists
|
|
policyWithRule := &P{
|
|
DefaultPolicy: "allow", // Allow default
|
|
Rules: map[int]Rule{
|
|
700: {
|
|
WriteAllow: []string{hex.Enc(bobPubkey)}, // Only Bob
|
|
},
|
|
},
|
|
}
|
|
|
|
eventKind700 := createTestEvent(t, aliceSigner, "alice", 700)
|
|
allowed, err = policyWithRule.CheckPolicy("write", eventKind700, alicePubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("FAIL: Default allow should NOT override exclusive allow list")
|
|
} else {
|
|
t.Log("PASS: Allow list correctly overrides default policy")
|
|
}
|
|
})
|
|
} |