package com.softwaremill.common.faces.security; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.softwaremill.common.cdi.el.ELEvaluator; import com.softwaremill.common.cdi.security.LoginBean; import com.softwaremill.common.cdi.util.BeanInject; import com.softwaremill.common.faces.navigation.NavBase; import com.softwaremill.common.faces.navigation.Page; import javax.faces.context.FacesContext; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; import javax.faces.event.PhaseListener; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * TODO: go back after a redirect to login * <p/> * TODO: secure the cache: * //Forces caches to obtain a new copy of the page from the origin server * response.setHeader("Cache-Control","no-cache"); * //Directs caches not to store the page under any circumstance * response.setHeader("Cache-Control","no-store"); * //Causes the proxy cache to see the page as "stale" * response.setDateHeader("Expires", 0); * //HTTP 1.0 backward compatibility * response.setHeader("Pragma","no-cache"); * * @author Adam Warski (adam at warski dot org) */ public class SecurityPhaseListener implements PhaseListener { private final static Logger LOG = LoggerFactory.getLogger(SecurityPhaseListener.class); private final static String PREVIOUS_VIEW = "previous:viewId:redirect"; private final static String PREVIOUS_VIEW_PARAMETERS = "previous:viewId:parameters"; private NavBase nav; public SecurityPhaseListener() { this.nav = BeanInject.lookup(NavBase.class); } public void beforePhase(PhaseEvent event) { FacesContext ctx = event.getFacesContext(); HttpSession session = ((HttpSession) ctx.getExternalContext().getSession(true)); LoginBean login = BeanInject.lookup(LoginBean.class); Page page = nav.lookup(event.getFacesContext().getViewRoot().getViewId()); if (page.isRequiresLogin() && !login.isLoggedIn()) { if (nav.shouldRedirectToLogin()) { redirectToLogin(ctx, session, nav, page); } else { nav.responseForbidden(ctx); } } else if (login.isLoggedIn() && session.getAttribute(PREVIOUS_VIEW) != null) { redirectToPreviousView(ctx, session); } else if (page.getSecurityEL() != null && page.getSecurityEL().length() > 0) { evaluateSecurityExpression(ctx, page); } } private void redirectToLogin(FacesContext ctx, HttpSession session, NavBase nav, Page page) { session.setAttribute(PREVIOUS_VIEW, ctx.getApplication().getViewHandler().getActionURL(ctx, page.s())); LOG.debug("Stored PREVIOUS_VIEW = ["+ session.getAttribute(PREVIOUS_VIEW) + "] in session!"); // this is needed as the default implementation returned by # getRequestParameterValuesMap() loose parameters when session is restored Map<String, List<String>> map = repackageParameters(ctx.getExternalContext().getRequestParameterValuesMap()); session.setAttribute(PREVIOUS_VIEW_PARAMETERS, map); LOG.debug("Stored PREVIOUS_VIEW_PARAMETERS = ["+ session.getAttribute(PREVIOUS_VIEW_PARAMETERS) + "] in session!"); try { String url = ctx.getApplication().getViewHandler().getActionURL(ctx, nav.getLogin().s()); ctx.getExternalContext().redirect(ctx.getExternalContext().encodeActionURL(url)); } catch (IOException e) { throw new RuntimeException(e); } ctx.responseComplete(); } private Map<String, List<String>> repackageParameters(Map<String, String[]> requestParameterValuesMap) { Map<String, List<String>> map = new HashMap<String, List<String>>(); Set<Map.Entry<String, String[]>> entries = requestParameterValuesMap.entrySet(); for (Map.Entry<String, String[]> entry : entries) { map.put(entry.getKey(), Arrays.asList(entry.getValue())); } return map; } private void redirectToPreviousView(FacesContext ctx, HttpSession session) { String redirect = (String) session.getAttribute(PREVIOUS_VIEW); Map parameters = (Map) session.getAttribute(PREVIOUS_VIEW_PARAMETERS); session.setAttribute(PREVIOUS_VIEW, null); session.setAttribute(PREVIOUS_VIEW_PARAMETERS, null); try { LOG.debug("Redirecting to [" + redirect +"] with params [" + parameters +"]"); if (parameters != null && !parameters.isEmpty()) { ctx.getExternalContext().redirect(ctx.getExternalContext().encodeRedirectURL(redirect, parameters)); } else { ctx.getExternalContext().redirect(ctx.getExternalContext().encodeActionURL(redirect)); } } catch (IOException e) { throw new RuntimeException(e); } ctx.responseComplete(); } private void evaluateSecurityExpression(FacesContext ctx, Page page) { ELEvaluator evaluator = BeanInject.lookup(ELEvaluator.class); Boolean securityResult = evaluator.evaluate(page.getSecurityEL(), Boolean.class); if (securityResult == null) { throw new RuntimeException("Security EL: " + page.getSecurityEL() + " on page " + page.s() + " doesn't resolve to Boolean"); } if (!securityResult) { nav.responseForbidden(ctx); } } public void afterPhase(PhaseEvent event) { } @Override public PhaseId getPhaseId() { return PhaseId.RENDER_RESPONSE; } }