Skip to content

feat: Trusted Publishing support (#1980) - #2000

Open
netomi wants to merge 8 commits into
mainfrom
trusted-publishing
Open

feat: Trusted Publishing support (#1980)#2000
netomi wants to merge 8 commits into
mainfrom
trusted-publishing

Conversation

@netomi

@netomi netomi commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
  • Trusted Publishing support

How the finished flow works

  1. A namespace owner registers a trusted publisher via POST /user/namespace/{ns}/trusted-publishing/create (body: provider, owner, repo, workflow, optional extension/environment). The provider resolves names to stable IDs (GitHub repos API, GitLab projects API) and the claims are stored in a new trusted_publisher table (JSONB column, migration V1_70). List and delete endpoints exist alongside it, all owner-only.
  2. CI exchanges its OIDC ID token via POST /api/-/trusted-publishing/token (CSRF-exempted like /api/-/publish). The manager picks the provider by iss, validates the JWT (signature, issuer, audience, forbidden headers), and matches claims against registrations.
  3. On match, it mints a short-lived PersonalAccessToken (default 15 min, ovsx.trusted-publishing.token-expiry) owned by the registering user, so the existing publish pipeline — permissions, auditing, expiry — is reused unchanged.

Key decisions baked in

  • Matching is anchored on stable numeric IDs (repository_id/repository_owner_id, project_id/namespace_id) to block resurrection attacks, plus the workflow path with the @ suffix stripped (any branch/tag can publish), plus environment only if pinned.
  • The OIDC decoder is built lazily — JwtDecoders.fromIssuerLocation() does network discovery, which previously ran in the constructor and would have made app startup depend on external issuers.
  • The tests for both providers added, also got offline matches() tests covering ref-independence, ID mismatch, and pinned environments.

Note: namespace-scoped tokens (the minted token has the registering user's full publish rights). This is server-side work only; no CLI or WebUI changes added.

  • Regenerate jOOQ classes with TP

  • Fix response: it is 403 not 400

  • Add trustedPublishingUrl and sort out inputs

  • Add TODO and remove it for now

  • feat: adding trusted publishers widget

  • feat: adds promo banner on user tokens page

  • feat: conditionally render namespace trusted publishers

  • feat: scope TP widget to the namespace trustedPublishingUrl


* Trusted Publishing support

How the finished flow works

1. A namespace owner registers a trusted publisher via `POST /user/namespace/{ns}/trusted-publishing/create` (body: provider, owner, repo, workflow, optional extension/environment). The provider resolves names to stable IDs (GitHub repos API, GitLab projects API) and the claims are stored in a new trusted_publisher table (JSONB column, migration V1_70). List and delete endpoints exist alongside it, all owner-only.
2. CI exchanges its OIDC ID token via POST /api/-/trusted-publishing/token (CSRF-exempted like /api/-/publish). The manager picks the provider by `iss`, validates the JWT (signature, issuer, audience, forbidden headers), and matches claims against registrations.
3. On match, it mints a short-lived `PersonalAccessToken` (default 15 min, `ovsx.trusted-publishing.token-expiry`) owned by the registering user, so the existing publish pipeline — permissions, auditing, expiry — is reused unchanged.

Key decisions baked in

- Matching is anchored on stable numeric IDs (repository_id/repository_owner_id, project_id/namespace_id) to block resurrection attacks, plus the workflow path with the @<ref> suffix stripped (any branch/tag can publish), plus environment only if pinned.
- The OIDC decoder is built lazily — `JwtDecoders.fromIssuerLocation()` does network discovery, which previously ran in the constructor and would have made app startup depend on external issuers.
- The tests for both providers added, also got offline matches() tests covering ref-independence, ID mismatch, and pinned environments.

Note: namespace-scoped tokens (the minted token has the registering user's full publish rights).
This is server-side work only; no CLI or WebUI changes added.

* Regenerate jOOQ classes with TP

* Fix response: it is 403 not 400

* Add trustedPublishingUrl and sort out inputs

* Add TODO and remove it for now

* feat: adding trusted publishers widget

* feat: adds promo banner on user tokens page

* feat: conditionally render namespace trusted publishers

* feat: scope TP widget to the namespace trustedPublishingUrl

---------

Signed-off-by: Thomas Neidhart <thomas.neidhart@eclipse-foundation.org>
Co-authored-by: Jordi Gómez <jordi.gomez@eclipse-foundation.org>
Co-authored-by: Thomas Neidhart <thomas.neidhart@eclipse-foundation.org>
@netomi netomi added the snapshot Pull requests with that label will automatically build and publish snapshot images label Jul 27, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds “Trusted Publishing” support end-to-end: server-side APIs and provider/JWT validation to exchange OIDC ID tokens for short-lived publish tokens, plus Web UI surfaces for managing trusted publishers at user/namespace/extension scopes.

Changes:

  • Server: introduce trusted publisher persistence (Flyway + jOOQ + JPA), management endpoints, and OIDC token exchange flow with GitHub/GitLab provider support.
  • Web UI: add trusted publishers settings tab/sections, registration dialog, promo banner, and supporting query hooks.
  • Shared: add a “strict” request helper to treat { error: ... } JSON bodies as failures even on HTTP 200.

Reviewed changes

Copilot reviewed 59 out of 62 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
webui/src/server-request.ts Adds sendStrictRequest to reject “error-shaped” JSON bodies even on 200.
webui/src/pages/user/user-settings.tsx Adds Trusted Publishers settings tab rendering.
webui/src/pages/user/user-settings-tokens.tsx Adds Trusted Publishing promo banner to tokens page.
webui/src/pages/user/user-settings-routes.ts Adds route constant for Trusted Publishers tab.
webui/src/pages/user/user-settings-namespace-detail.tsx Adds namespace-scoped Trusted Publishers section; adjusts section ordering.
webui/src/pages/user/user-setting-tabs.tsx Adds “Trusted Publishers” tab to settings tabs.
webui/src/pages/user/use-user-namespaces.ts New TanStack Query hook to load user namespaces.
webui/src/pages/user/trusted-publishing/user-settings-trusted-publishers.tsx New settings page aggregating trusted publishers across manageable namespaces.
webui/src/pages/user/trusted-publishing/use-trusted-publishers.ts New query/mutation hooks for providers/list/create/delete trusted publishers.
webui/src/pages/user/trusted-publishing/trusted-publishing-promo.tsx New info alert promo linking to Trusted Publishers setup.
webui/src/pages/user/trusted-publishing/trusted-publishers-section.tsx New reusable section for listing/adding trusted publishers (namespace + extension contexts).
webui/src/pages/user/trusted-publishing/trusted-publisher-provider-icon.tsx Adds provider-specific icons (GitHub/GitLab/generic).
webui/src/pages/user/trusted-publishing/registration-fields.ts Provider-kind helpers + stable registration-field ordering helpers.
webui/src/pages/user/trusted-publishing/register-trusted-publisher-dialog.tsx New registration dialog driven by server-provided provider inputs.
webui/src/pages/user/trusted-publishing/publisher-list.tsx New list UI with delete confirmation and registration summaries.
webui/src/pages/admin-dashboard/publisher-revoke-tokens-button.tsx Removes custom “danger hover” styling for revoke button.
webui/src/pages/admin-dashboard/publisher-revoke-dialog.tsx Removes custom “danger hover” styling for revoke button.
webui/src/page-settings.ts Extends PageSettings URLs with trustedPublishing doc link.
webui/src/hooks/use-reported-query.ts New helper to auto-report query errors via global error dialog.
webui/src/extension-registry-types.ts Adds Trusted Publishing-related type definitions and namespace URL field.
webui/src/extension-registry-service.ts Adds client methods for trusted publishing URLs/providers/list/create/delete via strict requests.
webui/src/default/theme.tsx Styles MuiAlert standard info variant to match app “info tone”.
webui/src/default/page-settings.tsx Adds default Trusted Publishing documentation URL.
webui/src/components/extension/extension-detail-view.tsx Renders extension-scoped Trusted Publishers section on extension detail page.
server/src/test/java/org/eclipse/openvsx/trustedpublishing/gitlab/GitLabTrustedPublishingProviderTest.java Adds unit tests for GitLab provider extraction/matching.
server/src/test/java/org/eclipse/openvsx/trustedpublishing/github/GitHubTrustedPublishingProviderTest.java Adds unit tests for GitHub provider extraction/matching.
server/src/test/java/org/eclipse/openvsx/repositories/RepositoryServiceSmokeTest.java Updates smoke test to persist/query new TrustedPublisher entity and repository methods.
server/src/main/resources/db/migration/V1_70__Trusted_Publisher.sql Adds trusted_publisher table + sequence + index.
server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/TrustedPublisher.java Generated jOOQ table for trusted_publisher.
server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/TrustedPublisherRecord.java Generated jOOQ record for trusted_publisher.
server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Tables.java Registers TrustedPublisher table constant.
server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Sequences.java Registers trusted_publisher_seq sequence constant.
server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Public.java Registers TrustedPublisher table + sequence in schema metadata.
server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Keys.java Registers trusted_publisher PK + FKs.
server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Indexes.java Registers trusted_publisher_namespace_idx.
server/src/main/java/org/eclipse/openvsx/UserAPI.java Adds trustedPublishingUrl to namespace JSON for owners.
server/src/main/java/org/eclipse/openvsx/UpstreamProxyService.java Rewrites trustedPublishingUrl when proxying.
server/src/main/java/org/eclipse/openvsx/TrustedPublishingAPI.java Adds endpoints for create/list/delete/providers and token exchange.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/TrustedPublishingService.java Implements registration, listing, deletion, and token exchange logic.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/TrustedPublishingProviderSupport.java Base provider logic: lazy JWT decoder, validation, and claim helpers.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/TrustedPublishingConfig.java Adds config toggles (enabled/audience/forbidden headers/token expiry) + validation.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/package-info.java Adds package-level documentation for trusted publishing flow.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/MissingRequiredClaimException.java Custom exception for missing required JWT claims.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/gitlab/GitLabTrustedPublishingProviderSupport.java GitLab claim extraction, registration resolution, and matching logic.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/gitlab/GitLabTrustedPublishingProvider.java GitLab.com provider implementation.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/gitlab/EclipseGitLabTrustedPublishingProvider.java Eclipse GitLab provider implementation.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/github/GitHubTrustedPublishingProviderSupport.java GitHub claim extraction, repo resolution, and matching logic.
server/src/main/java/org/eclipse/openvsx/trustedpublishing/github/GitHubTrustedPublishingProvider.java GitHub.com provider implementation.
server/src/main/java/org/eclipse/openvsx/security/SecurityConfig.java Exempts trusted publishing token exchange endpoint from CSRF (like publish).
server/src/main/java/org/eclipse/openvsx/repositories/TrustedPublisherRepository.java Adds Spring Data repository for TrustedPublisher.
server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java Wires TrustedPublisherRepository and exposes find/delete methods.
server/src/main/java/org/eclipse/openvsx/json/TrustedPublisherTokenRequestJson.java Adds request DTO for OIDC token exchange.
server/src/main/java/org/eclipse/openvsx/json/TrustedPublisherProviderListJson.java Adds providers list DTO.
server/src/main/java/org/eclipse/openvsx/json/TrustedPublisherProviderJson.java Adds provider DTO (id/name/url/inputs).
server/src/main/java/org/eclipse/openvsx/json/TrustedPublisherListJson.java Adds trusted publishers list DTO.
server/src/main/java/org/eclipse/openvsx/json/TrustedPublisherJson.java Adds trusted publisher DTO for create/list responses.
server/src/main/java/org/eclipse/openvsx/json/TrustedPublisherInputJson.java Adds provider input descriptor DTO.
server/src/main/java/org/eclipse/openvsx/json/NamespaceJson.java Adds trustedPublishingUrl to NamespaceJson.
server/src/main/java/org/eclipse/openvsx/entities/TrustedPublisher.java Adds JPA entity for trusted publisher registrations.
server/src/main/java/org/eclipse/openvsx/admin/AdminAPI.java Notes (TODO) potential future admin API exposure for trusted publishing.
server/src/main/java/org/eclipse/openvsx/accesstoken/AccessTokenService.java Adds overload to create tokens with explicit expiry (used for trusted publishing).
server/build.gradle Adjusts dependencies (restclient starter; oauth2 client starter ordering).
Files not reviewed (3)
  • server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Indexes.java: Generated file
  • server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Keys.java: Generated file
  • server/src/main/jooq-gen/org/eclipse/openvsx/jooq/Public.java: Generated file
Comments suppressed due to low confidence (1)

server/src/main/java/org/eclipse/openvsx/TrustedPublishingAPI.java:186

  • requestPublishToken accepts requests without an extension, but TrustedPublishingService.requestPublishToken(...) calls requireNonNull(extensionName), which will cause a 500/NullPointerException if the client omits it. Either make extension mandatory at the API boundary or update the service to support namespace-scoped publishing; but as-is this endpoint can crash on valid JSON input.
        if (!StringUtils.hasText(request.getNamespace()) || !StringUtils.hasText(request.getToken())) {
            var json = AccessTokenJson.error("The fields namespace and token are mandatory.");
            return new ResponseEntity<>(json, HttpStatus.BAD_REQUEST);
        }

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

Comment on lines +65 to +70
if (!StringUtils.hasText(request.getProvider())
|| !StringUtils.hasText(request.getNamespace()) || !StringUtils.hasText(request.getExtension())
|| request.getRegistration() == null || request.getRegistration().isEmpty()) {
var json = TrustedPublisherJson
.error("The fields provider, namespace, extension and registration are mandatory.");
return new ResponseEntity<>(json, HttpStatus.BAD_REQUEST);
Comment on lines +56 to +57
@Column(name = "extension_name", nullable = false)
private String extensionName;
Comment on lines +7 to +11
id BIGINT NOT NULL PRIMARY KEY DEFAULT nextval('trusted_publisher_seq'),
namespace BIGINT NOT NULL REFERENCES public.namespace(id),
extension_name CHARACTER VARYING(255),
provider CHARACTER VARYING(32) NOT NULL,
registration JSONB NOT NULL,
netomi and others added 6 commits July 28, 2026 11:30
Signed-off-by: Thomas Neidhart <thomas.neidhart@eclipse-foundation.org>
* feat: adding TP enabled flag in version response

* feat(webui): using TP enabled check to show/hide entrypoints
* Do not use properties duplicated across

Instead, use the proper config bean as source of truth.

* reformat
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

snapshot Pull requests with that label will automatically build and publish snapshot images

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants