Package org.apache.jackrabbit.core.security.authorization.acl

Source Code of org.apache.jackrabbit.core.security.authorization.acl.PentahoCompiledPermissionsImpl

/*!
* Copyright 2010 - 2013 Pentaho Corporation.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.apache.jackrabbit.core.security.authorization.acl;

import org.apache.jackrabbit.api.JackrabbitWorkspace;
import org.apache.jackrabbit.core.ItemImpl;
import org.apache.jackrabbit.core.ItemManager;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.cache.GrowingLRUMap;
import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.id.PropertyId;
import org.apache.jackrabbit.core.security.authorization.AbstractCompiledPermissions;
import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
import org.apache.jackrabbit.core.security.authorization.AccessControlListener;
import org.apache.jackrabbit.core.security.authorization.AccessControlModifications;
import org.apache.jackrabbit.core.security.authorization.AccessControlUtils;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.core.security.authorization.PrivilegeBits;
import org.apache.jackrabbit.core.security.authorization.PrivilegeManagerImpl;
import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.util.Text;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.repository2.unified.ServerRepositoryPaths;
import org.pentaho.platform.repository2.unified.jcr.JcrRepositoryFileAclUtils;
import org.pentaho.platform.repository2.unified.jcr.JcrRepositoryFileUtils;
import org.pentaho.platform.repository2.unified.jcr.PentahoJcrConstants;

import javax.jcr.ItemNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.security.AccessControlEntry;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* A copy-paste of {@code CompiledPermissionsImpl} modified with more lenient locking on cache to prevent deadlocks
* between {@link #clearCache} and {@link #canRead} as detailed in <a
* href="http://jira.pentaho.com/browse/BISERVER-8382">BISERVER-8382</a><br/>
* This shouldn't differ from {@code CompiledPermissionsImpl} except for the extra lock and the
* <code>synchronized</code> changes within {@link #canRead(Path, ItemId)}
*
* @see CompiledPermissionsImpl
*/
public class PentahoCompiledPermissionsImpl extends AbstractCompiledPermissions implements AccessControlListener {

  private final List<String> principalNames;
  private final SessionImpl session;
  private final EntryCollector entryCollector;
  private final AccessControlUtils util;

  /*
   * Start with initial map size of 1024 and grow up to 5000 before removing LRU items.
   */
  @SuppressWarnings( "unchecked" )
  private final Map<ItemId, Boolean> readCache = new GrowingLRUMap( 1024, 5000 );
  private final Object monitor = new Object();
  private final Object readMonitor = new Object();

  PentahoCompiledPermissionsImpl( Set<Principal> principals, SessionImpl session, EntryCollector entryCollector,
      AccessControlUtils util, boolean listenToEvents ) throws RepositoryException {
    this.session = session;
    this.entryCollector = entryCollector;
    this.util = util;

    principalNames = new ArrayList<String>( principals.size() );
    for ( Principal princ : principals ) {
      principalNames.add( princ.getName() );
    }

    if ( listenToEvents ) {
      /*
       * Make sure this AclPermission recalculates the permissions if any ACL concerning it is modified.
       */
      entryCollector.addListener( this );
    }
  }

  private Result buildResult( NodeImpl node, boolean isExistingNode, boolean isAcItem, EntryFilterImpl filter )
    throws RepositoryException {
    // retrieve all ACEs at path or at the direct ancestor of path that
    // apply for the principal names.
    NodeImpl n = ACLProvider.getNode( node, isAcItem );
    Iterator<AccessControlEntry> entries = entryCollector.collectEntries( n, filter ).iterator();

    /*
     * Calculate privileges and permissions: Since the ACEs only define privileges on a node and do not allow to
     * add additional restrictions, the permissions can be determined without taking the given target name or
     * target item into account.
     */
    int allows = Permission.NONE;
    int denies = Permission.NONE;

    PrivilegeBits allowBits = PrivilegeBits.getInstance();
    PrivilegeBits denyBits = PrivilegeBits.getInstance();
    PrivilegeBits parentAllowBits = PrivilegeBits.getInstance();
    PrivilegeBits parentDenyBits = PrivilegeBits.getInstance();

    String parentPath = Text.getRelativeParent( filter.getPath(), 1 );
    NodeId nodeId = ( node == null ) ? null : node.getNodeId();

    while ( entries.hasNext() ) {
      ACLTemplate.Entry ace = (ACLTemplate.Entry) entries.next();
      /*
       * Determine if the ACE also takes effect on the parent: Some permissions (e.g. add-node or removal) must be
       * determined from privileges defined for the parent. A 'local' entry defined on the target node never
       * effects the parent. For inherited ACEs determine if the ACE matches the parent path.
       */
      PrivilegeBits entryBits = ace.getPrivilegeBits();
      boolean isLocal = isExistingNode && ace.isLocal( nodeId );
      boolean matchesParent = ( !isLocal && ace.matches( parentPath ) );

      // check specific case: "Inherit permissions" may have been unchecked, and node operation permissions may
      // have been granted directly to the item ( thus not requiring having those permissions defined for the parent )
      boolean isLocalAndDoesNotInheritPermissions = isLocal && isValidPentahoNode( node ) && !isEntriesInheriting( node );
      if ( matchesParent || isLocalAndDoesNotInheritPermissions ) {
        if ( ace.isAllow() ) {
          parentAllowBits.addDifference( entryBits, parentDenyBits );
        } else {
          parentDenyBits.addDifference( entryBits, parentAllowBits );
        }
      }
      if ( ace.isAllow() ) {
        allowBits.addDifference( entryBits, denyBits );
        int permissions = PrivilegeRegistry.calculatePermissions( allowBits, parentAllowBits, true, isAcItem );
        allows |= Permission.diff( permissions, denies );
      } else {
        denyBits.addDifference( entryBits, allowBits );
        int permissions = PrivilegeRegistry.calculatePermissions( denyBits, parentDenyBits, false, isAcItem );
        denies |= Permission.diff( permissions, allows );
      }
    }

    return new Result( allows, denies, allowBits, denyBits );
  }

  // ------------------------------------< AbstractCompiledPermissions >---
  /**
   * @see AbstractCompiledPermissions#buildResult(org.apache.jackrabbit.spi.Path)
   */
  @Override
  protected Result buildResult( Path absPath ) throws RepositoryException {
    boolean existingNode = false;
    NodeImpl node;

    ItemManager itemMgr = session.getItemManager();
    try {
      ItemImpl item = itemMgr.getItem( absPath );
      if ( item.isNode() ) {
        node = (NodeImpl) item;
        existingNode = true;
      } else {
        node = (NodeImpl) item.getParent();
      }
    } catch ( RepositoryException e ) {
      // path points to a non-persisted item.
      // -> find the nearest persisted node starting from the root.
      Path.Element[] elems = absPath.getElements();
      NodeImpl parent = (NodeImpl) session.getRootNode();
      for ( int i = 1; i < elems.length - 1; i++ ) {
        Name name = elems[i].getName();
        int index = elems[i].getIndex();
        if ( !parent.hasNode( name, index ) ) {
          // last persisted node reached
          break;
        }
        parent = parent.getNode( name, index );

      }
      node = parent;
    }

    if ( node == null ) {
      // should never get here
      throw new ItemNotFoundException( "Item out of hierarchy." );
    }

    boolean isAcItem = util.isAcItem( absPath );
    return buildResult( node, existingNode, isAcItem, new EntryFilterImpl( principalNames, absPath, session ) );
  }

  @Override
  protected Result buildRepositoryResult() throws RepositoryException {
    return buildResult( null, true, false, new EntryFilterImpl( principalNames, session.getQPath( "/" ), session ) );
  }

  /**
   * @see AbstractCompiledPermissions#getPrivilegeManagerImpl()
   */
  @Override
  protected PrivilegeManagerImpl getPrivilegeManagerImpl() throws RepositoryException {
    return (PrivilegeManagerImpl) ( (JackrabbitWorkspace) session.getWorkspace() ).getPrivilegeManager();
  }

  /**
   * @see AbstractCompiledPermissions#clearCache()
   */
  @Override
  protected void clearCache() {
    synchronized ( monitor ) {
      readCache.clear();
    }
    super.clearCache();
  }

  // --------------------------------------------< CompiledPermissions >---
  /**
   * @see org.apache.jackrabbit.core.security.authorization.CompiledPermissions#close()
   */
  @Override
  public void close() {
    entryCollector.removeListener( this );
    // NOTE: do not logout shared session.
    super.close();
  }

  /**
   * Changed so that access to entryCollector is done outside of a <code>monitor</code> synchronized block.<br/>
   * Should be functionally equivalent to {@link CompiledPermissions#canRead(Path, ItemId)}
   *
   * @see org.apache.jackrabbit.core.security.authorization.CompiledPermissions#canRead(Path, ItemId)
   */
  public boolean canRead( Path path, ItemId itemId ) throws RepositoryException {
    ItemId id = ( itemId == null ) ? session.getHierarchyManager().resolvePath( path ) : itemId;
    // no extra check for existence as method may only be called for existing items.
    boolean isExistingNode = id.denotesNode();
    boolean canRead = false;
    // emulates behavior of CompiledPermissionsImpl with two locks
    // synchronized (readMonitor) {
    synchronized ( monitor ) {
      if ( readCache.containsKey( id ) ) {
        return readCache.get( id );
      }
    }

    ItemManager itemMgr = session.getItemManager();
    NodeId nodeId = ( isExistingNode ) ? (NodeId) id : ( (PropertyId) id ).getParentId();
    NodeImpl node = (NodeImpl) itemMgr.getItem( nodeId );

    boolean isAcItem = util.isAcItem( node );
    EntryFilterImpl filter;
    if ( path == null ) {
      filter = new EntryFilterImpl( principalNames, id, session );
    } else {
      filter = new EntryFilterImpl( principalNames, path, session );
    }

    if ( isAcItem ) {
      /* item defines ac content -> regular evaluation */
      Result result = buildResult( node, isExistingNode, isAcItem, filter );
      canRead = result.grants( Permission.READ );
    } else {
      /*
       * simplified evaluation focusing on READ permission. this allows to omit evaluation of parent node
       * permissions that are required when calculating the complete set of permissions (see special treatment of
       * remove, create or ac-specific permissions).
       */
      for ( AccessControlEntry accessControlEntry : entryCollector.collectEntries( node, filter ) ) {
        ACLTemplate.Entry ace = (ACLTemplate.Entry) accessControlEntry;
        if ( ace.getPrivilegeBits().includesRead() ) {
          canRead = ace.isAllow();
          break;
        }
      }
    }
    synchronized ( monitor ) {
      readCache.put( id, canRead );
    }
    // } // readMonitor
    return canRead;
  }

  // ----------------------------------------< ACLModificationListener >---
  /**
   * @see org.apache.jackrabbit.core.security.authorization.AccessControlListener#
   *      acModified(org.apache.jackrabbit.core.security.authorization.AccessControlModifications)
   */
  public void acModified( AccessControlModifications modifications ) {
    // ignore the details of the modifications and clear all caches.
    clearCache();
  }

  /**
   * Returns stored entriesInheriting flag for given node
   */
  private boolean isEntriesInheriting( final NodeImpl node ) throws RepositoryException {
   return JcrRepositoryFileAclUtils.getAclMetadata( session, node.getPath(), new ACLTemplate( node.getNode(
        AccessControlConstants.N_POLICY ) ) ).isEntriesInheriting();
  }

  private boolean isBelowRootFolder( final NodeImpl node ) throws RepositoryException {

    final String tenantRootFolderPath = ServerRepositoryPaths.getTenantRootFolderPath() + RepositoryFile.SEPARATOR;

    return node != null && node.getPath().startsWith( tenantRootFolderPath )
            && node.getPath().replace( tenantRootFolderPath , "" ).length() > 0;
  }

  private boolean isValidPentahoNode( final NodeImpl node ) throws RepositoryException {
    return node != null && isBelowRootFolder( node )
            && JcrRepositoryFileUtils.isPentahoHierarchyNode( session, new PentahoJcrConstants( session ), node );
  }
}
TOP

Related Classes of org.apache.jackrabbit.core.security.authorization.acl.PentahoCompiledPermissionsImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.