fleet/frontend/components/LiveQuery/SelectTargets.tests.tsx
Nico 24e0ef47c8
Fix observer query bypass: prevent cross-team targeting (#40717)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #36093 

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.


# Testing

- [x] Added/updated automated tests

- [x] QA'd all new/changed functionality manually

## Queries/reports

### Team user with team report (observer_can_run = true)

Created user with the following assignments:

<img width="596" height="285" alt="Screenshot 2026-03-02 at 4 58 47 PM"
src="https://github.com/user-attachments/assets/a3a8e7dd-2bfc-40f9-948c-b26b016162ae"
/>

Created report on **Workstations (canary)** fleet with
**observers_can_run = true**

<img width="1020" height="711" alt="Screenshot 2026-03-02 at 5 09 25 PM"
src="https://github.com/user-attachments/assets/58aa98c7-8cbd-4a7a-a159-f4b40a65f2c9"
/>

Logged in with newly-created user, selected the report above to run it
as a live report.

- Verified that **Servers (canary)** is disabled => user is **Observer**
on that fleet, but query belongs to **Workstations (canary)**.
- All the other fleets are enabled:
  - User is **Observer+ or more** in those fleets.
- User is **Observer** in **Workstations (canary)** => enabled because
report belongs to this fleet, AND **observer_can_run = true**.

<img width="986" height="823" alt="Screenshot 2026-03-02 at 5 07 29 PM"
src="https://github.com/user-attachments/assets/b6b7aa4b-5036-46e3-8497-3a77f93a3a2c"
/>


### Global user with team report (observer_can_run = true)

- Created global Observer user.
- Accessed same report created above for **Workstations (canary)** fleet
with **observers_can_run = true**.
- Logged in with newly-created user, selected the report above to run it
as a live report.
- Verified that the only target available is **Workstations (canary)**:

<img width="1087" height="883" alt="Screenshot 2026-03-03 at 10 47
05 AM"
src="https://github.com/user-attachments/assets/9fc8d4d4-6a38-4ecb-98fe-b56b46ac4f74"
/>

### Global user with global report (observer_can_run = true)

Global Observer user can target all fleets.

<img width="1329" height="609" alt="Screenshot 2026-03-03 at 10 56
03 AM"
src="https://github.com/user-attachments/assets/059d4eb2-546f-4a19-9eee-b64dd0250bf1"
/>

<img width="981" height="818" alt="Screenshot 2026-03-03 at 10 57 50 AM"
src="https://github.com/user-attachments/assets/afa0ee58-3457-4838-a96e-dd508d924079"
/>

### Global user with global report (observer_can_run = false)

Global Observer user can't target any fleet.

<img width="691" height="574" alt="Screenshot 2026-03-03 at 10 59 57 AM"
src="https://github.com/user-attachments/assets/f328d547-ed06-4c30-ac22-5df7bb32240a"
/>

<img width="985" height="814" alt="Screenshot 2026-03-03 at 11 00 06 AM"
src="https://github.com/user-attachments/assets/bb55da11-ea3f-40c7-bd98-652880d9e8f9"
/>

## Policies

On the FE, the same component is used to display the targets for Live
Policies, so just making sure that I didn't introduce any regression.

### Global technician user, all fleets policy

Can select all fleets.

<img width="1130" height="858" alt="Screenshot 2026-03-03 at 11 13
40 AM"
src="https://github.com/user-attachments/assets/8d9d97c4-9946-4c4c-9a8a-d79c65d9cb33"
/>

### Team user with team policy

Created user:

- **Technician** on **Servers**.
- **Observer** on **Servers (canary)**.

<img width="745" height="770" alt="Screenshot 2026-03-03 at 11 18 11 AM"
src="https://github.com/user-attachments/assets/56973c34-49bb-4007-9fac-09cf5315bdff"
/>

Can only select **Servers** as a target:

<img width="999" height="754" alt="Screenshot 2026-03-03 at 11 18 56 AM"
src="https://github.com/user-attachments/assets/82d14a8f-46e1-41f5-9355-d717477c85d8"
/>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lucas Manuel Rodriguez <lucas@fleetdm.com>
2026-03-05 15:12:04 -03:00

538 lines
14 KiB
TypeScript

import React from "react";
import { screen, waitFor } from "@testing-library/react";
import { http, HttpResponse } from "msw";
import mockServer from "test/mock-server";
import { baseUrl, createCustomRenderer } from "test/test-utils";
import createMockUser from "__mocks__/userMock";
import SelectTargets from "./SelectTargets";
const MOCK_LABELS = [
{ id: 1, name: "All Hosts", label_type: "builtin", description: "" },
{ id: 2, name: "macOS", label_type: "builtin", description: "" },
];
const MOCK_TEAMS = [
{ id: 1, name: "Team Alpha", host_count: 10, user_count: 5 },
{ id: 2, name: "Team Beta", host_count: 20, user_count: 8 },
];
const labelSummariesHandler = http.get(baseUrl("/labels/summary"), () => {
return HttpResponse.json({ labels: MOCK_LABELS });
});
const teamsHandler = http.get(baseUrl("/teams"), () => {
return HttpResponse.json({ teams: MOCK_TEAMS });
});
const defaultProps = {
baseClass: "select-targets",
selectedTargets: [],
targetedHosts: [],
targetedLabels: [],
targetedTeams: [],
goToQueryEditor: jest.fn(),
goToRunQuery: jest.fn(),
setSelectedTargets: jest.fn(),
setTargetedHosts: jest.fn(),
setTargetedLabels: jest.fn(),
setTargetedTeams: jest.fn(),
setTargetsTotalCount: jest.fn(),
};
const getTeamButton = (name: string) =>
screen.getByText(name).closest("button");
describe("SelectTargets - team disabling", () => {
beforeEach(() => {
mockServer.use(labelSummariesHandler, teamsHandler);
});
describe("plain observer (not observer+)", () => {
const plainObserverOnBothTeams = createMockUser({
global_role: null,
teams: [
{ ...MOCK_TEAMS[0], role: "observer" },
{ ...MOCK_TEAMS[1], role: "observer" },
],
});
it("disables all observer teams for live policies", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: plainObserverOnBothTeams,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
render(
<SelectTargets
{...defaultProps}
isLivePolicy
isObserverCanRunQuery={false}
/>
);
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeDisabled();
expect(getTeamButton("Team Beta")).toBeDisabled();
});
});
it("disables observer teams when query does not have observer_can_run", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: plainObserverOnBothTeams,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery={false}
queryTeamId={1}
/>
);
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeDisabled();
expect(getTeamButton("Team Beta")).toBeDisabled();
});
});
it("enables only the query's own team when observer_can_run is true", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: plainObserverOnBothTeams,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery
queryTeamId={1}
/>
);
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeDisabled();
});
});
it("disables observer teams targeting a different team than the query's team", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: plainObserverOnBothTeams,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery
queryTeamId={2}
/>
);
await waitFor(() => {
// Team Alpha is not the query's team, so disabled for observer
expect(getTeamButton("Team Alpha")).toBeDisabled();
// Team Beta IS the query's team, so enabled
expect(getTeamButton("Team Beta")).toBeEnabled();
});
});
});
describe("observer+ user", () => {
const observerPlusOnBothTeams = createMockUser({
global_role: null,
teams: [
{ ...MOCK_TEAMS[0], role: "observer_plus" },
{ ...MOCK_TEAMS[1], role: "observer_plus" },
],
});
it("enables all teams for observer+ even on live policies", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: observerPlusOnBothTeams,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
render(
<SelectTargets
{...defaultProps}
isLivePolicy
isObserverCanRunQuery={false}
/>
);
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeEnabled();
});
});
it("enables all teams for observer+ on queries", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: observerPlusOnBothTeams,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery={false}
queryTeamId={1}
/>
);
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeEnabled();
});
});
});
describe("multi-team user with mixed roles (admin on team 1, observer on team 2)", () => {
const adminObsUser = createMockUser({
global_role: null,
teams: [
{ ...MOCK_TEAMS[0], role: "admin" },
{ ...MOCK_TEAMS[1], role: "observer" },
],
});
it("disables only the observer team for live policies", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: adminObsUser,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
render(
<SelectTargets
{...defaultProps}
isLivePolicy
isObserverCanRunQuery={false}
/>
);
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeDisabled();
});
});
it("disables observer team when observer_can_run query belongs to a different team", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: adminObsUser,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
// Query belongs to team 1 (admin team), observer_can_run is true
// Team 2 (observer) should still be disabled because query belongs to team 1
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery
queryTeamId={1}
/>
);
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeDisabled();
});
});
it("enables observer team when observer_can_run query belongs to that team", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: adminObsUser,
isPremiumTier: true,
isOnGlobalTeam: false,
},
},
});
// Query belongs to team 2 (observer team) and observer_can_run is true
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery
queryTeamId={2}
/>
);
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeEnabled();
});
});
});
describe("global observer", () => {
const globalObserver = createMockUser({
global_role: "observer",
teams: [],
});
it("disables all teams (including Unassigned) for live policies", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: globalObserver,
isPremiumTier: true,
isOnGlobalTeam: true,
},
},
});
render(
<SelectTargets
{...defaultProps}
isLivePolicy
isObserverCanRunQuery={false}
/>
);
await waitFor(() => {
expect(getTeamButton("Unassigned")).toBeDisabled();
expect(getTeamButton("Team Alpha")).toBeDisabled();
expect(getTeamButton("Team Beta")).toBeDisabled();
});
});
it("disables all teams when query does not have observer_can_run", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: globalObserver,
isPremiumTier: true,
isOnGlobalTeam: true,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery={false}
queryTeamId={1}
/>
);
await waitFor(() => {
expect(getTeamButton("Unassigned")).toBeDisabled();
expect(getTeamButton("Team Alpha")).toBeDisabled();
expect(getTeamButton("Team Beta")).toBeDisabled();
});
});
it("enables only the query's own team when observer_can_run is true", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: globalObserver,
isPremiumTier: true,
isOnGlobalTeam: true,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery
queryTeamId={1}
/>
);
await waitFor(() => {
expect(getTeamButton("Unassigned")).toBeDisabled();
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeDisabled();
});
});
it("enables all teams for a global observer_can_run query (no team_id)", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: globalObserver,
isPremiumTier: true,
isOnGlobalTeam: true,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery
queryTeamId={null}
/>
);
await waitFor(() => {
expect(getTeamButton("Unassigned")).toBeEnabled();
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeEnabled();
});
});
});
describe("helper text visibility", () => {
it("shows helper text when some fleets are disabled", async () => {
const plainObserver = createMockUser({
global_role: "observer",
teams: [],
});
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: plainObserver,
isPremiumTier: true,
isOnGlobalTeam: true,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery={false}
queryTeamId={1}
/>
);
await waitFor(() => {
expect(
screen.getByText("Results limited to fleets you can access.")
).toBeInTheDocument();
});
});
it("does not show helper text when no fleets are disabled", async () => {
const globalAdmin = createMockUser({
global_role: "admin",
teams: [],
});
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: globalAdmin,
isPremiumTier: true,
isOnGlobalTeam: true,
},
},
});
render(
<SelectTargets
{...defaultProps}
isObserverCanRunQuery={false}
queryTeamId={1}
/>
);
// Wait for teams to render to confirm loading is done before asserting absence
await waitFor(() => {
expect(getTeamButton("Team Alpha")).toBeInTheDocument();
});
expect(
screen.queryByText("Results limited to fleets you can access.")
).not.toBeInTheDocument();
});
});
describe("global observer+", () => {
const globalObserverPlus = createMockUser({
global_role: "observer_plus",
teams: [],
});
it("enables all teams for global observer+ even on live policies", async () => {
const render = createCustomRenderer({
withBackendMock: true,
context: {
app: {
currentUser: globalObserverPlus,
isPremiumTier: true,
isOnGlobalTeam: true,
},
},
});
render(
<SelectTargets
{...defaultProps}
isLivePolicy
isObserverCanRunQuery={false}
/>
);
await waitFor(() => {
expect(getTeamButton("Unassigned")).toBeEnabled();
expect(getTeamButton("Team Alpha")).toBeEnabled();
expect(getTeamButton("Team Beta")).toBeEnabled();
});
});
});
});