Browse Source
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.main
8 changed files with 1570 additions and 298 deletions
@ -0,0 +1,686 @@ |
|||||||
|
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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue