Skip to content

Fix get policy by id endpoint and unify access in UI#45048

Merged
lucasmrod merged 4 commits into
mainfrom
fix-get-policy-id-endpoint-and-unify-access-in-ui
May 11, 2026
Merged

Fix get policy by id endpoint and unify access in UI#45048
lucasmrod merged 4 commits into
mainfrom
fix-get-policy-id-endpoint-and-unify-access-in-ui

Conversation

@lucasmrod
Copy link
Copy Markdown
Member

@lucasmrod lucasmrod commented May 8, 2026

Related issue: Resolves #44949.

  • 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

For unreleased bug fixes in a release candidate, one of:

  • Confirmed that the fix is not expected to adversely impact load test results.

Summary by CodeRabbit

  • Bug Fixes

    • Policy retrieval now correctly enforces team authorization, preventing unauthorized cross-team access and ensuring team policies are returned properly.
  • New Features

    • UI uses a unified policy access path for viewing/editing policies, improving consistency for inherited/team-scoped policies, back-navigation, and fleet-name display (All fleets / No team).
  • Tests

    • Added unit and integration tests covering cross-team access rules and that policy automation fields are populated when policies are returned.

Copilot AI review requested due to automatic review settings May 8, 2026 17:35
@lucasmrod lucasmrod requested review from a team as code owners May 8, 2026 17:35
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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

Codecov Report

❌ Patch coverage is 57.40741% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.81%. Comparing base (d25a76a) to head (ac9ba6a).
⚠️ Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
server/service/team_policies.go 27.27% 8 Missing and 8 partials ⚠️
frontend/services/entities/policies.ts 0.00% 4 Missing ⚠️
server/service/policies.go 87.50% 1 Missing and 1 partial ⚠️
...policies/edit/components/PolicyForm/PolicyForm.tsx 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #45048      +/-   ##
==========================================
+ Coverage   66.77%   66.81%   +0.03%     
==========================================
  Files        2718     2722       +4     
  Lines      218816   218987     +171     
  Branches    10622    10754     +132     
==========================================
+ Hits       146123   146307     +184     
+ Misses      59530    59517      -13     
  Partials    13163    13163              
Flag Coverage Δ
backend 68.67% <52.63%> (+0.02%) ⬆️
frontend 55.32% <68.75%> (+0.08%) ⬆️

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.

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

This PR fixes policy detail fetching for inherited/global policies in team context by switching the UI to a single policy “get by id” API and hardening the backend endpoint to safely return team-scoped policies.

Changes:

  • Added a new GetPolicyByID service method/endpoint implementation that re-authorizes on the fetched policy to prevent cross-team reads.
  • Refactored team policy endpoints to use a shared automation-population helper and renamed service interface methods (Get*ByIDQueriesGet*ByID).
  • Updated several policy-related UI pages to always load policy details via /fleet/policies/:id (global endpoint), fixing the inherited-policy 404.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
server/service/team_policies.go Refactors automation population and renames team policy “get by id” service method.
server/service/team_policies_test.go Updates auth test to new method name; adds coverage ensuring automation fields are populated for team policies.
server/service/policies.go Introduces unified GetPolicyByID endpoint/service method with re-authorization after fetch.
server/service/integration_core_test.go Adds integration test for cross-team access control on GET /fleet/policies/:id.
server/service/global_policies.go Removes legacy global-policy “get by id” implementation in favor of the unified one.
server/service/global_policies_test.go Updates auth test to call GetPolicyByID; adds regression test for cross-team reads.
server/mock/service/service_mock.go Updates mock service interface to renamed GetPolicyByID/GetTeamPolicyByID methods.
server/fleet/service.go Updates fleet.Service interface method names.
frontend/services/entities/policies.ts Adds a unified “policy by id” API client wrapper.
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx Preserves team context in links for inherited policies.
frontend/pages/policies/live/LivePolicyPage/LivePolicyPage.tsx Switches policy load to unified policies API.
frontend/pages/policies/edit/EditPolicyPage.tsx Switches policy load to unified policies API (removes prior endpoint branching).
frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx Switches policy load to unified policies API; drives displayed fleet/team from returned policy ownership.
frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx Switches policy load to unified policies API.
changes/fix-get-policy-id-endpoint-and-unify-access-in-ui Adds a user-visible changelog entry for the endpoint/UI behavior changes.

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

Comment thread server/service/policies.go Outdated
Comment thread changes/fix-get-policy-id-endpoint-and-unify-access-in-ui Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack
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: a4e22b8b-4f0c-4c1b-a64d-c97bedc54c71

📥 Commits

Reviewing files that changed from the base of the PR and between 67aba8e and ac9ba6a.

📒 Files selected for processing (2)
  • frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tests.tsx
  • frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tsx

Walkthrough

This PR fixes team-observer 404s for inherited policies by unifying policy retrieval under GET /api/latest/fleet/policies/:id. Backend adds Service.GetPolicyByID and getPolicyByIDEndpoint, centralizes team-policy automation enrichment via populateAutomationsForTeamPolicy, and renames service/mocks. Frontend adds policiesAPI.load and updates pages (PolicyDetails/Edit/Live/ManageHosts/PoliciesTable) to use it and derive fleet display from storedPolicy.team_id. Tests added/updated cover cross-team authorization, integration access checks, and automation population.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% 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 accurately summarizes the main changes: fixing the GET policy by ID endpoint and unifying access in the UI, which directly addresses the linked issue.
Description check ✅ Passed The description includes the related issue link, checks off relevant template items (changes file, tests, manual QA), and confirms no adverse load test impact.
Linked Issues check ✅ Passed All code changes directly address issue #44949: the backend now has a unified GET /api/latest/fleet/policies/:id endpoint with authorization checks [policies.go, team_policies.go], and the UI calls this endpoint for all policies [ManagePoliciesPage, PolicyDetailsPage, EditPolicyPage, LivePolicyPage, ManageHostsPage].
Out of Scope Changes check ✅ Passed All changes are directly scoped to the issue: backend endpoint unification, authorization checks, UI consolidation to use unified endpoint, test coverage for authorization and cross-team access, and updated mock implementations.

✏️ 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 fix-get-policy-id-endpoint-and-unify-access-in-ui

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.

🧹 Nitpick comments (4)
server/service/global_policies_test.go (1)

178-208: 💤 Low value

Missing coverage: "team admin of the policy's own team can read it"

TestGetPolicyByIDCrossTeamAuth checks team observer of the policy's team (case 3), but not team admin of the policy's team. Adding this case gives full coverage of the positive-access surface for team-scoped users.

🧪 Suggested additional test case
  {
    "team observer of the policy's team can read it",
    &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: policyTeamID}, Role: fleet.RoleObserver}}},
    false,
  },
+ {
+   "team admin of the policy's team can read it",
+   &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: policyTeamID}, Role: fleet.RoleAdmin}}},
+   false,
+ },
  {
    "team observer of a different team cannot read it",
🤖 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/global_policies_test.go` around lines 178 - 208, Add a missing
positive test case to the TestGetPolicyByIDCrossTeamAuth table: include a case
where the user is a team admin of the policy's own team (use Teams:
[]fleet.UserTeam{{Team: fleet.Team{ID: policyTeamID}, Role: fleet.RoleAdmin}})
and set shouldFailRead to false so the test verifies team admins on the policy's
team can read the policy; place this alongside the existing cases in the
testCases slice.
server/service/team_policies_test.go (1)

243-287: 💤 Low value

setupDS mock functions capture the outer t, not the sub-test's t.

The require.Equal(t, ...) assertions inside the DS mock closures (e.g. GetSoftwareInstallerMetadataByIDFunc, ScriptFunc) use the t from TestTeamPolicyAutomationsPopulated, not from the individual sub-test. When a require fails in a sub-test's goroutine using the outer t, the failure is attributed to the parent test rather than to the specific sub-test, making it harder to diagnose which sub-test triggered the assertion failure.

Consider threading the sub-test's t through setupDS (e.g., as a parameter) or moving the require.Equal calls into requireAutomationsPopulated (which already accepts the correct t).

♻️ Suggested approach
-setupDS := func() *mock.Store {
+setupDS := func(t *testing.T) *mock.Store {
     ds := new(mock.Store)
     ...
     ds.GetSoftwareInstallerMetadataByIDFunc = func(ctx context.Context, id uint) (*fleet.SoftwareInstaller, error) {
         require.Equal(t, softwareInstallerID, id)
         ...
     }
     ...
 }

 t.Run("NewTeamPolicy", func(t *testing.T) {
-    ds := setupDS()
+    ds := setupDS(t)
     ...
 })
🤖 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/team_policies_test.go` around lines 243 - 287, The mock
closures in setupDS (e.g., GetSoftwareInstallerMetadataByIDFunc, ScriptFunc,
GetSoftwareInstallerMetadataByTeamAndTitleIDFunc) capture the outer test
variable t, so assertion failures are attributed to the parent test instead of
the sub-test; fix by making setupDS accept a *testing.T parameter (or remove
those require.Equal checks from setupDS and move them into
requireAutomationsPopulated which already takes the sub-test t) and use that
sub-test t inside the mock functions (or perform the equality assertions inside
requireAutomationsPopulated), ensuring failures are reported against the correct
sub-test.
frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx (1)

115-119: ⚡ Quick win

teamIdForApi in the "policy" query key is now a stale dependency.

The fetch function unconditionally calls policiesAPI.load(policyId) and no longer uses teamIdForApi. Keeping it in the key causes React Query to treat the same policy as a separate cache entry for each team context, triggering redundant network requests whenever the user's selected team changes (e.g., navigating from an inherited-policy view on Team A's list to the same policy's detail page).

React Query's own guidance is to "treat the query key like a dependency array" — only include values that actually affect what the fetch returns.

♻️ Proposed fix
- ["policy", policyId, teamIdForApi],
+ ["policy", policyId],
  () => policiesAPI.load(policyId as number),

teamIdForApi can remain in backToPoliciesPath and the "Run"/"Edit" button links where it's still relevant.

🤖 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/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx`
around lines 115 - 119, The query key for the policy useQuery includes
teamIdForApi but the fetcher (policiesAPI.load in the call that builds
["policy", policyId, teamIdForApi]) does not use it, causing stale cache entries
per team; remove teamIdForApi from the query key (leave it in backToPoliciesPath
and button link generation where needed) so the key becomes ["policy", policyId]
and keep the enabled: isRouteOk && !!policyId logic unchanged; update any
references to the old key usage (e.g., places that invalidate or read the query)
to use the new ["policy", policyId] key.
frontend/pages/policies/edit/EditPolicyPage.tsx (1)

149-152: ⚡ Quick win

Same stale teamIdForApi in the "policy" query key.

Identical to PolicyDetailsPage.tsx: the fetch is now policiesAPI.load(policyId) but the key still includes teamIdForApi, splitting the cache unnecessarily across team-context changes.

♻️ Proposed fix
- ["policy", policyId, teamIdForApi],
+ ["policy", policyId],
  () => policiesAPI.load(policyId as number),
🤖 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/policies/edit/EditPolicyPage.tsx` around lines 149 - 152, The
query key for useQuery in EditPolicyPage.tsx currently includes teamIdForApi but
the fetcher is policiesAPI.load(policyId) (no team), which causes unnecessary
cache splits; update the key in the useQuery call from ["policy", policyId,
teamIdForApi] to ["policy", policyId] (or alternatively pass teamIdForApi into
the fetcher and keep the key consistent) so the cache key matches the
fetcher—adjust the useQuery invocation that references ["policy", policyId,
teamIdForApi"] accordingly.
🤖 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.

Nitpick comments:
In `@frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx`:
- Around line 115-119: The query key for the policy useQuery includes
teamIdForApi but the fetcher (policiesAPI.load in the call that builds
["policy", policyId, teamIdForApi]) does not use it, causing stale cache entries
per team; remove teamIdForApi from the query key (leave it in backToPoliciesPath
and button link generation where needed) so the key becomes ["policy", policyId]
and keep the enabled: isRouteOk && !!policyId logic unchanged; update any
references to the old key usage (e.g., places that invalidate or read the query)
to use the new ["policy", policyId] key.

In `@frontend/pages/policies/edit/EditPolicyPage.tsx`:
- Around line 149-152: The query key for useQuery in EditPolicyPage.tsx
currently includes teamIdForApi but the fetcher is policiesAPI.load(policyId)
(no team), which causes unnecessary cache splits; update the key in the useQuery
call from ["policy", policyId, teamIdForApi] to ["policy", policyId] (or
alternatively pass teamIdForApi into the fetcher and keep the key consistent) so
the cache key matches the fetcher—adjust the useQuery invocation that references
["policy", policyId, teamIdForApi"] accordingly.

In `@server/service/global_policies_test.go`:
- Around line 178-208: Add a missing positive test case to the
TestGetPolicyByIDCrossTeamAuth table: include a case where the user is a team
admin of the policy's own team (use Teams: []fleet.UserTeam{{Team:
fleet.Team{ID: policyTeamID}, Role: fleet.RoleAdmin}}) and set shouldFailRead to
false so the test verifies team admins on the policy's team can read the policy;
place this alongside the existing cases in the testCases slice.

In `@server/service/team_policies_test.go`:
- Around line 243-287: The mock closures in setupDS (e.g.,
GetSoftwareInstallerMetadataByIDFunc, ScriptFunc,
GetSoftwareInstallerMetadataByTeamAndTitleIDFunc) capture the outer test
variable t, so assertion failures are attributed to the parent test instead of
the sub-test; fix by making setupDS accept a *testing.T parameter (or remove
those require.Equal checks from setupDS and move them into
requireAutomationsPopulated which already takes the sub-test t) and use that
sub-test t inside the mock functions (or perform the equality assertions inside
requireAutomationsPopulated), ensuring failures are reported against the correct
sub-test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d9c1e5a9-3006-4976-808c-f65a06aca466

📥 Commits

Reviewing files that changed from the base of the PR and between bc89773 and c992d27.

📒 Files selected for processing (15)
  • changes/fix-get-policy-id-endpoint-and-unify-access-in-ui
  • frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
  • frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx
  • frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx
  • frontend/pages/policies/edit/EditPolicyPage.tsx
  • frontend/pages/policies/live/LivePolicyPage/LivePolicyPage.tsx
  • frontend/services/entities/policies.ts
  • server/fleet/service.go
  • server/mock/service/service_mock.go
  • server/service/global_policies.go
  • server/service/global_policies_test.go
  • server/service/integration_core_test.go
  • server/service/policies.go
  • server/service/team_policies.go
  • server/service/team_policies_test.go
💤 Files with no reviewable changes (1)
  • server/service/global_policies.go

Comment thread frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx Outdated
Comment thread frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx Outdated
Comment thread frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx Outdated
Comment thread server/service/global_policies_test.go
Comment thread server/service/policies.go
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.

🧹 Nitpick comments (1)
server/service/global_policies_test.go (1)

162-226: ⚡ Quick win

Previous feedback addressed; consider adding team admin test case for completeness.

The three new test functions (TestGetPolicyByIDCrossTeamAuth, TestGetPolicyByIDGlobalPolicyAuth, TestGetPolicyByIDNoTeamPolicyAuth) comprehensively address the earlier request for global and no-team policy test coverage.

Minor: The cross-team test includes positive cases for team observer (line 194) and team gitops (line 199) on the policy's team, but doesn't have a corresponding positive case for "team admin of the policy's team can read it". Adding this would make the test coverage more symmetric, though team admins are likely covered in integration tests.

➕ Optional: Add team admin positive test case
 		{
 			"team gitops of the policy's team can read it",
 			&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: policyTeamID}, Role: fleet.RoleGitOps}}},
 			false,
 		},
+		{
+			"team admin of the policy's team can read it",
+			&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: policyTeamID}, Role: fleet.RoleAdmin}}},
+			false,
+		},
 		{
 			"team observer of a different team cannot read it",
🤖 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/global_policies_test.go` around lines 162 - 226, Add a
positive assertion that a team admin on the policy's team can read the policy in
the TestGetPolicyByIDCrossTeamAuth test: create or reuse a team-admin identity
for the policy's team (matching how other roles are constructed in that test),
perform the same GetPolicyByID request used for team observer/gitops checks, and
assert a successful response/expected policy body; update setup/teardown or
helper calls used in TestGetPolicyByIDCrossTeamAuth to include this team-admin
case for symmetry with the other role checks.
🤖 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.

Nitpick comments:
In `@server/service/global_policies_test.go`:
- Around line 162-226: Add a positive assertion that a team admin on the
policy's team can read the policy in the TestGetPolicyByIDCrossTeamAuth test:
create or reuse a team-admin identity for the policy's team (matching how other
roles are constructed in that test), perform the same GetPolicyByID request used
for team observer/gitops checks, and assert a successful response/expected
policy body; update setup/teardown or helper calls used in
TestGetPolicyByIDCrossTeamAuth to include this team-admin case for symmetry with
the other role checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a368ca44-bccb-4c2e-b7fd-3786ee3fce89

📥 Commits

Reviewing files that changed from the base of the PR and between 72dc919 and 67aba8e.

📒 Files selected for processing (4)
  • changes/fix-get-policy-id-endpoint-and-unify-access-in-ui
  • frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx
  • server/service/global_policies_test.go
  • server/service/policies.go
✅ Files skipped from review due to trivial changes (1)
  • changes/fix-get-policy-id-endpoint-and-unify-access-in-ui
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx

@lucasmrod lucasmrod merged commit 04d773f into main May 11, 2026
58 checks passed
@lucasmrod lucasmrod deleted the fix-get-policy-id-endpoint-and-unify-access-in-ui branch May 11, 2026 12:39
lucasmrod added a commit that referenced this pull request May 11, 2026
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.

UI > Policies: Team observers get 404 when accessing inherited (global) policies

3 participants