Skip to content

feat: #7 S6a — device-group scope enforcement on device + device-group handlers#339

Closed
PaulDotterer wants to merge 2 commits into
mainfrom
feat/7-s6a-device-scope-enforcement
Closed

feat: #7 S6a — device-group scope enforcement on device + device-group handlers#339
PaulDotterer wants to merge 2 commits into
mainfrom
feat/7-s6a-device-scope-enforcement

Conversation

@PaulDotterer

@PaulDotterer PaulDotterer commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

First slice of #7's enforcement-consuming half — the scope primitives from S2b (#335) now have real call sites on the device and device-group handlers. Purely additive: an unscoped admin is globally scoped, so every existing call path is unchanged.

Builds on the design decisions settled for S6:

  • Group-keyed handlers match the group id first-class — a caller holding RenameDeviceGroup scoped to group X may act on X directly (no member enumeration).
  • :assigned composition — device-group scope applies ONLY to the unrestricted base-permission tier; :assigned self-service users stay owner-filtered, untouched.

What's wired

  • storeScopeResolver — the concrete auth.ScopeResolver over the device-group / user-group membership projections (DeviceGroupRepo.ListForDevice / UserGroupRepo.ListForUser). The auth package stays store-independent.
  • EnforceDeviceGroupScope (new auth helper) — first-class group-id match for group-keyed handlers; no resolver/membership lookup needed.
  • Per-item device handlersGetDevice, DeleteDevice, SetDeviceSyncInterval call EnforceDeviceScope, gated on userFilterID(...) == nil (the unrestricted tier). Checked before the read so an out-of-scope id is denied, not leaked via NotFound-vs-PermissionDenied.
  • All six group-keyed device-group handlersGetDeviceGroup, RenameDeviceGroup, UpdateDeviceGroupDescription, DeleteDeviceGroup, SetDeviceGroupSyncInterval, SetDeviceGroupMaintenanceWindow call EnforceDeviceGroupScope.

CI guard

Separate commit bumps the Integration Tests -timeout 20m→30m (interim, #338) so S6a's added tests don't tip the job over while the durable container-reuse fix is pending.

Tests

  • EnforceDeviceGroupScope units: global / first-class match / cross-kind / no-grant / unauthenticated.
  • Handler integration: scoped admin allowed in-scope, denied out-of-scope, denied non-existent (no existence oracle), unscoped admin sees all — across the per-item and group-keyed handlers.
  • go vet ./..., gofmt, full Device suite green. Local CodeRabbit: no findings.

Follow-ups (#7)

  • S6b — dispatch / executions / compliance / osquery / logs / terminal (several derive the device id from the execution row or session token).
  • S6c — list narrowing (ListDevices, ListDeviceGroups, ListExecutions, ListActiveTerminalSessions).

Closes part of #7.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added device-group scope enforcement for device and group management operations, restricting scoped administrators to their assigned device groups. Affected operations include device retrieval, deletion, sync interval configuration, and group management tasks such as renaming and maintenance window settings.
  • Tests

    • Added comprehensive test coverage for device-group scope authorization and permission validation.

The Integration Tests job runs each test against its own fresh Postgres
testcontainer; at #337 it hit 19m20s of the 20m budget. Until the durable
fix (container reuse, #338) lands, raise the ceiling so #7 S6's added
handler tests don't tip CI into a timeout failure.

Refs #338.
…up handlers (#7 S6a)

First slice of #7's enforcement-consuming half — the scope primitives from
S2b now have real call sites.

- storeScopeResolver: the concrete auth.ScopeResolver over the device-group
  / user-group membership projections (auth stays store-independent).
- EnforceDeviceGroupScope: authorizes group-keyed handlers by a FIRST-CLASS
  match of the group id against the caller's scope groups (a scoped admin
  may act on the groups in their scope) — no membership lookup needed.
- Per-item device handlers (GetDevice, DeleteDevice, SetDeviceSyncInterval)
  call EnforceDeviceScope. They compose with the existing :self/:assigned
  tier via the ownerScope == nil gate: device-group scope applies ONLY to
  the unrestricted base-permission holder; :assigned self-service users
  stay owner-filtered, untouched. Checked before the read, so an
  out-of-scope id is denied, not leaked via NotFound-vs-PermissionDenied.
- All six group-keyed device-group handlers (GetDeviceGroup, Rename,
  UpdateDescription, Delete, SetSyncInterval, SetMaintenanceWindow) call
  EnforceDeviceGroupScope.

An unscoped admin is globally scoped (DeviceScopeFilterFor.Global), so every
existing call path is unchanged.

Tests: EnforceDeviceGroupScope units (global / first-class match / cross-kind
/ no-grant); handler integration for in-scope allow, out-of-scope deny,
no-existence-oracle, unscoped-sees-all, across the per-item and group-keyed
handlers. Full Device suite green.

Follow-ups (#7): S6b — dispatch / executions / compliance / osquery / logs /
terminal (some derive the device id from the row or session token); S6c —
list narrowing (ListDevices, ListDeviceGroups, ListExecutions,
ListActiveTerminalSessions).

Refs #7.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR implements device-group scope enforcement for device and device-group API handlers. New authorization logic enforces scope restrictions, a store-backed resolver provides membership data, and handlers apply these checks before performing operations. Comprehensive tests verify scoped and unscoped access patterns. CI timeouts are extended to accommodate the test suite.

Changes

Device-Group Scope Enforcement

Layer / File(s) Summary
Authorization scope enforcement
internal/auth/scope.go, internal/auth/scope_test.go
EnforceDeviceGroupScope checks authentication and allows global or matching scoped group access, denying user-group-scoped permissions. Tests cover unauthenticated, global, matching scoped, mismatched scoped, and no-grant scenarios.
Store-backed scope resolver
internal/api/scope_resolver.go
storeScopeResolver implements auth.ScopeResolver by querying device-group and user-group membership projections, providing group IDs for scope resolution.
Device group handler scope enforcement
internal/api/device_group_handler.go
Six handlers (GetDeviceGroup, RenameDeviceGroup, UpdateDeviceGroupDescription, DeleteDeviceGroup, SetDeviceGroupSyncInterval, SetDeviceGroupMaintenanceWindow) call EnforceDeviceGroupScope before proceeding with group operations.
Device handler scope enforcement
internal/api/device_handler.go
Three handlers (GetDevice, DeleteDevice, SetDeviceSyncInterval) call EnforceDeviceScope with explicit ownerScope handling to restrict scoped admins to devices within their assigned groups.
Scope handler integration tests
internal/api/device_scope_handler_test.go
Helper deviceScopedAdminCtx and four test functions verify scoped/unscoped admin access, permission denial for out-of-scope resources, and group-keyed scope checks across device and device-group operations.
CI test timeout extension
.github/workflows/release.yml, .github/workflows/test.yml
Integration test timeouts increased from 20 to 30 minutes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hopped through scope-gated doors,
Device-group keys unlocking hidden floors.
Auth checks guard each handler's way,
No scoped admin roams astray!
Tests verify the boundaries hold,
Secure access, trusted and bold.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main changes: implementing device-group scope enforcement on device and device-group handlers, which is the primary focus across multiple files in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/7-s6a-device-scope-enforcement

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@PaulDotterer

Copy link
Copy Markdown
Contributor Author

Closing — wrong consumer for V1. Per the authoritative call (2026-06-10), V1 server scope enforcement targets the StartTerminal / TerminalAdmin path ONLY, not generic device / device-group handlers. The enforcement primitives (S2b) and authoring (S5) stand; this slice enforced on the wrong handlers (GetDevice/DeleteDevice/device-group management). Rebuilding S6 against StartTerminal + the TerminalAdmin per-scope reconciler. The broad-curated-table enforcement is deferred to V2 (if ever).

@PaulDotterer
PaulDotterer deleted the feat/7-s6a-device-scope-enforcement branch June 10, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant