blob: a639fc480edac328aea9a5e4735af65104c2ad81 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.directory.server.core.admin;
import java.net.Authenticator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.naming.directory.SearchControls;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Modification;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException;
import org.apache.directory.api.ldap.model.exception.LdapOperationException;
import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
import org.apache.directory.api.ldap.model.filter.ExprNode;
import org.apache.directory.api.ldap.model.filter.PresenceNode;
import org.apache.directory.api.ldap.model.message.AliasDerefMode;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.subtree.AdministrativeRole;
import org.apache.directory.api.ldap.util.tree.DnNode;
import org.apache.directory.api.util.Strings;
import org.apache.directory.server.constants.ServerDNConstants;
import org.apache.directory.server.core.api.CoreSession;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.api.InterceptorEnum;
import org.apache.directory.server.core.api.administrative.AccessControlAAP;
import org.apache.directory.server.core.api.administrative.AccessControlAdministrativePoint;
import org.apache.directory.server.core.api.administrative.AccessControlIAP;
import org.apache.directory.server.core.api.administrative.AccessControlSAP;
import org.apache.directory.server.core.api.administrative.AdministrativePoint;
import org.apache.directory.server.core.api.administrative.CollectiveAttributeAAP;
import org.apache.directory.server.core.api.administrative.CollectiveAttributeAdministrativePoint;
import org.apache.directory.server.core.api.administrative.CollectiveAttributeIAP;
import org.apache.directory.server.core.api.administrative.CollectiveAttributeSAP;
import org.apache.directory.server.core.api.administrative.SubschemaAAP;
import org.apache.directory.server.core.api.administrative.SubschemaAdministrativePoint;
import org.apache.directory.server.core.api.administrative.SubschemaSAP;
import org.apache.directory.server.core.api.administrative.TriggerExecutionAAP;
import org.apache.directory.server.core.api.administrative.TriggerExecutionAdministrativePoint;
import org.apache.directory.server.core.api.administrative.TriggerExecutionIAP;
import org.apache.directory.server.core.api.administrative.TriggerExecutionSAP;
import org.apache.directory.server.core.api.entry.ClonedServerEntry;
import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
import org.apache.directory.server.core.api.partition.PartitionNexus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An interceptor to manage the Administrative model
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class AdministrativePointInterceptor extends BaseInterceptor
{
/** A {@link Logger} for this class */
private static final Logger LOG = LoggerFactory.getLogger( AdministrativePointInterceptor.class );
/**
* Speedup for logs
*/
private static final boolean IS_DEBUG = LOG.isDebugEnabled();
/** A reference to the nexus for direct backend operations */
private PartitionNexus nexus;
/** The possible roles */
private static final Set<String> ROLES = new HashSet<String>();
// Initialize the ROLES field
static
{
ROLES.add( Strings.toLowerCase( SchemaConstants.AUTONOMOUS_AREA ) );
ROLES.add( SchemaConstants.AUTONOMOUS_AREA_OID );
ROLES.add( Strings.toLowerCase( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA ) );
ROLES.add( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA_OID );
ROLES.add( Strings.toLowerCase( SchemaConstants.ACCESS_CONTROL_INNER_AREA ) );
ROLES.add( SchemaConstants.ACCESS_CONTROL_INNER_AREA_OID );
ROLES.add( Strings.toLowerCase( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA ) );
ROLES.add( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA_OID );
ROLES.add( Strings.toLowerCase( SchemaConstants.COLLECTIVE_ATTRIBUTE_INNER_AREA ) );
ROLES.add( SchemaConstants.COLLECTIVE_ATTRIBUTE_INNER_AREA_OID );
ROLES.add( Strings.toLowerCase( SchemaConstants.SUB_SCHEMA_ADMIN_SPECIFIC_AREA ) );
ROLES.add( SchemaConstants.SUB_SCHEMA_ADMIN_SPECIFIC_AREA_OID );
ROLES.add( Strings.toLowerCase( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA ) );
ROLES.add( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA_OID );
ROLES.add( Strings.toLowerCase( SchemaConstants.TRIGGER_EXECUTION_INNER_AREA ) );
ROLES.add( SchemaConstants.TRIGGER_EXECUTION_INNER_AREA_OID );
}
/** A Map to associate a role with it's OID */
private static final Map<String, String> ROLES_OID = new HashMap<String, String>();
// Initialize the roles/oid map
static
{
ROLES_OID.put( Strings.toLowerCase( SchemaConstants.AUTONOMOUS_AREA ), SchemaConstants.AUTONOMOUS_AREA_OID );
ROLES_OID.put( Strings.toLowerCase( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA ),
SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA_OID );
ROLES_OID.put( Strings.toLowerCase( SchemaConstants.ACCESS_CONTROL_INNER_AREA ),
SchemaConstants.ACCESS_CONTROL_INNER_AREA_OID );
ROLES_OID.put( Strings.toLowerCase( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA ),
SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA_OID );
ROLES_OID.put( Strings.toLowerCase( SchemaConstants.COLLECTIVE_ATTRIBUTE_INNER_AREA ),
SchemaConstants.COLLECTIVE_ATTRIBUTE_INNER_AREA_OID );
ROLES_OID.put( Strings.toLowerCase( SchemaConstants.SUB_SCHEMA_ADMIN_SPECIFIC_AREA ),
SchemaConstants.SUB_SCHEMA_ADMIN_SPECIFIC_AREA_OID );
ROLES_OID.put( Strings.toLowerCase( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA ),
SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA_OID );
ROLES_OID.put( Strings.toLowerCase( SchemaConstants.TRIGGER_EXECUTION_INNER_AREA ),
SchemaConstants.TRIGGER_EXECUTION_INNER_AREA_OID );
}
/** The possible inner area roles */
private static final Set<String> INNER_AREA_ROLES = new HashSet<String>();
static
{
INNER_AREA_ROLES.add( Strings.toLowerCase( SchemaConstants.ACCESS_CONTROL_INNER_AREA ) );
INNER_AREA_ROLES.add( SchemaConstants.ACCESS_CONTROL_INNER_AREA_OID );
INNER_AREA_ROLES.add( Strings.toLowerCase( SchemaConstants.COLLECTIVE_ATTRIBUTE_INNER_AREA ) );
INNER_AREA_ROLES.add( SchemaConstants.COLLECTIVE_ATTRIBUTE_INNER_AREA_OID );
INNER_AREA_ROLES.add( Strings.toLowerCase( SchemaConstants.TRIGGER_EXECUTION_INNER_AREA ) );
INNER_AREA_ROLES.add( SchemaConstants.TRIGGER_EXECUTION_INNER_AREA_OID );
}
/** The possible specific area roles */
private static final Set<String> SPECIFIC_AREA_ROLES = new HashSet<String>();
static
{
SPECIFIC_AREA_ROLES.add( Strings.toLowerCase( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA ) );
SPECIFIC_AREA_ROLES.add( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA_OID );
SPECIFIC_AREA_ROLES.add( Strings.toLowerCase( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA ) );
SPECIFIC_AREA_ROLES.add( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA_OID );
SPECIFIC_AREA_ROLES.add( Strings.toLowerCase( SchemaConstants.SUB_SCHEMA_ADMIN_SPECIFIC_AREA ) );
SPECIFIC_AREA_ROLES.add( SchemaConstants.SUB_SCHEMA_ADMIN_SPECIFIC_AREA_OID );
SPECIFIC_AREA_ROLES.add( Strings.toLowerCase( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA ) );
SPECIFIC_AREA_ROLES.add( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA_OID );
}
/** A lock to guarantee the AP cache consistency */
private ReentrantReadWriteLock mutex = new ReentrantReadWriteLock();
/**
* Creates a new instance of a AdministrativePointInterceptor.
*/
public AdministrativePointInterceptor()
{
super( InterceptorEnum.ADMINISTRATIVE_POINT_INTERCEPTOR );
}
/**
* Get a read-lock on the AP cache.
* No read operation can be done on the AP cache if this
* method is not called before.
*/
public void lockRead()
{
mutex.readLock().lock();
}
/**
* Get a write-lock on the AP cache.
* No write operation can be done on the apCache if this
* method is not called before.
*/
public void lockWrite()
{
mutex.writeLock().lock();
}
/**
* Release the read-write lock on the AP cache.
* This method must be called after having read or modified the
* AP cache
*/
public void unlock()
{
if ( mutex.isWriteLockedByCurrentThread() )
{
mutex.writeLock().unlock();
}
else
{
mutex.readLock().unlock();
}
}
/**
* Create the list of AP for a given entry
*/
private void createAdministrativePoints( Attribute adminPoint, Dn dn, String uuid ) throws LdapException
{
if ( isAAP( adminPoint ) )
{
// The AC AAP
AccessControlAdministrativePoint acAap = new AccessControlAAP( dn, uuid );
directoryService.getAccessControlAPCache().add( dn, acAap );
// The CA AAP
CollectiveAttributeAdministrativePoint caAap = new CollectiveAttributeAAP( dn, uuid );
directoryService.getCollectiveAttributeAPCache().add( dn, caAap );
// The TE AAP
TriggerExecutionAdministrativePoint teAap = new TriggerExecutionAAP( dn, uuid );
directoryService.getTriggerExecutionAPCache().add( dn, teAap );
// The SS AAP
SubschemaAdministrativePoint ssAap = new SubschemaAAP( dn, uuid );
directoryService.getSubschemaAPCache().add( dn, ssAap );
// TODO : Here, we have to update the children, removing any
// reference to any other underlying AP
// If it's an AAP, we can get out immediately
return;
}
for ( Value<?> value : adminPoint )
{
String role = value.getString();
// Deal with AccessControl AP
if ( isAccessControlSpecificRole( role ) )
{
AccessControlAdministrativePoint sap = new AccessControlSAP( dn, uuid );
directoryService.getAccessControlAPCache().add( dn, sap );
// TODO : Here, we have to update the children, removing any
// reference to any other underlying AccessControl IAP or SAP
continue;
}
if ( isAccessControlInnerRole( role ) )
{
AccessControlAdministrativePoint iap = new AccessControlIAP( dn, uuid );
directoryService.getAccessControlAPCache().add( dn, iap );
continue;
}
// Deal with CollectiveAttribute AP
if ( isCollectiveAttributeSpecificRole( role ) )
{
CollectiveAttributeAdministrativePoint sap = new CollectiveAttributeSAP( dn, uuid );
directoryService.getCollectiveAttributeAPCache().add( dn, sap );
// TODO : Here, we have to update the children, removing any
// reference to any other underlying CollectiveAttribute IAP or SAP
continue;
}
if ( isCollectiveAttributeInnerRole( role ) )
{
CollectiveAttributeAdministrativePoint iap = new CollectiveAttributeIAP( dn, uuid );
directoryService.getCollectiveAttributeAPCache().add( dn, iap );
continue;
}
// Deal with SubSchema AP
if ( isSubschemaSpecficRole( role ) )
{
SubschemaAdministrativePoint sap = new SubschemaSAP( dn, uuid );
directoryService.getSubschemaAPCache().add( dn, sap );
// TODO : Here, we have to update the children, removing any
// reference to any other underlying Subschema IAP or SAP
continue;
}
// Deal with TriggerExecution AP
if ( isTriggerExecutionSpecificRole( role ) )
{
TriggerExecutionAdministrativePoint sap = new TriggerExecutionSAP( dn, uuid );
directoryService.getTriggerExecutionAPCache().add( dn, sap );
// TODO : Here, we have to update the children, removing any
// reference to any other underlying TriggerExecution IAP or SAP
continue;
}
if ( isTriggerExecutionInnerRole( role ) )
{
TriggerExecutionAdministrativePoint iap = new TriggerExecutionIAP( dn, uuid );
directoryService.getTriggerExecutionAPCache().add( dn, iap );
continue;
}
}
return;
}
/**
* Update the cache clones with the added roles
*/
private void addRole( String role, Dn dn, String uuid, DnNode<AccessControlAdministrativePoint> acapCache,
DnNode<CollectiveAttributeAdministrativePoint> caapCache,
DnNode<TriggerExecutionAdministrativePoint> teapCache,
DnNode<SubschemaAdministrativePoint> ssapCache ) throws LdapException
{
// Deal with Autonomous AP : create the 4 associated SAP/AAP
if ( isAutonomousAreaRole( role ) )
{
// The AC AAP
AccessControlAdministrativePoint acAap = new AccessControlAAP( dn, uuid );
acapCache.add( dn, acAap );
// The CA AAP
CollectiveAttributeAdministrativePoint caAap = new CollectiveAttributeAAP( dn, uuid );
caapCache.add( dn, caAap );
// The TE AAP
TriggerExecutionAdministrativePoint teAap = new TriggerExecutionAAP( dn, uuid );
teapCache.add( dn, teAap );
// The SS AAP
SubschemaAdministrativePoint ssAap = new SubschemaAAP( dn, uuid );
ssapCache.add( dn, ssAap );
// If it's an AAP, we can get out immediately
return;
}
// Deal with AccessControl AP
if ( isAccessControlSpecificRole( role ) )
{
AccessControlAdministrativePoint sap = new AccessControlSAP( dn, uuid );
acapCache.add( dn, sap );
return;
}
if ( isAccessControlInnerRole( role ) )
{
AccessControlAdministrativePoint iap = new AccessControlIAP( dn, uuid );
acapCache.add( dn, iap );
return;
}
// Deal with CollectiveAttribute AP
if ( isCollectiveAttributeSpecificRole( role ) )
{
CollectiveAttributeAdministrativePoint sap = new CollectiveAttributeSAP( dn, uuid );
caapCache.add( dn, sap );
return;
}
if ( isCollectiveAttributeInnerRole( role ) )
{
CollectiveAttributeAdministrativePoint iap = new CollectiveAttributeIAP( dn, uuid );
caapCache.add( dn, iap );
return;
}
// Deal with SubSchema AP
if ( isSubschemaSpecficRole( role ) )
{
SubschemaAdministrativePoint sap = new SubschemaSAP( dn, uuid );
ssapCache.add( dn, sap );
return;
}
// Deal with TriggerExecution AP
if ( isTriggerExecutionSpecificRole( role ) )
{
TriggerExecutionAdministrativePoint sap = new TriggerExecutionSAP( dn, uuid );
teapCache.add( dn, sap );
return;
}
if ( isTriggerExecutionInnerRole( role ) )
{
TriggerExecutionAdministrativePoint iap = new TriggerExecutionIAP( dn, uuid );
teapCache.add( dn, iap );
return;
}
}
/**
* Update the cache clones with the added roles
*/
private void delRole( String role, Dn dn, String uuid, DnNode<AccessControlAdministrativePoint> acapCache,
DnNode<CollectiveAttributeAdministrativePoint> caapCache,
DnNode<TriggerExecutionAdministrativePoint> teapCache,
DnNode<SubschemaAdministrativePoint> ssapCache ) throws LdapException
{
// Deal with Autonomous AP : remove the 4 associated SAP/AAP
if ( isAutonomousAreaRole( role ) )
{
// The AC AAP
acapCache.remove( dn );
// The CA AAP
caapCache.remove( dn );
// The TE AAP
teapCache.remove( dn );
// The SS AAP
ssapCache.remove( dn );
return;
}
// Deal with AccessControl AP
if ( isAccessControlSpecificRole( role ) || isAccessControlInnerRole( role ) )
{
acapCache.remove( dn );
return;
}
// Deal with CollectiveAttribute AP
if ( isCollectiveAttributeSpecificRole( role ) || isCollectiveAttributeInnerRole( role ) )
{
caapCache.remove( dn );
return;
}
// Deal with SubSchema AP
if ( isSubschemaSpecficRole( role ) )
{
ssapCache.remove( dn );
return;
}
// Deal with TriggerExecution AP
if ( isTriggerExecutionSpecificRole( role ) || isTriggerExecutionInnerRole( role ) )
{
teapCache.remove( dn );
return;
}
}
private AdministrativePoint getParent( AdministrativePoint ap, List<AdministrativePoint> aps,
AdministrativeRole role, DnNode<List<AdministrativePoint>> currentNode )
{
AdministrativePoint parent = null;
for ( AdministrativePoint adminPoint : aps )
{
if ( adminPoint.isAutonomous() || ( adminPoint.getRole() == ap.getRole() ) )
{
// Same role or AP : this is the parent
return adminPoint;
}
else if ( adminPoint.getRole() == role )
{
parent = adminPoint;
}
}
if ( parent != null )
{
return parent;
}
// We have to go down one level
if ( currentNode.hasParent() )
{
return findParent( ap, currentNode );
}
else
{
return null;
}
}
/**
* Find the parent for the given administrative point. If the AP is an AAP, the parent will be the closest
* AAP or the closest SAP. If we have a SAP between the added AAP and a AAP, then
*/
private AdministrativePoint findParent( AdministrativePoint ap, DnNode<List<AdministrativePoint>> currentNode )
{
List<AdministrativePoint> aps = currentNode.getElement();
if ( aps != null )
{
// Check if the current element is a valid parent
switch ( ap.getRole() )
{
case AutonomousArea:
AdministrativePoint currentAp = aps.get( 0 );
if ( currentAp.isAutonomous() )
{
return currentAp;
}
else
{
// We have to go down one level, as an AAP
// must have another AAP as a parent
if ( currentNode.hasParent() )
{
return findParent( ap, currentNode );
}
else
{
return null;
}
}
case AccessControlInnerArea:
return getParent( ap, aps, AdministrativeRole.AccessControlSpecificArea, currentNode );
case CollectiveAttributeInnerArea:
return getParent( ap, aps, AdministrativeRole.CollectiveAttributeSpecificArea, currentNode );
case TriggerExecutionInnerArea:
return getParent( ap, aps, AdministrativeRole.TriggerExecutionSpecificArea, currentNode );
case AccessControlSpecificArea:
return getParent( ap, aps, AdministrativeRole.AccessControlSpecificArea, currentNode );
case CollectiveAttributeSpecificArea:
return getParent( ap, aps, AdministrativeRole.CollectiveAttributeSpecificArea, currentNode );
case SubSchemaSpecificArea:
return getParent( ap, aps, AdministrativeRole.SubSchemaSpecificArea, currentNode );
case TriggerExecutionSpecificArea:
return getParent( ap, aps, AdministrativeRole.TriggerExecutionSpecificArea, currentNode );
default:
return null;
}
}
else
{
if ( currentNode.hasParent() )
{
return findParent( ap, currentNode.getParent() );
}
else
{
return null;
}
}
}
/**
* Check if we can safely add a role. If it's an AAP, we have to be sure that
* it's the only role present in the AT.
*/
private void checkAddRole( Value<?> role, Attribute adminPoint, Dn dn ) throws LdapException
{
String roleStr = Strings.toLowerCase( Strings.trim( role.getString() ) );
// Check that the added AdministrativeRole is valid
if ( !ROLES.contains( roleStr ) )
{
String message = "Cannot add the given role, it's not a valid one :" + role;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
// If we are trying to add an AAP, we have to check that
// it's the only role in the AdminPoint AT
if ( isAutonomousAreaRole( roleStr ) )
{
if ( adminPoint.size() > 1 )
{
String message = "Cannot add an Autonomous Administratve Point when some other roles are added : "
+ adminPoint;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
else
{
// Fine : we only have one AAP
return;
}
}
// Check that we don't have already an AAP in the AdminPoint AT when we try to
// add a role
if ( adminPoint.contains( SchemaConstants.AUTONOMOUS_AREA ) )
{
String message = "Cannot add a role when an Autonomous Administratve Point is already present : "
+ adminPoint;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
// check that we can't mix Inner and Specific areas
checkInnerSpecificMix( roleStr, adminPoint );
// Check that we don't add an IAP with no parent. The IAP must be added under
// either a AAP, or a SAP/IAP within the same family
if ( isIAP( roleStr ) )
{
checkIAPHasParent( roleStr, adminPoint, dn );
}
}
/**
* Check if we can safely delete a role
*/
private void checkDelRole( Value<?> role, Attribute adminPoint, Dn dn ) throws LdapException
{
String roleStr = Strings.toLowerCase( Strings.trim( role.getString() ) );
// Check that the removed AdministrativeRole is valid
if ( !ROLES.contains( roleStr ) )
{
String message = "Cannot delete the given role, it's not a valid one :" + role;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
// Now we are trying to delete an Administrative point. We have to check that
// we only have one role if the deleted role is an AAP
if ( isAutonomousAreaRole( roleStr ) )
{
// We know have to check that removing the AAP, we will not
// left any pending IAP. We should check for the 3 potential IAPs :
// AccessControl, CollectiveAttribute and TriggerExecution.
// If the removed AP has a parent, no need to go any further :
// the children IAPs will depend on this parent.
// Process the ACs
DnNode<AccessControlAdministrativePoint> acAps = directoryService.getAccessControlAPCache();
if ( !acAps.hasParent( dn ) )
{
// No parent, check for any IAP
List<AccessControlAdministrativePoint> children = acAps.getDescendantElements( dn );
for ( AccessControlAdministrativePoint child : children )
{
if ( child.isInner() )
{
// Ok, we are dead : the IAP will remain with no parent.
String message = "Cannot delete the given role, the " + child.getDn()
+ " AccessControl IAP will remain orphan";
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}
}
// Process the CAs
DnNode<CollectiveAttributeAdministrativePoint> caAps = directoryService.getCollectiveAttributeAPCache();
if ( !acAps.hasParent( dn ) )
{
// No parent, check for any IAP
List<CollectiveAttributeAdministrativePoint> children = caAps.getDescendantElements( dn );
for ( CollectiveAttributeAdministrativePoint child : children )
{
if ( child.isInner() )
{
// Ok, we are dead : the IAP will remain with no parent.
String message = "Cannot delete the given role, the " + child.getDn()
+ " CollectiveAttribute IAP will remain orphan";
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}
}
// Process the TEs
DnNode<TriggerExecutionAdministrativePoint> teAps = directoryService.getTriggerExecutionAPCache();
if ( !acAps.hasParent( dn ) )
{
// No parent, check for any IAP
List<TriggerExecutionAdministrativePoint> children = teAps.getDescendantElements( dn );
for ( TriggerExecutionAdministrativePoint child : children )
{
if ( child.isInner() )
{
// Ok, we are dead : the IAP will remain with no parent.
String message = "Cannot delete the given role, the " + child.getDn()
+ " TriggerExecution IAP will remain orphan";
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}
}
}
}
//-------------------------------------------------------------------------------------------
// Helper methods
//-------------------------------------------------------------------------------------------
private List<Entry> getAdministrativePoints() throws LdapException
{
List<Entry> entries = new ArrayList<Entry>();
Dn adminDn = new Dn( schemaManager, ServerDNConstants.ADMIN_SYSTEM_DN );
SearchControls controls = new SearchControls();
controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
controls.setReturningAttributes( new String[]
{ SchemaConstants.ADMINISTRATIVE_ROLE_AT, SchemaConstants.ENTRY_UUID_AT } );
// Search for all the adminstrativePoints in the base
ExprNode filter = new PresenceNode( ADMINISTRATIVE_ROLE_AT );
CoreSession adminSession = directoryService.getAdminSession();
SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter,
controls );
searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
EntryFilteringCursor results = nexus.search( searchOperationContext );
try
{
while ( results.next() )
{
Entry entry = results.get();
entries.add( entry );
}
results.close();
}
catch ( Exception e )
{
throw new LdapOperationException( e.getMessage(), e );
}
return entries;
}
/**
* Tells if a given role is a valid administrative role. We check the lower cased
* and trimmed value, and also the OID value.
*/
private boolean isValidRole( String role )
{
return ROLES.contains( Strings.toLowerCase( Strings.trim( role ) ) );
}
/**
* Update The Administrative Points cache, adding the given AdminPoints
*/
private void addAdminPointCache( List<Entry> adminPointEntries ) throws LdapException
{
for ( Entry adminPointEntry : adminPointEntries )
{
// update the cache
Dn dn = adminPointEntry.getDn();
String uuid = adminPointEntry.get( ENTRY_UUID_AT ).getString();
Attribute adminPoint = adminPointEntry.get( ADMINISTRATIVE_ROLE_AT );
createAdministrativePoints( adminPoint, dn, uuid );
}
}
/**
* Update The Administrative Points cache, removing the given AdminPoint
*/
private void deleteAdminPointCache( Attribute adminPoint, DeleteOperationContext deleteContext )
throws LdapException
{
Dn dn = deleteContext.getDn();
// Remove the APs in the AP cache
for ( Value<?> value : adminPoint )
{
String role = value.getString();
// Deal with Autonomous AP : delete the 4 associated SAP/AAP
if ( isAutonomousAreaRole( role ) )
{
// The AC AAP
directoryService.getAccessControlAPCache().remove( dn );
// The CA AAP
directoryService.getCollectiveAttributeAPCache().remove( dn );
// The TE AAP
directoryService.getTriggerExecutionAPCache().remove( dn );
// The SS AAP
directoryService.getSubschemaAPCache().remove( dn );
// If it's an AAP, we can get out immediately
return;
}
// Deal with AccessControl AP
if ( isAccessControlSpecificRole( role ) || isAccessControlInnerRole( role ) )
{
directoryService.getAccessControlAPCache().remove( dn );
continue;
}
// Deal with CollectveAttribute AP
if ( isCollectiveAttributeSpecificRole( role ) || isCollectiveAttributeInnerRole( role ) )
{
directoryService.getCollectiveAttributeAPCache().remove( dn );
continue;
}
// Deal with SubSchema AP
if ( isSubschemaSpecficRole( role ) )
{
directoryService.getSubschemaAPCache().remove( dn );
continue;
}
// Deal with TriggerExecution AP
if ( isTriggerExecutionSpecificRole( role ) || isTriggerExecutionInnerRole( role ) )
{
directoryService.getTriggerExecutionAPCache().remove( dn );
continue;
}
}
}
/**
* Tells if the role is an AC IAP
*/
private boolean isAccessControlInnerRole( String role )
{
return role.equalsIgnoreCase( SchemaConstants.ACCESS_CONTROL_INNER_AREA ) ||
role.equals( SchemaConstants.ACCESS_CONTROL_INNER_AREA_OID );
}
/**
* Tells if the role is an AC SAP
*/
private boolean isAccessControlSpecificRole( String role )
{
return role.equalsIgnoreCase( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA ) ||
role.equals( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA_OID );
}
/**
* Tells if the role is a CA IAP
*/
private boolean isCollectiveAttributeInnerRole( String role )
{
return role.equalsIgnoreCase( SchemaConstants.COLLECTIVE_ATTRIBUTE_INNER_AREA ) ||
role.equals( SchemaConstants.COLLECTIVE_ATTRIBUTE_INNER_AREA_OID );
}
/**
* Tells if the role is a CA SAP
*/
private boolean isCollectiveAttributeSpecificRole( String role )
{
return role.equalsIgnoreCase( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA ) ||
role.equals( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA_OID );
}
/**
* Tells if the role is a TE IAP
*/
private boolean isTriggerExecutionInnerRole( String role )
{
return role.equalsIgnoreCase( SchemaConstants.TRIGGER_EXECUTION_INNER_AREA ) ||
role.equals( SchemaConstants.TRIGGER_EXECUTION_INNER_AREA_OID );
}
/**
* Tells if the role is a TE SAP
*/
private boolean isTriggerExecutionSpecificRole( String role )
{
return role.equalsIgnoreCase( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA ) ||
role.equals( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA_OID );
}
/**
* Tells if the role is a SS SAP
*/
private boolean isSubschemaSpecficRole( String role )
{
return role.equalsIgnoreCase( SchemaConstants.SUB_SCHEMA_ADMIN_SPECIFIC_AREA ) ||
role.equals( SchemaConstants.SUB_SCHEMA_ADMIN_SPECIFIC_AREA_OID );
}
/**
* Tells if the role is an AAP
*/
private boolean isAutonomousAreaRole( String role )
{
return role.equalsIgnoreCase( SchemaConstants.AUTONOMOUS_AREA ) ||
role.equals( SchemaConstants.AUTONOMOUS_AREA_OID );
}
/**
* Tells if the Administrative Point role is an AAP
*/
private boolean isAAP( Attribute adminPoint )
{
return ( adminPoint.contains( SchemaConstants.AUTONOMOUS_AREA ) || adminPoint
.contains( SchemaConstants.AUTONOMOUS_AREA_OID ) );
}
private boolean hasAccessControlSpecificRole( Attribute adminPoint )
{
return adminPoint.contains( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA ) ||
adminPoint.contains( SchemaConstants.ACCESS_CONTROL_SPECIFIC_AREA_OID );
}
private boolean isIAP( String role )
{
return INNER_AREA_ROLES.contains( role );
}
private boolean hasCollectiveAttributeSpecificRole( Attribute adminPoint )
{
return adminPoint.contains( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA ) ||
adminPoint.contains( SchemaConstants.COLLECTIVE_ATTRIBUTE_SPECIFIC_AREA_OID );
}
private boolean hasTriggerExecutionSpecificRole( Attribute adminPoint )
{
return adminPoint.contains( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA ) ||
adminPoint.contains( SchemaConstants.TRIGGER_EXECUTION_SPECIFIC_AREA_OID );
}
/**
* Check that we don't have an IAP and a SAP with the same family
*/
private void checkInnerSpecificMix( String role, Attribute adminPoint ) throws LdapUnwillingToPerformException
{
if ( isAccessControlInnerRole( role ) )
{
if ( hasAccessControlSpecificRole( adminPoint ) )
{
// This is inconsistent
String message = "Cannot add a specific Administrative Point and the same"
+ " inner Administrative point at the same time : " + adminPoint;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
else
{
return;
}
}
if ( isCollectiveAttributeInnerRole( role ) )
{
if ( hasCollectiveAttributeSpecificRole( adminPoint ) )
{
// This is inconsistent
String message = "Cannot add a specific Administrative Point and the same"
+ " inner Administrative point at the same time : " + adminPoint;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
else
{
return;
}
}
if ( isTriggerExecutionInnerRole( role ) )
{
if ( hasTriggerExecutionSpecificRole( adminPoint ) )
{
// This is inconsistent
String message = "Cannot add a specific Administrative Point and the same"
+ " inner Administrative point at the same time : " + adminPoint;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
else
{
return;
}
}
}
/**
* Check that the IAPs (if any) have a parent. We will check for each kind or role :
* AC, CA and TE.
*/
private void checkIAPHasParent( String role, Attribute adminPoint, Dn dn ) throws LdapUnwillingToPerformException
{
// Check for the AC role
if ( isAccessControlInnerRole( role ) )
{
DnNode<AccessControlAdministrativePoint> acCache = directoryService.getAccessControlAPCache();
DnNode<AccessControlAdministrativePoint> parent = acCache.getNode( dn );
if ( parent == null )
{
// We don't have any AC administrativePoint in the tree, this is an error
String message = "Cannot add an IAP with no parent : " + adminPoint;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}
else if ( isCollectiveAttributeInnerRole( role ) )
{
DnNode<CollectiveAttributeAdministrativePoint> caCache = directoryService.getCollectiveAttributeAPCache();
boolean hasAP = caCache.hasParentElement( dn );
if ( !hasAP )
{
// We don't have any AC administrativePoint in the tree, this is an error
String message = "Cannot add an IAP with no parent : " + adminPoint;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}
else if ( isTriggerExecutionInnerRole( role ) )
{
DnNode<TriggerExecutionAdministrativePoint> caCache = directoryService.getTriggerExecutionAPCache();
DnNode<TriggerExecutionAdministrativePoint> parent = caCache.getNode( dn );
if ( parent == null )
{
// We don't have any AC administrativePoint in the tree, this is an error
String message = "Cannot add an IAP with no parent : " + adminPoint;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}
else
{
// Wtf ? We *must* have an IAP here...
String message = "This is not an IAP : " + role;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}
//-------------------------------------------------------------------------------------------
// Interceptor initialization
//-------------------------------------------------------------------------------------------
/**
* Registers and initializes all {@link Authenticator}s to this service.
*/
public void init( DirectoryService directoryService ) throws LdapException
{
LOG.debug( "Initializing the AdministrativeInterceptor" );
super.init( directoryService );
nexus = directoryService.getPartitionNexus();
// Load all the AdministratvePoint :
// Autonomous Administrative Point first, then Specific
// administrative point, finally the Inner administrative Point
Dn adminDn = new Dn( schemaManager, ServerDNConstants.ADMIN_SYSTEM_DN );
// get the list of all the AAPs
List<Entry> administrativePoints = getAdministrativePoints();
lockWrite();
try
{
addAdminPointCache( administrativePoints );
}
finally
{
unlock();
}
}
/**
* {@inheritDoc}
*/
public void destroy()
{
}
private void updateAPEntries( Attribute adminPoint, String apUuid )
{
if ( isAAP( adminPoint ) )
{
}
return;
}
/**
* Add an administrative point into the DIT.
*
* We have to deal with some specific cases :
* <ul>
* <li>If it's an AA, then the added role should be the only one</li>
* <li>It's not possible to add IA and SA at the same time</li>
* @param addContext The {@link AddOperationContext} instance
*
* @throws LdapException If we had some error while processing the Add operation
*/
public void add( AddOperationContext addContext ) throws LdapException
{
LOG.debug( ">>> Entering into the Administrative Interceptor, addRequest" );
Entry entry = addContext.getEntry();
Dn dn = entry.getDn();
// Check if we are adding an Administrative Point
Attribute adminPoint = entry.get( ADMINISTRATIVE_ROLE_AT );
if ( adminPoint == null )
{
// Nope, go on.
next( addContext );
LOG.debug( "Exit from Administrative Interceptor, no AP in the added entry" );
return;
}
LOG.debug( "Addition of an administrative point at {} for the role {}", dn, adminPoint );
// Protect the AP caches against concurrent access
lockWrite();
try
{
// Loop on all the added roles to check if they are valid
for ( Value<?> role : adminPoint )
{
checkAddRole( role, adminPoint, dn );
}
// Ok, we are golden.
next( addContext );
String apUuid = entry.get( ENTRY_UUID_AT ).getString();
// Now, update the AdminPoint cache
createAdministrativePoints( adminPoint, dn, apUuid );
}
finally
{
// Release the APCaches lock
unlock();
}
LOG.debug( "Added an Administrative Point at {}", dn );
return;
}
/**
* We have to check that we can remove the associated AdministrativePoint : <br/>
* <ul>
* <li> if we remove an AAP, no descendant IAP should remain orphan</li>
* <li> If we remove a SAP, no descendant IAP should remain orphan</li>
* </ul>
* {@inheritDoc}
*/
public void delete( DeleteOperationContext deleteContext ) throws LdapException
{
LOG.debug( ">>> Entering into the Administrative Interceptor, delRequest" );
Entry entry = deleteContext.getEntry();
Dn dn = entry.getDn();
// Check if we are deleting an Administrative Point
Attribute adminPoint = entry.get( ADMINISTRATIVE_ROLE_AT );
if ( adminPoint == null )
{
// Nope, go on.
next( deleteContext );
LOG.debug( "Exit from Administrative Interceptor" );
return;
}
LOG.debug( "Deletion of an administrative point at {} for the role {}", dn, adminPoint );
// Protect the AP caches against concurrent access
lockWrite();
try
{
// Check that the removed AdministrativeRoles are valid. We don't have to do
// any other check, as the deleted entry has no children.
for ( Value<?> role : adminPoint )
{
if ( !isValidRole( role.getString() ) )
{
String message = "Cannot remove the given role, it's not a valid one :" + role;
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}
// Ok, we can remove the AP
next( deleteContext );
// Now, update the AdminPoint cache
deleteAdminPointCache( adminPoint, deleteContext );
}
finally
{
// Release the APCaches lock
unlock();
}
LOG.debug( "Deleted an Administrative Point at {}", dn );
return;
}
/**
* Only the add and remove modifications are fully supported. We have to check that the
* underlying APs are still consistent.
* We first have to compute the final AdministrativeRole, then do a diff with the
* initial attribute, to determinate which roles have been added and which ones have
* been deleted.
* Once this is done, we have to check that when deleting or adding each of those roles
* the admin model remains consistent.
*
* {@inheritDoc}
*/
public void modify( ModifyOperationContext modifyContext ) throws LdapException
{
LOG.debug( ">>> Entering into the Administrative Interceptor, modifyRequest" );
// We have to check that the modification is acceptable
List<Modification> modifications = modifyContext.getModItems();
Dn dn = modifyContext.getDn();
String uuid = modifyContext.getEntry().get( ENTRY_UUID_AT ).getString();
// Check if we are modifying any AdminRole
boolean adminRolePresent = false;
for ( Modification modification : modifications )
{
if ( modification.getAttribute().getAttributeType() == ADMINISTRATIVE_ROLE_AT )
{
adminRolePresent = true;
break;
}
}
if ( adminRolePresent )
{
// We have modified any AdministrativeRole attribute, we can continue
// Create a clone of the current AdminRole AT
Attribute modifiedAdminRole = ( ( ClonedServerEntry ) modifyContext.getEntry() ).getOriginalEntry().get(
ADMINISTRATIVE_ROLE_AT );
if ( modifiedAdminRole == null )
{
// Create the attribute, as it does not already exist in the entry
modifiedAdminRole = new DefaultAttribute( ADMINISTRATIVE_ROLE_AT );
}
else
{
// We have already an AdminRole AT clone it
modifiedAdminRole = modifiedAdminRole.clone();
}
try
{
// Acquire the lock
lockWrite();
// Get the AP caches as we will apply modifications to them
DnNode<AccessControlAdministrativePoint> acapCache = directoryService.getAccessControlAPCache();
DnNode<CollectiveAttributeAdministrativePoint> caapCache = directoryService
.getCollectiveAttributeAPCache();
DnNode<TriggerExecutionAdministrativePoint> teapCache = directoryService.getTriggerExecutionAPCache();
DnNode<SubschemaAdministrativePoint> ssapCache = directoryService.getSubschemaAPCache();
// Loop on the modification to select the AdministrativeRole and process it :
// we will create a new AT containing all the roles after having applied the modifications
// on it
for ( Modification modification : modifications )
{
Attribute attribute = modification.getAttribute();
// Skip all the attributes but AdministrativeRole
if ( attribute.getAttributeType() == ADMINISTRATIVE_ROLE_AT )
{
// Ok, we have a modification impacting the administrative role
// Apply it to a virtual AdministrativeRole attribute
switch ( modification.getOperation() )
{
case ADD_ATTRIBUTE:
for ( Value<?> role : attribute )
{
addRole( role.getString(), dn, uuid, acapCache, caapCache, teapCache,
ssapCache );
// Add the role to the modified attribute
modifiedAdminRole.add( role );
}
break;
case REMOVE_ATTRIBUTE:
// It may be a complete removal
if ( attribute.size() == 0 )
{
// Complete removal. Loop on all the existing roles and remove them
for ( Value<?> role : modifiedAdminRole )
{
//checkDelRole( role, modifiedAdminRole, dn, directoryService.getAdministrativePoints() );
delRole( role.getString(), dn, uuid, acapCache, caapCache, teapCache, ssapCache );
}
modifiedAdminRole.clear();
break;
}
// Now deal with the values to remove
for ( Value<?> value : attribute )
{
if ( !isValidRole( value.getString() ) )
{
// Not a valid role : we will throw an exception
String msg = "Invalid role : " + value.getString();
LOG.error( msg );
throw new LdapInvalidAttributeValueException(
ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX,
msg );
}
if ( !modifiedAdminRole.contains( value ) )
{
// We can't remove a value if it does not exist !
String msg = "Cannot remove the administrative role value" + value
+ ", it does not exist";
LOG.error( msg );
throw new LdapNoSuchAttributeException( msg );
}
modifiedAdminRole.remove( value );
delRole( value.getString(), dn, uuid, acapCache, caapCache, teapCache, ssapCache );
}
break;
case REPLACE_ATTRIBUTE:
if( ! ( modifyContext.isReplEvent() && modifyContext.getSession().isAdministrator() ) )
{
// Not supported in non-replication related operations
String msg = "Cannot replace an administrative role, the opertion is not supported";
LOG.error( msg );
throw new LdapUnwillingToPerformException( msg );
}
}
}
}
// At this point, we have a new AdministrativeRole AT, we need to check that the
// roles hierarchy is still correct
// TODO !!!
}
finally
{
unlock();
}
}
next( modifyContext );
}
/**
* {@inheritDoc}
*/
public void move( MoveOperationContext moveContext ) throws LdapException
{
LOG.debug( ">>> Entering into the Administrative Interceptor, moveRequest" );
Entry entry = moveContext.getOriginalEntry();
// Check if we are moving an Administrative Point
Attribute adminPoint = entry.get( ADMINISTRATIVE_ROLE_AT );
if ( adminPoint == null )
{
// Nope, go on.
next( moveContext );
LOG.debug( "Exit from Administrative Interceptor" );
return;
}
// Else throw an UnwillingToPerform exception ATM
String message = "Cannot move an Administrative Point in the current version";
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
/**
* {@inheritDoc}
*/
public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
{
LOG.debug( ">>> Entering into the Administrative Interceptor, moveAndRenameRequest" );
Entry entry = moveAndRenameContext.getOriginalEntry();
// Check if we are moving and renaming an Administrative Point
Attribute adminPoint = entry.get( ADMINISTRATIVE_ROLE_AT );
if ( adminPoint == null )
{
// Nope, go on.
next( moveAndRenameContext );
LOG.debug( "Exit from Administrative Interceptor" );
return;
}
// Else throw an UnwillingToPerform exception ATM
String message = "Cannot move and rename an Administrative Point in the current version";
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
/**
* {@inheritDoc}
*/
public void rename( RenameOperationContext renameContext ) throws LdapException
{
LOG.debug( ">>> Entering into the Administrative Interceptor, renameRequest" );
Entry entry = renameContext.getEntry();
// Check if we are renaming an Administrative Point
Attribute adminPoint = entry.get( ADMINISTRATIVE_ROLE_AT );
if ( adminPoint == null )
{
// Nope, go on.
next( renameContext );
LOG.debug( "Exit from Administrative Interceptor" );
return;
}
// Else throw an UnwillingToPerform exception ATM
String message = "Cannot rename an Administrative Point in the current version";
LOG.error( message );
throw new LdapUnwillingToPerformException( message );
}
}