318 lines
9.4 KiB
Go
318 lines
9.4 KiB
Go
package policy
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.mleku.dev/mleku/nostr/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)")
|
|
}
|
|
})
|
|
}
|