Browse Source
- Enhanced the HandleReq function to incorporate policy checks for privileged events, ensuring only authorized users can access sensitive data. - Introduced a new integration test suite for policy filtering, validating the behavior of event access based on user authentication and policy rules. - Added a script to automate the policy filter integration tests, improving testing efficiency and reliability. - Updated version to v0.20.2 to reflect the new features and improvements.main
17 changed files with 1445 additions and 2 deletions
@ -0,0 +1,319 @@
@@ -0,0 +1,319 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"flag" |
||||
"fmt" |
||||
"os" |
||||
"strings" |
||||
"time" |
||||
|
||||
"lol.mleku.dev/chk" |
||||
"lol.mleku.dev/log" |
||||
"next.orly.dev/pkg/crypto/p256k" |
||||
"next.orly.dev/pkg/encoders/event" |
||||
"next.orly.dev/pkg/encoders/filter" |
||||
"next.orly.dev/pkg/encoders/hex" |
||||
"next.orly.dev/pkg/encoders/kind" |
||||
"next.orly.dev/pkg/encoders/tag" |
||||
"next.orly.dev/pkg/protocol/ws" |
||||
) |
||||
|
||||
func main() { |
||||
var err error |
||||
url := flag.String("url", "ws://127.0.0.1:34568", "relay websocket URL") |
||||
allowedPubkeyHex := flag.String("allowed-pubkey", "", "hex-encoded allowed pubkey") |
||||
allowedSecHex := flag.String("allowed-sec", "", "hex-encoded allowed secret key") |
||||
unauthorizedPubkeyHex := flag.String("unauthorized-pubkey", "", "hex-encoded unauthorized pubkey") |
||||
unauthorizedSecHex := flag.String("unauthorized-sec", "", "hex-encoded unauthorized secret key") |
||||
timeout := flag.Duration("timeout", 10*time.Second, "operation timeout") |
||||
flag.Parse() |
||||
|
||||
if *allowedPubkeyHex == "" || *allowedSecHex == "" { |
||||
log.E.F("required flags: -allowed-pubkey and -allowed-sec") |
||||
os.Exit(1) |
||||
} |
||||
if *unauthorizedPubkeyHex == "" || *unauthorizedSecHex == "" { |
||||
log.E.F("required flags: -unauthorized-pubkey and -unauthorized-sec") |
||||
os.Exit(1) |
||||
} |
||||
|
||||
// Decode keys
|
||||
allowedSecBytes, err := hex.Dec(*allowedSecHex) |
||||
if err != nil { |
||||
log.E.F("failed to decode allowed secret key: %v", err) |
||||
os.Exit(1) |
||||
} |
||||
allowedSigner := &p256k.Signer{} |
||||
if err = allowedSigner.InitSec(allowedSecBytes); chk.E(err) { |
||||
log.E.F("failed to initialize allowed signer: %v", err) |
||||
os.Exit(1) |
||||
} |
||||
|
||||
unauthorizedSecBytes, err := hex.Dec(*unauthorizedSecHex) |
||||
if err != nil { |
||||
log.E.F("failed to decode unauthorized secret key: %v", err) |
||||
os.Exit(1) |
||||
} |
||||
unauthorizedSigner := &p256k.Signer{} |
||||
if err = unauthorizedSigner.InitSec(unauthorizedSecBytes); chk.E(err) { |
||||
log.E.F("failed to initialize unauthorized signer: %v", err) |
||||
os.Exit(1) |
||||
} |
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *timeout) |
||||
defer cancel() |
||||
|
||||
// Test 1: Authenticated as allowed pubkey - should work
|
||||
fmt.Println("Test 1: Publishing event 30520 with allowed pubkey (authenticated)...") |
||||
if err := testWriteEvent(ctx, *url, 30520, allowedSigner, allowedSigner); err != nil { |
||||
fmt.Printf("❌ FAILED: %v\n", err) |
||||
os.Exit(1) |
||||
} |
||||
fmt.Println("✅ PASSED: Event published successfully") |
||||
|
||||
// Test 2: Authenticated as allowed pubkey, then read event 10306 - should work
|
||||
// First publish an event, then read it
|
||||
fmt.Println("\nTest 2: Publishing and reading event 10306 with allowed pubkey (authenticated)...") |
||||
if err := testWriteEvent(ctx, *url, 10306, allowedSigner, allowedSigner); err != nil { |
||||
fmt.Printf("❌ FAILED to publish: %v\n", err) |
||||
os.Exit(1) |
||||
} |
||||
if err := testReadEvent(ctx, *url, 10306, allowedSigner); err != nil { |
||||
fmt.Printf("❌ FAILED to read: %v\n", err) |
||||
os.Exit(1) |
||||
} |
||||
fmt.Println("✅ PASSED: Event readable by allowed user") |
||||
|
||||
// Test 3: Unauthenticated request - should be blocked
|
||||
fmt.Println("\nTest 3: Publishing event 30520 without authentication...") |
||||
if err := testWriteEventUnauthenticated(ctx, *url, 30520, allowedSigner); err != nil { |
||||
fmt.Printf("✅ PASSED: Event correctly blocked (expected): %v\n", err) |
||||
} else { |
||||
fmt.Println("❌ FAILED: Event was allowed when it should have been blocked") |
||||
os.Exit(1) |
||||
} |
||||
|
||||
// Test 4: Authenticated as unauthorized pubkey - should be blocked
|
||||
fmt.Println("\nTest 4: Publishing event 30520 with unauthorized pubkey...") |
||||
if err := testWriteEvent(ctx, *url, 30520, unauthorizedSigner, unauthorizedSigner); err != nil { |
||||
fmt.Printf("✅ PASSED: Event correctly blocked (expected): %v\n", err) |
||||
} else { |
||||
fmt.Println("❌ FAILED: Event was allowed when it should have been blocked") |
||||
os.Exit(1) |
||||
} |
||||
|
||||
// Test 5: Read event 10306 without authentication - should be blocked
|
||||
// Event was published in test 2, so it exists in the database
|
||||
fmt.Println("\nTest 5: Reading event 10306 without authentication (should be blocked)...") |
||||
// Wait a bit to ensure event is stored
|
||||
time.Sleep(500 * time.Millisecond) |
||||
// If no error is returned, that means no events were received (which is correct)
|
||||
// If an error is returned, it means an event was received (which is wrong)
|
||||
if err := testReadEventUnauthenticated(ctx, *url, 10306); err != nil { |
||||
// If we got an error about receiving an event, that's a failure
|
||||
if strings.Contains(err.Error(), "unexpected event received") { |
||||
fmt.Printf("❌ FAILED: %v\n", err) |
||||
os.Exit(1) |
||||
} |
||||
// Other errors (like connection errors) are also failures
|
||||
fmt.Printf("❌ FAILED: Unexpected error: %v\n", err) |
||||
os.Exit(1) |
||||
} |
||||
fmt.Println("✅ PASSED: No events received (correctly filtered by policy)") |
||||
|
||||
// Test 6: Read event 10306 with unauthorized pubkey - should be blocked
|
||||
fmt.Println("\nTest 6: Reading event 10306 with unauthorized pubkey (should be blocked)...") |
||||
// If no error is returned, that means no events were received (which is correct)
|
||||
// If an error is returned about receiving an event, that's a failure
|
||||
if err := testReadEvent(ctx, *url, 10306, unauthorizedSigner); err != nil { |
||||
// Connection/subscription errors are failures
|
||||
fmt.Printf("❌ FAILED: Unexpected error: %v\n", err) |
||||
os.Exit(1) |
||||
} |
||||
fmt.Println("✅ PASSED: No events received (correctly filtered by policy)") |
||||
|
||||
fmt.Println("\n✅ All tests passed!") |
||||
} |
||||
|
||||
func testWriteEvent(ctx context.Context, url string, kindNum uint16, eventSigner, authSigner *p256k.Signer) error { |
||||
rl, err := ws.RelayConnect(ctx, url) |
||||
if err != nil { |
||||
return fmt.Errorf("connect error: %w", err) |
||||
} |
||||
defer rl.Close() |
||||
|
||||
// Send a REQ first to trigger AUTH challenge (when AuthToWrite is enabled)
|
||||
// This is needed because challenges are sent on REQ, not on connect
|
||||
limit := uint(1) |
||||
ff := filter.NewS(&filter.F{ |
||||
Kinds: kind.NewS(kind.New(kindNum)), |
||||
Limit: &limit, |
||||
}) |
||||
sub, err := rl.Subscribe(ctx, ff) |
||||
if err != nil { |
||||
return fmt.Errorf("subscription error (may be expected): %w", err) |
||||
} |
||||
// Wait a bit for challenge to arrive
|
||||
time.Sleep(500 * time.Millisecond) |
||||
sub.Unsub() |
||||
|
||||
// Authenticate
|
||||
if err = rl.Auth(ctx, authSigner); err != nil { |
||||
return fmt.Errorf("auth error: %w", err) |
||||
} |
||||
|
||||
// Create and sign event
|
||||
ev := &event.E{ |
||||
CreatedAt: time.Now().Unix(), |
||||
Kind: kind.K{K: kindNum}.K, |
||||
Tags: tag.NewS(), |
||||
Content: []byte(fmt.Sprintf("test event kind %d", kindNum)), |
||||
} |
||||
// Add p tag for privileged check
|
||||
pTag := tag.NewFromAny("p", hex.Enc(authSigner.Pub())) |
||||
ev.Tags.Append(pTag) |
||||
|
||||
// Add d tag for addressable events (kinds 30000-39999)
|
||||
if kindNum >= 30000 && kindNum < 40000 { |
||||
dTag := tag.NewFromAny("d", "test") |
||||
ev.Tags.Append(dTag) |
||||
} |
||||
|
||||
if err = ev.Sign(eventSigner); err != nil { |
||||
return fmt.Errorf("sign error: %w", err) |
||||
} |
||||
|
||||
// Publish
|
||||
if err = rl.Publish(ctx, ev); err != nil { |
||||
return fmt.Errorf("publish error: %w", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func testWriteEventUnauthenticated(ctx context.Context, url string, kindNum uint16, eventSigner *p256k.Signer) error { |
||||
rl, err := ws.RelayConnect(ctx, url) |
||||
if err != nil { |
||||
return fmt.Errorf("connect error: %w", err) |
||||
} |
||||
defer rl.Close() |
||||
|
||||
// Do NOT authenticate
|
||||
|
||||
// Create and sign event
|
||||
ev := &event.E{ |
||||
CreatedAt: time.Now().Unix(), |
||||
Kind: kind.K{K: kindNum}.K, |
||||
Tags: tag.NewS(), |
||||
Content: []byte(fmt.Sprintf("test event kind %d (unauthenticated)", kindNum)), |
||||
} |
||||
|
||||
// Add d tag for addressable events (kinds 30000-39999)
|
||||
if kindNum >= 30000 && kindNum < 40000 { |
||||
dTag := tag.NewFromAny("d", "test") |
||||
ev.Tags.Append(dTag) |
||||
} |
||||
|
||||
if err = ev.Sign(eventSigner); err != nil { |
||||
return fmt.Errorf("sign error: %w", err) |
||||
} |
||||
|
||||
// Publish (should fail)
|
||||
if err = rl.Publish(ctx, ev); err != nil { |
||||
return fmt.Errorf("publish error (expected): %w", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func testReadEvent(ctx context.Context, url string, kindNum uint16, authSigner *p256k.Signer) error { |
||||
rl, err := ws.RelayConnect(ctx, url) |
||||
if err != nil { |
||||
return fmt.Errorf("connect error: %w", err) |
||||
} |
||||
defer rl.Close() |
||||
|
||||
// Send a REQ first to trigger AUTH challenge (when AuthToWrite is enabled)
|
||||
// Then authenticate
|
||||
ff := filter.NewS(&filter.F{ |
||||
Kinds: kind.NewS(kind.New(kindNum)), |
||||
}) |
||||
sub, err := rl.Subscribe(ctx, ff) |
||||
if err != nil { |
||||
return fmt.Errorf("subscription error: %w", err) |
||||
} |
||||
// Wait a bit for challenge to arrive
|
||||
time.Sleep(500 * time.Millisecond) |
||||
|
||||
// Authenticate
|
||||
if err = rl.Auth(ctx, authSigner); err != nil { |
||||
sub.Unsub() |
||||
return fmt.Errorf("auth error: %w", err) |
||||
} |
||||
|
||||
// Wait for events or timeout
|
||||
// If we receive any events, return nil (success)
|
||||
// If we don't receive events, also return nil (no events found, which may be expected)
|
||||
select { |
||||
case ev := <-sub.Events: |
||||
if ev != nil { |
||||
sub.Unsub() |
||||
return nil // Event received
|
||||
} |
||||
case <-sub.EndOfStoredEvents: |
||||
// EOSE received, no more events
|
||||
sub.Unsub() |
||||
return nil |
||||
case <-time.After(5 * time.Second): |
||||
// No events received - this might be OK if no events exist or they're filtered
|
||||
sub.Unsub() |
||||
return nil |
||||
case <-ctx.Done(): |
||||
sub.Unsub() |
||||
return ctx.Err() |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func testReadEventUnauthenticated(ctx context.Context, url string, kindNum uint16) error { |
||||
rl, err := ws.RelayConnect(ctx, url) |
||||
if err != nil { |
||||
return fmt.Errorf("connect error: %w", err) |
||||
} |
||||
defer rl.Close() |
||||
|
||||
// Do NOT authenticate
|
||||
|
||||
// Subscribe to events
|
||||
ff := filter.NewS(&filter.F{ |
||||
Kinds: kind.NewS(kind.New(kindNum)), |
||||
}) |
||||
|
||||
sub, err := rl.Subscribe(ctx, ff) |
||||
if err != nil { |
||||
return fmt.Errorf("subscription error (may be expected): %w", err) |
||||
} |
||||
defer sub.Unsub() |
||||
|
||||
// Wait for events or timeout
|
||||
// If we receive any events, that's a failure (should be blocked)
|
||||
select { |
||||
case ev := <-sub.Events: |
||||
if ev != nil { |
||||
return fmt.Errorf("unexpected event received: should have been blocked by policy (event ID: %s)", hex.Enc(ev.ID)) |
||||
} |
||||
case <-sub.EndOfStoredEvents: |
||||
// EOSE received, no events (this is expected for unauthenticated privileged events)
|
||||
return nil |
||||
case <-time.After(5 * time.Second): |
||||
// No events received - this is expected for unauthenticated requests
|
||||
return nil |
||||
case <-ctx.Done(): |
||||
return ctx.Err() |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
@ -0,0 +1,516 @@
@@ -0,0 +1,516 @@
|
||||
package policy |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
"testing" |
||||
"time" |
||||
|
||||
"lol.mleku.dev/chk" |
||||
"next.orly.dev/pkg/crypto/p256k" |
||||
"next.orly.dev/pkg/encoders/event" |
||||
"next.orly.dev/pkg/encoders/hex" |
||||
"next.orly.dev/pkg/encoders/kind" |
||||
"next.orly.dev/pkg/encoders/tag" |
||||
) |
||||
|
||||
// TestPolicyIntegration runs the relay with policy enabled and tests event filtering
|
||||
func TestPolicyIntegration(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
|
||||
// Generate test keys
|
||||
allowedSigner := &p256k.Signer{} |
||||
if err := allowedSigner.Generate(); chk.E(err) { |
||||
t.Fatalf("Failed to generate allowed signer: %v", err) |
||||
} |
||||
allowedPubkeyHex := hex.Enc(allowedSigner.Pub()) |
||||
|
||||
unauthorizedSigner := &p256k.Signer{} |
||||
if err := unauthorizedSigner.Generate(); chk.E(err) { |
||||
t.Fatalf("Failed to generate unauthorized signer: %v", err) |
||||
} |
||||
|
||||
// Create temporary directory for policy config
|
||||
tempDir := t.TempDir() |
||||
configDir := filepath.Join(tempDir, "ORLY_TEST") |
||||
if err := os.MkdirAll(configDir, 0755); chk.E(err) { |
||||
t.Fatalf("Failed to create config directory: %v", err) |
||||
} |
||||
|
||||
// Create policy JSON with generated keys
|
||||
policyJSON := map[string]interface{}{ |
||||
"kind": map[string]interface{}{ |
||||
"whitelist": []int{4678, 10306, 30520, 30919}, |
||||
}, |
||||
"rules": map[string]interface{}{ |
||||
"4678": map[string]interface{}{ |
||||
"description": "Zenotp message events", |
||||
"script": filepath.Join(configDir, "validate4678.js"), // Won't exist, should fall back to default
|
||||
"privileged": true, |
||||
}, |
||||
"10306": map[string]interface{}{ |
||||
"description": "End user whitelist changes", |
||||
"read_allow": []string{allowedPubkeyHex}, |
||||
"privileged": true, |
||||
}, |
||||
"30520": map[string]interface{}{ |
||||
"description": "Zenotp events", |
||||
"write_allow": []string{allowedPubkeyHex}, |
||||
"privileged": true, |
||||
}, |
||||
"30919": map[string]interface{}{ |
||||
"description": "Zenotp events", |
||||
"write_allow": []string{allowedPubkeyHex}, |
||||
"privileged": true, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
policyJSONBytes, err := json.MarshalIndent(policyJSON, "", " ") |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal policy JSON: %v", err) |
||||
} |
||||
|
||||
policyPath := filepath.Join(configDir, "policy.json") |
||||
if err := os.WriteFile(policyPath, policyJSONBytes, 0644); chk.E(err) { |
||||
t.Fatalf("Failed to write policy file: %v", err) |
||||
} |
||||
|
||||
// Create events with proper signatures
|
||||
// Event 1: Kind 30520 with allowed pubkey (should be allowed)
|
||||
event30520Allowed := event.New() |
||||
event30520Allowed.CreatedAt = time.Now().Unix() |
||||
event30520Allowed.Kind = kind.K{K: 30520}.K |
||||
event30520Allowed.Content = []byte("test event 30520") |
||||
event30520Allowed.Tags = tag.NewS() |
||||
addPTag(event30520Allowed, allowedSigner.Pub()) // Add p tag for privileged check
|
||||
if err := event30520Allowed.Sign(allowedSigner); chk.E(err) { |
||||
t.Fatalf("Failed to sign event30520Allowed: %v", err) |
||||
} |
||||
|
||||
// Event 2: Kind 30520 with unauthorized pubkey (should be denied)
|
||||
event30520Unauthorized := event.New() |
||||
event30520Unauthorized.CreatedAt = time.Now().Unix() |
||||
event30520Unauthorized.Kind = kind.K{K: 30520}.K |
||||
event30520Unauthorized.Content = []byte("test event 30520 unauthorized") |
||||
event30520Unauthorized.Tags = tag.NewS() |
||||
if err := event30520Unauthorized.Sign(unauthorizedSigner); chk.E(err) { |
||||
t.Fatalf("Failed to sign event30520Unauthorized: %v", err) |
||||
} |
||||
|
||||
// Event 3: Kind 10306 with allowed pubkey (should be readable by allowed user)
|
||||
event10306Allowed := event.New() |
||||
event10306Allowed.CreatedAt = time.Now().Unix() |
||||
event10306Allowed.Kind = kind.K{K: 10306}.K |
||||
event10306Allowed.Content = []byte("test event 10306") |
||||
event10306Allowed.Tags = tag.NewS() |
||||
addPTag(event10306Allowed, allowedSigner.Pub()) // Add p tag for privileged check
|
||||
if err := event10306Allowed.Sign(allowedSigner); chk.E(err) { |
||||
t.Fatalf("Failed to sign event10306Allowed: %v", err) |
||||
} |
||||
|
||||
// Event 4: Kind 4678 with allowed pubkey (script-based, should fall back to default)
|
||||
event4678Allowed := event.New() |
||||
event4678Allowed.CreatedAt = time.Now().Unix() |
||||
event4678Allowed.Kind = kind.K{K: 4678}.K |
||||
event4678Allowed.Content = []byte("test event 4678") |
||||
event4678Allowed.Tags = tag.NewS() |
||||
addPTag(event4678Allowed, allowedSigner.Pub()) // Add p tag for privileged check
|
||||
if err := event4678Allowed.Sign(allowedSigner); chk.E(err) { |
||||
t.Fatalf("Failed to sign event4678Allowed: %v", err) |
||||
} |
||||
|
||||
// Test policy loading
|
||||
policy, err := New(policyJSONBytes) |
||||
if err != nil { |
||||
t.Fatalf("Failed to create policy: %v", err) |
||||
} |
||||
|
||||
// Verify policy loaded correctly
|
||||
if len(policy.Rules) != 4 { |
||||
t.Errorf("Expected 4 rules, got %d", len(policy.Rules)) |
||||
} |
||||
|
||||
// Test policy checks directly
|
||||
t.Run("policy checks", func(t *testing.T) { |
||||
// Test 1: Event 30520 with allowed pubkey should be allowed
|
||||
allowed, err := policy.CheckPolicy("write", event30520Allowed, allowedSigner.Pub(), "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
} |
||||
if !allowed { |
||||
t.Error("Expected event30520Allowed to be allowed") |
||||
} |
||||
|
||||
// Test 2: Event 30520 with unauthorized pubkey should be denied
|
||||
allowed, err = policy.CheckPolicy("write", event30520Unauthorized, unauthorizedSigner.Pub(), "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
} |
||||
if allowed { |
||||
t.Error("Expected event30520Unauthorized to be denied") |
||||
} |
||||
|
||||
// Test 3: Event 10306 should be readable by allowed user
|
||||
allowed, err = policy.CheckPolicy("read", event10306Allowed, allowedSigner.Pub(), "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
} |
||||
if !allowed { |
||||
t.Error("Expected event10306Allowed to be readable by allowed user") |
||||
} |
||||
|
||||
// Test 4: Event 10306 should NOT be readable by unauthorized user
|
||||
allowed, err = policy.CheckPolicy("read", event10306Allowed, unauthorizedSigner.Pub(), "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
} |
||||
if allowed { |
||||
t.Error("Expected event10306Allowed to be denied for unauthorized user") |
||||
} |
||||
|
||||
// Test 5: Event 10306 should NOT be readable without authentication
|
||||
allowed, err = policy.CheckPolicy("read", event10306Allowed, nil, "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
} |
||||
if allowed { |
||||
t.Error("Expected event10306Allowed to be denied without authentication (privileged)") |
||||
} |
||||
|
||||
// Test 6: Event 30520 should NOT be writable without authentication
|
||||
allowed, err = policy.CheckPolicy("write", event30520Allowed, nil, "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
} |
||||
if allowed { |
||||
t.Error("Expected event30520Allowed to be denied without authentication (privileged)") |
||||
} |
||||
|
||||
// Test 7: Event 4678 should fall back to default policy (allow) when script not running
|
||||
allowed, err = policy.CheckPolicy("write", event4678Allowed, allowedSigner.Pub(), "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
} |
||||
if !allowed { |
||||
t.Error("Expected event4678Allowed to be allowed when script not running (falls back to default)") |
||||
} |
||||
|
||||
// Test 8: Event 4678 should be denied without authentication (privileged check)
|
||||
allowed, err = policy.CheckPolicy("write", event4678Allowed, nil, "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
} |
||||
if allowed { |
||||
t.Error("Expected event4678Allowed to be denied without authentication (privileged)") |
||||
} |
||||
}) |
||||
|
||||
// Test with relay simulation (checking log output)
|
||||
t.Run("relay simulation", func(t *testing.T) { |
||||
// Note: We can't easily capture log output in tests, so we just verify
|
||||
// that policy checks work correctly
|
||||
|
||||
// Simulate policy checks that would happen in relay
|
||||
// First, publish events (simulate write checks)
|
||||
checks := []struct { |
||||
name string |
||||
event *event.E |
||||
loggedInPubkey []byte |
||||
access string |
||||
shouldAllow bool |
||||
shouldLog string // Expected log message substring, empty means no specific log expected
|
||||
}{ |
||||
{ |
||||
name: "write 30520 with allowed pubkey", |
||||
event: event30520Allowed, |
||||
loggedInPubkey: allowedSigner.Pub(), |
||||
access: "write", |
||||
shouldAllow: true, |
||||
}, |
||||
{ |
||||
name: "write 30520 with unauthorized pubkey", |
||||
event: event30520Unauthorized, |
||||
loggedInPubkey: unauthorizedSigner.Pub(), |
||||
access: "write", |
||||
shouldAllow: false, |
||||
}, |
||||
{ |
||||
name: "read 10306 with allowed pubkey", |
||||
event: event10306Allowed, |
||||
loggedInPubkey: allowedSigner.Pub(), |
||||
access: "read", |
||||
shouldAllow: true, |
||||
}, |
||||
{ |
||||
name: "read 10306 with unauthorized pubkey", |
||||
event: event10306Allowed, |
||||
loggedInPubkey: unauthorizedSigner.Pub(), |
||||
access: "read", |
||||
shouldAllow: false, |
||||
}, |
||||
{ |
||||
name: "read 10306 without authentication", |
||||
event: event10306Allowed, |
||||
loggedInPubkey: nil, |
||||
access: "read", |
||||
shouldAllow: false, |
||||
}, |
||||
{ |
||||
name: "write 30520 without authentication", |
||||
event: event30520Allowed, |
||||
loggedInPubkey: nil, |
||||
access: "write", |
||||
shouldAllow: false, |
||||
}, |
||||
{ |
||||
name: "write 4678 with allowed pubkey", |
||||
event: event4678Allowed, |
||||
loggedInPubkey: allowedSigner.Pub(), |
||||
access: "write", |
||||
shouldAllow: true, |
||||
shouldLog: "", // Should not log "policy rule is inactive" if script is not configured
|
||||
}, |
||||
} |
||||
|
||||
for _, check := range checks { |
||||
t.Run(check.name, func(t *testing.T) { |
||||
allowed, err := policy.CheckPolicy(check.access, check.event, check.loggedInPubkey, "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
return |
||||
} |
||||
if allowed != check.shouldAllow { |
||||
t.Errorf("Expected allowed=%v, got %v", check.shouldAllow, allowed) |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
|
||||
// Test event IDs are regenerated correctly after signing
|
||||
t.Run("event ID regeneration", func(t *testing.T) { |
||||
// Create a new event, sign it, then verify ID is correct
|
||||
testEvent := event.New() |
||||
testEvent.CreatedAt = time.Now().Unix() |
||||
testEvent.Kind = kind.K{K: 30520}.K |
||||
testEvent.Content = []byte("test content") |
||||
testEvent.Tags = tag.NewS() |
||||
|
||||
// Sign the event
|
||||
if err := testEvent.Sign(allowedSigner); chk.E(err) { |
||||
t.Fatalf("Failed to sign test event: %v", err) |
||||
} |
||||
|
||||
// Verify event ID is correct (should be SHA256 of serialized event)
|
||||
if len(testEvent.ID) != 32 { |
||||
t.Errorf("Expected event ID to be 32 bytes, got %d", len(testEvent.ID)) |
||||
} |
||||
|
||||
// Verify signature is correct
|
||||
if len(testEvent.Sig) != 64 { |
||||
t.Errorf("Expected event signature to be 64 bytes, got %d", len(testEvent.Sig)) |
||||
} |
||||
|
||||
// Verify signature validates using event's Verify method
|
||||
valid, err := testEvent.Verify() |
||||
if err != nil { |
||||
t.Errorf("Failed to verify signature: %v", err) |
||||
} |
||||
if !valid { |
||||
t.Error("Event signature verification failed") |
||||
} |
||||
}) |
||||
|
||||
// Test WebSocket client simulation (for future integration)
|
||||
t.Run("websocket client simulation", func(t *testing.T) { |
||||
// This test simulates what would happen if we connected via WebSocket
|
||||
// For now, we'll just verify the events can be serialized correctly
|
||||
|
||||
events := []*event.E{ |
||||
event30520Allowed, |
||||
event30520Unauthorized, |
||||
event10306Allowed, |
||||
event4678Allowed, |
||||
} |
||||
|
||||
for i, ev := range events { |
||||
t.Run(fmt.Sprintf("event_%d", i), func(t *testing.T) { |
||||
// Serialize event
|
||||
serialized := ev.Serialize() |
||||
if len(serialized) == 0 { |
||||
t.Error("Event serialization returned empty") |
||||
} |
||||
|
||||
// Verify event can be parsed back (simplified check)
|
||||
if len(ev.ID) != 32 { |
||||
t.Errorf("Event ID length incorrect: %d", len(ev.ID)) |
||||
} |
||||
if len(ev.Pubkey) != 32 { |
||||
t.Errorf("Event pubkey length incorrect: %d", len(ev.Pubkey)) |
||||
} |
||||
if len(ev.Sig) != 64 { |
||||
t.Errorf("Event signature length incorrect: %d", len(ev.Sig)) |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
// TestPolicyWithRelay creates a comprehensive test that simulates relay behavior
|
||||
func TestPolicyWithRelay(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("skipping integration test") |
||||
} |
||||
|
||||
// Generate keys
|
||||
allowedSigner := &p256k.Signer{} |
||||
if err := allowedSigner.Generate(); chk.E(err) { |
||||
t.Fatalf("Failed to generate allowed signer: %v", err) |
||||
} |
||||
allowedPubkeyHex := hex.Enc(allowedSigner.Pub()) |
||||
|
||||
unauthorizedSigner := &p256k.Signer{} |
||||
if err := unauthorizedSigner.Generate(); chk.E(err) { |
||||
t.Fatalf("Failed to generate unauthorized signer: %v", err) |
||||
} |
||||
|
||||
// Create policy JSON
|
||||
policyJSON := map[string]interface{}{ |
||||
"kind": map[string]interface{}{ |
||||
"whitelist": []int{4678, 10306, 30520, 30919}, |
||||
}, |
||||
"rules": map[string]interface{}{ |
||||
"10306": map[string]interface{}{ |
||||
"description": "End user whitelist changes", |
||||
"read_allow": []string{allowedPubkeyHex}, |
||||
"privileged": true, |
||||
}, |
||||
"30520": map[string]interface{}{ |
||||
"description": "Zenotp events", |
||||
"write_allow": []string{allowedPubkeyHex}, |
||||
"privileged": true, |
||||
}, |
||||
"30919": map[string]interface{}{ |
||||
"description": "Zenotp events", |
||||
"write_allow": []string{allowedPubkeyHex}, |
||||
"privileged": true, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
policyJSONBytes, err := json.Marshal(policyJSON) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal policy JSON: %v", err) |
||||
} |
||||
|
||||
policy, err := New(policyJSONBytes) |
||||
if err != nil { |
||||
t.Fatalf("Failed to create policy: %v", err) |
||||
} |
||||
|
||||
// Create test event (kind 30520) with allowed pubkey
|
||||
testEvent := event.New() |
||||
testEvent.CreatedAt = time.Now().Unix() |
||||
testEvent.Kind = kind.K{K: 30520}.K |
||||
testEvent.Content = []byte("test content") |
||||
testEvent.Tags = tag.NewS() |
||||
addPTag(testEvent, allowedSigner.Pub()) |
||||
if err := testEvent.Sign(allowedSigner); chk.E(err) { |
||||
t.Fatalf("Failed to sign test event: %v", err) |
||||
} |
||||
|
||||
// Test scenarios
|
||||
scenarios := []struct { |
||||
name string |
||||
loggedInPubkey []byte |
||||
expectedResult bool |
||||
description string |
||||
}{ |
||||
{ |
||||
name: "authenticated as allowed pubkey", |
||||
loggedInPubkey: allowedSigner.Pub(), |
||||
expectedResult: true, |
||||
description: "Should allow when authenticated as allowed pubkey", |
||||
}, |
||||
{ |
||||
name: "unauthenticated", |
||||
loggedInPubkey: nil, |
||||
expectedResult: false, |
||||
description: "Should deny when not authenticated (privileged check)", |
||||
}, |
||||
{ |
||||
name: "authenticated as different pubkey", |
||||
loggedInPubkey: unauthorizedSigner.Pub(), |
||||
expectedResult: false, |
||||
description: "Should deny when authenticated as different pubkey", |
||||
}, |
||||
} |
||||
|
||||
for _, scenario := range scenarios { |
||||
t.Run(scenario.name, func(t *testing.T) { |
||||
allowed, err := policy.CheckPolicy("write", testEvent, scenario.loggedInPubkey, "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
return |
||||
} |
||||
if allowed != scenario.expectedResult { |
||||
t.Errorf("%s: Expected allowed=%v, got %v", scenario.description, scenario.expectedResult, allowed) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
// Test read access for kind 10306
|
||||
readEvent := event.New() |
||||
readEvent.CreatedAt = time.Now().Unix() |
||||
readEvent.Kind = kind.K{K: 10306}.K |
||||
readEvent.Content = []byte("test read event") |
||||
readEvent.Tags = tag.NewS() |
||||
addPTag(readEvent, allowedSigner.Pub()) |
||||
if err := readEvent.Sign(allowedSigner); chk.E(err) { |
||||
t.Fatalf("Failed to sign read event: %v", err) |
||||
} |
||||
|
||||
readScenarios := []struct { |
||||
name string |
||||
loggedInPubkey []byte |
||||
expectedResult bool |
||||
description string |
||||
}{ |
||||
{ |
||||
name: "read authenticated as allowed pubkey", |
||||
loggedInPubkey: allowedSigner.Pub(), |
||||
expectedResult: true, |
||||
description: "Should allow read when authenticated as allowed pubkey", |
||||
}, |
||||
{ |
||||
name: "read unauthenticated", |
||||
loggedInPubkey: nil, |
||||
expectedResult: false, |
||||
description: "Should deny read when not authenticated (privileged check)", |
||||
}, |
||||
{ |
||||
name: "read authenticated as different pubkey", |
||||
loggedInPubkey: unauthorizedSigner.Pub(), |
||||
expectedResult: false, |
||||
description: "Should deny read when authenticated as different pubkey", |
||||
}, |
||||
} |
||||
|
||||
for _, scenario := range readScenarios { |
||||
t.Run(scenario.name, func(t *testing.T) { |
||||
allowed, err := policy.CheckPolicy("read", readEvent, scenario.loggedInPubkey, "127.0.0.1") |
||||
if err != nil { |
||||
t.Errorf("Unexpected error: %v", err) |
||||
return |
||||
} |
||||
if allowed != scenario.expectedResult { |
||||
t.Errorf("%s: Expected allowed=%v, got %v", scenario.description, scenario.expectedResult, allowed) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
@ -1 +0,0 @@
@@ -1 +0,0 @@
|
||||
Subproject commit 0cdc758a56360bf58a851fe91085a327ec97685a |
||||
Loading…
Reference in new issue