Add support for read/write permissive overrides in policies
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
Introduce `read_allow_permissive` and `write_allow_permissive` flags in the global rule to override kind whitelists for read or write operations. These flags allow more flexible policy configurations while maintaining blacklist enforcement and preventing conflicting settings. Updated tests and documentation for clarity.
This commit is contained in:
@@ -19,6 +19,7 @@ The policy system provides fine-grained control over event storage and retrieval
|
|||||||
- [Dynamic Policy Updates](#dynamic-policy-updates)
|
- [Dynamic Policy Updates](#dynamic-policy-updates)
|
||||||
- [Evaluation Order](#evaluation-order)
|
- [Evaluation Order](#evaluation-order)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
|
- [Permissive Mode Examples](#permissive-mode-examples)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -271,6 +272,38 @@ Validates that tag values match the specified regex patterns. Only validates tag
|
|||||||
|
|
||||||
See [Follows-Based Whitelisting](#follows-based-whitelisting) for details.
|
See [Follows-Based Whitelisting](#follows-based-whitelisting) for details.
|
||||||
|
|
||||||
|
#### Permissive Mode Overrides
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `read_allow_permissive` | boolean | Override kind whitelist for READ access (reads allowed for all kinds) |
|
||||||
|
| `write_allow_permissive` | boolean | Override kind whitelist for WRITE access (writes use global rule only) |
|
||||||
|
|
||||||
|
These fields, when set on the **global** rule, allow independent control over read and write access relative to the kind whitelist/blacklist:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": {
|
||||||
|
"whitelist": [1, 3, 5, 7]
|
||||||
|
},
|
||||||
|
"global": {
|
||||||
|
"read_allow_permissive": true,
|
||||||
|
"size_limit": 100000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example:
|
||||||
|
- **READ**: Allowed for ALL kinds (permissive override ignores whitelist)
|
||||||
|
- **WRITE**: Only kinds 1, 3, 5, 7 can be written (whitelist applies)
|
||||||
|
|
||||||
|
**Important constraints:**
|
||||||
|
- These flags only work on the **global** rule (ignored on kind-specific rules)
|
||||||
|
- You cannot enable BOTH `read_allow_permissive` AND `write_allow_permissive` when a kind whitelist/blacklist is configured (this would make the whitelist meaningless)
|
||||||
|
- Blacklists always take precedence—permissive flags do NOT override explicit blacklist entries
|
||||||
|
|
||||||
|
See [Permissive Mode Examples](#permissive-mode-examples) for detailed use cases.
|
||||||
|
|
||||||
#### Rate Limiting
|
#### Rate Limiting
|
||||||
|
|
||||||
| Field | Type | Unit | Description |
|
| Field | Type | Unit | Description |
|
||||||
@@ -809,6 +842,83 @@ access_allowed = (
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Permissive Mode Examples
|
||||||
|
|
||||||
|
#### Read-Permissive Relay (Write-Restricted)
|
||||||
|
|
||||||
|
Allow anyone to read all events, but restrict writes to specific kinds:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"default_policy": "allow",
|
||||||
|
"kind": {
|
||||||
|
"whitelist": [1, 3, 7, 9735]
|
||||||
|
},
|
||||||
|
"global": {
|
||||||
|
"read_allow_permissive": true,
|
||||||
|
"size_limit": 100000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **READ**: Any kind can be read (permissive override)
|
||||||
|
- **WRITE**: Only kinds 1, 3, 7, 9735 can be written
|
||||||
|
|
||||||
|
This is useful for relays that want to serve as aggregators (read any event type) but only accept specific event types from clients.
|
||||||
|
|
||||||
|
#### Write-Permissive with Read Restrictions
|
||||||
|
|
||||||
|
Allow writes of any kind (with global constraints), but restrict reads:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"default_policy": "allow",
|
||||||
|
"kind": {
|
||||||
|
"whitelist": [0, 1, 3]
|
||||||
|
},
|
||||||
|
"global": {
|
||||||
|
"write_allow_permissive": true,
|
||||||
|
"size_limit": 50000,
|
||||||
|
"max_age_of_event": 86400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **READ**: Only kinds 0, 1, 3 can be read (whitelist applies)
|
||||||
|
- **WRITE**: Any kind can be written (with size and age limits from global rule)
|
||||||
|
|
||||||
|
This is useful for relays that want to accept any event type but only serve a curated subset.
|
||||||
|
|
||||||
|
#### Archive Relay (Read Any, Accept Specific)
|
||||||
|
|
||||||
|
Perfect for archive/backup relays:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"default_policy": "allow",
|
||||||
|
"kind": {
|
||||||
|
"whitelist": [0, 1, 3, 4, 7, 30023]
|
||||||
|
},
|
||||||
|
"global": {
|
||||||
|
"read_allow_permissive": true,
|
||||||
|
"size_limit": 500000
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"30023": {
|
||||||
|
"description": "Long-form articles with validation",
|
||||||
|
"identifier_regex": "^[a-z0-9-]{1,64}$",
|
||||||
|
"max_expiry_duration": "P365D"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **READ**: All kinds can be read (historical data)
|
||||||
|
- **WRITE**: Only whitelisted kinds accepted, with specific rules for articles
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
### Run Policy Tests
|
### Run Policy Tests
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func BenchmarkCheckKindsPolicy(b *testing.B) {
|
|||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
policy.checkKindsPolicy(1)
|
policy.checkKindsPolicy("write", 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,8 +168,8 @@ func TestBugReproduction_DebugPolicyFlow(t *testing.T) {
|
|||||||
t.Logf("=== Policy Check Flow ===")
|
t.Logf("=== Policy Check Flow ===")
|
||||||
|
|
||||||
// Step 1: Check kinds policy
|
// Step 1: Check kinds policy
|
||||||
kindsAllowed := policy.checkKindsPolicy(event.Kind)
|
kindsAllowed := policy.checkKindsPolicy("write", event.Kind)
|
||||||
t.Logf("1. checkKindsPolicy(kind=%d) returned: %v", event.Kind, kindsAllowed)
|
t.Logf("1. checkKindsPolicy(access=write, kind=%d) returned: %v", event.Kind, kindsAllowed)
|
||||||
|
|
||||||
// Full policy check
|
// Full policy check
|
||||||
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
|
allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
|
||||||
|
|||||||
@@ -1351,6 +1351,57 @@ func TestValidateJSONNewFields(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
|
// Tests for read_allow_permissive and write_allow_permissive
|
||||||
|
{
|
||||||
|
name: "valid read_allow_permissive alone with whitelist",
|
||||||
|
json: `{
|
||||||
|
"kind": {"whitelist": [1, 3, 5]},
|
||||||
|
"global": {"read_allow_permissive": true}
|
||||||
|
}`,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid write_allow_permissive alone with whitelist",
|
||||||
|
json: `{
|
||||||
|
"kind": {"whitelist": [1, 3, 5]},
|
||||||
|
"global": {"write_allow_permissive": true}
|
||||||
|
}`,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid both permissive flags with whitelist",
|
||||||
|
json: `{
|
||||||
|
"kind": {"whitelist": [1, 3, 5]},
|
||||||
|
"global": {
|
||||||
|
"read_allow_permissive": true,
|
||||||
|
"write_allow_permissive": true
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectError: true,
|
||||||
|
errorMatch: "read_allow_permissive and write_allow_permissive cannot be enabled together",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid both permissive flags with blacklist",
|
||||||
|
json: `{
|
||||||
|
"kind": {"blacklist": [2, 4, 6]},
|
||||||
|
"global": {
|
||||||
|
"read_allow_permissive": true,
|
||||||
|
"write_allow_permissive": true
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectError: true,
|
||||||
|
errorMatch: "read_allow_permissive and write_allow_permissive cannot be enabled together",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid both permissive flags without any kind restriction",
|
||||||
|
json: `{
|
||||||
|
"global": {
|
||||||
|
"read_allow_permissive": true,
|
||||||
|
"write_allow_permissive": true
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -144,6 +144,23 @@ type Rule struct {
|
|||||||
// Example: "^[a-z0-9-]{1,64}$" requires lowercase alphanumeric with hyphens, max 64 chars.
|
// Example: "^[a-z0-9-]{1,64}$" requires lowercase alphanumeric with hyphens, max 64 chars.
|
||||||
IdentifierRegex string `json:"identifier_regex,omitempty"`
|
IdentifierRegex string `json:"identifier_regex,omitempty"`
|
||||||
|
|
||||||
|
// ReadAllowPermissive when set on a GLOBAL rule, allows read access for ALL kinds,
|
||||||
|
// even when a kind whitelist is configured. This allows the kind whitelist to
|
||||||
|
// restrict WRITE operations while keeping reads permissive.
|
||||||
|
// When true:
|
||||||
|
// - READ: Allowed for all kinds (global rule still applies for other read restrictions)
|
||||||
|
// - WRITE: Kind whitelist/blacklist applies as normal
|
||||||
|
// Only meaningful on the Global rule - ignored on kind-specific rules.
|
||||||
|
ReadAllowPermissive bool `json:"read_allow_permissive,omitempty"`
|
||||||
|
|
||||||
|
// WriteAllowPermissive when set on a GLOBAL rule, allows write access for kinds
|
||||||
|
// that don't have specific rules defined, bypassing the implicit kind whitelist.
|
||||||
|
// When true:
|
||||||
|
// - Kinds without specific rules apply global rule constraints only
|
||||||
|
// - Kind whitelist still blocks reads for unlisted kinds (unless ReadAllowPermissive is also set)
|
||||||
|
// Only meaningful on the Global rule - ignored on kind-specific rules.
|
||||||
|
WriteAllowPermissive bool `json:"write_allow_permissive,omitempty"`
|
||||||
|
|
||||||
// Binary caches for faster comparison (populated from hex strings above)
|
// Binary caches for faster comparison (populated from hex strings above)
|
||||||
// These are not exported and not serialized to JSON
|
// These are not exported and not serialized to JSON
|
||||||
writeAllowBin [][]byte
|
writeAllowBin [][]byte
|
||||||
@@ -178,7 +195,8 @@ func (r *Rule) hasAnyRules() bool {
|
|||||||
len(r.ReadFollowsWhitelist) > 0 || len(r.WriteFollowsWhitelist) > 0 ||
|
len(r.ReadFollowsWhitelist) > 0 || len(r.WriteFollowsWhitelist) > 0 ||
|
||||||
len(r.readFollowsWhitelistBin) > 0 || len(r.writeFollowsWhitelistBin) > 0 ||
|
len(r.readFollowsWhitelistBin) > 0 || len(r.writeFollowsWhitelistBin) > 0 ||
|
||||||
len(r.TagValidation) > 0 ||
|
len(r.TagValidation) > 0 ||
|
||||||
r.ProtectedRequired || r.IdentifierRegex != ""
|
r.ProtectedRequired || r.IdentifierRegex != "" ||
|
||||||
|
r.ReadAllowPermissive || r.WriteAllowPermissive
|
||||||
}
|
}
|
||||||
|
|
||||||
// populateBinaryCache converts hex-encoded pubkey strings to binary for faster comparison.
|
// populateBinaryCache converts hex-encoded pubkey strings to binary for faster comparison.
|
||||||
@@ -1280,7 +1298,7 @@ func (p *P) CheckPolicy(
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// STEP 1: Check kinds whitelist/blacklist (applies before any rule checks)
|
// STEP 1: Check kinds whitelist/blacklist (applies before any rule checks)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
if !p.checkKindsPolicy(ev.Kind) {
|
if !p.checkKindsPolicy(access, ev.Kind) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1341,19 +1359,32 @@ func (p *P) CheckPolicy(
|
|||||||
return p.getDefaultPolicyAction(), nil
|
return p.getDefaultPolicyAction(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkKindsPolicy checks if the event kind is allowed.
|
// checkKindsPolicy checks if the event kind is allowed for the given access type.
|
||||||
// Logic:
|
// Logic:
|
||||||
// 1. If explicit whitelist exists, use it (backwards compatibility)
|
// 1. If explicit whitelist exists, use it (but respect permissive flags for read/write)
|
||||||
// 2. If explicit blacklist exists, use it (backwards compatibility)
|
// 2. If explicit blacklist exists, use it (but respect permissive flags for read/write)
|
||||||
// 3. Otherwise, kinds with defined rules are implicitly allowed, others denied
|
// 3. Otherwise, kinds with defined rules are implicitly allowed, others denied (with permissive overrides)
|
||||||
func (p *P) checkKindsPolicy(kind uint16) bool {
|
//
|
||||||
// If whitelist is present, only allow whitelisted kinds
|
// Permissive flags (set on Global rule):
|
||||||
|
// - ReadAllowPermissive: Allows READ access for kinds not in whitelist (write still restricted)
|
||||||
|
// - WriteAllowPermissive: Allows WRITE access for kinds not in whitelist (uses global rule constraints)
|
||||||
|
func (p *P) checkKindsPolicy(access string, kind uint16) bool {
|
||||||
|
// If whitelist is present, only allow whitelisted kinds (with permissive overrides)
|
||||||
if len(p.Kind.Whitelist) > 0 {
|
if len(p.Kind.Whitelist) > 0 {
|
||||||
for _, allowedKind := range p.Kind.Whitelist {
|
for _, allowedKind := range p.Kind.Whitelist {
|
||||||
if kind == uint16(allowedKind) {
|
if kind == uint16(allowedKind) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Kind not in whitelist - check permissive flags
|
||||||
|
if access == "read" && p.Global.ReadAllowPermissive {
|
||||||
|
log.D.F("read_allow_permissive: allowing read for kind %d not in whitelist", kind)
|
||||||
|
return true // Allow read even though kind not whitelisted
|
||||||
|
}
|
||||||
|
if access == "write" && p.Global.WriteAllowPermissive {
|
||||||
|
log.D.F("write_allow_permissive: allowing write for kind %d not in whitelist (global rules apply)", kind)
|
||||||
|
return true // Allow write even though kind not whitelisted, global rule will be applied
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1361,12 +1392,25 @@ func (p *P) checkKindsPolicy(kind uint16) bool {
|
|||||||
if len(p.Kind.Blacklist) > 0 {
|
if len(p.Kind.Blacklist) > 0 {
|
||||||
for _, deniedKind := range p.Kind.Blacklist {
|
for _, deniedKind := range p.Kind.Blacklist {
|
||||||
if kind == uint16(deniedKind) {
|
if kind == uint16(deniedKind) {
|
||||||
|
// Kind is explicitly blacklisted - permissive flags don't override blacklist
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not in blacklist - check if rule exists for implicit whitelist
|
// Not in blacklist - check if rule exists for implicit whitelist
|
||||||
_, hasRule := p.rules[int(kind)]
|
_, hasRule := p.rules[int(kind)]
|
||||||
return hasRule // Only allow if there's a rule defined
|
if hasRule {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// No kind-specific rule - check permissive flags
|
||||||
|
if access == "read" && p.Global.ReadAllowPermissive {
|
||||||
|
log.D.F("read_allow_permissive: allowing read for kind %d (not blacklisted, no rule)", kind)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if access == "write" && p.Global.WriteAllowPermissive {
|
||||||
|
log.D.F("write_allow_permissive: allowing write for kind %d (not blacklisted, no rule)", kind)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false // Only allow if there's a rule defined
|
||||||
}
|
}
|
||||||
|
|
||||||
// No explicit whitelist or blacklist
|
// No explicit whitelist or blacklist
|
||||||
@@ -1374,6 +1418,7 @@ func (p *P) checkKindsPolicy(kind uint16) bool {
|
|||||||
// - If default_policy is explicitly "allow", allow all kinds (rules add constraints, not restrictions)
|
// - If default_policy is explicitly "allow", allow all kinds (rules add constraints, not restrictions)
|
||||||
// - If default_policy is unset or "deny", use implicit whitelist (only allow kinds with rules)
|
// - If default_policy is unset or "deny", use implicit whitelist (only allow kinds with rules)
|
||||||
// - If global rule has any configuration, allow kinds through for global rule checking
|
// - If global rule has any configuration, allow kinds through for global rule checking
|
||||||
|
// - Permissive flags can override implicit whitelist behavior
|
||||||
if len(p.rules) > 0 {
|
if len(p.rules) > 0 {
|
||||||
// If default_policy is explicitly "allow", don't use implicit whitelist
|
// If default_policy is explicitly "allow", don't use implicit whitelist
|
||||||
if p.DefaultPolicy == "allow" {
|
if p.DefaultPolicy == "allow" {
|
||||||
@@ -1388,6 +1433,15 @@ func (p *P) checkKindsPolicy(kind uint16) bool {
|
|||||||
if p.Global.hasAnyRules() {
|
if p.Global.hasAnyRules() {
|
||||||
return true // Allow through for global rule check
|
return true // Allow through for global rule check
|
||||||
}
|
}
|
||||||
|
// Check permissive flags for implicit whitelist override
|
||||||
|
if access == "read" && p.Global.ReadAllowPermissive {
|
||||||
|
log.D.F("read_allow_permissive: allowing read for kind %d (implicit whitelist override)", kind)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if access == "write" && p.Global.WriteAllowPermissive {
|
||||||
|
log.D.F("write_allow_permissive: allowing write for kind %d (implicit whitelist override)", kind)
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// No kind-specific rules - check if global rule exists
|
// No kind-specific rules - check if global rule exists
|
||||||
@@ -2052,6 +2106,13 @@ func (p *P) ValidateJSON(policyJSON []byte) error {
|
|||||||
return fmt.Errorf("invalid default_policy value: %q (must be \"allow\" or \"deny\")", tempPolicy.DefaultPolicy)
|
return fmt.Errorf("invalid default_policy value: %q (must be \"allow\" or \"deny\")", tempPolicy.DefaultPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate permissive flags: if both read_allow_permissive AND write_allow_permissive are set
|
||||||
|
// with a kind whitelist or blacklist, this makes the whitelist/blacklist meaningless
|
||||||
|
hasKindRestriction := len(tempPolicy.Kind.Whitelist) > 0 || len(tempPolicy.Kind.Blacklist) > 0
|
||||||
|
if hasKindRestriction && tempPolicy.Global.ReadAllowPermissive && tempPolicy.Global.WriteAllowPermissive {
|
||||||
|
return fmt.Errorf("invalid policy: both read_allow_permissive and write_allow_permissive cannot be enabled together with a kind whitelist or blacklist (this would make the kind restriction meaningless)")
|
||||||
|
}
|
||||||
|
|
||||||
log.D.F("policy JSON validation passed")
|
log.D.F("policy JSON validation passed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
policy *P
|
policy *P
|
||||||
|
access string // "read" or "write"
|
||||||
kind uint16
|
kind uint16
|
||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
@@ -155,6 +156,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
Kind: Kinds{},
|
Kind: Kinds{},
|
||||||
rules: map[int]Rule{}, // No rules defined
|
rules: map[int]Rule{}, // No rules defined
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 1,
|
kind: 1,
|
||||||
expected: true, // Should be allowed (no rules = allow all kinds)
|
expected: true, // Should be allowed (no rules = allow all kinds)
|
||||||
},
|
},
|
||||||
@@ -166,6 +168,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
2: {Description: "Rule for kind 2"},
|
2: {Description: "Rule for kind 2"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 1,
|
kind: 1,
|
||||||
expected: false, // Should be denied (implicit whitelist, no rule for kind 1)
|
expected: false, // Should be denied (implicit whitelist, no rule for kind 1)
|
||||||
},
|
},
|
||||||
@@ -177,6 +180,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
1: {Description: "Rule for kind 1"},
|
1: {Description: "Rule for kind 1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 1,
|
kind: 1,
|
||||||
expected: true, // Should be allowed (has rule)
|
expected: true, // Should be allowed (has rule)
|
||||||
},
|
},
|
||||||
@@ -189,6 +193,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
rules: map[int]Rule{}, // No specific rules
|
rules: map[int]Rule{}, // No specific rules
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 1,
|
kind: 1,
|
||||||
expected: true, // Should be allowed (global rule exists)
|
expected: true, // Should be allowed (global rule exists)
|
||||||
},
|
},
|
||||||
@@ -199,6 +204,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
Whitelist: []int{1, 3, 5},
|
Whitelist: []int{1, 3, 5},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 1,
|
kind: 1,
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
@@ -209,6 +215,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
Whitelist: []int{1, 3, 5},
|
Whitelist: []int{1, 3, 5},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 2,
|
kind: 2,
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
@@ -222,6 +229,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
3: {Description: "Rule for kind 3"}, // Has at least one rule
|
3: {Description: "Rule for kind 3"}, // Has at least one rule
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 1,
|
kind: 1,
|
||||||
expected: false, // Should be denied (not blacklisted but no rule for kind 1)
|
expected: false, // Should be denied (not blacklisted but no rule for kind 1)
|
||||||
},
|
},
|
||||||
@@ -235,6 +243,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
1: {Description: "Rule for kind 1"},
|
1: {Description: "Rule for kind 1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 1,
|
kind: 1,
|
||||||
expected: true, // Should be allowed (not blacklisted and has rule)
|
expected: true, // Should be allowed (not blacklisted and has rule)
|
||||||
},
|
},
|
||||||
@@ -245,6 +254,7 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
Blacklist: []int{2, 4, 6},
|
Blacklist: []int{2, 4, 6},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 2,
|
kind: 2,
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
@@ -256,14 +266,87 @@ func TestCheckKindsPolicy(t *testing.T) {
|
|||||||
Blacklist: []int{1, 2, 3},
|
Blacklist: []int{1, 2, 3},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
access: "write",
|
||||||
kind: 1,
|
kind: 1,
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
|
// Tests for new permissive flags
|
||||||
|
{
|
||||||
|
name: "read_allow_permissive - allows read for non-whitelisted kind",
|
||||||
|
policy: &P{
|
||||||
|
Kind: Kinds{
|
||||||
|
Whitelist: []int{1, 3, 5},
|
||||||
|
},
|
||||||
|
Global: Rule{
|
||||||
|
ReadAllowPermissive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: "read",
|
||||||
|
kind: 2,
|
||||||
|
expected: true, // Should be allowed (read permissive overrides whitelist)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read_allow_permissive - write still blocked for non-whitelisted kind",
|
||||||
|
policy: &P{
|
||||||
|
Kind: Kinds{
|
||||||
|
Whitelist: []int{1, 3, 5},
|
||||||
|
},
|
||||||
|
Global: Rule{
|
||||||
|
ReadAllowPermissive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: "write",
|
||||||
|
kind: 2,
|
||||||
|
expected: false, // Should be denied (only read is permissive)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "write_allow_permissive - allows write for non-whitelisted kind",
|
||||||
|
policy: &P{
|
||||||
|
Kind: Kinds{
|
||||||
|
Whitelist: []int{1, 3, 5},
|
||||||
|
},
|
||||||
|
Global: Rule{
|
||||||
|
WriteAllowPermissive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: "write",
|
||||||
|
kind: 2,
|
||||||
|
expected: true, // Should be allowed (write permissive overrides whitelist)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "write_allow_permissive - read still blocked for non-whitelisted kind",
|
||||||
|
policy: &P{
|
||||||
|
Kind: Kinds{
|
||||||
|
Whitelist: []int{1, 3, 5},
|
||||||
|
},
|
||||||
|
Global: Rule{
|
||||||
|
WriteAllowPermissive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: "read",
|
||||||
|
kind: 2,
|
||||||
|
expected: false, // Should be denied (only write is permissive)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blacklist - permissive flags do NOT override blacklist",
|
||||||
|
policy: &P{
|
||||||
|
Kind: Kinds{
|
||||||
|
Blacklist: []int{2, 4, 6},
|
||||||
|
},
|
||||||
|
Global: Rule{
|
||||||
|
ReadAllowPermissive: true,
|
||||||
|
WriteAllowPermissive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: "write",
|
||||||
|
kind: 2,
|
||||||
|
expected: false, // Should be denied (blacklist always applies)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := tt.policy.checkKindsPolicy(tt.kind)
|
result := tt.policy.checkKindsPolicy(tt.access, tt.kind)
|
||||||
if result != tt.expected {
|
if result != tt.expected {
|
||||||
t.Errorf("Expected %v, got %v", tt.expected, result)
|
t.Errorf("Expected %v, got %v", tt.expected, result)
|
||||||
}
|
}
|
||||||
@@ -996,19 +1079,19 @@ func TestEdgeCasesWhitelistBlacklistConflict(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test kind in both whitelist and blacklist - whitelist should win
|
// Test kind in both whitelist and blacklist - whitelist should win
|
||||||
allowed := policy.checkKindsPolicy(1)
|
allowed := policy.checkKindsPolicy("write", 1)
|
||||||
if !allowed {
|
if !allowed {
|
||||||
t.Error("Expected whitelist to override blacklist")
|
t.Error("Expected whitelist to override blacklist")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test kind in blacklist but not whitelist
|
// Test kind in blacklist but not whitelist
|
||||||
allowed = policy.checkKindsPolicy(2)
|
allowed = policy.checkKindsPolicy("write", 2)
|
||||||
if allowed {
|
if allowed {
|
||||||
t.Error("Expected kind in blacklist but not whitelist to be blocked")
|
t.Error("Expected kind in blacklist but not whitelist to be blocked")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test kind in whitelist but not blacklist
|
// Test kind in whitelist but not blacklist
|
||||||
allowed = policy.checkKindsPolicy(5)
|
allowed = policy.checkKindsPolicy("write", 5)
|
||||||
if !allowed {
|
if !allowed {
|
||||||
t.Error("Expected kind in whitelist to be allowed")
|
t.Error("Expected kind in whitelist to be allowed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v0.32.6
|
v0.32.7
|
||||||
Reference in New Issue
Block a user