You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
690 lines
17 KiB
690 lines
17 KiB
package policy |
|
|
|
import ( |
|
"encoding/json" |
|
"testing" |
|
) |
|
|
|
// TestValidateOwnerPolicyUpdate tests owner-specific validation |
|
func TestValidateOwnerPolicyUpdate(t *testing.T) { |
|
// Create a base policy |
|
basePolicy := &P{ |
|
DefaultPolicy: "allow", |
|
Owners: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, |
|
PolicyAdmins: []string{"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}, |
|
} |
|
|
|
tests := []struct { |
|
name string |
|
newPolicy string |
|
expectError bool |
|
errorMsg string |
|
}{ |
|
{ |
|
name: "valid owner update with non-empty owners", |
|
newPolicy: `{ |
|
"default_policy": "deny", |
|
"owners": ["cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"], |
|
"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"] |
|
}`, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "invalid - empty owners list", |
|
newPolicy: `{ |
|
"default_policy": "deny", |
|
"owners": [], |
|
"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"] |
|
}`, |
|
expectError: true, |
|
errorMsg: "owners list cannot be empty", |
|
}, |
|
{ |
|
name: "invalid - missing owners field", |
|
newPolicy: `{ |
|
"default_policy": "deny", |
|
"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"] |
|
}`, |
|
expectError: true, |
|
errorMsg: "owners list cannot be empty", |
|
}, |
|
{ |
|
name: "invalid - bad owner pubkey format", |
|
newPolicy: `{ |
|
"default_policy": "deny", |
|
"owners": ["not-a-valid-pubkey"] |
|
}`, |
|
expectError: true, |
|
errorMsg: "invalid owner pubkey", |
|
}, |
|
{ |
|
name: "valid - owner can add multiple owners", |
|
newPolicy: `{ |
|
"default_policy": "deny", |
|
"owners": [ |
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", |
|
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" |
|
] |
|
}`, |
|
expectError: false, |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
err := basePolicy.ValidateOwnerPolicyUpdate([]byte(tt.newPolicy)) |
|
if tt.expectError { |
|
if err == nil { |
|
t.Errorf("expected error containing %q, got nil", tt.errorMsg) |
|
} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) { |
|
t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error()) |
|
} |
|
} else { |
|
if err != nil { |
|
t.Errorf("unexpected error: %v", err) |
|
} |
|
} |
|
}) |
|
} |
|
} |
|
|
|
// TestValidatePolicyAdminUpdate tests policy admin validation |
|
func TestValidatePolicyAdminUpdate(t *testing.T) { |
|
// Create a base policy with known owners and admins |
|
ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
|
adminPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" |
|
allowedPubkey := "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" |
|
|
|
baseJSON := `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}` |
|
|
|
basePolicy := &P{} |
|
if err := json.Unmarshal([]byte(baseJSON), basePolicy); err != nil { |
|
t.Fatalf("failed to create base policy: %v", err) |
|
} |
|
|
|
adminPubkeyBin := make([]byte, 32) |
|
for i := range adminPubkeyBin { |
|
adminPubkeyBin[i] = 0xbb |
|
} |
|
|
|
tests := []struct { |
|
name string |
|
newPolicy string |
|
expectError bool |
|
errorMsg string |
|
}{ |
|
{ |
|
name: "valid - policy admin can extend write_allow", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `", "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "valid - policy admin can add to kind whitelist", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7, 30023] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "valid - policy admin can increase size limit", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"size_limit": 20000 |
|
} |
|
} |
|
}`, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "invalid - policy admin cannot modify owners", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: true, |
|
errorMsg: "cannot modify the 'owners' field", |
|
}, |
|
{ |
|
name: "invalid - policy admin cannot modify policy_admins", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: true, |
|
errorMsg: "cannot modify the 'policy_admins' field", |
|
}, |
|
{ |
|
name: "invalid - policy admin cannot remove from kind whitelist", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: true, |
|
errorMsg: "cannot remove kind 7 from whitelist", |
|
}, |
|
{ |
|
name: "invalid - policy admin cannot remove from write_allow", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": [], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: true, |
|
errorMsg: "cannot remove pubkey", |
|
}, |
|
{ |
|
name: "invalid - policy admin cannot reduce size limit", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"size_limit": 5000 |
|
} |
|
} |
|
}`, |
|
expectError: true, |
|
errorMsg: "cannot reduce size_limit", |
|
}, |
|
{ |
|
name: "invalid - policy admin cannot remove rule", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": {} |
|
}`, |
|
expectError: true, |
|
errorMsg: "cannot remove rule for kind 1", |
|
}, |
|
{ |
|
name: "valid - policy admin can add blacklist entries for non-admin users", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7], |
|
"blacklist": [4] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"write_deny": ["eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "invalid - policy admin cannot blacklist owner in write_deny", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"write_deny": ["` + ownerPubkey + `"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: true, |
|
errorMsg: "cannot blacklist owner", |
|
}, |
|
{ |
|
name: "invalid - policy admin cannot blacklist other policy admin", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"write_deny": ["` + adminPubkey + `"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: true, |
|
errorMsg: "cannot blacklist policy admin", |
|
}, |
|
{ |
|
name: "valid - policy admin can blacklist whitelisted non-admin user", |
|
newPolicy: `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"], |
|
"policy_admins": ["` + adminPubkey + `"], |
|
"kind": { |
|
"whitelist": [1, 3, 7] |
|
}, |
|
"rules": { |
|
"1": { |
|
"description": "Text notes", |
|
"write_allow": ["` + allowedPubkey + `"], |
|
"write_deny": ["` + allowedPubkey + `"], |
|
"size_limit": 10000 |
|
} |
|
} |
|
}`, |
|
expectError: false, |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
err := basePolicy.ValidatePolicyAdminUpdate([]byte(tt.newPolicy), adminPubkeyBin) |
|
if tt.expectError { |
|
if err == nil { |
|
t.Errorf("expected error containing %q, got nil", tt.errorMsg) |
|
} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) { |
|
t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error()) |
|
} |
|
} else { |
|
if err != nil { |
|
t.Errorf("unexpected error: %v", err) |
|
} |
|
} |
|
}) |
|
} |
|
} |
|
|
|
// TestIsOwner tests the IsOwner method |
|
func TestIsOwner(t *testing.T) { |
|
ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
|
nonOwnerPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" |
|
|
|
_ = nonOwnerPubkey // Silence unused variable warning |
|
|
|
policyJSON := `{ |
|
"default_policy": "allow", |
|
"owners": ["` + ownerPubkey + `"] |
|
}` |
|
|
|
policy, err := New([]byte(policyJSON)) |
|
if err != nil { |
|
t.Fatalf("failed to create policy: %v", err) |
|
} |
|
|
|
// Create binary pubkeys |
|
ownerBin := make([]byte, 32) |
|
for i := range ownerBin { |
|
ownerBin[i] = 0xaa |
|
} |
|
|
|
nonOwnerBin := make([]byte, 32) |
|
for i := range nonOwnerBin { |
|
nonOwnerBin[i] = 0xbb |
|
} |
|
|
|
tests := []struct { |
|
name string |
|
pubkey []byte |
|
expected bool |
|
}{ |
|
{ |
|
name: "owner is recognized", |
|
pubkey: ownerBin, |
|
expected: true, |
|
}, |
|
{ |
|
name: "non-owner is not recognized", |
|
pubkey: nonOwnerBin, |
|
expected: false, |
|
}, |
|
{ |
|
name: "nil pubkey returns false", |
|
pubkey: nil, |
|
expected: false, |
|
}, |
|
{ |
|
name: "empty pubkey returns false", |
|
pubkey: []byte{}, |
|
expected: false, |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
result := policy.IsOwner(tt.pubkey) |
|
if result != tt.expected { |
|
t.Errorf("expected %v, got %v", tt.expected, result) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
// TestStringSliceEqual tests the helper function |
|
func TestStringSliceEqual(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
a []string |
|
b []string |
|
expected bool |
|
}{ |
|
{ |
|
name: "equal slices same order", |
|
a: []string{"a", "b", "c"}, |
|
b: []string{"a", "b", "c"}, |
|
expected: true, |
|
}, |
|
{ |
|
name: "equal slices different order", |
|
a: []string{"a", "b", "c"}, |
|
b: []string{"c", "a", "b"}, |
|
expected: true, |
|
}, |
|
{ |
|
name: "different lengths", |
|
a: []string{"a", "b"}, |
|
b: []string{"a", "b", "c"}, |
|
expected: false, |
|
}, |
|
{ |
|
name: "different contents", |
|
a: []string{"a", "b", "c"}, |
|
b: []string{"a", "b", "d"}, |
|
expected: false, |
|
}, |
|
{ |
|
name: "empty slices", |
|
a: []string{}, |
|
b: []string{}, |
|
expected: true, |
|
}, |
|
{ |
|
name: "nil slices", |
|
a: nil, |
|
b: nil, |
|
expected: true, |
|
}, |
|
{ |
|
name: "nil vs empty", |
|
a: nil, |
|
b: []string{}, |
|
expected: true, |
|
}, |
|
{ |
|
name: "duplicates in both", |
|
a: []string{"a", "a", "b"}, |
|
b: []string{"a", "b", "a"}, |
|
expected: true, |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
result := stringSliceEqual(tt.a, tt.b) |
|
if result != tt.expected { |
|
t.Errorf("expected %v, got %v", tt.expected, result) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
// TestPolicyAdminContributionValidation tests the contribution validation |
|
func TestPolicyAdminContributionValidation(t *testing.T) { |
|
ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
|
adminPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" |
|
|
|
ownerPolicy := &P{ |
|
DefaultPolicy: "allow", |
|
Owners: []string{ownerPubkey}, |
|
PolicyAdmins: []string{adminPubkey}, |
|
Kind: Kinds{ |
|
Whitelist: []int{1, 3, 7}, |
|
}, |
|
rules: map[int]Rule{ |
|
1: { |
|
Description: "Text notes", |
|
SizeLimit: ptr(int64(10000)), |
|
}, |
|
}, |
|
} |
|
|
|
tests := []struct { |
|
name string |
|
contribution *PolicyAdminContribution |
|
expectError bool |
|
errorMsg string |
|
}{ |
|
{ |
|
name: "valid - add kinds to whitelist", |
|
contribution: &PolicyAdminContribution{ |
|
AdminPubkey: adminPubkey, |
|
CreatedAt: 1234567890, |
|
EventID: "event123", |
|
KindWhitelistAdd: []int{30023}, |
|
}, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "valid - add to blacklist", |
|
contribution: &PolicyAdminContribution{ |
|
AdminPubkey: adminPubkey, |
|
CreatedAt: 1234567890, |
|
EventID: "event123", |
|
KindBlacklistAdd: []int{4}, |
|
}, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "valid - extend existing rule with larger limit", |
|
contribution: &PolicyAdminContribution{ |
|
AdminPubkey: adminPubkey, |
|
CreatedAt: 1234567890, |
|
EventID: "event123", |
|
RulesExtend: map[int]RuleExtension{ |
|
1: { |
|
SizeLimitOverride: ptr(int64(20000)), |
|
}, |
|
}, |
|
}, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "invalid - extend non-existent rule", |
|
contribution: &PolicyAdminContribution{ |
|
AdminPubkey: adminPubkey, |
|
CreatedAt: 1234567890, |
|
EventID: "event123", |
|
RulesExtend: map[int]RuleExtension{ |
|
999: { |
|
SizeLimitOverride: ptr(int64(20000)), |
|
}, |
|
}, |
|
}, |
|
expectError: true, |
|
errorMsg: "cannot extend rule for kind 999", |
|
}, |
|
{ |
|
name: "invalid - size limit override smaller than owner's", |
|
contribution: &PolicyAdminContribution{ |
|
AdminPubkey: adminPubkey, |
|
CreatedAt: 1234567890, |
|
EventID: "event123", |
|
RulesExtend: map[int]RuleExtension{ |
|
1: { |
|
SizeLimitOverride: ptr(int64(5000)), |
|
}, |
|
}, |
|
}, |
|
expectError: true, |
|
errorMsg: "size_limit_override for kind 1 must be >=", |
|
}, |
|
{ |
|
name: "valid - add new rule for undefined kind", |
|
contribution: &PolicyAdminContribution{ |
|
AdminPubkey: adminPubkey, |
|
CreatedAt: 1234567890, |
|
EventID: "event123", |
|
RulesAdd: map[int]Rule{ |
|
30023: { |
|
Description: "Long-form content", |
|
SizeLimit: ptr(int64(100000)), |
|
}, |
|
}, |
|
}, |
|
expectError: false, |
|
}, |
|
{ |
|
name: "invalid - add rule for already-defined kind", |
|
contribution: &PolicyAdminContribution{ |
|
AdminPubkey: adminPubkey, |
|
CreatedAt: 1234567890, |
|
EventID: "event123", |
|
RulesAdd: map[int]Rule{ |
|
1: { |
|
Description: "Trying to override", |
|
}, |
|
}, |
|
}, |
|
expectError: true, |
|
errorMsg: "cannot add rule for kind 1: already defined", |
|
}, |
|
{ |
|
name: "invalid - bad pubkey length in extension", |
|
contribution: &PolicyAdminContribution{ |
|
AdminPubkey: "short", |
|
CreatedAt: 1234567890, |
|
EventID: "event123", |
|
}, |
|
expectError: true, |
|
errorMsg: "invalid admin pubkey length", |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
err := ValidatePolicyAdminContribution(ownerPolicy, tt.contribution, nil) |
|
if tt.expectError { |
|
if err == nil { |
|
t.Errorf("expected error containing %q, got nil", tt.errorMsg) |
|
} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) { |
|
t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error()) |
|
} |
|
} else { |
|
if err != nil { |
|
t.Errorf("unexpected error: %v", err) |
|
} |
|
} |
|
}) |
|
} |
|
} |
|
|
|
// Helper function for generic pointer |
|
func ptr[T any](v T) *T { |
|
return &v |
|
}
|
|
|