/* * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. */ package net.curisit.securis.ioc; import java.io.IOException; import java.lang.reflect.Method; import java.util.List; import java.util.Set; import jakarta.annotation.Priority; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.ResourceInfo; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.WriterInterceptor; import jakarta.ws.rs.ext.WriterInterceptorContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import net.curisit.securis.db.User; import net.curisit.securis.security.BasicSecurityContext; import net.curisit.securis.security.Securable; import net.curisit.securis.utils.CacheTTL; import net.curisit.securis.utils.TokenHelper; /** * RequestsInterceptor *
* Authentication/authorization interceptor that: *
Cache usage: Uses {@link CacheTTL} to cache roles and scope sets.
* The new {@link CacheTTL#getSet(String, Class)} helper removes unchecked
* conversion warnings when retrieving {@code Set
* Entry point before resource method invocation.
*
* @param requestContext
* @throws IOException
*/
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
EntityManager em = emProvider.getEntityManager();
LOG.debug("GETTING EM: {}", em);
// Store EntityManager for later retrieval (writer interceptor)
requestContext.setProperty(EM_CONTEXT_PROPERTY, em);
Method method = resourceInfo.getResourceMethod();
if (checkSecurableMethods(requestContext, method)) {
if (method.isAnnotationPresent(EnsureTransaction.class)) {
LOG.debug("Beginning transaction");
em.getTransaction().begin();
}
}
}
/**
* checkSecurableMethods
* Enforce security checks for methods annotated with {@link Securable}.
* Builds {@link BasicSecurityContext} when authorized.
*
* @param ctx
* @param method
* @return true if request can proceed; false when aborted
*/
private boolean checkSecurableMethods(ContainerRequestContext ctx, Method method) {
if (!method.isAnnotationPresent(Securable.class)) return true;
String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PĂ€RAM);
if (token == null || !tokenHelper.isTokenValid(token)) {
LOG.warn("Access denied, invalid token");
ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
return false;
}
String username = tokenHelper.extractUserFromToken(token);
int roles = getUserRoles(username);
Securable securable = method.getAnnotation(Securable.class);
if (securable.roles() != 0 && (securable.roles() & roles) == 0) {
LOG.warn("User {} lacks required roles for method {}", username, method.getName());
ctx.abortWith(Response.status(Status.UNAUTHORIZED).build());
return false;
}
BasicSecurityContext sc = new BasicSecurityContext(username, roles, servletRequest.isSecure());
sc.setOrganizationsIds(getUserOrganizations(username));
sc.setApplicationsIds(getUserApplications(username));
ctx.setSecurityContext(sc);
return true;
}
// -------------------------------------------------------------
// Cached lookups (roles/orgs/apps)
// -------------------------------------------------------------
/**
* getUserRoles
* Retrieve roles bitmask for the given user (cached).
*
* @param username
* @return userRoles
*/
private int getUserRoles(String username) {
if (username == null) return 0;
Integer cached = cache.get("roles_" + username, Integer.class);
if (cached != null) return cached;
EntityManager em = emProvider.getEntityManager();
User user = em.find(User.class, username);
int roles = 0;
if (user != null) {
List
* Retrieve organization scope for the user as a typed {@code Set
* Retrieve application scope for the user as a typed {@code Set
* Commit/rollback and close EM after response writing.
*
* @param context
* @throws IOException
* @throws WebApplicationException
*/
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
context.proceed();
EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY);
if (em == null) return;
try {
if (em.getTransaction().isActive()) {
if (servletResponse.getStatus() == Status.OK.getStatusCode()) {
em.getTransaction().commit();
LOG.debug("Transaction committed");
} else {
em.getTransaction().rollback();
LOG.debug("Transaction rolled back");
}
}
} finally {
if (em.isOpen()) {
try {
em.close();
} catch (Exception e) {
LOG.error("Error closing EntityManager", e);
}
}
}
}
}