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