1   /**
2    * Copyright (c) 2000-2008 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.portlet.LiferayPortletURL;
26  import com.liferay.portal.kernel.security.permission.ActionKeys;
27  import com.liferay.portal.kernel.security.permission.PermissionChecker;
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.service.PortletLocalServiceUtil;
36  import com.liferay.portal.service.permission.PortletPermissionUtil;
37  import com.liferay.portal.theme.ThemeDisplay;
38  import com.liferay.portal.util.PortalUtil;
39  import com.liferay.portal.util.PropsValues;
40  import com.liferay.portal.util.WebKeys;
41  import com.liferay.portlet.ActionResponseImpl;
42  import com.liferay.portlet.PortletConfigImpl;
43  import com.liferay.portlet.PortletRequestDispatcherImpl;
44  
45  import java.io.IOException;
46  
47  import java.lang.reflect.Constructor;
48  
49  import javax.portlet.ActionRequest;
50  import javax.portlet.ActionResponse;
51  import javax.portlet.PortletException;
52  import javax.portlet.RenderRequest;
53  import javax.portlet.RenderResponse;
54  
55  import javax.servlet.ServletException;
56  import javax.servlet.http.HttpServletRequest;
57  import javax.servlet.http.HttpServletResponse;
58  
59  import org.apache.commons.logging.Log;
60  import org.apache.commons.logging.LogFactory;
61  import org.apache.struts.Globals;
62  import org.apache.struts.action.Action;
63  import org.apache.struts.action.ActionErrors;
64  import org.apache.struts.action.ActionForm;
65  import org.apache.struts.action.ActionForward;
66  import org.apache.struts.action.ActionMapping;
67  import org.apache.struts.action.ActionServlet;
68  import org.apache.struts.config.ForwardConfig;
69  import org.apache.struts.config.ModuleConfig;
70  import org.apache.struts.tiles.TilesRequestProcessor;
71  
72  /**
73   * <a href="PortletRequestProcessor.java.html"><b><i>View Source</i></b></a>
74   *
75   * @author Brian Wing Shun Chan
76   *
77   */
78  public class PortletRequestProcessor extends TilesRequestProcessor {
79  
80      public static PortletRequestProcessor getInstance(
81              ActionServlet servlet, ModuleConfig config)
82          throws ServletException {
83  
84          try {
85              String className = PropsValues.STRUTS_PORTLET_REQUEST_PROCESSOR;
86  
87              Class clazz = Class.forName(className);
88  
89              Constructor constructor = clazz.getConstructor(
90                  new Class[] {
91                      ActionServlet.class, ModuleConfig.class
92                  }
93              );
94  
95              PortletRequestProcessor portletReqProcessor =
96                  (PortletRequestProcessor)constructor.newInstance(
97                      new Object[] {
98                          servlet, config
99                      }
100                 );
101 
102             return portletReqProcessor;
103         }
104         catch (Exception e) {
105             _log.error(e);
106 
107             return new PortletRequestProcessor(servlet, config);
108         }
109     }
110 
111     public PortletRequestProcessor(ActionServlet servlet, ModuleConfig config)
112         throws ServletException {
113 
114         init(servlet, config);
115     }
116 
117     public void process(RenderRequest req, RenderResponse res)
118         throws IOException, ServletException {
119 
120         HttpServletRequest httpReq = PortalUtil.getHttpServletRequest(req);
121         HttpServletResponse httpRes = PortalUtil.getHttpServletResponse(res);
122 
123         process(httpReq, httpRes);
124     }
125 
126     public void process(ActionRequest req, ActionResponse res, String path)
127         throws IOException, ServletException {
128 
129         ActionResponseImpl resImpl = (ActionResponseImpl)res;
130 
131         HttpServletRequest httpReq = PortalUtil.getHttpServletRequest(req);
132         HttpServletResponse httpRes = PortalUtil.getHttpServletResponse(res);
133 
134         ActionMapping mapping = processMapping(httpReq, httpRes, path);
135 
136         if (mapping == null) {
137             return;
138         }
139 
140         if (!processRoles(httpReq, httpRes, mapping, true)) {
141             return;
142         }
143 
144         ActionForm form = processActionForm(httpReq, httpRes, mapping);
145 
146         processPopulate(httpReq, httpRes, form, mapping);
147 
148         if (!processValidateAction(httpReq, httpRes, form, mapping)) {
149             return;
150         }
151 
152         PortletAction action =
153             (PortletAction)processActionCreate(httpReq, httpRes, mapping);
154 
155         if (action == null) {
156             return;
157         }
158 
159         PortletConfigImpl portletConfig = (PortletConfigImpl)req.getAttribute(
160             JavaConstants.JAVAX_PORTLET_CONFIG);
161 
162         try {
163             if (action.isCheckMethodOnProcessAction()) {
164                 if (!PortalUtil.isMethodPost(req)) {
165                     String currentURL = PortalUtil.getCurrentURL(req);
166 
167                     throw new PrincipalException(currentURL);
168                 }
169             }
170 
171             action.processAction(mapping, form, portletConfig, req, res);
172         }
173         catch (Exception e) {
174             String exceptionId =
175                 WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
176                     portletConfig.getPortletId();
177 
178             req.setAttribute(exceptionId, e);
179         }
180 
181         String forward =
182             (String)req.getAttribute(PortletAction.getForwardKey(req));
183 
184         if (forward != null) {
185             String queryString = StringPool.BLANK;
186 
187             int pos = forward.indexOf("?");
188 
189             if (pos != -1) {
190                 queryString = forward.substring(pos + 1, forward.length());
191                 forward = forward.substring(0, pos);
192             }
193 
194             ActionForward actionForward = mapping.findForward(forward);
195 
196             if ((actionForward != null) && (actionForward.getRedirect())) {
197                 String forwardPath = actionForward.getPath();
198 
199                 if (forwardPath.startsWith("/")) {
200                     LiferayPortletURL forwardURL =
201                         (LiferayPortletURL)resImpl.createRenderURL();
202 
203                     forwardURL.setParameter("struts_action", forwardPath);
204 
205                     StrutsURLEncoder.setParameters(forwardURL, queryString);
206 
207                     forwardPath = forwardURL.toString();
208                 }
209 
210                 res.sendRedirect(forwardPath);
211             }
212         }
213     }
214 
215     protected ActionForm processActionForm(
216         HttpServletRequest req, HttpServletResponse res,
217         ActionMapping mapping) {
218 
219         ActionForm form = super.processActionForm(req, res, mapping);
220 
221         if (form instanceof InitializableActionForm) {
222             InitializableActionForm initForm = (InitializableActionForm)form;
223 
224             initForm.init(req, res, mapping);
225         }
226 
227         return form;
228     }
229 
230     protected ActionForward processActionPerform(
231             HttpServletRequest req, HttpServletResponse res, Action action,
232             ActionForm form, ActionMapping mapping)
233         throws IOException, ServletException {
234 
235         PortletConfigImpl portletConfig = (PortletConfigImpl)req.getAttribute(
236             JavaConstants.JAVAX_PORTLET_CONFIG);
237 
238         String exceptionId =
239             WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
240                 portletConfig.getPortletId();
241 
242         Exception e = (Exception)req.getAttribute(exceptionId);
243 
244         if (e != null) {
245             return processException(req, res, e, form, mapping);
246         }
247         else {
248             return super.processActionPerform(req, res, action, form, mapping);
249         }
250     }
251 
252     protected void processForwardConfig(
253             HttpServletRequest req, HttpServletResponse res,
254             ForwardConfig forward)
255         throws IOException, ServletException {
256 
257         if (forward == null) {
258             _log.error("Forward does not exist");
259         }
260         else {
261 
262             // Don't render a null path. This is useful if you're sending a file
263             // in an exclusive window state.
264 
265             if (forward.getPath().equals(ActionConstants.COMMON_NULL)) {
266                 return;
267             }
268         }
269 
270         super.processForwardConfig(req, res, forward);
271     }
272 
273     public ActionMapping processMapping(
274             HttpServletRequest req, HttpServletResponse res, String path)
275         throws IOException {
276 
277         if (path == null) {
278             return null;
279         }
280 
281         return super.processMapping(req, res, path);
282     }
283 
284     protected boolean processRoles(
285             HttpServletRequest req, HttpServletResponse res,
286             ActionMapping mapping)
287         throws IOException, ServletException {
288 
289         return processRoles(req, res, mapping, false);
290     }
291 
292     protected boolean processRoles(
293             HttpServletRequest req, HttpServletResponse res,
294             ActionMapping mapping, boolean action)
295         throws IOException, ServletException {
296 
297         User user = null;
298 
299         try {
300             user = PortalUtil.getUser(req);
301         }
302         catch (Exception e) {
303         }
304 
305         if (user == null) {
306             return true;
307         }
308 
309         String path = mapping.getPath();
310 
311         try {
312             PortletConfigImpl portletConfig =
313                 (PortletConfigImpl)req.getAttribute(
314                     JavaConstants.JAVAX_PORTLET_CONFIG);
315 
316             Portlet portlet = PortletLocalServiceUtil.getPortletById(
317                 user.getCompanyId(), portletConfig.getPortletId());
318 
319             if (portlet == null) {
320                 return false;
321             }
322 
323             String strutsPath = path.substring(
324                 1, path.lastIndexOf(StringPool.SLASH));
325 
326             if (!strutsPath.equals(portlet.getStrutsPath())) {
327                 if (_log.isWarnEnabled()) {
328                     _log.warn(
329                         "The struts path " + strutsPath + " does not belong " +
330                             "to portlet " + portlet.getPortletId() + ". " +
331                                 "Check the definition in liferay-portlet.xml");
332                 }
333 
334                 throw new PrincipalException();
335             }
336             else if (portlet.isActive()) {
337                 ThemeDisplay themeDisplay =
338                     (ThemeDisplay)req.getAttribute(WebKeys.THEME_DISPLAY);
339 
340                 Layout layout = themeDisplay.getLayout();
341                 PermissionChecker permissionChecker =
342                     themeDisplay.getPermissionChecker();
343 
344                 if (!PortletPermissionUtil.contains(
345                         permissionChecker, layout.getPlid(), portlet,
346                         ActionKeys.VIEW)) {
347 
348                     throw new PrincipalException();
349                 }
350             }
351             else if (!portlet.isActive()) {
352                 ForwardConfig forwardConfig =
353                     mapping.findForward(_PATH_PORTAL_PORTLET_INACTIVE);
354 
355                 if (!action) {
356                     processForwardConfig(req, res, forwardConfig);
357                 }
358 
359                 return false;
360             }
361         }
362         catch (Exception e) {
363             if (_log.isWarnEnabled()) {
364                 _log.warn(e.getMessage());
365             }
366 
367             ForwardConfig forwardConfig =
368                 mapping.findForward(_PATH_PORTAL_PORTLET_ACCESS_DENIED);
369 
370             if (!action) {
371                 processForwardConfig(req, res, forwardConfig);
372             }
373 
374             return false;
375         }
376 
377         return true;
378     }
379 
380     protected boolean processValidateAction(
381             HttpServletRequest req, HttpServletResponse res, ActionForm form,
382             ActionMapping mapping)
383         throws IOException, ServletException {
384 
385         if (form == null) {
386             return true;
387         }
388 
389         if (req.getAttribute(Globals.CANCEL_KEY) != null) {
390             return true;
391         }
392 
393         if (!mapping.getValidate()) {
394             return true;
395         }
396 
397         ActionErrors errors = form.validate(mapping, req);
398 
399         if ((errors == null) || errors.isEmpty()) {
400             return true;
401         }
402 
403         if (form.getMultipartRequestHandler() != null) {
404             form.getMultipartRequestHandler().rollback();
405         }
406 
407         String input = mapping.getInput();
408 
409         if (input == null) {
410             _log.error("Validation failed but no input form is available");
411 
412             return false;
413         }
414 
415         req.setAttribute(Globals.ERROR_KEY, errors);
416 
417         // Struts normally calls internalModuleRelativeForward which breaks
418         // if called inside processAction
419 
420         req.setAttribute(PortletAction.getForwardKey(req), input);
421 
422         return false;
423     }
424 
425     protected void doForward(
426             String uri, HttpServletRequest req, HttpServletResponse res)
427         throws IOException, ServletException {
428 
429         doInclude(uri, req, res);
430     }
431 
432     protected void doInclude(
433             String uri, HttpServletRequest req, HttpServletResponse res)
434         throws IOException, ServletException {
435 
436         PortletConfigImpl portletConfig = (PortletConfigImpl)req.getAttribute(
437             JavaConstants.JAVAX_PORTLET_CONFIG);
438 
439         RenderRequest renderRequest = (RenderRequest)req.getAttribute(
440             JavaConstants.JAVAX_PORTLET_REQUEST);
441 
442         RenderResponse renderResponse = (RenderResponse)req.getAttribute(
443             JavaConstants.JAVAX_PORTLET_RESPONSE);
444 
445         PortletRequestDispatcherImpl prd = (PortletRequestDispatcherImpl)
446             portletConfig.getPortletContext().getRequestDispatcher(
447                 StrutsUtil.TEXT_HTML_DIR + uri);
448 
449         try {
450             if (prd == null) {
451                 _log.error(uri + " is not a valid include");
452             }
453             else {
454                 prd.include(renderRequest, renderResponse, true);
455             }
456         }
457         catch (PortletException pe) {
458             Throwable cause = pe.getCause();
459 
460             if (cause instanceof ServletException) {
461                 throw (ServletException)cause;
462             }
463             else {
464                 _log.error(cause, cause);
465             }
466         }
467     }
468 
469     protected HttpServletRequest processMultipart(HttpServletRequest req) {
470 
471         // Disable Struts from automatically wrapping a multipart request
472 
473         return req;
474     }
475 
476     protected String processPath(
477             HttpServletRequest req, HttpServletResponse res)
478         throws IOException {
479 
480         String path = req.getParameter("struts_action");
481 
482         if (_log.isDebugEnabled()) {
483             _log.debug("Getting request parameter path " + path);
484         }
485 
486         if (Validator.isNull(path)) {
487             if (_log.isDebugEnabled()) {
488                 _log.debug("Getting request attribute path " + path);
489             }
490 
491             path = (String)req.getAttribute(WebKeys.PORTLET_STRUTS_ACTION);
492         }
493 
494         if (path == null) {
495             PortletConfigImpl portletConfig =
496                 (PortletConfigImpl)req.getAttribute(
497                     JavaConstants.JAVAX_PORTLET_CONFIG);
498 
499             _log.error(
500                 portletConfig.getPortletName() +
501                     " does not have any paths specified");
502         }
503         else {
504             if (_log.isDebugEnabled()) {
505                 _log.debug("Processing path " + path);
506             }
507         }
508 
509         return path;
510     }
511 
512     private static final String _PATH_PORTAL_PORTLET_ACCESS_DENIED =
513         "/portal/portlet_access_denied";
514 
515     private static final String _PATH_PORTAL_PORTLET_INACTIVE =
516         "/portal/portlet_inactive";
517 
518     private static Log _log = LogFactory.getLog(PortletRequestProcessor.class);
519 
520 }