read_access_test.go raw

   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