Skip to content

Commit 12b0104

Browse files
committed
Fix #32637: add group support to CustomHeaderAuthenticationBackend
1 parent 7f6f0cd commit 12b0104

4 files changed

Lines changed: 67 additions & 6 deletions

File tree

src/main/java/eu/openanalytics/containerproxy/auth/impl/CustomHeaderAuthenticationBackend.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ public class CustomHeaderAuthenticationBackend implements IAuthenticationBackend
3737
public final static String NAME = "customHeader";
3838

3939
private final static String PROP_CUSTOM_AUTH_USERNAME_HEADER_NAME = "proxy.custom-header.username-header-name";
40+
private final static String PROP_CUSTOM_AUTH_GROUPS_HEADER_NAME = "proxy.custom-header.groups-header-name";
4041
private final static String DEFAULT_USERNAME_HEADER_NAME = "REMOTE_USER";
4142

4243
private final CustomHeaderAuthenticationFilter filter;
4344

4445
public CustomHeaderAuthenticationBackend(Environment environment, ApplicationEventPublisher applicationEventPublisher) {
4546
String usernameHeaderName = environment.getProperty(PROP_CUSTOM_AUTH_USERNAME_HEADER_NAME, DEFAULT_USERNAME_HEADER_NAME);
47+
String groupsHeaderName = environment.getProperty(PROP_CUSTOM_AUTH_GROUPS_HEADER_NAME); // disabled by default
4648
ProviderManager providerManager = new ProviderManager(new CustomHeaderAuthenticationProvider());
47-
filter = new CustomHeaderAuthenticationFilter(providerManager, applicationEventPublisher, usernameHeaderName);
49+
filter = new CustomHeaderAuthenticationFilter(providerManager, applicationEventPublisher, usernameHeaderName, groupsHeaderName);
4850
}
4951

5052
@Override

src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationFilter.java

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@
2424
import jakarta.servlet.ServletException;
2525
import jakarta.servlet.http.HttpServletRequest;
2626
import jakarta.servlet.http.HttpServletResponse;
27+
import net.minidev.json.parser.JSONParser;
28+
import net.minidev.json.parser.ParseException;
2729
import org.slf4j.Logger;
2830
import org.slf4j.LoggerFactory;
2931
import org.springframework.context.ApplicationEventPublisher;
3032
import org.springframework.security.authentication.AuthenticationManager;
3133
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
3234
import org.springframework.security.core.Authentication;
3335
import org.springframework.security.core.AuthenticationException;
36+
import org.springframework.security.core.GrantedAuthority;
37+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
3438
import org.springframework.security.core.context.SecurityContextHolder;
3539
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3640
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
@@ -40,6 +44,9 @@
4044

4145
import javax.annotation.Nonnull;
4246
import java.io.IOException;
47+
import java.util.ArrayList;
48+
import java.util.Arrays;
49+
import java.util.List;
4350

4451
public class CustomHeaderAuthenticationFilter extends OncePerRequestFilter {
4552

@@ -55,11 +62,13 @@ public class CustomHeaderAuthenticationFilter extends OncePerRequestFilter {
5562
);
5663

5764
private final String usernameHeaderName;
65+
private final String groupsHeaderName;
5866

59-
public CustomHeaderAuthenticationFilter(AuthenticationManager authenticationManager, ApplicationEventPublisher eventPublisher, String usernameHeaderName) {
67+
public CustomHeaderAuthenticationFilter(AuthenticationManager authenticationManager, ApplicationEventPublisher eventPublisher, String usernameHeaderName, String groupsHeaderName) {
6068
this.authenticationManager = authenticationManager;
6169
this.eventPublisher = eventPublisher;
6270
this.usernameHeaderName = usernameHeaderName;
71+
this.groupsHeaderName = groupsHeaderName;
6372
}
6473

6574
@Override
@@ -84,7 +93,7 @@ protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull Ht
8493
}
8594
}
8695

87-
Authentication authRequest = new CustomHeaderAuthenticationToken(remoteUser, false);
96+
Authentication authRequest = new CustomHeaderAuthenticationToken(remoteUser, parseGroups(request, remoteUser), false);
8897
Authentication authResult = authenticationManager.authenticate(authRequest);
8998
if (authResult == null) {
9099
throw new CustomHeaderAuthenticationException("No authentication");
@@ -93,10 +102,57 @@ protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull Ht
93102
SecurityContextHolder.getContext().setAuthentication(authResult);
94103
eventPublisher.publishEvent(new AuthenticationSuccessEvent(authResult));
95104
} catch (CustomHeaderAuthenticationException e) {
105+
logger.warn("Authentication failed: {}", e.getMessage());
106+
SecurityContextHolder.clearContext();
107+
} catch (Exception e) {
96108
logger.warn("Authentication failed", e);
97109
SecurityContextHolder.clearContext();
98110
}
99111
chain.doFilter(request, response);
100112
}
101113

114+
private List<GrantedAuthority> parseGroups(HttpServletRequest request, String username) {
115+
if (groupsHeaderName == null) {
116+
return List.of();
117+
}
118+
String remoteGroups = request.getHeader(groupsHeaderName);
119+
if (remoteGroups == null) {
120+
logger.warn("Header '{}' does not contain the groups of user '{}', the proxy should always override this header. This is a security risk, users might spoof groups!", groupsHeaderName, username);
121+
return List.of();
122+
}
123+
124+
remoteGroups = remoteGroups.strip();
125+
126+
List<String> roles = new ArrayList<>();
127+
128+
if (remoteGroups.startsWith("[")) {
129+
// this is probably json
130+
try {
131+
Object value = new JSONParser(JSONParser.MODE_PERMISSIVE).parse(remoteGroups);
132+
if (value instanceof List<?> valueList) {
133+
valueList.forEach(o -> roles.add(o.toString()));
134+
logger.debug("Parsed groups header as JSON: {} -> {}", groupsHeaderName, roles);
135+
}
136+
} catch (ParseException e) {
137+
// Unable to parse JSON
138+
logger.debug("Unable to parse groups header as JSON: {} -> {}", groupsHeaderName, remoteGroups);
139+
}
140+
} else {
141+
if (remoteGroups.contains(",")) {
142+
Arrays.stream(remoteGroups.split(",")).forEach(g -> roles.add(g.strip()));
143+
} else {
144+
// assuming it's a single role
145+
roles.add(remoteGroups);
146+
}
147+
logger.debug("Parsed groups header as comma-separated string: {} -> {}", groupsHeaderName, roles);
148+
}
149+
150+
List<GrantedAuthority> authorities = new ArrayList<>();
151+
for (String role : roles) {
152+
String mappedRole = role.toUpperCase().startsWith("ROLE_") ? role : "ROLE_" + role;
153+
authorities.add(new SimpleGrantedAuthority(mappedRole.toUpperCase()));
154+
}
155+
return authorities;
156+
}
157+
102158
}

src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public Authentication authenticate(Authentication authentication) throws Authent
3131
CustomHeaderAuthenticationToken authRequest = (CustomHeaderAuthenticationToken) authentication;
3232

3333
if (authRequest.isValid()) {
34-
return new CustomHeaderAuthenticationToken(authRequest.getPrincipal().toString(), true);
34+
return new CustomHeaderAuthenticationToken(authRequest.getPrincipal().toString(), authRequest.getAuthorities(), true);
3535
}
3636

3737
throw new CustomHeaderAuthenticationException("Invalid username");

src/main/java/eu/openanalytics/containerproxy/auth/impl/customHeader/CustomHeaderAuthenticationToken.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@
2121
package eu.openanalytics.containerproxy.auth.impl.customHeader;
2222

2323
import org.springframework.security.authentication.AbstractAuthenticationToken;
24+
import org.springframework.security.core.GrantedAuthority;
25+
26+
import java.util.Collection;
2427

2528
public class CustomHeaderAuthenticationToken extends AbstractAuthenticationToken {
2629

2730
private final String username;
2831

29-
public CustomHeaderAuthenticationToken(String username, boolean isAuthenticated) {
30-
super(null);
32+
public CustomHeaderAuthenticationToken(String username, Collection<GrantedAuthority> authorities, boolean isAuthenticated) {
33+
super(authorities);
3134
this.username = username;
3235
super.setAuthenticated(isAuthenticated);
3336
}

0 commit comments

Comments
 (0)