Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,17 @@ WORKDIR /app
COPY --from=builder \
/workspace/git-proxy-java-dashboard/build/install/git-proxy-java-dashboard/ /app/

# Create the conf directory; mount a git-proxy-local.yml here to override config.
# Create the conf directory; mount a git-proxy-{profile}.yml here to override config.
# Example: -v ./docker/git-proxy-local.yml:/app/conf/git-proxy-local.yml:ro
# docker run -e GITPROXY_CONFIG_PROFILE=local -v ./docker/git-proxy-local.yml:/app/conf/git-proxy-local.yml:ro ...
RUN mkdir -p /app/conf

# Data directory for file-based databases (h2-file, sqlite), log output, and
# JGit home (used for lock files and system config). Owned by GID 0 with
# group-write so the image works under OpenShift's arbitrary-UID security model.
RUN mkdir -p /app/.data /app/logs /app/home \
&& chown -R 1000:0 /app/.data /app/logs /app/home \
&& chmod -R g+rwX /app/.data /app/logs /app/home
RUN bash -c 'mkdir -p /app/{.data,logs,home} \
&& chown -R 1000:0 /app/{.data,logs,home} \
&& chmod g+rwX /app/{.data,logs,home}'

ENV HOME=/app/home

Expand Down
13 changes: 11 additions & 2 deletions git-proxy-java-dashboard/frontend/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/**
* Provider IDs are internally `{type}/{host}` (e.g. `github/github.com`). The `/` breaks URL path
* routing — Spring Security's StrictHttpFirewall rejects `%2F` with HTTP 400. Swap `/` for `@` on
* the wire; the controller swaps it back before touching the store.
*/
function providerToPathKey(provider: string): string {
return provider.replace(/\//g, '@')
}

/** Reads the XSRF-TOKEN cookie set by Spring Security's CookieCsrfTokenRepository. */
function getCsrfToken(): string | null {
const match = document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]+)/)
Expand Down Expand Up @@ -156,7 +165,7 @@ export async function addScmIdentity(provider: string, username: string) {

export async function removeScmIdentity(provider: string, scmUsername: string) {
const res = await apiFetch(
`/api/me/identities/${encodeURIComponent(provider)}/${encodeURIComponent(scmUsername)}`,
`/api/me/identities/${encodeURIComponent(providerToPathKey(provider))}/${encodeURIComponent(scmUsername)}`,
{ method: 'DELETE' },
)
if (!res.ok) await parseErrorResponse(res, 'Failed to remove SCM identity')
Expand Down Expand Up @@ -222,7 +231,7 @@ export async function removeUserEmail(username: string, email: string) {

export async function removeUserIdentity(username: string, provider: string, scmUsername: string) {
const res = await apiFetch(
`/api/users/${encodeURIComponent(username)}/identities/${encodeURIComponent(provider)}/${encodeURIComponent(scmUsername)}`,
`/api/users/${encodeURIComponent(username)}/identities/${encodeURIComponent(providerToPathKey(provider))}/${encodeURIComponent(scmUsername)}`,
{ method: 'DELETE' },
)
if (!res.ok) await parseErrorResponse(res, 'Failed to remove SCM identity')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,8 @@ private void configureOidcAuth(
userInfo.oidcUserService(buildOidcUserService(roleMappings, groupsClaim)));

if (usePrivateKeyJwt) {
RSAKey rsaKey = loadRsaKey(
oidcCfg.getPrivateKeyPath(), oidcCfg.getCertPath(), oidcCfg.getKeyId());
RSAKey rsaKey =
loadRsaKey(oidcCfg.getPrivateKeyPath(), oidcCfg.getCertPath(), oidcCfg.getKeyId());
Function<ClientRegistration, JWK> jwkResolver = reg ->
ClientAuthenticationMethod.PRIVATE_KEY_JWT.equals(reg.getClientAuthenticationMethod())
? rsaKey
Expand Down Expand Up @@ -597,6 +597,7 @@ private Set<GrantedAuthority> mapIdpGroupsToRoles(
* embedded in the private key — no separate public key file is needed.
*
* <p>Key-ID precedence when {@code private-key-path} is set:
*
* <ol>
* <li>{@code certPath} non-blank → SHA-256 thumbprint set as {@code x5t#S256} (Entra ID)
* <li>{@code keyId} non-blank → used as explicit {@code kid} (Keycloak, Okta, Auth0, Dex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ public ResponseEntity<?> addScmIdentity(@RequestBody Map<String, String> body) {
public ResponseEntity<?> removeScmIdentity(@PathVariable String provider, @PathVariable String scmUsername) {
if (!(userStore instanceof UserStore mutable)) return NOT_MUTABLE;
try {
mutable.removeScmIdentity(currentUsername(), provider, scmUsername);
// Provider IDs are `{type}/{host}`; frontend swaps `/` → `@` to survive URL path routing.
mutable.removeScmIdentity(currentUsername(), provider.replace('@', '/'), scmUsername);
} catch (LockedByConfigException e) {
return LOCKED_BY_CONFIG;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ public ResponseEntity<?> removeIdentity(
return ResponseEntity.notFound().build();
}
try {
mutable.removeScmIdentity(username, provider, scmUsername);
// Provider IDs are `{type}/{host}`; frontend swaps `/` → `@` to survive URL path routing.
mutable.removeScmIdentity(username, provider.replace('@', '/'), scmUsername);
} catch (LockedByConfigException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Map.of("error", e.getMessage()));
}
Expand Down
Loading