/*
 * File   : $Source: /usr/local/cvs/opencms/src/org/opencms/security/CmsDefaultPermissionHandler.java,v $
 * Date   : $Date: 2008-05-16 09:17:50 $
 * Version: $Revision: 1.6 $
 *
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.security;

import org.opencms.configuration.CmsSystemConfiguration;
import org.opencms.db.CmsCacheSettings;
import org.opencms.db.CmsDbContext;
import org.opencms.db.CmsDriverManager;
import org.opencms.db.CmsSecurityManager;
import org.opencms.db.I_CmsCacheKey;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.types.CmsResourceTypeJsp;
import org.opencms.lock.CmsLock;
import org.opencms.main.CmsException;
import org.opencms.main.CmsInitException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;

import java.util.Iterator;

import org.apache.commons.logging.Log;

/**
 * Generic base driver interface.<p>
 * 
 * @author Michael Moossen
 * 
 * @version $Revision: 1.6 $
 * 
 * @since 7.0.2
 */
public class CmsDefaultPermissionHandler implements I_CmsPermissionHandler {

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsDefaultPermissionHandler.class);

    /** Driver Manager instance. */
    protected CmsDriverManager m_driverManager;

    /** Security Manager instance. */
    protected CmsSecurityManager m_securityManager;

    /** The class used for cache key generation. */
    private I_CmsCacheKey m_keyGenerator;

    /**
     * @see org.opencms.security.I_CmsPermissionHandler#hasPermissions(org.opencms.db.CmsDbContext, org.opencms.file.CmsResource, org.opencms.security.CmsPermissionSet, boolean, org.opencms.file.CmsResourceFilter)
     */
    public CmsPermissionCheckResult hasPermissions(
        CmsDbContext dbc,
        CmsResource resource,
        CmsPermissionSet requiredPermissions,
        boolean checkLock,
        CmsResourceFilter filter) throws CmsException {

        // check if the resource is valid according to the current filter
        // if not, throw a CmsResourceNotFoundException
        if (!filter.isValid(dbc.getRequestContext(), resource)) {
            return I_CmsPermissionHandler.PERM_FILTERED;
        }

        // checking the filter is less cost intensive then checking the cache,
        // this is why basic filter results are not cached
        String cacheKey = m_keyGenerator.getCacheKeyForUserPermissions(
            filter.requireVisible() && checkLock ? "11" : (!filter.requireVisible() && checkLock ? "01"
            : (filter.requireVisible() && !checkLock ? "10" : "00")),
            dbc,
            resource,
            requiredPermissions);
        CmsPermissionCheckResult cacheResult = OpenCms.getMemoryMonitor().getCachedPermission(cacheKey);
        if (cacheResult != null) {
            return cacheResult;
        }

        int denied = 0;

        // if this is the online project, write is rejected 
        if (dbc.currentProject().isOnlineProject()) {
            denied |= CmsPermissionSet.PERMISSION_WRITE;
        }

        // check if the current user is admin
        boolean canIgnorePermissions = m_securityManager.hasRoleForResource(
            dbc,
            dbc.currentUser(),
            CmsRole.VFS_MANAGER,
            resource);

        // check lock status 
        boolean writeRequired = requiredPermissions.requiresWritePermission()
            || requiredPermissions.requiresControlPermission();

        // if the resource type is jsp
        // write is only allowed for administrators
        if (writeRequired && !canIgnorePermissions && (resource.getTypeId() == CmsResourceTypeJsp.getStaticTypeId())) {
            if (!m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.DEVELOPER, resource)) {
                denied |= CmsPermissionSet.PERMISSION_WRITE;
                denied |= CmsPermissionSet.PERMISSION_CONTROL;
            }
        }

        if (writeRequired && checkLock) {
            // check lock state only if required
            CmsLock lock = m_driverManager.getLock(dbc, resource);
            // if the resource is not locked by the current user, write and control 
            // access must cause a permission error that must not be cached
            if (lock.isUnlocked() || !lock.isLockableBy(dbc.currentUser())) {
                return I_CmsPermissionHandler.PERM_NOTLOCKED;
            }
        }

        CmsPermissionSetCustom permissions;
        if (canIgnorePermissions) {
            // if the current user is administrator, anything is allowed
            permissions = new CmsPermissionSetCustom(~0);
        } else {
            // otherwise, get the permissions from the access control list
            permissions = m_driverManager.getPermissions(dbc, resource, dbc.currentUser());
        }

        // revoke the denied permissions
        permissions.denyPermissions(denied);

        if ((permissions.getPermissions() & CmsPermissionSet.PERMISSION_VIEW) == 0) {
            // resource "invisible" flag is set for this user
            if (filter.requireVisible()) {
                // filter requires visible permission - extend required permission set
                requiredPermissions = new CmsPermissionSet(requiredPermissions.getAllowedPermissions()
                    | CmsPermissionSet.PERMISSION_VIEW, requiredPermissions.getDeniedPermissions());
            } else {
                // view permissions can be ignored by filter
                permissions.setPermissions(
                // modify permissions so that view is allowed
                    permissions.getAllowedPermissions() | CmsPermissionSet.PERMISSION_VIEW,
                    permissions.getDeniedPermissions() & ~CmsPermissionSet.PERMISSION_VIEW);
            }
        }

        if (requiredPermissions.requiresDirectPublishPermission()) {
            // direct publish permission is required
            if ((permissions.getPermissions() & CmsPermissionSet.PERMISSION_DIRECT_PUBLISH) == 0) {
                // but the user has no direct publish permission, so check if the user has the project manager role
                boolean canIgnorePublishPermission = m_securityManager.hasRoleForResource(
                    dbc,
                    dbc.currentUser(),
                    CmsRole.PROJECT_MANAGER,
                    resource);
                // if not, check the manageable projects
                if (!canIgnorePublishPermission) {
                    Iterator itProjects = m_driverManager.getAllManageableProjects(
                        dbc,
                        m_driverManager.readOrganizationalUnit(dbc, ""),
                        true).iterator();
                    while (itProjects.hasNext()) {
                        CmsProject project = (CmsProject)itProjects.next();
                        if (CmsProject.isInsideProject(m_driverManager.readProjectResources(dbc, project), resource)) {
                            canIgnorePublishPermission = true;
                            break;
                        }
                    }
                }

                if (canIgnorePublishPermission) {
                    // direct publish permission can be ignored
                    permissions.setPermissions(
                    // modify permissions so that direct publish is allowed
                        permissions.getAllowedPermissions() | CmsPermissionSet.PERMISSION_DIRECT_PUBLISH,
                        permissions.getDeniedPermissions() & ~CmsPermissionSet.PERMISSION_DIRECT_PUBLISH);
                }
            }
        }

        CmsPermissionCheckResult result;
        if ((requiredPermissions.getPermissions() & (permissions.getPermissions())) == requiredPermissions.getPermissions()) {
            result = I_CmsPermissionHandler.PERM_ALLOWED;
        } else {
            result = I_CmsPermissionHandler.PERM_DENIED;
            if (LOG.isDebugEnabled()) {
                LOG.debug(Messages.get().getBundle().key(
                    Messages.LOG_NO_PERMISSION_RESOURCE_USER_4,
                    new Object[] {
                        dbc.getRequestContext().removeSiteRoot(resource.getRootPath()),
                        dbc.currentUser().getName(),
                        requiredPermissions.getPermissionString(),
                        permissions.getPermissionString()}));
            }
        }
        if (dbc.getProjectId().isNullUUID()) {
            OpenCms.getMemoryMonitor().cachePermission(cacheKey, result);
        }

        return result;
    }

    /**
     * @see org.opencms.security.I_CmsPermissionHandler#init(org.opencms.db.CmsDriverManager, CmsSystemConfiguration)
     */
    public void init(CmsDriverManager driverManager, CmsSystemConfiguration systemConfiguration) {

        m_driverManager = driverManager;
        m_securityManager = driverManager.getSecurityManager();

        CmsCacheSettings settings = systemConfiguration.getCacheSettings();

        String className = settings.getCacheKeyGenerator();
        try {
            // initialize the key generator
            m_keyGenerator = (I_CmsCacheKey)Class.forName(className).newInstance();
        } catch (Exception e) {
            throw new CmsInitException(org.opencms.main.Messages.get().container(
                org.opencms.main.Messages.ERR_CRITICAL_CLASS_CREATION_1,
                className), e);
        }
    }
}
