Add User Applications REST API

What does this MR do and why?

This Merge Request adds the User Applications REST API, fulfilling the requirements described in #23054.

It introduces a set of CRUD endpoints located at /api/v4/user/applications that allow users to manage their instance-level OAuth applications. This is designed to enable programmatic management of user-owned OAuth applications, which proves extremely useful for dynamically deploying and un-deploying Review Apps without having to employ insecure wildcard redirect URIs.

Specifically, it creates the following endpoints:

  1. POST /api/v4/user/applications: Creates an application, exclusively returning the secret during this action.
  2. GET /api/v4/user/applications: Lists all applications owned by the authenticated user (supports pagination).
  3. GET /api/v4/user/applications/:id: Retrieves a specific application.
  4. PUT /api/v4/user/applications/:id: Updates an existing application (e.g. modifying the name or scopes). Note that sensitive fields like redirect_uri and confidential cannot be mutated after creation.
  5. DELETE /api/v4/user/applications/:id: Removes the application.

Security Considerations

The implementation follows strict security best practices regarding credential exposure and bounded context:

  • Secret Exclusivity: In accordance with OAuth 2.0 best practices, the application secret is explicitly and exclusively returned only on the POST create event. Subsequent GET or PUT requests will never expose the secret.
  • Targeting Wildcards: The primary motivation for this API is to give developers a programmatic way to provision dynamic redirect_uri targets (such as ephemeral Review Apps). This eliminates the insecure practice of users resorting to wildcard redirect URIs to handle dynamic domains.
  • Immutability of Sensitive Fields: To prevent redirect hijacking or unintended security downgrade chaining via API abuse, the redirect_uri and confidential flags are entirely immutable through the API PUT endpoint after initial creation.
  • Authorization Enforcement: The endpoint exclusively operates within the strict boundary of the current_user. The Authn::UserApplicationsFinder inherently scopes all GET, PUT, and DELETE requests to only OAuth Applications owned by the authenticated user, completely preventing Insecure Direct Object Reference (IDOR) or cross-user data leakage.
  • Confidentiality Awareness: The endpoints explicitly support defining whether an OAuth application is confidential or public (e.g. for SPAs or Native apps), ensuring the system handles the client appropriately during the OAuth flow.

Database Queries

The User Applications API executes standard CRUD operations on the oauth_applications table, scoped to the current user. Here are the query execution plans:

1. Read collection (GET index with Pagination)

EXPLAIN SELECT "oauth_applications".* FROM "oauth_applications" WHERE "oauth_applications"."owner_id" = 1 AND "oauth_applications"."owner_type" = 'User' LIMIT 20 OFFSET 0;
 Limit  (cost=0.15..2.17 rows=1 width=237)
   ->  Index Scan using index_oauth_applications_on_owner_id_and_owner_type on oauth_applications  (cost=0.15..2.17 rows=1 width=237)
         Index Cond: ((owner_id = 1) AND ((owner_type)::text = 'User'::text))

2. Read single item (GET :id)

EXPLAIN SELECT "oauth_applications".* FROM "oauth_applications" WHERE "oauth_applications"."id" = 1 LIMIT 1;
 Limit  (cost=0.15..2.17 rows=1 width=237)
   ->  Index Scan using oauth_applications_pkey on oauth_applications  (cost=0.15..2.17 rows=1 width=237)
         Index Cond: (id = 1)

3. Create (POST)

EXPLAIN INSERT INTO "oauth_applications" ("name", "uid", "secret", "redirect_uri", "scopes", "created_at", "updated_at", "owner_id", "owner_type", "confidential", "organization_id") VALUES ('test', 'uid', 'secret', 'url', 'api', NOW(), NOW(), 1, 'User', true, 1) RETURNING "id";
 Insert on oauth_applications  (cost=0.00..0.02 rows=1 width=237)
   ->  Result  (cost=0.00..0.02 rows=1 width=237)

4. Update (PUT)

EXPLAIN UPDATE "oauth_applications" SET "name" = 'test2', "updated_at" = NOW() WHERE "oauth_applications"."id" = 1;
 Update on oauth_applications  (cost=0.15..2.17 rows=0 width=0)
   ->  Index Scan using oauth_applications_pkey on oauth_applications  (cost=0.15..2.17 rows=1 width=46)
         Index Cond: (id = 1)

5. Delete (DELETE)

EXPLAIN DELETE FROM "oauth_applications" WHERE "oauth_applications"."id" = 1;
 Delete on oauth_applications  (cost=0.15..2.17 rows=0 width=0)
   ->  Index Scan using oauth_applications_pkey on oauth_applications  (cost=0.15..2.17 rows=1 width=6)
         Index Cond: (id = 1)

6. Count (Pagination meta)

EXPLAIN SELECT COUNT(*) FROM "oauth_applications" WHERE "oauth_applications"."owner_id" = 1 AND "oauth_applications"."owner_type" = 'User';
 Aggregate  (cost=2.17..2.18 rows=1 width=8)
   ->  Index Only Scan using index_oauth_applications_on_owner_id_and_owner_type on oauth_applications  (cost=0.15..2.17 rows=1 width=0)
         Index Cond: ((owner_id = 1) AND (owner_type = 'User'::text))

References

Screenshots or screen recordings

The API returns standard GitLab API JSON shapes, and correctly provisions the underlying Authn::OauthApplication record associated with the user.

Browser Dev Tools Verification
api_success_verification

How to set up and validate locally

  1. Ensure your GDK is up and running.

  2. Open your browser developer console while authenticated as any user (e.g. root) on the GitLab dashboard.

  3. Execute the following fetch command to provision an application:

    const csrf = document.querySelector('meta[name="csrf-token"]').content;
    fetch('/api/v4/user/applications', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrf
      },
      body: JSON.stringify({
        name: 'Browser UI Test App',
        redirect_uri: 'http://localhost/callback',
        scopes: 'api',
        confidential: false
      })
    }).then(r => r.json()).then(console.log);
  4. Verify that the response returns a success with the secret and an application_id.

  5. You can assert the creation of the Application by checking your /-/profile/applications interface.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Sijin T V

Merge request reports

Loading