feat: Trusted Publishing support (#1980) - #2000
Open
netomi wants to merge 8 commits into
Open
Conversation
* 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>
There was a problem hiding this comment.
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
requestPublishTokenaccepts requests without anextension, butTrustedPublishingService.requestPublishToken(...)callsrequireNonNull(extensionName), which will cause a 500/NullPointerException if the client omits it. Either makeextensionmandatory 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, |
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
Fix the flyway migration conflict.
* Do not use properties duplicated across Instead, use the proper config bean as source of truth. * reformat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
How the finished flow works
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.iss, validates the JWT (signature, issuer, audience, forbidden headers), and matches claims against registrations.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
JwtDecoders.fromIssuerLocation()does network discovery, which previously ran in the constructor and would have made app startup depend on external issuers.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