| /* |
| * 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.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.naming.directory.SearchControls; |
| |
| import org.apache.directory.server.constants.ServerDNConstants; |
| import org.apache.directory.server.core.DirectoryService; |
| import org.apache.directory.server.core.entry.ClonedServerEntry; |
| import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor; |
| import org.apache.directory.server.core.filtering.EntryFilter; |
| import org.apache.directory.server.core.filtering.EntryFilteringCursor; |
| import org.apache.directory.server.core.interceptor.BaseInterceptor; |
| import org.apache.directory.server.core.interceptor.NextInterceptor; |
| import org.apache.directory.server.core.interceptor.context.AddOperationContext; |
| import org.apache.directory.server.core.interceptor.context.CompareOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ListOperationContext; |
| import org.apache.directory.server.core.interceptor.context.LookupOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.core.interceptor.context.RenameOperationContext; |
| import org.apache.directory.server.core.interceptor.context.SearchOperationContext; |
| import org.apache.directory.server.core.interceptor.context.SearchingOperationContext; |
| import org.apache.directory.server.core.partition.PartitionNexus; |
| import org.apache.directory.server.i18n.I18n; |
| import org.apache.directory.shared.ldap.model.message.controls.Cascade; |
| import org.apache.directory.shared.ldap.model.constants.MetaSchemaConstants; |
| import org.apache.directory.shared.ldap.model.constants.SchemaConstants; |
| import org.apache.directory.shared.ldap.model.cursor.EmptyCursor; |
| import org.apache.directory.shared.ldap.model.cursor.SingletonCursor; |
| import org.apache.directory.shared.ldap.model.entry.BinaryValue; |
| import org.apache.directory.shared.ldap.model.entry.DefaultEntryAttribute; |
| import org.apache.directory.shared.ldap.model.entry.DefaultModification; |
| import org.apache.directory.shared.ldap.model.entry.Entry; |
| import org.apache.directory.shared.ldap.model.entry.EntryAttribute; |
| import org.apache.directory.shared.ldap.model.entry.Modification; |
| import org.apache.directory.shared.ldap.model.entry.StringValue; |
| import org.apache.directory.shared.ldap.model.entry.Value; |
| import org.apache.directory.shared.ldap.model.exception.LdapAttributeInUseException; |
| import org.apache.directory.shared.ldap.model.exception.LdapException; |
| import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeTypeException; |
| import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException; |
| import org.apache.directory.shared.ldap.model.exception.LdapNoPermissionException; |
| import org.apache.directory.shared.ldap.model.exception.LdapNoSuchAttributeException; |
| import org.apache.directory.shared.ldap.model.exception.LdapSchemaViolationException; |
| import org.apache.directory.shared.ldap.model.filter.ApproximateNode; |
| import org.apache.directory.shared.ldap.model.filter.AssertionNode; |
| import org.apache.directory.shared.ldap.model.filter.BranchNode; |
| import org.apache.directory.shared.ldap.model.filter.EqualityNode; |
| import org.apache.directory.shared.ldap.model.filter.ExprNode; |
| import org.apache.directory.shared.ldap.model.filter.ExtensibleNode; |
| import org.apache.directory.shared.ldap.model.filter.GreaterEqNode; |
| import org.apache.directory.shared.ldap.model.filter.LessEqNode; |
| import org.apache.directory.shared.ldap.model.filter.PresenceNode; |
| import org.apache.directory.shared.ldap.model.filter.ScopeNode; |
| import org.apache.directory.shared.ldap.model.filter.SimpleNode; |
| import org.apache.directory.shared.ldap.model.filter.SubstringNode; |
| import org.apache.directory.shared.ldap.model.message.ResultCodeEnum; |
| import org.apache.directory.shared.ldap.model.name.Ava; |
| import org.apache.directory.shared.ldap.model.name.Dn; |
| import org.apache.directory.shared.ldap.model.name.Rdn; |
| import org.apache.directory.shared.ldap.model.schema.MutableAttributeTypeImpl; |
| import org.apache.directory.shared.ldap.model.schema.AttributeTypeOptions; |
| import org.apache.directory.shared.ldap.model.schema.ObjectClass; |
| import org.apache.directory.shared.ldap.model.schema.ObjectClassTypeEnum; |
| import org.apache.directory.shared.ldap.model.schema.SyntaxChecker; |
| import org.apache.directory.shared.ldap.model.schema.UsageEnum; |
| import org.apache.directory.shared.ldap.model.schema.registries.Schema; |
| import org.apache.directory.shared.ldap.model.schema.registries.SchemaLoader; |
| import org.apache.directory.shared.ldap.model.schema.syntaxCheckers.OctetStringSyntaxChecker; |
| 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> |
| */ |
| public class SchemaInterceptor extends BaseInterceptor |
| { |
| /** The LoggerFactory used by this Interceptor */ |
| private static Logger LOG = LoggerFactory.getLogger( SchemaInterceptor.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<EntryFilter> filters = new ArrayList<EntryFilter>(); |
| |
| /** A normalized form for the SubschemaSubentry Dn */ |
| private String subschemaSubentryDnNorm; |
| |
| /** The SubschemaSubentry Dn */ |
| private Dn subschemaSubentryDn; |
| |
| /** |
| * the normalized name for the schema modification attributes |
| */ |
| private Dn schemaModificationAttributesDn; |
| |
| /** The schema manager */ |
| private SchemaSubentryManager schemaSubEntryManager; |
| |
| private SchemaService schemaService; |
| |
| /** the base Dn (normalized) of the schema partition */ |
| private Dn schemaBaseDn; |
| |
| /** A map used to store all the objectClasses superiors */ |
| private Map<String, List<ObjectClass>> superiors; |
| |
| /** A map used to store all the objectClasses may attributes */ |
| private Map<String, List<MutableAttributeTypeImpl>> allMay; |
| |
| /** A map used to store all the objectClasses must */ |
| private Map<String, List<MutableAttributeTypeImpl>> allMust; |
| |
| /** A map used to store all the objectClasses allowed attributes (may + must) */ |
| private Map<String, List<MutableAttributeTypeImpl>> allowed; |
| |
| |
| /** |
| * Initialize the Schema Service |
| * |
| * @param directoryService the directory service core |
| * @throws Exception if there are problems during initialization |
| */ |
| public void init( DirectoryService directoryService ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Initializing SchemaInterceptor..." ); |
| } |
| |
| super.init( directoryService ); |
| |
| nexus = directoryService.getPartitionNexus(); |
| binaryAttributeFilter = new BinaryAttributeFilter(); |
| topFilter = new TopFilter(); |
| filters.add( binaryAttributeFilter ); |
| filters.add( topFilter ); |
| |
| schemaBaseDn = directoryService.getDnFactory().create( SchemaConstants.OU_SCHEMA ); |
| schemaService = directoryService.getSchemaService(); |
| |
| // stuff for dealing with subentries (garbage for now) |
| Value<?> subschemaSubentry = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get(); |
| subschemaSubentryDn = directoryService.getDnFactory().create( subschemaSubentry.getString() ); |
| subschemaSubentryDn.normalize( schemaManager ); |
| subschemaSubentryDnNorm = subschemaSubentryDn.getNormName(); |
| |
| schemaModificationAttributesDn = directoryService.getDnFactory().create( ServerDNConstants.SCHEMA_MODIFICATIONS_DN ); |
| schemaModificationAttributesDn.normalize( schemaManager ); |
| |
| computeSuperiors(); |
| |
| // Initialize the schema manager |
| SchemaLoader loader = schemaService.getSchemaPartition().getSchemaManager().getLoader(); |
| schemaSubEntryManager = new SchemaSubentryManager( schemaManager, loader, directoryService.getDnFactory() ); |
| |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "SchemaInterceptor Initialized !" ); |
| } |
| } |
| |
| |
| /** |
| * Compute the MUST attributes for an objectClass. This method gather all the |
| * MUST from all the objectClass and its superors. |
| * |
| * @param atSeen ??? |
| * @param objectClass the object class to gather MUST attributes for |
| * @throws Exception if there are problems resolving schema entitites |
| */ |
| private void computeMustAttributes( ObjectClass objectClass, Set<String> atSeen ) throws LdapException |
| { |
| List<ObjectClass> parents = superiors.get( objectClass.getOid() ); |
| |
| List<MutableAttributeTypeImpl> mustList = new ArrayList<MutableAttributeTypeImpl>(); |
| List<MutableAttributeTypeImpl> allowedList = new ArrayList<MutableAttributeTypeImpl>(); |
| Set<String> mustSeen = new HashSet<String>(); |
| |
| allMust.put( objectClass.getOid(), mustList ); |
| allowed.put( objectClass.getOid(), allowedList ); |
| |
| for ( ObjectClass parent : parents ) |
| { |
| List<MutableAttributeTypeImpl> mustParent = parent.getMustAttributeTypes(); |
| |
| if ( ( mustParent != null ) && ( mustParent.size() != 0 ) ) |
| { |
| for ( MutableAttributeTypeImpl attributeType : mustParent ) |
| { |
| 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 |
| * |
| * @param atSeen ??? |
| * @param objectClass the object class to get all the MAY attributes for |
| * @throws Exception with problems accessing registries |
| */ |
| private void computeMayAttributes( ObjectClass objectClass, Set<String> atSeen ) throws LdapException |
| { |
| List<ObjectClass> parents = superiors.get( objectClass.getOid() ); |
| |
| List<MutableAttributeTypeImpl> mayList = new ArrayList<MutableAttributeTypeImpl>(); |
| Set<String> maySeen = new HashSet<String>(); |
| List<MutableAttributeTypeImpl> allowedList = allowed.get( objectClass.getOid() ); |
| |
| allMay.put( objectClass.getOid(), mayList ); |
| |
| for ( ObjectClass parent : parents ) |
| { |
| List<MutableAttributeTypeImpl> mustParent = parent.getMustAttributeTypes(); |
| |
| if ( ( mustParent != null ) && ( mustParent.size() != 0 ) ) |
| { |
| for ( MutableAttributeTypeImpl attributeType : mustParent ) |
| { |
| 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<ObjectClass> superiors, Set<String> ocSeen ) |
| throws LdapException |
| { |
| List<ObjectClass> parents = objectClass.getSuperiors(); |
| |
| // Loop on all the objectClass superiors |
| if ( ( parents != null ) && ( parents.size() != 0 ) ) |
| { |
| for ( ObjectClass parent : parents ) |
| { |
| // Top is not added |
| if ( SchemaConstants.TOP_OC.equals( parent.getName() ) ) |
| { |
| continue; |
| } |
| |
| // For each one, recurse |
| computeOCSuperiors( parent, superiors, ocSeen ); |
| |
| String oid = parent.getOid(); |
| |
| if ( !ocSeen.contains( oid ) ) |
| { |
| superiors.add( parent ); |
| ocSeen.add( oid ); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Compute the superiors and MUST/MAY attributes for a specific |
| * ObjectClass |
| */ |
| private void computeSuperior( ObjectClass objectClass ) throws LdapException |
| { |
| List<ObjectClass> ocSuperiors = new ArrayList<ObjectClass>(); |
| |
| superiors.put( objectClass.getOid(), ocSuperiors ); |
| |
| computeOCSuperiors( objectClass, ocSuperiors, new HashSet<String>() ); |
| |
| Set<String> atSeen = new HashSet<String>(); |
| computeMustAttributes( objectClass, atSeen ); |
| computeMayAttributes( objectClass, atSeen ); |
| |
| superiors.put( objectClass.getName(), ocSuperiors ); |
| } |
| |
| |
| /** |
| * Compute all ObjectClasses superiors, MAY and MUST attributes. |
| * @throws Exception |
| */ |
| private void computeSuperiors() throws LdapException |
| { |
| Iterator<ObjectClass> objectClasses = schemaManager.getObjectClassRegistry().iterator(); |
| superiors = new ConcurrentHashMap<String, List<ObjectClass>>(); |
| allMust = new ConcurrentHashMap<String, List<MutableAttributeTypeImpl>>(); |
| allMay = new ConcurrentHashMap<String, List<MutableAttributeTypeImpl>>(); |
| allowed = new ConcurrentHashMap<String, List<MutableAttributeTypeImpl>>(); |
| |
| while ( objectClasses.hasNext() ) |
| { |
| ObjectClass objectClass = objectClasses.next(); |
| computeSuperior( objectClass ); |
| } |
| } |
| |
| |
| public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext listContext ) |
| throws LdapException |
| { |
| EntryFilteringCursor cursor = nextInterceptor.list( listContext ); |
| cursor.addEntryFilter( binaryAttributeFilter ); |
| return cursor; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean compare( NextInterceptor next, CompareOperationContext compareContext ) throws LdapException |
| { |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "Operation Context: {}", compareContext ); |
| } |
| |
| // Check that the requested AT exists |
| // complain if we do not recognize the attribute being compared |
| if ( !schemaManager.getAttributeTypeRegistry().contains( compareContext.getOid() ) ) |
| { |
| throw new LdapInvalidAttributeTypeException( I18n.err( I18n.ERR_266, compareContext.getOid() ) ); |
| } |
| |
| boolean result = next.compare( compareContext ); |
| |
| return result; |
| } |
| |
| |
| /** |
| * 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 |
| */ |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| @SuppressWarnings("PMD.EmptyCatchBlock") |
| private void filterAttributesToReturn( SearchControls searchCtls ) |
| { |
| String[] attributes = searchCtls.getReturningAttributes(); |
| |
| if ( ( attributes == null ) || ( attributes.length == 0 ) ) |
| { |
| // We have no attributes, that means "*" (all users attributes) |
| searchCtls.setReturningAttributes( SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY ); |
| return; |
| } |
| |
| Map<String, String> filteredAttrs = new HashMap<String, String>(); |
| boolean hasNoAttribute = false; |
| boolean hasAttributes = false; |
| |
| for ( String attribute : attributes ) |
| { |
| // Skip special attributes |
| if ( ( SchemaConstants.ALL_USER_ATTRIBUTES.equals( attribute ) ) |
| || ( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES.equals( attribute ) ) |
| || ( SchemaConstants.NO_ATTRIBUTE.equals( attribute ) ) ) |
| { |
| if ( !filteredAttrs.containsKey( attribute ) ) |
| { |
| filteredAttrs.put( attribute, attribute ); |
| } |
| |
| if ( SchemaConstants.NO_ATTRIBUTE.equals( attribute ) ) |
| { |
| hasNoAttribute = true; |
| } |
| else |
| { |
| hasAttributes = true; |
| } |
| |
| continue; |
| } |
| |
| try |
| { |
| // Check that the attribute is declared |
| if ( schemaManager.getAttributeTypeRegistry().contains( attribute ) ) |
| { |
| String oid = schemaManager.getAttributeTypeRegistry().getOidByName( attribute ); |
| |
| // The attribute must be an AttributeType |
| if ( schemaManager.getAttributeTypeRegistry().contains( oid ) && !filteredAttrs.containsKey( oid ) ) |
| { |
| // Ok, we can add the attribute to the list of filtered attributes |
| filteredAttrs.put( oid, attribute ); |
| } |
| } |
| |
| hasAttributes = true; |
| } |
| catch ( Exception ne ) |
| { |
| /* Do nothing, the attribute does not exist */ |
| } |
| } |
| |
| // Treat a special case : if we have an attribute and "1.1", then discard "1.1" |
| if ( hasAttributes && hasNoAttribute ) |
| { |
| filteredAttrs.remove( SchemaConstants.NO_ATTRIBUTE ); |
| } |
| |
| // If we still have the same attribute number, then we can just get out the method |
| if ( filteredAttrs.size() == attributes.length ) |
| { |
| return; |
| } |
| |
| // Deal with the special case where the attribute list is now empty |
| if ( filteredAttrs.size() == 0 ) |
| { |
| // We just have to pass the special 1.1 attribute, |
| // as we don't want to return any attribute |
| searchCtls.setReturningAttributes( SchemaConstants.NO_ATTRIBUTE_ARRAY ); |
| return; |
| } |
| |
| // Some attributes have been removed. let's modify the searchControl |
| String[] newAttributesList = new String[filteredAttrs.size()]; |
| |
| int pos = 0; |
| |
| for ( String key : filteredAttrs.keySet() ) |
| { |
| newAttributesList[pos++] = filteredAttrs.get( key ); |
| } |
| |
| searchCtls.setReturningAttributes( newAttributesList ); |
| } |
| |
| |
| private Value<?> convert( MutableAttributeTypeImpl attributeType, Value<?> value ) throws LdapException |
| { |
| if ( attributeType.getSyntax().isHumanReadable() ) |
| { |
| if ( value instanceof BinaryValue ) |
| { |
| try |
| { |
| return new StringValue( attributeType, new String( (( BinaryValue ) value).getBytes(), "UTF-8" ) ); |
| } |
| catch ( UnsupportedEncodingException uee ) |
| { |
| String message = I18n.err( I18n.ERR_47 ); |
| LOG.error( message ); |
| throw new LdapException( message ); |
| } |
| } |
| } |
| else |
| { |
| if ( value instanceof StringValue ) |
| { |
| return new BinaryValue( attributeType, ( ( StringValue ) value ).getBytes() ); |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| /** |
| * Check that the filter values are compatible with the AttributeType. Typically, |
| * a HumanReadible filter should have a String value. The substring filter should |
| * not be used with binary attributes. |
| */ |
| private void checkFilter( ExprNode filter ) throws LdapException |
| { |
| if ( filter == null ) |
| { |
| String message = I18n.err( I18n.ERR_49 ); |
| LOG.error( message ); |
| throw new LdapException( message ); |
| } |
| |
| if ( filter.isLeaf() ) |
| { |
| if ( filter instanceof EqualityNode) |
| { |
| EqualityNode node = ( ( EqualityNode ) filter ); |
| Value<?> value = node.getValue(); |
| |
| Value<?> newValue = convert( node.getAttributeType(), value ); |
| |
| if ( newValue != null ) |
| { |
| node.setValue( newValue ); |
| } |
| } |
| else if ( ( filter instanceof SubstringNode ) || |
| ( filter instanceof PresenceNode ) || |
| ( filter instanceof AssertionNode ) || |
| ( filter instanceof ScopeNode ) ) |
| { |
| // Nothing to do |
| } |
| else if ( filter instanceof GreaterEqNode ) |
| { |
| GreaterEqNode node = ( (GreaterEqNode) filter ); |
| Value<?> value = node.getValue(); |
| |
| Value<?> newValue = convert( node.getAttributeType(), value ); |
| |
| if ( newValue != null ) |
| { |
| node.setValue( newValue ); |
| } |
| |
| } |
| else if ( filter instanceof LessEqNode ) |
| { |
| LessEqNode node = ( ( LessEqNode ) filter ); |
| Value<?> value = node.getValue(); |
| |
| Value<?> newValue = convert( node.getAttributeType(), value ); |
| |
| if ( newValue != null ) |
| { |
| node.setValue( newValue ); |
| } |
| } |
| else if ( filter instanceof ExtensibleNode) |
| { |
| ExtensibleNode node = ( ( ExtensibleNode ) filter ); |
| } |
| else if ( filter instanceof ApproximateNode ) |
| { |
| ApproximateNode node = ( (ApproximateNode) filter ); |
| Value<?> value = node.getValue(); |
| |
| Value<?> newValue = convert( node.getAttributeType(), value ); |
| |
| if ( newValue != null ) |
| { |
| node.setValue( newValue ); |
| } |
| } |
| } |
| else |
| { |
| // Recursively iterate through all the children. |
| for ( ExprNode child : ( ( BranchNode ) filter ).getChildren() ) |
| { |
| checkFilter( child ); |
| } |
| } |
| } |
| |
| |
| public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext searchContext ) |
| throws LdapException |
| { |
| Dn base = searchContext.getDn(); |
| SearchControls searchCtls = searchContext.getSearchControls(); |
| ExprNode filter = searchContext.getFilter(); |
| |
| // 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 |
| if ( searchCtls.getReturningAttributes() != null ) |
| { |
| filterAttributesToReturn( searchCtls ); |
| } |
| |
| // We also have to check the H/R flag for the filter attributes |
| checkFilter( filter ); |
| |
| String baseNormForm = ( base.isNormalized() ? base.getNormName() : base.getNormName() ); |
| |
| // Deal with the normal case : searching for a normal value (not subSchemaSubEntry) |
| if ( !subschemaSubentryDnNorm.equals( baseNormForm ) ) |
| { |
| EntryFilteringCursor cursor = nextInterceptor.search( searchContext ); |
| |
| if ( searchCtls.getReturningAttributes() != null ) |
| { |
| cursor.addEntryFilter( topFilter ); |
| return cursor; |
| } |
| |
| for ( EntryFilter ef : filters ) |
| { |
| cursor.addEntryFilter( ef ); |
| } |
| |
| return cursor; |
| } |
| |
| // The user was searching into the subSchemaSubEntry |
| // This 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; |
| |
| objectClass = node.getValue().getString(); |
| |
| String objectClassOid = null; |
| |
| if ( schemaManager.getObjectClassRegistry().contains( objectClass ) ) |
| { |
| objectClassOid = schemaManager.getObjectClassRegistry().lookup( objectClass ).getOid(); |
| } |
| else |
| { |
| return new BaseEntryFilteringCursor( new EmptyCursor<Entry>(), searchContext ); |
| } |
| |
| MutableAttributeTypeImpl nodeAt = node.getAttributeType(); |
| |
| // see if node attribute is objectClass |
| if ( nodeAt.equals( OBJECT_CLASS_AT ) |
| && ( objectClassOid.equals( SchemaConstants.TOP_OC_OID ) || objectClassOid |
| .equals( SchemaConstants.SUBSCHEMA_OC_OID ) ) && ( node instanceof EqualityNode ) ) |
| { |
| // call.setBypass( true ); |
| Entry serverEntry = schemaService.getSubschemaEntry( searchCtls.getReturningAttributes() ); |
| serverEntry.setDn( base ); |
| return new BaseEntryFilteringCursor( new SingletonCursor<Entry>( serverEntry ), searchContext ); |
| } |
| else |
| { |
| return new BaseEntryFilteringCursor( new EmptyCursor<Entry>(), searchContext ); |
| } |
| } |
| else if ( filter instanceof PresenceNode ) |
| { |
| PresenceNode node = ( PresenceNode ) filter; |
| |
| // see if node attribute is objectClass |
| if ( node.getAttributeType().equals( OBJECT_CLASS_AT ) ) |
| { |
| // call.setBypass( true ); |
| Entry serverEntry = schemaService.getSubschemaEntry( searchCtls.getReturningAttributes() ); |
| serverEntry.setDn( base ); |
| EntryFilteringCursor cursor = new BaseEntryFilteringCursor( |
| new SingletonCursor<Entry>( serverEntry ), searchContext ); |
| return cursor; |
| } |
| } |
| } |
| |
| // In any case not handled previously, just return an empty result |
| return new BaseEntryFilteringCursor( new EmptyCursor<Entry>(), searchContext ); |
| } |
| |
| |
| /** |
| * Search for an entry, using its Dn. Binary attributes and ObjectClass attribute are removed. |
| */ |
| public Entry lookup( NextInterceptor nextInterceptor, LookupOperationContext lookupContext ) throws LdapException |
| { |
| Entry result = nextInterceptor.lookup( lookupContext ); |
| |
| filterBinaryAttributes( result ); |
| |
| return result; |
| } |
| |
| |
| private void getSuperiors( ObjectClass oc, Set<String> ocSeen, List<ObjectClass> result ) throws LdapException |
| { |
| for ( ObjectClass parent : oc.getSuperiors() ) |
| { |
| // Skip 'top' |
| if ( SchemaConstants.TOP_OC.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( EntryAttribute objectClasses, List<ObjectClass> result ) throws LdapException |
| { |
| Set<String> ocSeen = new HashSet<String>(); |
| |
| // We must select all the ObjectClasses, except 'top', |
| // but including all the inherited ObjectClasses |
| boolean hasExtensibleObject = false; |
| |
| for ( Value<?> objectClass : objectClasses ) |
| { |
| String objectClassName = objectClass.getString(); |
| |
| if ( SchemaConstants.TOP_OC.equals( objectClassName ) ) |
| { |
| continue; |
| } |
| |
| if ( SchemaConstants.EXTENSIBLE_OBJECT_OC.equalsIgnoreCase( objectClassName ) ) |
| { |
| hasExtensibleObject = true; |
| } |
| |
| ObjectClass oc = schemaManager.getObjectClassRegistry().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<String> getAllMust( EntryAttribute objectClasses ) throws LdapException |
| { |
| Set<String> must = new HashSet<String>(); |
| |
| // Loop on all objectclasses |
| for ( Value<?> value : objectClasses ) |
| { |
| String ocName = value.getString(); |
| ObjectClass oc = schemaManager.getObjectClassRegistry().lookup( ocName ); |
| |
| List<MutableAttributeTypeImpl> types = oc.getMustAttributeTypes(); |
| |
| // For each objectClass, loop on all MUST attributeTypes, if any |
| if ( ( types != null ) && ( types.size() > 0 ) ) |
| { |
| for ( MutableAttributeTypeImpl type : types ) |
| { |
| must.add( type.getOid() ); |
| } |
| } |
| } |
| |
| return must; |
| } |
| |
| |
| private Set<String> getAllAllowed( EntryAttribute objectClasses, Set<String> must ) throws LdapException |
| { |
| Set<String> allowed = new HashSet<String>( must ); |
| |
| // Add the 'ObjectClass' attribute ID |
| allowed.add( SchemaConstants.OBJECT_CLASS_AT_OID ); |
| |
| // Loop on all objectclasses |
| for ( Value<?> objectClass : objectClasses ) |
| { |
| String ocName = objectClass.getString(); |
| ObjectClass oc = schemaManager.getObjectClassRegistry().lookup( ocName ); |
| |
| List<MutableAttributeTypeImpl> types = oc.getMayAttributeTypes(); |
| |
| // For each objectClass, loop on all MAY attributeTypes, if any |
| if ( ( types != null ) && ( types.size() > 0 ) ) |
| { |
| for ( MutableAttributeTypeImpl type : types ) |
| { |
| String oid = type.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 Exception if there are problems |
| */ |
| private void alterObjectClasses( EntryAttribute objectClassAttr ) throws LdapException |
| { |
| Set<String> objectClasses = new HashSet<String>(); |
| Set<String> objectClassesUP = new HashSet<String>(); |
| |
| // Init the objectClass list with 'top' |
| objectClasses.add( SchemaConstants.TOP_OC ); |
| objectClassesUP.add( SchemaConstants.TOP_OC ); |
| |
| // Construct the new list of ObjectClasses |
| for ( Value<?> ocValue : objectClassAttr ) |
| { |
| String ocName = ocValue.getString(); |
| |
| if ( !ocName.equalsIgnoreCase( SchemaConstants.TOP_OC ) ) |
| { |
| String ocLowerName = ocName.toLowerCase(); |
| |
| ObjectClass objectClass = schemaManager.getObjectClassRegistry().lookup( ocLowerName ); |
| |
| if ( !objectClasses.contains( ocLowerName ) ) |
| { |
| objectClasses.add( ocLowerName ); |
| objectClassesUP.add( ocName ); |
| } |
| |
| List<ObjectClass> ocSuperiors = superiors.get( objectClass.getOid() ); |
| |
| if ( ocSuperiors != null ) |
| { |
| for ( ObjectClass oc : ocSuperiors ) |
| { |
| if ( !objectClasses.contains( oc.getName().toLowerCase() ) ) |
| { |
| objectClasses.add( oc.getName() ); |
| objectClassesUP.add( oc.getName() ); |
| } |
| } |
| } |
| } |
| } |
| |
| // Now, reset the ObjectClass attribute and put the new list into it |
| objectClassAttr.clear(); |
| |
| for ( String attribute : objectClassesUP ) |
| { |
| objectClassAttr.add( attribute ); |
| } |
| } |
| |
| |
| public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws LdapException |
| { |
| Dn oldDn = renameContext.getDn(); |
| Rdn newRdn = renameContext.getNewRdn(); |
| boolean deleteOldRn = renameContext.getDeleteOldRdn(); |
| Entry entry = renameContext.getEntry().getClonedEntry(); |
| |
| /* |
| * Note: This is only a consistency checks, to the ensure that all |
| * mandatory attributes are available after deleting the old Rdn. |
| * The real modification is done in the XdbmStore class. |
| * - TODO: this check is missing in the moveAndRename() method |
| */ |
| if ( deleteOldRn ) |
| { |
| Rdn oldRdn = oldDn.getRdn(); |
| |
| // Delete the old Rdn means we remove some attributes and values. |
| // We must make sure that after this operation all must attributes |
| // are still present in the entry. |
| for ( Ava atav : oldRdn) |
| { |
| MutableAttributeTypeImpl type = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() ); |
| entry.remove( type, atav.getUpValue() ); |
| } |
| |
| // Check that no operational attributes are removed |
| for ( Ava atav : oldRdn) |
| { |
| MutableAttributeTypeImpl attributeType = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() ); |
| |
| if ( !attributeType.isUserModifiable() ) |
| { |
| throw new LdapNoPermissionException( "Cannot modify the attribute '" + atav.getUpType() + "'" ); |
| } |
| } |
| } |
| |
| for ( Ava atav : newRdn ) |
| { |
| MutableAttributeTypeImpl type = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() ); |
| |
| if ( !entry.contains( type, atav.getNormValue() ) ) |
| { |
| entry.add( new DefaultEntryAttribute( type, atav.getUpValue() ) ); |
| } |
| } |
| |
| // Substitute the Rdn and check if the new entry is correct |
| entry.setDn( renameContext.getNewDn() ); |
| |
| check( renameContext.getNewDn(), entry ); |
| |
| next.rename( renameContext ); |
| } |
| |
| |
| /** |
| * Create a new attribute using the given values |
| */ |
| private EntryAttribute createNewAttribute( EntryAttribute attribute ) |
| { |
| MutableAttributeTypeImpl attributeType = attribute.getAttributeType(); |
| |
| // Create the new Attribute |
| EntryAttribute newAttribute = new DefaultEntryAttribute( attribute.getUpId(), attributeType ); |
| |
| for ( Value<?> value : attribute ) |
| { |
| newAttribute.add( value ); |
| } |
| |
| return newAttribute; |
| } |
| |
| |
| /** |
| * Modify an entry, applying the given modifications, and check if it's OK |
| */ |
| private void checkModifyEntry( Dn dn, Entry currentEntry, List<Modification> mods ) throws LdapException |
| { |
| // The first step is to check that the modifications are valid : |
| // - the ATs are present in the schema |
| // - The value is syntaxically correct |
| // |
| // While doing that, we will apply the modification to a copy of the current entry |
| Entry tempEntry = currentEntry.clone(); |
| |
| // Now, apply each mod one by one |
| for ( Modification mod : mods ) |
| { |
| EntryAttribute attribute = mod.getAttribute(); |
| MutableAttributeTypeImpl attributeType = attribute.getAttributeType(); |
| |
| // We don't allow modification of operational attributes |
| if ( !attributeType.isUserModifiable() |
| && ( !attributeType.equals( MODIFIERS_NAME_AT ) |
| && ( !attributeType.equals( MODIFY_TIMESTAMP_AT ) ) |
| && ( !attributeType.equals( ENTRY_CSN_AT ) ) |
| && ( !PWD_POLICY_STATE_ATTRIBUTE_TYPES.contains( attributeType ) ) ) ) |
| { |
| String msg = I18n.err( I18n.ERR_52, attributeType ); |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| switch ( mod.getOperation() ) |
| { |
| case ADD_ATTRIBUTE: |
| // Check the syntax here |
| if ( !attribute.isValid() ) |
| { |
| // The value syntax is incorrect : this is an error |
| String msg = I18n.err( I18n.ERR_53, attributeType ); |
| LOG.error( msg ); |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, msg ); |
| } |
| |
| EntryAttribute currentAttribute = tempEntry.get( attributeType ); |
| |
| // First check if the added Attribute is already present in the entry |
| // If not, we have to create the entry |
| if ( currentAttribute != null ) |
| { |
| for ( Value<?> value : attribute ) |
| { |
| // At this point, we know that the attribute's syntax is correct |
| // We just have to check that the current attribute does not |
| // contains the value already |
| if ( currentAttribute.contains( value ) ) |
| { |
| // This is an error. |
| String msg = I18n.err( I18n.ERR_54, value ); |
| LOG.error( msg ); |
| throw new LdapAttributeInUseException( msg ); |
| } |
| |
| currentAttribute.add( value ); |
| } |
| } |
| else |
| { |
| // We don't check if the attribute is not in the MUST or MAY at this |
| // point, as one of the following modification can change the |
| // ObjectClasses. |
| EntryAttribute newAttribute = createNewAttribute( attribute ); |
| |
| tempEntry.put( newAttribute ); |
| } |
| |
| break; |
| |
| case REMOVE_ATTRIBUTE: |
| // First check that the removed attribute exists |
| if ( !tempEntry.containsAttribute( attributeType ) ) |
| { |
| String msg = I18n.err( I18n.ERR_55, attributeType ); |
| LOG.error( msg ); |
| throw new LdapNoSuchAttributeException( msg ); |
| } |
| |
| // We may have to remove the attribute or only some values |
| if ( attribute.size() == 0 ) |
| { |
| // No value : we have to remove the entire attribute |
| tempEntry.removeAttributes( attributeType ); |
| } |
| else |
| { |
| currentAttribute = tempEntry.get( attributeType ); |
| |
| // Now remove all the values |
| for ( Value<?> value : attribute ) |
| { |
| // We can only remove existing values. |
| if ( currentAttribute.contains( value ) ) |
| { |
| currentAttribute.remove( value ); |
| } |
| else |
| { |
| String msg = I18n.err( I18n.ERR_56, attributeType ); |
| LOG.error( msg ); |
| throw new LdapNoSuchAttributeException( msg ); |
| } |
| } |
| |
| // If the current attribute is empty, we have to remove |
| // it from the entry |
| if ( currentAttribute.size() == 0 ) |
| { |
| tempEntry.removeAttributes( attributeType ); |
| } |
| } |
| |
| break; |
| |
| case REPLACE_ATTRIBUTE: |
| // The replaced attribute might not exist, it will then be a Add |
| // If there is no value, then the attribute will be removed |
| if ( !tempEntry.containsAttribute( attributeType ) ) |
| { |
| if ( attribute.size() == 0 ) |
| { |
| // Ignore the modification, as the attributeType does not |
| // exists in the entry |
| break; |
| } |
| else |
| { |
| // Create the new Attribute |
| EntryAttribute newAttribute = createNewAttribute( attribute ); |
| |
| tempEntry.put( newAttribute ); |
| } |
| } |
| else |
| { |
| if ( attribute.size() == 0 ) |
| { |
| // Remove the attribute from the entry |
| tempEntry.removeAttributes( attributeType ); |
| } |
| else |
| { |
| // Replace the existing values with the new values |
| // This is done by removing the Attribute |
| tempEntry.removeAttributes( attributeType ); |
| |
| // Create the new Attribute |
| EntryAttribute newAttribute = createNewAttribute( attribute ); |
| |
| tempEntry.put( newAttribute ); |
| } |
| } |
| |
| break; |
| } |
| } |
| |
| // Ok, we have created the modified entry. We now have to check that it's a valid |
| // entry wrt the schema. |
| // We have to check that : |
| // - the rdn values are present in the entry |
| // - the objectClasses inheritence is correct |
| // - all the MUST are present |
| // - all the attribute are in MUST and MAY, except fo the extensibleObeject OC |
| // is present |
| // - We haven't removed a part of the Rdn |
| check( dn, tempEntry ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void modify( NextInterceptor next, ModifyOperationContext modifyContext ) throws LdapException |
| { |
| // A modification on a simple entry will be done in three steps : |
| // - get the original entry (it should already been in the context) |
| // - apply the modification on it |
| // - check that the entry is still correct |
| // - add the operational attributes (modifiersName/modifyTimeStamp) |
| // - store the modified entry on the backend. |
| // |
| // A modification done on the schema is a bit different, as there is two more |
| // steps |
| // - We have to update the registries |
| // - We have to modify the ou=schemaModifications entry |
| // |
| |
| // First, check that the entry is either a subschemaSubentry or a schema element. |
| // This is the case if it's a child of cn=schema or ou=schema |
| Dn dn = modifyContext.getDn(); |
| |
| // Gets the stored entry on which the modification must be applied |
| if ( dn.equals( subschemaSubentryDn ) ) |
| { |
| LOG.debug( "Modification attempt on schema subentry {}: \n{}", dn, modifyContext ); |
| |
| // We can get rid of the modifiersName and modifyTimestamp, they are useless. |
| List<Modification> mods = modifyContext.getModItems(); |
| List<Modification> cleanMods = new ArrayList<Modification>(); |
| |
| for ( Modification mod : mods ) |
| { |
| MutableAttributeTypeImpl at = ( ( DefaultModification ) mod ).getAttribute().getAttributeType(); |
| |
| if ( !MODIFIERS_NAME_AT.equals( at ) && !MODIFY_TIMESTAMP_AT.equals( at ) ) |
| { |
| cleanMods.add( mod ); |
| } |
| } |
| |
| modifyContext.setModItems( cleanMods ); |
| |
| // Now that the entry has been modified, update the SSSE |
| schemaSubEntryManager.modifySchemaSubentry( modifyContext, modifyContext |
| .hasRequestControl( Cascade.OID ) ); |
| |
| return; |
| } |
| |
| Entry entry = modifyContext.getEntry(); |
| List<Modification> modifications = modifyContext.getModItems(); |
| checkModifyEntry( dn, entry, modifications ); |
| |
| next.modify( modifyContext ); |
| } |
| |
| |
| /** |
| * Filter the attributes by removing the ones which are not allowed |
| */ |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| @SuppressWarnings("PMD.EmptyCatchBlock") |
| private void filterAttributeTypes( SearchingOperationContext operation, ClonedServerEntry result ) |
| { |
| if ( operation.getReturningAttributes() == null ) |
| { |
| return; |
| } |
| |
| for ( AttributeTypeOptions attrOptions : operation.getReturningAttributes() ) |
| { |
| EntryAttribute attribute = result.get( attrOptions.getAttributeType() ); |
| |
| if ( attrOptions.hasOption() ) |
| { |
| for ( String option : attrOptions.getOptions() ) |
| { |
| if ( "binary".equalsIgnoreCase( option ) ) |
| { |
| continue; |
| } |
| else |
| { |
| try |
| { |
| if ( result.contains( attribute ) ) |
| { |
| result.remove( attribute ); |
| } |
| } |
| catch ( LdapException ne ) |
| { |
| // Do nothings |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| |
| private void filterBinaryAttributes( Entry entry ) throws LdapException |
| { |
| /* |
| * start converting values of attributes to byte[]s which are not |
| * human readable and those that are in the binaries set |
| */ |
| for ( EntryAttribute attribute : entry ) |
| { |
| if ( !attribute.getAttributeType().getSyntax().isHumanReadable() ) |
| { |
| List<Value<?>> binaries = new ArrayList<Value<?>>(); |
| |
| for ( Value<?> value : attribute ) |
| { |
| attribute.add( value ); |
| binaries.add( new BinaryValue( attribute.getAttributeType(), value.getBytes() ) ); |
| } |
| |
| attribute.clear(); |
| |
| for ( Value<?> value : binaries ) |
| { |
| attribute.add( value ); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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 EntryFilter |
| { |
| public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception |
| { |
| filterBinaryAttributes( result ); |
| return true; |
| } |
| } |
| |
| |
| /** |
| * Filters objectClass attribute to inject top when not present. |
| */ |
| private class TopFilter implements EntryFilter |
| { |
| public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception |
| { |
| filterAttributeTypes( operation, result ); |
| return true; |
| } |
| } |
| |
| |
| /** |
| * Check that all the attributes exist in the schema for this entry. |
| * |
| * We also check the syntaxes |
| */ |
| private void check( Dn dn, Entry entry ) throws LdapException |
| { |
| // --------------------------------------------------------------- |
| // First, make sure all attributes are valid schema defined attributes |
| // --------------------------------------------------------------- |
| |
| for ( MutableAttributeTypeImpl attributeType : entry.getAttributeTypes() ) |
| { |
| if ( !schemaManager.getAttributeTypeRegistry().contains( attributeType.getName() ) ) |
| { |
| throw new LdapInvalidAttributeTypeException( I18n.err( I18n.ERR_275, attributeType.getName() ) ); |
| } |
| } |
| |
| // 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 |
| EntryAttribute objectClassAttr = entry.get( OBJECT_CLASS_AT ); |
| |
| // 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 DefaultEntryAttribute( OBJECT_CLASS_AT ); |
| } |
| |
| List<ObjectClass> ocs = new ArrayList<ObjectClass>(); |
| |
| alterObjectClasses( objectClassAttr ); |
| |
| // Now we can process the MUST and MAY attributes |
| Set<String> must = getAllMust( objectClassAttr ); |
| Set<String> allowed = getAllAllowed( objectClassAttr, must ); |
| |
| boolean hasExtensibleObject = getObjectClasses( objectClassAttr, ocs ); |
| |
| // As we now have all the ObjectClasses updated, we have |
| // to check that we don't have conflicting ObjectClasses |
| assertObjectClasses( dn, 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 ); |
| |
| // Now check the syntaxes |
| assertSyntaxes( entry ); |
| |
| assertRdn( dn, entry ); |
| } |
| |
| |
| private void checkOcSuperior( Entry entry ) throws LdapException |
| { |
| // handle the m-supObjectClass meta attribute |
| EntryAttribute supOC = entry.get( MetaSchemaConstants.M_SUP_OBJECT_CLASS_AT ); |
| |
| if ( supOC != null ) |
| { |
| ObjectClassTypeEnum ocType = ObjectClassTypeEnum.STRUCTURAL; |
| |
| if ( entry.get( MetaSchemaConstants.M_TYPE_OBJECT_CLASS_AT ) != null ) |
| { |
| String type = entry.get( MetaSchemaConstants.M_TYPE_OBJECT_CLASS_AT ).getString(); |
| ocType = ObjectClassTypeEnum.getClassType( type ); |
| } |
| |
| // First check that the inheritence scheme is correct. |
| // 1) If the ocType is ABSTRACT, it should not have any other SUP not ABSTRACT |
| for ( Value<?> sup : supOC ) |
| { |
| try |
| { |
| String supName = sup.getString(); |
| |
| ObjectClass superior = schemaManager.getObjectClassRegistry().lookup( supName ); |
| |
| switch ( ocType ) |
| { |
| case ABSTRACT: |
| if ( !superior.isAbstract() ) |
| { |
| String message = I18n.err( I18n.ERR_57 ); |
| LOG.error( message ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message ); |
| } |
| |
| break; |
| |
| case AUXILIARY: |
| if ( !superior.isAbstract() && !superior.isAuxiliary() ) |
| { |
| String message = I18n.err( I18n.ERR_58 ); |
| LOG.error( message ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message ); |
| } |
| |
| break; |
| |
| case STRUCTURAL: |
| break; |
| } |
| } |
| catch ( LdapException ne ) |
| { |
| // The superior OC does not exist : this is an error |
| String message = I18n.err( I18n.ERR_59 ); |
| LOG.error( message ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message ); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Check that all the attributes exist in the schema for this entry. |
| */ |
| public void add( NextInterceptor next, AddOperationContext addContext ) throws LdapException |
| { |
| Dn name = addContext.getDn(); |
| Entry entry = addContext.getEntry(); |
| |
| check( name, entry ); |
| |
| // Special checks for the MetaSchema branch |
| if ( name.isDescendantOf(schemaBaseDn) ) |
| { |
| // get the schema name |
| String schemaName = getSchemaName( name ); |
| |
| if ( entry.contains( OBJECT_CLASS_AT, SchemaConstants.META_SCHEMA_OC ) ) |
| { |
| next.add( addContext ); |
| |
| if ( schemaManager.isSchemaLoaded( schemaName ) ) |
| { |
| // Update the OC superiors for each added ObjectClass |
| computeSuperiors(); |
| } |
| } |
| else if ( entry.contains( OBJECT_CLASS_AT, SchemaConstants.META_OBJECT_CLASS_OC ) ) |
| { |
| // This is an ObjectClass addition |
| checkOcSuperior( addContext.getEntry() ); |
| |
| next.add( addContext ); |
| |
| // Update the structures now that the schema element has been added |
| Schema schema = schemaManager.getLoadedSchema( schemaName ); |
| |
| if ( ( schema != null ) && schema.isEnabled() ) |
| { |
| EntryAttribute oidAT = entry.get( MetaSchemaConstants.M_OID_AT ); |
| String ocOid = oidAT.getString(); |
| |
| ObjectClass addedOC = schemaManager.getObjectClassRegistry().lookup( ocOid ); |
| computeSuperior( addedOC ); |
| } |
| } |
| else if ( entry.contains( OBJECT_CLASS_AT, SchemaConstants.META_ATTRIBUTE_TYPE_OC ) ) |
| { |
| // This is an AttributeType addition |
| next.add( addContext ); |
| } |
| else |
| { |
| next.add( addContext ); |
| } |
| |
| } |
| else |
| { |
| next.add( addContext ); |
| } |
| } |
| |
| |
| private String getSchemaName( Dn dn ) throws LdapException |
| { |
| if ( dn.size() < 2 ) |
| { |
| throw new LdapException( I18n.err( I18n.ERR_276 ) ); |
| } |
| |
| Rdn rdn = dn.getRdn( 1 ); |
| return rdn.getNormValue().getString(); |
| } |
| |
| |
| /** |
| * Checks to see if an attribute is required by as determined from an entry's |
| * set of objectClass attribute values. |
| * |
| * @return true if the objectClass values require the attribute, false otherwise |
| * @throws Exception if the attribute is not recognized |
| */ |
| private void assertAllAttributesAllowed( Dn dn, Entry entry, Set<String> allowed ) throws LdapException |
| { |
| // Never check the attributes if the extensibleObject objectClass is |
| // declared for this entry |
| EntryAttribute objectClass = entry.get( OBJECT_CLASS_AT ); |
| |
| if ( objectClass.contains( SchemaConstants.EXTENSIBLE_OBJECT_OC ) ) |
| { |
| return; |
| } |
| |
| for ( EntryAttribute attribute : entry ) |
| { |
| String attrOid = attribute.getAttributeType().getOid(); |
| |
| MutableAttributeTypeImpl attributeType = attribute.getAttributeType(); |
| |
| if ( !attributeType.isCollective() && ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) |
| && !allowed.contains( attrOid ) ) |
| { |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_277, |
| attribute.getUpId(), dn.getName() ) ); |
| } |
| } |
| } |
| |
| |
| /** |
| * Checks to see number of values of an attribute conforms to the schema |
| */ |
| private void assertNumberOfAttributeValuesValid( Entry entry ) throws LdapInvalidAttributeValueException |
| { |
| for ( EntryAttribute attribute : entry ) |
| { |
| assertNumberOfAttributeValuesValid( attribute ); |
| } |
| } |
| |
| |
| /** |
| * Checks to see numbers of values of attributes conforms to the schema |
| */ |
| private void assertNumberOfAttributeValuesValid( EntryAttribute attribute ) |
| throws LdapInvalidAttributeValueException |
| { |
| if ( attribute.size() > 1 && attribute.getAttributeType().isSingleValued() ) |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.CONSTRAINT_VIOLATION, I18n.err( I18n.ERR_278, |
| attribute.getUpId() ) ); |
| } |
| } |
| |
| |
| /** |
| * Checks to see the presence of all required attributes within an entry. |
| */ |
| private void assertRequiredAttributesPresent( Dn dn, Entry entry, Set<String> must ) throws LdapException |
| { |
| for ( EntryAttribute attribute : entry ) |
| { |
| must.remove( attribute.getAttributeType().getOid() ); |
| } |
| |
| if ( must.size() != 0 ) |
| { |
| // include AT names for better error reporting |
| StringBuilder sb = new StringBuilder(); |
| sb.append( '[' ); |
| |
| for( String oid: must ) |
| { |
| String name = schemaManager.getAttributeType( oid ).getName(); |
| sb.append( name ) |
| .append( '(' ) |
| .append( oid ) |
| .append( "), " ); |
| } |
| |
| int end = sb.length(); |
| sb.replace( end - 2, end, "" ); // remove the trailing ', ' |
| sb.append( ']' ); |
| |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_279, |
| sb, dn.getName() ) ); |
| } |
| } |
| |
| |
| /** |
| * Checck that OC does not conflict : |
| * - we can't have more than one STRUCTURAL OC unless they are in the same |
| * inheritance tree |
| * - we must have at least one STRUCTURAL OC |
| */ |
| private void assertObjectClasses( Dn dn, List<ObjectClass> ocs ) throws LdapException |
| { |
| Set<ObjectClass> structuralObjectClasses = new HashSet<ObjectClass>(); |
| |
| /* |
| * Since the number of ocs present in an entry is small it's not |
| * so expensive to take two passes while determining correctness |
| * since it will result in clear simple code instead of a deep nasty |
| * for loop with nested loops. Plus after the first pass we can |
| * quickly know if there are no structural object classes at all. |
| */ |
| |
| // -------------------------------------------------------------------- |
| // Extract all structural objectClasses within the entry |
| // -------------------------------------------------------------------- |
| for ( ObjectClass oc : ocs ) |
| { |
| if ( oc.isStructural() ) |
| { |
| structuralObjectClasses.add( oc ); |
| } |
| } |
| |
| // -------------------------------------------------------------------- |
| // Throw an error if no STRUCTURAL objectClass are found. |
| // -------------------------------------------------------------------- |
| |
| if ( structuralObjectClasses.isEmpty() ) |
| { |
| String message = I18n.err( I18n.ERR_60, dn ); |
| LOG.error( message ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message ); |
| } |
| |
| // -------------------------------------------------------------------- |
| // Put all structural object classes into new remaining container and |
| // start removing any which are superiors of others in the set. What |
| // is left in the remaining set will be unrelated structural |
| /// objectClasses. If there is more than one then we have a problem. |
| // -------------------------------------------------------------------- |
| |
| Set<ObjectClass> remaining = new HashSet<ObjectClass>( structuralObjectClasses.size() ); |
| remaining.addAll( structuralObjectClasses ); |
| |
| for ( ObjectClass oc : structuralObjectClasses ) |
| { |
| if ( oc.getSuperiors() != null ) |
| { |
| for ( ObjectClass superClass : oc.getSuperiors() ) |
| { |
| if ( superClass.isStructural() ) |
| { |
| remaining.remove( superClass ); |
| } |
| } |
| } |
| } |
| |
| // Like the highlander there can only be one :). |
| if ( remaining.size() > 1 ) |
| { |
| String message = I18n.err( I18n.ERR_61, dn, remaining ); |
| LOG.error( message ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message ); |
| } |
| } |
| |
| |
| /** |
| * Check the entry attributes syntax, using the syntaxCheckers |
| */ |
| private void assertSyntaxes( Entry entry ) throws LdapException |
| { |
| // First, loop on all attributes |
| for ( EntryAttribute attribute : entry ) |
| { |
| MutableAttributeTypeImpl attributeType = attribute.getAttributeType(); |
| SyntaxChecker syntaxChecker = attributeType.getSyntax().getSyntaxChecker(); |
| |
| if ( syntaxChecker instanceof OctetStringSyntaxChecker ) |
| { |
| // This is a speedup : no need to check the syntax of any value |
| // if all the syntaxes are accepted... |
| continue; |
| } |
| |
| // Then loop on all values |
| for ( Value<?> value : attribute ) |
| { |
| if ( value.isValid() ) |
| { |
| // No need to validate something which is already ok |
| continue; |
| } |
| |
| try |
| { |
| syntaxChecker.assertSyntax( value.get() ); |
| } |
| catch ( Exception ne ) |
| { |
| String message = I18n.err( I18n.ERR_280, value.getString(), attribute.getUpId() ); |
| LOG.info( message ); |
| |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message ); |
| } |
| } |
| } |
| } |
| |
| |
| private void assertRdn( Dn dn, Entry entry ) throws LdapException |
| { |
| for ( Ava atav : dn.getRdn() ) |
| { |
| EntryAttribute attribute = entry.get( atav.getNormType() ); |
| |
| if ( ( attribute == null ) || ( !attribute.contains( atav.getNormValue() ) ) ) |
| { |
| String message = I18n.err( I18n.ERR_62, dn, atav.getUpType() ); |
| LOG.error( message ); |
| throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, message ); |
| } |
| } |
| } |
| |
| |
| /** |
| * Check a String attribute to see if there is some byte[] value in it. |
| * |
| * If this is the case, try to change it to a String value. |
| */ |
| private boolean checkHumanReadable( EntryAttribute attribute ) throws LdapException |
| { |
| boolean isModified = false; |
| |
| // Loop on each values |
| for ( Value<?> value : attribute ) |
| { |
| if ( value instanceof StringValue ) |
| { |
| continue; |
| } |
| else if ( value instanceof BinaryValue ) |
| { |
| // we have a byte[] value. It should be a String UTF-8 encoded |
| // Let's transform it |
| try |
| { |
| String valStr = new String( value.getBytes(), "UTF-8" ); |
| attribute.remove( value ); |
| attribute.add( valStr ); |
| isModified = true; |
| } |
| catch ( UnsupportedEncodingException uee ) |
| { |
| throw new LdapException( I18n.err( I18n.ERR_281 ) ); |
| } |
| } |
| else |
| { |
| throw new LdapException( I18n.err( I18n.ERR_282 ) ); |
| } |
| } |
| |
| return isModified; |
| } |
| |
| |
| /** |
| * Check a binary attribute to see if there is some String value in it. |
| * |
| * If this is the case, try to change it to a binary value. |
| */ |
| private boolean checkNotHumanReadable( EntryAttribute attribute ) throws LdapException |
| { |
| boolean isModified = false; |
| |
| // Loop on each values |
| for ( Value<?> value : attribute ) |
| { |
| if ( value instanceof BinaryValue ) |
| { |
| continue; |
| } |
| else if ( value instanceof StringValue) |
| { |
| // We have a String value. It should be a byte[] |
| // Let's transform it |
| try |
| { |
| byte[] valBytes = value.getString().getBytes( "UTF-8" ); |
| |
| attribute.remove( value ); |
| attribute.add( valBytes ); |
| isModified = true; |
| } |
| catch ( UnsupportedEncodingException uee ) |
| { |
| String message = I18n.err( I18n.ERR_63 ); |
| LOG.error( message ); |
| throw new LdapException( message ); |
| } |
| } |
| else |
| { |
| String message = I18n.err( I18n.ERR_64 ); |
| LOG.error( message ); |
| throw new LdapException( message ); |
| } |
| } |
| |
| return isModified; |
| } |
| |
| |
| /** |
| * Check that all the attribute's values which are Human Readable can be transformed |
| * to valid String if they are stored as byte[], and that non Human Readable attributes |
| * stored as String can be transformed to byte[] |
| */ |
| private void assertHumanReadable( Entry entry ) throws LdapException |
| { |
| boolean isModified = false; |
| |
| Entry clonedEntry = null; |
| |
| // Loops on all attributes |
| for ( EntryAttribute attribute : entry ) |
| { |
| MutableAttributeTypeImpl attributeType = attribute.getAttributeType(); |
| |
| // If the attributeType is H-R, check all of its values |
| if ( attributeType.getSyntax().isHumanReadable() ) |
| { |
| isModified = checkHumanReadable( attribute ); |
| } |
| else |
| { |
| isModified = checkNotHumanReadable( attribute ); |
| } |
| |
| // If we have a returned attribute, then we need to store it |
| // into a new entry |
| if ( isModified ) |
| { |
| if ( clonedEntry == null ) |
| { |
| clonedEntry = entry.clone(); |
| } |
| |
| // Switch the attributes |
| clonedEntry.put( attribute ); |
| |
| isModified = false; |
| } |
| } |
| |
| if ( clonedEntry != null ) |
| { |
| entry = clonedEntry; |
| } |
| } |
| } |