Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Allow technicians to transfer hosts#45956

Merged
lucasmrod merged 6 commits into
mainfrom
41783-technician-transfer-hosts
May 21, 2026
Merged

Allow technicians to transfer hosts#45956
lucasmrod merged 6 commits into
mainfrom
41783-technician-transfer-hosts

Conversation

@lucasmrod
Copy link
Copy Markdown
Member

@lucasmrod lucasmrod commented May 21, 2026

Resolves #41783.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually.

Summary by CodeRabbit

  • New Features

    • Technicians can transfer hosts between fleets (Fleet Premium). Global technicians can transfer via the Fleet UI and REST API; fleet-scoped technicians can transfer between fleets they manage via the REST API.
    • Bulk transfer selection added on the hosts list for eligible global technicians, enabling multi-host transfers while preserving other bulk behaviors.
  • Tests

    • Added and updated tests covering Transfer visibility and authorization for global and fleet-scoped technician roles.

Review Change Stack

@codecov
Copy link
Copy Markdown

codecov Bot commented May 21, 2026

Codecov Report

❌ Patch coverage is 82.35294% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.81%. Comparing base (48b32e6) to head (e8f3b27).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
...nd/pages/hosts/ManageHostsPage/ManageHostsPage.tsx 66.66% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #45956      +/-   ##
==========================================
+ Coverage   66.78%   66.81%   +0.03%     
==========================================
  Files        2751     2754       +3     
  Lines      219892   220120     +228     
  Branches    10880    11025     +145     
==========================================
+ Hits       146846   147076     +230     
+ Misses      59775    59760      -15     
- Partials    13271    13284      +13     
Flag Coverage Δ
backend 68.61% <100.00%> (+0.01%) ⬆️
frontend 55.81% <76.92%> (+0.18%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lucasmrod lucasmrod marked this pull request as ready for review May 21, 2026 12:38
@lucasmrod lucasmrod requested review from a team as code owners May 21, 2026 12:38
Copilot AI review requested due to automatic review settings May 21, 2026 12:38
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a dedicated “transfer host” authorization action so technicians can transfer hosts between fleets per the updated permissions model, and exposes the Transfer UI controls to global technicians on Fleet Premium.

Changes:

  • Introduces ActionTransferHost and corresponding OPA policy rules for global and team-scoped technicians.
  • Updates host transfer service endpoints to authorize via ActionTransferHost (destination and source fleets) instead of ActionWrite.
  • Expands test coverage across Rego policy tests, service unit tests, integration tests, and frontend UI tests; enables bulk transfer selection in the Manage hosts table for global technicians.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
server/service/integration_mdm_test.go Updates integration permission expectations to allow global technician transfers; adds team-tech transfer scenarios.
server/service/hosts.go Switches transfer authorization from ActionWrite to ActionTransferHost for destination/source checks.
server/service/hosts_test.go Adds unit tests covering global and team-scoped technician transfer rules (including no-team behavior).
server/fleet/authz.go Adds new ActionTransferHost action constant.
server/authz/policy.rego Adds allow rules for transfer_host for relevant global/team roles.
server/authz/policy_test.go Extends authorization matrix tests to cover transfer_host.
frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx Shows Transfer action for global technicians and enables bulk-selection for transfer while hiding bulk delete.
frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tests.tsx Adds/updates tests for Transfer visibility for global technicians and ensures unrelated actions stay hidden.
frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/helpers.tsx Allows Transfer action for global technicians on Premium (non-Primo mode).
changes/41783-technician-transfer-hosts Adds changelog entry for technician host transfers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/service/hosts.go
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1d17f91-54ee-4768-bf9b-96d16686e069

📥 Commits

Reviewing files that changed from the base of the PR and between b0edc1f and e8f3b27.

📒 Files selected for processing (1)
  • frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tests.tsx

Walkthrough

This PR extends host transfer permissions to Technician role in policy and fleet authz, updates service methods to authorize transfers with ActionTransferHost (including source-team checks and No Team), exposes Transfer to global technicians in the frontend (individual and bulk flows), and adds/updates unit, policy, frontend, and integration tests plus a release note.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Allow technicians to transfer hosts' clearly and accurately summarizes the main change: enabling the technician role to transfer hosts.
Description check ✅ Passed The PR description references issue #41783 and checks off completed items (changes file added, automated tests added/updated, manual QA completed), though some non-applicable template sections were omitted as per template instructions.
Linked Issues check ✅ Passed The code changes comprehensively implement all objectives from #41783: UI changes for global technicians on manage hosts and host detail pages, REST API access via transfer-specific authorization, backend permission rules for technician transfer rights, and extensive test coverage for both global and team technicians.
Out of Scope Changes check ✅ Passed All changes are scoped to host transfer functionality for technicians: authorization rules, UI adjustments, API permission handling, and comprehensive tests. No unrelated changes detected.

✏️ 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 41783-technician-transfer-hosts

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@changes/41783-technician-transfer-hosts`:
- Line 1: Update the change description to explicitly state that host transfer
is a premium-only feature: modify the added release note sentence ("Added the
ability for users with the Technician role to transfer hosts...") to append a
clear note such as "This functionality is available on premium tiers only; it is
not available on the free tier." Also clarify scope differences (global
technicians via UI and API, fleet-scoped technicians via API) remain unchanged
but are limited to premium customers so users have correct expectations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1aa292f-c2b4-4bb3-b1c8-9f8a810cb782

📥 Commits

Reviewing files that changed from the base of the PR and between 86c6345 and 03bb0f1.

📒 Files selected for processing (10)
  • changes/41783-technician-transfer-hosts
  • frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
  • frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tests.tsx
  • frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/helpers.tsx
  • server/authz/policy.rego
  • server/authz/policy_test.go
  • server/fleet/authz.go
  • server/service/hosts.go
  • server/service/hosts_test.go
  • server/service/integration_mdm_test.go

Comment thread changes/41783-technician-transfer-hosts Outdated
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/service/hosts.go (1)

1243-1251: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make source-team authorization atomic with the transfer.

Line 1243/Line 1393 read each host's current TeamID, but the move happens later at Line 1251/Line 1407. A concurrent transfer can change the source team between those steps, so this can authorize against team A and then move the host out of team B. Please enforce the source-team check inside the same datastore transaction/update that performs the transfer, and fail if any host's current team drifted after authorization.

Also applies to: 1393-1408

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/service/hosts.go` around lines 1243 - 1251, The source-team check
(svc.authorizeHostSourceTeams called after svc.ds.ListHostsLiteByIDs) must be
moved into the same datastore operation that performs the transfer to avoid
TOCTOU races: modify the datastore update (currently svc.ds.AddHostsToTeam /
fleet.NewAddHostsToTeamParams) to accept the expected source team(s) or
host->team snapshot and perform an atomic transaction that verifies each host's
current TeamID matches the authorized source team(s) and only then updates
TeamID to the destination; if any host's TeamID differs, the datastore call must
fail and return an error so the service (svc) returns that error instead of
proceeding. Use or add a new method like AddHostsToTeamWithSourceCheck or add
parameters to AddHostsToTeam, and remove the separate
svc.authorizeHostSourceTeams step so authorization and update occur inside the
same transactional datastore call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@server/service/hosts.go`:
- Around line 1243-1251: The source-team check (svc.authorizeHostSourceTeams
called after svc.ds.ListHostsLiteByIDs) must be moved into the same datastore
operation that performs the transfer to avoid TOCTOU races: modify the datastore
update (currently svc.ds.AddHostsToTeam / fleet.NewAddHostsToTeamParams) to
accept the expected source team(s) or host->team snapshot and perform an atomic
transaction that verifies each host's current TeamID matches the authorized
source team(s) and only then updates TeamID to the destination; if any host's
TeamID differs, the datastore call must fail and return an error so the service
(svc) returns that error instead of proceeding. Use or add a new method like
AddHostsToTeamWithSourceCheck or add parameters to AddHostsToTeam, and remove
the separate svc.authorizeHostSourceTeams step so authorization and update occur
inside the same transactional datastore call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: da4eb874-90b2-49e6-9e36-80ab77dda685

📥 Commits

Reviewing files that changed from the base of the PR and between 562410d and 735c43f.

📒 Files selected for processing (1)
  • server/service/hosts.go

Copy link
Copy Markdown
Member

@nulmete nulmete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just suggesting some minor tweaks. (Except for the isOnlyObserver -> canSelectHosts prop change, which I think we should tackle separately.)

Comment thread server/authz/policy.rego
Comment thread server/service/hosts_test.go Outdated
Comment thread frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/service/hosts_test.go (1)

1961-1972: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test logic bug: destination team ID doesn't match test intent.

The test comment states "team 1 maintainer tries to transfer it to team 1" and the test name is "cannot steal host from another team", but line 1969 uses new(uint(2)) as the destination, attempting to transfer from team 2 to team 2.

To properly test the "stealing" scenario (unauthorized access to source team), the destination should be a team the user CAN write to (team 1), so the failure is specifically due to lack of access to the source team. Compare with the similar test at line 1982 and the technician test at line 2165, which correctly use new(uint(1)).

🐛 Proposed fix
     userCtx := test.UserContext(ctx, test.UserTeamMaintainerTeam1)
-    err := svc.AddHostsToTeam(userCtx, new(uint(2)), []uint{10}, false)
+    err := svc.AddHostsToTeam(userCtx, new(uint(1)), []uint{10}, false)
     require.Error(t, err)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/service/hosts_test.go` around lines 1961 - 1972, The test is using the
wrong destination team ID so it doesn't exercise the "steal from another team"
case; update the destination passed to svc.AddHostsToTeam in the "team
maintainer cannot steal host from another team" test to be new(uint(1)) so a
user in test.UserTeamMaintainerTeam1 attempts to transfer host ID 10 (which
ds.ListHostsLiteByIDsFunc returns with TeamID=2) into team 1; ensure the test
still asserts require.Error and that the error contains "forbidden" (references:
svc.AddHostsToTeam, ds.ListHostsLiteByIDsFunc, test.UserTeamMaintainerTeam1).
🧹 Nitpick comments (1)
frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tests.tsx (1)

117-117: 💤 Low value

Prefer getByText for required elements in this assertion path.

Using screen.queryByText("Actions") here returns HTMLElement | null. If the "Actions" button is ever unexpectedly absent (e.g., a regression hides it for team technicians too), user.click(null) will fail with a less informative error than getByText's "Unable to find an element with the text: Actions". The other Transfer tests in this block (e.g., Line 90) already use getByText, so aligning here improves consistency and diagnostics. Note: a previous reviewer suggested queryByText specifically to drop the if-check — the if-check removal is right, but getByText achieves the same intent (failing fast on absence) with a clearer message.

♻️ Proposed change
-      await user.click(screen.queryByText("Actions"));
+      await user.click(screen.getByText("Actions"));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tests.tsx`
at line 117, Replace the nullable lookup with a required lookup in the test: in
HostActionsDropdown.tests.tsx change the user.click call that uses
screen.queryByText("Actions") to use screen.getByText("Actions") so the test
fails fast with a clear "Unable to find an element with the text: Actions"
message; update the invocation around user.click(screen.getByText("Actions"))
and ensure no conditional/null checks remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@server/service/hosts_test.go`:
- Around line 1961-1972: The test is using the wrong destination team ID so it
doesn't exercise the "steal from another team" case; update the destination
passed to svc.AddHostsToTeam in the "team maintainer cannot steal host from
another team" test to be new(uint(1)) so a user in test.UserTeamMaintainerTeam1
attempts to transfer host ID 10 (which ds.ListHostsLiteByIDsFunc returns with
TeamID=2) into team 1; ensure the test still asserts require.Error and that the
error contains "forbidden" (references: svc.AddHostsToTeam,
ds.ListHostsLiteByIDsFunc, test.UserTeamMaintainerTeam1).

---

Nitpick comments:
In
`@frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tests.tsx`:
- Line 117: Replace the nullable lookup with a required lookup in the test: in
HostActionsDropdown.tests.tsx change the user.click call that uses
screen.queryByText("Actions") to use screen.getByText("Actions") so the test
fails fast with a clear "Unable to find an element with the text: Actions"
message; update the invocation around user.click(screen.getByText("Actions"))
and ensure no conditional/null checks remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fa352915-de4e-4da0-a8ae-dbefdffa63ae

📥 Commits

Reviewing files that changed from the base of the PR and between 735c43f and b0edc1f.

📒 Files selected for processing (2)
  • frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/HostActionsDropdown.tests.tsx
  • server/service/hosts_test.go

@lucasmrod lucasmrod requested a review from nulmete May 21, 2026 18:26
@lucasmrod lucasmrod merged commit 4e3f8c0 into main May 21, 2026
44 checks passed
@lucasmrod lucasmrod deleted the 41783-technician-transfer-hosts branch May 21, 2026 18:46
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.

Technician role: allow transferring hosts between fleets

3 participants