UserFilter.java
package de.dlr.shepard.filters;
import java.io.IOException;
import de.dlr.shepard.exceptions.ApiError;
import de.dlr.shepard.exceptions.ShepardProcessingException;
import de.dlr.shepard.neo4Core.entities.User;
import de.dlr.shepard.neo4Core.services.UserService;
import de.dlr.shepard.security.GracePeriodUtil;
import de.dlr.shepard.security.JWTPrincipal;
import de.dlr.shepard.security.Userinfo;
import de.dlr.shepard.security.UserinfoService;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j;
@Provider
@Slf4j
@Priority(Priorities.AUTHENTICATION + 1)
public class UserFilter implements ContainerRequestFilter {
private static final int THIRTY_MINUTES_IN_MILLIS = 30 * 60 * 1000;
private final GracePeriodUtil lastSeen;
public UserFilter() {
lastSeen = new GracePeriodUtil(THIRTY_MINUTES_IN_MILLIS);
}
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
UserService userService = getUserService();
var principal = requestContext.getSecurityContext().getUserPrincipal();
if (!(principal instanceof JWTPrincipal)) {
log.warn("Unknown principal {}", principal);
abort(requestContext, "User could not be read from the request context");
return;
}
var jwtPrincipal = (JWTPrincipal) principal;
var header = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith("Bearer ") && !lastSeen.elementIsKnown(jwtPrincipal.getUsername())) {
User user;
Userinfo userinfo;
try {
userinfo = getUserinfoService().fetchUserinfo(header);
} catch (ShepardProcessingException e) {
abort(requestContext, "User info could not be retrieved");
return;
}
user = parseUserFromUserinfo(userinfo);
if (!jwtPrincipal.getUsername().equals(user.getUsername())) {
log.warn("The usernames from the access token and the userinfo response do not match");
abort(requestContext, "The usernames from the access token and the userinfo response do not match");
return;
}
var created = userService.updateUser(user);
if (created == null) {
log.warn("The user could not be updated or created");
abort(requestContext, "The user could not be updated or created");
return;
}
lastSeen.elementSeen(jwtPrincipal.getUsername());
}
}
private void abort(ContainerRequestContext requestContext, String reason) {
requestContext.abortWith(Response.status(Status.UNAUTHORIZED)
.entity(new ApiError(Status.UNAUTHORIZED.getStatusCode(), "AuthenticationException", reason)).build());
}
private User parseUserFromUserinfo(Userinfo userinfo) {
// We only want the last part of the subject, since this is usually a human
// readable user name
var splitted = userinfo.getSub().split(":");
String username = splitted[splitted.length - 1];
User user = new User(username, userinfo.getGivenName(), userinfo.getFamilyName(), userinfo.getEmail());
return user;
}
protected UserService getUserService() {
return new UserService();
}
protected UserinfoService getUserinfoService() {
return new UserinfoService();
}
}