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