package net.curisit.securis.ioc; import java.io.IOException; import java.lang.reflect.Method; import java.util.List; import java.util.Set; import javax.annotation.Priority; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Priorities; import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.core.ResourceMethodInvoker; import org.jboss.resteasy.core.ServerResponse; import org.jboss.resteasy.spi.Failure; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.ResteasyProviderFactory; @Provider @Priority(Priorities.AUTHENTICATION) public class RequestsInterceptor implements ContainerRequestFilter, WriterInterceptor { private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class); @Context private HttpServletResponse servletResponse; @Context private HttpServletRequest servletRequest; @Inject private CacheTTL cache; @Inject private TokenHelper tokenHelper; @Context private Dispatcher dispatcher; @Inject private EntityManagerProvider emProvider; @Override public void filter(ContainerRequestContext containerRequestContext) throws IOException { EntityManager em = emProvider.getEntityManager(); LOG.info("GETTING EM: {}", em); ResteasyProviderFactory.pushContext(EntityManager.class, em); ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) containerRequestContext .getProperty("org.jboss.resteasy.core.ResourceMethodInvoker"); Method method = methodInvoker.getMethod(); LOG.debug("Stored in context, em: {}, {}", em, method.toGenericString()); boolean next = checkSecurableMethods(containerRequestContext, method); if (next) { prepareTransaction(containerRequestContext, method, em); } } private void prepareTransaction(ContainerRequestContext containerRequestContext, Method method, EntityManager em) { if (method.isAnnotationPresent(EnsureTransaction.class)) { LOG.debug("Beginning a new transaction"); em.getTransaction().begin(); } } private boolean checkSecurableMethods(ContainerRequestContext containerRequestContext, 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.info("Access denied to '{}', Token not valid.", servletRequest.getPathInfo()); containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).build()); return false; } else { // If roles == 0 we only need to validate the token String username = tokenHelper.extractUserFromToken(token); int userRoles = getUserRoles(username); Set orgs = getUserOrganizations(username); BasicSecurityContext scw = new BasicSecurityContext(username, userRoles, servletRequest.isSecure()); scw.setOrganizationsIds(orgs); containerRequestContext.setSecurityContext(scw); // Next line provide injection in resource methods ResteasyProviderFactory.pushContext(BasicSecurityContext.class, scw); LOG.debug("Added custom SecurityContext for user {}, orgs: {}", username, orgs); } return true; } private Set getUserOrganizations(String username) { @SuppressWarnings("unchecked") Set userOrgs = cache.get("orgs_" + username, Set.class); if (userOrgs == null) { EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class); // Theorically this shouldn't be never null, but just in case... User user = em.find(User.class, username); if (user != null) { userOrgs = user.getAllOrgsIds(); // We store user orgs in cache only for one hour cache.set("orgs_" + username, userOrgs, 3600); } } return userOrgs; } private int getUserRoles(String username) { if (username == null) { return 0; } Integer userRoles = cache.get("roles_" + username, Integer.class); if (userRoles == null) { EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class); User user = em.find(User.class, username); if (user != null) { userRoles = 0; List roles = user.getRoles(); for (Integer rol : roles) { userRoles += rol; } // We store user roles in cache only for one hour cache.set("roles_" + username, userRoles, 3600); cache.set("orgs_" + username, user.getOrgsIds(), 3600); } } return userRoles == null ? 0 : userRoles.intValue(); } // @Override public ServerResponse preProcess(HttpRequest request, ResourceMethodInvoker method) throws Failure, WebApplicationException { return null; } @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { context.proceed(); EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class); try { if (em != null && em.getTransaction().isActive()) { if (servletResponse.getStatus() == Status.OK.getStatusCode()) { em.getTransaction().commit(); LOG.debug("COMMIT"); } else { // This code is never executed if there is an error the // filter chain is broken em.getTransaction().rollback(); LOG.debug("ROLLBACK"); } } } finally { if (em.isOpen()) { LOG.info("CLOSING EM: {}, trans: {}", em, em.isJoinedToTransaction()); try { em.close(); } catch (Exception ex) { ex.printStackTrace(); LOG.error("Error closing EM: {}, {}", em, ex); } } } } }