GET /groups/:id/service_accounts/:user_id/personal_access_tokens returns 500 ArgumentError when service account has admin privileges
#### Summary The `GET /groups/:id/service_accounts/:user_id/personal_access_tokens` endpoint returns an HTTP 500 with `ArgumentError: wrong number of arguments (given 1, expected 0)` when the service account user has admin privileges. This affects any caller (admin or group owner) and is not specific to the caller's role. This blocks the Terraform GitLab provider v18.8+ on self-managed instances where service accounts have admin privileges, as the provider switched to this endpoint in [terraform-provider-gitlab commit 9253647d](https://gitlab.com/gitlab-org/terraform-provider-gitlab/-/commit/9253647d). #### Root Cause `PersonalAccessTokensHelpers` defines a [`user(user_id)` method](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/lib/api/helpers/personal_access_tokens_helpers.rb#L59-L61) expecting one argument: ```ruby def user(user_id) UserFinder.new(user_id).find_by_id end ``` `GroupServiceAccounts` [overrides this](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/ee/lib/api/group_service_accounts.rb#L17) as a zero-argument method: ```ruby def user user_group.provisioned_users.find_by_id(params[:user_id]) end ``` The endpoint calls [`finder_params(user)`](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/ee/lib/api/group_service_accounts.rb#L189) where `user` is the **service account** object. Inside `finder_params`, `can_admin_all_resources?` is checked on this service account object. When the service account is **not** an admin, `finder_params` takes the [`else` branch](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/lib/api/helpers/personal_access_tokens_helpers.rb#L53) which never calls `user(...)` with an argument, so it works fine. When the service account **is** an admin, `finder_params` enters the [admin branch](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/lib/api/helpers/personal_access_tokens_helpers.rb#L42) and calls `user(params[:user_id])` with one argument. Ruby dispatches this to the overridden zero-arg method, causing the `ArgumentError`. #### Missing test coverage The [spec file](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/ee/spec/requests/api/group_service_accounts_spec.rb#L630-L644) for this endpoint does not test the case where the service account has admin privileges. It only covers `when the user is a top-level-group owner` and `when user is not a top-level-group owner`. Other endpoints in the same file (DELETE, rotate) include admin test contexts, but this GET endpoint does not. #### Introduced by !186105 ("Add GET /personal_access_tokens to service_accounts API"), merged 2025-04-01, milestone 17.11. #### Affected versions 17.11 and later (all versions since the endpoint was added). Confirmed still present in 18.10.1. #### Steps to reproduce 1. Open a `gitlab-rails console` and create the test data: ```ruby # Create a group owner owner_result = ::Users::CreateService.new( User.find(1), { name: 'Test Owner', username: 'sa-bug-test-owner', email: 'sa-bug-test-owner@example.com', password: SecureRandom.hex(16) + 'A1!', skip_confirmation: true, organization_id: Organizations::Organization.default_organization.id } ).execute owner = owner_result.payload[:user] # Create group result = Groups::CreateService.new( owner, { name: 'sa-bug-test', path: 'sa-bug-test', organization_id: Organizations::Organization.default_organization.id } ).execute group = result.payload[:group] # Create service account (requires instance admin) sa_result = ::Namespaces::ServiceAccounts::CreateService.new( User.find(1), { namespace_id: group.id, organization_id: group.organization_id } ).execute sa = sa_result.payload[:user] # Make the service account an admin (this is the trigger condition) sa.admin = true sa.save! # Create a PAT for the service account token = PersonalAccessToken.create!( user: sa, name: 'test-token', scopes: ['api'], expires_at: 1.day.from_now ) # Create an owner PAT owner_pat = PersonalAccessToken.create!( user: owner, name: 'owner-test-token', scopes: ['api'], expires_at: 1.day.from_now ) puts "Group ID: #{group.id}" puts "Service Account ID: #{sa.id}" puts "Owner PAT: #{owner_pat.token}" ``` 2. Call the endpoint as the group owner: ```shell curl --header "PRIVATE-TOKEN: <owner_pat_token>" \ "http://<gitlab-host>/api/v4/groups/<group_id>/service_accounts/<sa_id>/personal_access_tokens" ``` 3. Observe HTTP 500 response. 4. Verify the bug is specific to admin service accounts by removing admin status: ```ruby sa = User.find(<sa_id>) sa.admin = false sa.save! ``` 5. Repeat the same curl request. 6. Observe HTTP 200 response with the token list. #### Expected behavior The endpoint should return a 200 with the list of personal access tokens regardless of whether the service account has admin privileges. #### Actual behavior When the service account has admin privileges, the endpoint returns HTTP 500: ```shell $ curl --header "PRIVATE-TOKEN: <OWNER_PAT>" \ "https://gitlab.example.com/api/v4/groups/<group_id>/service_accounts/<sa_id>/personal_access_tokens" {"message":"500 Internal Server Error"} ``` After removing admin status from the service account, the same request returns HTTP 200: ```shell $ curl --header "PRIVATE-TOKEN: <OWNER_PAT>" \ "https://gitlab.example.com/api/v4/groups/<group_id>/service_accounts/<sa_id>/personal_access_tokens" [{"id":24,"name":"test-token","revoked":false,"created_at":"2026-04-15T05:20:27.451Z","description":null,"scopes":["api"],"user_id":87,"last_used_at":null,"active":true,"expires_at":"2026-04-16"}] ``` Relevant `api_json.log` entry: ```json { "status": 500, "method": "GET", "path": "/api/v4/groups/<group_id>/service_accounts/<sa_id>/personal_access_tokens", "route": "/api/:version/groups/:id/service_accounts/:user_id/personal_access_tokens", "username": "sa-bug-test-owner", "token_type": "PersonalAccessToken", "exception.class": "ArgumentError", "exception.message": "wrong number of arguments (given 1, expected 0)", "exception.backtrace": [ "ee/lib/api/group_service_accounts.rb:17:in `user'", "lib/api/helpers/personal_access_tokens_helpers.rb:42:in `finder_params'", "ee/lib/api/group_service_accounts.rb:192:in `block (4 levels) in <class:GroupServiceAccounts>'" ], "meta.feature_category": "user_management" } ``` #### Workaround Pin the Terraform GitLab provider to v18.7.x or earlier, which uses `GET /api/v4/personal_access_tokens/:id` instead of the affected group-scoped endpoint.
issue