Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dd34300
Update actionlint.yml
hupling Jul 24, 2026
698308f
fix bug
hupling Jul 24, 2026
53cfd6e
Update actionlint.yml
hupling Jul 24, 2026
9c76177
fix bug
hupling Jul 24, 2026
94df8c3
Merge branch 'test-action-lint' of https://github.com/it-at-m/sps-tes…
hupling Jul 24, 2026
0e29810
test
hupling Jul 24, 2026
b5cc77a
Apply suggestion from @hupling
hupling Jul 24, 2026
adc9c33
Delete .github/problem-matcher.json
hupling Jul 24, 2026
0bfcd67
Apply suggestion from @hupling
hupling Jul 24, 2026
121488f
chore(deps): lock file maintenance (#1738)
renovate[bot] Jul 27, 2026
7f719fc
fix(deps): update dependency @fontsource/roboto to v5.3.0 (#1737)
renovate[bot] Jul 27, 2026
52c5020
chore(deps): update dependency eslint-plugin-vue to v10.10.0 (#1736)
renovate[bot] Jul 27, 2026
a3d4f8a
chore(deps): update dependency @openapitools/openapi-generator-cli to…
renovate[bot] Jul 27, 2026
8ceb5a9
chore(deps): update dependency vite to v8.1.5 (#1728)
renovate[bot] Jul 27, 2026
cbaf323
fix(deps): update dependency vue-i18n to v11.4.7 (#1734)
renovate[bot] Jul 27, 2026
8f988b9
chore(deps): update dependency vue-tsc to v3.3.8 (#1730)
renovate[bot] Jul 27, 2026
697b0da
chore(deps): update dependency prettier to v3.9.6 (#1727)
renovate[bot] Jul 27, 2026
28a8da4
chore(deps): update dependency vitest to v4.1.10 (#1729)
renovate[bot] Jul 27, 2026
2c9743a
chore(deps): update registry.access.redhat.com/ubi10/nginx-126 docker…
renovate[bot] Jul 27, 2026
470cd66
chore(deps): update registry.access.redhat.com/ubi10/openjdk-21-runti…
renovate[bot] Jul 27, 2026
25aba4f
chore(deps): update dependency @vitejs/plugin-vue to v6.0.8 (#1726)
renovate[bot] Jul 27, 2026
5eef0f5
fix(deps): update dependency vue to v3.5.40 (#1733)
renovate[bot] Jul 27, 2026
e1698e0
:sparkles: Username extraction claim configurable (#1740)
devtobi Jul 28, 2026
ff7285b
Apply suggestion from @hupling
hupling Jul 28, 2026
42eaef8
Merge branch 'main' into test-action-lint
hupling Jul 29, 2026
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
17 changes: 0 additions & 17 deletions .github/problem-matcher.json

This file was deleted.

4 changes: 3 additions & 1 deletion .github/workflows/actionlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
Comment thread
hupling marked this conversation as resolved.
# needed to annotate the files in a pull request with comments
pull-requests: write
steps:
- uses: it-at-m/lhm_actions/action-templates/actions/action-actionlint@4ed8f906bab8111fbd9a7af8e3f4129f9b721757 # v1.2.3
- uses: it-at-m/lhm_actions/action-templates/actions/action-actionlint@e9a8422d740d4e9eaef25cfeeb3a5790da97a7aa # v1.2.4
4 changes: 2 additions & 2 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Deno-runtime related vulnerability, irrelevant as Node.js is used
GHSA-gv7w-rqvm-qjhr
# brace-expansion (npm) related DoS vulnerability, however irrelevant as we do not use it in production
CVE-2026-14257
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# For documentation see https://jboss-container-images.github.io/openjdk/
FROM registry.access.redhat.com/ubi10/openjdk-21-runtime:1.24-9@sha256:434a4e1c5d6d57c4380ddc0f2c1707ec65df1cb294ea84b21d37ca54aa4b923c
FROM registry.access.redhat.com/ubi10/openjdk-21-runtime:1.24-10@sha256:3f21794f7e1c568a194798dc36f8bb09da239471fa88d7fa8353efaa091549cc

# Copy runnable jar to deployments
COPY target/*.jar /deployments/application.jar
1 change: 1 addition & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@

<!-- TEMPORARY TRANSITIVE DEPENDENCY OVERRIDES FOR SECURITY FIXES -->
<!-- Preferably override properties defined in spring-boot-starter -> spring-boot-dependencies POM -->
<postgresql.version>42.7.12</postgresql.version> <!-- CVE-2026-54291 -->
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.NonNull;
import org.springframework.boot.web.servlet.FilterRegistration;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

/**
* This filter logs the username for requests.
* This filter logs the username from requests using the {@link AuthUtils} bean.
*/
@Component
@FilterRegistration(urlPatterns = "/*", order = 1)
Expand All @@ -28,6 +29,7 @@ public class RequestResponseLoggingFilter extends OncePerRequestFilter {
private static final List<String> CHANGING_METHODS = List.of(HttpMethod.POST.name(), HttpMethod.PUT.name(), HttpMethod.PATCH.name(),
HttpMethod.DELETE.name());

private final AuthUtils authUtils;
private final SecurityProperties securityProperties;

/**
Expand All @@ -54,12 +56,12 @@ public enum LoggingMode {
* {@inheritDoc}
*/
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
protected void doFilterInternal(final @NonNull HttpServletRequest request, final @NonNull HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(request, response);
if (checkForLogging(request)) {
log.info("User {} executed {} on URI {} with http status {}",
AuthUtils.getUsername(),
authUtils.getUsername(),
request.getMethod(),
request.getRequestURI(),
response.getStatus());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ public class SecurityProperties {
*/
@NotBlank private String clientId;

/**
* Name of the JWT claim that contains the username.
* Defaults to "preferred_username" if not configured.
*/
@NotBlank private String usernameClaim = "preferred_username";

/**
* URI of the endpoint used for fetching permissions,
* see also {@link KeycloakPermissionsAuthoritiesConverter}.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
package de.muenchen.oss.refarch.backend.security;

import de.muenchen.oss.refarch.backend.configuration.security.SecurityProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;

/**
* Utilities for authentication data.
* Utility bean for authentication related data.
*/
public final class AuthUtils {
@Component
@RequiredArgsConstructor
public class AuthUtils {

public static final String NAME_UNAUTHENTICATED_USER = "unauthenticated";

private static final String TOKEN_USER_NAME = "preferred_username";

private AuthUtils() {
}
private final SecurityProperties securityProperties;

/**
* Extracts the user name from the existing Spring Security Context via
* {@link SecurityContextHolder}.
*
* @return the username or an "unauthenticated" if no {@link Authentication} exists
*/
public static String getUsername() {
public String getUsername() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof JwtAuthenticationToken) {
final JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) authentication;
return (String) jwtAuth.getTokenAttributes().getOrDefault(TOKEN_USER_NAME, null);
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
final UsernamePasswordAuthenticationToken usernameAuth = (UsernamePasswordAuthenticationToken) authentication;
if (authentication instanceof JwtAuthenticationToken jwtAuth) {
return (String) jwtAuth.getTokenAttributes().get(securityProperties.getUsernameClaim());
} else if (authentication instanceof UsernamePasswordAuthenticationToken usernameAuth) {
return usernameAuth.getName();
} else {
return NAME_UNAUTHENTICATED_USER;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package de.muenchen.oss.refarch.backend.security;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import de.muenchen.oss.refarch.backend.configuration.security.SecurityProperties;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

@ExtendWith(MockitoExtension.class)
class AuthUtilsTest {

private static final String USERNAME_CLAIM = "preferred_username";

@Mock
private SecurityProperties securityProperties;

@InjectMocks
private AuthUtils authUtils;

@AfterEach
void tearDown() {
SecurityContextHolder.clearContext();
}

@Test
void givenJwtWithUsernameClaim_thenReturnUsername() {
// given
when(securityProperties.getUsernameClaim()).thenReturn(USERNAME_CLAIM);
final Jwt jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.claim(USERNAME_CLAIM, "john.doe")
.build();
final JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);

// when
final String username = authUtils.getUsername();

// then
assertThat(username).isEqualTo("john.doe");
}

@Test
void givenJwtWithoutUsernameClaim_thenReturnUnauthenticatedUser() {
// given
when(securityProperties.getUsernameClaim()).thenReturn(USERNAME_CLAIM);
final Jwt jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.claims(claims -> claims.put("other", "value"))
.build();
final JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);

// when
final String username = authUtils.getUsername();

// then
assertThat(username).isNull();
}

@Test
void givenUsernamePasswordAuthentication_thenReturnUsername() {
// given
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("jane.doe", "password");
SecurityContextHolder.getContext().setAuthentication(authentication);

// when
final String username = authUtils.getUsername();

// then
assertThat(username).isEqualTo("jane.doe");
}

@Test
void givenNoAuthentication_thenReturnUnauthenticatedUser() {
// given
SecurityContextHolder.clearContext();

// when
final String username = authUtils.getUsername();

// then
assertThat(username).isEqualTo(AuthUtils.NAME_UNAUTHENTICATED_USER);
}

}
Loading
Loading