/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.deltaspike.jsf.impl.security; import org.apache.deltaspike.core.api.config.view.metadata.ViewConfigDescriptor; import org.apache.deltaspike.core.api.config.view.metadata.ViewConfigResolver; import org.apache.deltaspike.core.api.provider.BeanProvider; import org.apache.deltaspike.core.spi.activation.Deactivatable; import org.apache.deltaspike.core.util.ClassDeactivationUtils; import org.apache.deltaspike.core.util.ExceptionUtils; import org.apache.deltaspike.jsf.api.config.JsfModuleConfig; import org.apache.deltaspike.jsf.api.config.view.View; import org.apache.deltaspike.jsf.impl.util.SecurityUtils; import org.apache.deltaspike.security.api.authorization.ErrorViewAwareAccessDeniedException; import org.apache.deltaspike.security.spi.authorization.EditableAccessDecisionVoterContext; import javax.faces.application.ViewHandler; import javax.faces.application.ViewHandlerWrapper; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; public class SecurityAwareViewHandler extends ViewHandlerWrapper implements Deactivatable { public static final String PRE_DESTROY_VIEW_MAP_EVENT_FILTER_ENABLED = SecurityAwareViewHandler.class.getName() + "#PRE_DESTROY_VIEW_MAP_EVENT_FILTER_ENABLED"; protected final ViewHandler wrapped; private final boolean activated; private Boolean securityModuleActivated; /** * Constructor for wrapping the given {@link ViewHandler} * * @param wrapped view-handler which should be wrapped */ public SecurityAwareViewHandler(ViewHandler wrapped) { this.wrapped = wrapped; this.activated = ClassDeactivationUtils.isActivated(getClass()); } @Override public ViewHandler getWrapped() { return this.wrapped; } @Override public UIViewRoot createView(FacesContext context, String viewId) { UIViewRoot result = this.wrapped.createView(context, viewId); if (!this.activated) { return result; } if (this.securityModuleActivated == null) { lazyInit(); } if (!this.securityModuleActivated) { return result; } UIViewRoot originalViewRoot = context.getViewRoot(); Map<String, Object> viewMap = null; if (originalViewRoot != null) { Map<String, Object> originalViewMap = originalViewRoot.getViewMap(false); if (originalViewMap != null && !originalViewMap.isEmpty()) { viewMap = new HashMap<String, Object>(); viewMap.putAll(originalViewMap); } } //workaround for PreDestroyViewMapEvent which would be caused by the security check deactivatePreDestroyViewMapEvent(context); //we have to use it as current view if an AccessDecisionVoter uses the JSF API to check access to the view-id context.setViewRoot(result); try { ViewRootAccessHandler viewRootAccessHandler = BeanProvider.getContextualReference(ViewRootAccessHandler.class); viewRootAccessHandler.checkAccessTo(result); } catch (ErrorViewAwareAccessDeniedException accessDeniedException) { ViewConfigResolver viewConfigResolver = BeanProvider.getContextualReference(ViewConfigResolver.class); ViewConfigDescriptor errorViewDescriptor = viewConfigResolver .getViewConfigDescriptor(accessDeniedException.getErrorView()); try { if (errorViewDescriptor != null && View.NavigationMode.REDIRECT == errorViewDescriptor.getMetaData(View.class).iterator().next().navigation() /*always available*/ && BeanProvider.getContextualReference(JsfModuleConfig.class) .isAlwaysUseNavigationHandlerOnSecurityViolation()) { SecurityUtils.tryToHandleSecurityViolation(accessDeniedException); } else { SecurityUtils.handleSecurityViolationWithoutNavigation(accessDeniedException); } } finally { broadcastAccessDeniedException(accessDeniedException); } if (errorViewDescriptor != null) { return this.wrapped.createView(context, errorViewDescriptor.getViewId()); } else { //only in case of GET requests, because an exception during POST requests leads to re-rendering //the previous page (including the error message) if (!context.isPostback() && context.getViewRoot() != null) { context.getViewRoot().setViewId(null); } } throw accessDeniedException; //security exception without error-view } finally { activatePreDestroyViewMapEvent(context); if (originalViewRoot != null) { context.setViewRoot(originalViewRoot); if (viewMap != null) { originalViewRoot.getViewMap().putAll(viewMap); } } } return result; } protected void broadcastAccessDeniedException(ErrorViewAwareAccessDeniedException accessDeniedException) { AccessDeniedExceptionBroadcaster exceptionBroadcaster = BeanProvider.getContextualReference(AccessDeniedExceptionBroadcaster.class); Throwable broadcastResult = exceptionBroadcaster.broadcastAccessDeniedException(accessDeniedException); if (broadcastResult != null) { throw ExceptionUtils.throwAsRuntimeException(broadcastResult); } } private synchronized void lazyInit() { this.securityModuleActivated = BeanProvider.getContextualReference(EditableAccessDecisionVoterContext.class, true) != null; if (!this.securityModuleActivated) { Logger.getLogger(getClass().getName()) //it's the only case for which a logger is needed in this class .info("security-module-impl isn't used -> " + getClass().getName() + " gets deactivated"); } } private void deactivatePreDestroyViewMapEvent(FacesContext facesContext) { facesContext.getExternalContext().getRequestMap().put(PRE_DESTROY_VIEW_MAP_EVENT_FILTER_ENABLED, Boolean.TRUE); } private void activatePreDestroyViewMapEvent(FacesContext facesContext) { facesContext.getExternalContext().getRequestMap().put(PRE_DESTROY_VIEW_MAP_EVENT_FILTER_ENABLED, Boolean.FALSE); } }