001    /**
002     * Copyright (c) 2000-present 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.portlet;
016    
017    import com.liferay.portal.kernel.exception.PortalException;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
020    import com.liferay.portal.kernel.portlet.ActionResult;
021    import com.liferay.portal.kernel.portlet.PortletContainer;
022    import com.liferay.portal.kernel.portlet.PortletContainerException;
023    import com.liferay.portal.kernel.portlet.PortletContainerUtil;
024    import com.liferay.portal.kernel.portlet.PortletModeFactory;
025    import com.liferay.portal.kernel.resiliency.spi.SPIUtil;
026    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
027    import com.liferay.portal.kernel.servlet.HttpHeaders;
028    import com.liferay.portal.kernel.servlet.TempAttributesServletRequest;
029    import com.liferay.portal.kernel.struts.LastPath;
030    import com.liferay.portal.kernel.util.ArrayUtil;
031    import com.liferay.portal.kernel.util.CharPool;
032    import com.liferay.portal.kernel.util.GetterUtil;
033    import com.liferay.portal.kernel.util.ParamUtil;
034    import com.liferay.portal.kernel.util.StringUtil;
035    import com.liferay.portal.kernel.util.Validator;
036    import com.liferay.portal.model.Group;
037    import com.liferay.portal.model.Layout;
038    import com.liferay.portal.model.LayoutTypePortlet;
039    import com.liferay.portal.model.Portlet;
040    import com.liferay.portal.security.auth.AuthTokenUtil;
041    import com.liferay.portal.security.auth.PrincipalException;
042    import com.liferay.portal.security.permission.ActionKeys;
043    import com.liferay.portal.security.permission.PermissionChecker;
044    import com.liferay.portal.security.permission.PermissionThreadLocal;
045    import com.liferay.portal.service.permission.GroupPermissionUtil;
046    import com.liferay.portal.service.permission.LayoutPermissionUtil;
047    import com.liferay.portal.service.permission.LayoutPrototypePermissionUtil;
048    import com.liferay.portal.service.permission.LayoutSetPrototypePermissionUtil;
049    import com.liferay.portal.service.permission.OrganizationPermissionUtil;
050    import com.liferay.portal.service.permission.PortletPermissionUtil;
051    import com.liferay.portal.theme.ThemeDisplay;
052    import com.liferay.portal.util.PortalUtil;
053    import com.liferay.portal.util.PortletKeys;
054    import com.liferay.portal.util.PropsValues;
055    import com.liferay.portal.util.WebKeys;
056    
057    import java.util.List;
058    import java.util.Map;
059    
060    import javax.portlet.Event;
061    import javax.portlet.PortletMode;
062    
063    import javax.servlet.RequestDispatcher;
064    import javax.servlet.http.HttpServletRequest;
065    import javax.servlet.http.HttpServletResponse;
066    
067    /**
068     * @author Tomas Polesovsky
069     * @author Raymond Aug??
070     */
071    @DoPrivileged
072    public class SecurityPortletContainerWrapper implements PortletContainer {
073    
074            public static PortletContainer createSecurityPortletContainerWrapper(
075                    PortletContainer portletContainer) {
076    
077                    if (!SPIUtil.isSPI()) {
078                            portletContainer = new SecurityPortletContainerWrapper(
079                                    portletContainer);
080                    }
081    
082                    return portletContainer;
083            }
084    
085            public SecurityPortletContainerWrapper(PortletContainer portletContainer) {
086                    _portletContainer = portletContainer;
087            }
088    
089            @Override
090            public void preparePortlet(HttpServletRequest request, Portlet portlet)
091                    throws PortletContainerException {
092    
093                    _portletContainer.preparePortlet(request, portlet);
094            }
095    
096            @Override
097            public ActionResult processAction(
098                            HttpServletRequest request, HttpServletResponse response,
099                            Portlet portlet)
100                    throws PortletContainerException {
101    
102                    try {
103                            HttpServletRequest ownerLayoutRequest =
104                                    getOwnerLayoutRequestWrapper(request, portlet);
105    
106                            checkAction(ownerLayoutRequest, portlet);
107    
108                            return _portletContainer.processAction(request, response, portlet);
109                    }
110                    catch (PrincipalException pe) {
111                            return processActionException(request, response, portlet, pe);
112                    }
113                    catch (PortletContainerException pce) {
114                            throw pce;
115                    }
116                    catch (Exception e) {
117                            throw new PortletContainerException(e);
118                    }
119            }
120    
121            @Override
122            public List<Event> processEvent(
123                            HttpServletRequest request, HttpServletResponse response,
124                            Portlet portlet, Layout layout, Event event)
125                    throws PortletContainerException {
126    
127                    return _portletContainer.processEvent(
128                            request, response, portlet, layout, event);
129            }
130    
131            @Override
132            public void render(
133                            HttpServletRequest request, HttpServletResponse response,
134                            Portlet portlet)
135                    throws PortletContainerException {
136    
137                    try {
138                            checkRender(request, portlet);
139    
140                            _portletContainer.render(request, response, portlet);
141                    }
142                    catch (PrincipalException e) {
143                            processRenderException(request, response, portlet);
144                    }
145                    catch (PortletContainerException e) {
146                            throw e;
147                    }
148                    catch (Exception e) {
149                            throw new PortletContainerException(e);
150                    }
151            }
152    
153            @Override
154            public void serveResource(
155                            HttpServletRequest request, HttpServletResponse response,
156                            Portlet portlet)
157                    throws PortletContainerException {
158    
159                    try {
160                            HttpServletRequest ownerLayoutRequest =
161                                    getOwnerLayoutRequestWrapper(request, portlet);
162    
163                            checkResource(ownerLayoutRequest, portlet);
164    
165                            _portletContainer.serveResource(request, response, portlet);
166                    }
167                    catch (PrincipalException pe) {
168                            processServeResourceException(request, response, portlet, pe);
169                    }
170                    catch (PortletContainerException pce) {
171                            throw pce;
172                    }
173                    catch (Exception e) {
174                            throw new PortletContainerException(e);
175                    }
176            }
177    
178            protected void check(HttpServletRequest request, Portlet portlet)
179                    throws Exception {
180    
181                    if (portlet == null) {
182                            return;
183                    }
184    
185                    if (!isValidPortletId(portlet.getPortletId())) {
186                            if (_log.isWarnEnabled()) {
187                                    _log.warn("Invalid portlet id " + portlet.getPortletId());
188                            }
189    
190                            throw new PrincipalException();
191                    }
192    
193                    if (portlet.isUndeployedPortlet()) {
194                            return;
195                    }
196    
197                    Layout layout = (Layout)request.getAttribute(WebKeys.LAYOUT);
198    
199                    if (layout.isTypeControlPanel()) {
200                            isAccessAllowedToControlPanelPortlet(request, portlet);
201    
202                            return;
203                    }
204    
205                    if (isAccessAllowedToLayoutPortlet(request, portlet)) {
206                            PortalUtil.addPortletDefaultResource(request, portlet);
207    
208                            if (hasAccessPermission(request, portlet)) {
209                                    return;
210                            }
211                    }
212    
213                    throw new PrincipalException();
214            }
215    
216            protected void checkAction(HttpServletRequest request, Portlet portlet)
217                    throws Exception {
218    
219                    checkCSRFProtection(request, portlet);
220    
221                    check(request, portlet);
222            }
223    
224            protected void checkCSRFProtection(
225                            HttpServletRequest request, Portlet portlet)
226                    throws PortalException {
227    
228                    Map<String, String> initParams = portlet.getInitParams();
229    
230                    boolean checkAuthToken = GetterUtil.getBoolean(
231                            initParams.get("check-auth-token"), true);
232    
233                    if (checkAuthToken) {
234                            AuthTokenUtil.checkCSRFToken(
235                                    request, SecurityPortletContainerWrapper.class.getName());
236                    }
237            }
238    
239            protected void checkRender(HttpServletRequest request, Portlet portlet)
240                    throws Exception {
241    
242                    check(request, portlet);
243            }
244    
245            protected void checkResource(HttpServletRequest request, Portlet portlet)
246                    throws Exception {
247    
248                    check(request, portlet);
249            }
250    
251            protected String getOriginalURL(HttpServletRequest request) {
252                    LastPath lastPath = (LastPath)request.getAttribute(WebKeys.LAST_PATH);
253    
254                    if (lastPath == null) {
255                            return String.valueOf(request.getRequestURI());
256                    }
257    
258                    String portalURL = PortalUtil.getPortalURL(request);
259    
260                    return portalURL.concat(
261                            lastPath.getContextPath()).concat(lastPath.getPath());
262            }
263    
264            protected HttpServletRequest getOwnerLayoutRequestWrapper(
265                            HttpServletRequest request, Portlet portlet)
266                    throws Exception {
267    
268                    if (!PropsValues.PORTLET_EVENT_DISTRIBUTION_LAYOUT_SET ||
269                            PropsValues.PORTLET_CROSS_LAYOUT_INVOCATION_MODE.equals("render")) {
270    
271                            return request;
272                    }
273    
274                    Layout ownerLayout = null;
275                    LayoutTypePortlet ownerLayoutTypePortlet = null;
276    
277                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
278                            WebKeys.THEME_DISPLAY);
279    
280                    Layout requestLayout = (Layout)request.getAttribute(WebKeys.LAYOUT);
281    
282                    List<LayoutTypePortlet> layoutTypePortlets =
283                            PortletContainerUtil.getLayoutTypePortlets(requestLayout);
284    
285                    for (LayoutTypePortlet layoutTypePortlet : layoutTypePortlets) {
286                            if (layoutTypePortlet.hasPortletId(portlet.getPortletId())) {
287                                    ownerLayoutTypePortlet = layoutTypePortlet;
288    
289                                    ownerLayout = layoutTypePortlet.getLayout();
290    
291                                    break;
292                            }
293                    }
294    
295                    if (ownerLayout == null) {
296                            return request;
297                    }
298    
299                    Layout currentLayout = themeDisplay.getLayout();
300    
301                    if (currentLayout.equals(ownerLayout)) {
302                            return request;
303                    }
304    
305                    ThemeDisplay themeDisplayClone = (ThemeDisplay)themeDisplay.clone();
306    
307                    themeDisplayClone.setLayout(ownerLayout);
308                    themeDisplayClone.setLayoutTypePortlet(ownerLayoutTypePortlet);
309    
310                    TempAttributesServletRequest tempAttributesServletRequest =
311                            new TempAttributesServletRequest(request);
312    
313                    tempAttributesServletRequest.setTempAttribute(
314                            WebKeys.LAYOUT, ownerLayout);
315                    tempAttributesServletRequest.setTempAttribute(
316                            WebKeys.THEME_DISPLAY, themeDisplayClone);
317    
318                    return tempAttributesServletRequest;
319            }
320    
321            protected boolean hasAccessPermission(
322                            HttpServletRequest request, Portlet portlet)
323                    throws PortalException {
324    
325                    PermissionChecker permissionChecker =
326                            PermissionThreadLocal.getPermissionChecker();
327    
328                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
329                            WebKeys.THEME_DISPLAY);
330    
331                    Layout layout = (Layout)request.getAttribute(WebKeys.LAYOUT);
332    
333                    PortletMode portletMode = PortletMode.VIEW;
334    
335                    String portletId = portlet.getPortletId();
336                    String ppid = request.getParameter("p_p_id");
337                    String ppmode = request.getParameter("p_p_mode");
338    
339                    if (portletId.equals(ppid) && (ppmode != null)) {
340                            portletMode = PortletModeFactory.getPortletMode(ppmode);
341                    }
342    
343                    return PortletPermissionUtil.hasAccessPermission(
344                            permissionChecker, themeDisplay.getScopeGroupId(), layout, portlet,
345                            portletMode);
346            }
347    
348            protected void isAccessAllowedToControlPanelPortlet(
349                            HttpServletRequest request, Portlet portlet)
350                    throws PortalException {
351    
352                    PermissionChecker permissionChecker =
353                            PermissionThreadLocal.getPermissionChecker();
354    
355                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
356                            WebKeys.THEME_DISPLAY);
357    
358                    if (PortletPermissionUtil.hasControlPanelAccessPermission(
359                                    permissionChecker, themeDisplay.getScopeGroupId(), portlet)) {
360    
361                            return;
362                    }
363    
364                    if (isAccessGrantedByRuntimePortlet(request, portlet)) {
365                            return;
366                    }
367    
368                    if (isAccessGrantedByPortletAuthenticationToken(request, portlet)) {
369                            return;
370                    }
371    
372                    throw new PrincipalException();
373            }
374    
375            protected boolean isAccessAllowedToLayoutPortlet(
376                            HttpServletRequest request, Portlet portlet)
377                    throws PortalException {
378    
379                    if (isAccessGrantedByRuntimePortlet(request, portlet)) {
380                            return true;
381                    }
382    
383                    if (isAccessGrantedByPortletOnPage(request, portlet)) {
384                            return true;
385                    }
386    
387                    if (isLayoutConfigurationAllowed(request, portlet)) {
388                            return true;
389                    }
390    
391                    if (isAccessGrantedByPortletAuthenticationToken(request, portlet)) {
392                            return true;
393                    }
394    
395                    return false;
396            }
397    
398            protected boolean isAccessGrantedByPortletAuthenticationToken(
399                    HttpServletRequest request, Portlet portlet) {
400    
401                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
402                            WebKeys.THEME_DISPLAY);
403    
404                    String portletId = portlet.getPortletId();
405    
406                    if (!portlet.isAddDefaultResource()) {
407                            return false;
408                    }
409    
410                    if (!PropsValues.PORTLET_ADD_DEFAULT_RESOURCE_CHECK_ENABLED) {
411                            return true;
412                    }
413    
414                    String namespace = PortalUtil.getPortletNamespace(portletId);
415    
416                    String strutsAction = ParamUtil.getString(
417                            request, namespace + "struts_action");
418    
419                    if (Validator.isNull(strutsAction)) {
420                            strutsAction = ParamUtil.getString(request, "struts_action");
421                    }
422    
423                    String requestPortletAuthenticationToken = ParamUtil.getString(
424                            request, "p_p_auth");
425    
426                    if (Validator.isNull(requestPortletAuthenticationToken)) {
427                            HttpServletRequest originalRequest =
428                                    PortalUtil.getOriginalServletRequest(request);
429    
430                            requestPortletAuthenticationToken = ParamUtil.getString(
431                                    originalRequest, "p_p_auth");
432                    }
433    
434                    if (AuthTokenUtil.isValidPortletInvocationToken(
435                                    request, themeDisplay.getPlid(), portletId, strutsAction,
436                            requestPortletAuthenticationToken)) {
437    
438                            return true;
439                    }
440    
441                    return false;
442            }
443    
444            protected boolean isAccessGrantedByPortletOnPage(
445                            HttpServletRequest request, Portlet portlet)
446                    throws PortalException {
447    
448                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
449                            WebKeys.THEME_DISPLAY);
450    
451                    Layout layout = themeDisplay.getLayout();
452    
453                    String portletId = portlet.getPortletId();
454    
455                    if (layout.isTypePanel() &&
456                            isPanelSelectedPortlet(themeDisplay, portletId)) {
457    
458                            return true;
459                    }
460    
461                    LayoutTypePortlet layoutTypePortlet =
462                            themeDisplay.getLayoutTypePortlet();
463    
464                    if ((layoutTypePortlet != null) &&
465                            layoutTypePortlet.hasPortletId(portletId)) {
466    
467                            return true;
468                    }
469    
470                    return false;
471            }
472    
473            protected boolean isAccessGrantedByRuntimePortlet(
474                    HttpServletRequest request, Portlet portlet) {
475    
476                    Boolean renderPortletResource = (Boolean)request.getAttribute(
477                            WebKeys.RENDER_PORTLET_RESOURCE);
478    
479                    if (renderPortletResource != null) {
480                            boolean runtimePortlet = renderPortletResource.booleanValue();
481    
482                            if (runtimePortlet) {
483                                    return true;
484                            }
485                    }
486    
487                    return false;
488            }
489    
490            protected boolean isLayoutConfigurationAllowed(
491                            HttpServletRequest request, Portlet portlet)
492                    throws PortalException {
493    
494                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
495                            WebKeys.THEME_DISPLAY);
496    
497                    if (!themeDisplay.isSignedIn()) {
498                            return false;
499                    }
500    
501                    String portletId = portlet.getPortletId();
502    
503                    if (!portletId.equals(PortletKeys.LAYOUTS_ADMIN)) {
504                            return false;
505                    }
506    
507                    PermissionChecker permissionChecker =
508                            themeDisplay.getPermissionChecker();
509    
510                    Layout layout = themeDisplay.getLayout();
511    
512                    Group group = layout.getGroup();
513    
514                    if (group.isSite()) {
515                            if (LayoutPermissionUtil.contains(
516                                            permissionChecker, layout, ActionKeys.CUSTOMIZE) ||
517                                    LayoutPermissionUtil.contains(
518                                            permissionChecker, layout, ActionKeys.UPDATE)) {
519    
520                                    return true;
521                            }
522                    }
523    
524                    if (group.isCompany()) {
525                            if (permissionChecker.isCompanyAdmin()) {
526                                    return true;
527                            }
528                    }
529                    else if (group.isLayoutPrototype()) {
530                            long layoutPrototypeId = group.getClassPK();
531    
532                            if (LayoutPrototypePermissionUtil.contains(
533                                            permissionChecker, layoutPrototypeId,
534                                    ActionKeys.UPDATE)) {
535    
536                                    return true;
537                            }
538                    }
539                    else if (group.isLayoutSetPrototype()) {
540                            long layoutSetPrototypeId = group.getClassPK();
541    
542                            if (LayoutSetPrototypePermissionUtil.contains(
543                                            permissionChecker, layoutSetPrototypeId,
544                                    ActionKeys.UPDATE)) {
545    
546                                    return true;
547                            }
548                    }
549                    else if (group.isOrganization()) {
550                            long organizationId = group.getOrganizationId();
551    
552                            if (OrganizationPermissionUtil.contains(
553                                            permissionChecker, organizationId, ActionKeys.UPDATE)) {
554    
555                                    return true;
556                            }
557                    }
558                    else if (group.isUserGroup()) {
559                            long scopeGroupId = themeDisplay.getScopeGroupId();
560    
561                            if (GroupPermissionUtil.contains(
562                                            permissionChecker, scopeGroupId, ActionKeys.UPDATE)) {
563    
564                                    return true;
565                            }
566                    }
567                    else if (group.isUser()) {
568                            return true;
569                    }
570    
571                    return false;
572            }
573    
574            protected boolean isPanelSelectedPortlet(
575                    ThemeDisplay themeDisplay, String portletId) {
576    
577                    Layout layout = themeDisplay.getLayout();
578    
579                    String panelSelectedPortlets = layout.getTypeSettingsProperty(
580                            "panelSelectedPortlets");
581    
582                    if (Validator.isNotNull(panelSelectedPortlets)) {
583                            String[] panelSelectedPortletsArray = StringUtil.split(
584                                    panelSelectedPortlets);
585    
586                            return ArrayUtil.contains(panelSelectedPortletsArray, portletId);
587                    }
588    
589                    return false;
590            }
591    
592            protected boolean isValidPortletId(String portletId) {
593                    for (int i = 0; i < portletId.length(); i++) {
594                            char c = portletId.charAt(i);
595    
596                            if ((c >= CharPool.LOWER_CASE_A) && (c <= CharPool.LOWER_CASE_Z)) {
597                                    continue;
598                            }
599    
600                            if ((c >= CharPool.UPPER_CASE_A) && (c <= CharPool.UPPER_CASE_Z)) {
601                                    continue;
602                            }
603    
604                            if ((c >= CharPool.NUMBER_0) && (c <= CharPool.NUMBER_9)) {
605                                    continue;
606                            }
607    
608                            if (c == CharPool.UNDERLINE) {
609                                    continue;
610                            }
611    
612                            return false;
613                    }
614    
615                    return true;
616            }
617    
618            protected ActionResult processActionException(
619                    HttpServletRequest request, HttpServletResponse response,
620                    Portlet portlet, PrincipalException e) {
621    
622                    if (_log.isDebugEnabled()) {
623                            _log.debug(e);
624                    }
625    
626                    String url = getOriginalURL(request);
627    
628                    if (_log.isWarnEnabled()) {
629                            _log.warn(
630                                    "Reject process action for " + url + " on " +
631                                            portlet.getPortletId());
632                    }
633    
634                    return ActionResult.EMPTY_ACTION_RESULT;
635            }
636    
637            protected void processRenderException(
638                            HttpServletRequest request, HttpServletResponse response,
639                            Portlet portlet)
640                    throws PortletContainerException {
641    
642                    String portletContent = null;
643    
644                    if (portlet.isShowPortletAccessDenied()) {
645                            portletContent = "/html/portal/portlet_access_denied.jsp";
646                    }
647    
648                    try {
649                            if (portletContent != null) {
650                                    RequestDispatcher requestDispatcher =
651                                            request.getRequestDispatcher(portletContent);
652    
653                                    requestDispatcher.include(request, response);
654                            }
655                    }
656                    catch (Exception ex) {
657                            throw new PortletContainerException(ex);
658                    }
659            }
660    
661            protected void processServeResourceException(
662                    HttpServletRequest request, HttpServletResponse response,
663                    Portlet portlet, PrincipalException e) {
664    
665                    if (_log.isDebugEnabled()) {
666                            _log.debug(e);
667                    }
668    
669                    String url = getOriginalURL(request);
670    
671                    response.setHeader(
672                            HttpHeaders.CACHE_CONTROL,
673                            HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
674    
675                    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
676    
677                    if (_log.isWarnEnabled()) {
678                            _log.warn(
679                                    "Reject serveResource for " + url + " on " +
680                                            portlet.getPortletId());
681                    }
682            }
683    
684            private static Log _log = LogFactoryUtil.getLog(
685                    SecurityPortletContainerWrapper.class);
686    
687            private PortletContainer _portletContainer;
688    
689    }