Files
next.orly.dev/pkg/policy/kind_whitelist_test.go
mleku ebe0012863
Some checks failed
Go / build-and-release (push) Has been cancelled
fix auth, read/white whitelisting and rule precedence, bump to v0.29.13
Policy System Verification & Testing (Latest Updates) Authentication & Security:

Verified policy system enforces authentication for all REQ and EVENT messages when enabled

Confirmed AUTH challenges are sent immediately on connection and repeated until authentication succeeds

Validated unauthenticated requests are silently rejected regardless of other policy rules

Access Control Logic:

Confirmed privileged flag only restricts read access (REQ queries), not write operations (EVENT submissions)

Validated read_allow and privileged use OR logic: users get access if EITHER they're in the allow list OR they're a party to the event (author/p-tag)
This design allows both explicit whitelisting and privacy for involved parties

Kind Whitelisting:

Verified kind filtering properly rejects unlisted events in all scenarios:

Explicit kind.whitelist: Only listed kinds accepted, even if rules exist for other kinds

Implicit whitelist (rules only): Only kinds with defined rules accepted

Blacklist mode: Blacklisted kinds rejected, others require rules

Added comprehensive test suite (10 scenarios) covering edge cases and real-world configurations
2025-11-21 16:13:34 +00:00

318 lines
9.4 KiB
Go

package policy
import (
"testing"
"next.orly.dev/pkg/encoders/hex"
)
// TestKindWhitelistComprehensive verifies that kind whitelisting properly rejects
// unlisted kinds in all scenarios: explicit whitelist, implicit whitelist (rules), and combinations
func TestKindWhitelistComprehensive(t *testing.T) {
testSigner, testPubkey := generateTestKeypair(t)
t.Run("Explicit Whitelist - kind IN whitelist, HAS rule", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow", // Changed to allow so rules without constraints allow by default
Kind: Kinds{
Whitelist: []int{1, 3, 5}, // Explicit whitelist
},
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
3: {Description: "Rule for kind 3"},
5: {Description: "Rule for kind 5"},
},
}
event := createTestEvent(t, testSigner, "test", 1)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !allowed {
t.Error("Kind 1 should be ALLOWED (in whitelist, has rule, passes rule check)")
}
})
t.Run("Explicit Whitelist - kind IN whitelist, NO rule", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow",
Kind: Kinds{
Whitelist: []int{1, 3, 5}, // Explicit whitelist
},
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
// Kind 3 has no rule
},
}
event := createTestEvent(t, testSigner, "test", 3)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !allowed {
t.Error("Kind 3 should be ALLOWED (in whitelist, no rule, default policy is allow)")
}
})
t.Run("Explicit Whitelist - kind NOT in whitelist, HAS rule", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow",
Kind: Kinds{
Whitelist: []int{1, 3, 5}, // Explicit whitelist - kind 10 NOT included
},
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
10: {Description: "Rule for kind 10"}, // Has rule but not in whitelist!
},
}
event := createTestEvent(t, testSigner, "test", 10)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 10 should be REJECTED (NOT in whitelist, even though it has a rule)")
}
})
t.Run("Explicit Whitelist - kind NOT in whitelist, NO rule", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow",
Kind: Kinds{
Whitelist: []int{1, 3, 5}, // Explicit whitelist
},
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
},
}
event := createTestEvent(t, testSigner, "test", 99)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 99 should be REJECTED (NOT in whitelist)")
}
})
t.Run("Implicit Whitelist (rules) - kind HAS rule", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow", // Changed to allow so rules without constraints allow by default
// No explicit whitelist
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
3: {Description: "Rule for kind 3"},
},
}
event := createTestEvent(t, testSigner, "test", 1)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !allowed {
t.Error("Kind 1 should be ALLOWED (has rule, implicit whitelist)")
}
})
t.Run("Implicit Whitelist (rules) - kind NO rule", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow",
// No explicit whitelist
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
3: {Description: "Rule for kind 3"},
},
}
event := createTestEvent(t, testSigner, "test", 99)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 99 should be REJECTED (no rule, implicit whitelist mode)")
}
})
t.Run("Explicit Whitelist + Global Rule - kind NOT in whitelist", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow",
Kind: Kinds{
Whitelist: []int{1, 3, 5}, // Explicit whitelist
},
Global: Rule{
Description: "Global rule applies to all kinds",
WriteAllow: []string{hex.Enc(testPubkey)},
},
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
},
}
// Even with global rule, kind not in whitelist should be rejected
event := createTestEvent(t, testSigner, "test", 99)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 99 should be REJECTED (NOT in whitelist, even with global rule)")
}
})
t.Run("Blacklist + Rules - kind in blacklist but has rule", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow",
Kind: Kinds{
Blacklist: []int{10, 20}, // Blacklist
},
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
10: {Description: "Rule for kind 10"}, // Has rule but blacklisted!
},
}
// Kind 10 is blacklisted, should be rejected
event := createTestEvent(t, testSigner, "test", 10)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 10 should be REJECTED (in blacklist)")
}
})
t.Run("Blacklist + Rules - kind NOT in blacklist but NO rule (implicit whitelist)", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow",
Kind: Kinds{
Blacklist: []int{10, 20}, // Blacklist
},
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
3: {Description: "Rule for kind 3"},
},
}
// Kind 99 is not blacklisted but has no rule
// With blacklist present + rules, implicit whitelist applies
event := createTestEvent(t, testSigner, "test", 99)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 99 should be REJECTED (not in blacklist but no rule, implicit whitelist)")
}
})
t.Run("Whitelist takes precedence over Blacklist", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow",
Kind: Kinds{
Whitelist: []int{1, 3, 5, 10}, // Whitelist includes 10
Blacklist: []int{10, 20}, // Blacklist also includes 10
},
Rules: map[int]Rule{
1: {Description: "Rule for kind 1"},
10: {Description: "Rule for kind 10"},
},
}
// Kind 10 is in BOTH whitelist and blacklist - whitelist should win
event := createTestEvent(t, testSigner, "test", 10)
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !allowed {
t.Error("Kind 10 should be ALLOWED (whitelist takes precedence over blacklist)")
}
})
}
// TestKindWhitelistRealWorld tests real-world scenarios from the documentation
func TestKindWhitelistRealWorld(t *testing.T) {
testSigner, testPubkey := generateTestKeypair(t)
_, otherPubkey := generateTestKeypair(t)
t.Run("Real World: Only allow kinds 1, 3, 30023", func(t *testing.T) {
policy := &P{
DefaultPolicy: "allow", // Allow by default for kinds in whitelist
Kind: Kinds{
Whitelist: []int{1, 3, 30023},
},
Rules: map[int]Rule{
1: {
Description: "Text notes",
// No WriteAllow = anyone authenticated can write
},
3: {
Description: "Contact lists",
// No WriteAllow = anyone authenticated can write
},
30023: {
Description: "Long-form content",
WriteAllow: []string{hex.Enc(testPubkey)}, // Only specific user can write
},
},
}
// Test kind 1 (allowed)
event1 := createTestEvent(t, testSigner, "Hello world", 1)
allowed, err := policy.CheckPolicy("write", event1, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !allowed {
t.Error("Kind 1 should be ALLOWED")
}
// Test kind 4 (NOT in whitelist, should be rejected)
event4 := createTestEvent(t, testSigner, "DM", 4)
allowed, err = policy.CheckPolicy("write", event4, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 4 should be REJECTED (not in whitelist)")
}
// Test kind 30023 by authorized user (allowed)
event30023Auth := createTestEvent(t, testSigner, "Article", 30023)
allowed, err = policy.CheckPolicy("write", event30023Auth, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !allowed {
t.Error("Kind 30023 should be ALLOWED for authorized user")
}
// Test kind 30023 by unauthorized user (should fail rule check)
event30023Unauth := createTestEvent(t, testSigner, "Article", 30023)
allowed, err = policy.CheckPolicy("write", event30023Unauth, otherPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 30023 should be REJECTED for unauthorized user")
}
// Test kind 9735 (NOT in whitelist, should be rejected even with valid signature)
event9735 := createTestEvent(t, testSigner, "Zap", 9735)
allowed, err = policy.CheckPolicy("write", event9735, testPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Kind 9735 should be REJECTED (not in whitelist)")
}
})
}