Files
next.orly.dev/pkg/policy/default_permissive_test.go
mleku 7a27c44bc9
Some checks failed
Go / build-and-release (push) Has been cancelled
Enhance policy system tests and documentation.
Added extensive tests for default-permissive access control, read/write follow whitelists, and privileged-only fields. Updated policy documentation with new configuration examples, access control reference, and logging details.
2025-12-03 19:19:36 +00:00

687 lines
18 KiB
Go

package policy
import (
"testing"
"time"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/hex"
"git.mleku.dev/mleku/nostr/encoders/tag"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
"lol.mleku.dev/chk"
)
// =============================================================================
// Default-Permissive Access Control Tests
// =============================================================================
// TestDefaultPermissiveRead tests that read access is allowed by default
// when no read restrictions are configured.
func TestDefaultPermissiveRead(t *testing.T) {
// No read restrictions configured
policyJSON := []byte(`{
"default_policy": "deny",
"rules": {
"1": {
"description": "No read restrictions"
}
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
authorSigner, authorPubkey := generateTestKeypair(t)
_, readerPubkey := generateTestKeypair(t)
_, randomPubkey := generateTestKeypair(t)
ev := createTestEvent(t, authorSigner, "test content", 1)
tests := []struct {
name string
pubkey []byte
expectAllow bool
}{
{
name: "author can read (default permissive)",
pubkey: authorPubkey,
expectAllow: true,
},
{
name: "reader can read (default permissive)",
pubkey: readerPubkey,
expectAllow: true,
},
{
name: "random user can read (default permissive)",
pubkey: randomPubkey,
expectAllow: true,
},
{
name: "nil pubkey can read (default permissive)",
pubkey: nil,
expectAllow: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed != tt.expectAllow {
t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
}
})
}
}
// TestDefaultPermissiveWrite tests that write access is allowed by default
// when no write restrictions are configured.
func TestDefaultPermissiveWrite(t *testing.T) {
// No write restrictions configured
policyJSON := []byte(`{
"default_policy": "deny",
"rules": {
"1": {
"description": "No write restrictions"
}
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
writerSigner, writerPubkey := generateTestKeypair(t)
_, randomPubkey := generateTestKeypair(t)
tests := []struct {
name string
signer *p8k.Signer
pubkey []byte
expectAllow bool
}{
{
name: "writer can write (default permissive)",
signer: writerSigner,
pubkey: writerPubkey,
expectAllow: true,
},
{
name: "random user can write (default permissive)",
signer: writerSigner,
pubkey: randomPubkey,
expectAllow: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ev := createTestEvent(t, tt.signer, "test content", 1)
allowed, err := policy.CheckPolicy("write", ev, tt.pubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed != tt.expectAllow {
t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
}
})
}
}
// TestReadFollowsWhitelist tests the read_follows_whitelist field.
func TestReadFollowsWhitelist(t *testing.T) {
_, curatorPubkey := generateTestKeypair(t)
_, followedPubkey := generateTestKeypair(t)
_, unfollowedPubkey := generateTestKeypair(t)
authorSigner, authorPubkey := generateTestKeypair(t)
curatorHex := hex.Enc(curatorPubkey)
policyJSON := []byte(`{
"default_policy": "deny",
"rules": {
"1": {
"description": "Only curator follows can read",
"read_follows_whitelist": ["` + curatorHex + `"]
}
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
// Simulate loading curator's follows (includes followed user and curator themselves)
policy.UpdateRuleReadFollowsWhitelist(1, [][]byte{followedPubkey})
ev := createTestEvent(t, authorSigner, "test content", 1)
tests := []struct {
name string
pubkey []byte
expectAllow bool
}{
{
name: "curator can read (is in whitelist pubkeys)",
pubkey: curatorPubkey,
expectAllow: true,
},
{
name: "followed user can read",
pubkey: followedPubkey,
expectAllow: true,
},
{
name: "unfollowed user denied",
pubkey: unfollowedPubkey,
expectAllow: false,
},
{
name: "author cannot read (not in follows)",
pubkey: authorPubkey,
expectAllow: false,
},
{
name: "nil pubkey denied",
pubkey: nil,
expectAllow: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed != tt.expectAllow {
t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
}
})
}
// Verify write is still default-permissive (no write restriction)
t.Run("write is still default permissive", func(t *testing.T) {
allowed, err := policy.CheckPolicy("write", ev, unfollowedPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if !allowed {
t.Error("Expected write to be allowed (no write restriction)")
}
})
}
// TestWriteFollowsWhitelist tests the write_follows_whitelist field.
func TestWriteFollowsWhitelist(t *testing.T) {
moderatorSigner, moderatorPubkey := generateTestKeypair(t)
followedSigner, followedPubkey := generateTestKeypair(t)
unfollowedSigner, unfollowedPubkey := generateTestKeypair(t)
moderatorHex := hex.Enc(moderatorPubkey)
policyJSON := []byte(`{
"default_policy": "deny",
"rules": {
"1": {
"description": "Only moderator follows can write",
"write_follows_whitelist": ["` + moderatorHex + `"]
}
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
// Simulate loading moderator's follows
policy.UpdateRuleWriteFollowsWhitelist(1, [][]byte{followedPubkey})
tests := []struct {
name string
signer *p8k.Signer
pubkey []byte
expectAllow bool
}{
{
name: "moderator can write (is in whitelist pubkeys)",
signer: moderatorSigner,
pubkey: moderatorPubkey,
expectAllow: true,
},
{
name: "followed user can write",
signer: followedSigner,
pubkey: followedPubkey,
expectAllow: true,
},
{
name: "unfollowed user denied",
signer: unfollowedSigner,
pubkey: unfollowedPubkey,
expectAllow: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ev := createTestEvent(t, tt.signer, "test content", 1)
allowed, err := policy.CheckPolicy("write", ev, tt.pubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed != tt.expectAllow {
t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
}
})
}
// Verify read is still default-permissive (no read restriction)
t.Run("read is still default permissive", func(t *testing.T) {
ev := createTestEvent(t, unfollowedSigner, "test content", 1)
allowed, err := policy.CheckPolicy("read", ev, unfollowedPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if !allowed {
t.Error("Expected read to be allowed (no read restriction)")
}
})
}
// TestGlobalReadFollowsWhitelist tests read_follows_whitelist in global rule.
func TestGlobalReadFollowsWhitelist(t *testing.T) {
_, curatorPubkey := generateTestKeypair(t)
_, followedPubkey := generateTestKeypair(t)
_, unfollowedPubkey := generateTestKeypair(t)
authorSigner, _ := generateTestKeypair(t)
curatorHex := hex.Enc(curatorPubkey)
policyJSON := []byte(`{
"default_policy": "deny",
"global": {
"description": "Global read follows whitelist",
"read_follows_whitelist": ["` + curatorHex + `"]
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
// Update global read follows whitelist
policy.UpdateGlobalReadFollowsWhitelist([][]byte{followedPubkey})
// Test with kind 1
t.Run("kind 1", func(t *testing.T) {
ev := createTestEvent(t, authorSigner, "test content", 1)
// Followed user can read
allowed, err := policy.CheckPolicy("read", ev, followedPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if !allowed {
t.Error("Expected followed user to be allowed to read")
}
// Unfollowed user denied
allowed, err = policy.CheckPolicy("read", ev, unfollowedPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed {
t.Error("Expected unfollowed user to be denied")
}
})
}
// TestGlobalWriteFollowsWhitelist tests write_follows_whitelist in global rule.
func TestGlobalWriteFollowsWhitelist(t *testing.T) {
_, moderatorPubkey := generateTestKeypair(t)
followedSigner, followedPubkey := generateTestKeypair(t)
unfollowedSigner, unfollowedPubkey := generateTestKeypair(t)
moderatorHex := hex.Enc(moderatorPubkey)
policyJSON := []byte(`{
"default_policy": "deny",
"global": {
"description": "Global write follows whitelist",
"write_follows_whitelist": ["` + moderatorHex + `"]
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
// Update global write follows whitelist
policy.UpdateGlobalWriteFollowsWhitelist([][]byte{followedPubkey})
// Test with kind 1
t.Run("kind 1", func(t *testing.T) {
// Followed user can write
ev := createTestEvent(t, followedSigner, "test content", 1)
allowed, err := policy.CheckPolicy("write", ev, followedPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if !allowed {
t.Error("Expected followed user to be allowed to write")
}
// Unfollowed user denied
ev = createTestEvent(t, unfollowedSigner, "test content", 1)
allowed, err = policy.CheckPolicy("write", ev, unfollowedPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed {
t.Error("Expected unfollowed user to be denied")
}
})
}
// TestPrivilegedOnlyAppliesToReadDP tests that privileged only affects read access.
func TestPrivilegedOnlyAppliesToReadDP(t *testing.T) {
authorSigner, authorPubkey := generateTestKeypair(t)
_, recipientPubkey := generateTestKeypair(t)
thirdPartySigner, thirdPartyPubkey := generateTestKeypair(t)
policyJSON := []byte(`{
"default_policy": "deny",
"rules": {
"4": {
"description": "Encrypted DMs - privileged",
"privileged": true
}
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
// Create event with p-tag for recipient
ev := event.New()
ev.Kind = 4
ev.Content = []byte("encrypted content")
ev.CreatedAt = time.Now().Unix()
ev.Tags = tag.NewS()
pTag := tag.NewFromAny("p", hex.Enc(recipientPubkey))
ev.Tags.Append(pTag)
if err := ev.Sign(authorSigner); chk.E(err) {
t.Fatalf("Failed to sign event: %v", err)
}
// READ tests
t.Run("author can read", func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", ev, authorPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if !allowed {
t.Error("Expected author to be allowed to read")
}
})
t.Run("recipient can read", func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", ev, recipientPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if !allowed {
t.Error("Expected recipient to be allowed to read")
}
})
t.Run("third party cannot read", func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", ev, thirdPartyPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed {
t.Error("Expected third party to be denied read access")
}
})
// WRITE tests - privileged should NOT affect write
t.Run("third party CAN write (privileged doesn't affect write)", func(t *testing.T) {
ev := createTestEvent(t, thirdPartySigner, "test content", 4)
allowed, err := policy.CheckPolicy("write", ev, thirdPartyPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if !allowed {
t.Error("Expected third party to be allowed to write (privileged doesn't restrict write)")
}
})
}
// TestCombinedReadWriteFollowsWhitelists tests using both whitelists on same rule.
func TestCombinedReadWriteFollowsWhitelists(t *testing.T) {
_, curatorPubkey := generateTestKeypair(t)
_, moderatorPubkey := generateTestKeypair(t)
readerSigner, readerPubkey := generateTestKeypair(t)
writerSigner, writerPubkey := generateTestKeypair(t)
_, outsiderPubkey := generateTestKeypair(t)
curatorHex := hex.Enc(curatorPubkey)
moderatorHex := hex.Enc(moderatorPubkey)
policyJSON := []byte(`{
"default_policy": "deny",
"rules": {
"30023": {
"description": "Articles - different read/write follows",
"read_follows_whitelist": ["` + curatorHex + `"],
"write_follows_whitelist": ["` + moderatorHex + `"]
}
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
// Curator follows reader, moderator follows writer
policy.UpdateRuleReadFollowsWhitelist(30023, [][]byte{readerPubkey})
policy.UpdateRuleWriteFollowsWhitelist(30023, [][]byte{writerPubkey})
tests := []struct {
name string
access string
signer *p8k.Signer
pubkey []byte
expectAllow bool
}{
// Read tests
{
name: "reader can read",
access: "read",
signer: readerSigner,
pubkey: readerPubkey,
expectAllow: true,
},
{
name: "writer cannot read (not in read follows)",
access: "read",
signer: writerSigner,
pubkey: writerPubkey,
expectAllow: false,
},
{
name: "outsider cannot read",
access: "read",
signer: readerSigner,
pubkey: outsiderPubkey,
expectAllow: false,
},
// Write tests
{
name: "writer can write",
access: "write",
signer: writerSigner,
pubkey: writerPubkey,
expectAllow: true,
},
{
name: "reader cannot write (not in write follows)",
access: "write",
signer: readerSigner,
pubkey: readerPubkey,
expectAllow: false,
},
{
name: "outsider cannot write",
access: "write",
signer: readerSigner,
pubkey: outsiderPubkey,
expectAllow: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ev := createTestEvent(t, tt.signer, "test content", 30023)
allowed, err := policy.CheckPolicy(tt.access, ev, tt.pubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed != tt.expectAllow {
t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
}
})
}
}
// TestReadAllowWithReadFollowsWhitelist tests combining read_allow and read_follows_whitelist.
func TestReadAllowWithReadFollowsWhitelist(t *testing.T) {
_, curatorPubkey := generateTestKeypair(t)
_, followedPubkey := generateTestKeypair(t)
_, explicitPubkey := generateTestKeypair(t)
_, outsiderPubkey := generateTestKeypair(t)
authorSigner, _ := generateTestKeypair(t)
curatorHex := hex.Enc(curatorPubkey)
explicitHex := hex.Enc(explicitPubkey)
policyJSON := []byte(`{
"default_policy": "deny",
"rules": {
"1": {
"description": "Read via follows OR explicit allow",
"read_follows_whitelist": ["` + curatorHex + `"],
"read_allow": ["` + explicitHex + `"]
}
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
policy.UpdateRuleReadFollowsWhitelist(1, [][]byte{followedPubkey})
ev := createTestEvent(t, authorSigner, "test content", 1)
tests := []struct {
name string
pubkey []byte
expectAllow bool
}{
{
name: "followed user can read",
pubkey: followedPubkey,
expectAllow: true,
},
{
name: "explicit allow user can read",
pubkey: explicitPubkey,
expectAllow: true,
},
{
name: "curator can read (is whitelist pubkey)",
pubkey: curatorPubkey,
expectAllow: true,
},
{
name: "outsider denied",
pubkey: outsiderPubkey,
expectAllow: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
if err != nil {
t.Fatalf("CheckPolicy error: %v", err)
}
if allowed != tt.expectAllow {
t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
}
})
}
}
// TestGetAllFollowsWhitelistPubkeysDP tests the combined pubkey retrieval.
func TestGetAllFollowsWhitelistPubkeysDP(t *testing.T) {
read1 := "1111111111111111111111111111111111111111111111111111111111111111"
read2 := "2222222222222222222222222222222222222222222222222222222222222222"
write1 := "3333333333333333333333333333333333333333333333333333333333333333"
legacy := "4444444444444444444444444444444444444444444444444444444444444444"
policyJSON := []byte(`{
"default_policy": "allow",
"global": {
"read_follows_whitelist": ["` + read1 + `"],
"write_follows_whitelist": ["` + write1 + `"]
},
"rules": {
"1": {
"read_follows_whitelist": ["` + read2 + `"],
"follows_whitelist_admins": ["` + legacy + `"]
}
}
}`)
policy, err := New(policyJSON)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
allPubkeys := policy.GetAllFollowsWhitelistPubkeys()
if len(allPubkeys) != 4 {
t.Errorf("Expected 4 unique pubkeys, got %d", len(allPubkeys))
}
// Check each is present
pubkeySet := make(map[string]bool)
for _, pk := range allPubkeys {
pubkeySet[pk] = true
}
expected := []string{read1, read2, write1, legacy}
for _, exp := range expected {
if !pubkeySet[exp] {
t.Errorf("Expected pubkey %s not found", exp)
}
}
}