Some checks failed
Go / build-and-release (push) Has been cancelled
Introduce `read_allow_permissive` and `write_allow_permissive` flags in the global rule to override kind whitelists for read or write operations. These flags allow more flexible policy configurations while maintaining blacklist enforcement and preventing conflicting settings. Updated tests and documentation for clarity.
2296 lines
65 KiB
Go
2296 lines
65 KiB
Go
package policy
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"lol.mleku.dev/chk"
|
|
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
|
|
"git.mleku.dev/mleku/nostr/encoders/event"
|
|
"git.mleku.dev/mleku/nostr/encoders/hex"
|
|
"git.mleku.dev/mleku/nostr/encoders/tag"
|
|
)
|
|
|
|
// Helper function to create int64 pointer
|
|
func int64Ptr(i int64) *int64 {
|
|
return &i
|
|
}
|
|
|
|
// Helper function to generate a keypair for testing
|
|
func generateTestKeypair(t *testing.T) (signer *p8k.Signer, pubkey []byte) {
|
|
signer = p8k.MustNew()
|
|
if err := signer.Generate(); chk.E(err) {
|
|
t.Fatalf("Failed to generate test keypair: %v", err)
|
|
}
|
|
pubkey = signer.Pub()
|
|
return
|
|
}
|
|
|
|
// Helper function to generate a keypair for benchmarks
|
|
func generateTestKeypairB(b *testing.B) (signer *p8k.Signer, pubkey []byte) {
|
|
signer = p8k.MustNew()
|
|
if err := signer.Generate(); chk.E(err) {
|
|
b.Fatalf("Failed to generate test keypair: %v", err)
|
|
}
|
|
pubkey = signer.Pub()
|
|
return
|
|
}
|
|
|
|
// Helper function to create a real test event with proper signing
|
|
func createTestEvent(t *testing.T, signer *p8k.Signer, content string, kind uint16) *event.E {
|
|
ev := event.New()
|
|
ev.CreatedAt = time.Now().Unix()
|
|
ev.Kind = kind
|
|
ev.Content = []byte(content)
|
|
ev.Tags = tag.NewS()
|
|
|
|
// Sign the event properly
|
|
if err := ev.Sign(signer); chk.E(err) {
|
|
t.Fatalf("Failed to sign test event: %v", err)
|
|
}
|
|
|
|
return ev
|
|
}
|
|
|
|
// Helper function to create a test event with a specific pubkey (for unauthorized tests)
|
|
func createTestEventWithPubkey(t *testing.T, signer *p8k.Signer, content string, kind uint16) *event.E {
|
|
ev := event.New()
|
|
ev.CreatedAt = time.Now().Unix()
|
|
ev.Kind = kind
|
|
ev.Content = []byte(content)
|
|
ev.Tags = tag.NewS()
|
|
|
|
// Sign the event properly
|
|
if err := ev.Sign(signer); chk.E(err) {
|
|
t.Fatalf("Failed to sign test event: %v", err)
|
|
}
|
|
|
|
return ev
|
|
}
|
|
|
|
// Helper function to add p tag with hex-encoded pubkey to event
|
|
func addPTag(ev *event.E, pubkey []byte) {
|
|
pTag := tag.NewFromAny("p", hex.Enc(pubkey))
|
|
ev.Tags.Append(pTag)
|
|
}
|
|
|
|
// Helper function to add other tags to event
|
|
func addTag(ev *event.E, key, value string) {
|
|
tagItem := tag.NewFromAny(key, value)
|
|
ev.Tags.Append(tagItem)
|
|
}
|
|
|
|
func TestNew(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
policyJSON []byte
|
|
expectError bool
|
|
expectRules int
|
|
}{
|
|
{
|
|
name: "empty JSON",
|
|
policyJSON: []byte("{}"),
|
|
expectError: false,
|
|
expectRules: 0,
|
|
},
|
|
{
|
|
name: "valid policy JSON",
|
|
policyJSON: []byte(`{"kind":{"whitelist":[1,3,5]},"rules":{"1":{"description":"test"}}}`),
|
|
expectError: false,
|
|
expectRules: 1,
|
|
},
|
|
{
|
|
name: "invalid JSON",
|
|
policyJSON: []byte(`{"invalid": json}`),
|
|
expectError: true,
|
|
expectRules: 0,
|
|
},
|
|
{
|
|
name: "nil JSON",
|
|
policyJSON: nil,
|
|
expectError: false,
|
|
expectRules: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
policy, err := New(tt.policyJSON)
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Errorf("Expected error but got none")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
return
|
|
}
|
|
if policy == nil {
|
|
t.Errorf("Expected policy but got nil")
|
|
return
|
|
}
|
|
if len(policy.rules) != tt.expectRules {
|
|
t.Errorf("Expected %d rules, got %d", tt.expectRules, len(policy.rules))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckKindsPolicy(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
policy *P
|
|
access string // "read" or "write"
|
|
kind uint16
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "no whitelist or blacklist - allow (no rules at all)",
|
|
policy: &P{
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{}, // No rules defined
|
|
},
|
|
access: "write",
|
|
kind: 1,
|
|
expected: true, // Should be allowed (no rules = allow all kinds)
|
|
},
|
|
{
|
|
name: "no whitelist or blacklist - deny (has other rules)",
|
|
policy: &P{
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{
|
|
2: {Description: "Rule for kind 2"},
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 1,
|
|
expected: false, // Should be denied (implicit whitelist, no rule for kind 1)
|
|
},
|
|
{
|
|
name: "no whitelist or blacklist - allow (has rule)",
|
|
policy: &P{
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{
|
|
1: {Description: "Rule for kind 1"},
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 1,
|
|
expected: true, // Should be allowed (has rule)
|
|
},
|
|
{
|
|
name: "no whitelist or blacklist - allow (has global rule)",
|
|
policy: &P{
|
|
Kind: Kinds{},
|
|
Global: Rule{
|
|
WriteAllow: []string{"test"}, // Global rule exists
|
|
},
|
|
rules: map[int]Rule{}, // No specific rules
|
|
},
|
|
access: "write",
|
|
kind: 1,
|
|
expected: true, // Should be allowed (global rule exists)
|
|
},
|
|
{
|
|
name: "whitelist - kind allowed",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 3, 5},
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 1,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "whitelist - kind not allowed",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 3, 5},
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 2,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "blacklist - kind not blacklisted (no rule)",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Blacklist: []int{2, 4, 6},
|
|
},
|
|
rules: map[int]Rule{
|
|
3: {Description: "Rule for kind 3"}, // Has at least one rule
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 1,
|
|
expected: false, // Should be denied (not blacklisted but no rule for kind 1)
|
|
},
|
|
{
|
|
name: "blacklist - kind not blacklisted (has rule)",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Blacklist: []int{2, 4, 6},
|
|
},
|
|
rules: map[int]Rule{
|
|
1: {Description: "Rule for kind 1"},
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 1,
|
|
expected: true, // Should be allowed (not blacklisted and has rule)
|
|
},
|
|
{
|
|
name: "blacklist - kind blacklisted",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Blacklist: []int{2, 4, 6},
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 2,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "whitelist overrides blacklist",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 3, 5},
|
|
Blacklist: []int{1, 2, 3},
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 1,
|
|
expected: true,
|
|
},
|
|
// Tests for new permissive flags
|
|
{
|
|
name: "read_allow_permissive - allows read for non-whitelisted kind",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 3, 5},
|
|
},
|
|
Global: Rule{
|
|
ReadAllowPermissive: true,
|
|
},
|
|
},
|
|
access: "read",
|
|
kind: 2,
|
|
expected: true, // Should be allowed (read permissive overrides whitelist)
|
|
},
|
|
{
|
|
name: "read_allow_permissive - write still blocked for non-whitelisted kind",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 3, 5},
|
|
},
|
|
Global: Rule{
|
|
ReadAllowPermissive: true,
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 2,
|
|
expected: false, // Should be denied (only read is permissive)
|
|
},
|
|
{
|
|
name: "write_allow_permissive - allows write for non-whitelisted kind",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 3, 5},
|
|
},
|
|
Global: Rule{
|
|
WriteAllowPermissive: true,
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 2,
|
|
expected: true, // Should be allowed (write permissive overrides whitelist)
|
|
},
|
|
{
|
|
name: "write_allow_permissive - read still blocked for non-whitelisted kind",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 3, 5},
|
|
},
|
|
Global: Rule{
|
|
WriteAllowPermissive: true,
|
|
},
|
|
},
|
|
access: "read",
|
|
kind: 2,
|
|
expected: false, // Should be denied (only write is permissive)
|
|
},
|
|
{
|
|
name: "blacklist - permissive flags do NOT override blacklist",
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Blacklist: []int{2, 4, 6},
|
|
},
|
|
Global: Rule{
|
|
ReadAllowPermissive: true,
|
|
WriteAllowPermissive: true,
|
|
},
|
|
},
|
|
access: "write",
|
|
kind: 2,
|
|
expected: false, // Should be denied (blacklist always applies)
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.policy.checkKindsPolicy(tt.access, tt.kind)
|
|
if result != tt.expected {
|
|
t.Errorf("Expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckRulePolicy(t *testing.T) {
|
|
// Generate real keypairs for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
_, pTagPubkey := generateTestKeypair(t)
|
|
_, unauthorizedPubkey := generateTestKeypair(t)
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
// Add p tag with hex-encoded pubkey
|
|
addPTag(testEvent, pTagPubkey)
|
|
addTag(testEvent, "expiration", "1234567890")
|
|
|
|
tests := []struct {
|
|
name string
|
|
access string
|
|
event *event.E
|
|
rule Rule
|
|
loggedInPubkey []byte
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "write access - no restrictions",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "no restrictions",
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "write access - pubkey allowed",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "pubkey allowed",
|
|
WriteAllow: []string{hex.Enc(testEvent.Pubkey)},
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "write access - pubkey not allowed",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "pubkey not allowed",
|
|
WriteAllow: []string{hex.Enc(pTagPubkey)}, // Different pubkey
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "size limit - within limit",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "size limit",
|
|
SizeLimit: int64Ptr(10000),
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "size limit - exceeds limit",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "size limit exceeded",
|
|
SizeLimit: int64Ptr(10),
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "content limit - within limit",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "content limit",
|
|
ContentLimit: int64Ptr(1000),
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "content limit - exceeds limit",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "content limit exceeded",
|
|
ContentLimit: int64Ptr(5),
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "required tags - has required tag",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "required tags",
|
|
MustHaveTags: []string{"p"},
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "required tags - missing required tag",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "required tags missing",
|
|
MustHaveTags: []string{"e"},
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "privileged write - event authored by logged in user (privileged doesn't affect write)",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "privileged event",
|
|
Privileged: true,
|
|
},
|
|
loggedInPubkey: testEvent.Pubkey,
|
|
expected: true, // Privileged doesn't restrict write, uses default (allow)
|
|
},
|
|
{
|
|
name: "privileged write - event contains logged in user in p tag (privileged doesn't affect write)",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "privileged event with p tag",
|
|
Privileged: true,
|
|
},
|
|
loggedInPubkey: pTagPubkey,
|
|
expected: true, // Privileged doesn't restrict write, uses default (allow)
|
|
},
|
|
{
|
|
name: "privileged write - not authenticated (privileged doesn't affect write)",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "privileged event not authenticated",
|
|
Privileged: true,
|
|
},
|
|
loggedInPubkey: nil,
|
|
expected: true, // Privileged doesn't restrict write, uses default (allow)
|
|
},
|
|
{
|
|
name: "privileged write - authenticated but not authorized (privileged doesn't affect write)",
|
|
access: "write",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "privileged event unauthorized user",
|
|
Privileged: true,
|
|
},
|
|
loggedInPubkey: unauthorizedPubkey,
|
|
expected: true, // Privileged doesn't restrict write, uses default (allow)
|
|
},
|
|
{
|
|
name: "privileged read - event authored by logged in user",
|
|
access: "read",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "privileged event read access",
|
|
Privileged: true,
|
|
},
|
|
loggedInPubkey: testEvent.Pubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "privileged read - event contains logged in user in p tag",
|
|
access: "read",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "privileged event read access with p tag",
|
|
Privileged: true,
|
|
},
|
|
loggedInPubkey: pTagPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "privileged read - not authenticated",
|
|
access: "read",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "privileged event read access not authenticated",
|
|
Privileged: true,
|
|
},
|
|
loggedInPubkey: nil,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "privileged read - authenticated but not authorized (different pubkey, not in p tags)",
|
|
access: "read",
|
|
event: testEvent,
|
|
rule: Rule{
|
|
Description: "privileged event read access unauthorized user",
|
|
Privileged: true,
|
|
},
|
|
loggedInPubkey: unauthorizedPubkey,
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
policy := &P{}
|
|
result, err := policy.checkRulePolicy(tt.access, tt.event, tt.rule, tt.loggedInPubkey)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
return
|
|
}
|
|
if result != tt.expected {
|
|
t.Errorf("Expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckPolicy(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
tests := []struct {
|
|
name string
|
|
access string
|
|
event *event.E
|
|
policy *P
|
|
loggedInPubkey []byte
|
|
ipAddress string
|
|
expected bool
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "no policy rules - allow",
|
|
access: "write",
|
|
event: testEvent,
|
|
policy: &P{
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{},
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
ipAddress: "127.0.0.1",
|
|
expected: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "kinds policy blocks - deny",
|
|
access: "write",
|
|
event: testEvent,
|
|
policy: &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{3, 5},
|
|
},
|
|
rules: map[int]Rule{},
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
ipAddress: "127.0.0.1",
|
|
expected: false,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "rule blocks - deny",
|
|
access: "write",
|
|
event: testEvent,
|
|
policy: &P{
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{
|
|
1: {
|
|
Description: "block test",
|
|
WriteDeny: []string{hex.Enc(testEvent.Pubkey)},
|
|
},
|
|
},
|
|
},
|
|
loggedInPubkey: eventPubkey,
|
|
ipAddress: "127.0.0.1",
|
|
expected: false,
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := tt.policy.CheckPolicy(tt.access, tt.event, tt.loggedInPubkey, tt.ipAddress)
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Errorf("Expected error but got none")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
return
|
|
}
|
|
if result != tt.expected {
|
|
t.Errorf("Expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadFromFile(t *testing.T) {
|
|
// Create temporary directory
|
|
tempDir := t.TempDir()
|
|
configPath := filepath.Join(tempDir, "policy.json")
|
|
|
|
tests := []struct {
|
|
name string
|
|
configData string
|
|
expectError bool
|
|
expectRules int
|
|
}{
|
|
{
|
|
name: "valid policy file",
|
|
configData: `{"kind":{"whitelist":[1,3,5]},"rules":{"1":{"description":"test"}}}`,
|
|
expectError: false,
|
|
expectRules: 1,
|
|
},
|
|
{
|
|
name: "empty policy file",
|
|
configData: `{}`,
|
|
expectError: false,
|
|
expectRules: 0,
|
|
},
|
|
{
|
|
name: "invalid JSON",
|
|
configData: `{"invalid": json}`,
|
|
expectError: true,
|
|
expectRules: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Write test config file
|
|
if tt.configData != "" {
|
|
err := os.WriteFile(configPath, []byte(tt.configData), 0644)
|
|
if err != nil {
|
|
t.Fatalf("Failed to write test config file: %v", err)
|
|
}
|
|
}
|
|
|
|
policy := &P{}
|
|
err := policy.LoadFromFile(configPath)
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Errorf("Expected error but got none")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
return
|
|
}
|
|
if len(policy.rules) != tt.expectRules {
|
|
t.Errorf("Expected %d rules, got %d", tt.expectRules, len(policy.rules))
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test file not found
|
|
t.Run("file not found", func(t *testing.T) {
|
|
policy := &P{}
|
|
err := policy.LoadFromFile("/nonexistent/policy.json")
|
|
if err == nil {
|
|
t.Errorf("Expected error for nonexistent file but got none")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPolicyEventSerialization(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Create policy event
|
|
policyEvent := &PolicyEvent{
|
|
E: testEvent,
|
|
LoggedInPubkey: hex.Enc(eventPubkey),
|
|
IPAddress: "127.0.0.1",
|
|
}
|
|
|
|
// Test JSON serialization
|
|
jsonData, err := json.Marshal(policyEvent)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal policy event: %v", err)
|
|
}
|
|
|
|
// Verify the JSON contains expected fields
|
|
jsonStr := string(jsonData)
|
|
t.Logf("Generated JSON: %s", jsonStr)
|
|
|
|
if !strings.Contains(jsonStr, hex.Enc(eventPubkey)) {
|
|
t.Error("JSON should contain logged_in_pubkey field")
|
|
}
|
|
if !strings.Contains(jsonStr, "127.0.0.1") {
|
|
t.Error("JSON should contain ip_address field")
|
|
}
|
|
if !strings.Contains(jsonStr, hex.Enc(testEvent.ID)) {
|
|
t.Error("JSON should contain event id field (hex encoded)")
|
|
}
|
|
|
|
// Test with nil event
|
|
nilPolicyEvent := &PolicyEvent{
|
|
E: nil,
|
|
LoggedInPubkey: "test-logged-in-pubkey",
|
|
IPAddress: "127.0.0.1",
|
|
}
|
|
|
|
jsonData2, err := json.Marshal(nilPolicyEvent)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal nil policy event: %v", err)
|
|
}
|
|
|
|
jsonStr2 := string(jsonData2)
|
|
if !strings.Contains(jsonStr2, "test-logged-in-pubkey") {
|
|
t.Error("JSON should contain logged_in_pubkey field even with nil event")
|
|
}
|
|
if !strings.Contains(jsonStr2, "127.0.0.1") {
|
|
t.Error("JSON should contain ip_address field even with nil event")
|
|
}
|
|
}
|
|
|
|
func TestPolicyResponseSerialization(t *testing.T) {
|
|
// Test JSON serialization
|
|
response := &PolicyResponse{
|
|
ID: "test-id",
|
|
Action: "accept",
|
|
Msg: "test message",
|
|
}
|
|
|
|
jsonData, err := json.Marshal(response)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal policy response: %v", err)
|
|
}
|
|
|
|
// Test JSON deserialization
|
|
var deserializedResponse PolicyResponse
|
|
err = json.Unmarshal(jsonData, &deserializedResponse)
|
|
if err != nil {
|
|
t.Fatalf("Failed to unmarshal policy response: %v", err)
|
|
}
|
|
|
|
// Verify fields
|
|
if deserializedResponse.ID != response.ID {
|
|
t.Errorf("Expected ID %s, got %s", response.ID, deserializedResponse.ID)
|
|
}
|
|
if deserializedResponse.Action != response.Action {
|
|
t.Errorf("Expected Action %s, got %s", response.Action, deserializedResponse.Action)
|
|
}
|
|
if deserializedResponse.Msg != response.Msg {
|
|
t.Errorf("Expected Msg %s, got %s", response.Msg, deserializedResponse.Msg)
|
|
}
|
|
}
|
|
|
|
func TestNewWithManager(t *testing.T) {
|
|
ctx := context.Background()
|
|
appName := "test-app"
|
|
|
|
// Test with disabled policy (doesn't require policy.json file)
|
|
t.Run("disabled policy", func(t *testing.T) {
|
|
enabled := false
|
|
policy := NewWithManager(ctx, appName, enabled)
|
|
|
|
if policy == nil {
|
|
t.Fatal("Expected policy but got nil")
|
|
}
|
|
|
|
if policy.manager == nil {
|
|
t.Fatal("Expected policy manager but got nil")
|
|
}
|
|
|
|
if policy.manager.IsEnabled() {
|
|
t.Error("Expected policy manager to be disabled")
|
|
}
|
|
|
|
if policy.manager.IsRunning() {
|
|
t.Error("Expected policy manager to not be running")
|
|
}
|
|
|
|
// Verify default policy was set
|
|
if policy.DefaultPolicy != "allow" {
|
|
t.Errorf("Expected default_policy='allow', got '%s'", policy.DefaultPolicy)
|
|
}
|
|
|
|
// Clean up
|
|
policy.manager.Shutdown()
|
|
})
|
|
|
|
// Test with enabled policy and valid config file
|
|
t.Run("enabled policy with valid config", func(t *testing.T) {
|
|
// Create a temporary config directory with a valid policy.json
|
|
tmpDir := t.TempDir()
|
|
configDir := filepath.Join(tmpDir, "test-policy-enabled")
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
t.Fatalf("Failed to create config dir: %v", err)
|
|
}
|
|
|
|
// Write a minimal valid policy.json
|
|
policyJSON := `{
|
|
"default_policy": "allow",
|
|
"kind": {
|
|
"whitelist": [1, 3, 4]
|
|
},
|
|
"rules": {
|
|
"1": {
|
|
"description": "Text notes"
|
|
}
|
|
}
|
|
}`
|
|
policyPath := filepath.Join(configDir, "policy.json")
|
|
if err := os.WriteFile(policyPath, []byte(policyJSON), 0644); err != nil {
|
|
t.Fatalf("Failed to write policy.json: %v", err)
|
|
}
|
|
|
|
// Create policy manager manually to use custom config path
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
manager := &PolicyManager{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
configDir: configDir,
|
|
scriptPath: filepath.Join(configDir, "policy.sh"),
|
|
enabled: true,
|
|
runners: make(map[string]*ScriptRunner),
|
|
}
|
|
|
|
policy := &P{
|
|
DefaultPolicy: "allow",
|
|
manager: manager,
|
|
}
|
|
|
|
// Load policy from our test file
|
|
if err := policy.LoadFromFile(policyPath); err != nil {
|
|
t.Fatalf("Failed to load policy: %v", err)
|
|
}
|
|
|
|
if policy.manager == nil {
|
|
t.Fatal("Expected policy manager but got nil")
|
|
}
|
|
|
|
if !policy.manager.IsEnabled() {
|
|
t.Error("Expected policy manager to be enabled")
|
|
}
|
|
|
|
// Verify policy was loaded correctly
|
|
if len(policy.Kind.Whitelist) != 3 {
|
|
t.Errorf("Expected 3 whitelisted kinds, got %d", len(policy.Kind.Whitelist))
|
|
}
|
|
|
|
if policy.DefaultPolicy != "allow" {
|
|
t.Errorf("Expected default_policy='allow', got '%s'", policy.DefaultPolicy)
|
|
}
|
|
|
|
// Clean up
|
|
policy.manager.Shutdown()
|
|
})
|
|
}
|
|
|
|
func TestPolicyManagerLifecycle(t *testing.T) {
|
|
// Test basic manager initialization without script execution
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
manager := &PolicyManager{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
configDir: "/tmp",
|
|
scriptPath: "/tmp/policy.sh",
|
|
enabled: true,
|
|
runners: make(map[string]*ScriptRunner),
|
|
}
|
|
|
|
// Test manager state
|
|
if !manager.IsEnabled() {
|
|
t.Error("Expected policy manager to be enabled")
|
|
}
|
|
|
|
if manager.IsRunning() {
|
|
t.Error("Expected policy manager to not be running initially")
|
|
}
|
|
|
|
// Test getting or creating a runner for a non-existent script
|
|
runner := manager.getOrCreateRunner("/tmp/policy.sh")
|
|
if runner == nil {
|
|
t.Fatal("Expected runner to be created")
|
|
}
|
|
|
|
// Test starting with non-existent script (should fail gracefully)
|
|
err := runner.Start()
|
|
if err == nil {
|
|
t.Error("Expected error when starting script with non-existent file")
|
|
}
|
|
|
|
// Test stopping when not running (should fail gracefully)
|
|
err = runner.Stop()
|
|
if err == nil {
|
|
t.Error("Expected error when stopping script that's not running")
|
|
}
|
|
}
|
|
|
|
func TestPolicyManagerProcessEvent(t *testing.T) {
|
|
// Test processing event when runner is not running (should fail gracefully)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
manager := &PolicyManager{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
configDir: "/tmp",
|
|
scriptPath: "/tmp/policy.sh",
|
|
enabled: true,
|
|
runners: make(map[string]*ScriptRunner),
|
|
}
|
|
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Create policy event
|
|
policyEvent := &PolicyEvent{
|
|
E: testEvent,
|
|
LoggedInPubkey: hex.Enc(eventPubkey),
|
|
IPAddress: "127.0.0.1",
|
|
}
|
|
|
|
// Get or create a runner
|
|
runner := manager.getOrCreateRunner("/tmp/policy.sh")
|
|
|
|
// Process event when not running (should fail gracefully)
|
|
_, err := runner.ProcessEvent(policyEvent)
|
|
if err == nil {
|
|
t.Error("Expected error when processing event with non-running script")
|
|
}
|
|
}
|
|
|
|
func TestEdgeCasesEmptyPolicy(t *testing.T) {
|
|
policy := &P{}
|
|
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Should allow all events when policy is empty
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed with empty policy")
|
|
}
|
|
}
|
|
|
|
func TestEdgeCasesNilEvent(t *testing.T) {
|
|
policy := &P{}
|
|
|
|
// Should handle nil event gracefully
|
|
allowed, err := policy.CheckPolicy("write", nil, []byte("test-pubkey"), "127.0.0.1")
|
|
if err == nil {
|
|
t.Error("Expected error when event is nil")
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be blocked when nil")
|
|
}
|
|
|
|
// Verify the error message
|
|
if err != nil && !strings.Contains(err.Error(), "event cannot be nil") {
|
|
t.Errorf("Expected error message to contain 'event cannot be nil', got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEdgeCasesLargeEvent(t *testing.T) {
|
|
// Create large content
|
|
largeContent := strings.Repeat("a", 100000) // 100KB content
|
|
|
|
policy := &P{
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{
|
|
1: {
|
|
Description: "size limit test",
|
|
SizeLimit: int64Ptr(50000), // 50KB limit
|
|
ContentLimit: int64Ptr(10000), // 10KB content limit
|
|
},
|
|
},
|
|
}
|
|
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Create real test event with large content
|
|
testEvent := createTestEvent(t, eventSigner, largeContent, 1)
|
|
|
|
// Should block large event
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected large event to be blocked")
|
|
}
|
|
}
|
|
|
|
func TestEdgeCasesWhitelistBlacklistConflict(t *testing.T) {
|
|
policy := &P{
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 3, 5},
|
|
Blacklist: []int{1, 2, 3}, // Overlap with whitelist
|
|
},
|
|
}
|
|
|
|
// Test kind in both whitelist and blacklist - whitelist should win
|
|
allowed := policy.checkKindsPolicy("write", 1)
|
|
if !allowed {
|
|
t.Error("Expected whitelist to override blacklist")
|
|
}
|
|
|
|
// Test kind in blacklist but not whitelist
|
|
allowed = policy.checkKindsPolicy("write", 2)
|
|
if allowed {
|
|
t.Error("Expected kind in blacklist but not whitelist to be blocked")
|
|
}
|
|
|
|
// Test kind in whitelist but not blacklist
|
|
allowed = policy.checkKindsPolicy("write", 5)
|
|
if !allowed {
|
|
t.Error("Expected kind in whitelist to be allowed")
|
|
}
|
|
}
|
|
|
|
func TestEdgeCasesManagerWithInvalidScript(t *testing.T) {
|
|
// Create temporary directory
|
|
tempDir := t.TempDir()
|
|
scriptPath := filepath.Join(tempDir, "policy.sh")
|
|
|
|
// Create invalid script (not executable, wrong shebang, etc.)
|
|
scriptContent := `invalid script content`
|
|
err := os.WriteFile(scriptPath, []byte(scriptContent), 0644) // Not executable
|
|
if err != nil {
|
|
t.Fatalf("Failed to create invalid script: %v", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
manager := &PolicyManager{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
configDir: tempDir,
|
|
scriptPath: scriptPath,
|
|
enabled: true,
|
|
runners: make(map[string]*ScriptRunner),
|
|
}
|
|
|
|
// Get runner and try to start with invalid script
|
|
runner := manager.getOrCreateRunner(scriptPath)
|
|
err = runner.Start()
|
|
if err == nil {
|
|
t.Error("Expected error when starting invalid script")
|
|
}
|
|
}
|
|
|
|
func TestEdgeCasesManagerDoubleStart(t *testing.T) {
|
|
// Test double start without actually starting (simpler test)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
manager := &PolicyManager{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
configDir: "/tmp",
|
|
scriptPath: "/tmp/policy.sh",
|
|
enabled: true,
|
|
runners: make(map[string]*ScriptRunner),
|
|
}
|
|
|
|
// Get runner
|
|
runner := manager.getOrCreateRunner("/tmp/policy.sh")
|
|
|
|
// Try to start with non-existent script - should fail
|
|
err := runner.Start()
|
|
if err == nil {
|
|
t.Error("Expected error when starting script with non-existent file")
|
|
}
|
|
|
|
// Try to start again - should still fail
|
|
err = runner.Start()
|
|
if err == nil {
|
|
t.Error("Expected error when starting script twice")
|
|
}
|
|
}
|
|
|
|
func TestCheckGlobalRulePolicy(t *testing.T) {
|
|
// Generate real keypairs for testing
|
|
eventSigner, _ := generateTestKeypair(t)
|
|
_, loggedInPubkey := generateTestKeypair(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
globalRule Rule
|
|
event *event.E
|
|
loggedInPubkey []byte
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "global rule with write allow - submitter allowed",
|
|
globalRule: Rule{
|
|
WriteAllow: []string{hex.Enc(loggedInPubkey)}, // Allow the submitter
|
|
},
|
|
event: createTestEvent(t, eventSigner, "test content", 1),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "global rule with write deny - submitter denied",
|
|
globalRule: Rule{
|
|
WriteDeny: []string{hex.Enc(loggedInPubkey)}, // Deny the submitter
|
|
},
|
|
event: createTestEvent(t, eventSigner, "test content", 1),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "global rule with size limit - event too large",
|
|
globalRule: Rule{
|
|
SizeLimit: func() *int64 { v := int64(10); return &v }(),
|
|
},
|
|
event: createTestEvent(t, eventSigner, "this is a very long content that exceeds the size limit", 1),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "global rule with max age of event - event too old",
|
|
globalRule: Rule{
|
|
MaxAgeOfEvent: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
|
},
|
|
event: func() *event.E {
|
|
ev := createTestEvent(t, eventSigner, "test content", 1)
|
|
ev.CreatedAt = time.Now().Unix() - 7200 // 2 hours ago
|
|
return ev
|
|
}(),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "global rule with max age event in future - event too far in future",
|
|
globalRule: Rule{
|
|
MaxAgeEventInFuture: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
|
},
|
|
event: func() *event.E {
|
|
ev := createTestEvent(t, eventSigner, "test content", 1)
|
|
ev.CreatedAt = time.Now().Unix() + 7200 // 2 hours in future
|
|
return ev
|
|
}(),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
policy := &P{
|
|
Global: tt.globalRule,
|
|
}
|
|
|
|
result := policy.checkGlobalRulePolicy("write", tt.event, tt.loggedInPubkey)
|
|
if result != tt.expected {
|
|
t.Errorf("Expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckPolicyWithGlobalRule(t *testing.T) {
|
|
// Generate real keypairs for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
_, loggedInPubkey := generateTestKeypair(t)
|
|
|
|
// Test that global rule is applied first
|
|
policy := &P{
|
|
Global: Rule{
|
|
WriteDeny: []string{hex.Enc(eventPubkey)}, // Deny event pubkey globally
|
|
},
|
|
Kind: Kinds{
|
|
Whitelist: []int{1}, // Allow kind 1
|
|
},
|
|
rules: map[int]Rule{
|
|
1: {
|
|
WriteAllow: []string{hex.Enc(eventPubkey)}, // Allow event pubkey for kind 1
|
|
},
|
|
},
|
|
}
|
|
|
|
event := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Global rule should deny this event even though kind-specific rule would allow it
|
|
allowed, err := policy.CheckPolicy("write", event, loggedInPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatalf("CheckPolicy failed: %v", err)
|
|
}
|
|
|
|
if allowed {
|
|
t.Error("Expected event to be denied by global rule, but it was allowed")
|
|
}
|
|
}
|
|
|
|
func TestMaxAgeChecks(t *testing.T) {
|
|
// Generate real keypairs for testing
|
|
eventSigner, _ := generateTestKeypair(t)
|
|
_, loggedInPubkey := generateTestKeypair(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
rule Rule
|
|
event *event.E
|
|
loggedInPubkey []byte
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "max age of event - event within allowed age",
|
|
rule: Rule{
|
|
MaxAgeOfEvent: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
|
},
|
|
event: func() *event.E {
|
|
ev := createTestEvent(t, eventSigner, "test content", 1)
|
|
ev.CreatedAt = time.Now().Unix() - 1800 // 30 minutes ago
|
|
return ev
|
|
}(),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "max age of event - event too old",
|
|
rule: Rule{
|
|
MaxAgeOfEvent: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
|
},
|
|
event: func() *event.E {
|
|
ev := createTestEvent(t, eventSigner, "test content", 1)
|
|
ev.CreatedAt = time.Now().Unix() - 7200 // 2 hours ago
|
|
return ev
|
|
}(),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "max age event in future - event within allowed future time",
|
|
rule: Rule{
|
|
MaxAgeEventInFuture: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
|
},
|
|
event: func() *event.E {
|
|
ev := createTestEvent(t, eventSigner, "test content", 1)
|
|
ev.CreatedAt = time.Now().Unix() + 1800 // 30 minutes in future
|
|
return ev
|
|
}(),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "max age event in future - event too far in future",
|
|
rule: Rule{
|
|
MaxAgeEventInFuture: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
|
},
|
|
event: func() *event.E {
|
|
ev := createTestEvent(t, eventSigner, "test content", 1)
|
|
ev.CreatedAt = time.Now().Unix() + 7200 // 2 hours in future
|
|
return ev
|
|
}(),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "both age checks - event within both limits",
|
|
rule: Rule{
|
|
MaxAgeOfEvent: func() *int64 { v := int64(3600); return &v }(), // 1 hour
|
|
MaxAgeEventInFuture: func() *int64 { v := int64(1800); return &v }(), // 30 minutes
|
|
},
|
|
event: func() *event.E {
|
|
ev := createTestEvent(t, eventSigner, "test content", 1)
|
|
ev.CreatedAt = time.Now().Unix() + 900 // 15 minutes in future
|
|
return ev
|
|
}(),
|
|
loggedInPubkey: loggedInPubkey,
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
policy := &P{}
|
|
|
|
allowed, err := policy.checkRulePolicy("write", tt.event, tt.rule, tt.loggedInPubkey)
|
|
if err != nil {
|
|
t.Fatalf("checkRulePolicy failed: %v", err)
|
|
}
|
|
|
|
if allowed != tt.expected {
|
|
t.Errorf("Expected %v, got %v", tt.expected, allowed)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScriptPolicyDisabledFallsBackToDefault(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Create a policy with a script rule but policy is disabled, default policy is "allow"
|
|
policy := &P{
|
|
DefaultPolicy: "allow",
|
|
rules: map[int]Rule{
|
|
1: {
|
|
Description: "script rule",
|
|
Script: "policy.sh",
|
|
},
|
|
},
|
|
manager: &PolicyManager{
|
|
enabled: false, // Policy is disabled
|
|
runners: make(map[string]*ScriptRunner),
|
|
},
|
|
}
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Should allow the event when policy is disabled (falls back to default "allow")
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed when policy is disabled (should fall back to default policy 'allow')")
|
|
}
|
|
|
|
// Test with default policy "deny"
|
|
policy.DefaultPolicy = "deny"
|
|
allowed2, err2 := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err2 != nil {
|
|
t.Errorf("Unexpected error: %v", err2)
|
|
}
|
|
if allowed2 {
|
|
t.Error("Expected event to be denied when policy is disabled and default policy is 'deny'")
|
|
}
|
|
}
|
|
|
|
func TestDefaultPolicyAllow(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Test default policy "allow" behavior
|
|
policy := &P{
|
|
DefaultPolicy: "allow",
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{}, // No specific rules
|
|
}
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Should allow the event with default policy "allow"
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed with default_policy 'allow'")
|
|
}
|
|
}
|
|
|
|
func TestDefaultPolicyDeny(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Test default policy "deny" behavior
|
|
policy := &P{
|
|
DefaultPolicy: "deny",
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{}, // No specific rules
|
|
}
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Should deny the event with default policy "deny"
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied with default_policy 'deny'")
|
|
}
|
|
}
|
|
|
|
func TestDefaultPolicyEmpty(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Test empty default policy (should default to "allow")
|
|
policy := &P{
|
|
DefaultPolicy: "",
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{}, // No specific rules
|
|
}
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Should allow the event with empty default policy (defaults to "allow")
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed with empty default_policy (should default to 'allow')")
|
|
}
|
|
}
|
|
|
|
func TestDefaultPolicyInvalid(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Test invalid default policy (should default to "allow")
|
|
policy := &P{
|
|
DefaultPolicy: "invalid",
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{}, // No specific rules
|
|
}
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Should allow the event with invalid default policy (defaults to "allow")
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed with invalid default_policy (should default to 'allow')")
|
|
}
|
|
}
|
|
|
|
func TestDefaultPolicyWithSpecificRule(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Test that specific rules override default policy
|
|
policy := &P{
|
|
DefaultPolicy: "deny", // Default is deny
|
|
Kind: Kinds{},
|
|
rules: map[int]Rule{
|
|
1: {
|
|
Description: "allow kind 1",
|
|
WriteAllow: []string{}, // Allow all for kind 1
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create real test event with proper signing for kind 1 (has specific rule)
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Should allow the event because specific rule allows it, despite default policy being "deny"
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed by specific rule, despite default_policy 'deny'")
|
|
}
|
|
|
|
// Create real test event with proper signing for kind 2 (no specific rule exists)
|
|
testEvent2 := createTestEvent(t, eventSigner, "test content", 2)
|
|
|
|
// Should deny the event because no specific rule and default policy is "deny"
|
|
allowed2, err2 := policy.CheckPolicy("write", testEvent2, eventPubkey, "127.0.0.1")
|
|
if err2 != nil {
|
|
t.Errorf("Unexpected error: %v", err2)
|
|
}
|
|
if allowed2 {
|
|
t.Error("Expected event to be denied with default_policy 'deny' for kind without specific rule")
|
|
}
|
|
}
|
|
|
|
func TestNewPolicyDefaultsToAllow(t *testing.T) {
|
|
// Test that New() function sets default policy to "allow"
|
|
policy, err := New([]byte(`{}`))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create policy: %v", err)
|
|
}
|
|
|
|
if policy.DefaultPolicy != "allow" {
|
|
t.Errorf("Expected default policy to be 'allow', got '%s'", policy.DefaultPolicy)
|
|
}
|
|
}
|
|
|
|
func TestNewPolicyWithDefaultPolicyJSON(t *testing.T) {
|
|
// Test loading default policy from JSON
|
|
jsonConfig := `{"default_policy": "deny"}`
|
|
policy, err := New([]byte(jsonConfig))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create policy: %v", err)
|
|
}
|
|
|
|
if policy.DefaultPolicy != "deny" {
|
|
t.Errorf("Expected default policy to be 'deny', got '%s'", policy.DefaultPolicy)
|
|
}
|
|
}
|
|
|
|
func TestScriptProcessingDisabledFallsBackToDefault(t *testing.T) {
|
|
// Generate real keypair for testing
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Test that when policy is disabled, it falls back to default policy
|
|
policy := &P{
|
|
DefaultPolicy: "allow",
|
|
rules: map[int]Rule{
|
|
1: {
|
|
Description: "script rule",
|
|
Script: "policy.sh",
|
|
},
|
|
},
|
|
manager: &PolicyManager{
|
|
enabled: false, // Policy is disabled
|
|
runners: make(map[string]*ScriptRunner),
|
|
},
|
|
}
|
|
|
|
// Create real test event with proper signing
|
|
testEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
|
|
// Should allow the event when policy is disabled (falls back to default "allow")
|
|
allowed, err := policy.checkScriptPolicy("write", testEvent, "policy.sh", eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed when policy is disabled (should fall back to default policy 'allow')")
|
|
}
|
|
|
|
// Test with default policy "deny"
|
|
policy.DefaultPolicy = "deny"
|
|
allowed2, err2 := policy.checkScriptPolicy("write", testEvent, "policy.sh", eventPubkey, "127.0.0.1")
|
|
if err2 != nil {
|
|
t.Errorf("Unexpected error: %v", err2)
|
|
}
|
|
if allowed2 {
|
|
t.Error("Expected event to be denied when policy is disabled and default policy is 'deny'")
|
|
}
|
|
}
|
|
|
|
func TestDefaultPolicyLogicWithRules(t *testing.T) {
|
|
// Generate real keypairs for testing
|
|
testSigner, _ := generateTestKeypair(t)
|
|
_, deniedPubkey := generateTestKeypair(t) // Only need pubkey for denied user
|
|
_, loggedInPubkey := generateTestKeypair(t)
|
|
|
|
// Test that default policy logic works correctly with rules
|
|
|
|
// Test 1: default_policy "deny" - should only allow if rule explicitly allows
|
|
policy1 := &P{
|
|
DefaultPolicy: "deny",
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 2, 3}, // Allow kinds 1, 2, 3
|
|
},
|
|
rules: map[int]Rule{
|
|
1: {
|
|
Description: "allow all for kind 1",
|
|
WriteAllow: []string{}, // Empty means allow all
|
|
},
|
|
2: {
|
|
Description: "deny specific pubkey for kind 2",
|
|
WriteDeny: []string{hex.Enc(deniedPubkey)},
|
|
},
|
|
// No rule for kind 3
|
|
},
|
|
}
|
|
|
|
// Kind 1: has rule that allows all - should be allowed
|
|
event1 := createTestEvent(t, testSigner, "content", 1)
|
|
allowed1, err1 := policy1.CheckPolicy("write", event1, loggedInPubkey, "127.0.0.1")
|
|
if err1 != nil {
|
|
t.Errorf("Unexpected error for kind 1: %v", err1)
|
|
}
|
|
if !allowed1 {
|
|
t.Error("Expected kind 1 to be allowed (rule allows all)")
|
|
}
|
|
|
|
// Kind 2: has rule that denies specific pubkey - should be allowed for other pubkeys
|
|
event2 := createTestEvent(t, testSigner, "content", 2)
|
|
allowed2, err2 := policy1.CheckPolicy("write", event2, loggedInPubkey, "127.0.0.1")
|
|
if err2 != nil {
|
|
t.Errorf("Unexpected error for kind 2: %v", err2)
|
|
}
|
|
if !allowed2 {
|
|
t.Error("Expected kind 2 to be allowed for non-denied pubkey")
|
|
}
|
|
|
|
// Kind 2: submitter in deny list should be denied
|
|
event2Denied := createTestEvent(t, testSigner, "content", 2) // Event can be from anyone
|
|
allowed2Denied, err2Denied := policy1.CheckPolicy("write", event2Denied, deniedPubkey, "127.0.0.1") // But submitted by denied user
|
|
if err2Denied != nil {
|
|
t.Errorf("Unexpected error for kind 2 denied: %v", err2Denied)
|
|
}
|
|
if allowed2Denied {
|
|
t.Error("Expected kind 2 to be denied when submitter is in deny list")
|
|
}
|
|
|
|
// Kind 3: whitelisted but no rule - should follow default policy (deny)
|
|
event3 := createTestEvent(t, testSigner, "content", 3)
|
|
allowed3, err3 := policy1.CheckPolicy("write", event3, loggedInPubkey, "127.0.0.1")
|
|
if err3 != nil {
|
|
t.Errorf("Unexpected error for kind 3: %v", err3)
|
|
}
|
|
if allowed3 {
|
|
t.Error("Expected kind 3 to be denied (no rule, default policy is deny)")
|
|
}
|
|
|
|
// Test 2: default_policy "allow" - should allow unless rule explicitly denies
|
|
policy2 := &P{
|
|
DefaultPolicy: "allow",
|
|
Kind: Kinds{
|
|
Whitelist: []int{1, 2, 3}, // Allow kinds 1, 2, 3
|
|
},
|
|
rules: map[int]Rule{
|
|
1: {
|
|
Description: "deny specific pubkey for kind 1",
|
|
WriteDeny: []string{hex.Enc(deniedPubkey)},
|
|
},
|
|
// No rules for kind 2, 3
|
|
},
|
|
}
|
|
|
|
// Kind 1: has rule that denies specific pubkey - should be allowed for other pubkeys
|
|
event1Allow := createTestEvent(t, testSigner, "content", 1)
|
|
allowed1Allow, err1Allow := policy2.CheckPolicy("write", event1Allow, loggedInPubkey, "127.0.0.1")
|
|
if err1Allow != nil {
|
|
t.Errorf("Unexpected error for kind 1 allow: %v", err1Allow)
|
|
}
|
|
if !allowed1Allow {
|
|
t.Error("Expected kind 1 to be allowed for non-denied pubkey")
|
|
}
|
|
|
|
// Kind 1: denied pubkey should be denied when they try to submit
|
|
event1Deny := createTestEvent(t, testSigner, "content", 1) // Event can be authored by anyone
|
|
allowed1Deny, err1Deny := policy2.CheckPolicy("write", event1Deny, deniedPubkey, "127.0.0.1") // But denied user cannot submit
|
|
if err1Deny != nil {
|
|
t.Errorf("Unexpected error for kind 1 deny: %v", err1Deny)
|
|
}
|
|
if allowed1Deny {
|
|
t.Error("Expected kind 1 to be denied for denied pubkey")
|
|
}
|
|
|
|
// Kind 2: whitelisted but no rule - should follow default policy (allow)
|
|
event2Allow := createTestEvent(t, testSigner, "content", 2)
|
|
allowed2Allow, err2Allow := policy2.CheckPolicy("write", event2Allow, loggedInPubkey, "127.0.0.1")
|
|
if err2Allow != nil {
|
|
t.Errorf("Unexpected error for kind 2 allow: %v", err2Allow)
|
|
}
|
|
if !allowed2Allow {
|
|
t.Error("Expected kind 2 to be allowed (no rule, default policy is allow)")
|
|
}
|
|
}
|
|
|
|
func TestRuleScriptLoading(t *testing.T) {
|
|
// This test validates that a policy script loads for a specific Rule
|
|
// and properly processes events
|
|
|
|
// Create temporary directory for test files
|
|
tempDir := t.TempDir()
|
|
scriptPath := filepath.Join(tempDir, "test-rule-script.sh")
|
|
|
|
// Create a test script that accepts events with "allowed" in content
|
|
scriptContent := `#!/bin/bash
|
|
while IFS= read -r line; do
|
|
if echo "$line" | grep -q 'allowed'; then
|
|
echo '{"action":"accept","msg":"Content approved"}'
|
|
else
|
|
echo '{"action":"reject","msg":"Content not allowed"}'
|
|
fi
|
|
done
|
|
`
|
|
err := os.WriteFile(scriptPath, []byte(scriptContent), 0755)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test script: %v", err)
|
|
}
|
|
|
|
// Create policy manager with script support
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
manager := &PolicyManager{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
configDir: tempDir,
|
|
scriptPath: filepath.Join(tempDir, "default-policy.sh"), // Different from rule script
|
|
enabled: true,
|
|
runners: make(map[string]*ScriptRunner),
|
|
}
|
|
|
|
// Create policy with a rule that uses the script
|
|
policy := &P{
|
|
DefaultPolicy: "deny",
|
|
manager: manager,
|
|
rules: map[int]Rule{
|
|
4678: {
|
|
Description: "Test rule with custom script",
|
|
Script: scriptPath, // Rule-specific script path
|
|
},
|
|
},
|
|
}
|
|
|
|
// Generate test keypairs
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
|
|
// Pre-start the script before running tests
|
|
runner := manager.getOrCreateRunner(scriptPath)
|
|
err = runner.Start()
|
|
if err != nil {
|
|
t.Fatalf("Failed to start script: %v", err)
|
|
}
|
|
|
|
// Wait for script to be ready
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
if !runner.IsRunning() {
|
|
t.Fatal("Script should be running after Start()")
|
|
}
|
|
|
|
// Test sending a warmup event to ensure script is responsive
|
|
signer := p8k.MustNew()
|
|
signer.Generate()
|
|
warmupEv := event.New()
|
|
warmupEv.CreatedAt = time.Now().Unix()
|
|
warmupEv.Kind = 4678
|
|
warmupEv.Content = []byte("warmup")
|
|
warmupEv.Tags = tag.NewS()
|
|
warmupEv.Sign(signer)
|
|
|
|
warmupEvent := &PolicyEvent{
|
|
E: warmupEv,
|
|
IPAddress: "127.0.0.1",
|
|
}
|
|
|
|
// Send warmup event to verify script is responding
|
|
_, err = runner.ProcessEvent(warmupEvent)
|
|
if err != nil {
|
|
t.Fatalf("Script not responding to warmup event: %v", err)
|
|
}
|
|
|
|
t.Log("Script is ready and responding")
|
|
|
|
// Test 1: Event with "allowed" content should be accepted
|
|
t.Run("script_accepts_allowed_content", func(t *testing.T) {
|
|
testEvent := createTestEvent(t, eventSigner, "this is allowed content", 4678)
|
|
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Logf("Policy check failed: %v", err)
|
|
// Check if script exists
|
|
if _, statErr := os.Stat(scriptPath); statErr != nil {
|
|
t.Errorf("Script file error: %v", statErr)
|
|
}
|
|
t.Fatalf("Unexpected error during policy check: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event with 'allowed' content to be accepted by script")
|
|
t.Logf("Event content: %s", string(testEvent.Content))
|
|
}
|
|
|
|
// Verify the script runner was created and is running
|
|
manager.mutex.RLock()
|
|
runner, exists := manager.runners[scriptPath]
|
|
manager.mutex.RUnlock()
|
|
|
|
if !exists {
|
|
t.Fatal("Expected script runner to be created for rule script path")
|
|
}
|
|
if !runner.IsRunning() {
|
|
t.Error("Expected script runner to be running after processing event")
|
|
}
|
|
})
|
|
|
|
// Test 2: Event without "allowed" content should be rejected
|
|
t.Run("script_rejects_disallowed_content", func(t *testing.T) {
|
|
testEvent := createTestEvent(t, eventSigner, "this is not permitted", 4678)
|
|
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event without 'allowed' content to be rejected by script")
|
|
}
|
|
})
|
|
|
|
// Test 3: Verify script path is correct (rule-specific, not default)
|
|
t.Run("script_path_is_rule_specific", func(t *testing.T) {
|
|
manager.mutex.RLock()
|
|
runner, exists := manager.runners[scriptPath]
|
|
_, defaultExists := manager.runners[manager.scriptPath]
|
|
manager.mutex.RUnlock()
|
|
|
|
if !exists {
|
|
t.Fatal("Expected rule-specific script runner to exist")
|
|
}
|
|
if defaultExists {
|
|
t.Error("Default script runner should not be created when only rule-specific scripts are used")
|
|
}
|
|
|
|
// Verify the runner is using the correct script path
|
|
if runner.scriptPath != scriptPath {
|
|
t.Errorf("Expected runner to use script path %s, got %s", scriptPath, runner.scriptPath)
|
|
}
|
|
})
|
|
|
|
// Test 4: Multiple events should use the same script instance
|
|
t.Run("script_reused_for_multiple_events", func(t *testing.T) {
|
|
// Get initial runner
|
|
manager.mutex.RLock()
|
|
initialRunner, _ := manager.runners[scriptPath]
|
|
initialRunnerCount := len(manager.runners)
|
|
manager.mutex.RUnlock()
|
|
|
|
// Process multiple events
|
|
for i := 0; i < 5; i++ {
|
|
content := "this is allowed message " + string(rune('0'+i))
|
|
testEvent := createTestEvent(t, eventSigner, content, 4678)
|
|
_, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error on event %d: %v", i, err)
|
|
}
|
|
}
|
|
|
|
// Verify same runner is used
|
|
manager.mutex.RLock()
|
|
currentRunner, _ := manager.runners[scriptPath]
|
|
currentRunnerCount := len(manager.runners)
|
|
manager.mutex.RUnlock()
|
|
|
|
if currentRunner != initialRunner {
|
|
t.Error("Expected same runner instance to be reused for multiple events")
|
|
}
|
|
if currentRunnerCount != initialRunnerCount {
|
|
t.Errorf("Expected runner count to stay at %d, got %d", initialRunnerCount, currentRunnerCount)
|
|
}
|
|
})
|
|
|
|
// Test 5: Different kind without script should use default policy
|
|
t.Run("different_kind_uses_default_policy", func(t *testing.T) {
|
|
testEvent := createTestEvent(t, eventSigner, "any content", 1) // Kind 1 has no rule
|
|
|
|
allowed, err := policy.CheckPolicy("write", testEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
// Should be denied by default policy (deny)
|
|
if allowed {
|
|
t.Error("Expected event of kind without rule to be denied by default policy")
|
|
}
|
|
})
|
|
|
|
// Cleanup: Stop the script
|
|
manager.mutex.RLock()
|
|
runner, exists := manager.runners[scriptPath]
|
|
manager.mutex.RUnlock()
|
|
if exists && runner.IsRunning() {
|
|
runner.Stop()
|
|
}
|
|
}
|
|
|
|
func TestPolicyFilterProcessing(t *testing.T) {
|
|
// Test policy filter processing using the provided filter JSON specification
|
|
filterJSON := []byte(`{
|
|
"kind": {
|
|
"whitelist": [4678, 10306, 30520, 30919]
|
|
},
|
|
"rules": {
|
|
"4678": {
|
|
"description": "Zenotp message events",
|
|
"script": "/home/orly/.config/ORLY/validate4678.js",
|
|
"privileged": true
|
|
},
|
|
"10306": {
|
|
"description": "End user whitelist changes",
|
|
"read_allow": [
|
|
"04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
|
|
],
|
|
"privileged": true
|
|
},
|
|
"30520": {
|
|
"description": "Zenotp events",
|
|
"write_allow": [
|
|
"04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
|
|
],
|
|
"privileged": true
|
|
},
|
|
"30919": {
|
|
"description": "Zenotp events",
|
|
"write_allow": [
|
|
"04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
|
|
],
|
|
"privileged": true
|
|
}
|
|
}
|
|
}`)
|
|
|
|
// Create policy from JSON
|
|
policy, err := New(filterJSON)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create policy from JSON: %v", err)
|
|
}
|
|
|
|
// Verify whitelist is set correctly
|
|
if len(policy.Kind.Whitelist) != 4 {
|
|
t.Errorf("Expected whitelist to have 4 kinds, got %d", len(policy.Kind.Whitelist))
|
|
}
|
|
expectedKinds := map[int]bool{4678: true, 10306: true, 30520: true, 30919: true}
|
|
for _, kind := range policy.Kind.Whitelist {
|
|
if !expectedKinds[kind] {
|
|
t.Errorf("Unexpected kind in whitelist: %d", kind)
|
|
}
|
|
}
|
|
|
|
// Verify rules are loaded correctly
|
|
if len(policy.rules) != 4 {
|
|
t.Errorf("Expected 4 rules, got %d", len(policy.rules))
|
|
}
|
|
|
|
// Verify rule 4678 (script-based)
|
|
rule4678, ok := policy.rules[4678]
|
|
if !ok {
|
|
t.Fatal("Rule 4678 not found")
|
|
}
|
|
if rule4678.Description != "Zenotp message events" {
|
|
t.Errorf("Expected description 'Zenotp message events', got '%s'", rule4678.Description)
|
|
}
|
|
if rule4678.Script != "/home/orly/.config/ORLY/validate4678.js" {
|
|
t.Errorf("Expected script path '/home/orly/.config/ORLY/validate4678.js', got '%s'", rule4678.Script)
|
|
}
|
|
if !rule4678.Privileged {
|
|
t.Error("Expected rule 4678 to be privileged")
|
|
}
|
|
|
|
// Verify rule 10306 (read_allow)
|
|
rule10306, ok := policy.rules[10306]
|
|
if !ok {
|
|
t.Fatal("Rule 10306 not found")
|
|
}
|
|
if rule10306.Description != "End user whitelist changes" {
|
|
t.Errorf("Expected description 'End user whitelist changes', got '%s'", rule10306.Description)
|
|
}
|
|
if len(rule10306.ReadAllow) != 1 {
|
|
t.Errorf("Expected 1 read_allow pubkey, got %d", len(rule10306.ReadAllow))
|
|
}
|
|
if rule10306.ReadAllow[0] != "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" {
|
|
t.Errorf("Expected read_allow pubkey '04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5', got '%s'", rule10306.ReadAllow[0])
|
|
}
|
|
if !rule10306.Privileged {
|
|
t.Error("Expected rule 10306 to be privileged")
|
|
}
|
|
|
|
// Verify rule 30520 (write_allow)
|
|
rule30520, ok := policy.rules[30520]
|
|
if !ok {
|
|
t.Fatal("Rule 30520 not found")
|
|
}
|
|
if rule30520.Description != "Zenotp events" {
|
|
t.Errorf("Expected description 'Zenotp events', got '%s'", rule30520.Description)
|
|
}
|
|
if len(rule30520.WriteAllow) != 1 {
|
|
t.Errorf("Expected 1 write_allow pubkey, got %d", len(rule30520.WriteAllow))
|
|
}
|
|
if rule30520.WriteAllow[0] != "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" {
|
|
t.Errorf("Expected write_allow pubkey '04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5', got '%s'", rule30520.WriteAllow[0])
|
|
}
|
|
if !rule30520.Privileged {
|
|
t.Error("Expected rule 30520 to be privileged")
|
|
}
|
|
|
|
// Verify rule 30919 (write_allow)
|
|
rule30919, ok := policy.rules[30919]
|
|
if !ok {
|
|
t.Fatal("Rule 30919 not found")
|
|
}
|
|
if rule30919.Description != "Zenotp events" {
|
|
t.Errorf("Expected description 'Zenotp events', got '%s'", rule30919.Description)
|
|
}
|
|
if len(rule30919.WriteAllow) != 1 {
|
|
t.Errorf("Expected 1 write_allow pubkey, got %d", len(rule30919.WriteAllow))
|
|
}
|
|
if rule30919.WriteAllow[0] != "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" {
|
|
t.Errorf("Expected write_allow pubkey '04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5', got '%s'", rule30919.WriteAllow[0])
|
|
}
|
|
if !rule30919.Privileged {
|
|
t.Error("Expected rule 30919 to be privileged")
|
|
}
|
|
|
|
// Test kind whitelist filtering
|
|
t.Run("kind whitelist filtering", func(t *testing.T) {
|
|
eventSigner, eventPubkey := generateTestKeypair(t)
|
|
_, loggedInPubkey := generateTestKeypair(t)
|
|
|
|
// Test whitelisted kind (should pass kind check, but may fail other checks)
|
|
whitelistedEvent := createTestEvent(t, eventSigner, "test content", 4678)
|
|
addPTag(whitelistedEvent, loggedInPubkey)
|
|
allowed, err := policy.CheckPolicy("write", whitelistedEvent, loggedInPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
// Should be allowed because script isn't running (falls back to default "allow")
|
|
// and privileged check passes (loggedInPubkey is in p tag)
|
|
if !allowed {
|
|
t.Error("Expected whitelisted kind to be allowed when script not running and privileged check passes")
|
|
}
|
|
|
|
// Test non-whitelisted kind (should be denied)
|
|
nonWhitelistedEvent := createTestEvent(t, eventSigner, "test content", 1)
|
|
allowed, err = policy.CheckPolicy("write", nonWhitelistedEvent, eventPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected non-whitelisted kind to be denied")
|
|
}
|
|
})
|
|
|
|
// Test read access for kind 10306
|
|
t.Run("read access for kind 10306", func(t *testing.T) {
|
|
allowedPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
|
|
allowedPubkeyBytes, err := hex.Dec(allowedPubkeyHex)
|
|
if err != nil {
|
|
t.Fatalf("Failed to decode allowed pubkey: %v", err)
|
|
}
|
|
|
|
// Create event with allowed pubkey using a temporary signer
|
|
// Note: We can't actually sign with just the pubkey, so we'll create the event
|
|
// with a random signer and then manually set the pubkey for testing
|
|
tempSigner, _ := generateTestKeypair(t)
|
|
event10306 := createTestEvent(t, tempSigner, "test content", 10306)
|
|
event10306.Pubkey = allowedPubkeyBytes
|
|
// Re-sign won't work without private key, but policy checks use the pubkey field
|
|
// So we'll test with the pubkey set correctly
|
|
|
|
// Test read access with allowed pubkey (logged in user matches event pubkey)
|
|
allowed, err := policy.CheckPolicy("read", event10306, allowedPubkeyBytes, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed for read access with allowed pubkey")
|
|
}
|
|
|
|
// Test read access with non-allowed pubkey
|
|
_, unauthorizedPubkey := generateTestKeypair(t)
|
|
allowed, err = policy.CheckPolicy("read", event10306, unauthorizedPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied for read access with non-allowed pubkey")
|
|
}
|
|
|
|
// Test read access without authentication (privileged check)
|
|
allowed, err = policy.CheckPolicy("read", event10306, nil, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied for read access without authentication (privileged)")
|
|
}
|
|
})
|
|
|
|
// Test write access for kind 30520
|
|
t.Run("write access for kind 30520", func(t *testing.T) {
|
|
allowedPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
|
|
allowedPubkeyBytes, err := hex.Dec(allowedPubkeyHex)
|
|
if err != nil {
|
|
t.Fatalf("Failed to decode allowed pubkey: %v", err)
|
|
}
|
|
|
|
// Create event with allowed pubkey using a temporary signer
|
|
// We'll set the pubkey manually for testing since we don't have the private key
|
|
tempSigner, _ := generateTestKeypair(t)
|
|
event30520 := createTestEvent(t, tempSigner, "test content", 30520)
|
|
event30520.Pubkey = allowedPubkeyBytes
|
|
|
|
// Test write access with allowed pubkey (event pubkey matches write_allow)
|
|
allowed, err := policy.CheckPolicy("write", event30520, allowedPubkeyBytes, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed for write access with allowed pubkey")
|
|
}
|
|
|
|
// Test write access with non-allowed pubkey
|
|
unauthorizedSigner, unauthorizedPubkey := generateTestKeypair(t)
|
|
unauthorizedEvent := createTestEvent(t, unauthorizedSigner, "test content", 30520)
|
|
allowed, err = policy.CheckPolicy("write", unauthorizedEvent, unauthorizedPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied for write access with non-allowed pubkey")
|
|
}
|
|
|
|
// Test write access without authentication (privileged check)
|
|
allowed, err = policy.CheckPolicy("write", event30520, nil, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied for write access without authentication (privileged)")
|
|
}
|
|
})
|
|
|
|
// Test write access for kind 30919
|
|
t.Run("write access for kind 30919", func(t *testing.T) {
|
|
allowedPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
|
|
allowedPubkeyBytes, err := hex.Dec(allowedPubkeyHex)
|
|
if err != nil {
|
|
t.Fatalf("Failed to decode allowed pubkey: %v", err)
|
|
}
|
|
|
|
// Create event with allowed pubkey using a temporary signer
|
|
// We'll set the pubkey manually for testing since we don't have the private key
|
|
tempSigner, _ := generateTestKeypair(t)
|
|
event30919 := createTestEvent(t, tempSigner, "test content", 30919)
|
|
event30919.Pubkey = allowedPubkeyBytes
|
|
|
|
// Test write access with allowed pubkey (event pubkey matches write_allow)
|
|
allowed, err := policy.CheckPolicy("write", event30919, allowedPubkeyBytes, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed for write access with allowed pubkey")
|
|
}
|
|
|
|
// Test write access with non-allowed pubkey
|
|
unauthorizedSigner, unauthorizedPubkey := generateTestKeypair(t)
|
|
unauthorizedEvent := createTestEvent(t, unauthorizedSigner, "test content", 30919)
|
|
allowed, err = policy.CheckPolicy("write", unauthorizedEvent, unauthorizedPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied for write access with non-allowed pubkey")
|
|
}
|
|
|
|
// Test write access without authentication (privileged check)
|
|
allowed, err = policy.CheckPolicy("write", event30919, nil, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied for write access without authentication (privileged)")
|
|
}
|
|
})
|
|
|
|
// Test privileged flag behavior with p tags
|
|
t.Run("privileged flag with p tags", func(t *testing.T) {
|
|
eventSigner, _ := generateTestKeypair(t)
|
|
_, loggedInPubkey := generateTestKeypair(t)
|
|
allowedPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
|
|
allowedPubkeyBytes, err := hex.Dec(allowedPubkeyHex)
|
|
if err != nil {
|
|
t.Fatalf("Failed to decode allowed pubkey: %v", err)
|
|
}
|
|
|
|
// Create event with allowed pubkey and logged-in pubkey in p tag
|
|
event30520 := createTestEvent(t, eventSigner, "test content", 30520)
|
|
event30520.Pubkey = allowedPubkeyBytes
|
|
addPTag(event30520, loggedInPubkey)
|
|
|
|
// Test that event is DENIED when submitter (logged-in pubkey) is not in write_allow
|
|
// Even though the submitter is in p-tag, write_allow is about who can submit
|
|
allowed, err := policy.CheckPolicy("write", event30520, loggedInPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied when submitter (logged-in pubkey) is not in write_allow")
|
|
}
|
|
|
|
// Test that event is denied when submitter is not in write_allow (even without p-tag)
|
|
event30520NoPTag := createTestEvent(t, eventSigner, "test content", 30520)
|
|
event30520NoPTag.Pubkey = allowedPubkeyBytes
|
|
allowed, err = policy.CheckPolicy("write", event30520NoPTag, loggedInPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if allowed {
|
|
t.Error("Expected event to be denied when submitter is not in write_allow")
|
|
}
|
|
})
|
|
|
|
// Test script-based rule (kind 4678)
|
|
t.Run("script-based rule for kind 4678", func(t *testing.T) {
|
|
eventSigner, _ := generateTestKeypair(t)
|
|
_, loggedInPubkey := generateTestKeypair(t)
|
|
|
|
event4678 := createTestEvent(t, eventSigner, "test content", 4678)
|
|
addPTag(event4678, loggedInPubkey)
|
|
|
|
// Test with script not running (should fall back to default policy)
|
|
allowed, err := policy.CheckPolicy("write", event4678, loggedInPubkey, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
// Should allow because default policy is "allow" and script is not running
|
|
// and privileged check passes (loggedInPubkey is in p tag)
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed when script is not running (falls back to default 'allow') and privileged check passes")
|
|
}
|
|
|
|
// Test without authentication (privileged doesn't affect write operations)
|
|
allowed, err = policy.CheckPolicy("write", event4678, nil, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
// Should be allowed because privileged doesn't affect write operations
|
|
// Falls back to default policy which is "allow"
|
|
if !allowed {
|
|
t.Error("Expected event to be allowed without authentication (privileged doesn't affect write)")
|
|
}
|
|
})
|
|
}
|