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