Conversation
WalkthroughAdds scope/permission materialization: new event constants, background jobs to materialize/cleanup scopes, an RLS payload refactor to collect scope UUIDs, permission-driven materialization engine, test updates, and a minor dependency/go.mod and server import change. Changes
Sequence DiagramsequenceDiagram
participant Scheduler as Background Scheduler
participant Jobs as Job System
participant Events as Event Handler
participant Permission as Permission Processor
participant DB as Database
Scheduler->>Jobs: schedule materializeAllScopesJob
Jobs->>DB: query non-deleted scopes
DB-->>Jobs: scopes list
loop per scope
Jobs->>Events: enqueue EventScopeMaterialize(scope_id, rebuild)
Events->>Permission: GetProcessScopeJob(scope_id, "rebuild")
Permission->>DB: fetch scope/permission details
DB-->>Permission: scope targets & selectors
Permission->>DB: apply __scope updates (batch per table)
DB-->>Permission: update results
Permission-->>Events: job result (success/errors)
end
Scheduler->>Jobs: schedule cleanupDeletedScopesJob
Jobs->>DB: query deleted scopes
DB-->>Jobs: deleted scope list
loop per deleted scope
Jobs->>Events: enqueue EventScopeMaterialize(scope_id, remove)
Events->>Permission: GetProcessScopeJob(scope_id, "remove")
Permission->>DB: remove scope from tables
DB-->>Permission: removal confirmation
end
Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
f8347d0 to
0f32be7
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
auth/rls.go (1)
97-104: Build failure:SetPostgresSessionRLSWithRoleis undefined onrls.Payload.The pipeline failures show that
rlsPayload.SetPostgresSessionRLSWithRoledoes not exist on*rls.Payload. This method must be added to the duty package.Both this and the
DBRoleBypassissue indicate the duty dependency PR needs to be merged first, and this PR'sgo.modupdated to reference the merged version.
🤖 Fix all issues with AI agents
In `@auth/rls.go`:
- Around line 79-94: Add the missing DBRoleBypass field to the PostgrestConfig
in the duty package so the reference
dutyAPI.DefaultConfig.Postgrest.DBRoleBypass compiles: add a DBRoleBypass string
field to the PostgrestConfig struct, ensure DefaultConfig initializes it (and
any config parsing/loading populates it, e.g., from env/YAML), and update any
relevant tests or config docs; this will allow the rls.go code that reads
dutyAPI.DefaultConfig.Postgrest.DBRoleBypass and falls back to DBRole to build
successfully.
In `@auth/tokens.go`:
- Around line 74-82: The code references the non-existent field
config.Postgrest.DBRoleBypass causing build failures; update the role selection
in the block around GetRLSPayload and the local variable role so it does not
reference DBRoleBypass — keep role := config.Postgrest.DBRole and, for now,
remove the rlsPayload.Disable && config.Postgrest.DBRoleBypass != "" branch (or
replace it with a no-op/fallback that leaves role as DBRole), and add a TODO
noting to reintroduce DBRoleBypass once the duty dependency adds that field; use
the symbols GetRLSPayload, rlsPayload, and the local role to locate the change.
In `@permission/rls_scopes.go`:
- Around line 275-276: permission.Action tokens aren't trimmed before matching,
so MatchItems(policy.ActionRead, strings.Split(permission.Action, ",")...) can
miss "read" when permission.Action includes spaces; update the check in the
materialization path to normalize tokens by splitting and trimming each token
(e.g., use strings.Split and strings.TrimSpace or strings.FieldsFunc) before
calling collections.MatchItems with the cleaned slice, keeping the same
semantics for policy.ActionRead and permission.Action.
- Around line 429-433: The update can append duplicate scopeIDs when concurrent
workers race; modify the UpdateColumn call that uses
ctx.DB().Table(table).Where("id IN ?", ids).UpdateColumn("__scope",
gorm.Expr("array_append(COALESCE(__scope, '{}'::uuid[]), ?)", scopeID)) so the
WHERE clause also excludes rows that already contain scopeID (e.g. add a NOT
condition like NOT (? = ANY(__scope)) or equivalent SQL to the Where) so the
update is idempotent; keep the same symbols (ctx.DB(), table, ids, scopeID,
UpdateColumn, gorm.Expr) and ensure the combined WHERE prevents appending if
__scope already includes scopeID.
🧹 Nitpick comments (2)
jobs/rls_scope_jobs.go (1)
18-26: Consider gatingRunNowafter the migration.
WithRunNow: true, every startup will trigger a full rebuild. If this was only for the initial migration, a config/feature flag gate inStartwould avoid unnecessary load later.permission/rls_scopes.go (1)
445-452: Wrap DB errors with ctx.Oops for consistent error codes.Both functions return raw GORM errors without context. Prefer ctx.Oops() wrapping for consistent error codes and diagnostics. As per coding guidelines, use
ctx.Oops().♻️ Proposed refactor
result := ctx.DB().Table(table). Where("id IN ?", ids). Where("NOT (COALESCE(__scope, '{}'::uuid[]) @> ARRAY[?]::uuid[])", scopeID). UpdateColumn("__scope", gorm.Expr("array_append(COALESCE(__scope, '{}'::uuid[]), ?)", scopeID)) if result.Error != nil { - return 0, result.Error + return 0, ctx.Oops().Wrapf(result.Error, "failed to update __scope for %s", table) } return result.RowsAffected, nil } @@ result := ctx.DB().Table(table). Where("__scope @> ARRAY[?]::uuid[]", scopeID). UpdateColumn("__scope", gorm.Expr("array_remove(__scope, ?)", scopeID)) if result.Error != nil { - return 0, result.Error + return 0, ctx.Oops().Wrapf(result.Error, "failed to remove __scope for %s", table) } return result.RowsAffected, nil }Also applies to: 469-474
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (13)
api/event.goapi/v1/permission_types.goauth/rls.goauth/tokens.gocmd/server.godb/permissions.gogo.modjobs/jobs.gojobs/rls_scope_jobs.gopermission/events.gopermission/rls_scopes.gotests/permissions/permissions_test.gotests/permissions/testdata/scopes/multi-target-scope.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
- cmd/server.go
- tests/permissions/testdata/scopes/multi-target-scope.yaml
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*.go: Use context from github.com/flanksource/duty/context.Context to access db (ctx.DB()), properties (ctx.Properties()), and logger (ctx.Logger)
If a file needs to use both context and duty's context, alias context to 'gocontext'
Preferanyoverinterface{}in Go type declarations
Usectx.Oops()to craft new errors with error codes fromgithub.com/flanksource/duty/apias tags
UsedutyAPI.WriteError(c, err)in HTTP handlers to return errors with proper HTTP status code mapping based on error codes
For validation errors with no underlying error in HTTP handlers, usedutyAPI.Errorf(dutyAPI.EINVALID, "message")
For wrapping database/internal errors in HTTP handlers, usectx.Oops().Wrap(err)orctx.Oops().Wrapf(err, "context")
For permission errors in HTTP handlers, usectx.Oops().Code(dutyAPI.EFORBIDDEN).Errorf("message")
Useduty.Now()instead oftime.Now()for database timestamps and soft deletes
Only add comments if really necessary. Do not add comments that simply explain the code. Exception: comments about functions are considered good practice in Go even if they are self-explanatory
Files:
db/permissions.goapi/v1/permission_types.goapi/event.goauth/tokens.gojobs/rls_scope_jobs.gopermission/events.gotests/permissions/permissions_test.gojobs/jobs.goauth/rls.gopermission/rls_scopes.go
api/v1/**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
CRD definitions must be located in the api/v1 directory
Files:
api/v1/permission_types.go
**/*_test.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*_test.go: Always use ginkgo to run tests. Never rungo testdirectly
Always usegithub.com/onsi/gomegapackage for assertions in test files
When using gomega with native go tests, useg := gomega.NewWithT(t)andg.Expect()for assertions
Files:
tests/permissions/permissions_test.go
🧠 Learnings (8)
📚 Learning: 2025-11-24T18:35:17.129Z
Learnt from: CR
Repo: flanksource/mission-control PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:17.129Z
Learning: Applies to **/*.go : Use context from github.com/flanksource/duty/context.Context to access db (ctx.DB()), properties (ctx.Properties()), and logger (ctx.Logger)
Applied to files:
api/v1/permission_types.gotests/permissions/permissions_test.gogo.modauth/rls.go
📚 Learning: 2025-11-24T18:35:17.129Z
Learnt from: CR
Repo: flanksource/mission-control PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:17.129Z
Learning: Applies to **/*.go : Use `ctx.Oops()` to craft new errors with error codes from `github.com/flanksource/duty/api` as tags
Applied to files:
api/v1/permission_types.gotests/permissions/permissions_test.gogo.modauth/rls.go
📚 Learning: 2025-11-24T18:35:17.129Z
Learnt from: CR
Repo: flanksource/mission-control PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:17.129Z
Learning: Migrations are handled by the external package `github.com/flanksource/duty` using Atlas-go
Applied to files:
api/v1/permission_types.gotests/permissions/permissions_test.gogo.modauth/rls.go
📚 Learning: 2025-11-24T18:35:17.129Z
Learnt from: CR
Repo: flanksource/mission-control PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:17.129Z
Learning: Applies to **/*.go : If a file needs to use both context and duty's context, alias context to 'gocontext'
Applied to files:
api/v1/permission_types.go
📚 Learning: 2025-11-24T18:35:17.129Z
Learnt from: CR
Repo: flanksource/mission-control PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:17.129Z
Learning: Applies to **/*.go : Use `duty.Now()` instead of `time.Now()` for database timestamps and soft deletes
Applied to files:
api/v1/permission_types.gogo.modauth/rls.go
📚 Learning: 2025-11-24T18:35:17.129Z
Learnt from: CR
Repo: flanksource/mission-control PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:17.129Z
Learning: Applies to **/*.go : For permission errors in HTTP handlers, use `ctx.Oops().Code(dutyAPI.EFORBIDDEN).Errorf("message")`
Applied to files:
api/v1/permission_types.goauth/rls.go
📚 Learning: 2025-11-24T18:35:17.129Z
Learnt from: CR
Repo: flanksource/mission-control PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:17.129Z
Learning: Applies to api/**/*.go : Domain models and REST API models are located in the api/ directory
Applied to files:
tests/permissions/permissions_test.goauth/rls.go
📚 Learning: 2025-11-24T18:35:17.129Z
Learnt from: CR
Repo: flanksource/mission-control PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:17.129Z
Learning: Applies to **/*.go : Use `dutyAPI.WriteError(c, err)` in HTTP handlers to return errors with proper HTTP status code mapping based on error codes
Applied to files:
auth/rls.go
🧬 Code graph analysis (4)
auth/tokens.go (1)
auth/rls.go (1)
GetRLSPayload(37-63)
permission/events.go (3)
events/event_queue.go (2)
Register(36-38)RegisterAsyncHandler(40-48)api/event.go (3)
EventScopeMaterialize(71-71)EventPermissionMaterialize(72-72)Events(164-164)permission/rls_scopes.go (6)
ScopeQueueSourceScope(21-21)ScopeQueueSourcePermission(22-22)ScopeQueueActionRebuild(26-26)ScopeQueueActionApply(24-24)ScopeQueueActionRemove(25-25)GetProcessScopeJob(55-92)
tests/permissions/permissions_test.go (3)
api/v1/permission_types.go (1)
Permission(19-25)api/v1/scope_types.go (1)
Scope(77-83)permission/rls_scopes.go (4)
GetProcessScopeJob(55-92)ScopeQueueSourceScope(21-21)ScopeQueueActionRebuild(26-26)ScopeQueueSourcePermission(22-22)
jobs/jobs.go (1)
notification/context.go (1)
Context(24-29)
🪛 GitHub Actions: Build
auth/tokens.go
[error] 80-81: Build failed: config.Postgrest.DBRoleBypass is undefined in tokens.go (lines 80-81). Go code references a field that no longer exists in the duty API.
auth/rls.go
[error] 80-98: Build failed: dutyAPI.DefaultConfig.Postgrest.DBRoleBypass is undefined and SetPostgresSessionRLSWithRole is undefined (rls.go:80, rls.go:98). This indicates API type changes in the duty package breaking Go compilation during 'make build'.
🪛 GitHub Actions: CodeQL
auth/rls.go
[error] 80-80: Go compile error: undefined field DBRoleBypass in PostgrestConfig (duty API). The field DBRoleBypass does not exist on type 'github.com/flanksource/duty/api'.PostgrestConfig.
🪛 GitHub Actions: Lint
auth/rls.go
[error] 80-80: auth/rls.go:80:44: dutyAPI.DefaultConfig.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api'.PostgrestConfig has no field or method DBRoleBypass). GolangCI-Lint: run command failed: golangci-lint run --verbose --max-same-issues=0 --max-issues-per-linter=0
🪛 GitHub Actions: Test
auth/rls.go
[error] 80-80: dutyAPI.DefaultConfig.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api".PostgrestConfig has no field or method DBRoleBypass)
🪛 GitHub Check: lint
auth/tokens.go
[failure] 81-81:
config.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api".PostgrestConfig has no field or method DBRoleBypass) (typecheck)
[failure] 80-80:
config.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api".PostgrestConfig has no field or method DBRoleBypass)
auth/rls.go
[failure] 98-98:
rlsPayload.SetPostgresSessionRLSWithRole undefined (type *"github.com/flanksource/duty/rls".Payload has no field or method SetPostgresSessionRLSWithRole)
[failure] 80-80:
dutyAPI.DefaultConfig.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api".PostgrestConfig has no field or method DBRoleBypass)
🔇 Additional comments (13)
db/permissions.go (1)
47-51: LGTM - Simplified object selector handling.The refactored code now unconditionally marshals
obj.Spec.Objectto JSON, removing the previous global object branch. This aligns with the broader shift to scope-based permissions where global object semantics are handled elsewhere via the Scope CRD.api/v1/permission_types.go (1)
147-150: LGTM - Appropriate deprecation of GlobalObject.The method now returns a no-op result, which is consistent with the shift to scope-based permissions. The deprecation comment clearly communicates this change. The corresponding change in
db/permissions.go(removing theglobalObjectbranch) aligns with this deprecation.auth/rls.go (2)
128-188: Scope-ID-centric refactor looks correct.The refactored
buildRLSPayloadFromScopesfunction:
- Collects scope IDs into a
map[uuid.UUID]struct{}for deduplication- Uses the permission's own ID as the scope ID when direct resource selectors are present
- Builds the final payload from sorted scope IDs for deterministic ordering
One minor observation: Line 131-133 filters by
ActionReadafter the query already filters byaction = policy.ActionRead. This is redundant but harmless since permissions can have comma-separated actions.
213-228: LGTM - Deterministic UUID sorting helper.The
setToSortedUUIDSlicehelper correctly:
- Returns
nilfor empty sets (avoiding empty slice allocation)- Pre-allocates the output slice with correct capacity
- Sorts by string representation for deterministic ordering
This ensures consistent RLS payloads across requests, which is important for caching and testing.
auth/tokens.go (1)
86-93: LGTM - JWT claims construction.The JWT claims construction correctly:
- Uses the computed
rolevariable based on RLS state- Merges RLS payload claims when available via
JWTClaims()- Maintains backward compatibility with existing claim structure
Error handling follows coding guidelines with
ctx.Oops().Wrap().go.mod (1)
15-15: Temporary pseudo-version dependency should be replaced before merge.This dependency uses a pseudo-version from an unmerged PR branch. Please verify that the referenced PR is merged and update this to a stable release version before this PR can be merged.
jobs/jobs.go (1)
116-124: Scope materialization jobs are wired consistently.
Scheduling with sharedFuncSchedulerand setting the job context mirrors existing patterns.api/event.go (1)
70-72: New materialization event names look consistent.
These align with the new materialization handlers.permission/events.go (1)
17-70: Materialization handler validation is solid.
Defaulting the action, validating IDs/actions, and collecting failed events keeps the handler robust.jobs/rls_scope_jobs.go (1)
69-117: Cleanup job flow mirrors rebuild logic cleanly.
The deleted-scope sweep and per-scope removal handling are consistent with the rebuild job.tests/permissions/permissions_test.go (3)
68-69: Materialization before assertions improves determinism.
CallingmaterializeScopesAndPermissions()in setup makes the tests resilient to asynchronous materialization timing.Also applies to: 633-635
108-233: Scope-based payload assertions are aligned with the new model.
The shift to validatingpayload.Scopesand direct-permission IDs matches the precomputed scope approach.
1114-1138: Helper covers scope + permission materialization cleanly.
The helper’s per-scope/per-permission processing matches the new pipeline and keeps test setup centralized.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| return ctx.Transaction(func(txCtx context.Context, _ trace.Span) error { | ||
| role := dutyAPI.DefaultConfig.Postgrest.DBRoleBypass | ||
| if role == "" { | ||
| role = dutyAPI.DefaultConfig.Postgrest.DBRole | ||
| if role != "" { | ||
| txCtx.Logger.Warnf("RLS bypass role not configured, using role=%s", role) | ||
| } | ||
| } | ||
| if role == "" { | ||
| return fmt.Errorf("role is required") | ||
| } | ||
| if err := txCtx.DB().Exec(fmt.Sprintf("SET LOCAL ROLE %s", pq.QuoteIdentifier(role))).Error; err != nil { | ||
| return err | ||
| } | ||
| return fn(txCtx) | ||
| }) |
There was a problem hiding this comment.
Build failure: DBRoleBypass is undefined on PostgrestConfig.
The static analysis and pipeline failures confirm that dutyAPI.DefaultConfig.Postgrest.DBRoleBypass does not exist in the current duty version. This field must be added to the duty package before this code will compile.
The logic itself is sound: when RLS is disabled, set a local role for the transaction to ensure proper database access. However, the implementation depends on API that doesn't exist yet in the pinned duty version.
🧰 Tools
🪛 GitHub Actions: CodeQL
[error] 80-80: Go compile error: undefined field DBRoleBypass in PostgrestConfig (duty API). The field DBRoleBypass does not exist on type 'github.com/flanksource/duty/api'.PostgrestConfig.
🪛 GitHub Actions: Lint
[error] 80-80: auth/rls.go:80:44: dutyAPI.DefaultConfig.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api'.PostgrestConfig has no field or method DBRoleBypass). GolangCI-Lint: run command failed: golangci-lint run --verbose --max-same-issues=0 --max-issues-per-linter=0
🪛 GitHub Actions: Test
[error] 80-80: dutyAPI.DefaultConfig.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api".PostgrestConfig has no field or method DBRoleBypass)
🪛 GitHub Check: lint
[failure] 80-80:
dutyAPI.DefaultConfig.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api".PostgrestConfig has no field or method DBRoleBypass)
🤖 Prompt for AI Agents
In `@auth/rls.go` around lines 79 - 94, Add the missing DBRoleBypass field to the
PostgrestConfig in the duty package so the reference
dutyAPI.DefaultConfig.Postgrest.DBRoleBypass compiles: add a DBRoleBypass string
field to the PostgrestConfig struct, ensure DefaultConfig initializes it (and
any config parsing/loading populates it, e.g., from env/YAML), and update any
relevant tests or config docs; this will allow the rls.go code that reads
dutyAPI.DefaultConfig.Postgrest.DBRoleBypass and falls back to DBRole to build
successfully.
| rlsPayload, err := GetRLSPayload(ctx.WithUser(user)) | ||
| if err != nil { | ||
| return "", ctx.Oops().Wrap(err) | ||
| } | ||
|
|
||
| role := config.Postgrest.DBRole | ||
| if rlsPayload.Disable && config.Postgrest.DBRoleBypass != "" { | ||
| role = config.Postgrest.DBRoleBypass | ||
| } |
There was a problem hiding this comment.
Build failure: DBRoleBypass is undefined on PostgrestConfig.
The pipeline failures confirm that config.Postgrest.DBRoleBypass does not exist in the current duty API.
The refactored logic is correct:
- Fetch RLS payload upfront to determine the appropriate role
- Use the bypass role when RLS is disabled and a bypass role is configured
- Fall back to the standard
DBRoleotherwise
This aligns with the changes in auth/rls.go. Once the duty dependency is updated with the DBRoleBypass field, this will work correctly.
🧰 Tools
🪛 GitHub Actions: Build
[error] 80-81: Build failed: config.Postgrest.DBRoleBypass is undefined in tokens.go (lines 80-81). Go code references a field that no longer exists in the duty API.
🪛 GitHub Check: lint
[failure] 81-81:
config.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api".PostgrestConfig has no field or method DBRoleBypass) (typecheck)
[failure] 80-80:
config.Postgrest.DBRoleBypass undefined (type "github.com/flanksource/duty/api".PostgrestConfig has no field or method DBRoleBypass)
🤖 Prompt for AI Agents
In `@auth/tokens.go` around lines 74 - 82, The code references the non-existent
field config.Postgrest.DBRoleBypass causing build failures; update the role
selection in the block around GetRLSPayload and the local variable role so it
does not reference DBRoleBypass — keep role := config.Postgrest.DBRole and, for
now, remove the rlsPayload.Disable && config.Postgrest.DBRoleBypass != "" branch
(or replace it with a no-op/fallback that leaves role as DBRole), and add a TODO
noting to reintroduce DBRoleBypass once the duty dependency adds that field; use
the symbols GetRLSPayload, rlsPayload, and the local role to locate the change.
| if !collections.MatchItems(policy.ActionRead, strings.Split(permission.Action, ",")...) { | ||
| return nil |
There was a problem hiding this comment.
Normalize permission actions before the read check.
If permission.Action contains spaces (e.g., "read, write"), MatchItems can miss read, skipping materialization. Trim tokens first to avoid false negatives.
🛠️ Proposed fix
- if !collections.MatchItems(policy.ActionRead, strings.Split(permission.Action, ",")...) {
+ actions := strings.Split(permission.Action, ",")
+ for i, a := range actions {
+ actions[i] = strings.TrimSpace(a)
+ }
+ if !collections.MatchItems(policy.ActionRead, actions...) {
return nil
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if !collections.MatchItems(policy.ActionRead, strings.Split(permission.Action, ",")...) { | |
| return nil | |
| actions := strings.Split(permission.Action, ",") | |
| for i, a := range actions { | |
| actions[i] = strings.TrimSpace(a) | |
| } | |
| if !collections.MatchItems(policy.ActionRead, actions...) { | |
| return nil | |
| } |
🤖 Prompt for AI Agents
In `@permission/rls_scopes.go` around lines 275 - 276, permission.Action tokens
aren't trimmed before matching, so MatchItems(policy.ActionRead,
strings.Split(permission.Action, ",")...) can miss "read" when permission.Action
includes spaces; update the check in the materialization path to normalize
tokens by splitting and trimming each token (e.g., use strings.Split and
strings.TrimSpace or strings.FieldsFunc) before calling collections.MatchItems
with the cleaned slice, keeping the same semantics for policy.ActionRead and
permission.Action.
| if err := ctx.DB().Table(table). | ||
| Where("id IN ?", ids). | ||
| UpdateColumn("__scope", gorm.Expr("array_append(COALESCE(__scope, '{}'::uuid[]), ?)", scopeID)).Error; err != nil { | ||
| return 0, ctx.Oops().Wrapf(err, "failed to update __scope for %s", table) | ||
| } |
There was a problem hiding this comment.
Guard against duplicate __scope entries on concurrent runs.
The select/update split can append duplicates if another worker updates between the two. Add the same NOT condition to the update query for idempotency.
🛠️ Proposed fix
- if err := ctx.DB().Table(table).
- Where("id IN ?", ids).
- UpdateColumn("__scope", gorm.Expr("array_append(COALESCE(__scope, '{}'::uuid[]), ?)", scopeID)).Error; err != nil {
+ if err := ctx.DB().Table(table).
+ Where("id IN ?", ids).
+ Where("NOT (COALESCE(__scope, '{}'::uuid[]) @> ARRAY[?]::uuid[])", scopeID).
+ UpdateColumn("__scope", gorm.Expr("array_append(COALESCE(__scope, '{}'::uuid[]), ?)", scopeID)).Error; err != nil {
return 0, ctx.Oops().Wrapf(err, "failed to update __scope for %s", table)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if err := ctx.DB().Table(table). | |
| Where("id IN ?", ids). | |
| UpdateColumn("__scope", gorm.Expr("array_append(COALESCE(__scope, '{}'::uuid[]), ?)", scopeID)).Error; err != nil { | |
| return 0, ctx.Oops().Wrapf(err, "failed to update __scope for %s", table) | |
| } | |
| if err := ctx.DB().Table(table). | |
| Where("id IN ?", ids). | |
| Where("NOT (COALESCE(__scope, '{}'::uuid[]) @> ARRAY[?]::uuid[])", scopeID). | |
| UpdateColumn("__scope", gorm.Expr("array_append(COALESCE(__scope, '{}'::uuid[]), ?)", scopeID)).Error; err != nil { | |
| return 0, ctx.Oops().Wrapf(err, "failed to update __scope for %s", table) | |
| } |
🤖 Prompt for AI Agents
In `@permission/rls_scopes.go` around lines 429 - 433, The update can append
duplicate scopeIDs when concurrent workers race; modify the UpdateColumn call
that uses ctx.DB().Table(table).Where("id IN ?", ids).UpdateColumn("__scope",
gorm.Expr("array_append(COALESCE(__scope, '{}'::uuid[]), ?)", scopeID)) so the
WHERE clause also excludes rows that already contain scopeID (e.g. add a NOT
condition like NOT (? = ANY(__scope)) or equivalent SQL to the Where) so the
update is idempotent; keep the same symbols (ctx.DB(), table, ids, scopeID,
UpdateColumn, gorm.Expr) and ensure the combined WHERE prevents appending if
__scope already includes scopeID.
resolves: #2395
dependsOn: flanksource/duty#1728
Summary by CodeRabbit
New Features
Bug Fixes / Improvements
Tests
✏️ Tip: You can customize this high-level summary in your review settings.