All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.lifecycle.RestoreViewExecutor Maven / Gradle / Ivy

/*
 * 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.myfaces.lifecycle;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.logging.Level;
import java.util.logging.Logger;

import jakarta.el.MethodExpression;
import jakarta.faces.FacesException;
import jakarta.faces.FactoryFinder;
import jakarta.faces.application.Application;
import jakarta.faces.application.ProjectStage;
import jakarta.faces.application.ProtectedViewException;
import jakarta.faces.application.ViewExpiredException;
import jakarta.faces.application.ViewHandler;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.PhaseEvent;
import jakarta.faces.event.PhaseId;
import jakarta.faces.event.PostAddToViewEvent;
import jakarta.faces.flow.FlowHandler;
import jakarta.faces.lifecycle.ClientWindow;
import jakarta.faces.lifecycle.Lifecycle;
import jakarta.faces.lifecycle.LifecycleFactory;
import jakarta.faces.render.RenderKit;
import jakarta.faces.render.RenderKitFactory;
import jakarta.faces.render.ResponseStateManager;
import jakarta.faces.view.ViewDeclarationLanguage;
import jakarta.faces.view.ViewMetadata;
import jakarta.faces.webapp.FacesServlet;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.myfaces.application.ViewIdSupport;
import org.apache.myfaces.config.webparameters.MyfacesConfig;
import org.apache.myfaces.event.PostClientWindowAndViewInitializedEvent;

import org.apache.myfaces.renderkit.ErrorPageWriter;
import org.apache.myfaces.util.ExternalContextUtils;

/**
 * Implements the Restore View Phase (Faces Spec 2.2.1)
 * 
 * @author Nikolay Petrov (latest modification by $Author$)
 * @author Bruno Aranda (Faces 1.2)
 * @version $Revision$ $Date$
 */
class RestoreViewExecutor extends PhaseExecutor
{
    private static final Logger log = Logger.getLogger(RestoreViewExecutor.class.getName());
    
    private RestoreViewSupport _restoreViewSupport;
    private ViewIdSupport _viewIdSupport;
    
    private Boolean _viewNotFoundCheck;
    
    private RenderKitFactory _renderKitFactory = null;
    
    @Override
    public void doPrePhaseActions(FacesContext facesContext)
    {
        // Call initView() on the ViewHandler. 
        // This will set the character encoding properly for this request.
        // Note that we are doing this here, because we need the character encoding
        // to be set as early as possible (before any PhaseListener is executed).
        facesContext.getApplication().getViewHandler().initView(facesContext);
    }

    @Override
    public boolean execute(FacesContext facesContext)
    {
        if (facesContext == null)
        {
            throw new FacesException("FacesContext is null");
        }

        // get some required Objects
        Application application = facesContext.getApplication();
        ViewHandler viewHandler = application.getViewHandler();
        RestoreViewSupport restoreViewSupport = getRestoreViewSupport(facesContext);
        ViewIdSupport viewHandlerSupport = getViewIdSupport(facesContext);

        // Examine the FacesContext instance for the current request. If it already contains a UIViewRoot
        UIViewRoot viewRoot = facesContext.getViewRoot();
        if (viewRoot != null)
        {
            if (log.isLoggable(Level.FINEST))
            {
                log.finest("View already exists in the FacesContext");
            }
            
            // Set the locale on this UIViewRoot to the value returned by the getRequestLocale() method on the
            // ExternalContext for this request
            viewRoot.setLocale(facesContext.getExternalContext().getRequestLocale());
            
            restoreViewSupport.processComponentBinding(facesContext, viewRoot);
            
            // invoke the afterPhase MethodExpression of UIViewRoot
            _invokeViewRootAfterPhaseListener(facesContext);
            
            return false;
        }
        
        String viewId = viewHandlerSupport.calculateViewId(facesContext);

        // Determine if the current request is an attempt by the 
        // servlet container to display an error page.
        // If the request is an error page request, the servlet container
        // is required to set the request parameter "jakarta.servlet.error.message".
        final boolean errorPageRequest = facesContext.getExternalContext().getRequestMap()
                                                 .get("jakarta.servlet.error.message") != null;
        
        // Determine if this request is a postback or an initial request.
        // But if it is an error page request, do not treat it as a postback (since 2.0)
        if (!errorPageRequest && restoreViewSupport.isPostback(facesContext))
        { // If the request is a postback
            if (log.isLoggable(Level.FINEST))
            {
                log.finest("Request is a postback");
            }

            if (checkViewNotFound(facesContext))
            {
                String derivedViewId = viewHandler.deriveLogicalViewId(facesContext, viewId);
                ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, derivedViewId);
            
                // viewHandler.deriveLogicalViewId() could trigger an InvalidViewIdException, which
                // it is handled internally sending a 404 error code set the response as complete.
                if (facesContext.getResponseComplete())
                {
                    return true;
                }
            
                if (vdl == null || derivedViewId == null)
                {
                    sendSourceNotFound(facesContext, viewId);
                    return true;
                }
                else if (!viewHandlerSupport.isViewExistent(facesContext, derivedViewId))
                {
                    sendSourceNotFound(facesContext, viewId);
                    return true;
                }
            }
            
            try
            {
                facesContext.setProcessingEvents(false);
                // call ViewHandler.restoreView(), passing the FacesContext instance for the current request and the 
                // view identifier, and returning a UIViewRoot for the restored view.
                
                viewRoot = viewHandler.restoreView(facesContext, viewId);
                if (viewRoot == null)
                {
                    if (facesContext.getResponseComplete())
                    {
                        // If the view handler cannot restore the view and the response
                        // is complete, it can be an error or some logic in restoreView.
                        return true;
                    }
                    else
                    {
                        // If the return from ViewHandler.restoreView() is null, throw a ViewExpiredException with an 
                        // appropriate error message.
                        throw new ViewExpiredException("View \"" + viewId + "\" could not be restored.", viewId);
                    }
                }
                // If the view is transient (stateless), it is necessary to check the view protection
                // in POST case.
                if (viewRoot.isTransient())
                {
                    checkViewProtection(facesContext, viewHandler, viewRoot.getViewId(), viewRoot);
                }
                
                // Store the restored UIViewRoot in the FacesContext.
                facesContext.setViewRoot(viewRoot);
            }
            finally
            {
                facesContext.setProcessingEvents(true);
            }
            
            // Restore binding
            // See https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=806
            restoreViewSupport.processComponentBinding(facesContext, viewRoot);
            
            ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
            if (clientWindow != null)
            {
                // The idea of this event is once the client window and the view is initialized, 
                // you have the information required to clean up any scope that belongs to old
                // clientWindow instances like flow scope (in server side state saving).
                facesContext.getApplication().publishEvent(facesContext, PostClientWindowAndViewInitializedEvent.class, 
                        clientWindow);
            }
        }
        else
        { 
            // If the request is a non-postback
            if (log.isLoggable(Level.FINEST))
            {
                log.finest("Request is not a postback. New UIViewRoot will be created");
            }

            String logicalViewId = viewHandler.deriveLogicalViewId(facesContext, viewId);
            ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, logicalViewId);
            
            // viewHandler.deriveLogicalViewId() could trigger an InvalidViewIdException, which
            // it is handled internally sending a 404 error code set the response as complete.
            if (facesContext.getResponseComplete())
            {
                return true;
            }
            
            if (checkViewNotFound(facesContext))
            {
                if (vdl == null || logicalViewId == null)
                {
                    sendSourceNotFound(facesContext, viewId);
                    return true;
                }
                else if (!viewHandlerSupport.isViewExistent(facesContext, logicalViewId))
                {
                    sendSourceNotFound(facesContext, viewId);
                    return true;
                }
            }
            
            if (vdl != null)
            {
                ViewMetadata metadata = vdl.getViewMetadata(facesContext, viewId);
                
                if (metadata != null)
                {
                    viewRoot = metadata.createMetadataView(facesContext);
                    
                    if(facesContext.getResponseComplete())
                    {
                        // this can happen if the current request is a debug request,
                        // in this case no further processing is necessary
                        return true;
                    }
                }
    
                if (viewRoot == null)
                {
                    facesContext.renderResponse();
                }
                else if (viewRoot != null && !ViewMetadata.hasMetadata(viewRoot))
                {
                    facesContext.renderResponse();
                }
            }
            else
            {
                // Call renderResponse
                facesContext.renderResponse();
            }

            checkViewProtection(facesContext, viewHandler, logicalViewId, viewRoot);
            
            // viewRoot can be null here, if ...
            //   - we don't have a ViewDeclarationLanguage (e.g. when using facelets-1.x)
            //   - there is no view metadata or metadata.createMetadataView() returned null
            if (viewRoot == null)
            {
                // call ViewHandler.createView(), passing the FacesContext instance for the current request and 
                // the view identifier
                viewRoot = viewHandler.createView(facesContext, viewId);
            }
            
            if (viewRoot == null && facesContext.getResponseComplete())
            {
                // If the view handler cannot create the view and the response
                // is complete, it can be an error, just get out of the algorithm.
                return true;
            }
            
            // Subscribe the newly created UIViewRoot instance to the AfterAddToParent event, passing the 
            // UIViewRoot instance itself as the listener.
            // -= Leonardo Uribe =- This line it is not necessary because it was
            // removed from jsf 2.0 section 2.2.1 when pass from EDR2 to Public Review 
            // viewRoot.subscribeToEvent(PostAddToViewEvent.class, viewRoot);
            
            // Store the new UIViewRoot instance in the FacesContext.
            facesContext.setViewRoot(viewRoot);

            ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
            if (clientWindow != null)
            {
                facesContext.getApplication().publishEvent(facesContext, PostClientWindowAndViewInitializedEvent.class, 
                        clientWindow);
            }
            
            FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
            if (flowHandler != null)
            {
                flowHandler.clientWindowTransition(facesContext);
            }

            // Publish an AfterAddToParent event with the created UIViewRoot as the event source.
            application.publishEvent(facesContext, PostAddToViewEvent.class, viewRoot);
        }

        // add the ErrorPageBean to the view map to fully support 
        // facelet error pages, if we are in ProjectStage Development
        // and currently generating an error page
        if (errorPageRequest && facesContext.isProjectStage(ProjectStage.Development))
        {
            facesContext.getViewRoot().getViewMap()
                    .put(ErrorPageWriter.ERROR_PAGE_BEAN_KEY, new ErrorPageWriter.ErrorPageBean());
        }
        
        // invoke the afterPhase MethodExpression of UIViewRoot
        _invokeViewRootAfterPhaseListener(facesContext);
        
        return false;
    }
    
    private void checkViewProtection(FacesContext facesContext, ViewHandler viewHandler, 
        String viewId, UIViewRoot root) throws ProtectedViewException
    {
        boolean valid = true;
        if (getViewIdSupport(facesContext).isViewProtected(facesContext, viewId))
        {
            // "... Obtain the value of the value of the request parameter whose 
            // name is given by the value of ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM. 
            // If there is no value, throw ProtectedViewException ..."
            String token = facesContext.getExternalContext().getRequestParameterMap().get(
                ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM);
            if (token != null && token.length() > 0)
            {
                String renderKitId = null;
                if (root != null)
                {
                    renderKitId = root.getRenderKitId();
                }
                if (renderKitId == null)
                {
                    renderKitId = viewHandler.calculateRenderKitId(facesContext);
                }
                RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
                ResponseStateManager rsm = renderKit.getResponseStateManager();
                
                String storedToken = rsm.getCryptographicallyStrongTokenFromSession(facesContext);
                if (token.equals(storedToken))
                {
                    if (!ExternalContextUtils.isPortlet(facesContext.getExternalContext()))
                    {
                        // Any check beyond this point only has sense for servlet requests.
                        String referer = facesContext.getExternalContext().getRequestHeaderMap().get("Referer");
                        if (referer != null)
                        {
                            valid = valid && checkRefererOrOriginHeader(facesContext, viewHandler, referer);
                        }
                        String origin = facesContext.getExternalContext().getRequestHeaderMap().get("Origin");
                        if (valid && origin != null)
                        {
                            valid = valid && checkRefererOrOriginHeader(facesContext, viewHandler, origin);
                        }
                    }
                }
                else
                {
                    valid = false;
                }
            }
            else
            {
                valid = false;
            }
        }
        if (!valid)
        {
            throw new ProtectedViewException();
        }
    }
    
    private boolean checkRefererOrOriginHeader(FacesContext facesContext, 
        ViewHandler viewHandler, String refererOrOrigin)
    {
        try
        {
            // The referer can be absolute or relative. 
            ExternalContext ectx = facesContext.getExternalContext();
            URI refererURI = new URI(refererOrOrigin);
            String path = refererURI.getPath();
            String appContextPath = ectx.getApplicationContextPath();
            if (refererURI.isAbsolute())
            {
                // Check if the referer comes from the same host
                String host = refererURI.getHost();
                int port = refererURI.getPort();
                String serverHost = ectx.getRequestServerName();
                int serverPort = ectx.getRequestServerPort();

                boolean matchPort = true;
                if (serverPort != -1 && port != -1)
                {
                    matchPort = (serverPort == port);
                }
                boolean isStrictJsf2OriginHeaderAppPath = 
                                MyfacesConfig.getCurrentInstance(facesContext).isStrictJsf2OriginHeaderAppPath();
                if (!path.isEmpty())
                {
                    if (serverHost.equals(host) && matchPort && path.contains(appContextPath))
                    {
                        // Referer Header match
                    }
                    else
                    {
                        // Referer Header does not match
                        return false;
                    }
                }
                else
                {
                    if (serverHost.equals(host) && matchPort && !isStrictJsf2OriginHeaderAppPath)
                    {
                        // Origin Header match and 
                        // STRICT_JSF_2_ORIGIN_HEADER_APP_PATH property is set to false (default)
                        // Because we don't want to strictly follow Faces 2.x spec
                    }
                    else
                    {
                        // Origin Header does not match
                        return false;
                    }
                }
            }
            // In theory path = appContextPath + servletPath + pathInfo. 
            int appContextPathIndex = appContextPath != null ? path.indexOf(appContextPath) : -1;
            int servletPathIndex = -1;
            int pathInfoIndex = -1;
            if (ectx.getRequestServletPath() != null && ectx.getRequestPathInfo() != null)
            {
                servletPathIndex = ectx.getRequestServletPath() != null ? 
                    path.indexOf(ectx.getRequestServletPath(),
                            Math.max(appContextPathIndex, 0)) : -1;
                if (servletPathIndex != -1)
                {
                    pathInfoIndex = servletPathIndex + ectx.getRequestServletPath().length();
                }
            }
            else
            {
                servletPathIndex = -1;
                pathInfoIndex = (Math.max(appContextPathIndex, 0)) + appContextPath.length();
            }

            // If match appContextPath(if any) and match servletPath or pathInfo referer header is ok
            if ((appContextPath == null || appContextPathIndex >= 0) && 
                (servletPathIndex >= 0 || pathInfoIndex >= 0))
            {
                String refererViewId;
                if (pathInfoIndex >= 0)
                {
                    refererViewId = path.substring(pathInfoIndex);
                }
                else
                {
                    refererViewId = path.substring(servletPathIndex);
                }
                
                String logicalViewId = viewHandler.deriveViewId(facesContext, refererViewId);
                    
                // If the header is present, use the protected view API to determine if any of
                // the declared protected views match the value of the Referer header.
                // - If so, conclude that the previously visited page is also a protected 
                //   view and it is therefore safe to continue.
                // - Otherwise, try to determine if the value of the Referer header corresponds 
                //   to any of the views in the current web application.
                // -= Leonardo Uribe =- All views that are protected also should exists!. the
                // only relevant check is use ViewHandler.deriveViewId(...) here. Check if the
                // view is protected here is not necessary.
                if (logicalViewId != null)
                {
                    return true;
                }
                else
                {
                    // View do not exists
                }
            }
            return true;
        }
        catch (URISyntaxException ex)
        {
            return false;
        }
    }
    
    /**
     * Invoke afterPhase MethodExpression of UIViewRoot.
     * Note: In this phase it is not possible to invoke the beforePhase method, because we
     * first have to restore the view to get its attributes. Also it is not really possible
     * to call the afterPhase method inside of UIViewRoot for this phase, thus it was decided
     * in the Faces 2.0 spec rev A to put this here.
     * @param facesContext
     */
    private void _invokeViewRootAfterPhaseListener(FacesContext facesContext)
    {
        // get the UIViewRoot (note that it must not be null at this point)
        UIViewRoot root = facesContext.getViewRoot();
        MethodExpression afterPhaseExpression = root.getAfterPhaseListener();
        if (afterPhaseExpression != null)
        {
            PhaseEvent event = new PhaseEvent(facesContext, getPhase(), _getLifecycle(facesContext));
            try
            {
                afterPhaseExpression.invoke(facesContext.getELContext(), new Object[] { event });
            }
            catch (Throwable t) 
            {
                log.log(Level.SEVERE, "An Exception occurred while processing " +
                        afterPhaseExpression.getExpressionString() + 
                        " in Phase " + getPhase(), t);
            }
        }
    }
    
    /**
     * Gets the current Lifecycle instance from the LifecycleFactory
     * @param facesContext
     * @return
     */
    private Lifecycle _getLifecycle(FacesContext facesContext)
    {
        LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
        String id = facesContext.getExternalContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
        if (id == null)
        {
            id = LifecycleFactory.DEFAULT_LIFECYCLE;
        }
        return factory.getLifecycle(id);  
    }

    protected RestoreViewSupport getRestoreViewSupport(FacesContext context)
    {
        if (_restoreViewSupport == null)
        {
            _restoreViewSupport = new RestoreViewSupport(context);
        }
        return _restoreViewSupport;
    }
    
    protected ViewIdSupport getViewIdSupport(FacesContext context)
    {
        if (_viewIdSupport == null)
        {
            _viewIdSupport = ViewIdSupport.getInstance(context);
        }
        return _viewIdSupport;
    }

    protected RenderKitFactory getRenderKitFactory()
    {
        if (_renderKitFactory == null)
        {
            _renderKitFactory = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
        }
        return _renderKitFactory;
    }
    

    public void setRestoreViewSupport(RestoreViewSupport restoreViewSupport)
    {
        _restoreViewSupport = restoreViewSupport;
    }
    
    public void setViewHandlerSupport(ViewIdSupport viewHandlerSupport)
    {
        _viewIdSupport = viewHandlerSupport;
    }

    @Override
    public PhaseId getPhase()
    {
        return PhaseId.RESTORE_VIEW;
    }
    
    protected boolean checkViewNotFound(FacesContext facesContext)
    {
        if (_viewNotFoundCheck == null)
        {
            
            _viewNotFoundCheck = MyfacesConfig.getCurrentInstance(facesContext).isStrictJsf2ViewNotFound();
        }
        return _viewNotFoundCheck;
    }
    
    private void sendSourceNotFound(FacesContext context, String message)
    {
        HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
        try
        {
            context.responseComplete();
            response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
        }
        catch (IOException ioe)
        {
            throw new FacesException(ioe);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy