/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License, version 2 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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 General Public License for more details.
*
*
* Copyright 2006 - 2013 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.platform.repository2.unified.jcr;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jackrabbit.api.security.user.Group;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAce;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAcl;
import org.pentaho.platform.api.repository2.unified.RepositoryFilePermission;
import org.pentaho.platform.api.repository2.unified.RepositoryFileSid;
import org.pentaho.platform.repository2.messages.Messages;
import org.pentaho.platform.repository2.unified.jcr.IAclMetadataStrategy.AclMetadata;
import org.pentaho.platform.repository2.unified.jcr.JcrRepositoryFileAclDao.IPermissionConversionHelper;
import org.pentaho.platform.repository2.unified.jcr.jackrabbit.security.SpringSecurityRolePrincipal;
import org.pentaho.platform.repository2.unified.jcr.jackrabbit.security.SpringSecurityUserPrincipal;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlList;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.AccessControlPolicyIterator;
import javax.jcr.security.Privilege;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.security.Principal;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* ACL utilities.
*
* <p>
* These utility methods are static because they are used from within Jackrabbit.
* </p>
*
* @author mlowery
*/
public class JcrRepositoryFileAclUtils {
// ~ Static fields/initializers
// ======================================================================================
private static final Log logger = LogFactory.getLog( JcrRepositoryFileAclUtils.class );
public static final String DEFAULT = "DEFAULT"; //$NON-NLS-1$
public static final String SYSTEM_PROPERTY = "pentaho.repository.server.aclMetadataStrategy"; //$NON-NLS-1$
private static String strategyName = System.getProperty( SYSTEM_PROPERTY );
private static IAclMetadataStrategy strategy;
static {
initialize();
}
// ~ Instance fields
// =================================================================================================
// ~ Constructors
// ====================================================================================================
private JcrRepositoryFileAclUtils() {
super();
}
// ~ Methods
// =========================================================================================================
private static void initialize() {
if ( ( strategyName == null ) || "".equals( strategyName ) ) { //$NON-NLS-1$
strategyName = DEFAULT;
}
if ( strategyName.equals( DEFAULT ) ) {
strategy = new JcrAclMetadataStrategy();
} else {
// Try to load a custom strategy
try {
Class<?> clazz = Class.forName( strategyName );
Constructor<?> customStrategy = clazz.getConstructor( new Class[] {} );
strategy = (IAclMetadataStrategy) customStrategy.newInstance( new Object[] {} );
} catch ( Exception e ) {
throw new RuntimeException( e );
}
}
logger.debug( "JcrRepositoryFileAclUtils initialized: strategy=" + strategyName ); //$NON-NLS-1$
}
public static AclMetadata getAclMetadata( final Session session, final String path, final AccessControlList acList )
throws RepositoryException {
return strategy.getAclMetadata( session, path, acList );
}
public static void setAclMetadata( final Session session, final String path, final AccessControlList acList,
final AclMetadata aclMetadata ) throws RepositoryException {
strategy.setAclMetadata( session, path, acList, aclMetadata );
}
public static List<AccessControlEntry> removeAclMetadata( final List<AccessControlEntry> acEntries )
throws RepositoryException {
return strategy.removeAclMetadata( acEntries );
}
/**
* Expands all aggregate privileges.
*
* @param privileges
* input privileges
* @param expandNonStandardOnly
* if {@code true} expand only privileges outside of jcr: namespace
* @return expanded privileges
*/
public static Privilege[] expandPrivileges( final Privilege[] privileges, final boolean expandNonStandardOnly ) {
// find all aggregate privileges and expand
Set<Privilege> expandedPrivileges = new HashSet<Privilege>();
expandedPrivileges.addAll( Arrays.asList( privileges ) );
while ( true ) {
boolean foundAggregatePrivilege = false;
Set<Privilege> iterable = new HashSet<Privilege>( expandedPrivileges );
for ( Privilege privilege : iterable ) {
// expand impl custom privileges (e.g. rep:write) but keep aggregates like jcr:write intact
if ( !expandNonStandardOnly || expandNonStandardOnly && !privilege.getName().startsWith( "jcr:" ) ) { //$NON-NLS-1$
if ( privilege.isAggregate() ) {
expandedPrivileges.remove( privilege );
expandedPrivileges.addAll( Arrays.asList( privilege.getAggregatePrivileges() ) );
foundAggregatePrivilege = true;
}
}
}
if ( !foundAggregatePrivilege ) {
break;
}
}
return expandedPrivileges.toArray( new Privilege[0] );
}
public static RepositoryFileAcl createAcl( Session session, PentahoJcrConstants pentahoJcrConstants,
Serializable fileId, RepositoryFileAcl acl ) throws ItemNotFoundException, RepositoryException {
Node node = session.getNodeByIdentifier( fileId.toString() );
String absPath = node.getPath();
AccessControlManager acMgr = session.getAccessControlManager();
AccessControlList acList = getAccessControlList( acMgr, absPath );
acMgr.setPolicy( absPath, acList );
return internalUpdateAcl( session, pentahoJcrConstants, fileId, acl );
}
public static void
addPermission( final Session session, final PentahoJcrConstants pentahoJcrConstants,
final Serializable fileId,
final RepositoryFileSid recipient, final EnumSet<RepositoryFilePermission> permissions )
throws RepositoryException {
addAce( session, pentahoJcrConstants, fileId, recipient, permissions );
}
public static void setOwner( final Session session, final PentahoJcrConstants pentahoJcrConstants,
final RepositoryFile file, final RepositoryFileSid owner ) throws RepositoryException {
RepositoryFileSid newOwnerSid = owner;
if ( JcrTenantUtils.getUserNameUtils().getTenant( owner.getName() ) == null ) {
newOwnerSid = new RepositoryFileSid( JcrTenantUtils.getTenantedUser( owner.getName() ), owner.getType() );
}
RepositoryFileAcl acl = getAcl( session, pentahoJcrConstants, file.getId() );
RepositoryFileAcl newAcl = new RepositoryFileAcl.Builder( acl ).owner( newOwnerSid ).build();
updateAcl( session, newAcl );
}
public static void setFullControl( final Session session, final PentahoJcrConstants pentahoJcrConstants,
final Serializable fileId, final RepositoryFileSid sid ) throws RepositoryException {
addAce( session, pentahoJcrConstants, fileId, sid, EnumSet.of( RepositoryFilePermission.ALL ) );
}
public static void addAce( final Session session, final PentahoJcrConstants pentahoJcrConstants,
final Serializable id, final RepositoryFileSid recipient, final EnumSet<RepositoryFilePermission> permission )
throws RepositoryException {
RepositoryFileSid newRecipient = recipient;
if ( JcrTenantUtils.getUserNameUtils().getTenant( recipient.getName() ) == null ) {
newRecipient = new RepositoryFileSid( JcrTenantUtils.getTenantedUser( recipient.getName() ),
recipient.getType() );
}
RepositoryFileAcl acl = getAcl( session, pentahoJcrConstants, id );
RepositoryFileAcl updatedAcl = new RepositoryFileAcl.Builder( acl ).ace( newRecipient, permission ).build();
updateAcl( session, updatedAcl );
}
private static RepositoryFileAcl internalUpdateAcl( final Session session,
final PentahoJcrConstants pentahoJcrConstants, final Serializable fileId, final RepositoryFileAcl acl )
throws RepositoryException {
Node node = session.getNodeByIdentifier( fileId.toString() );
if ( node == null ) {
throw new RepositoryException( "Node not found" ); //$NON-NLS-1$
}
String absPath = node.getPath();
AccessControlManager acMgr = session.getAccessControlManager();
AccessControlList acList = getAccessControlList( acMgr, absPath );
// clear all entries
AccessControlEntry[] acEntries = acList.getAccessControlEntries();
for ( int i = 0; i < acEntries.length; i++ ) {
acList.removeAccessControlEntry( acEntries[i] );
}
JcrRepositoryFileAclUtils.setAclMetadata( session, absPath, acList, new AclMetadata( acl.getOwner().getName(), acl
.isEntriesInheriting() ) );
// add entries to now empty list but only if not inheriting; force user to start with clean slate
if ( !acl.isEntriesInheriting() ) {
for ( RepositoryFileAce ace : acl.getAces() ) {
Principal principal = null;
if ( RepositoryFileSid.Type.ROLE == ace.getSid().getType() ) {
principal = new SpringSecurityRolePrincipal( JcrTenantUtils.getTenantedRole( ace.getSid().getName() ) );
} else {
principal = new SpringSecurityUserPrincipal( JcrTenantUtils.getTenantedUser( ace.getSid().getName() ) );
}
IPermissionConversionHelper permissionConversionHelper = new DefaultPermissionConversionHelper( session );
acList.addAccessControlEntry( principal, permissionConversionHelper.pentahoPermissionsToPrivileges( session,
ace.getPermissions() ) );
}
}
acMgr.setPolicy( absPath, acList );
session.save();
return getAcl( session, pentahoJcrConstants, fileId );
}
public static void updateAcl( final Session session, final RepositoryFileAcl acl ) throws RepositoryException {
PentahoJcrConstants pentahoJcrConstants = new PentahoJcrConstants( session );
JcrRepositoryFileUtils.checkoutNearestVersionableFileIfNecessary( session, pentahoJcrConstants, acl.getId() );
internalUpdateAcl( session, pentahoJcrConstants, acl.getId(), acl );
JcrRepositoryFileUtils.checkinNearestVersionableFileIfNecessary( session, pentahoJcrConstants, acl.getId(), null,
null, true );
}
public static RepositoryFileAcl getAcl( final Session session, final PentahoJcrConstants pentahoJcrConstants,
final Serializable id ) throws RepositoryException {
Node node = session.getNodeByIdentifier( id.toString() );
if ( node == null ) {
throw new RepositoryException( Messages.getInstance().getString(
"JackrabbitRepositoryFileAclDao.ERROR_0001_NODE_NOT_FOUND", id.toString() ) ); //$NON-NLS-1$
}
String absPath = node.getPath();
AccessControlManager acMgr = session.getAccessControlManager();
AccessControlList acList = getAccessControlList( acMgr, absPath );
RepositoryFileSid owner = null;
String ownerString = JcrTenantUtils.getUserNameUtils().getPrincipleName( getOwner( session, absPath, acList ) );
if ( ownerString != null ) {
// for now, just assume all owners are users; only has UI impact
owner = new RepositoryFileSid( ownerString, RepositoryFileSid.Type.USER );
}
RepositoryFileAcl.Builder aclBuilder = new RepositoryFileAcl.Builder( id, owner );
aclBuilder.entriesInheriting( isEntriesInheriting( session, absPath, acList ) );
List<AccessControlEntry> cleanedAcEntries =
JcrRepositoryFileAclUtils.removeAclMetadata( Arrays.asList( acList.getAccessControlEntries() ) );
for ( AccessControlEntry acEntry : cleanedAcEntries ) {
aclBuilder.ace( toAce( session, acEntry ) );
}
return aclBuilder.build();
}
private static AccessControlList getAccessControlList( final AccessControlManager acMgr, final String path )
throws RepositoryException {
AccessControlPolicyIterator applicablePolicies = acMgr.getApplicablePolicies( path );
while ( applicablePolicies.hasNext() ) {
AccessControlPolicy policy = applicablePolicies.nextAccessControlPolicy();
if ( policy instanceof AccessControlList ) {
return (AccessControlList) policy;
}
}
AccessControlPolicy[] policies = acMgr.getPolicies( path );
for ( int i = 0; i < policies.length; i++ ) {
if ( policies[i] instanceof AccessControlList ) {
return (AccessControlList) policies[i];
}
}
throw new IllegalStateException( "no access control list applies or is bound to node" );
}
private static String getOwner( final Session session, final String path, final AccessControlList acList )
throws RepositoryException {
AclMetadata aclMetadata = JcrRepositoryFileAclUtils.getAclMetadata( session, path, acList );
if ( aclMetadata != null ) {
return aclMetadata.getOwner();
} else {
return null;
}
}
private static boolean isEntriesInheriting( final Session session, final String path, final AccessControlList acList )
throws RepositoryException {
AclMetadata aclMetadata = JcrRepositoryFileAclUtils.getAclMetadata( session, path, acList );
if ( aclMetadata != null ) {
return aclMetadata.isEntriesInheriting();
} else {
return false;
}
}
private static RepositoryFileAce toAce( final Session session, final AccessControlEntry acEntry )
throws RepositoryException {
Principal principal = acEntry.getPrincipal();
RepositoryFileSid sid = null;
if ( principal instanceof Group ) {
sid = new RepositoryFileSid( principal.getName(), RepositoryFileSid.Type.ROLE );
} else {
sid = new RepositoryFileSid( principal.getName(), RepositoryFileSid.Type.USER );
}
Privilege[] privileges = acEntry.getPrivileges();
IPermissionConversionHelper permissionConversionHelper = new DefaultPermissionConversionHelper( session );
return new RepositoryFileAce( sid, permissionConversionHelper.privilegesToPentahoPermissions( session,
privileges ) );
}
}