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                     if (_log.isWarnEnabled()) {
168                         _log.warn(
169                             "This URL can only be invoked using POST: " +
170                                 currentURL);
171                     }
172 
173                     throw new PrincipalException(currentURL);
174                 }
175             }
176 
177             action.processAction(mapping, form, portletConfig, req, res);
178         }
179         catch (Exception e) {
180             String exceptionId =
181                 WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
182                     portletConfig.getPortletId();
183 
184             req.setAttribute(exceptionId, e);
185         }
186 
187         String forward =
188             (String)req.getAttribute(PortletAction.getForwardKey(req));
189 
190         if (forward != null) {
191             String queryString = StringPool.BLANK;
192 
193             int pos = forward.indexOf("?");
194 
195             if (pos != -1) {
196                 queryString = forward.substring(pos + 1, forward.length());
197                 forward = forward.substring(0, pos);
198             }
199 
200             ActionForward actionForward = mapping.findForward(forward);
201 
202             if ((actionForward != null) && (actionForward.getRedirect())) {
203                 String forwardPath = actionForward.getPath();
204 
205                 if (forwardPath.startsWith("/")) {
206                     LiferayPortletURL forwardURL =
207                         (LiferayPortletURL)resImpl.createRenderURL();
208 
209                     forwardURL.setParameter("struts_action", forwardPath);
210 
211                     StrutsURLEncoder.setParameters(forwardURL, queryString);
212 
213                     forwardPath = forwardURL.toString();
214                 }
215 
216                 res.sendRedirect(forwardPath);
217             }
218         }
219     }
220 
221     protected ActionForm processActionForm(
222         HttpServletRequest req, HttpServletResponse res,
223         ActionMapping mapping) {
224 
225         ActionForm form = super.processActionForm(req, res, mapping);
226 
227         if (form instanceof InitializableActionForm) {
228             InitializableActionForm initForm = (InitializableActionForm)form;
229 
230             initForm.init(req, res, mapping);
231         }
232 
233         return form;
234     }
235 
236     protected ActionForward processActionPerform(
237             HttpServletRequest req, HttpServletResponse res, Action action,
238             ActionForm form, ActionMapping mapping)
239         throws IOException, ServletException {
240 
241         PortletConfigImpl portletConfig = (PortletConfigImpl)req.getAttribute(
242             JavaConstants.JAVAX_PORTLET_CONFIG);
243 
244         String exceptionId =
245             WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
246                 portletConfig.getPortletId();
247 
248         Exception e = (Exception)req.getAttribute(exceptionId);
249 
250         if (e != null) {
251             return processException(req, res, e, form, mapping);
252         }
253         else {
254             return super.processActionPerform(req, res, action, form, mapping);
255         }
256     }
257 
258     protected void processForwardConfig(
259             HttpServletRequest req, HttpServletResponse res,
260             ForwardConfig forward)
261         throws IOException, ServletException {
262 
263         if (forward == null) {
264             _log.error("Forward does not exist");
265         }
266         else {
267 
268             // Don't render a null path. This is useful if you're sending a file
269             // in an exclusive window state.
270 
271             if (forward.getPath().equals(ActionConstants.COMMON_NULL)) {
272                 return;
273             }
274         }
275 
276         super.processForwardConfig(req, res, forward);
277     }
278 
279     public ActionMapping processMapping(
280             HttpServletRequest req, HttpServletResponse res, String path)
281         throws IOException {
282 
283         if (path == null) {
284             return null;
285         }
286 
287         return super.processMapping(req, res, path);
288     }
289 
290     protected boolean processRoles(
291             HttpServletRequest req, HttpServletResponse res,
292             ActionMapping mapping)
293         throws IOException, ServletException {
294 
295         return processRoles(req, res, mapping, false);
296     }
297 
298     protected boolean processRoles(
299             HttpServletRequest req, HttpServletResponse res,
300             ActionMapping mapping, boolean action)
301         throws IOException, ServletException {
302 
303         User user = null;
304 
305         try {
306             user = PortalUtil.getUser(req);
307         }
308         catch (Exception e) {
309         }
310 
311         if (user == null) {
312             return true;
313         }
314 
315         String path = mapping.getPath();
316 
317         try {
318             PortletConfigImpl portletConfig =
319                 (PortletConfigImpl)req.getAttribute(
320                     JavaConstants.JAVAX_PORTLET_CONFIG);
321 
322             Portlet portlet = PortletLocalServiceUtil.getPortletById(
323                 user.getCompanyId(), portletConfig.getPortletId());
324 
325             if (portlet == null) {
326                 return false;
327             }
328 
329             String strutsPath = path.substring(
330                 1, path.lastIndexOf(StringPool.SLASH));
331 
332             if (!strutsPath.equals(portlet.getStrutsPath())) {
333                 if (_log.isWarnEnabled()) {
334                     _log.warn(
335                         "The struts path " + strutsPath + " does not belong " +
336                             "to portlet " + portlet.getPortletId() + ". " +
337                                 "Check the definition in liferay-portlet.xml");
338                 }
339 
340                 throw new PrincipalException();
341             }
342             else if (portlet.isActive()) {
343                 ThemeDisplay themeDisplay =
344                     (ThemeDisplay)req.getAttribute(WebKeys.THEME_DISPLAY);
345 
346                 Layout layout = themeDisplay.getLayout();
347                 PermissionChecker permissionChecker =
348                     themeDisplay.getPermissionChecker();
349 
350                 if (!PortletPermissionUtil.contains(
351                         permissionChecker, layout.getPlid(), portlet,
352                         ActionKeys.VIEW)) {
353 
354                     throw new PrincipalException();
355                 }
356             }
357             else if (!portlet.isActive()) {
358                 ForwardConfig forwardConfig =
359                     mapping.findForward(_PATH_PORTAL_PORTLET_INACTIVE);
360 
361                 if (!action) {
362                     processForwardConfig(req, res, forwardConfig);
363                 }
364 
365                 return false;
366             }
367         }
368         catch (Exception e) {
369             if (_log.isWarnEnabled()) {
370                 _log.warn(e.getMessage());
371             }
372 
373             ForwardConfig forwardConfig =
374                 mapping.findForward(_PATH_PORTAL_PORTLET_ACCESS_DENIED);
375 
376             if (!action) {
377                 processForwardConfig(req, res, forwardConfig);
378             }
379 
380             return false;
381         }
382 
383         return true;
384     }
385 
386     protected boolean processValidateAction(
387             HttpServletRequest req, HttpServletResponse res, ActionForm form,
388             ActionMapping mapping)
389         throws IOException, ServletException {
390 
391         if (form == null) {
392             return true;
393         }
394 
395         if (req.getAttribute(Globals.CANCEL_KEY) != null) {
396             return true;
397         }
398 
399         if (!mapping.getValidate()) {
400             return true;
401         }
402 
403         ActionErrors errors = form.validate(mapping, req);
404 
405         if ((errors == null) || errors.isEmpty()) {
406             return true;
407         }
408 
409         if (form.getMultipartRequestHandler() != null) {
410             form.getMultipartRequestHandler().rollback();
411         }
412 
413         String input = mapping.getInput();
414 
415         if (input == null) {
416             _log.error("Validation failed but no input form is available");
417 
418             return false;
419         }
420 
421         req.setAttribute(Globals.ERROR_KEY, errors);
422 
423         // Struts normally calls internalModuleRelativeForward which breaks
424         // if called inside processAction
425 
426         req.setAttribute(PortletAction.getForwardKey(req), input);
427 
428         return false;
429     }
430 
431     protected void doForward(
432             String uri, HttpServletRequest req, HttpServletResponse res)
433         throws IOException, ServletException {
434 
435         doInclude(uri, req, res);
436     }
437 
438     protected void doInclude(
439             String uri, HttpServletRequest req, HttpServletResponse res)
440         throws IOException, ServletException {
441 
442         PortletConfigImpl portletConfig = (PortletConfigImpl)req.getAttribute(
443             JavaConstants.JAVAX_PORTLET_CONFIG);
444 
445         RenderRequest renderRequest = (RenderRequest)req.getAttribute(
446             JavaConstants.JAVAX_PORTLET_REQUEST);
447 
448         RenderResponse renderResponse = (RenderResponse)req.getAttribute(
449             JavaConstants.JAVAX_PORTLET_RESPONSE);
450 
451         PortletRequestDispatcherImpl prd = (PortletRequestDispatcherImpl)
452             portletConfig.getPortletContext().getRequestDispatcher(
453                 StrutsUtil.TEXT_HTML_DIR + uri);
454 
455         try {
456             if (prd == null) {
457                 _log.error(uri + " is not a valid include");
458             }
459             else {
460                 prd.include(renderRequest, renderResponse, true);
461             }
462         }
463         catch (PortletException pe) {
464             Throwable cause = pe.getCause();
465 
466             if (cause instanceof ServletException) {
467                 throw (ServletException)cause;
468             }
469             else {
470                 _log.error(cause, cause);
471             }
472         }
473     }
474 
475     protected HttpServletRequest processMultipart(HttpServletRequest req) {
476 
477         // Disable Struts from automatically wrapping a multipart request
478 
479         return req;
480     }
481 
482     protected String processPath(
483             HttpServletRequest req, HttpServletResponse res)
484         throws IOException {
485 
486         String path = req.getParameter("struts_action");
487 
488         if (_log.isDebugEnabled()) {
489             _log.debug("Getting request parameter path " + path);
490         }
491 
492         if (Validator.isNull(path)) {
493             if (_log.isDebugEnabled()) {
494                 _log.debug("Getting request attribute path " + path);
495             }
496 
497             path = (String)req.getAttribute(WebKeys.PORTLET_STRUTS_ACTION);
498         }
499 
500         if (path == null) {
501             PortletConfigImpl portletConfig =
502                 (PortletConfigImpl)req.getAttribute(
503                     JavaConstants.JAVAX_PORTLET_CONFIG);
504 
505             _log.error(
506                 portletConfig.getPortletName() +
507                     " does not have any paths specified");
508         }
509         else {
510             if (_log.isDebugEnabled()) {
511                 _log.debug("Processing path " + path);
512             }
513         }
514 
515         return path;
516     }
517 
518     private static final String _PATH_PORTAL_PORTLET_ACCESS_DENIED =
519         "/portal/portlet_access_denied";
520 
521     private static final String _PATH_PORTAL_PORTLET_INACTIVE =
522         "/portal/portlet_inactive";
523 
524     private static Log _log = LogFactory.getLog(PortletRequestProcessor.class);
525 
526 }