View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.lifecycle;
20  
21  import java.io.IOException;
22  import java.net.URI;
23  import java.net.URISyntaxException;
24  import java.util.logging.Level;
25  import java.util.logging.Logger;
26  
27  import javax.el.MethodExpression;
28  import javax.faces.FacesException;
29  import javax.faces.FactoryFinder;
30  import javax.faces.application.Application;
31  import javax.faces.application.ProjectStage;
32  import javax.faces.application.ProtectedViewException;
33  import javax.faces.application.ViewExpiredException;
34  import javax.faces.application.ViewHandler;
35  import javax.faces.component.UIViewRoot;
36  import javax.faces.context.ExternalContext;
37  import javax.faces.context.FacesContext;
38  import javax.faces.event.PhaseEvent;
39  import javax.faces.event.PhaseId;
40  import javax.faces.event.PostAddToViewEvent;
41  import javax.faces.flow.FlowHandler;
42  import javax.faces.lifecycle.ClientWindow;
43  import javax.faces.lifecycle.Lifecycle;
44  import javax.faces.lifecycle.LifecycleFactory;
45  import javax.faces.render.RenderKit;
46  import javax.faces.render.RenderKitFactory;
47  import javax.faces.render.ResponseStateManager;
48  import javax.faces.view.ViewDeclarationLanguage;
49  import javax.faces.view.ViewMetadata;
50  import javax.faces.webapp.FacesServlet;
51  import javax.servlet.http.HttpServletResponse;
52  import org.apache.myfaces.event.PostClientWindowAndViewInitializedEvent;
53  
54  import org.apache.myfaces.renderkit.ErrorPageWriter;
55  import org.apache.myfaces.shared.config.MyfacesConfig;
56  import org.apache.myfaces.shared.util.ExternalContextUtils;
57  import org.apache.myfaces.shared.util.ViewProtectionUtils;
58  
59  /**
60   * Implements the Restore View Phase (JSF Spec 2.2.1)
61   * 
62   * @author Nikolay Petrov (latest modification by $Author$)
63   * @author Bruno Aranda (JSF 1.2)
64   * @version $Revision$ $Date$
65   */
66  class RestoreViewExecutor extends PhaseExecutor
67  {
68      private static final Logger log = Logger.getLogger(RestoreViewExecutor.class.getName());
69      
70      private RestoreViewSupport _restoreViewSupport;
71      
72      private Boolean _viewNotFoundCheck;
73      
74      private RenderKitFactory _renderKitFactory = null;
75      
76      @Override
77      public void doPrePhaseActions(FacesContext facesContext)
78      {
79          // Call initView() on the ViewHandler. 
80          // This will set the character encoding properly for this request.
81          // Note that we are doing this here, because we need the character encoding
82          // to be set as early as possible (before any PhaseListener is executed).
83          facesContext.getApplication().getViewHandler().initView(facesContext);
84      }
85  
86      public boolean execute(FacesContext facesContext)
87      {
88          if (facesContext == null)
89          {
90              throw new FacesException("FacesContext is null");
91          }
92  
93          // get some required Objects
94          Application application = facesContext.getApplication();
95          ViewHandler viewHandler = application.getViewHandler();
96          UIViewRoot viewRoot = facesContext.getViewRoot();
97          RestoreViewSupport restoreViewSupport = getRestoreViewSupport(facesContext);
98  
99          // Examine the FacesContext instance for the current request. If it already contains a UIViewRoot
100         if (viewRoot != null)
101         {
102             if (log.isLoggable(Level.FINEST))
103             {
104                 log.finest("View already exists in the FacesContext");
105             }
106             
107             // Set the locale on this UIViewRoot to the value returned by the getRequestLocale() method on the
108             // ExternalContext for this request
109             viewRoot.setLocale(facesContext.getExternalContext().getRequestLocale());
110             
111             restoreViewSupport.processComponentBinding(facesContext, viewRoot);
112             
113             // invoke the afterPhase MethodExpression of UIViewRoot
114             _invokeViewRootAfterPhaseListener(facesContext);
115             
116             return false;
117         }
118         
119         String viewId = restoreViewSupport.calculateViewId(facesContext);
120 
121         // Determine if the current request is an attempt by the 
122         // servlet container to display an error page.
123         // If the request is an error page request, the servlet container
124         // is required to set the request parameter "javax.servlet.error.message".
125         final boolean errorPageRequest = facesContext.getExternalContext().getRequestMap()
126                                                  .get("javax.servlet.error.message") != null;
127         
128         // Determine if this request is a postback or an initial request.
129         // But if it is an error page request, do not treat it as a postback (since 2.0)
130         if (!errorPageRequest && restoreViewSupport.isPostback(facesContext))
131         { // If the request is a postback
132             if (log.isLoggable(Level.FINEST))
133             {
134                 log.finest("Request is a postback");
135             }
136 
137             if (checkViewNotFound(facesContext))
138             {
139                 String derivedViewId = viewHandler.deriveLogicalViewId(facesContext, viewId);
140                 ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, 
141                     derivedViewId);
142             
143                 // viewHandler.deriveLogicalViewId() could trigger an InvalidViewIdException, which
144                 // it is handled internally sending a 404 error code set the response as complete.
145                 if (facesContext.getResponseComplete())
146                 {
147                     return true;
148                 }
149             
150                 if (vdl == null || derivedViewId == null)
151                 {
152                     sendSourceNotFound(facesContext, viewId);
153                     return true;
154                 }
155                 else if (!restoreViewSupport.checkViewExists(facesContext, derivedViewId))
156                 {
157                     sendSourceNotFound(facesContext, viewId);
158                     return true;
159                 }
160             }
161             
162             try
163             {
164                 facesContext.setProcessingEvents(false);
165                 // call ViewHandler.restoreView(), passing the FacesContext instance for the current request and the 
166                 // view identifier, and returning a UIViewRoot for the restored view.
167                 
168                 viewRoot = viewHandler.restoreView(facesContext, viewId);
169                 if (viewRoot == null)
170                 {
171                     if (facesContext.getResponseComplete())
172                     {
173                         // If the view handler cannot restore the view and the response
174                         // is complete, it can be an error or some logic in restoreView.
175                         return true;
176                     }
177                     else
178                     {
179                         // If the return from ViewHandler.restoreView() is null, throw a ViewExpiredException with an 
180                         // appropriate error message.
181                         throw new ViewExpiredException("View \"" + viewId + "\" could not be restored.", viewId);
182                     }
183                 }
184                 // If the view is transient (stateless), it is necessary to check the view protection
185                 // in POST case.
186                 if (viewRoot.isTransient())
187                 {
188                     checkViewProtection(facesContext, viewHandler, viewRoot.getViewId(), viewRoot);
189                 }
190                 
191                 // Store the restored UIViewRoot in the FacesContext.
192                 facesContext.setViewRoot(viewRoot);
193             }
194             finally
195             {
196                 facesContext.setProcessingEvents(true);
197             }
198             
199             // Restore binding
200             // See https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=806
201             restoreViewSupport.processComponentBinding(facesContext, viewRoot);
202             
203             ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
204             if (clientWindow != null)
205             {
206                 // The idea of this event is once the client window and the view is initialized, 
207                 // you have the information required to clean up any scope that belongs to old
208                 // clientWindow instances like flow scope (in server side state saving).
209                 facesContext.getApplication().publishEvent(facesContext, PostClientWindowAndViewInitializedEvent.class, 
210                         clientWindow);
211             }
212         }
213         else
214         { // If the request is a non-postback
215             if (log.isLoggable(Level.FINEST))
216             {
217                 log.finest("Request is not a postback. New UIViewRoot will be created");
218             }
219             
220             //viewHandler.deriveViewId(facesContext, viewId)
221             //restoreViewSupport.deriveViewId(facesContext, viewId)
222             String logicalViewId = viewHandler.deriveLogicalViewId(facesContext, viewId);
223             ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, logicalViewId);
224             
225             // viewHandler.deriveLogicalViewId() could trigger an InvalidViewIdException, which
226             // it is handled internally sending a 404 error code set the response as complete.
227             if (facesContext.getResponseComplete())
228             {
229                 return true;
230             }
231             
232             if (checkViewNotFound(facesContext))
233             {
234                 if (vdl == null || logicalViewId == null)
235                 {
236                     sendSourceNotFound(facesContext, viewId);
237                     return true;
238                 }
239                 else if (!restoreViewSupport.checkViewExists(facesContext, logicalViewId))
240                 {
241                     sendSourceNotFound(facesContext, viewId);
242                     return true;
243                 }
244             }
245             
246             if (vdl != null)
247             {
248                 ViewMetadata metadata = vdl.getViewMetadata(facesContext, viewId);
249                 
250                 if (metadata != null)
251                 {
252                     viewRoot = metadata.createMetadataView(facesContext);
253                     
254                     if(facesContext.getResponseComplete())
255                     {
256                         // this can happen if the current request is a debug request,
257                         // in this case no further processing is necessary
258                         return true;
259                     }
260                 }
261     
262                 if (viewRoot == null)
263                 {
264                     facesContext.renderResponse();
265                 }
266                 else if (viewRoot != null && !ViewMetadata.hasMetadata(viewRoot))
267                 {
268                     facesContext.renderResponse();
269                 }
270             }
271             else
272             {
273                 // Call renderResponse
274                 facesContext.renderResponse();
275             }
276 
277             checkViewProtection(facesContext, viewHandler, logicalViewId, viewRoot);
278             
279             // viewRoot can be null here, if ...
280             //   - we don't have a ViewDeclarationLanguage (e.g. when using facelets-1.x)
281             //   - there is no view metadata or metadata.createMetadataView() returned null
282             if (viewRoot == null)
283             {
284                 // call ViewHandler.createView(), passing the FacesContext instance for the current request and 
285                 // the view identifier
286                 viewRoot = viewHandler.createView(facesContext, viewId);
287             }
288             
289             if (viewRoot == null && facesContext.getResponseComplete())
290             {
291                 // If the view handler cannot create the view and the response
292                 // is complete, it can be an error, just get out of the algorithm.
293                 return true;
294             }
295             
296             // Subscribe the newly created UIViewRoot instance to the AfterAddToParent event, passing the 
297             // UIViewRoot instance itself as the listener.
298             // -= Leonardo Uribe =- This line it is not necessary because it was
299             // removed from jsf 2.0 section 2.2.1 when pass from EDR2 to Public Review 
300             // viewRoot.subscribeToEvent(PostAddToViewEvent.class, viewRoot);
301             
302             // Store the new UIViewRoot instance in the FacesContext.
303             facesContext.setViewRoot(viewRoot);
304 
305             ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
306             if (clientWindow != null)
307             {
308                 facesContext.getApplication().publishEvent(facesContext, PostClientWindowAndViewInitializedEvent.class, 
309                         clientWindow);
310             }
311             
312             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
313             if (flowHandler != null)
314             {
315                 flowHandler.clientWindowTransition(facesContext);
316             }
317 
318             // Publish an AfterAddToParent event with the created UIViewRoot as the event source.
319             application.publishEvent(facesContext, PostAddToViewEvent.class, viewRoot);
320         }
321 
322         // add the ErrorPageBean to the view map to fully support 
323         // facelet error pages, if we are in ProjectStage Development
324         // and currently generating an error page
325         if (errorPageRequest && facesContext.isProjectStage(ProjectStage.Development))
326         {
327             facesContext.getViewRoot().getViewMap()
328                     .put(ErrorPageWriter.ERROR_PAGE_BEAN_KEY, new ErrorPageWriter.ErrorPageBean());
329         }
330         
331         // invoke the afterPhase MethodExpression of UIViewRoot
332         _invokeViewRootAfterPhaseListener(facesContext);
333         
334         return false;
335     }
336     
337     private void checkViewProtection(FacesContext facesContext, ViewHandler viewHandler, 
338         String viewId, UIViewRoot root) throws ProtectedViewException
339     {
340         boolean valid = true;
341         if (ViewProtectionUtils.isViewProtected(facesContext, viewId))
342         {
343             // "... Obtain the value of the value of the request parameter whose 
344             // name is given by the value of ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM. 
345             // If there is no value, throw ProtectedViewException ..."
346             String token = (String) facesContext.getExternalContext().getRequestParameterMap().get(
347                 ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM);
348             if (token != null && token.length() > 0)
349             {
350                 String renderKitId = null;
351                 if (root != null)
352                 {
353                     renderKitId = root.getRenderKitId();
354                 }
355                 if (renderKitId == null)
356                 {
357                     renderKitId = viewHandler.calculateRenderKitId(facesContext);
358                 }
359                 RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
360                 ResponseStateManager rsm = renderKit.getResponseStateManager();
361                 
362                 String storedToken = rsm.getCryptographicallyStrongTokenFromSession(facesContext);
363                 if (token.equals(storedToken))
364                 {
365                     if (!ExternalContextUtils.isPortlet(facesContext.getExternalContext()))
366                     {
367                         // Any check beyond this point only has sense for servlet requests.
368                         String referer = facesContext.getExternalContext().
369                             getRequestHeaderMap().get("Referer");
370                         if (referer != null)
371                         {
372                             valid = valid && checkRefererOrOriginHeader(
373                                 facesContext, viewHandler, referer);
374                         }
375                         String origin = facesContext.getExternalContext().
376                             getRequestHeaderMap().get("Origin");
377                         if (valid && origin != null)
378                         {
379                             valid = valid && checkRefererOrOriginHeader(
380                                 facesContext, viewHandler, origin);
381                         }
382                     }
383                 }
384                 else
385                 {
386                     valid = false;
387                 }
388             }
389             else
390             {
391                 valid = false;
392             }
393         }
394         if (!valid)
395         {
396             throw new ProtectedViewException();
397         }
398     }
399     
400     private boolean checkRefererOrOriginHeader(FacesContext facesContext, 
401         ViewHandler viewHandler, String refererOrOrigin)
402     {
403         try
404         {
405             // The referer can be absolute or relative. 
406             ExternalContext ectx = facesContext.getExternalContext();
407             URI refererURI = new URI(refererOrOrigin);
408             String path = refererURI.getPath();
409             String appContextPath = ectx.getApplicationContextPath();
410             if (refererURI.isAbsolute())
411             {
412                 // Check if the referer comes from the same host
413                 String host = refererURI.getHost();
414                 int port = refererURI.getPort();
415                 String serverHost = ectx.getRequestServerName();
416                 int serverPort = ectx.getRequestServerPort();
417 
418                 boolean matchPort = true;
419                 if (serverPort != -1 && port != -1)
420                 {
421                     matchPort = (serverPort == port);
422                 }
423                 boolean isStrictJsf2OriginHeaderAppPath = 
424                                 MyfacesConfig.getCurrentInstance(ectx).isStrictJsf2OriginHeaderAppPath();
425                 if (!path.equals(""))
426                 {
427                     if (serverHost.equals(host) && matchPort && path.contains(appContextPath))
428                     {
429                         // Referer Header match
430                     }
431                     else
432                     {
433                         // Referer Header does not match
434                         return false;
435                     }
436                 }
437                 else
438                 {
439                     if (serverHost.equals(host) && matchPort && !isStrictJsf2OriginHeaderAppPath)
440                     {
441                         // Origin Header match and 
442                         // STRICT_JSF_2_ORIGIN_HEADER_APP_PATH property is set to false (default)
443                         // Because we don't want to strictly follow JSF 2.x spec
444                     }
445                     else
446                     {
447                         // Origin Header does not match
448                         return false;
449                     }
450                 }
451             }
452             // In theory path = appContextPath + servletPath + pathInfo. 
453             int appContextPathIndex = appContextPath != null ? path.indexOf(appContextPath) : -1;
454             int servletPathIndex = -1;
455             int pathInfoIndex = -1;
456             if (ectx.getRequestServletPath() != null && ectx.getRequestPathInfo() != null)
457             {
458                 servletPathIndex = ectx.getRequestServletPath() != null ? 
459                     path.indexOf(ectx.getRequestServletPath(), 
460                                  appContextPathIndex >= 0 ? appContextPathIndex : 0) : -1;
461                 if (servletPathIndex != -1)
462                 {
463                     pathInfoIndex = servletPathIndex + ectx.getRequestServletPath().length();
464                 }
465             }
466             else
467             {
468                 servletPathIndex = -1;
469                 pathInfoIndex = (appContextPathIndex >= 0 ? appContextPathIndex : 0) + appContextPath.length();
470             }
471 
472             // If match appContextPath(if any) and match servletPath or pathInfo referer header is ok
473             if ((appContextPath == null || appContextPathIndex >= 0) && 
474                 (servletPathIndex >= 0 || pathInfoIndex >= 0))
475             {
476                 String refererViewId;
477                 if (pathInfoIndex >= 0)
478                 {
479                     refererViewId = path.substring(pathInfoIndex);
480                 }
481                 else
482                 {
483                     refererViewId = path.substring(servletPathIndex);
484                 }
485                 
486                 String logicalViewId = viewHandler.deriveViewId(facesContext, refererViewId);
487                     
488                 // If the header is present, use the protected view API to determine if any of
489                 // the declared protected views match the value of the Referer header.
490                 // - If so, conclude that the previously visited page is also a protected 
491                 //   view and it is therefore safe to continue.
492                 // - Otherwise, try to determine if the value of the Referer header corresponds 
493                 //   to any of the views in the current web application.
494                 // -= Leonardo Uribe =- All views that are protected also should exists!. the
495                 // only relevant check is use ViewHandler.deriveViewId(...) here. Check if the
496                 // view is protected here is not necessary.
497                 if (logicalViewId != null)
498                 {
499                     return true;
500                 }
501                 else
502                 {
503                     // View do not exists
504                 }
505             }
506             return true;
507         }
508         catch (URISyntaxException ex)
509         {
510             return false;
511         }
512     }
513     
514     /**
515      * Invoke afterPhase MethodExpression of UIViewRoot.
516      * Note: In this phase it is not possible to invoke the beforePhase method, because we
517      * first have to restore the view to get its attributes. Also it is not really possible
518      * to call the afterPhase method inside of UIViewRoot for this phase, thus it was decided
519      * in the JSF 2.0 spec rev A to put this here.
520      * @param facesContext
521      */
522     private void _invokeViewRootAfterPhaseListener(FacesContext facesContext)
523     {
524         // get the UIViewRoot (note that it must not be null at this point)
525         UIViewRoot root = facesContext.getViewRoot();
526         MethodExpression afterPhaseExpression = root.getAfterPhaseListener();
527         if (afterPhaseExpression != null)
528         {
529             PhaseEvent event = new PhaseEvent(facesContext, getPhase(), _getLifecycle(facesContext));
530             try
531             {
532                 afterPhaseExpression.invoke(facesContext.getELContext(), new Object[] { event });
533             }
534             catch (Throwable t) 
535             {
536                 log.log(Level.SEVERE, "An Exception occured while processing " +
537                         afterPhaseExpression.getExpressionString() + 
538                         " in Phase " + getPhase(), t);
539             }
540         }
541     }
542     
543     /**
544      * Gets the current Lifecycle instance from the LifecycleFactory
545      * @param facesContext
546      * @return
547      */
548     private Lifecycle _getLifecycle(FacesContext facesContext)
549     {
550         LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
551         String id = facesContext.getExternalContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
552         if (id == null)
553         {
554             id = LifecycleFactory.DEFAULT_LIFECYCLE;
555         }
556         return factory.getLifecycle(id);  
557     }
558     
559     protected RestoreViewSupport getRestoreViewSupport()
560     {
561         return getRestoreViewSupport(FacesContext.getCurrentInstance());
562     }
563     
564     protected RestoreViewSupport getRestoreViewSupport(FacesContext context)
565     {
566         if (_restoreViewSupport == null)
567         {
568             _restoreViewSupport = new DefaultRestoreViewSupport(context);
569         }
570         return _restoreViewSupport;
571     }
572 
573     protected RenderKitFactory getRenderKitFactory()
574     {
575         if (_renderKitFactory == null)
576         {
577             _renderKitFactory = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
578         }
579         return _renderKitFactory;
580     }
581     
582     /**
583      * @param restoreViewSupport
584      *            the restoreViewSupport to set
585      */
586     public void setRestoreViewSupport(RestoreViewSupport restoreViewSupport)
587     {
588         _restoreViewSupport = restoreViewSupport;
589     }
590 
591     public PhaseId getPhase()
592     {
593         return PhaseId.RESTORE_VIEW;
594     }
595     
596     protected boolean checkViewNotFound(FacesContext facesContext)
597     {
598         if (_viewNotFoundCheck == null)
599         {
600             
601             _viewNotFoundCheck = MyfacesConfig.getCurrentInstance(
602                 facesContext.getExternalContext()).isStrictJsf2ViewNotFound();
603         }
604         return _viewNotFoundCheck;
605     }
606     
607     private void sendSourceNotFound(FacesContext context, String message)
608     {
609         HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
610         try
611         {
612             context.responseComplete();
613             response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
614         }
615         catch (IOException ioe)
616         {
617             throw new FacesException(ioe);
618         }
619     }
620 }