Skip to content

Add fleet-user creation check#47566

Open
lucasmrod wants to merge 1 commit into
mainfrom
add-integration-test-for-fleet-users-creation
Open

Add fleet-user creation check#47566
lucasmrod wants to merge 1 commit into
mainfrom
add-integration-test-for-fleet-users-creation

Conversation

@lucasmrod

@lucasmrod lucasmrod commented Jun 13, 2026

Copy link
Copy Markdown
Member
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • Tests
    • Added test coverage to verify team administrator access controls and boundary enforcement. The test ensures that team admins cannot create users in other teams, validating this restriction across both standard and API-only user creation endpoints. The test also confirms that users can be successfully created within their authorized team scope.

Copilot AI review requested due to automatic review settings June 13, 2026 14:15
@lucasmrod lucasmrod requested a review from a team as a code owner June 13, 2026 14:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds an enterprise integration test to ensure a team-scoped admin cannot create a user with team roles in fleets/teams they don’t administer (even if one of the requested team roles is allowed), covering both the “admin create” and “API-only create” endpoints that flow through svc.CreateUser.

Changes:

  • Added TestTeamAdminCannotCreateUserInOtherTeams to validate cross-team user creation is rejected with 403 Forbidden.
  • Verified the forbidden request does not create the user (admin create path) and that a team admin can still create a user scoped only to their own team.

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

Comment on lines +5127 to +5132
// A fleet-admin must not be able to create a user with roles in teams they don't administer,
// even when one team in the payload is their own.
//
// Both the admin create path (POST /users) and the API-only create
// path (POST /users/api_only) funnel through svc.CreateUser, so both are
// exercised here.
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds a new integration test TestTeamAdminCannotCreateUserInOtherTeams to the enterprise test suite. The test establishes two teams and a user with admin privileges on only the first team, then verifies that both user creation endpoints (POST /users/admin and POST /users/api_only) reject requests containing role assignments for teams the admin doesn't manage. The test confirms these requests return 403 Forbidden, validates the user record is not created in the forbidden case, and confirms that user creation succeeds when limited to the admin's authorized team.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request is missing required description content, including the related issue reference, testing checklist, and explanation of changes. Add a complete pull request description following the template, including the related issue number, relevant checklist items, and testing details.
Title check ❓ Inconclusive The title is vague and does not clearly describe the main change, which is adding a specific test for team admin permissions. Use a more descriptive title like 'Add test for team admin user creation restrictions' or 'Add integration test for cross-team user creation validation'.
✅ Passed checks (3 passed)
Check name Status Explanation
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 add-integration-test-for-fleet-users-creation

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.43.0)
server/service/integration_enterprise_test.go

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
server/service/integration_enterprise_test.go (2)

5148-5158: 💤 Low value

Consider cleaning up the team admin user.

The test creates a teamAdmin user directly via the datastore but never deletes it. While integration test databases may be reset between runs, explicit cleanup improves test hygiene.

♻️ Add cleanup for the team admin user
 	_, err = s.ds.NewUser(ctx, teamAdmin)
 	require.NoError(t, err)
+	defer func() {
+		s.token = s.getTestAdminToken()
+		require.NoError(t, s.ds.DeleteUser(ctx, teamAdmin.ID))
+	}()
 
 	// Act as the team1 admin for the rest of the test.
 	s.token = s.getTestToken(teamAdmin.Email, test.GoodPassword)

Note: Adjust based on whether s.ds.DeleteUser exists or if a different cleanup method is appropriate.

🤖 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/integration_enterprise_test.go` around lines 5148 - 5158, Add
explicit cleanup for the teamAdmin user created in the test: after you finish
using teamAdmin call the datastore delete method (e.g. s.ds.DeleteUser(ctx,
teamAdmin.ID)) and assert success (require.NoError) to remove the user; if
s.ds.DeleteUser does not exist, call the appropriate teardown method (e.g.
RemoveUser/DestroyUser or delete via SQL helper) and assert no error—this
ensures the user created by NewUser (teamAdmin) is removed at the end of the
test.

5190-5202: 💤 Low value

Consider cleaning up the successfully created user.

The sanity check creates a user that succeeds (line 5193), but the test doesn't clean up this resource. Adding cleanup improves test isolation.

♻️ Add cleanup for the successfully created user
 	}, http.StatusOK, &resp)
 	require.NotNil(t, resp.User)
+	defer func() {
+		s.token = s.getTestAdminToken()
+		require.NoError(t, s.ds.DeleteUser(ctx, resp.User.ID))
+	}()
 }

Note: Since this is the last operation in the test, you may alternatively add the cleanup before the function returns rather than using defer.

🤖 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/integration_enterprise_test.go` around lines 5190 - 5202, The
test creates a user into resp (createUserResponse) via s.DoJSON but never
removes it; after the successful creation capture resp.User.ID (or resp.User)
and delete the created user to avoid leaking state — either immediately defer a
cleanup call (e.g., call the existing user-deletion helper or s.DoJSON with the
users delete endpoint and the created ID) or perform the delete before the test
returns; update the block containing the s.DoJSON POST that populates resp so it
runs the corresponding delete using that ID.
🤖 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 `@server/service/integration_enterprise_test.go`:
- Around line 5180-5188: The API-only endpoint test calls s.DoJSON("POST",
"/api/latest/fleet/users/api_only", createAPIOnlyUserRequest...) and asserts
http.StatusForbidden but does not verify the user wasn't persisted; add a
persistence check after that call similar to the admin-path check: attempt to
lookup the user (by email if createAPIOnlyUserRequest included one, or by name
using the same query used earlier in the file) and assert the user does not
exist (or that the lookup returns empty/not found), using the same helper/query
used in the admin test to ensure symmetry with the admin endpoint verification;
if API-only users require a different lookup, use that mechanism instead.

---

Nitpick comments:
In `@server/service/integration_enterprise_test.go`:
- Around line 5148-5158: Add explicit cleanup for the teamAdmin user created in
the test: after you finish using teamAdmin call the datastore delete method
(e.g. s.ds.DeleteUser(ctx, teamAdmin.ID)) and assert success (require.NoError)
to remove the user; if s.ds.DeleteUser does not exist, call the appropriate
teardown method (e.g. RemoveUser/DestroyUser or delete via SQL helper) and
assert no error—this ensures the user created by NewUser (teamAdmin) is removed
at the end of the test.
- Around line 5190-5202: The test creates a user into resp (createUserResponse)
via s.DoJSON but never removes it; after the successful creation capture
resp.User.ID (or resp.User) and delete the created user to avoid leaking state —
either immediately defer a cleanup call (e.g., call the existing user-deletion
helper or s.DoJSON with the users delete endpoint and the created ID) or perform
the delete before the test returns; update the block containing the s.DoJSON
POST that populates resp so it runs the corresponding delete using that ID.
🪄 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: a1fd76da-c93f-4c99-85d2-573ace97b431

📥 Commits

Reviewing files that changed from the base of the PR and between 268c918 and a6a254b.

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

Comment on lines +5180 to +5188
// API-only create path inherits the same authorization, so it must reject
// the cross-team payload too.
s.DoJSON("POST", "/api/latest/fleet/users/api_only", createAPIOnlyUserRequest{
Name: new(t.Name() + "_crossteam_api"),
Fleets: &[]fleetsPayload{
{ID: team1.ID, Role: fleet.RoleAdmin},
{ID: team2.ID, Role: fleet.RoleAdmin},
},
}, http.StatusForbidden, &resp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add persistence verification for the API-only forbidden request.

The admin endpoint test (lines 5176-5178) verifies the forbidden user was not persisted, but the API-only endpoint test has no equivalent check. While both funnel through the same authorization layer, asymmetric verification weakens regression protection.

✅ Add a persistence check after the API-only request

If the createAPIOnlyUserRequest includes an email or if API-only users can be queried by name, add a verification similar to lines 5176-5178:

 	s.DoJSON("POST", "/api/latest/fleet/users/api_only", createAPIOnlyUserRequest{
 		Name: new(t.Name() + "_crossteam_api"),
 		Fleets: &[]fleetsPayload{
 			{ID: team1.ID, Role: fleet.RoleAdmin},
 			{ID: team2.ID, Role: fleet.RoleAdmin},
 		},
 	}, http.StatusForbidden, &resp)
+
+	// Verify the forbidden API-only request also didn't persist a user.
+	// Adjust the query method based on how API-only users can be retrieved.
+	_, err = s.ds.UserByEmail(ctx, t.Name()+"_crossteam_api@example.com")
+	require.True(t, fleet.IsNotFound(err))

Note: Adjust the verification method if API-only users don't have email addresses or require a different lookup mechanism.

🤖 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/integration_enterprise_test.go` around lines 5180 - 5188, The
API-only endpoint test calls s.DoJSON("POST",
"/api/latest/fleet/users/api_only", createAPIOnlyUserRequest...) and asserts
http.StatusForbidden but does not verify the user wasn't persisted; add a
persistence check after that call similar to the admin-path check: attempt to
lookup the user (by email if createAPIOnlyUserRequest included one, or by name
using the same query used earlier in the file) and assert the user does not
exist (or that the lookup returns empty/not found), using the same helper/query
used in the admin test to ensure symmetry with the admin endpoint verification;
if API-only users require a different lookup, use that mechanism instead.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: aggregate-result

Failed stage: Check for failures [❌]

Failed test name: integration-enterprise-mysql8.0.44

Failure summary:

The action failed because one of the downloaded test job status artifacts reported a failure:
- The
status file ./integration-enterprise-mysql8.0.44-status/status contained fail, so the script marked
the test integration-enterprise-mysql8.0.44 as failed and exited with code 1 (see lines 179-190).

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

124:  Extracting artifact entry: /home/runner/work/fleet/fleet/fast-status/status
125:  Extracting artifact entry: /home/runner/work/fleet/fleet/integration-mdm-mysql8.0.44-status/status
126:  Extracting artifact entry: /home/runner/work/fleet/fleet/integration-enterprise-mysql8.0.44-status/status
127:  Artifact download completed successfully.
128:  Extracting artifact entry: /home/runner/work/fleet/fleet/vuln-mysql8.0.44-status/status
129:  Artifact download completed successfully.
130:  Artifact download completed successfully.
131:  Artifact download completed successfully.
132:  Artifact download completed successfully.
133:  Artifact download completed successfully.
134:  Artifact download completed successfully.
135:  Extracting artifact entry: /home/runner/work/fleet/fleet/scripts-status/status
136:  Artifact download completed successfully.
137:  Total of 10 artifact(s) downloaded
138:  Download artifact has finished successfully
139:  ##[group]Run failed_tests=""
140:  �[36;1mfailed_tests=""�[0m
141:  �[36;1mstatus_count=0�[0m
142:  �[36;1m# Find all status files (they are in directories like 'fleetctl-mysql8.0.44-status/status')�[0m
143:  �[36;1mfor status_file in $(find ./ -type f -name 'status'); do�[0m
144:  �[36;1m  status_count=$((status_count + 1))�[0m
145:  �[36;1m  # Extract test name from parent directory (e.g., 'fleetctl-mysql8.0.44-status')�[0m
146:  �[36;1m  test_dir=$(basename $(dirname "$status_file"))�[0m
147:  �[36;1m  # Remove '-status' suffix to get the test name�[0m
148:  �[36;1m  test_name="${test_dir%-status}"�[0m
149:  �[36;1m  status_content=$(cat "$status_file")�[0m
150:  �[36;1m  echo "Processing: $status_file (Test: $test_name) with status content: $status_content"�[0m
151:  �[36;1m  if grep -q "fail" "$status_file"; then�[0m
152:  �[36;1m    echo "  ❌ Test failed: $test_name"�[0m
153:  �[36;1m    failed_tests="${failed_tests}${test_name}, "�[0m
154:  �[36;1m  else�[0m
155:  �[36;1m    echo "  ✅ Test passed: $test_name"�[0m
156:  �[36;1m  fi�[0m
157:  �[36;1mdone�[0m
158:  �[36;1mif [[ $status_count -eq 0 ]]; then�[0m
159:  �[36;1m  echo "❌ ERROR: No status files found! This indicates a workflow issue."�[0m
160:  �[36;1m  exit 1�[0m
161:  �[36;1mfi�[0m
162:  �[36;1mif [[ -n "$failed_tests" ]]; then�[0m
163:  �[36;1m  echo "❌ One or more test jobs failed: ${failed_tests%, }"�[0m
164:  �[36;1m  exit 1�[0m
165:  �[36;1mfi�[0m
166:  �[36;1mecho "✅ All test jobs succeeded."�[0m
167:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
168:  ##[endgroup]
169:  Processing: ./fleetctl-mysql8.0.44-status/status (Test: fleetctl-mysql8.0.44) with status content: success
170:  ✅ Test passed: fleetctl-mysql8.0.44
171:  Processing: ./vuln-mysql8.0.44-status/status (Test: vuln-mysql8.0.44) with status content: success
172:  ✅ Test passed: vuln-mysql8.0.44
173:  Processing: ./service-mysql8.0.44-status/status (Test: service-mysql8.0.44) with status content: success
174:  ✅ Test passed: service-mysql8.0.44
175:  Processing: ./integration-core-mysql8.0.44-status/status (Test: integration-core-mysql8.0.44) with status content: success
176:  ✅ Test passed: integration-core-mysql8.0.44
177:  Processing: ./mysql-mysql8.0.44-status/status (Test: mysql-mysql8.0.44) with status content: success
178:  ✅ Test passed: mysql-mysql8.0.44
179:  Processing: ./integration-enterprise-mysql8.0.44-status/status (Test: integration-enterprise-mysql8.0.44) with status content: fail
180:  ❌ Test failed: integration-enterprise-mysql8.0.44
181:  Processing: ./integration-mdm-mysql8.0.44-status/status (Test: integration-mdm-mysql8.0.44) with status content: success
182:  ✅ Test passed: integration-mdm-mysql8.0.44
183:  Processing: ./scripts-status/status (Test: scripts) with status content: success
184:  ✅ Test passed: scripts
185:  Processing: ./fast-status/status (Test: fast) with status content: success
186:  ✅ Test passed: fast
187:  Processing: ./main-mysql8.0.44-status/status (Test: main-mysql8.0.44) with status content: success
188:  ✅ Test passed: main-mysql8.0.44
189:  ❌ One or more test jobs failed: integration-enterprise-mysql8.0.44
190:  ##[error]Process completed with exit code 1.
191:  Post job cleanup.

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.18%. Comparing base (a30298d) to head (a6a254b).
⚠️ Report is 15 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #47566      +/-   ##
==========================================
+ Coverage   67.15%   67.18%   +0.03%     
==========================================
  Files        3547     3548       +1     
  Lines      228778   228829      +51     
  Branches    11907    11907              
==========================================
+ Hits       153634   153750     +116     
+ Misses      61291    61243      -48     
+ Partials    13853    13836      -17     
Flag Coverage Δ
backend 68.83% <ø> (+0.04%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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

Copy link
Copy Markdown
Member Author

Test failure is unrelated (FMA panic nil). This is just adding a test.

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.

3 participants