001    /**
002     * Copyright (c) 2000-2013 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.security.pacl.checker;
016    
017    import com.liferay.portal.bean.BeanLocatorImpl;
018    import com.liferay.portal.dao.orm.hibernate.DynamicQueryFactoryImpl;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.portlet.PortletClassLoaderUtil;
022    import com.liferay.portal.kernel.security.pacl.permission.PortalRuntimePermission;
023    import com.liferay.portal.kernel.util.GetterUtil;
024    import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
025    import com.liferay.portal.kernel.util.SetUtil;
026    import com.liferay.portal.kernel.util.StringBundler;
027    import com.liferay.portal.kernel.util.StringPool;
028    import com.liferay.portal.kernel.util.StringUtil;
029    import com.liferay.portal.kernel.util.Validator;
030    import com.liferay.portal.service.BaseLocalServiceImpl;
031    import com.liferay.portal.service.BaseServiceImpl;
032    import com.liferay.portal.service.persistence.impl.BasePersistenceImpl;
033    import com.liferay.portal.template.TemplateContextHelper;
034    
035    import java.lang.reflect.Modifier;
036    
037    import java.security.Permission;
038    
039    import java.util.ArrayList;
040    import java.util.HashMap;
041    import java.util.List;
042    import java.util.Map;
043    import java.util.Properties;
044    import java.util.Set;
045    import java.util.TreeSet;
046    import java.util.regex.Matcher;
047    import java.util.regex.Pattern;
048    
049    import sun.reflect.Reflection;
050    
051    /**
052     * @author Brian Wing Shun Chan
053     * @author Raymond Augé
054     */
055    public class PortalRuntimeChecker extends BaseChecker {
056    
057            public void afterPropertiesSet() {
058                    initClassLoaderReferenceIds();
059                    initExpandoBridgeClassNames();
060                    initGetBeanPropertyClassNames();
061                    initPortletBagPoolPortletIds();
062                    initSearchEngineIds();
063                    initSetBeanPropertyClassNames();
064                    initThreadPoolExecutorNames();
065            }
066    
067            @Override
068            public AuthorizationProperty generateAuthorizationProperty(
069                    Object... arguments) {
070    
071                    if ((arguments == null) || (arguments.length != 1) ||
072                            !(arguments[0] instanceof Permission)) {
073    
074                            return null;
075                    }
076    
077                    PortalRuntimePermission portalRuntimePermission =
078                            (PortalRuntimePermission)arguments[0];
079    
080                    String name = portalRuntimePermission.getShortName();
081                    String servletContextName =
082                            portalRuntimePermission.getServletContextName();
083                    String subject = portalRuntimePermission.getSubject();
084                    String property = portalRuntimePermission.getProperty();
085    
086                    String key = null;
087                    String value = subject;
088    
089                    if (name.startsWith(PORTAL_RUNTIME_PERMISSION_GET_CLASSLOADER)) {
090                            key = "security-manager-class-loader-reference-ids";
091                    }
092                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_EXPANDO_BRIDGE)) {
093                            key = "security-manager-expando-bridge";
094                    }
095                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_GET_BEAN_PROPERTY)) {
096                            StringBundler sb = new StringBundler(4);
097    
098                            sb.append("security-manager-get-bean-property");
099                            sb.append(StringPool.OPEN_BRACKET);
100                            sb.append(servletContextName);
101                            sb.append(StringPool.CLOSE_BRACKET);
102    
103                            key = sb.toString();
104    
105                            if (Validator.isNotNull(property)) {
106                                    value = value + StringPool.POUND + property;
107                            }
108                    }
109                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_PORTLET_BAG_POOL)) {
110                            key = "security-manager-portlet-bag-pool-portlet-ids";
111                    }
112                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_SEARCH_ENGINE)) {
113                            key = "security-manager-search-engine-ids";
114                    }
115                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_SET_BEAN_PROPERTY)) {
116                            StringBundler sb = new StringBundler(4);
117    
118                            sb.append("security-manager-set-bean-property");
119                            sb.append(StringPool.OPEN_BRACKET);
120                            sb.append(servletContextName);
121                            sb.append(StringPool.CLOSE_BRACKET);
122    
123                            key = sb.toString();
124    
125                            if (Validator.isNotNull(property)) {
126                                    value = value + StringPool.POUND + property;
127                            }
128                    }
129                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_THREAD_POOL_EXECUTOR)) {
130                            key = "security-manager-thread-pool-executor-names";
131                    }
132                    else {
133                            return null;
134                    }
135    
136                    AuthorizationProperty authorizationProperty =
137                            new AuthorizationProperty();
138    
139                    authorizationProperty.setKey(key);
140                    authorizationProperty.setValue(value);
141    
142                    return authorizationProperty;
143            }
144    
145            public boolean implies(Permission permission) {
146                    PortalRuntimePermission portalRuntimePermission =
147                            (PortalRuntimePermission)permission;
148    
149                    String name = portalRuntimePermission.getShortName();
150                    String subject = portalRuntimePermission.getSubject();
151                    String servletContextName =
152                            portalRuntimePermission.getServletContextName();
153                    String property = GetterUtil.getString(
154                            portalRuntimePermission.getProperty());
155    
156                    if (name.equals(PORTAL_RUNTIME_PERMISSION_EXPANDO_BRIDGE)) {
157                            if (!_expandoBridgeClassNames.contains(subject)) {
158                                    logSecurityException(
159                                            _log, "Attempted to get Expando bridge on " + subject);
160    
161                                    return false;
162                            }
163                    }
164                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_GET_BEAN_PROPERTY)) {
165                            if (!hasGetBeanProperty(
166                                            servletContextName, subject, property, permission)) {
167    
168                                    if (Validator.isNotNull(property)) {
169                                            logSecurityException(
170                                                    _log,
171                                                    "Attempted to get bean property " + property + " on " +
172                                                            subject + " from " + servletContextName);
173                                    }
174                                    else {
175                                            logSecurityException(
176                                                    _log, "Attempted to get bean property on " + subject +
177                                                            " from " + servletContextName);
178                                    }
179    
180                                    return false;
181                            }
182                    }
183                    else if (name.startsWith(PORTAL_RUNTIME_PERMISSION_GET_CLASSLOADER)) {
184                            if (!hasGetClassLoader(subject, permission)) {
185                                    logSecurityException(
186                                            _log, "Attempted to get class loader " + subject);
187    
188                                    return false;
189                            }
190                    }
191                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_PORTLET_BAG_POOL)) {
192                            if (!hasPortletBagPoolPortletId(subject)) {
193                                    logSecurityException(
194                                            _log,
195                                            "Attempted to handle portlet bag pool portlet ID " +
196                                                    subject);
197    
198                                    return false;
199                            }
200                    }
201                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_SEARCH_ENGINE)) {
202                            if (!_searchEngineIds.contains(subject)) {
203                                    logSecurityException(
204                                            _log, "Attempted to get search engine " + subject);
205    
206                                    return false;
207                            }
208                    }
209                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_SET_BEAN_PROPERTY)) {
210                            if (!hasSetBeanProperty(servletContextName, subject, property)) {
211                                    if (Validator.isNotNull(property)) {
212                                            logSecurityException(
213                                                    _log,
214                                                    "Attempted to set bean property " + property + " on " +
215                                                            subject + " from " + servletContextName);
216                                    }
217                                    else {
218                                            logSecurityException(
219                                                    _log, "Attempted to set bean property on " + subject +
220                                                            " from " + servletContextName);
221                                    }
222    
223                                    return false;
224                            }
225                    }
226                    else if (name.equals(PORTAL_RUNTIME_PERMISSION_THREAD_POOL_EXECUTOR)) {
227                            if (!hasThreadPoolExecutorNames(subject)) {
228                                    logSecurityException(
229                                            _log,
230                                            "Attempted to modify thread pool executor " + subject);
231    
232                                    return false;
233                            }
234                    }
235    
236                    return true;
237            }
238    
239            protected boolean hasGetBeanProperty(
240                    String servletContextName, String className, String property,
241                    Permission permission) {
242    
243                    if (servletContextName.equals(getServletContextName())) {
244                            return true;
245                    }
246    
247                    int stackIndex = getStackIndex(13, 12);
248    
249                    Class<?> callerClass = Reflection.getCallerClass(stackIndex);
250    
251                    if (isTrustedCaller(callerClass, permission)) {
252                            stackIndex = stackIndex + 1;
253    
254                            if (callerClass.equals(BeanLocatorImpl.class)) {
255                                    stackIndex = stackIndex + 2;
256                            }
257    
258                            callerClass = Reflection.getCallerClass(stackIndex);
259    
260                            if (!callerClass.equals(TemplateContextHelper.class) &&
261                                    isTrustedCaller(callerClass, permission)) {
262    
263                                    return true;
264                            }
265                    }
266    
267                    Set<String> getBeanPropertyClassNames = _getBeanPropertyClassNames.get(
268                            servletContextName);
269    
270                    if (getBeanPropertyClassNames == null) {
271                            return false;
272                    }
273    
274                    if (getBeanPropertyClassNames.contains(className)) {
275                            return true;
276                    }
277    
278                    if (Validator.isNotNull(property)) {
279                            if (getBeanPropertyClassNames.contains(
280                                            className.concat(StringPool.POUND).concat(property))) {
281    
282                                    return true;
283                            }
284                    }
285    
286                    return false;
287            }
288    
289            protected boolean hasGetClassLoader(
290                    String classLoaderReferenceId, Permission permission) {
291    
292                    int stackIndex = getStackIndex(12, 11);
293    
294                    if (_classLoaderReferenceIds.contains(classLoaderReferenceId)) {
295                            return true;
296                    }
297    
298                    Class<?> callerClass = Reflection.getCallerClass(stackIndex);
299    
300                    String callerClassName = callerClass.getName();
301    
302                    if (callerClassName.equals(
303                                    PortalClassLoaderUtil.class.getName()) ||
304                            callerClassName.equals(
305                                    PortletClassLoaderUtil.class.getName())) {
306    
307                            callerClass = Reflection.getCallerClass(stackIndex + 1);
308                    }
309                    else if (callerClassName.equals(
310                                            DynamicQueryFactoryImpl.class.getName())) {
311    
312                            callerClass = Reflection.getCallerClass(stackIndex + 3);
313                    }
314    
315                    if (isTrustedCaller(callerClass, permission)) {
316                            return true;
317                    }
318    
319                    Class<?> superClass = callerClass.getSuperclass();
320    
321                    // Handle the case for our CLP classes
322    
323                    if (Modifier.isAbstract(callerClass.getModifiers()) &&
324                            (superClass.equals(BaseLocalServiceImpl.class) ||
325                             superClass.equals(BasePersistenceImpl.class) ||
326                             superClass.equals(BaseServiceImpl.class))) {
327    
328                            return true;
329                    }
330    
331                    return false;
332            }
333    
334            protected boolean hasPortletBagPoolPortletId(String portletId) {
335                    for (Pattern portletBagPoolPortletIdPattern :
336                                    _portletBagPoolPortletIdPatterns) {
337    
338                            Matcher matcher = portletBagPoolPortletIdPattern.matcher(portletId);
339    
340                            if (matcher.matches()) {
341                                    return true;
342                            }
343                    }
344    
345                    return false;
346            }
347    
348            protected boolean hasSetBeanProperty(
349                    String servletContextName, String className, String property) {
350    
351                    if (servletContextName.equals(getServletContextName())) {
352                            return true;
353                    }
354    
355                    Set<String> setBeanPropertyClassNames = _setBeanPropertyClassNames.get(
356                            servletContextName);
357    
358                    if (setBeanPropertyClassNames == null) {
359                            return false;
360                    }
361    
362                    if (setBeanPropertyClassNames.contains(className)) {
363                            return true;
364                    }
365    
366                    if (Validator.isNotNull(property)) {
367                            if (setBeanPropertyClassNames.contains(
368                                            className.concat(StringPool.POUND).concat(property))) {
369    
370                                    return true;
371                            }
372                    }
373    
374                    return false;
375            }
376    
377            protected boolean hasThreadPoolExecutorNames(
378                    String threadPoolExecutorName) {
379    
380                    for (Pattern threadPoolExecutorNamePattern :
381                                    _threadPoolExecutorNamePatterns) {
382    
383                            Matcher matcher = threadPoolExecutorNamePattern.matcher(
384                                    threadPoolExecutorName);
385    
386                            if (matcher.matches()) {
387                                    return true;
388                            }
389                    }
390    
391                    return false;
392            }
393    
394            protected void initClassLoaderReferenceIds() {
395                    _classLoaderReferenceIds = getPropertySet(
396                            "security-manager-class-loader-reference-ids");
397    
398                    if (_log.isDebugEnabled()) {
399                            Set<String> classLoaderReferenceIds = new TreeSet<String>(
400                                    _classLoaderReferenceIds);
401    
402                            for (String classLoaderReferenceId : classLoaderReferenceIds) {
403                                    _log.debug(
404                                            "Allowing access to class loader for reference " +
405                                                    classLoaderReferenceId);
406                            }
407                    }
408            }
409    
410            protected void initExpandoBridgeClassNames() {
411                    _expandoBridgeClassNames = getPropertySet(
412                            "security-manager-expando-bridge");
413    
414                    if (_log.isDebugEnabled()) {
415                            Set<String> classNames = new TreeSet<String>(
416                                    _expandoBridgeClassNames);
417    
418                            for (String className : classNames) {
419                                    _log.debug("Allowing Expando bridge on class " + className);
420                            }
421                    }
422            }
423    
424            protected void initGetBeanPropertyClassNames() {
425                    Properties properties = getProperties();
426    
427                    for (Map.Entry<Object, Object> entry : properties.entrySet()) {
428                            String key = (String)entry.getKey();
429                            String value = (String)entry.getValue();
430    
431                            if (!key.startsWith("security-manager-get-bean-property[")) {
432                                    continue;
433                            }
434    
435                            int x = key.indexOf("[");
436                            int y = key.indexOf("]", x);
437    
438                            String servletContextName = key.substring(x + 1, y);
439    
440                            Set<String> getBeanPropertyClassNames = SetUtil.fromArray(
441                                    StringUtil.split(value));
442    
443                            _getBeanPropertyClassNames.put(
444                                    servletContextName, getBeanPropertyClassNames);
445    
446                            if (_log.isDebugEnabled() &&
447                                    !servletContextName.equals(_PORTAL_SERVLET_CONTEXT_NAME)) {
448    
449                                    Set<String> classNames = new TreeSet<String>(
450                                            getBeanPropertyClassNames);
451    
452                                    for (String className : classNames) {
453                                            _log.debug(
454                                                    "Allowing get bean property from " +
455                                                            servletContextName + " on class " + className);
456                                    }
457                            }
458                    }
459    
460                    // Backwards compatibility
461    
462                    Set<String> getBeanPropertyClassNames = _getBeanPropertyClassNames.get(
463                            _PORTAL_SERVLET_CONTEXT_NAME);
464    
465                    if (getBeanPropertyClassNames == null) {
466                            getBeanPropertyClassNames = getPropertySet(
467                                    "security-manager-get-bean-property");
468                    }
469                    else {
470                            getBeanPropertyClassNames.addAll(
471                                    getPropertySet("security-manager-get-bean-property"));
472                    }
473    
474                    _getBeanPropertyClassNames.put(
475                            _PORTAL_SERVLET_CONTEXT_NAME, getBeanPropertyClassNames);
476    
477                    if (_log.isDebugEnabled()) {
478                            Set<String> classNames = new TreeSet<String>(
479                                    getBeanPropertyClassNames);
480    
481                            for (String className : classNames) {
482                                    _log.debug(
483                                            "Allowing get bean property from " +
484                                                    _PORTAL_SERVLET_CONTEXT_NAME + " on class " +
485                                                            className);
486                            }
487                    }
488            }
489    
490            protected void initPortletBagPoolPortletIds() {
491                    Set<String> portletBagPoolPortletIds = getPropertySet(
492                            "security-manager-portlet-bag-pool-portlet-ids");
493    
494                    _portletBagPoolPortletIdPatterns = new ArrayList<Pattern>(
495                            portletBagPoolPortletIds.size());
496    
497                    for (String portletBagPoolPortletId : portletBagPoolPortletIds) {
498                            Pattern portletBagPoolPortletIdPattern = Pattern.compile(
499                                    portletBagPoolPortletId);
500    
501                            _portletBagPoolPortletIdPatterns.add(
502                                    portletBagPoolPortletIdPattern);
503    
504                            if (_log.isDebugEnabled()) {
505                                    _log.debug(
506                                            "Allowing portlet bag pool portlet IDs that match the " +
507                                                    "regular expression " + portletBagPoolPortletId);
508                            }
509                    }
510            }
511    
512            protected void initSearchEngineIds() {
513                    _searchEngineIds = getPropertySet("security-manager-search-engine-ids");
514    
515                    if (_log.isDebugEnabled()) {
516                            Set<String> searchEngineIds = new TreeSet<String>(_searchEngineIds);
517    
518                            for (String searchEngineId : searchEngineIds) {
519                                    _log.debug("Allowing search engine " + searchEngineId);
520                            }
521                    }
522            }
523    
524            protected void initSetBeanPropertyClassNames() {
525                    Properties properties = getProperties();
526    
527                    for (Map.Entry<Object, Object> entry : properties.entrySet()) {
528                            String key = (String)entry.getKey();
529                            String value = (String)entry.getValue();
530    
531                            if (!key.startsWith("security-manager-set-bean-property[")) {
532                                    continue;
533                            }
534    
535                            int x = key.indexOf("[");
536                            int y = key.indexOf("]", x);
537    
538                            String servletContextName = key.substring(x + 1, y);
539    
540                            Set<String> setBeanPropertyClassNames = SetUtil.fromArray(
541                                    StringUtil.split(value));
542    
543                            _setBeanPropertyClassNames.put(
544                                    servletContextName, setBeanPropertyClassNames);
545    
546                            if (_log.isDebugEnabled() &&
547                                    !servletContextName.equals(_PORTAL_SERVLET_CONTEXT_NAME)) {
548    
549                                    Set<String> classNames = new TreeSet<String>(
550                                            setBeanPropertyClassNames);
551    
552                                    for (String className : classNames) {
553                                            _log.debug(
554                                                    "Allowing set bean property from " +
555                                                            servletContextName + " on class " + className);
556                                    }
557                            }
558                    }
559    
560                    // Backwards compatibility
561    
562                    Set<String> setBeanPropertyClassNames = _setBeanPropertyClassNames.get(
563                            _PORTAL_SERVLET_CONTEXT_NAME);
564    
565                    if (setBeanPropertyClassNames == null) {
566                            setBeanPropertyClassNames = getPropertySet(
567                                    "security-manager-set-bean-property");
568                    }
569                    else {
570                            setBeanPropertyClassNames.addAll(
571                                    getPropertySet("security-manager-set-bean-property"));
572                    }
573    
574                    _setBeanPropertyClassNames.put(
575                            _PORTAL_SERVLET_CONTEXT_NAME, setBeanPropertyClassNames);
576    
577                    if (_log.isDebugEnabled()) {
578                            Set<String> classNames = new TreeSet<String>(
579                                    setBeanPropertyClassNames);
580    
581                            for (String className : classNames) {
582                                    _log.debug(
583                                            "Allowing set bean property from " +
584                                                    _PORTAL_SERVLET_CONTEXT_NAME + " on class " +
585                                                            className);
586                            }
587                    }
588            }
589    
590            protected void initThreadPoolExecutorNames() {
591                    Set<String> threadPoolExecutorNames = getPropertySet(
592                            "security-manager-thread-pool-executor-names");
593    
594                    _threadPoolExecutorNamePatterns = new ArrayList<Pattern>(
595                            threadPoolExecutorNames.size());
596    
597                    for (String threadPoolExecutorName : threadPoolExecutorNames) {
598                            Pattern threadPoolExecutorNamePattern = Pattern.compile(
599                                    threadPoolExecutorName);
600    
601                            _threadPoolExecutorNamePatterns.add(threadPoolExecutorNamePattern);
602    
603                            if (_log.isDebugEnabled()) {
604                                    _log.debug(
605                                            "Allowing thread pool executors that match the regular " +
606                                                    "expression " + threadPoolExecutorName);
607                            }
608                    }
609            }
610    
611            private static final String _PORTAL_SERVLET_CONTEXT_NAME = "portal";
612    
613            private static Log _log = LogFactoryUtil.getLog(PortalRuntimeChecker.class);
614    
615            private Set<String> _classLoaderReferenceIds;
616            private Set<String> _expandoBridgeClassNames;
617            private Map<String, Set<String>> _getBeanPropertyClassNames =
618                    new HashMap<String, Set<String>>();
619            private List<Pattern> _portletBagPoolPortletIdPatterns;
620            private Set<String> _searchEngineIds;
621            private Map<String, Set<String>> _setBeanPropertyClassNames =
622                    new HashMap<String, Set<String>>();
623            private List<Pattern> _threadPoolExecutorNamePatterns;
624    
625    }