001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.security;
029
030import org.opencms.configuration.CmsSystemConfiguration;
031import org.opencms.db.CmsCacheSettings;
032import org.opencms.db.CmsDbContext;
033import org.opencms.db.CmsDriverManager;
034import org.opencms.db.CmsModificationContext;
035import org.opencms.db.CmsSecurityManager;
036import org.opencms.db.I_CmsCacheKey;
037import org.opencms.file.CmsProject;
038import org.opencms.file.CmsResource;
039import org.opencms.file.CmsResourceFilter;
040import org.opencms.file.CmsUser;
041import org.opencms.file.types.CmsResourceTypeJsp;
042import org.opencms.lock.CmsLock;
043import org.opencms.main.CmsException;
044import org.opencms.main.CmsInitException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.OpenCms;
047
048import java.util.Iterator;
049
050import org.apache.commons.logging.Log;
051
052/**
053 * Generic base driver interface.<p>
054 *
055 * @since 7.0.2
056 */
057public class CmsDefaultPermissionHandler implements I_CmsPermissionHandler {
058
059    /** The log object for this class. */
060    private static final Log LOG = CmsLog.getLog(CmsDefaultPermissionHandler.class);
061
062    /** Driver Manager instance. */
063    protected CmsDriverManager m_driverManager;
064
065    /** Security Manager instance. */
066    protected CmsSecurityManager m_securityManager;
067
068    /** The class used for cache key generation. */
069    private I_CmsCacheKey m_keyGenerator;
070
071    /**
072     * @see org.opencms.security.I_CmsPermissionHandler#hasPermissions(org.opencms.db.CmsDbContext, org.opencms.file.CmsResource, org.opencms.security.CmsPermissionSet, org.opencms.security.I_CmsPermissionHandler.LockCheck, org.opencms.file.CmsResourceFilter)
073     */
074    public CmsPermissionCheckResult hasPermissions(
075        CmsDbContext dbc,
076        CmsResource resource,
077        CmsPermissionSet requiredPermissions,
078        LockCheck checkLock,
079        CmsResourceFilter filter)
080    throws CmsException {
081
082        // check if the resource is valid according to the current filter
083        // if not, throw a CmsResourceNotFoundException
084        if (!filter.isValid(dbc.getRequestContext(), resource)) {
085            return I_CmsPermissionHandler.PERM_FILTERED;
086        }
087
088        // checking the filter is less cost intensive then checking the cache,
089        // this is why basic filter results are not cached
090        String requireVisibleStr = filter.requireVisible() ? "1" : "0";
091        String lockCheckStr = checkLock.getCode();
092        String keyPrefix = requireVisibleStr + lockCheckStr;
093
094        String cacheKey = m_keyGenerator.getCacheKeyForUserPermissions(keyPrefix, dbc, resource, requiredPermissions);
095        CmsPermissionCheckResult cacheResult = OpenCms.getMemoryMonitor().getCachedPermission(cacheKey);
096        if (cacheResult != null) {
097            return cacheResult;
098        }
099
100        int denied = 0;
101
102        // if this is the online project, write is rejected
103        if (dbc.currentProject().isOnlineProject()) {
104            denied |= CmsPermissionSet.PERMISSION_WRITE;
105        }
106
107        // check if the current user is admin
108        boolean canIgnorePermissions = m_securityManager.hasRoleForResource(
109            dbc,
110            dbc.currentUser(),
111            CmsRole.VFS_MANAGER,
112            resource);
113
114        // check lock status
115        boolean writeRequired = requiredPermissions.requiresWritePermission()
116            || requiredPermissions.requiresControlPermission();
117
118        // if the resource type is jsp
119        // write is only allowed for administrators
120        if (writeRequired && !canIgnorePermissions && (CmsResourceTypeJsp.isJsp(resource))) {
121            if (!m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.VFS_MANAGER, resource)) {
122                denied |= CmsPermissionSet.PERMISSION_WRITE;
123                denied |= CmsPermissionSet.PERMISSION_CONTROL;
124            }
125        }
126
127        if (writeRequired && (checkLock != LockCheck.no)) {
128            // check lock state only if required
129            CmsLock lock = m_driverManager.getLock(dbc, resource);
130            // if the resource is not locked by the current user, write and control
131            // access must cause a permission error that must not be cached
132            if (lock.isUnlocked() || !lock.isLockableBy(dbc.currentUser())) {
133                return I_CmsPermissionHandler.PERM_NOTLOCKED;
134            }
135            // if we have a shallow lock, but need a non-shallow one, return NOTLOCKED
136            if (lock.getType().isShallow() && (checkLock != LockCheck.shallowOnly)) {
137                return I_CmsPermissionHandler.PERM_NOTLOCKED;
138            }
139        }
140
141        CmsPermissionSetCustom permissions;
142        if (canIgnorePermissions) {
143            // if the current user is administrator, anything is allowed
144            permissions = new CmsPermissionSetCustom(~0);
145        } else {
146            // otherwise, get the permissions from the access control list
147            permissions = m_driverManager.getPermissions(dbc, resource, dbc.currentUser());
148        }
149
150        // revoke the denied permissions
151        permissions.denyPermissions(denied);
152
153        if ((permissions.getPermissions() & CmsPermissionSet.PERMISSION_VIEW) == 0) {
154            // resource "invisible" flag is set for this user
155            if (!canIgnorePermissions && filter.requireVisible()) {
156                // filter requires visible permission - extend required permission set
157                requiredPermissions = new CmsPermissionSet(
158                    requiredPermissions.getAllowedPermissions() | CmsPermissionSet.PERMISSION_VIEW,
159                    requiredPermissions.getDeniedPermissions());
160            } else {
161                // view permissions can be ignored by filter
162                permissions.setPermissions(
163                    // modify permissions so that view is allowed
164                    permissions.getAllowedPermissions() | CmsPermissionSet.PERMISSION_VIEW,
165                    permissions.getDeniedPermissions() & ~CmsPermissionSet.PERMISSION_VIEW);
166            }
167        }
168
169        if (requiredPermissions.requiresDirectPublishPermission()) {
170            // direct publish permission is required
171            if ((permissions.getPermissions() & CmsPermissionSet.PERMISSION_DIRECT_PUBLISH) == 0) {
172                // but the user has no direct publish permission, so check if the user has the project manager role
173                boolean canIgnorePublishPermission = CmsModificationContext.isInstantPublishing()
174                    || m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.PROJECT_MANAGER, resource);
175                // if not, check the manageable projects
176                if (!canIgnorePublishPermission) {
177                    CmsUser user = dbc.currentUser();
178                    Iterator<CmsProject> itProjects = m_driverManager.getAllManageableProjects(
179                        dbc,
180                        m_driverManager.readOrganizationalUnit(dbc, user.getOuFqn()),
181                        true).iterator();
182                    while (itProjects.hasNext()) {
183                        CmsProject project = itProjects.next();
184                        if (CmsProject.isInsideProject(m_driverManager.readProjectResources(dbc, project), resource)) {
185                            canIgnorePublishPermission = true;
186                            break;
187                        }
188                    }
189                }
190
191                if (canIgnorePublishPermission) {
192                    // direct publish permission can be ignored
193                    permissions.setPermissions(
194                        // modify permissions so that direct publish is allowed
195                        permissions.getAllowedPermissions() | CmsPermissionSet.PERMISSION_DIRECT_PUBLISH,
196                        permissions.getDeniedPermissions() & ~CmsPermissionSet.PERMISSION_DIRECT_PUBLISH);
197                }
198            }
199        }
200
201        CmsPermissionCheckResult result;
202        if ((requiredPermissions.getPermissions()
203            & (permissions.getPermissions())) == requiredPermissions.getPermissions()) {
204            result = I_CmsPermissionHandler.PERM_ALLOWED;
205        } else {
206            result = I_CmsPermissionHandler.PERM_DENIED;
207            if (LOG.isDebugEnabled()) {
208                LOG.debug(
209                    Messages.get().getBundle().key(
210                        Messages.LOG_NO_PERMISSION_RESOURCE_USER_4,
211                        new Object[] {
212                            dbc.getRequestContext().removeSiteRoot(resource.getRootPath()),
213                            dbc.currentUser().getName(),
214                            requiredPermissions.getPermissionString(),
215                            permissions.getPermissionString()}));
216            }
217        }
218        if (dbc.getProjectId().isNullUUID() && permissions.isCacheable()) {
219            OpenCms.getMemoryMonitor().cachePermission(cacheKey, result);
220        }
221        if (!permissions.isCacheable()) {
222            // if this method is used for checking permissions in resource lists, the resulting resource lists
223            // are not cacheable either if the permission check for this resource isn't
224            boolean[] nocacheArray = (boolean[])dbc.getAttribute(CmsDriverManager.ATTR_PERMISSION_NOCACHE);
225            if (nocacheArray != null) {
226                nocacheArray[0] = true;
227            }
228        }
229
230        return result;
231    }
232
233    /**
234     * @see org.opencms.security.I_CmsPermissionHandler#init(org.opencms.db.CmsDriverManager, CmsSystemConfiguration)
235     */
236    public void init(CmsDriverManager driverManager, CmsSystemConfiguration systemConfiguration) {
237
238        m_driverManager = driverManager;
239        m_securityManager = driverManager.getSecurityManager();
240
241        CmsCacheSettings settings = systemConfiguration.getCacheSettings();
242
243        String className = settings.getCacheKeyGenerator();
244        try {
245            // initialize the key generator
246            m_keyGenerator = (I_CmsCacheKey)Class.forName(className).newInstance();
247        } catch (Exception e) {
248            throw new CmsInitException(
249                org.opencms.main.Messages.get().container(
250                    org.opencms.main.Messages.ERR_CRITICAL_CLASS_CREATION_1,
251                    className),
252                e);
253        }
254    }
255}