001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.struts;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.portlet.LiferayPortletURL;
020    import com.liferay.portal.kernel.util.CharPool;
021    import com.liferay.portal.kernel.util.JavaConstants;
022    import com.liferay.portal.kernel.util.StringPool;
023    import com.liferay.portal.kernel.util.Validator;
024    import com.liferay.portal.model.Layout;
025    import com.liferay.portal.model.Portlet;
026    import com.liferay.portal.security.auth.PrincipalException;
027    import com.liferay.portal.security.permission.ActionKeys;
028    import com.liferay.portal.security.permission.PermissionChecker;
029    import com.liferay.portal.service.PortletLocalServiceUtil;
030    import com.liferay.portal.service.permission.PortletPermissionUtil;
031    import com.liferay.portal.theme.ThemeDisplay;
032    import com.liferay.portal.util.PortalUtil;
033    import com.liferay.portal.util.PropsValues;
034    import com.liferay.portal.util.WebKeys;
035    import com.liferay.portlet.ActionResponseImpl;
036    import com.liferay.portlet.PortletConfigImpl;
037    import com.liferay.portlet.PortletRequestDispatcherImpl;
038    
039    import java.io.IOException;
040    
041    import java.lang.reflect.Constructor;
042    
043    import javax.portlet.ActionRequest;
044    import javax.portlet.ActionResponse;
045    import javax.portlet.PortletContext;
046    import javax.portlet.PortletException;
047    import javax.portlet.PortletRequest;
048    import javax.portlet.PortletResponse;
049    import javax.portlet.RenderRequest;
050    import javax.portlet.RenderResponse;
051    import javax.portlet.ResourceRequest;
052    import javax.portlet.ResourceResponse;
053    
054    import javax.servlet.ServletException;
055    import javax.servlet.http.HttpServletRequest;
056    import javax.servlet.http.HttpServletResponse;
057    
058    import org.apache.struts.Globals;
059    import org.apache.struts.action.Action;
060    import org.apache.struts.action.ActionErrors;
061    import org.apache.struts.action.ActionForm;
062    import org.apache.struts.action.ActionForward;
063    import org.apache.struts.action.ActionMapping;
064    import org.apache.struts.action.ActionServlet;
065    import org.apache.struts.config.ActionConfig;
066    import org.apache.struts.config.ForwardConfig;
067    import org.apache.struts.config.ModuleConfig;
068    import org.apache.struts.tiles.TilesRequestProcessor;
069    import org.apache.struts.util.MessageResources;
070    
071    /**
072     * @author Brian Wing Shun Chan
073     * @author Raymond Augé
074     */
075    public class PortletRequestProcessor extends TilesRequestProcessor {
076    
077            public static PortletRequestProcessor getInstance(
078                            ActionServlet servlet, ModuleConfig moduleConfig)
079                    throws ServletException {
080    
081                    try {
082                            String className = PropsValues.STRUTS_PORTLET_REQUEST_PROCESSOR;
083    
084                            Class<?> clazz = Class.forName(className);
085    
086                            Constructor<?> constructor = clazz.getConstructor(
087                                    new Class[] {
088                                            ActionServlet.class, ModuleConfig.class
089                                    }
090                            );
091    
092                            PortletRequestProcessor portletReqProcessor =
093                                    (PortletRequestProcessor)constructor.newInstance(
094                                            new Object[] {
095                                                    servlet, moduleConfig
096                                            }
097                                    );
098    
099                            return portletReqProcessor;
100                    }
101                    catch (Exception e) {
102                            _log.error(e);
103    
104                            return new PortletRequestProcessor(servlet, moduleConfig);
105                    }
106            }
107    
108            public PortletRequestProcessor(
109                            ActionServlet actionServlet, ModuleConfig moduleConfig)
110                    throws ServletException {
111    
112                    init(actionServlet, moduleConfig);
113            }
114    
115            public void process(
116                            ActionRequest actionRequest, ActionResponse actionResponse,
117                            String path)
118                    throws IOException, ServletException {
119    
120                    ActionResponseImpl actionResponseImpl =
121                            (ActionResponseImpl)actionResponse;
122    
123                    HttpServletRequest request = PortalUtil.getHttpServletRequest(
124                            actionRequest);
125                    HttpServletResponse response = PortalUtil.getHttpServletResponse(
126                            actionResponse);
127    
128                    ActionMapping actionMapping = processMapping(request, response, path);
129    
130                    if (actionMapping == null) {
131                            return;
132                    }
133    
134                    if (!processRoles(request, response, actionMapping, true)) {
135                            return;
136                    }
137    
138                    ActionForm actionForm = processActionForm(
139                            request, response, actionMapping);
140    
141                    processPopulate(request, response, actionForm, actionMapping);
142    
143                    if (!processValidateAction(
144                                    request, response, actionForm, actionMapping)) {
145    
146                            return;
147                    }
148    
149                    PortletAction portletAction = (PortletAction)processActionCreate(
150                            request, response, actionMapping);
151    
152                    if (portletAction == null) {
153                            return;
154                    }
155    
156                    PortletConfigImpl portletConfigImpl =
157                            (PortletConfigImpl)actionRequest.getAttribute(
158                                    JavaConstants.JAVAX_PORTLET_CONFIG);
159    
160                    try {
161                            if (portletAction.isCheckMethodOnProcessAction()) {
162                                    if (!PortalUtil.isMethodPost(actionRequest)) {
163                                            String currentURL = PortalUtil.getCurrentURL(actionRequest);
164    
165                                            if (_log.isWarnEnabled()) {
166                                                    _log.warn(
167                                                            "This URL can only be invoked using POST: " +
168                                                                    currentURL);
169                                            }
170    
171                                            throw new PrincipalException(currentURL);
172                                    }
173                            }
174    
175                            portletAction.processAction(
176                                    actionMapping, actionForm, portletConfigImpl, actionRequest,
177                                    actionResponse);
178                    }
179                    catch (Exception e) {
180                            String exceptionId =
181                                    WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
182                                            portletConfigImpl.getPortletId();
183    
184                            actionRequest.setAttribute(exceptionId, e);
185                    }
186    
187                    String forward = (String)actionRequest.getAttribute(
188                            PortletAction.getForwardKey(actionRequest));
189    
190                    if (forward != null) {
191                            String queryString = StringPool.BLANK;
192    
193                            int pos = forward.indexOf(CharPool.QUESTION);
194    
195                            if (pos != -1) {
196                                    queryString = forward.substring(pos + 1, forward.length());
197                                    forward = forward.substring(0, pos);
198                            }
199    
200                            ActionForward actionForward = actionMapping.findForward(forward);
201    
202                            if ((actionForward != null) && (actionForward.getRedirect())) {
203                                    String forwardPath = actionForward.getPath();
204    
205                                    if (forwardPath.startsWith(StringPool.SLASH)) {
206                                            LiferayPortletURL forwardURL =
207                                                    (LiferayPortletURL)actionResponseImpl.createRenderURL();
208    
209                                            forwardURL.setParameter("struts_action", forwardPath);
210    
211                                            StrutsURLEncoder.setParameters(forwardURL, queryString);
212    
213                                            forwardPath = forwardURL.toString();
214                                    }
215    
216                                    actionResponse.sendRedirect(forwardPath);
217                            }
218                    }
219            }
220    
221            public void process(
222                            RenderRequest renderRequest, RenderResponse renderResponse)
223                    throws IOException, ServletException {
224    
225                    HttpServletRequest request = PortalUtil.getHttpServletRequest(
226                            renderRequest);
227                    HttpServletResponse response = PortalUtil.getHttpServletResponse(
228                            renderResponse);
229    
230                    process(request, response);
231            }
232    
233            public void process(
234                            ResourceRequest resourceRequest, ResourceResponse resourceResponse)
235                    throws IOException, ServletException {
236    
237                    HttpServletRequest request = PortalUtil.getHttpServletRequest(
238                            resourceRequest);
239                    HttpServletResponse response = PortalUtil.getHttpServletResponse(
240                            resourceResponse);
241    
242                    process(request, response);
243            }
244    
245            @Override
246            public ActionMapping processMapping(
247                    HttpServletRequest request, HttpServletResponse response, String path) {
248    
249                    if (path == null) {
250                            return null;
251                    }
252    
253                    ActionMapping actionMapping = null;
254    
255                    long companyId = PortalUtil.getCompanyId(request);
256    
257                    PortletConfigImpl portletConfigImpl =
258                            (PortletConfigImpl)request.getAttribute(
259                                    JavaConstants.JAVAX_PORTLET_CONFIG);
260    
261                    try {
262                            Portlet portlet = PortletLocalServiceUtil.getPortletById(
263                                    companyId, portletConfigImpl.getPortletId());
264    
265                            if (StrutsActionRegistryUtil.getAction(path) != null) {
266                                    actionMapping = (ActionMapping)moduleConfig.findActionConfig(
267                                            path);
268    
269                                    if (actionMapping == null) {
270                                            actionMapping = new ActionMapping();
271    
272                                            actionMapping.setModuleConfig(moduleConfig);
273                                            actionMapping.setPath(path);
274    
275                                            request.setAttribute(Globals.MAPPING_KEY, actionMapping);
276                                    }
277                            }
278                            else if (moduleConfig.findActionConfig(path) != null) {
279                                    actionMapping = super.processMapping(request, response, path);
280                            }
281                            else if (Validator.isNotNull(portlet.getParentStrutsPath())) {
282                                    int pos = path.indexOf(StringPool.SLASH, 1);
283    
284                                    String parentPath =
285                                            StringPool.SLASH + portlet.getParentStrutsPath() +
286                                                    path.substring(pos, path.length());
287    
288                                    if (StrutsActionRegistryUtil.getAction(parentPath) != null) {
289                                            actionMapping =
290                                                    (ActionMapping)moduleConfig.findActionConfig(path);
291    
292                                            if (actionMapping == null) {
293                                                    actionMapping = new ActionMapping();
294    
295                                                    actionMapping.setModuleConfig(moduleConfig);
296                                                    actionMapping.setPath(parentPath);
297    
298                                                    request.setAttribute(
299                                                            Globals.MAPPING_KEY, actionMapping);
300                                            }
301                                    }
302                                    else if (moduleConfig.findActionConfig(parentPath) != null) {
303                                            actionMapping = super.processMapping(
304                                                    request, response, parentPath);
305                                    }
306                            }
307                    }
308                    catch (Exception e) {
309                    }
310    
311                    if (actionMapping == null) {
312                            MessageResources messageResources = getInternal();
313    
314                            String msg = messageResources.getMessage("processInvalid");
315    
316                            _log.error("User ID " + request.getRemoteUser());
317                            _log.error("Current URL " + PortalUtil.getCurrentURL(request));
318                            _log.error("Referer " + request.getHeader("Referer"));
319                            _log.error("Remote address " + request.getRemoteAddr());
320    
321                            _log.error(msg + " " + path);
322                    }
323    
324                    return actionMapping;
325            }
326    
327            @Override
328            protected void doForward(
329                            String uri, HttpServletRequest request,
330                            HttpServletResponse response)
331                    throws IOException, ServletException {
332    
333                    doInclude(uri, request, response);
334            }
335    
336            @Override
337            protected void doInclude(
338                            String uri, HttpServletRequest request,
339                            HttpServletResponse response)
340                    throws IOException, ServletException {
341    
342                    PortletConfigImpl portletConfigImpl =
343                            (PortletConfigImpl)request.getAttribute(
344                                    JavaConstants.JAVAX_PORTLET_CONFIG);
345    
346                    PortletContext portletContext = portletConfigImpl.getPortletContext();
347    
348                    PortletRequest portletRequest = (PortletRequest)request.getAttribute(
349                            JavaConstants.JAVAX_PORTLET_REQUEST);
350    
351                    PortletResponse portletResponse = (PortletResponse)request.getAttribute(
352                            JavaConstants.JAVAX_PORTLET_RESPONSE);
353    
354                    PortletRequestDispatcherImpl portletRequestDispatcher =
355                            (PortletRequestDispatcherImpl)portletContext.getRequestDispatcher(
356                                    StrutsUtil.TEXT_HTML_DIR + uri);
357    
358                    try {
359                            if (portletRequestDispatcher == null) {
360                                    _log.error(uri + " is not a valid include");
361                            }
362                            else {
363                                    portletRequestDispatcher.include(
364                                            portletRequest, portletResponse, true);
365                            }
366                    }
367                    catch (PortletException pe) {
368                            Throwable cause = pe.getCause();
369    
370                            if (cause instanceof ServletException) {
371                                    throw (ServletException)cause;
372                            }
373                            else {
374                                    _log.error(cause, cause);
375                            }
376                    }
377            }
378    
379            @Override
380            protected Action processActionCreate(
381                            HttpServletRequest request, HttpServletResponse response,
382                            ActionMapping actionMapping)
383                    throws IOException {
384    
385                    PortletActionAdapter portletActionAdapter =
386                            (PortletActionAdapter)StrutsActionRegistryUtil.getAction(
387                                    actionMapping.getPath());
388    
389                    if (portletActionAdapter != null) {
390                            ActionConfig actionConfig = moduleConfig.findActionConfig(
391                                    actionMapping.getPath());
392    
393                            if (actionConfig != null) {
394                                    PortletAction originalPortletAction =
395                                            (PortletAction)super.processActionCreate(
396                                                    request, response, actionMapping);
397    
398                                    portletActionAdapter.setOriginalPortletAction(
399                                            originalPortletAction);
400                            }
401    
402                            return portletActionAdapter;
403                    }
404    
405                    return super.processActionCreate(request, response, actionMapping);
406            }
407    
408            @Override
409            protected ActionForm processActionForm(
410                    HttpServletRequest request, HttpServletResponse response,
411                    ActionMapping actionMapping) {
412    
413                    ActionForm actionForm = super.processActionForm(
414                            request, response, actionMapping);
415    
416                    if (actionForm instanceof InitializableActionForm) {
417                            InitializableActionForm initializableActionForm =
418                                    (InitializableActionForm)actionForm;
419    
420                            initializableActionForm.init(request, response, actionMapping);
421                    }
422    
423                    return actionForm;
424            }
425    
426            @Override
427            protected ActionForward processActionPerform(
428                            HttpServletRequest request, HttpServletResponse response,
429                            Action action, ActionForm actionForm, ActionMapping actionMapping)
430                    throws IOException, ServletException {
431    
432                    PortletConfigImpl portletConfigImpl =
433                            (PortletConfigImpl)request.getAttribute(
434                                    JavaConstants.JAVAX_PORTLET_CONFIG);
435    
436                    String exceptionId =
437                            WebKeys.PORTLET_STRUTS_EXCEPTION + StringPool.PERIOD +
438                                    portletConfigImpl.getPortletId();
439    
440                    Exception e = (Exception)request.getAttribute(exceptionId);
441    
442                    if (e != null) {
443                            return processException(
444                                    request, response, e, actionForm, actionMapping);
445                    }
446                    else {
447                            return super.processActionPerform(
448                                    request, response, action, actionForm, actionMapping);
449                    }
450            }
451    
452            @Override
453            protected void processForwardConfig(
454                            HttpServletRequest request, HttpServletResponse response,
455                            ForwardConfig forward)
456                    throws IOException, ServletException {
457    
458                    if (forward == null) {
459                            _log.error("Forward does not exist");
460                    }
461                    else {
462    
463                            // Don't render a null path. This is useful if you're sending a file
464                            // in an exclusive window state.
465    
466                            if (forward.getPath().equals(ActionConstants.COMMON_NULL)) {
467                                    return;
468                            }
469                    }
470    
471                    super.processForwardConfig(request, response, forward);
472            }
473    
474            @Override
475            protected HttpServletRequest processMultipart(HttpServletRequest request) {
476    
477                    // Disable Struts from automatically wrapping a multipart request
478    
479                    return request;
480            }
481    
482            @Override
483            protected String processPath(
484                    HttpServletRequest request, HttpServletResponse response) {
485    
486                    String path = request.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)request.getAttribute(WebKeys.PORTLET_STRUTS_ACTION);
498                    }
499    
500                    if (path == null) {
501                            PortletConfigImpl portletConfigImpl =
502                                    (PortletConfigImpl)request.getAttribute(
503                                            JavaConstants.JAVAX_PORTLET_CONFIG);
504    
505                            _log.error(
506                                    portletConfigImpl.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            @Override
519            protected boolean processRoles(
520                            HttpServletRequest request, HttpServletResponse response,
521                            ActionMapping actionMapping)
522                    throws IOException, ServletException {
523    
524                    return processRoles(request, response, actionMapping, false);
525            }
526    
527            protected boolean processRoles(
528                            HttpServletRequest request, HttpServletResponse response,
529                            ActionMapping actionMapping, boolean action)
530                    throws IOException, ServletException {
531    
532                    long companyId = PortalUtil.getCompanyId(request);
533    
534                    String path = actionMapping.getPath();
535    
536                    try {
537                            PortletConfigImpl portletConfigImpl =
538                                    (PortletConfigImpl)request.getAttribute(
539                                            JavaConstants.JAVAX_PORTLET_CONFIG);
540    
541                            Portlet portlet = PortletLocalServiceUtil.getPortletById(
542                                    companyId, portletConfigImpl.getPortletId());
543    
544                            if (portlet == null) {
545                                    return false;
546                            }
547    
548                            String strutsPath = path.substring(
549                                    1, path.lastIndexOf(CharPool.SLASH));
550    
551                            if (!strutsPath.equals(portlet.getStrutsPath()) &&
552                                    !strutsPath.equals(portlet.getParentStrutsPath())) {
553                                    if (_log.isWarnEnabled()) {
554                                            _log.warn(
555                                                    "The struts path " + strutsPath + " does not belong " +
556                                                            "to portlet " + portlet.getPortletId() + ". " +
557                                                                    "Check the definition in liferay-portlet.xml");
558                                    }
559    
560                                    throw new PrincipalException();
561                            }
562                            else if (portlet.isActive()) {
563                                    if (PortalUtil.isAllowAddPortletDefaultResource(
564                                                    request, portlet)) {
565    
566                                            PortalUtil.addPortletDefaultResource(request, portlet);
567                                    }
568    
569                                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
570                                            WebKeys.THEME_DISPLAY);
571    
572                                    Layout layout = themeDisplay.getLayout();
573                                    PermissionChecker permissionChecker =
574                                            themeDisplay.getPermissionChecker();
575    
576                                    if (!PortletPermissionUtil.contains(
577                                                    permissionChecker, layout, portlet, ActionKeys.VIEW)) {
578    
579                                            throw new PrincipalException();
580                                    }
581                            }
582                            else if (!portlet.isActive()) {
583                                    ForwardConfig forwardConfig = actionMapping.findForward(
584                                            _PATH_PORTAL_PORTLET_INACTIVE);
585    
586                                    if (!action) {
587                                            processForwardConfig(request, response, forwardConfig);
588                                    }
589    
590                                    return false;
591                            }
592                    }
593                    catch (Exception e) {
594                            if (_log.isWarnEnabled()) {
595                                    _log.warn(e.getMessage());
596                            }
597    
598                            ForwardConfig forwardConfig = actionMapping.findForward(
599                                    _PATH_PORTAL_PORTLET_ACCESS_DENIED);
600    
601                            if (!action) {
602                                    processForwardConfig(request, response, forwardConfig);
603                            }
604    
605                            return false;
606                    }
607    
608                    return true;
609            }
610    
611            protected boolean processValidateAction(
612                    HttpServletRequest request, HttpServletResponse response,
613                    ActionForm actionForm, ActionMapping actionMapping) {
614    
615                    if (actionForm == null) {
616                            return true;
617                    }
618    
619                    if (request.getAttribute(Globals.CANCEL_KEY) != null) {
620                            return true;
621                    }
622    
623                    if (!actionMapping.getValidate()) {
624                            return true;
625                    }
626    
627                    ActionErrors errors = actionForm.validate(actionMapping, request);
628    
629                    if ((errors == null) || errors.isEmpty()) {
630                            return true;
631                    }
632    
633                    if (actionForm.getMultipartRequestHandler() != null) {
634                            actionForm.getMultipartRequestHandler().rollback();
635                    }
636    
637                    String input = actionMapping.getInput();
638    
639                    if (input == null) {
640                            _log.error("Validation failed but no input form is available");
641    
642                            return false;
643                    }
644    
645                    request.setAttribute(Globals.ERROR_KEY, errors);
646    
647                    // Struts normally calls internalModuleRelativeForward which breaks
648                    // if called inside processAction
649    
650                    request.setAttribute(PortletAction.getForwardKey(request), input);
651    
652                    return false;
653            }
654    
655            private static final String _PATH_PORTAL_PORTLET_ACCESS_DENIED =
656                    "/portal/portlet_access_denied";
657    
658            private static final String _PATH_PORTAL_PORTLET_INACTIVE =
659                    "/portal/portlet_inactive";
660    
661            private static Log _log = LogFactoryUtil.getLog(
662                    PortletRequestProcessor.class);
663    
664    }