1 package policy
2 3 import (
4 "testing"
5 6 "next.orly.dev/pkg/nostr/encoders/hex"
7 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
8 )
9 10 // TestReadAllowLogic tests the correct semantics of ReadAllow:
11 // ReadAllow should control WHO can read events of a kind,
12 // not which event authors can be read.
13 func TestReadAllowLogic(t *testing.T) {
14 // Set up: Create 3 different users
15 // - alice: will author an event
16 // - bob: will be allowed to read (in ReadAllow list)
17 // - charlie: will NOT be allowed to read (not in ReadAllow list)
18 19 aliceSigner, alicePubkey := generateTestKeypair(t)
20 _, bobPubkey := generateTestKeypair(t)
21 _, charliePubkey := generateTestKeypair(t)
22 23 // Create an event authored by Alice (kind 30166)
24 aliceEvent := createTestEvent(t, aliceSigner, "server heartbeat", 30166)
25 26 // Create policy: Only Bob can READ kind 30166 events
27 policy := &P{
28 DefaultPolicy: "allow",
29 rules: map[int]Rule{
30 30166: {
31 Description: "Private server heartbeat events",
32 AccessControl: AccessControl{
33 ReadAllow: []string{hex.Enc(bobPubkey)}, // Only Bob can read
34 },
35 },
36 },
37 }
38 39 // Test 1: Bob (who is in ReadAllow) should be able to READ Alice's event
40 t.Run("allowed_reader_can_read", func(t *testing.T) {
41 allowed, err := policy.CheckPolicy("read", aliceEvent, bobPubkey, "127.0.0.1")
42 if err != nil {
43 t.Fatalf("Unexpected error: %v", err)
44 }
45 if !allowed {
46 t.Error("Bob should be allowed to READ Alice's event (Bob is in ReadAllow list)")
47 }
48 })
49 50 // Test 2: Charlie (who is NOT in ReadAllow) should NOT be able to READ Alice's event
51 t.Run("disallowed_reader_cannot_read", func(t *testing.T) {
52 allowed, err := policy.CheckPolicy("read", aliceEvent, charliePubkey, "127.0.0.1")
53 if err != nil {
54 t.Fatalf("Unexpected error: %v", err)
55 }
56 if allowed {
57 t.Error("Charlie should NOT be allowed to READ Alice's event (Charlie is not in ReadAllow list)")
58 }
59 })
60 61 // Test 3: Alice (the author) should NOT be able to READ her own event if she's not in ReadAllow
62 t.Run("author_not_in_readallow_cannot_read", func(t *testing.T) {
63 allowed, err := policy.CheckPolicy("read", aliceEvent, alicePubkey, "127.0.0.1")
64 if err != nil {
65 t.Fatalf("Unexpected error: %v", err)
66 }
67 if allowed {
68 t.Error("Alice should NOT be allowed to READ her own event (Alice is not in ReadAllow list)")
69 }
70 })
71 72 // Test 4: Unauthenticated user should NOT be able to READ
73 t.Run("unauthenticated_cannot_read", func(t *testing.T) {
74 allowed, err := policy.CheckPolicy("read", aliceEvent, nil, "127.0.0.1")
75 if err != nil {
76 t.Fatalf("Unexpected error: %v", err)
77 }
78 if allowed {
79 t.Error("Unauthenticated user should NOT be allowed to READ (not in ReadAllow list)")
80 }
81 })
82 }
83 84 // TestReadDenyLogic tests the correct semantics of ReadDeny:
85 // ReadDeny should control WHO cannot read events of a kind,
86 // not which event authors cannot be read.
87 func TestReadDenyLogic(t *testing.T) {
88 // Set up: Create 3 different users
89 aliceSigner, alicePubkey := generateTestKeypair(t)
90 _, bobPubkey := generateTestKeypair(t)
91 _, charliePubkey := generateTestKeypair(t)
92 93 // Create an event authored by Alice
94 aliceEvent := createTestEvent(t, aliceSigner, "test content", 1)
95 96 // Create policy: Charlie cannot READ kind 1 events (but others can)
97 policy := &P{
98 DefaultPolicy: "allow",
99 rules: map[int]Rule{
100 1: {
101 Description: "Test events",
102 AccessControl: AccessControl{
103 ReadDeny: []string{hex.Enc(charliePubkey)}, // Charlie cannot read
104 },
105 },
106 },
107 }
108 109 // Test 1: Bob (who is NOT in ReadDeny) should be able to READ Alice's event
110 t.Run("non_denied_reader_can_read", func(t *testing.T) {
111 allowed, err := policy.CheckPolicy("read", aliceEvent, bobPubkey, "127.0.0.1")
112 if err != nil {
113 t.Fatalf("Unexpected error: %v", err)
114 }
115 if !allowed {
116 t.Error("Bob should be allowed to READ Alice's event (Bob is not in ReadDeny list)")
117 }
118 })
119 120 // Test 2: Charlie (who IS in ReadDeny) should NOT be able to READ Alice's event
121 t.Run("denied_reader_cannot_read", func(t *testing.T) {
122 allowed, err := policy.CheckPolicy("read", aliceEvent, charliePubkey, "127.0.0.1")
123 if err != nil {
124 t.Fatalf("Unexpected error: %v", err)
125 }
126 if allowed {
127 t.Error("Charlie should NOT be allowed to READ Alice's event (Charlie is in ReadDeny list)")
128 }
129 })
130 131 // Test 3: Alice (the author, not in ReadDeny) should be able to READ her own event
132 t.Run("author_not_denied_can_read", func(t *testing.T) {
133 allowed, err := policy.CheckPolicy("read", aliceEvent, alicePubkey, "127.0.0.1")
134 if err != nil {
135 t.Fatalf("Unexpected error: %v", err)
136 }
137 if !allowed {
138 t.Error("Alice should be allowed to READ her own event (Alice is not in ReadDeny list)")
139 }
140 })
141 }
142 143 // TestSamplePolicyFromUser tests the exact policy configuration provided by the user
144 func TestSamplePolicyFromUser(t *testing.T) {
145 policyJSON := []byte(`{
146 "kind": {
147 "whitelist": [4678, 10306, 30520, 30919, 30166]
148 },
149 "rules": {
150 "4678": {
151 "description": "Zenotp message events",
152 "write_allow": [
153 "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5",
154 "e4101949fb0367c72f5105fc9bd810cde0e0e0f950da26c1f47a6af5f77ded31",
155 "3f5fefcdc3fb41f3b299732acad7dc9c3649e8bde97d4f238380dde547b5e0e0"
156 ],
157 "privileged": true
158 },
159 "10306": {
160 "description": "End user whitelist change requests",
161 "read_allow": [
162 "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
163 ],
164 "privileged": true
165 },
166 "30520": {
167 "description": "End user whitelist events",
168 "write_allow": [
169 "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
170 ],
171 "privileged": true
172 },
173 "30919": {
174 "description": "Customer indexing events",
175 "write_allow": [
176 "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
177 ],
178 "privileged": true
179 },
180 "30166": {
181 "description": "Private server heartbeat events",
182 "write_allow": [
183 "4d13154d82477a2d2e07a5c0d52def9035fdf379ae87cd6f0a5fb87801a4e5e4",
184 "e400106ed10310ea28b039e81824265434bf86ece58722655c7a98f894406112"
185 ],
186 "read_allow": [
187 "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5",
188 "4d13154d82477a2d2e07a5c0d52def9035fdf379ae87cd6f0a5fb87801a4e5e4",
189 "e400106ed10310ea28b039e81824265434bf86ece58722655c7a98f894406112"
190 ]
191 }
192 }
193 }`)
194 195 policy, err := New(policyJSON)
196 if err != nil {
197 t.Fatalf("Failed to create policy: %v", err)
198 }
199 200 // Define the test users
201 adminPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5"
202 server1PubkeyHex := "4d13154d82477a2d2e07a5c0d52def9035fdf379ae87cd6f0a5fb87801a4e5e4"
203 server2PubkeyHex := "e400106ed10310ea28b039e81824265434bf86ece58722655c7a98f894406112"
204 205 adminPubkey, _ := hex.Dec(adminPubkeyHex)
206 server1Pubkey, _ := hex.Dec(server1PubkeyHex)
207 server2Pubkey, _ := hex.Dec(server2PubkeyHex)
208 209 // Create a random user not in any allow list
210 randomSigner, randomPubkey := generateTestKeypair(t)
211 212 // Test Kind 30166 (Private server heartbeat events)
213 t.Run("kind_30166_read_access", func(t *testing.T) {
214 // We can't sign with the exact pubkey without the private key,
215 // so we'll create a generic event and manually set the pubkey for testing
216 heartbeatEvent := createTestEvent(t, randomSigner, "heartbeat data", 30166)
217 heartbeatEvent.Pubkey = server1Pubkey // Set to server1's pubkey
218 219 // Test 1: Admin (in read_allow) should be able to READ the heartbeat
220 allowed, err := policy.CheckPolicy("read", heartbeatEvent, adminPubkey, "127.0.0.1")
221 if err != nil {
222 t.Fatalf("Unexpected error: %v", err)
223 }
224 if !allowed {
225 t.Error("Admin should be allowed to READ kind 30166 events (admin is in read_allow list)")
226 }
227 228 // Test 2: Server1 (in read_allow) should be able to READ the heartbeat
229 allowed, err = policy.CheckPolicy("read", heartbeatEvent, server1Pubkey, "127.0.0.1")
230 if err != nil {
231 t.Fatalf("Unexpected error: %v", err)
232 }
233 if !allowed {
234 t.Error("Server1 should be allowed to READ kind 30166 events (server1 is in read_allow list)")
235 }
236 237 // Test 3: Server2 (in read_allow) should be able to READ the heartbeat
238 allowed, err = policy.CheckPolicy("read", heartbeatEvent, server2Pubkey, "127.0.0.1")
239 if err != nil {
240 t.Fatalf("Unexpected error: %v", err)
241 }
242 if !allowed {
243 t.Error("Server2 should be allowed to READ kind 30166 events (server2 is in read_allow list)")
244 }
245 246 // Test 4: Random user (NOT in read_allow) should NOT be able to READ the heartbeat
247 allowed, err = policy.CheckPolicy("read", heartbeatEvent, randomPubkey, "127.0.0.1")
248 if err != nil {
249 t.Fatalf("Unexpected error: %v", err)
250 }
251 if allowed {
252 t.Error("Random user should NOT be allowed to READ kind 30166 events (not in read_allow list)")
253 }
254 255 // Test 5: Unauthenticated user should NOT be able to READ (privileged + read_allow)
256 allowed, err = policy.CheckPolicy("read", heartbeatEvent, nil, "127.0.0.1")
257 if err != nil {
258 t.Fatalf("Unexpected error: %v", err)
259 }
260 if allowed {
261 t.Error("Unauthenticated user should NOT be allowed to READ kind 30166 events (privileged)")
262 }
263 })
264 265 // Test Kind 10306 (End user whitelist change requests)
266 t.Run("kind_10306_read_access", func(t *testing.T) {
267 // Create an event authored by a random user
268 requestEvent := createTestEvent(t, randomSigner, "whitelist change request", 10306)
269 // Add admin to p tag to satisfy privileged requirement
270 addPTag(requestEvent, adminPubkey)
271 272 // Test 1: Admin (in read_allow) should be able to READ the request
273 allowed, err := policy.CheckPolicy("read", requestEvent, adminPubkey, "127.0.0.1")
274 if err != nil {
275 t.Fatalf("Unexpected error: %v", err)
276 }
277 if !allowed {
278 t.Error("Admin should be allowed to READ kind 10306 events (admin is in read_allow list)")
279 }
280 281 // Test 2: Server1 (NOT in read_allow for kind 10306) should NOT be able to READ
282 // Even though server1 might be allowed for kind 30166
283 allowed, err = policy.CheckPolicy("read", requestEvent, server1Pubkey, "127.0.0.1")
284 if err != nil {
285 t.Fatalf("Unexpected error: %v", err)
286 }
287 if allowed {
288 t.Error("Server1 should NOT be allowed to READ kind 10306 events (not in read_allow list for this kind)")
289 }
290 291 // Test 3: Random user (author) SHOULD be able to READ
292 // OR logic: Random user is the author so privileged check passes -> ALLOWED
293 allowed, err = policy.CheckPolicy("read", requestEvent, randomPubkey, "127.0.0.1")
294 if err != nil {
295 t.Fatalf("Unexpected error: %v", err)
296 }
297 if !allowed {
298 t.Error("Random user SHOULD be allowed to READ kind 10306 events (author - privileged check passes, OR logic)")
299 }
300 })
301 }
302 303 // TestReadAllowWithPrivileged tests interaction between read_allow and privileged
304 func TestReadAllowWithPrivileged(t *testing.T) {
305 aliceSigner, alicePubkey := generateTestKeypair(t)
306 _, bobPubkey := generateTestKeypair(t)
307 _, charliePubkey := generateTestKeypair(t)
308 309 // Create policy: Kind 100 is privileged AND has read_allow
310 policy := &P{
311 DefaultPolicy: "allow",
312 rules: map[int]Rule{
313 100: {
314 Description: "Privileged with read_allow",
315 Constraints: Constraints{
316 Privileged: true,
317 },
318 AccessControl: AccessControl{
319 ReadAllow: []string{hex.Enc(bobPubkey)}, // Only Bob can read
320 },
321 },
322 },
323 }
324 325 // Create event authored by Alice, with Bob in p tag
326 ev := createTestEvent(t, aliceSigner, "secret message", 100)
327 addPTag(ev, bobPubkey)
328 329 // Test 1: Bob (in ReadAllow AND in p tag) should be able to READ
330 t.Run("bob_in_readallow_and_ptag", func(t *testing.T) {
331 allowed, err := policy.CheckPolicy("read", ev, bobPubkey, "127.0.0.1")
332 if err != nil {
333 t.Fatalf("Unexpected error: %v", err)
334 }
335 if !allowed {
336 t.Error("Bob should be allowed to READ (in ReadAllow AND satisfies privileged)")
337 }
338 })
339 340 // Test 2: Alice (author, but NOT in ReadAllow) SHOULD be able to READ
341 // OR logic: Alice is involved (author) so privileged check passes -> ALLOWED
342 t.Run("alice_author_but_not_in_readallow", func(t *testing.T) {
343 allowed, err := policy.CheckPolicy("read", ev, alicePubkey, "127.0.0.1")
344 if err != nil {
345 t.Fatalf("Unexpected error: %v", err)
346 }
347 if !allowed {
348 t.Error("Alice SHOULD be allowed to READ (privileged check passes - she's the author, OR logic)")
349 }
350 })
351 352 // Test 3: Charlie (NOT in ReadAllow, NOT in p tag) should NOT be able to READ
353 t.Run("charlie_not_authorized", func(t *testing.T) {
354 allowed, err := policy.CheckPolicy("read", ev, charliePubkey, "127.0.0.1")
355 if err != nil {
356 t.Fatalf("Unexpected error: %v", err)
357 }
358 if allowed {
359 t.Error("Charlie should NOT be allowed to READ (not in ReadAllow)")
360 }
361 })
362 363 // Test 4: Create event with Charlie in p tag but Charlie not in ReadAllow
364 evWithCharlie := createTestEvent(t, aliceSigner, "message for charlie", 100)
365 addPTag(evWithCharlie, charliePubkey)
366 367 t.Run("charlie_in_ptag_but_not_readallow", func(t *testing.T) {
368 allowed, err := policy.CheckPolicy("read", evWithCharlie, charliePubkey, "127.0.0.1")
369 if err != nil {
370 t.Fatalf("Unexpected error: %v", err)
371 }
372 if !allowed {
373 t.Error("Charlie SHOULD be allowed to READ (privileged check passes - he's in p-tag, OR logic)")
374 }
375 })
376 }
377 378 // TestReadAllowWriteAllowIndependent verifies that read_allow and write_allow are independent
379 func TestReadAllowWriteAllowIndependent(t *testing.T) {
380 aliceSigner, alicePubkey := generateTestKeypair(t)
381 bobSigner, bobPubkey := generateTestKeypair(t)
382 _, charliePubkey := generateTestKeypair(t)
383 384 // Create policy:
385 // - Alice can WRITE
386 // - Bob can READ
387 // - Charlie can do neither
388 policy := &P{
389 DefaultPolicy: "allow",
390 rules: map[int]Rule{
391 200: {
392 Description: "Write/Read separation test",
393 AccessControl: AccessControl{
394 WriteAllow: []string{hex.Enc(alicePubkey)}, // Only Alice can write
395 ReadAllow: []string{hex.Enc(bobPubkey)}, // Only Bob can read
396 },
397 },
398 },
399 }
400 401 // Alice creates an event
402 aliceEvent := createTestEvent(t, aliceSigner, "alice's message", 200)
403 404 // Test 1: Alice can WRITE her own event
405 t.Run("alice_can_write", func(t *testing.T) {
406 allowed, err := policy.CheckPolicy("write", aliceEvent, alicePubkey, "127.0.0.1")
407 if err != nil {
408 t.Fatalf("Unexpected error: %v", err)
409 }
410 if !allowed {
411 t.Error("Alice should be allowed to WRITE (in WriteAllow)")
412 }
413 })
414 415 // Test 2: Alice CANNOT READ her own event (not in ReadAllow)
416 t.Run("alice_cannot_read", func(t *testing.T) {
417 allowed, err := policy.CheckPolicy("read", aliceEvent, alicePubkey, "127.0.0.1")
418 if err != nil {
419 t.Fatalf("Unexpected error: %v", err)
420 }
421 if allowed {
422 t.Error("Alice should NOT be allowed to READ (not in ReadAllow, even though she wrote it)")
423 }
424 })
425 426 // Bob creates an event (will be denied on write)
427 bobEvent := createTestEvent(t, bobSigner, "bob's message", 200)
428 429 // Test 3: Bob CANNOT WRITE (not in WriteAllow)
430 t.Run("bob_cannot_write", func(t *testing.T) {
431 allowed, err := policy.CheckPolicy("write", bobEvent, bobPubkey, "127.0.0.1")
432 if err != nil {
433 t.Fatalf("Unexpected error: %v", err)
434 }
435 if allowed {
436 t.Error("Bob should NOT be allowed to WRITE (not in WriteAllow)")
437 }
438 })
439 440 // Test 4: Bob CAN READ Alice's event (in ReadAllow)
441 t.Run("bob_can_read", func(t *testing.T) {
442 allowed, err := policy.CheckPolicy("read", aliceEvent, bobPubkey, "127.0.0.1")
443 if err != nil {
444 t.Fatalf("Unexpected error: %v", err)
445 }
446 if !allowed {
447 t.Error("Bob should be allowed to READ Alice's event (in ReadAllow)")
448 }
449 })
450 451 // Test 5: Charlie cannot write or read
452 t.Run("charlie_cannot_write_or_read", func(t *testing.T) {
453 // Create an event authored by Charlie
454 charlieSigner := p8k.MustNew()
455 charlieSigner.Generate()
456 charlieEvent := createTestEvent(t, charlieSigner, "charlie's message", 200)
457 charlieEvent.Pubkey = charliePubkey // Set to Charlie's pubkey
458 459 // Charlie's event should be denied for write (Charlie not in WriteAllow)
460 allowed, err := policy.CheckPolicy("write", charlieEvent, charliePubkey, "127.0.0.1")
461 if err != nil {
462 t.Fatalf("Unexpected error: %v", err)
463 }
464 if allowed {
465 t.Error("Charlie should NOT be allowed to WRITE events of kind 200 (not in WriteAllow)")
466 }
467 468 // Charlie should not be able to READ Alice's event (not in ReadAllow)
469 allowed, err = policy.CheckPolicy("read", aliceEvent, charliePubkey, "127.0.0.1")
470 if err != nil {
471 t.Fatalf("Unexpected error: %v", err)
472 }
473 if allowed {
474 t.Error("Charlie should NOT be allowed to READ (not in ReadAllow)")
475 }
476 })
477 }
478 479 // TestReadAccessEdgeCases tests edge cases like nil pubkeys
480 func TestReadAccessEdgeCases(t *testing.T) {
481 aliceSigner, _ := generateTestKeypair(t)
482 483 policy := &P{
484 DefaultPolicy: "allow",
485 rules: map[int]Rule{
486 300: {
487 Description: "Test edge cases",
488 AccessControl: AccessControl{
489 ReadAllow: []string{"somepubkey"}, // Non-empty ReadAllow
490 },
491 },
492 },
493 }
494 495 event := createTestEvent(t, aliceSigner, "test", 300)
496 497 // Test 1: Nil loggedInPubkey with ReadAllow should be denied
498 t.Run("nil_pubkey_with_readallow", func(t *testing.T) {
499 allowed, err := policy.CheckPolicy("read", event, nil, "127.0.0.1")
500 if err != nil {
501 t.Fatalf("Unexpected error: %v", err)
502 }
503 if allowed {
504 t.Error("Nil pubkey should NOT be allowed when ReadAllow is set")
505 }
506 })
507 508 // Test 2: Verify hex.Enc(nil) doesn't accidentally match anything
509 t.Run("hex_enc_nil_no_match", func(t *testing.T) {
510 emptyStringHex := hex.Enc(nil)
511 t.Logf("hex.Enc(nil) = %q (len=%d)", emptyStringHex, len(emptyStringHex))
512 513 // Verify it's empty string
514 if emptyStringHex != "" {
515 t.Errorf("Expected hex.Enc(nil) to be empty string, got %q", emptyStringHex)
516 }
517 })
518 }
519