blob: 9a17c03dd49d758d5a01659d00b47a2ab221881f [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.schema;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NoPermissionException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InvalidAttributeValueException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.apache.directory.server.core.DirectoryServiceConfiguration;
import org.apache.directory.server.core.configuration.InterceptorConfiguration;
import org.apache.directory.server.core.enumeration.SearchResultFilter;
import org.apache.directory.server.core.enumeration.SearchResultFilteringEnumeration;
import org.apache.directory.server.core.interceptor.BaseInterceptor;
import org.apache.directory.server.core.interceptor.NextInterceptor;
import org.apache.directory.server.core.invocation.Invocation;
import org.apache.directory.server.core.invocation.InvocationStack;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.shared.ldap.exception.LdapAttributeInUseException;
import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeIdentifierException;
import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeValueException;
import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
import org.apache.directory.shared.ldap.exception.LdapNoSuchAttributeException;
import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.PresenceNode;
import org.apache.directory.shared.ldap.filter.SimpleNode;
import org.apache.directory.shared.ldap.message.LockableAttributeImpl;
import org.apache.directory.shared.ldap.message.LockableAttributesImpl;
import org.apache.directory.shared.ldap.message.ModificationItemImpl;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.schema.DITContentRule;
import org.apache.directory.shared.ldap.schema.DITStructureRule;
import org.apache.directory.shared.ldap.schema.MatchingRule;
import org.apache.directory.shared.ldap.schema.MatchingRuleUse;
import org.apache.directory.shared.ldap.schema.NameForm;
import org.apache.directory.shared.ldap.schema.ObjectClass;
import org.apache.directory.shared.ldap.schema.SchemaUtils;
import org.apache.directory.shared.ldap.schema.Syntax;
import org.apache.directory.shared.ldap.schema.UsageEnum;
import org.apache.directory.shared.ldap.util.AttributeUtils;
import org.apache.directory.shared.ldap.util.DateUtils;
import org.apache.directory.shared.ldap.util.EmptyEnumeration;
import org.apache.directory.shared.ldap.util.SingletonEnumeration;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An {@link org.apache.directory.server.core.interceptor.Interceptor} that manages and enforces schemas.
*
* @todo Better interceptor description required.
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public class SchemaService extends BaseInterceptor
{
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String BINARY_KEY = "java.naming.ldap.attributes.binary";
/** The LoggerFactory used by this Interceptor */
private static Logger log = LoggerFactory.getLogger( SchemaService.class );
/** Speedup for logs */
private static final boolean IS_DEBUG = log.isDebugEnabled();
/**
* the root nexus to all database partitions
*/
private PartitionNexus nexus;
/**
* a binary attribute tranforming filter: String -> byte[]
*/
private BinaryAttributeFilter binaryAttributeFilter;
private TopFilter topFilter;
private List filters = new ArrayList();
/**
* the global schema object registries
*/
private GlobalRegistries globalRegistries;
private Set binaries;
/**
* subschemaSubentry attribute's value from Root DSE
*/
private LdapDN subschemaSubentryDn;
/**
* The time when the server started up.
*/
private String startUpTimeStamp;
/** A map used to store all the objectClasses superiors */
private Map superiors;
/** A map used to store all the objectClasses may attributes */
private Map allMay;
/** A map used to store all the objectClasses must */
private Map allMust;
/** A map used to store all the objectClasses allowed attributes (may + must) */
private Map allowed;
/**
* Creates a schema service interceptor.
*/
public SchemaService()
{
startUpTimeStamp = DateUtils.getGeneralizedTime();
}
/**
* Initialize the Schema Service
*
* @param factoryCfg
* @param cfg
*
* @throws NamingException
*/
public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
{
if ( IS_DEBUG )
{
log.debug( "Initializing SchemaService..." );
}
nexus = factoryCfg.getPartitionNexus();
globalRegistries = factoryCfg.getGlobalRegistries();
binaryAttributeFilter = new BinaryAttributeFilter();
topFilter = new TopFilter();
filters.add( binaryAttributeFilter );
filters.add( topFilter );
binaries = ( Set ) factoryCfg.getEnvironment().get( BINARY_KEY );
// stuff for dealing with subentries (garbage for now)
String subschemaSubentry = ( String ) nexus.getRootDSE().get( "subschemaSubentry" ).get();
subschemaSubentryDn = new LdapDN( subschemaSubentry );
subschemaSubentryDn.normalize( globalRegistries.getAttributeTypeRegistry().getNormalizerMapping() );
computeSuperiors();
if ( IS_DEBUG )
{
log.debug( "SchemaService Initialized !" );
}
}
/**
* Compute the MUST attributes for an objectClass. This method gather all the
* MUST from all the objectClass and its superors.
*/
private void computeMustAttributes( ObjectClass objectClass, Set atSeen ) throws NamingException
{
List parents = (List)superiors.get( objectClass.getOid() );
List mustList = new ArrayList();
List allowedList = new ArrayList();
Set mustSeen = new HashSet();
allMust.put( objectClass.getOid(), mustList );
allowed.put( objectClass.getOid(), allowedList );
Iterator objectClasses = parents.iterator();
while ( objectClasses.hasNext() )
{
ObjectClass parent = (ObjectClass)objectClasses.next();
AttributeType[] mustParent = parent.getMustList();
if ( ( mustParent != null ) && ( mustParent.length != 0 ) )
{
for ( int i = 0; i < mustParent.length; i++ )
{
AttributeType attributeType = mustParent[i];
String oid = attributeType.getOid();
if ( !mustSeen.contains( oid ) )
{
mustSeen.add( oid );
mustList.add( attributeType );
allowedList.add( attributeType );
atSeen.add( attributeType.getOid() );
}
}
}
}
}
/**
* Compute the MAY attributes for an objectClass. This method gather all the
* MAY from all the objectClass and its superors.
*
* The allowed attributes is also computed, it's the union of MUST and MAY
*/
private void computeMayAttributes( ObjectClass objectClass, Set atSeen ) throws NamingException
{
List parents = (List)superiors.get( objectClass.getOid() );
List mayList = new ArrayList();
Set maySeen = new HashSet();
List allowedList = (List)allowed.get( objectClass.getOid() );
allMay.put( objectClass.getOid(), mayList );
Iterator objectClasses = parents.iterator();
while ( objectClasses.hasNext() )
{
ObjectClass parent = (ObjectClass)objectClasses.next();
AttributeType[] mustParent = parent.getMustList();
if ( ( mustParent != null ) && ( mustParent.length != 0 ) )
{
for ( int i = 0; i < mustParent.length; i++ )
{
AttributeType attributeType = mustParent[i];
String oid = attributeType.getOid();
if ( !maySeen.contains( oid ) )
{
maySeen.add( oid );
mayList.add( attributeType );
if ( !atSeen.contains( oid ) )
{
allowedList.add( attributeType );
}
}
}
}
}
}
/**
* Recursively compute all the superiors of an object class. For instance, considering
* 'inetOrgPerson', it's direct superior is 'organizationalPerson', which direct superior
* is 'Person', which direct superior is 'top'.
*
* As a result, we will gather all of these three ObjectClasses in 'inetOrgPerson' ObjectClasse
* superiors.
*/
private void computeOCSuperiors( ObjectClass objectClass, List superiors, Set ocSeen ) throws NamingException
{
ObjectClass[] parents = objectClass.getSuperClasses();
// Loop on all the objectClass superiors
if ( ( parents != null ) && ( parents.length != 0 ) )
{
for ( int i = 0; i < parents.length; i++ )
{
ObjectClass parent = parents[i];
// Top is not added
if ( "top".equals( parent.getName() ) )
{
continue;
}
// For each one, recurse
computeOCSuperiors( parent, superiors, ocSeen );
String oid = parents[i].getOid();
if ( !ocSeen.contains( oid ) )
{
superiors.add( parent );
ocSeen.add( oid );
}
}
}
return;
}
/**
* Compute all ObjectClasses superiors, MAY and MUST attributes.
* @throws NamingException
*/
private void computeSuperiors() throws NamingException
{
Iterator objectClasses = globalRegistries.getObjectClassRegistry().list();
superiors = new HashMap();
allMust = new HashMap();
allMay = new HashMap();
allowed = new HashMap();
while ( objectClasses.hasNext() )
{
List ocSuperiors = new ArrayList();
ObjectClass objectClass = (ObjectClass)objectClasses.next();
superiors.put( objectClass.getOid(), ocSuperiors );
computeOCSuperiors( objectClass, ocSuperiors, new HashSet() );
Set atSeen = new HashSet();
computeMustAttributes( objectClass, atSeen );
computeMayAttributes( objectClass, atSeen );
superiors.put( objectClass.getName(), ocSuperiors );
}
}
/**
*
*/
public NamingEnumeration list( NextInterceptor nextInterceptor, LdapDN base ) throws NamingException
{
NamingEnumeration e = nextInterceptor.list( base );
Invocation invocation = InvocationStack.getInstance().peek();
return new SearchResultFilteringEnumeration( e, new SearchControls(), invocation, binaryAttributeFilter );
}
/**
* Remove all unknown attributes from the searchControls, to avoid an exception.
*
* RFC 2251 states that :
* " Attributes MUST be named at most once in the list, and are returned "
* " at most once in an entry. "
* " If there are attribute descriptions in "
* " the list which are not recognized, they are ignored by the server."
*
* @param searchCtls The SearchControls we will filter
*/
private void filterAttributesToReturn( SearchControls searchCtls ) throws NamingException
{
String[] attributes = searchCtls.getReturningAttributes();
if ( ( attributes == null ) || ( attributes.length == 0 ) )
{
return;
}
Map filteredAttrs = new HashMap();
for ( int i = 0; i < attributes.length; i++ )
{
String attribute = attributes[i];
// Skip special attributes
if ( ( "*".equals( attribute ) ) || ( "+".equals( attribute ) ) || ( "1.1".equals( attribute ) ) )
{
if ( !filteredAttrs.containsKey( attribute ) )
{
filteredAttrs.put( attribute, attribute );
}
continue;
}
if ( globalRegistries.getAttributeTypeRegistry().hasAttributeType( attribute ) )
{
String oid = globalRegistries.getOidRegistry().getOid( attribute );
if ( !filteredAttrs.containsKey( oid ) )
{
filteredAttrs.put( oid, attribute );
}
}
else
{
log.warn( "The attribute " + attribute + " was not recognized as a valid attributeType." );
// Simply ignore the attribute
}
}
// If we still have the same attribute number, then we can just get out the method
if ( filteredAttrs.size() == attributes.length )
{
return;
}
// Some attributes have been removed. let's modify the searchControl
String[] newAttributesList = new String[filteredAttrs.size()];
int pos = 0;
Iterator keys = filteredAttrs.keySet().iterator();
while ( keys.hasNext() )
{
newAttributesList[pos++] = (String)filteredAttrs.get( keys.next() );
}
searchCtls.setReturningAttributes( newAttributesList );
}
/**
*
*/
public NamingEnumeration search( NextInterceptor nextInterceptor, LdapDN base, Map env, ExprNode filter,
SearchControls searchCtls ) throws NamingException
{
// We have to eliminate bad attributes from the request, accordingly
// to RFC 2251, chap. 4.5.1. Basically, all unknown attributes are removed
// from the list
filterAttributesToReturn( searchCtls );
// Deal with the normal case : searching for a normal value (not subSchemaSubEntry
if ( !subschemaSubentryDn.toNormName().equals( base.toNormName() ) )
{
NamingEnumeration e = nextInterceptor.search( base, env, filter, searchCtls );
Invocation invocation = InvocationStack.getInstance().peek();
if ( searchCtls.getReturningAttributes() != null )
{
return new SearchResultFilteringEnumeration( e, new SearchControls(), invocation, topFilter );
}
return new SearchResultFilteringEnumeration( e, searchCtls, invocation, filters );
}
// The user was searching into the subSchemaSubEntry
// Thgis kind of search _must_ be limited to OBJECT scope (the subSchemaSubEntry
// does not have any sub level)
if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE )
{
// The filter can be an equality or a presence, but nothing else
if ( filter instanceof SimpleNode )
{
// We should get the value for the filter.
// only 'top' and 'subSchema' are valid values
SimpleNode node = ( SimpleNode ) filter;
String objectClass = null;
if ( node.getValue() instanceof String )
{
objectClass = ( String ) node.getValue();
}
else
{
objectClass = node.getValue().toString();
}
String objectClassOid = null;
if ( globalRegistries.getObjectClassRegistry().hasObjectClass( objectClass ) )
{
objectClassOid = globalRegistries.getObjectClassRegistry().lookup( objectClass ).getName();
}
// see if node attribute is objectClass
if ( node.getAttribute().equalsIgnoreCase( "2.5.4.0" )
&& ( "top".equalsIgnoreCase( objectClassOid ) || "subschema".equalsIgnoreCase( objectClassOid ) )
&& ( node.getAssertionType() == SimpleNode.EQUALITY ) )
{
// call.setBypass( true );
Attributes attrs = getSubschemaEntry( searchCtls.getReturningAttributes() );
SearchResult result = new SearchResult( base.toString(), null, attrs );
return new SingletonEnumeration( result );
}
else
{
return new EmptyEnumeration();
}
}
else if ( filter instanceof PresenceNode )
{
PresenceNode node = ( PresenceNode ) filter;
// see if node attribute is objectClass
if ( node.getAttribute().equalsIgnoreCase( "2.5.4.0" ) )
{
// call.setBypass( true );
Attributes attrs = getSubschemaEntry( searchCtls.getReturningAttributes() );
SearchResult result = new SearchResult( base.toString(), null, attrs );
return new SingletonEnumeration( result );
}
}
}
// In any case not handled previously, just return an empty result
return new EmptyEnumeration();
}
/**
*
* @param ids
* @return
* @throws NamingException
*/
private Attributes getSubschemaEntry( String[] ids ) throws NamingException
{
if ( ids == null )
{
ids = EMPTY_STRING_ARRAY;
}
Set set = new HashSet();
LockableAttributesImpl attrs = new LockableAttributesImpl();
LockableAttributeImpl attr;
for ( int ii = 0; ii < ids.length; ii++ )
{
set.add( ids[ii].toLowerCase() );
}
// Check whether the set contains a plus, and use it below to include all
// operational attributes. Due to RFC 3673, and issue DIREVE-228 in JIRA
boolean returnAllOperationalAttributes = set.contains( "+" );
if ( returnAllOperationalAttributes || set.contains( "objectclasses" ) )
{
attr = new LockableAttributeImpl( "objectClasses" );
Iterator list = globalRegistries.getObjectClassRegistry().list();
while ( list.hasNext() )
{
ObjectClass oc = ( ObjectClass ) list.next();
attr.add( SchemaUtils.render( oc ).toString() );
}
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "attributetypes" ) )
{
attr = new LockableAttributeImpl( "attributeTypes" );
Iterator list = globalRegistries.getAttributeTypeRegistry().list();
while ( list.hasNext() )
{
AttributeType at = ( AttributeType ) list.next();
attr.add( SchemaUtils.render( at ).toString() );
}
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "matchingrules" ) )
{
attr = new LockableAttributeImpl( "matchingRules" );
Iterator list = globalRegistries.getMatchingRuleRegistry().list();
while ( list.hasNext() )
{
MatchingRule mr = ( MatchingRule ) list.next();
attr.add( SchemaUtils.render( mr ).toString() );
}
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "matchingruleuse" ) )
{
attr = new LockableAttributeImpl( "matchingRuleUse" );
Iterator list = globalRegistries.getMatchingRuleUseRegistry().list();
while ( list.hasNext() )
{
MatchingRuleUse mru = ( MatchingRuleUse ) list.next();
attr.add( SchemaUtils.render( mru ).toString() );
}
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "ldapsyntaxes" ) )
{
attr = new LockableAttributeImpl( "ldapSyntaxes" );
Iterator list = globalRegistries.getSyntaxRegistry().list();
while ( list.hasNext() )
{
Syntax syntax = ( Syntax ) list.next();
attr.add( SchemaUtils.render( syntax ).toString() );
}
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "ditcontentrules" ) )
{
attr = new LockableAttributeImpl( "dITContentRules" );
Iterator list = globalRegistries.getDitContentRuleRegistry().list();
while ( list.hasNext() )
{
DITContentRule dcr = ( DITContentRule ) list.next();
attr.add( SchemaUtils.render( dcr ).toString() );
}
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "ditstructurerules" ) )
{
attr = new LockableAttributeImpl( "dITStructureRules" );
Iterator list = globalRegistries.getDitStructureRuleRegistry().list();
while ( list.hasNext() )
{
DITStructureRule dsr = ( DITStructureRule ) list.next();
attr.add( SchemaUtils.render( dsr ).toString() );
}
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "nameforms" ) )
{
attr = new LockableAttributeImpl( "nameForms" );
Iterator list = globalRegistries.getNameFormRegistry().list();
while ( list.hasNext() )
{
NameForm nf = ( NameForm ) list.next();
attr.add( SchemaUtils.render( nf ).toString() );
}
attrs.put( attr );
}
// timeestamps are hacks for now until the schema is actually updateable these
// use the servers startup time stamp for both modify and create timestamps
if ( returnAllOperationalAttributes || set.contains( "createtimestamp" ) )
{
attr = new LockableAttributeImpl( "createTimestamp" );
attr.add( startUpTimeStamp );
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "modifytimestamp" ) )
{
attr = new LockableAttributeImpl( "modifyTimestamp" );
attr.add( startUpTimeStamp );
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "creatorsname" ) )
{
attr = new LockableAttributeImpl( "creatorsName" );
attr.add( PartitionNexus.ADMIN_PRINCIPAL );
attrs.put( attr );
}
if ( returnAllOperationalAttributes || set.contains( "modifiersname" ) )
{
attr = new LockableAttributeImpl( "modifiersName" );
attr.add( PartitionNexus.ADMIN_PRINCIPAL );
attrs.put( attr );
}
int minSetSize = 0;
if ( set.contains( "+" ) )
{
minSetSize++;
}
if ( set.contains( "*" ) )
{
minSetSize++;
}
if ( set.contains( "ref" ) )
{
minSetSize++;
}
// add the objectClass attribute
if ( set.contains( "*" ) || set.contains( "objectclass" ) || set.size() == minSetSize )
{
attr = new LockableAttributeImpl( "objectClass" );
attr.add( "top" );
attr.add( "subschema" );
attrs.put( attr );
}
// add the cn attribute as required for the RDN
if ( set.contains( "*" ) || set.contains( "cn" ) || set.contains( "commonname" ) || set.size() == minSetSize )
{
attrs.put( "cn", "schema" );
}
return attrs;
}
/**
* Search for an entry, using its DN. Binary attributes and ObjectClass attribute are removed.
*/
public Attributes lookup( NextInterceptor nextInterceptor, LdapDN name ) throws NamingException
{
Attributes result = nextInterceptor.lookup( name );
filterBinaryAttributes( result );
filterObjectClass( result );
return result;
}
/**
*
*/
public Attributes lookup( NextInterceptor nextInterceptor, LdapDN name, String[] attrIds ) throws NamingException
{
Attributes result = nextInterceptor.lookup( name, attrIds );
if ( result == null )
{
return null;
}
filterBinaryAttributes( result );
// filterTop( result );
filterObjectClass( result );
return result;
}
/**
* Checks to see if an attribute is required by as determined from an entry's
* set of objectClass attribute values.
*
* @param attrId the attribute to test if required by a set of objectClass values
* @param objectClass the objectClass values
* @return true if the objectClass values require the attribute, false otherwise
* @throws NamingException if the attribute is not recognized
*/
private boolean isRequired( String attrId, Attribute objectClass ) throws NamingException
{
OidRegistry oidRegistry = globalRegistries.getOidRegistry();
ObjectClassRegistry registry = globalRegistries.getObjectClassRegistry();
if ( !oidRegistry.hasOid( attrId ) )
{
return false;
}
String attrOid = oidRegistry.getOid( attrId );
for ( int ii = 0; ii < objectClass.size(); ii++ )
{
ObjectClass ocSpec = registry.lookup( ( String ) objectClass.get( ii ) );
AttributeType[] mustList = ocSpec.getMustList();
for ( int jj = 0; jj < mustList.length; jj++ )
{
if ( mustList[jj].getOid().equals( attrOid ) )
{
return true;
}
}
}
return false;
}
/**
* Checks to see if removing a set of attributes from an entry completely removes
* that attribute's values. If change has zero size then all attributes are
* presumed to be removed.
*
* @param change
* @param entry
* @return
* @throws NamingException
*/
private boolean isCompleteRemoval( Attribute change, Attributes entry ) throws NamingException
{
// if change size is 0 then all values are deleted then we're in trouble
if ( change.size() == 0 )
{
return true;
}
// can't do math to figure our if all values are removed since some
// values in the modify request may not be in the entry. we need to
// remove the values from a cloned version of the attribute and see
// if nothing is left.
Attribute changedEntryAttr = ( Attribute ) entry.get( change.getID() ).clone();
for ( int jj = 0; jj < change.size(); jj++ )
{
changedEntryAttr.remove( change.get( jj ) );
}
return changedEntryAttr.size() == 0;
}
/**
*
* @param modOp
* @param changes
* @param existing
* @return
* @throws NamingException
*/
private Attribute getResultantObjectClasses( int modOp, Attribute changes, Attribute existing ) throws NamingException
{
if ( changes == null && existing == null )
{
return new LockableAttributeImpl( "objectClass" );
}
if ( changes == null )
{
return existing;
}
if ( existing == null && modOp == DirContext.ADD_ATTRIBUTE )
{
return changes;
}
else if ( existing == null )
{
return new LockableAttributeImpl( "objectClass" );
}
switch ( modOp )
{
case ( DirContext.ADD_ATTRIBUTE ):
return AttributeUtils.getUnion( existing, changes );
case ( DirContext.REPLACE_ATTRIBUTE ):
return ( Attribute ) changes.clone();
case ( DirContext.REMOVE_ATTRIBUTE ):
return AttributeUtils.getDifference( existing, changes );
default:
throw new InternalError( "" );
}
}
private void getSuperiors( ObjectClass oc, Set ocSeen, List result ) throws NamingException
{
ObjectClass[] superiors = oc.getSuperClasses();
for ( int i = 0; i < superiors.length; i++ )
{
ObjectClass parent = superiors[i];
// Skip 'top'
if ( "top".equals( parent.getName() ) )
{
continue;
}
if ( !ocSeen.contains( parent.getOid() ) )
{
ocSeen.add( parent.getOid() );
result.add( parent );
}
// Recurse on the parent
getSuperiors( parent, ocSeen, result );
}
}
private boolean getObjectClasses( Attribute objectClasses, List result ) throws NamingException
{
Set ocSeen = new HashSet();
ObjectClassRegistry registry = globalRegistries.getObjectClassRegistry();
// We must select all the ObjectClasses, except 'top',
// but including all the inherited ObjectClasses
NamingEnumeration ocs = objectClasses.getAll();
boolean hasExtensibleObject = false;
while ( ocs.hasMoreElements() )
{
String objectClassName = (String)ocs.nextElement();
if ( "top".equals( objectClassName ) )
{
continue;
}
if ( "extensibleObject".equalsIgnoreCase( objectClassName ) )
{
hasExtensibleObject = true;
}
ObjectClass oc = registry.lookup( objectClassName );
// Add all unseen objectclasses to the list, except 'top'
if ( !ocSeen.contains( oc.getOid() ) )
{
ocSeen.add( oc.getOid() );
result.add( oc );
}
// Find all current OC parents
getSuperiors( oc, ocSeen, result );
}
return hasExtensibleObject;
}
private Set getAllMust( NamingEnumeration objectClasses ) throws NamingException
{
Set must = new HashSet();
// Loop on all objectclasses
while ( objectClasses.hasMoreElements() )
{
String ocName = (String)objectClasses.nextElement();
ObjectClass oc = globalRegistries.getObjectClassRegistry().lookup( ocName );
AttributeType[] types = oc.getMustList();
// For each objectClass, loop on all MUST attributeTypes, if any
if ( ( types != null ) && ( types.length > 0 ) )
{
for ( int j = 0; j < types.length; j++ )
{
String oid = types[j].getOid();
must.add( oid );
}
}
}
return must;
}
private Set getAllAllowed( NamingEnumeration objectClasses, Set must ) throws NamingException
{
Set allowed = new HashSet( must );
// Add the 'ObjectClass' attribute ID
allowed.add( globalRegistries.getOidRegistry().getOid( "objectClass" ) );
// Loop on all objectclasses
while ( objectClasses.hasMoreElements() )
{
String ocName = (String)objectClasses.nextElement();
ObjectClass oc = globalRegistries.getObjectClassRegistry().lookup( ocName );
AttributeType[] types = oc.getMayList();
// For each objectClass, loop on all MUST attributeTypes, if any
if ( ( types != null ) && ( types.length > 0 ) )
{
for ( int j = 0; j < types.length; j++ )
{
String oid = types[j].getOid();
allowed.add( oid );
}
}
}
return allowed;
}
/**
* Given the objectClasses for an entry, this method adds missing ancestors
* in the hierarchy except for top which it removes. This is used for this
* solution to DIREVE-276. More information about this solution can be found
* <a href="http://docs.safehaus.org:8080/x/kBE">here</a>.
*
* @param objectClassAttr the objectClass attribute to modify
* @throws NamingException if there are problems
*/
private void alterObjectClasses( Attribute objectClassAttr )
throws NamingException
{
Set objectClasses = new HashSet();
// Init the objectClass list with 'top'
objectClasses.add( "top" );
// Construct the new list of ObjectClasses
NamingEnumeration ocList = objectClassAttr.getAll();
while ( ocList.hasMoreElements() )
{
String ocName = ( String ) ocList.nextElement();
if ( !ocName.equalsIgnoreCase( "top" ) )
{
String ocLowerName = ocName.toLowerCase();
ObjectClass objectClass = globalRegistries.getObjectClassRegistry().lookup( ocLowerName );
if ( !objectClasses.contains( ocLowerName ) )
{
objectClasses.add( ocLowerName );
}
List ocSuperiors = (List)superiors.get( objectClass.getOid() );
if ( ( ocSuperiors != null ) && ( ocSuperiors.size() != 0 ) )
{
Iterator iter = ocSuperiors.iterator();
while ( iter.hasNext() )
{
ObjectClass oc = (ObjectClass)iter.next();
if ( !objectClasses.contains( oc.getName().toLowerCase() ) )
{
objectClasses.add( oc.getName() );
}
}
}
}
}
// Now, reset the ObjectClass attribute and put the new list into it
objectClassAttr.clear();
Iterator iter = objectClasses.iterator();
while ( iter.hasNext() )
{
objectClassAttr.add( iter.next() );
}
}
/**
* Check that the modify operations are allowed, and the conform to
* the schema.
*
* @param next The next interceptor to call when we are done with the local operation
* @param name The DN on which the modification is being done
* @param modOp The modification. One of :
* DirContext.ADD_ATTRIBUTE
* DirContext.REMOVE_ATTRIBUTE
* DirContext.REPLACE_ATTRIBUTE
* @param mods The modifications to check. Each operation is atomic, and should
* be applied to a copy of the entry, in order to check that the schema is not
* violated at the end. For instance, we can't delete an attribute that does
* not exist and add it later. The opposite is legal.
*
* @throws NamingException The generic exception we get if an illegal operation occurs
* @throws LdapNameNotFoundException If we don't find the entry, then this exception is thrown.
* @throws LdapInvalidAttributeIdentifierException The modified attribute is not known
* by the schema, or the Entry is not extensible.
* @throws LdapNoSuchAttributeException The modified Attribute does not exist in the
* current entry or is not added by a previous modification operation.
* @throws LdapSchemaViolationException Another schema violation occured.
*/
public void modify( NextInterceptor next, LdapDN name, int modOp, Attributes mods ) throws NamingException
{
// First, we get the entry from the backend. If it does not exist, then we throw an exception
Attributes entry = nexus.lookup( name );
if ( entry == null )
{
log.error( "No entry with this name :{}", name );
throw new LdapNameNotFoundException( "The entry which name is " + name + " is not found." );
}
Attribute objectClass = getResultantObjectClasses( modOp, mods.get( "objectClass" ), entry.get( "objectClass" ) );
ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
AttributeTypeRegistry atRegistry = this.globalRegistries.getAttributeTypeRegistry();
NamingEnumeration changes = mods.getIDs();
Attributes tmpEntryForAdd = (Attributes)entry.clone();
while ( changes.hasMore() )
{
String id = ( String ) changes.next();
Attribute change = mods.get( id );
if ( !atRegistry.hasAttributeType( change.getID() ) && !AttributeUtils.containsValueCaseIgnore( objectClass, "extensibleObject" ) )
{
throw new LdapInvalidAttributeIdentifierException( "unrecognized attributeID " + change.getID() );
}
if ( modOp == DirContext.ADD_ATTRIBUTE )
{
tmpEntryForAdd.put( change );
if ( change.size() == 0 )
{
// not ok for add but ok for replace and delete
throw new LdapInvalidAttributeValueException( "No value is not a valid value for an attribute.",
ResultCodeEnum.INVALIDATTRIBUTESYNTAX );
}
}
if ( modOp == DirContext.REMOVE_ATTRIBUTE && entry.get( change.getID() ) == null )
{
throw new LdapNoSuchAttributeException();
}
// for required attributes we need to check if all values are removed
// if so then we have a schema violation that must be thrown
if ( modOp == DirContext.REMOVE_ATTRIBUTE && isRequired( change.getID(), objectClass )
&& isCompleteRemoval( change, entry ) )
{
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECTCLASSVIOLATION );
}
}
if ( modOp == DirContext.ADD_ATTRIBUTE )
{
assertNumberOfAttributeValuesValid( tmpEntryForAdd );
}
if ( modOp == DirContext.REMOVE_ATTRIBUTE )
{
SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, mods, this.globalRegistries.getOidRegistry() );
SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, mods, objectClass );
}
if ( modOp == DirContext.REPLACE_ATTRIBUTE )
{
SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, mods, this.globalRegistries.getOidRegistry() );
SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, mods );
assertNumberOfAttributeValuesValid( mods );
}
// let's figure out if we need to add or take away from mods to maintain
// the objectClass attribute with it's hierarchy of ancestors
if ( mods.get( "objectClass" ) != null )
{
Attribute alteredObjectClass = ( Attribute ) objectClass.clone();
alterObjectClasses( alteredObjectClass );
if ( !alteredObjectClass.equals( objectClass ) )
{
Attribute ocMods = mods.get( "objectClass" );
switch ( modOp )
{
case ( DirContext.ADD_ATTRIBUTE ):
if ( AttributeUtils.containsValueCaseIgnore( ocMods, "top" ) )
{
ocMods.remove( "top" );
}
for ( int ii = 0; ii < alteredObjectClass.size(); ii++ )
{
if ( !AttributeUtils.containsValueCaseIgnore( objectClass, alteredObjectClass.get( ii ) ) )
{
ocMods.add( alteredObjectClass.get( ii ) );
}
}
break;
case ( DirContext.REMOVE_ATTRIBUTE ):
for ( int ii = 0; ii < alteredObjectClass.size(); ii++ )
{
if ( !AttributeUtils.containsValueCaseIgnore( objectClass, alteredObjectClass.get( ii ) ) )
{
ocMods.remove( alteredObjectClass.get( ii ) );
}
}
break;
case ( DirContext.REPLACE_ATTRIBUTE ):
for ( int ii = 0; ii < alteredObjectClass.size(); ii++ )
{
if ( !AttributeUtils.containsValueCaseIgnore( objectClass, alteredObjectClass.get( ii ) ) )
{
ocMods.add( alteredObjectClass.get( ii ) );
}
}
break;
default:
break;
}
}
}
next.modify( name, modOp, mods );
}
public void modify( NextInterceptor next, LdapDN name, ModificationItemImpl[] mods ) throws NamingException
{
// First, we get the entry from the backend. If it does not exist, then we throw an exception
Attributes entry = nexus.lookup( name );
if ( entry == null )
{
log.error( "No entry with this name :{}", name );
throw new LdapNameNotFoundException( "The entry which name is " + name + " is not found." );
}
// We will use this temporary entry to check that the modifications
// can be applied as atomic operations
Attributes tmpEntry = (Attributes)entry.clone();
Set modset = new HashSet();
ModificationItemImpl objectClassMod = null;
// Check that we don't have two times the same modification.
// This is somehow useless, as modification operations are supposed to
// be atomic, so we may have a sucession of Add, DEL, ADD operations
// for the same attribute, and this will be legal.
// @TODO : check if we can remove this test.
for ( int ii = 0; ii < mods.length; ii++ )
{
if ( mods[ii].getAttribute().getID().equalsIgnoreCase( "objectclass" ) )
{
objectClassMod = mods[ii];
}
// Freak out under some weird cases
if ( mods[ii].getAttribute().size() == 0 )
{
// not ok for add but ok for replace and delete
if ( mods[ii].getModificationOp() == DirContext.ADD_ATTRIBUTE )
{
throw new LdapInvalidAttributeValueException( "No value is not a valid value for an attribute.",
ResultCodeEnum.INVALIDATTRIBUTESYNTAX );
}
}
StringBuffer keybuf = new StringBuffer();
keybuf.append( mods[ii].getModificationOp() );
keybuf.append( mods[ii].getAttribute().getID() );
for ( int jj = 0; jj < mods[ii].getAttribute().size(); jj++ )
{
keybuf.append( mods[ii].getAttribute().get( jj ) );
}
if ( !modset.add( keybuf.toString() ) && mods[ii].getModificationOp() == DirContext.ADD_ATTRIBUTE )
{
throw new LdapAttributeInUseException( "found two copies of the following modification item: "
+ mods[ii] );
}
}
// Get the objectClass attribute.
Attribute objectClass;
if ( objectClassMod == null )
{
objectClass = entry.get( "objectClass" );
if ( objectClass == null )
{
objectClass = new LockableAttributeImpl( "objectClass" );
}
}
else
{
objectClass = getResultantObjectClasses( objectClassMod.getModificationOp(), objectClassMod.getAttribute(),
entry.get( "objectClass" ) );
}
ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
AttributeTypeRegistry atRegistry = this.globalRegistries.getAttributeTypeRegistry();
// -------------------------------------------------------------------
// DIRSERVER-646 Fix: Replacing an unknown attribute with no values
// (deletion) causes an error
// -------------------------------------------------------------------
if ( mods.length == 1 &&
mods[0].getAttribute().size() == 0 &&
mods[0].getModificationOp() == DirContext.REPLACE_ATTRIBUTE &&
! atRegistry.hasAttributeType( mods[0].getAttribute().getID() ) )
{
return;
}
// Now, apply the modifications on the cloned entry before applyong it to the
// real object.
for ( int ii = 0; ii < mods.length; ii++ )
{
int modOp = mods[ii].getModificationOp();
Attribute change = mods[ii].getAttribute();
if ( !atRegistry.hasAttributeType( change.getID() ) && !AttributeUtils.containsValueCaseIgnore( objectClass, "extensibleObject" ) )
{
throw new LdapInvalidAttributeIdentifierException();
}
// We will forbid modification of operationnal attributes which are not
// user modifiable.
AttributeType attributeType = atRegistry.lookup( change.getID() );
if //( ( attributeType.getUsage() == UsageEnum.DIRECTORYOPERATION ) &&
( !attributeType.isCanUserModify() ) //)
{
throw new NoPermissionException( "Cannot modify the attribute '" + change.getID() + "'" );
}
switch ( modOp )
{
case DirContext.ADD_ATTRIBUTE :
Attribute attr = tmpEntry.get( change.getID() );
if ( attr != null )
{
NamingEnumeration values = change.getAll();
while ( values.hasMoreElements() )
{
attr.add( values.nextElement() );
}
}
else
{
attr = new LockableAttributeImpl( change.getID() );
NamingEnumeration values = change.getAll();
while ( values.hasMoreElements() )
{
attr.add( values.nextElement() );
}
tmpEntry.put( attr );
}
break;
case DirContext.REMOVE_ATTRIBUTE :
if ( tmpEntry.get( change.getID() ) == null )
{
log.error( "Trying to remove an inexistant attribute: " + change.getID() );
throw new LdapNoSuchAttributeException();
}
// We may have to remove the attribute or only some values
if ( change.size() == 0 )
{
// No value : we have to remove the entire attribute
// Check that we aren't removing a MUST attribute
if ( isRequired( change.getID(), objectClass ) )
{
log.error( "Trying to remove a required attribute: " + change.getID() );
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECTCLASSVIOLATION );
}
}
else
{
// for required attributes we need to check if all values are removed
// if so then we have a schema violation that must be thrown
if ( isRequired( change.getID(), objectClass ) && isCompleteRemoval( change, entry ) )
{
log.error( "Trying to remove a required attribute: " + change.getID() );
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECTCLASSVIOLATION );
}
// Now remove the attribute and all its values
Attribute modified = tmpEntry.remove( change.getID() );
// And inject back the values except the ones to remove
NamingEnumeration values = change.getAll();
while ( values.hasMoreElements() )
{
modified.remove( values.next() );
}
// ok, done. Last check : if the attribute does not content any more value;
// and if it's a MUST one, we should thow an exception
if ( ( modified.size() == 0 ) && isRequired( change.getID(), objectClass ) )
{
log.error( "Trying to remove a required attribute: " + change.getID() );
throw new LdapSchemaViolationException( ResultCodeEnum.OBJECTCLASSVIOLATION );
}
// Put back the attribute in the entry
tmpEntry.put( modified );
}
SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, change,
this.globalRegistries.getOidRegistry() );
SchemaChecker
.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, change, objectClass );
break;
case DirContext.REPLACE_ATTRIBUTE :
SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, change,
globalRegistries.getOidRegistry() );
SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, change );
attr = tmpEntry.get( change.getID() );
if ( attr != null )
{
tmpEntry.remove( change.getID() );
}
attr = new LockableAttributeImpl( change.getID() );
NamingEnumeration values = change.getAll();
if ( values.hasMoreElements() )
{
while ( values.hasMoreElements() )
{
attr.add( values.nextElement() );
}
tmpEntry.put( attr );
}
break;
}
}
check( name, tmpEntry );
// let's figure out if we need to add or take away from mods to maintain
// the objectClass attribute with it's hierarchy of ancestors
if ( objectClassMod != null )
{
Attribute alteredObjectClass = ( Attribute ) objectClass.clone();
alterObjectClasses( alteredObjectClass );
if ( !alteredObjectClass.equals( objectClass ) )
{
Attribute ocMods = objectClassMod.getAttribute();
switch ( objectClassMod.getModificationOp() )
{
case ( DirContext.ADD_ATTRIBUTE ):
if ( AttributeUtils.containsValueCaseIgnore( ocMods, "top" ) )
{
ocMods.remove( "top" );
}
for ( int ii = 0; ii < alteredObjectClass.size(); ii++ )
{
if ( !AttributeUtils.containsValueCaseIgnore( objectClass, alteredObjectClass.get( ii ) ) )
{
ocMods.add( alteredObjectClass.get( ii ) );
}
}
break;
case ( DirContext.REMOVE_ATTRIBUTE ):
for ( int ii = 0; ii < alteredObjectClass.size(); ii++ )
{
if ( !AttributeUtils.containsValueCaseIgnore( objectClass, alteredObjectClass.get( ii ) ) )
{
ocMods.remove( alteredObjectClass.get( ii ) );
}
}
break;
case ( DirContext.REPLACE_ATTRIBUTE ):
for ( int ii = 0; ii < alteredObjectClass.size(); ii++ )
{
if ( !AttributeUtils.containsValueCaseIgnore( objectClass, alteredObjectClass.get( ii ) ) )
{
ocMods.add( alteredObjectClass.get( ii ) );
}
}
break;
default:
}
}
}
//assertNumberOfAttributeValuesValid( tmpEntry );
next.modify( name, mods );
}
private void filterObjectClass( Attributes entry ) throws NamingException
{
List objectClasses = new ArrayList();
Attribute oc = entry.get( "objectClass" );
if ( oc != null )
{
getObjectClasses( oc, objectClasses );
entry.remove( "objectClass" );
Attribute newOc = new LockableAttributeImpl( "objectClass" );
for ( int i = 0; i < objectClasses.size(); i++ )
{
Object currentOC = objectClasses.get(i);
if ( currentOC instanceof String )
{
newOc.add( currentOC );
}
else
{
newOc.add( ( (ObjectClass)currentOC ).getName() );
}
}
newOc.add( "top" );
entry.put( newOc );
}
}
private void filterBinaryAttributes( Attributes entry ) throws NamingException
{
/*
* start converting values of attributes to byte[]s which are not
* human readable and those that are in the binaries set
*/
NamingEnumeration list = entry.getIDs();
while ( list.hasMore() )
{
String id = ( String ) list.next();
AttributeType type = null;
boolean asBinary = false;
if ( globalRegistries.getAttributeTypeRegistry().hasAttributeType( id ) )
{
type = globalRegistries.getAttributeTypeRegistry().lookup( id );
}
else
{
continue;
}
asBinary = !type.getSyntax().isHumanReadable();
asBinary = asBinary || binaries.contains( type );
if ( asBinary )
{
Attribute attribute = entry.get( id );
Attribute binary = new LockableAttributeImpl( id );
for ( int i = 0; i < attribute.size(); i++ )
{
Object value = attribute.get( i );
if ( value instanceof String )
{
binary.add( i, StringTools.getBytesUtf8( ( String ) value ) );
}
else
{
binary.add( i, value );
}
}
entry.remove( id );
entry.put( binary );
}
}
}
/**
* A special filter over entry attributes which replaces Attribute String values with their respective byte[]
* representations using schema information and the value held in the JNDI environment property:
* <code>java.naming.ldap.attributes.binary</code>.
*
* @see <a href= "http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap-gl.html#binary">
* java.naming.ldap.attributes.binary</a>
*/
private class BinaryAttributeFilter implements SearchResultFilter
{
public boolean accept( Invocation invocation, SearchResult result, SearchControls controls )
throws NamingException
{
filterBinaryAttributes( result.getAttributes() );
return true;
}
}
/**
* Filters objectClass attribute to inject top when not present.
*/
private class TopFilter implements SearchResultFilter
{
public boolean accept( Invocation invocation, SearchResult result, SearchControls controls )
throws NamingException
{
//filterTop( result.getAttributes() );
filterObjectClass( result.getAttributes() );
return true;
}
}
private void check( LdapDN dn, Attributes entry ) throws NamingException
{
NamingEnumeration attrEnum = entry.getIDs();
// ---------------------------------------------------------------
// First, make sure all attributes are valid schema defined attributes
// ---------------------------------------------------------------
while ( attrEnum.hasMoreElements() )
{
String name = ( String ) attrEnum.nextElement();
if ( !globalRegistries.getAttributeTypeRegistry().hasAttributeType( name ) )
{
throw new LdapInvalidAttributeIdentifierException( name + " not found in attribute registry!" );
}
}
// We will check some elements :
// 1) the entry must have all the MUST attributes of all its ObjectClass
// 2) The SingleValued attributes must be SingleValued
// 3) No attributes should be used if they are not part of MUST and MAY
// 3-1) Except if the extensibleObject ObjectClass is used
// 3-2) or if the AttributeType is COLLECTIVE
// 4) We also check that for H-R attributes, we have a valid String in the values
Attribute objectClassAttr = entry.get( "objectClass" );
// Protect the server against a null objectClassAttr
// It can be the case if the user forgot to add it to the entry ...
// In this case, we create an new one, empty
if ( objectClassAttr == null )
{
objectClassAttr = new LockableAttributeImpl( "objectClass" );
}
List ocs = new ArrayList();
alterObjectClasses( objectClassAttr );
Set must = getAllMust( objectClassAttr.getAll() );
Set allowed = getAllAllowed( objectClassAttr.getAll(), must );
boolean hasExtensibleObject = getObjectClasses( objectClassAttr, ocs );
assertRequiredAttributesPresent( dn, entry, must );
assertNumberOfAttributeValuesValid( entry );
if ( !hasExtensibleObject )
{
assertAllAttributesAllowed( dn, entry, allowed );
}
// Check the attributes values and transform them to String if necessary
assertHumanReadable( entry );
}
/**
* Check that all the attributes exist in the schema for this entry.
*/
public void add( NextInterceptor next, LdapDN normName, Attributes attrs ) throws NamingException
{
check( normName, attrs );
next.add( normName, attrs );
}
/**
* Checks to see number of values of an attribute conforms to the schema
*/
private void assertNumberOfAttributeValuesValid( Attributes attributes ) throws InvalidAttributeValueException, NamingException
{
NamingEnumeration list = attributes.getAll();
while ( list.hasMore() )
{
Attribute attribute = ( Attribute ) list.next();
assertNumberOfAttributeValuesValid( attribute );
}
}
/**
* Checks to see numbers of values of attributes conforms to the schema
*/
private void assertNumberOfAttributeValuesValid( Attribute attribute ) throws InvalidAttributeValueException, NamingException
{
AttributeTypeRegistry registry = this.globalRegistries.getAttributeTypeRegistry();
if ( attribute.size() > 1 && registry.lookup( attribute.getID() ).isSingleValue() )
{
throw new LdapInvalidAttributeValueException( "More than one value has been provided " +
"for the single-valued attribute: " + attribute.getID(), ResultCodeEnum.CONSTRAINTVIOLATION );
}
}
/**
* Checks to see the presence of all required attributes within an entry.
*/
private void assertRequiredAttributesPresent( LdapDN dn, Attributes entry, Set must )
throws NamingException
{
NamingEnumeration attributes = entry.getAll();
while ( attributes.hasMoreElements() && ( must.size() > 0 ) )
{
Attribute attribute = ( Attribute ) attributes.nextElement();
String oid = globalRegistries.getOidRegistry().getOid( attribute.getID() );
must.remove( oid );
}
if ( must.size() != 0 )
{
throw new LdapSchemaViolationException( "Required attributes " +
must + " not found within entry " + dn.getUpName(),
ResultCodeEnum.OBJECTCLASSVIOLATION );
}
}
/**
* Check that all the attribute's values which are Human Readable can be transformed
* to valid String if they are stored as byte[].
*/
private void assertHumanReadable( Attributes entry ) throws NamingException
{
NamingEnumeration attributes = entry.getAll();
boolean isEntryModified = false;
Attributes cloneEntry = null;
// First, loop on all attributes
while ( attributes.hasMoreElements() )
{
Attribute attribute = ( Attribute ) attributes.nextElement();
AttributeType attributeType = globalRegistries.getAttributeTypeRegistry().lookup( attribute.getID() );
// If the attributeType is H-R, check alll of its values
if ( attributeType.getSyntax().isHumanReadable() )
{
Enumeration values = attribute.getAll();
Attribute clone = null;
boolean isModified = false;
// Loop on each values
while ( values.hasMoreElements() )
{
Object value = values.nextElement();
if ( value instanceof String )
{
continue;
}
else if ( value instanceof byte[] )
{
// Ve have a byte[] value. It should be a String UTF-8 encoded
// Let's transform it
try
{
String valStr = new String( (byte[])value, "UTF-8" );
if ( !isModified )
{
// Don't create useless clones. We only clone
// if we have at least one value which is a byte[]
isModified = true;
clone = (Attribute)attribute.clone();
}
// Swap the value into the clone
clone.remove( value );
clone.add( valStr );
}
catch ( UnsupportedEncodingException uee )
{
throw new NamingException( "The value is not a valid String" );
}
}
else
{
throw new NamingException( "The value stored in an Human Readable attribute is not a String" );
}
}
// The attribute has been checked. If one of its value has been modified,
// we have to modify the cloned Attributes/
if ( isModified )
{
if ( !isEntryModified )
{
// Again, let's avoid useless cloning. If no attribute is H-R
// or if no H-R value is stored as a byte[], we don't have to create a clone
// of the entry
cloneEntry = (Attributes)entry.clone();
isEntryModified = true;
}
// Swap the attribute into the cloned entry
cloneEntry.remove( attribute.getID() );
cloneEntry.put( clone );
}
}
}
// At the end, we now have to switch the entries, if it has been modified
if ( isEntryModified )
{
attributes = cloneEntry.getAll();
// We llop on all the attributes and modify them in the initial entry.
while ( attributes.hasMoreElements() )
{
Attribute attribute = (Attribute)attributes.nextElement();
entry.remove( attribute.getID() );
entry.put( attribute );
}
}
}
/**
* Checks to see if an attribute is required by as determined from an entry's
* set of objectClass attribute values.
*
* @param attrId the attribute to test if required by a set of objectClass values
* @param objectClass the objectClass values
* @return true if the objectClass values require the attribute, false otherwise
* @throws NamingException if the attribute is not recognized
*/
private void assertAllAttributesAllowed( LdapDN dn, Attributes attributes, Set allowed ) throws NamingException
{
// Never check the attributes if the extensibleObject objectClass is
// declared for this entry
Attribute objectClass = attributes.get( "objectclass" );
if ( AttributeUtils.containsValueCaseIgnore( objectClass, "extensibleobject" ) )
{
return;
}
NamingEnumeration attrs = attributes.getAll();
while ( attrs.hasMoreElements() )
{
Attribute attribute = (Attribute)attrs.nextElement();
String attrId = attribute.getID();
String attrOid = globalRegistries.getOidRegistry().getOid( attrId );
AttributeType attributeType = globalRegistries.getAttributeTypeRegistry().lookup( attrOid );
if ( !attributeType.isCollective() && ( attributeType.getUsage() == UsageEnum.USERAPPLICATIONS ) )
{
if ( !allowed.contains( attrOid ) )
{
throw new LdapSchemaViolationException( "Attribute " +
attribute.getID() + " not declared in objectClasses of entry " + dn.getUpName(),
ResultCodeEnum.OBJECTCLASSVIOLATION );
}
}
}
}
}