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

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

/*!
* 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.commons.lang.ArrayUtils;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.id.NodeId;
import org.pentaho.platform.api.engine.IAuthorizationPolicy;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.engine.ObjectFactoryException;
import org.pentaho.platform.api.mt.ITenant;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.security.SecurityHelper;
import org.pentaho.platform.repository2.unified.jcr.IAclMetadataStrategy.AclMetadata;
import org.pentaho.platform.repository2.unified.jcr.JcrRepositoryFileAclUtils;
import org.pentaho.platform.repository2.unified.jcr.JcrTenantUtils;
import org.pentaho.platform.security.policy.rolebased.AbstractJcrBackedRoleBindingDao;
import org.pentaho.platform.security.policy.rolebased.IRoleAuthorizationPolicyRoleBindingDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.util.Assert;

import javax.jcr.RepositoryException;
import javax.jcr.observation.Event;
import javax.jcr.observation.ObservationManager;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.Privilege;
import javax.jcr.version.VersionHistory;
import java.security.Principal;
import java.security.acl.Group;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
* Copy-and-paste of {@code org.apache.jackrabbit.core.security.authorization.acl.EntryCollector} in Jackrabbit 2.4.0.
* This class is in {@code org.apache.jackrabbit.core.security.authorization.acl} package due to the scope of
* collaborating classes.
* <p/>
* <p> Changes to original: </p> <ul> <li>{@code Entries} always have {@code null} {@code nextId}.</li> <li>{@code
* collectEntries()} copied from {@code EntryCollector} uses {@code entries.getNextId()} instead of {@code
* node.getParentId()}</li> <li>{@code filterEntries()} copied from {@code EntryCollector} as it was {@code static} and
* {@code private}.</li> <li>No caching is done in the presence of dynamic ACEs. This may need to be revisited but due
* to the short lifetime of the way we use Sessions, it may be acceptable.</li> <li>Understands {@code
* AclMetadataPrincipal}.</li> <li>Adds {@code MagicPrincipal}s on the fly.</li> <li>If access decision on
* versionStorage, then find the associated file node and use that ACL.</li>
* <p/>
* </ul>
*
* @author mlowery
*/
public class PentahoEntryCollector extends EntryCollector {

  /**
   * logger instance
   */
  private static final Logger log = LoggerFactory.getLogger( PentahoEntryCollector.class );

  private List<MagicAceDefinition> magicAceDefinitions = new ArrayList<MagicAceDefinition>();

  public PentahoEntryCollector( final SessionImpl systemSession, final NodeId rootID, final Map configuration )
    throws RepositoryException {
    super( systemSession, rootID );
    parseMagicAceDefinitions( configuration );
  }

  /**
   * Parses all magic ACE definitions.
   */
  protected void parseMagicAceDefinitions( final Map configuration ) throws RepositoryException {
    for ( int i = 0;; i++ ) {
      String value = (String) configuration.get( "magicAceDefinition" + i ); //$NON-NLS-1$
      if ( value == null ) {
        break;
      }
      MagicAceDefinition pam = parseMagicAceDefinition( value );
      magicAceDefinitions.add( pam );
    }
    if ( log.isDebugEnabled() ) {
      log.debug( "magic ACE definitions: " + magicAceDefinitions ); //$NON-NLS-1$
    }
  }

  /**
   * Parses a single magic ACE definition.
   */
  protected MagicAceDefinition parseMagicAceDefinition( final String value ) throws RepositoryException {
    String[] tokens = value.split( "\\;" ); //$NON-NLS-1$
    String path = tokens[ 0 ];
    String logicalRole = tokens[ 1 ];
    String privilegeString = tokens[ 2 ];
    boolean applyToTarget = Boolean.valueOf( tokens[ 3 ] );
    boolean applyToChildren = Boolean.valueOf( tokens[ 4 ] );
    boolean applyToAncestors = Boolean.valueOf( tokens[ 5 ] );
    String[] exceptChildren = null;
    if ( tokens.length > 6 ) {
      exceptChildren = new String[ tokens.length - 6 ];
      for ( int i = 6; i < tokens.length; i++ ) {
        exceptChildren[ i - 6 ] = tokens[ i ];
      }
    }

    String[] privilegeTokens = privilegeString.split( "\\," ); //$NON-NLS-1$
    List<Privilege> privileges = new ArrayList<Privilege>( privilegeTokens.length );
    for ( String privilegeToken : privilegeTokens ) {
      privileges.add( systemSession.getAccessControlManager().privilegeFromName( privilegeToken ) );
    }

    return new MagicAceDefinition( path, logicalRole, privileges.toArray( new Privilege[ 0 ] ), applyToTarget,
      applyToChildren, applyToAncestors, exceptChildren );
  }

  /**
   * Find the ancestor (maybe the node itself) that is access-controlled.
   */
  protected NodeImpl findAccessControlledNode( final NodeImpl node ) throws RepositoryException {
    NodeImpl currentNode = node;
    // skip all nodes that are not access-controlled; might eventually hit root which is always access-controlled
    while ( !ACLProvider.isAccessControlled( currentNode ) ) {
      currentNode = (NodeImpl) currentNode.getParent();
    }
    return currentNode;
  }

  /**
   * Find the ancestor (maybe the node itself) that is not inheriting ACEs.
   */
  protected NodeImpl findNonInheritingNode( final NodeImpl node ) throws RepositoryException {
    NodeImpl currentNode = node;
    ACLTemplate acl;
    while ( true ) {
      currentNode = findAccessControlledNode( currentNode );
      acl = new ACLTemplate( currentNode.getNode( N_POLICY ) );

      // skip all nodes that are inheriting
      AclMetadata aclMetadata = JcrRepositoryFileAclUtils.getAclMetadata( systemSession, currentNode.getPath(), acl );
      if ( aclMetadata != null && aclMetadata.isEntriesInheriting() ) {
        currentNode = (NodeImpl) currentNode.getParent();
        continue;
      }
      break;
    }
    return currentNode;
  }

  /**
   * Returns an {@code Entries} for the given node. This is where most of the customization lives.
   */
  @Override
  protected Entries getEntries( final NodeImpl node ) throws RepositoryException {
    // find nearest node with an ACL that is not inheriting ACEs
    NodeImpl currentNode = node;
    ACLTemplate acl;

    // version history governed by ACL on "versionable" which could be the root if no version history exists for
    // file;
    // if we do hit the root, then you get jcr:read for everyone which is acceptable
    if ( currentNode.getPath().startsWith( "/jcr:system/jcr:versionStorage" ) ) { //$NON-NLS-1$
      currentNode = getVersionable( currentNode );
    }

    // find first access-controlled node
    currentNode = findAccessControlledNode( currentNode );
    acl = new ACLTemplate( currentNode.getNode( N_POLICY ) );

    // owner comes from the first access-controlled node
    String owner = null;
    AclMetadata aclMetadata = JcrRepositoryFileAclUtils.getAclMetadata( systemSession, currentNode.getPath(), acl );
    if ( aclMetadata != null ) {
      owner = aclMetadata.getOwner();
    }

    // find the ACL
    NodeImpl firstAccessControlledNode = currentNode;
    currentNode = findNonInheritingNode( currentNode );
    acl = new ACLTemplate( currentNode.getNode( N_POLICY ) );

    // If we're inheriting from another node, check to see if that node has removeChildNodes or addChildNodes
    // permissions. This needs to transform to become addChild removeChild
    if ( !currentNode.isSame( node ) ) {
      Privilege removeNodePrivilege =
        systemSession.getAccessControlManager().privilegeFromName( Privilege.JCR_REMOVE_NODE );

      Privilege removeChildNodesPrivilege =
        systemSession.getAccessControlManager().privilegeFromName( Privilege.JCR_REMOVE_CHILD_NODES );

      for ( AccessControlEntry entry : acl.getEntries() ) {

        Privilege[] expandedPrivileges = JcrRepositoryFileAclUtils.expandPrivileges( entry.getPrivileges(), false );
        if ( ArrayUtils.contains( expandedPrivileges, removeChildNodesPrivilege )
          && !ArrayUtils.contains( expandedPrivileges, removeNodePrivilege ) ) {
          if ( !acl.addAccessControlEntry( entry.getPrincipal(), new Privilege[] { removeNodePrivilege } ) ) {
            // we can never fail to add this entry because it means we may be giving more permission than the above
            // two
            throw new RuntimeException();
          }
          break;
        }
      }
    }

    // find first ancestor that is not inheriting; its ACEs will be used if the ACL is not inheriting
    ACLTemplate ancestorAcl = null;
    if ( firstAccessControlledNode.isSame( currentNode ) && !rootID.equals( currentNode.getNodeId() ) ) {
      NodeImpl ancestorNode = findNonInheritingNode( (NodeImpl) currentNode.getParent() );
      ancestorAcl = new ACLTemplate( ancestorNode.getNode( N_POLICY ) );
    }

    // now acl points to the nearest ancestor that is access-controlled and is not inheriting;
    // ancestorAcl points to first ancestor of ACL that is access-controlled and is not inheriting--possibly null
    // owner is an owner string--possibly null
    return new Entries( new ArrayList<AccessControlEntry>( getAcesIncludingMagicAces( currentNode.getPath(), owner,
      ancestorAcl, acl ) ), null );
  }

  /**
   * Incoming node is in versionStorage. Find its associated versionable--the node associated with this version history
   * node.
   */
  protected NodeImpl getVersionable( final NodeImpl node ) throws RepositoryException {
    NodeImpl currentNode = node;
    while ( !currentNode.isNodeType( "nt:versionHistory" ) && !rootID
      .equals( currentNode.getNodeId() ) ) { //$NON-NLS-1$
      currentNode = (NodeImpl) currentNode.getParent();
    }
    if ( rootID.equals( currentNode.getNodeId() ) ) {
      return currentNode;
    } else {
      return (NodeImpl) systemSession.getNodeByIdentifier( ( (VersionHistory) currentNode )
        .getVersionableIdentifier() );
    }
  }

  /**
   * {@link IAuthorizationPolicy} is used in magic ACE definitions.
   */
  protected IAuthorizationPolicy getAuthorizationPolicy() {
    IAuthorizationPolicy authorizationPolicy = PentahoSystem.get( IAuthorizationPolicy.class );
    if ( authorizationPolicy == null ) {
      throw new IllegalStateException();
    }
    return authorizationPolicy;
  }

  protected IRoleAuthorizationPolicyRoleBindingDao getRoleBindingDao() {
    return PentahoSystem.get( IRoleAuthorizationPolicyRoleBindingDao.class );
  }

  /**
   * Extracts ACEs including magic aces. Magic ACEs are added for (1) the owner, (2) as a result of magic ACE
   * definitions, and (3) as a result of ancestor ACL contributions.
   * <p/>
   * <p> Modifications to these ACLs are not persisted. </p>
   */
  protected List<AccessControlEntry> getAcesIncludingMagicAces( final String path, final String owner,
                                                                final ACLTemplate ancestorAcl, final ACLTemplate acl )
    throws RepositoryException {
    if ( PentahoSessionHolder.getSession() == null || PentahoSessionHolder.getSession().getId() == null
      || PentahoSessionHolder.getSession().getId().trim().equals( "" ) ) { //$NON-NLS-1$
      if ( log.isDebugEnabled() ) {
        log.debug( "no PentahoSession so no magic ACEs" ); //$NON-NLS-1$
      }
      return Collections.emptyList();
    }
    if ( owner != null ) {
      addOwnerAce( owner, acl );
    }

    boolean match = false;
    IRoleAuthorizationPolicyRoleBindingDao roleBindingDao = null;
    try {
      roleBindingDao =
        PentahoSystem.getObjectFactory().get( IRoleAuthorizationPolicyRoleBindingDao.class,
          "roleAuthorizationPolicyRoleBindingDaoTarget", PentahoSessionHolder.getSession() );
    } catch ( ObjectFactoryException e ) {
      e.printStackTrace();
    }

    ITenant tenant = JcrTenantUtils.getTenant();
    for ( final MagicAceDefinition def : magicAceDefinitions ) {
      match = false;

      String substitutedPath = MessageFormat.format( def.path, tenant.getRootFolderAbsolutePath() );
      if ( isAllowed( roleBindingDao, def.logicalRole ) ) {
        if ( def.applyToTarget ) {
          match = path.equals( substitutedPath );
        }
        if ( !match && def.applyToChildren ) {
          match = path.startsWith( substitutedPath + "/" );
          // check to see if we should exclude the match due to the exclude list
          if ( match && def.exceptChildren != null ) {
            for ( String childPath : def.exceptChildren ) {
              String substitutedChildPath = MessageFormat.format( childPath, tenant.getRootFolderAbsolutePath() );
              if ( path.startsWith( substitutedChildPath + "/" ) ) {
                match = false;
                break;
              }
            }
          }
        }
        if ( !match && def.applyToAncestors ) {
          match = substitutedPath.startsWith( path + "/" );
        }
      }
      if ( match ) {
        Principal principal =
          new MagicPrincipal( JcrTenantUtils.getTenantedUser( PentahoSessionHolder.getSession().getName() ) );
        // unfortunately, we need the ACLTemplate because it alone can create ACEs that can be cast successfully
        // later;
        // changed never persisted
        acl.addAccessControlEntry( principal, def.privileges );
      }
    }

    List<AccessControlEntry> acEntries = new ArrayList<AccessControlEntry>();
    acEntries.addAll( acl.getEntries() ); // leaf ACEs go first so ACL metadata ACE stays first
    acEntries.addAll( getRelevantAncestorAces( ancestorAcl ) );
    return acEntries;
  }

  /**
   * Selects (and modifies) ACEs containing JCR_ADD_CHILD_NODES or JCR_REMOVE_CHILD_NODES privileges from the given
   * ACL.
   * <p/>
   * <p> Modifications to this ACL are not persisted. ACEs must be created in the given ACL because the path embedded in
   * the given ACL plays into authorization decisions using parentPrivs. </p>
   */
  protected List<AccessControlEntry> getRelevantAncestorAces( final ACLTemplate ancestorAcl )
    throws RepositoryException {
    if ( ancestorAcl == null ) {
      return Collections.emptyList();
    }
    NodeImpl ancestorNode = (NodeImpl) systemSession.getNode( ancestorAcl.getPath() );
    Entries fullEntriesIncludingMagicACEs = this.getEntries( ancestorNode );

    Privilege addChildNodesPrivilege =
      systemSession.getAccessControlManager().privilegeFromName( Privilege.JCR_ADD_CHILD_NODES );
    Privilege removeChildNodesPrivilege =
      systemSession.getAccessControlManager().privilegeFromName( Privilege.JCR_REMOVE_CHILD_NODES );

    for ( AccessControlEntry entry : fullEntriesIncludingMagicACEs.getACEs() ) {
      List<Privilege> privs = new ArrayList<Privilege>( 2 );
      Privilege[] expandedPrivileges = JcrRepositoryFileAclUtils.expandPrivileges( entry.getPrivileges(), false );
      if ( ArrayUtils.contains( expandedPrivileges, addChildNodesPrivilege ) ) {
        privs.add( addChildNodesPrivilege );
      }
      if ( ArrayUtils.contains( expandedPrivileges, removeChildNodesPrivilege ) ) {
        privs.add( removeChildNodesPrivilege );
      }
      // remove all physical entries from the ACL. MagicAces will not be present in the ACL Entries, so we check
      // before
      // trying to remove
      if ( ancestorAcl.getEntries().contains( entry ) ) {
        ancestorAcl.removeAccessControlEntry( entry );
      }
      // remove existing ACE since (1) it doesn't have the privs we're looking for and (2) the following
      // addAccessControlEntry will silently fail to add a new ACE if perms already exist
      if ( !privs.isEmpty() ) {
        // create new ACE with same principal but only privs relevant to child operations
        // clone to new list to allow concurrent modification
        List<AccessControlEntry> entries = new LinkedList<AccessControlEntry>( ancestorAcl.getEntries() );
        for ( AccessControlEntry accessControlEntry : entries ) {
          if ( accessControlEntry.getPrincipal().getName().equals( entry.getPrincipal().getName() ) ) {
            ancestorAcl.removeAccessControlEntry( accessControlEntry );
          }
        }
        if ( !ancestorAcl.addAccessControlEntry( entry.getPrincipal() instanceof Group ? new MagicGroup( entry
          .getPrincipal().getName() ) : new MagicPrincipal( entry.getPrincipal().getName() ), privs
          .toArray( new Privilege[ privs.size() ] ) ) ) {
          // we can never fail to add this entry because it means we may be giving more permission than the above
          // two
          throw new RuntimeException();
        }
      }
    }
    return ancestorAcl.getEntries();
  }

  /**
   * Creates an ACE that gives full access to the owner.
   * <p/>
   * <p> Modifications to this ACL are not persisted. </p>
   */
  protected void addOwnerAce( final String owner, final ACLTemplate acl ) throws RepositoryException {
    Principal ownerPrincipal = systemSession.getPrincipalManager().getPrincipal( owner );
    if ( ownerPrincipal != null ) {
      Principal magicPrincipal = null;
      if ( ownerPrincipal instanceof Group ) {
        magicPrincipal = new MagicGroup( JcrTenantUtils.getTenantedUser( ownerPrincipal.getName() ) );
      } else {
        magicPrincipal = new MagicPrincipal( JcrTenantUtils.getTenantedUser( ownerPrincipal.getName() ) );
      }
      // unfortunately, we need the ACLTemplate because it alone can create ACEs that can be cast successfully
      // later;
      // changed never persisted
      acl.addAccessControlEntry( magicPrincipal, new Privilege[] { systemSession.getAccessControlManager()
        .privilegeFromName( "jcr:all" ) } ); //$NON-NLS-1$
    } else {
      // if the Principal doesn't exist anymore, then there's no reason to add an ACE for it
      if ( log.isDebugEnabled() ) {
        log.debug( "PrincipalManager cannot find owner=" + owner ); //$NON-NLS-1$
      }
    }

  }

  /**
   * Overridden since {@code collectEntries()} from {@code EntryCollector} called {@code node.getParentId()} instead of
   * {@code entries.getNextId()}.
   */
  @Override
  protected List<AccessControlEntry> collectEntries( NodeImpl node, EntryFilter filter ) throws RepositoryException {
    LinkedList<AccessControlEntry> userAces = new LinkedList<AccessControlEntry>();
    LinkedList<AccessControlEntry> groupAces = new LinkedList<AccessControlEntry>();

    if ( node == null ) {
      // repository level permissions
      NodeImpl root = (NodeImpl) systemSession.getRootNode();
      if ( ACLProvider.isRepoAccessControlled( root ) ) {
        NodeImpl aclNode = root.getNode( N_REPO_POLICY );
        filterEntries( filter, new ACLTemplate( aclNode ).getEntries(), userAces, groupAces );
      }
    } else {
      Entries entries = getEntries( node );
      filterEntries( filter, entries.getACEs(), userAces, groupAces );
      NodeId next = entries.getNextId();
      while ( next != null ) {
        entries = getEntries( next );
        filterEntries( filter, entries.getACEs(), userAces, groupAces );
        next = entries.getNextId();
      }
    }

    List<AccessControlEntry> entries = new ArrayList<AccessControlEntry>( userAces.size() + groupAces.size() );
    entries.addAll( userAces );
    entries.addAll( groupAces );

    return entries;
  }

  /**
   * Copied from {@link EntryCollector} since that method was {@code private}.
   */
  @SuppressWarnings( "unchecked" )
  protected void filterEntries( EntryFilter filter, List<AccessControlEntry> aces,
                                LinkedList<AccessControlEntry> userAces, LinkedList<AccessControlEntry> groupAces ) {
    if ( !aces.isEmpty() && filter != null ) {
      filter.filterEntries( aces, userAces, groupAces );
    }
  }

  protected List<String> getRuntimeRoleNames() {
    IPentahoSession pentahoSession = PentahoSessionHolder.getSession();
    List<String> runtimeRoles = new ArrayList<String>();
    Assert.state( pentahoSession != null );
    Authentication authentication = SecurityHelper.getInstance().getAuthentication();
    if ( authentication != null ) {
      GrantedAuthority[] authorities = authentication.getAuthorities();
      for ( int i = 0; i < authorities.length; i++ ) {
        runtimeRoles.add( authorities[ i ].getAuthority() );
      }
    }
    return runtimeRoles;
  }

  protected boolean isAllowed( IRoleAuthorizationPolicyRoleBindingDao roleBindingDao, String logicalRoleName )
    throws RepositoryException {
    if ( roleBindingDao instanceof AbstractJcrBackedRoleBindingDao ) {
      AbstractJcrBackedRoleBindingDao jcrBackedRoleBindingDao = (AbstractJcrBackedRoleBindingDao) roleBindingDao;
      return jcrBackedRoleBindingDao.getBoundLogicalRoleNames( systemSession, getRuntimeRoleNames() ).contains(
        logicalRoleName );
    } else {
      return roleBindingDao.getBoundLogicalRoleNames( getRuntimeRoleNames() ).contains( logicalRoleName );
    }
  }
}
TOP

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

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.