1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portal.struts;
24  
25  import com.liferay.portal.kernel.log.Log;
26  import com.liferay.portal.kernel.log.LogFactoryUtil;
27  import com.liferay.portal.kernel.portlet.LiferayPortletURL;
28  import com.liferay.portal.kernel.util.JavaConstants;
29  import com.liferay.portal.kernel.util.StringPool;
30  import com.liferay.portal.kernel.util.Validator;
31  import com.liferay.portal.model.Layout;
32  import com.liferay.portal.model.Portlet;
33  import com.liferay.portal.model.User;
34  import com.liferay.portal.security.auth.PrincipalException;
35  import com.liferay.portal.security.permission.ActionKeys;
36  import com.liferay.portal.security.permission.PermissionChecker;
37  import com.liferay.portal.service.PortletLocalServiceUtil;
38  import com.liferay.portal.service.permission.PortletPermissionUtil;
39  import com.liferay.portal.theme.ThemeDisplay;
40  import com.liferay.portal.util.PortalUtil;
41  import com.liferay.portal.util.PropsValues;
42  import com.liferay.portal.util.WebKeys;
43  import com.liferay.portlet.ActionResponseImpl;
44  import com.liferay.portlet.PortletConfigImpl;
45  import com.liferay.portlet.PortletRequestDispatcherImpl;
46  
47  import java.io.IOException;
48  
49  import java.lang.reflect.Constructor;
50  
51  import javax.portlet.ActionRequest;
52  import javax.portlet.ActionResponse;
53  import javax.portlet.PortletContext;
54  import javax.portlet.PortletException;
55  import javax.portlet.PortletRequest;
56  import javax.portlet.PortletResponse;
57  import javax.portlet.RenderRequest;
58  import javax.portlet.RenderResponse;
59  import javax.portlet.ResourceRequest;
60  import javax.portlet.ResourceResponse;
61  
62  import javax.servlet.ServletException;
63  import javax.servlet.http.HttpServletRequest;
64  import javax.servlet.http.HttpServletResponse;
65  
66  import org.apache.struts.Globals;
67  import org.apache.struts.action.Action;
68  import org.apache.struts.action.ActionErrors;
69  import org.apache.struts.action.ActionForm;
70  import org.apache.struts.action.ActionForward;
71  import org.apache.struts.action.ActionMapping;
72  import org.apache.struts.action.ActionServlet;
73  import org.apache.struts.config.ForwardConfig;
74  import org.apache.struts.config.ModuleConfig;
75  import org.apache.struts.tiles.TilesRequestProcessor;
76  
77  /**
78   * <a href="PortletRequestProcessor.java.html"><b><i>View Source</i></b></a>
79   *
80   * @author Brian Wing Shun Chan
81   *
82   */
83  public class PortletRequestProcessor extends TilesRequestProcessor {
84  
85      public static PortletRequestProcessor getInstance(
86              ActionServlet servlet, ModuleConfig moduleConfig)
87          throws ServletException {
88  
89          try {
90              String className = PropsValues.STRUTS_PORTLET_REQUEST_PROCESSOR;
91  
92              Class<?> clazz = Class.forName(className);
93  
94              Constructor<?> constructor = clazz.getConstructor(
95                  new Class[] {
96                      ActionServlet.class, ModuleConfig.class
97                  }
98              );
99  
100             PortletRequestProcessor portletReqProcessor =
101                 (PortletRequestProcessor)constructor.newInstance(
102                     new Object[] {
103                         servlet, moduleConfig
104                     }
105                 );
106 
107             return portletReqProcessor;
108         }
109         catch (Exception e) {
110             _log.error(e);
111 
112             return new PortletRequestProcessor(servlet, moduleConfig);
113         }
114     }
115 
116     public PortletRequestProcessor(
117             ActionServlet actionServlet, ModuleConfig moduleConfig)
118         throws ServletException {
119 
120         init(actionServlet, moduleConfig);
121     }
122 
123     public void process(
124             ActionRequest actionRequest, ActionResponse actionResponse,
125             String path)
126         throws IOException, ServletException {
127 
128         ActionResponseImpl actionResponseImpl =
129             (ActionResponseImpl)actionResponse;
130 
131         HttpServletRequest request = PortalUtil.getHttpServletRequest(
132             actionRequest);
133         HttpServletResponse response = PortalUtil.getHttpServletResponse(
134             actionResponse);
135 
136         ActionMapping mapping = processMapping(request, response, path);
137 
138         if (mapping == null) {
139             return;
140         }
141 
142         if (!processRoles(request, response, mapping, true)) {
143             return;
144         }
145 
146         ActionForm form = processActionForm(request, response, mapping);
147 
148         processPopulate(request, response, form, mapping);
149 
150         if (!processValidateAction(request, response, form, mapping)) {
151             return;
152         }
153 
154         PortletAction action =
155             (PortletAction)processActionCreate(request, response, mapping);
156 
157         if (action == null) {
158             return;
159         }
160 
161         PortletConfigImpl portletConfigImpl =
162             (PortletConfigImpl)actionRequest.getAttribute(
163                 JavaConstants.JAVAX_PORTLET_CONFIG);
164 
165         try {
166             if (action.isCheckMethodOnProcessAction()) {
167                 if (!PortalUtil.isMethodPost(actionRequest)) {
168                     String currentURL = PortalUtil.getCurrentURL(actionRequest);
169 
170                     if (_log.isWarnEnabled()) {
171                         _log.warn(
172                             "This URL can only be invoked using POST: " +
173                                 currentURL);
174                     }
175 
176                     throw new PrincipalException(currentURL);
177                 }
178             }
179 
180             action.processAction(
181                 mapping, form, portletConfigImpl, actionRequest,
182                 actionResponse);
183         }
184         catch (Exception e) {
185             String exceptionId =
186                 WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
187                     portletConfigImpl.getPortletId();
188 
189             actionRequest.setAttribute(exceptionId, e);
190         }
191 
192         String forward = (String)actionRequest.getAttribute(
193             PortletAction.getForwardKey(actionRequest));
194 
195         if (forward != null) {
196             String queryString = StringPool.BLANK;
197 
198             int pos = forward.indexOf(StringPool.QUESTION);
199 
200             if (pos != -1) {
201                 queryString = forward.substring(pos + 1, forward.length());
202                 forward = forward.substring(0, pos);
203             }
204 
205             ActionForward actionForward = mapping.findForward(forward);
206 
207             if ((actionForward != null) && (actionForward.getRedirect())) {
208                 String forwardPath = actionForward.getPath();
209 
210                 if (forwardPath.startsWith(StringPool.SLASH)) {
211                     LiferayPortletURL forwardURL =
212                         (LiferayPortletURL)actionResponseImpl.createRenderURL();
213 
214                     forwardURL.setParameter("struts_action", forwardPath);
215 
216                     StrutsURLEncoder.setParameters(forwardURL, queryString);
217 
218                     forwardPath = forwardURL.toString();
219                 }
220 
221                 actionResponse.sendRedirect(forwardPath);
222             }
223         }
224     }
225 
226     public void process(
227             RenderRequest renderRequest, RenderResponse renderResponse)
228         throws IOException, ServletException {
229 
230         HttpServletRequest request = PortalUtil.getHttpServletRequest(
231             renderRequest);
232         HttpServletResponse response = PortalUtil.getHttpServletResponse(
233             renderResponse);
234 
235         process(request, response);
236     }
237 
238     public void process(
239             ResourceRequest resourceRequest, ResourceResponse resourceResponse)
240         throws IOException, ServletException {
241 
242         HttpServletRequest request = PortalUtil.getHttpServletRequest(
243             resourceRequest);
244         HttpServletResponse response = PortalUtil.getHttpServletResponse(
245             resourceResponse);
246 
247         process(request, response);
248     }
249 
250     protected void doForward(
251             String uri, HttpServletRequest request,
252             HttpServletResponse response)
253         throws IOException, ServletException {
254 
255         doInclude(uri, request, response);
256     }
257 
258     protected void doInclude(
259             String uri, HttpServletRequest request,
260             HttpServletResponse response)
261         throws IOException, ServletException {
262 
263         PortletConfigImpl portletConfig =
264             (PortletConfigImpl)request.getAttribute(
265                 JavaConstants.JAVAX_PORTLET_CONFIG);
266 
267         PortletContext portletContext = portletConfig.getPortletContext();
268 
269         PortletRequest portletRequest = (PortletRequest)request.getAttribute(
270             JavaConstants.JAVAX_PORTLET_REQUEST);
271 
272         PortletResponse portletResponse = (PortletResponse)request.getAttribute(
273             JavaConstants.JAVAX_PORTLET_RESPONSE);
274 
275         PortletRequestDispatcherImpl portletRequestDispatcher =
276             (PortletRequestDispatcherImpl)portletContext.getRequestDispatcher(
277                 StrutsUtil.TEXT_HTML_DIR + uri);
278 
279         try {
280             if (portletRequestDispatcher == null) {
281                 _log.error(uri + " is not a valid include");
282             }
283             else {
284                 portletRequestDispatcher.include(
285                     portletRequest, portletResponse, true);
286             }
287         }
288         catch (PortletException pe) {
289             Throwable cause = pe.getCause();
290 
291             if (cause instanceof ServletException) {
292                 throw (ServletException)cause;
293             }
294             else {
295                 _log.error(cause, cause);
296             }
297         }
298     }
299 
300     protected ActionForm processActionForm(
301         HttpServletRequest request, HttpServletResponse response,
302         ActionMapping mapping) {
303 
304         ActionForm form = super.processActionForm(request, response, mapping);
305 
306         if (form instanceof InitializableActionForm) {
307             InitializableActionForm initForm = (InitializableActionForm)form;
308 
309             initForm.init(request, response, mapping);
310         }
311 
312         return form;
313     }
314 
315     protected ActionForward processActionPerform(
316             HttpServletRequest request, HttpServletResponse response,
317             Action action, ActionForm form, ActionMapping mapping)
318         throws IOException, ServletException {
319 
320         PortletConfigImpl portletConfig =
321             (PortletConfigImpl)request.getAttribute(
322                 JavaConstants.JAVAX_PORTLET_CONFIG);
323 
324         String exceptionId =
325             WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
326                 portletConfig.getPortletId();
327 
328         Exception e = (Exception)request.getAttribute(exceptionId);
329 
330         if (e != null) {
331             return processException(request, response, e, form, mapping);
332         }
333         else {
334             return super.processActionPerform(
335                 request, response, action, form, mapping);
336         }
337     }
338 
339     protected void processForwardConfig(
340             HttpServletRequest request, HttpServletResponse response,
341             ForwardConfig forward)
342         throws IOException, ServletException {
343 
344         if (forward == null) {
345             _log.error("Forward does not exist");
346         }
347         else {
348 
349             // Don't render a null path. This is useful if you're sending a file
350             // in an exclusive window state.
351 
352             if (forward.getPath().equals(ActionConstants.COMMON_NULL)) {
353                 return;
354             }
355         }
356 
357         super.processForwardConfig(request, response, forward);
358     }
359 
360     public ActionMapping processMapping(
361             HttpServletRequest request, HttpServletResponse response,
362             String path)
363         throws IOException {
364 
365         if (path == null) {
366             return null;
367         }
368 
369         ActionMapping mapping = super.processMapping(request, response, path);
370 
371         if (mapping == null) {
372             String msg = getInternal().getMessage("processInvalid");
373 
374             _log.error("User ID " + request.getRemoteUser());
375             _log.error("Current URL " + PortalUtil.getCurrentURL(request));
376             _log.error("Referer " + request.getHeader("Referer"));
377             _log.error("Remote address " + request.getRemoteAddr());
378 
379             _log.error(msg + " " + path);
380         }
381 
382         return mapping;
383     }
384 
385     protected HttpServletRequest processMultipart(HttpServletRequest request) {
386 
387         // Disable Struts from automatically wrapping a multipart request
388 
389         return request;
390     }
391 
392     protected String processPath(
393         HttpServletRequest request, HttpServletResponse response) {
394 
395         String path = request.getParameter("struts_action");
396 
397         if (_log.isDebugEnabled()) {
398             _log.debug("Getting request parameter path " + path);
399         }
400 
401         if (Validator.isNull(path)) {
402             if (_log.isDebugEnabled()) {
403                 _log.debug("Getting request attribute path " + path);
404             }
405 
406             path = (String)request.getAttribute(WebKeys.PORTLET_STRUTS_ACTION);
407         }
408 
409         if (path == null) {
410             PortletConfigImpl portletConfig =
411                 (PortletConfigImpl)request.getAttribute(
412                     JavaConstants.JAVAX_PORTLET_CONFIG);
413 
414             _log.error(
415                 portletConfig.getPortletName() +
416                     " does not have any paths specified");
417         }
418         else {
419             if (_log.isDebugEnabled()) {
420                 _log.debug("Processing path " + path);
421             }
422         }
423 
424         return path;
425     }
426 
427     protected boolean processRoles(
428             HttpServletRequest request, HttpServletResponse response,
429             ActionMapping mapping)
430         throws IOException, ServletException {
431 
432         return processRoles(request, response, mapping, false);
433     }
434 
435     protected boolean processRoles(
436             HttpServletRequest request, HttpServletResponse response,
437             ActionMapping mapping, boolean action)
438         throws IOException, ServletException {
439 
440         User user = null;
441 
442         try {
443             user = PortalUtil.getUser(request);
444         }
445         catch (Exception e) {
446         }
447 
448         if (user == null) {
449             return true;
450         }
451 
452         String path = mapping.getPath();
453 
454         try {
455             PortletConfigImpl portletConfig =
456                 (PortletConfigImpl)request.getAttribute(
457                     JavaConstants.JAVAX_PORTLET_CONFIG);
458 
459             Portlet portlet = PortletLocalServiceUtil.getPortletById(
460                 user.getCompanyId(), portletConfig.getPortletId());
461 
462             if (portlet == null) {
463                 return false;
464             }
465 
466             String strutsPath = path.substring(
467                 1, path.lastIndexOf(StringPool.SLASH));
468 
469             if (!strutsPath.equals(portlet.getStrutsPath())) {
470                 if (_log.isWarnEnabled()) {
471                     _log.warn(
472                         "The struts path " + strutsPath + " does not belong " +
473                             "to portlet " + portlet.getPortletId() + ". " +
474                                 "Check the definition in liferay-portlet.xml");
475                 }
476 
477                 throw new PrincipalException();
478             }
479             else if (portlet.isActive()) {
480                 ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
481                     WebKeys.THEME_DISPLAY);
482 
483                 Layout layout = themeDisplay.getLayout();
484                 PermissionChecker permissionChecker =
485                     themeDisplay.getPermissionChecker();
486 
487                 if (!PortletPermissionUtil.contains(
488                         permissionChecker, layout.getPlid(), portlet,
489                         ActionKeys.VIEW)) {
490 
491                     throw new PrincipalException();
492                 }
493             }
494             else if (!portlet.isActive()) {
495                 ForwardConfig forwardConfig =
496                     mapping.findForward(_PATH_PORTAL_PORTLET_INACTIVE);
497 
498                 if (!action) {
499                     processForwardConfig(request, response, forwardConfig);
500                 }
501 
502                 return false;
503             }
504         }
505         catch (Exception e) {
506             if (_log.isWarnEnabled()) {
507                 _log.warn(e.getMessage());
508             }
509 
510             ForwardConfig forwardConfig =
511                 mapping.findForward(_PATH_PORTAL_PORTLET_ACCESS_DENIED);
512 
513             if (!action) {
514                 processForwardConfig(request, response, forwardConfig);
515             }
516 
517             return false;
518         }
519 
520         return true;
521     }
522 
523     protected boolean processValidateAction(
524         HttpServletRequest request, HttpServletResponse response,
525         ActionForm form, ActionMapping mapping) {
526 
527         if (form == null) {
528             return true;
529         }
530 
531         if (request.getAttribute(Globals.CANCEL_KEY) != null) {
532             return true;
533         }
534 
535         if (!mapping.getValidate()) {
536             return true;
537         }
538 
539         ActionErrors errors = form.validate(mapping, request);
540 
541         if ((errors == null) || errors.isEmpty()) {
542             return true;
543         }
544 
545         if (form.getMultipartRequestHandler() != null) {
546             form.getMultipartRequestHandler().rollback();
547         }
548 
549         String input = mapping.getInput();
550 
551         if (input == null) {
552             _log.error("Validation failed but no input form is available");
553 
554             return false;
555         }
556 
557         request.setAttribute(Globals.ERROR_KEY, errors);
558 
559         // Struts normally calls internalModuleRelativeForward which breaks
560         // if called inside processAction
561 
562         request.setAttribute(PortletAction.getForwardKey(request), input);
563 
564         return false;
565     }
566 
567     private static final String _PATH_PORTAL_PORTLET_ACCESS_DENIED =
568         "/portal/portlet_access_denied";
569 
570     private static final String _PATH_PORTAL_PORTLET_INACTIVE =
571         "/portal/portlet_inactive";
572 
573     private static Log _log =
574          LogFactoryUtil.getLog(PortletRequestProcessor.class);
575 
576 }