diff --git a/src/main/java/de/avatic/lcc/config/SecurityConfig.java b/src/main/java/de/avatic/lcc/config/SecurityConfig.java index 4485b49..eb1ca34 100644 --- a/src/main/java/de/avatic/lcc/config/SecurityConfig.java +++ b/src/main/java/de/avatic/lcc/config/SecurityConfig.java @@ -47,10 +47,7 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Supplier; @@ -65,6 +62,7 @@ public class SecurityConfig { @Value("${lcc.allowed_oauth_token_cors:*}") // Default: alle Origins private String oauthTokenCors; + @Bean @Profile("!dev & !test") // Only active when NOT in dev profile public SecurityFilterChain prodSecurityFilterChain(HttpSecurity http, JwtTokenService jwtTokenService) throws Exception { @@ -251,7 +249,25 @@ public class SecurityConfig { User user = null; - String workdayId = oidcUser.getAttribute("workday_id"); + String workdayId = oidcUser.getAttribute("employeeid"); + if (workdayId == null) { + workdayId = oidcUser.getAttribute("extension_WorkdayID"); + } + if (workdayId == null) { + workdayId = oidcUser.getAttribute("workdayWorkerID"); + } + if (workdayId == null) { + workdayId = oidcUser.getAttribute("onpremisesimmutableid"); + } + if (workdayId == null) { + // Check for any extension attribute containing "workday" + Map claims = oidcUser.getIdToken().getClaims(); + workdayId = claims.entrySet().stream() + .filter(e -> e.getKey().toLowerCase().contains("workday")) + .map(e -> String.valueOf(e.getValue())) + .findFirst() + .orElse(null); + } // Try different ways to get email String email = oidcUser.getEmail(); @@ -269,7 +285,7 @@ public class SecurityConfig { if (workdayId != null) { user = userRepository.getByWorkdayId(workdayId); if (user != null) { - user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName()))); + user.getGroups().forEach(group -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + group.getName().toUpperCase()))); userId = user.getId(); } } else if (email != null) { @@ -311,15 +327,15 @@ public class SecurityConfig { Claims claims = jwtTokenService.parseClaimsWithoutValidation(token); String tokenType = claims.get("token_type", String.class); if ("ext_app".equals(tokenType)) { - return null; // using the SelfIssuedJwtFilter + return null; } } catch (Exception e) { - // carry on ... + log.debug("Failed to parse token without validation", e); } - return token; // some other token + return token; } - return null; // all other requests + return null; }; } @@ -345,9 +361,12 @@ public class SecurityConfig { @Override protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException { - CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); - if (csrfToken != null) { - csrfToken.getToken(); + // Skip CSRF cookie for token endpoint + if (!request.getRequestURI().startsWith("/oauth2/token")) { + CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + if (csrfToken != null) { + csrfToken.getToken(); + } } filterChain.doFilter(request, response); } diff --git a/src/main/java/de/avatic/lcc/config/SelfIssuedJwtFilter.java b/src/main/java/de/avatic/lcc/config/SelfIssuedJwtFilter.java index 78c6493..749d9b0 100644 --- a/src/main/java/de/avatic/lcc/config/SelfIssuedJwtFilter.java +++ b/src/main/java/de/avatic/lcc/config/SelfIssuedJwtFilter.java @@ -30,6 +30,11 @@ public class SelfIssuedJwtFilter extends OncePerRequestFilter { @NotNull FilterChain filterChain) throws ServletException, IOException { + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + filterChain.doFilter(request, response); + return; + } + String token = extractToken(request); if (token != null && isSelfIssuedToken(token)) { diff --git a/src/main/java/de/avatic/lcc/controller/users/GroupController.java b/src/main/java/de/avatic/lcc/controller/users/GroupController.java index 02ccf09..314ff35 100644 --- a/src/main/java/de/avatic/lcc/controller/users/GroupController.java +++ b/src/main/java/de/avatic/lcc/controller/users/GroupController.java @@ -33,7 +33,7 @@ public class GroupController { * @return A ResponseEntity containing the list of groups and pagination headers. */ @GetMapping({"/", ""}) - @PreAuthorize("hasAnyRole('RIGHT-MANAGMENT', 'SERVICE')") + @PreAuthorize("hasAnyRole('RIGHT-MANAGEMENT', 'SERVICE')") public ResponseEntity> listGroups(@RequestParam(defaultValue = "20") @Min(1) int limit, @RequestParam(defaultValue = "1") @Min(1) int page) { diff --git a/src/main/java/de/avatic/lcc/service/apps/JwtTokenService.java b/src/main/java/de/avatic/lcc/service/apps/JwtTokenService.java index 5640732..2a10dd9 100644 --- a/src/main/java/de/avatic/lcc/service/apps/JwtTokenService.java +++ b/src/main/java/de/avatic/lcc/service/apps/JwtTokenService.java @@ -41,11 +41,25 @@ public class JwtTokenService { } public Claims parseClaimsWithoutValidation(String token) { - return Jwts.parser() - .unsecured() - .build() - .parseUnsecuredClaims(token) - .getPayload(); + try { + String[] parts = token.split("\\."); + + String payload = parts[1]; + byte[] decodedBytes = java.util.Base64.getUrlDecoder().decode(payload); + String decodedPayload = new String(decodedBytes, StandardCharsets.UTF_8); + + com.fasterxml.jackson.databind.ObjectMapper mapper = + new com.fasterxml.jackson.databind.ObjectMapper(); + + @SuppressWarnings("unchecked") + java.util.Map claimsMap = + mapper.readValue(decodedPayload, java.util.Map.class); + + return Jwts.claims().add(claimsMap).build(); + + } catch (Exception e) { + throw new IllegalArgumentException("Failed to parse JWT claims", e); + } } public Claims validateToken(String token) {