Users API returns 500 error on empty organization parameter

Summary

The PUT /api/v4/users/:id endpoint returns a 500 Internal Server Error when the organization parameter is provided with an empty string value. This causes a PostgreSQL NotNullViolation on the users.organization_id column instead of properly handling the empty value or returning a 400 Bad Request.

This issue has been observed in production environments.

🛠️ with ❤️ at Siemens

Steps to reproduce

  1. Create or identify a user in GitLab
  2. Make an API request to update the user with an empty organization parameter:
    curl --request PUT --header "PRIVATE-TOKEN: <your_token>" \
      --header "Content-Type: application/json" \
      --data '{"organization": ""}' \
      "https://gitlab.example.com/api/v4/users/:id"
  3. Observe the 500 Internal Server Error response

Example Project

N/A - This is a backend API bug reproducible on any GitLab instance with the affected code.

What is the current bug behavior?

When clients send an empty string for the organization parameter (e.g., {"organization": ""}), the API:

  1. Fails to properly transform the parameter from :organization to :user_detail_organization
  2. Rails interprets the parameter as an attempt to set the association user.organization (foreign key) instead of the delegated text field user.user_detail_organization
  3. The association setter coerces empty string to nil, setting users.organization_id = NULL
  4. PostgreSQL rejects the update due to NOT NULL constraint violation
  5. API returns HTTP 500 with error: PG::NotNullViolation: ERROR: null value in column "organization_id" of relation "users" violates not-null constraint

What is the expected correct behavior?

The API should:

  1. Accept empty string values for the organization parameter
  2. Properly route the parameter to user_details.organization (text field) instead of users.organization_id (foreign key)
  3. Update the user's organization text field to an empty string
  4. Return HTTP 200 with the updated user data, where organization field is an empty string

Alternatively, if empty organization values are not acceptable, the API should:

  1. Validate the parameter at the API layer
  2. Return HTTP 400 Bad Request with a clear error message
  3. Not attempt any database update

Relevant logs and/or screenshots

Error:

PG::NotNullViolation: ERROR: null value in column "organization_id" of relation "users" violates not-null constraint

SQL Statement:

UPDATE "users" SET "updated_at" = $1, "organization_id" = $2 WHERE "users"."id" = $3

Output of checks

This bug affects GitLab 18.3+ where the users.organization_id column has a NOT NULL constraint.

Results of GitLab environment info

Expand for output related to GitLab environment info
GitLab version: 18.3+
Ruby version: 3.2.8
PostgreSQL version: 15+

Affected migration timeline:
- Milestone 18.2 (20250618121819): Added organization_id column with default=1, null: false
- Milestone 18.3 (20250625103818): Removed database default
- Milestone 18.3 (20250709143510): Added foreign key constraint
- Milestone 18.3 (20250717144845): Validated foreign key constraint

Results of GitLab application Check

Expand for output related to the GitLab application check

Application checks passing. Issue is a logic bug in parameter transformation, not an environment issue.

Possible fixes

Root Cause:

The bug is in lib/api/users.rb:448 where parameter transformation is conditional on the value being truthy:

user_params[:user_detail_organization] = user_params.delete(:organization) if user_params[:organization]

When organization is an empty string (""), which is falsy in Ruby, the transformation doesn't occur. The parameter remains as :organization instead of being renamed to :user_detail_organization.

Technical Background:

Two distinct "organization" fields exist in the User model:

  1. user_details.organization - Text field (max 500 chars) for company name in user profile
  2. users.organization_id - Foreign key (NOT NULL) linking to organizations table for multi-tenancy

The User model has both:

  • belongs_to :organization (association for the FK)
  • delegate :organization, to: :user_detail, prefix: true (delegation for the text field)

Without the prefix, these would collide. The API parameter transformation adds the prefix, but only when values are truthy. Empty strings bypass the transformation and target the wrong field (the association instead of the delegated text field).

Recommended Fix:

Change lib/api/users.rb:448 to check for key presence instead of value truthiness:

# Current (buggy):
user_params[:user_detail_organization] = user_params.delete(:organization) if user_params[:organization]

# Fixed:
user_params[:user_detail_organization] = user_params.delete(:organization) if user_params.key?(:organization)

This ensures the parameter is transformed regardless of whether the value is a valid string, empty string, or nil.

Additional Changes:

  1. Add API tests for empty string and nil organization values (pattern exists in spec/requests/api/users_spec.rb:1884-1900 for bio field)
  2. Consider adding API-level validation if empty organization should not be allowed

Related Code:

  • lib/api/users.rb:448 - Buggy parameter transformation
  • lib/api/users.rb:69 - API parameter definition
  • lib/api/users.rb:423 - PUT /users/:id endpoint
  • app/models/user.rb:286 - User organization association (FK)
  • app/models/user.rb:524 - User organization delegation (text field)
  • app/models/user_detail.rb:67 - Organization field validation
  • spec/requests/api/users_spec.rb:1902-1908 - Existing organization tests

Patch release information for backports

If the bug fix needs to be backported in a patch release to a version under the maintenance policy, please follow the steps on the patch release runbook for GitLab engineers.

Refer to the internal "Release Information" dashboard for information about the next patch release, including the targeted versions, expected release date, and current status.

This bug was introduced when users.organization_id NOT NULL constraint was enforced (Milestone 18.2). Consider backporting to any supported versions running 18.2+.

High-severity bug remediation

To remediate high-severity issues requiring an internal release for single-tenant SaaS instances, refer to the internal release process for engineers.

Edited by 🤖 GitLab Bot 🤖