ldapsdk icon indicating copy to clipboard operation
ldapsdk copied to clipboard

Adding one record to LDAP server is throwing exception

Open bappajubcse05 opened this issue 6 years ago • 15 comments

Hello,

I am trying to create a record in LDAP server:

dn: CN=testuser01,OU=users,OU=myOrg,DC=ldap,DC=tester,DC=example,DC=com
distinguishedName: CN=testuser01,OU=users,OU=myOrg,DC=ldap,DC=tester,DC=example,DC=com
gn: bpa
objectClass: person
objectClass: organizationalPerson
objectClass: user
objectClass: top
cn: testuser01
sn: mdl
mail: [email protected]
department: it
objectCategory: CN=User,CN=Schema,CN=Config,DC=tester,DC=example,DC=com

It is throwing below exception:

com.unboundid.ldap.sdk.LDAPException: Unable to add entry 'CN=testuser01,OU=users,OU=myOrg,DC=ldap,DC=tester,DC=example,DC=com' because it violates the provided schema:  The entry contains object class user which is not defined in the schema.  The entry contains attribute distinguishedName which is not allowed by its object classes and/or DIT content rule.  The entry contains attribute gn which is not defined in the schema.  The entry contains attribute mail which is not allowed by its object classes and/or DIT content rule.  The entry contains attribute department which is not defined in the schema.  The entry contains attribute objectCategory which is not defined in the schema.
at com.unboundid.ldap.sdk.LDAPConnection.add(LDAPConnection.java:1970) ~[unboundid-ldapsdk-commercial-edition-3.2.1.jar!/:3.2.1]

The schema is attached. schema.txt

Could anyone help to find the issue.

Regards, Bappa

bappajubcse05 avatar May 07 '19 15:05 bappajubcse05

The message is telling you exactly what's wrong:

  • The object class "user" is not defined in the schema
  • The attribute "distinguishedName" is defined in the schema, but isn't allowed by any of the entry's object classes
  • The attribute "gn" is not defined in the schema
  • The attribute "mail" is defined in the schema but isn't allowed by any of the entry's object classes
  • The attribute "department" is not defined in the schema
  • The attribute "objectCategory" is not defined in the schema

dirmgr avatar May 07 '19 16:05 dirmgr

Hello @dirmgr ,

How can I create the tree structure for an entry using unboundid.ldap.sdk?

DC=com
|-- DC=example
	|-- DC=tester
		|-- DC=ldap
			|-- DC=ldap
				|-- OU=myOrg
					|-- OU=users
						|-- CN=testuser01

I tried to add 'DC=com' without specifying any object classes attribute. But it is throwing below exception:

com.unboundid.ldap.sdk.LDAPException: Unable to add entry 'DC=com' because it violates the provided schema: The entry does not have any object classes. The entry contains attribute dc which is not allowed by its object classes and/or DIT content rule. The entry's RDN contains attribute dc which is not allowed to be included in the entry.

But when I am specifying objectClass: top as attribute, it is throwing below exception:

com.unboundid.ldap.sdk.LDAPException: Unable to add entry 'DC=com' because it violates the provided schema: The entry contains object class top which is not defined in the schema. The entry contains attribute dc which is not allowed by its object classes and/or DIT content rule. The entry contains attribute objectClass which is not allowed by its object classes and/or DIT content rule. The entry's RDN contains attribute dc which is not allowed to be included in the entry.

I beleive I need to add DC=com entry first, then DC=example,DC=com then DC=tester,DC=example,DC=com..... and finally CN=testuser01,OU=users,OU=myOrg,DC=ldap,DC=tester,DC=example,DC=com

bappajubcse05 avatar May 07 '19 17:05 bappajubcse05

The message is telling you exactly what's wrong:

  • The object class "user" is not defined in the schema
  • The attribute "distinguishedName" is defined in the schema, but isn't allowed by any of the entry's object classes
  • The attribute "gn" is not defined in the schema
  • The attribute "mail" is defined in the schema but isn't allowed by any of the entry's object classes
  • The attribute "department" is not defined in the schema
  • The attribute "objectCategory" is not defined in the schema

How can I add a new attribute like distinguishedName ? Is it like below:

objectClasses: ( 2.5.6.6
  NAME 'user'
  SUP organizationalPerson
  STRUCTURAL
  MUST ( sn $
         distinguishedName $
         cn )
  MAY ( userPassword $
        telephoneNumber $
        seeAlso $
        description )
  X-ORIGIN 'RFC 4519' )

bappajubcse05 avatar May 07 '19 17:05 bappajubcse05

It sounds like these are general LDAP questions (and pretty fundamental ones, at that) and not specific to the LDAP SDK. This isn't really a general LDAP support forum. It's really intended for issues that are specific to the UnboundID LDAP SDK for Java.

But again, the error message that the server is returning tells you exactly what is wrong: you can't add an entry without any object classes. Every entry must have a structural object class. An entry with the dc naming attribute typically uses a structural class of "domain" (although it's also a common practice to use a structural class of "organization" and an auxiliary class of "dcObject").

Your "user" object class definition is largely okay, but there are a couple of problems with it. The only one that matters is the OID: you should definitely not use an OID of 2.5.6.6 because that's the OID for the person object class. Every attribute type and object class must have a unique OID, and you shouldn't just pick one at random. See https://ldap.com/object-identifiers/ for information about getting your own OID base from IANA, where you can do whatever you want.

However, based on the information you've provided so far, it sounds like you're trying to work with Active Directory data. If that's the case, then you should just get the schema from an Active Directory instance rather than trying to recreate it piecemeal.

dirmgr avatar May 07 '19 18:05 dirmgr

@dirmgr - Yes. You are right. This is related to Microsoft Active Directory. I have those schema files but still I am getting the same error. The schema that I have received for active directory is totally different from the default schema structure present in unboundid ldap sdk. Could you please let me know whether I can create a LDAP server using active directory schema(s).

bappajubcse05 avatar May 08 '19 11:05 bappajubcse05

If you have an LDIF representation of the AD schema (and if you don't then you can get it using ldapConnection.getSchema().getSchemaEntry().toLDIFString()), then you should just be able to use it directly.

dirmgr avatar May 08 '19 15:05 dirmgr

Hello @dirmgr,

The objectClasses defined in the schema as NO-USER-MODIFICATION. An object class that I am using to create the entry has a 'must' attribute called ObjectSID (which is also defined as NO-USER-MODIFICATION). Whenever I am trying create an entry without specifying ObjectSID (assuming that it should be generated by the system), it is throwing below exception: com.unboundid.ldap.sdk.LDAPException: Unable to add entry '................' because it violates the provided schema: The entry is missing required attribute objectSid.

When I am specifying it during entry creation, it is throwing com.unboundid.ldap.sdk.LDAPException: Unable to add entry '...................' because it includes attribute objectClass which is declared with NO-USER-MODIFICATION in the schema

Could you please help me to fix this problem.

bappajubcse05 avatar May 09 '19 11:05 bappajubcse05

ObjectSid is specific to ActiveDirectory. You are correct that because it is defined with NO-USER-MODIFICATION in schema by ActiveDirectory, it means that it is an operational attribute that is set and managed by the ActiveDirectory engine. However, just marking an attribute type definition USER-NO-MODIFICATION in the schema of another LDAP directory server will not make that other engine know how to compute a value. The computation for ObjectSid is not only not standard, but it is also proprietary to ActiveDirectory and apparently may even vary between ActiveDirectory versions.

as @dirmgr mentioned earlier, these are general LDAP questions better answered on another forum.

If you intend to create entries similar to those found in ActiveDirectory in a different engine, then you will have to provide all the required values in the MUST section of the objectClasses definitions for the objectClass defined on the entry you are attempting to create, except for those that are natively operational for the target engine.

arnaudlacour avatar May 09 '19 13:05 arnaudlacour

It seems like a bad design to have a MUST attribute be also declared NO-USER-MODIFICATION.

Although you haven't explicitly stated it, it sounds like you might be using the LDAP SDK's in-memory directory server. If that's the case, then there are a few ways that you can get around this limitation:

  • You can load the data from an LDIF file using the InMemoryDirectoryServer.importFromLDIF method. Data loaded into the server through this method is not subject to the same constraints as data added from LDAP clients.

  • You can use the InMemoryDirectoryServer.add method to add the entry. If you add the entry directly to the server using this method (as opposed to using an LDAPConnection to add the entry over LDAP), then it also bypasses some restrictions that are normally in place for LDAP clients.

  • If you want to do it over LDAP, then you can include an IgnoreNouserModificationRequestControl in your add request. This is a proprietary control created by UnboundID (now Ping Identity), so it's not supported by other directory servers, but it will let you work around the issue in the case of the in-memory directory server.

dirmgr avatar May 09 '19 18:05 dirmgr

Hello @dirmgr,

Thank you so much for your help.

I have already applied the 2nd approach. 3rd approach is failing with below exception:

com.unboundid.ldap.sdk.LDAPException: The control with OID '1.3.6.1.4.1.30221.2.5.5' is not supported by the in-memory request processor.

However, I have modified the schema and removed that NO-USER-MODIFICATION constraint and created an in-memory server using that modified schema. I have created an entry manually through Apache Directory Studio. But when I am trying filter a child based on below filter:

(&(typeFlag:1.2.840.113556.1.4.803:=-2147483648)(cn=test))

it is not returning any records. Whereas the same filter is working in Active Directory. I have verified the attribute properties, it seems it is created properly. In in-memory server, when I am applying (cn=test) as filter, it is able to return entries but when I am applying (typeFlag:1.2.840.113556.1.4.803:=-2147483648), it is not returning any record.

It seems 1.2.840.113556.1.4.803 (LDAP_MATCHING_RULE_BIT_AND) is specific to Microsoft Active Directory.

Could you please guide me a way around to achieve the same filtration logic (anything that is needed to create the server/entry) in in-memory server (Unboundid LDAP SDK).

If that is not possible, is there any way to define 1.2.840.113556.1.4.803 this operation in Unboundid LDAP SDK server?

bappajubcse05 avatar May 10 '19 09:05 bappajubcse05

The in-memory directory server does not support filters with extensible matching, and the LDAP SDK doesn't support that matching rule.

There's really no easy way to do what you want in the in-memory directory server. The only thing that you could possibly do would be to write a custom InMemoryOperationInterceptor that intercepts search operations that use this type of filter and either processes that search completely or at least identifies the matching entries and replaces the filter with one that will match those same entries (perhaps one that ORs the matching entries' entryUUID values together).

dirmgr avatar May 10 '19 14:05 dirmgr

Hello @dirmgr I found InMemoryExtendedOperationHandler and I have a feeling that it might be helpful to define 1.2.840.113556.1.4.803 (LDAP_MATCHING_RULE_BIT_AND) this operation in In-memory-server. Please let me know your opinion.

config.addExtendedOperationHandler(new BitwiseAndOperationHandler());
------------
public class BitwiseAndOperationHandler extends InMemoryExtendedOperationHandler {
    @Override
    public String getExtendedOperationHandlerName() {
        log.info("=============getExtendedOperationHandlerName==============");
        return "LDAP_MATCHING_RULE_BIT_AND";
    }

    @Override
    public List<String> getSupportedExtendedRequestOIDs() {
         log.info("=============getSupportedExtendedRequestOIDs==============");
        return Collections.singletonList("1.2.840.113556.1.4.803");
    }

    @Override
    public ExtendedResult processExtendedOperation(InMemoryRequestHandler handler, int messageID, ExtendedRequest request) {
         log.info("=============processExtendedOperation==============");
        handler.getConnectionState().forEach((key, value) -> System.out.println(key + ": " + value));
        return null;
    }
}

Unfortunately, it is not hitting processExtendedOperation() when I am performing a search operation with filter (&(typeFlag:1.2.840.113556.1.4.803:=-2147483648)(cn=test))

My approach was: get all entries using InMemoryRequestHandler.search() method and apply the operation on the result set but this method accepts bindDn but neither InMemoryRequestHandler nor ExtendedRequest in processExtendedOperation method provide the required information to perform the search. Could you please help me here.

bappajubcse05 avatar May 13 '19 17:05 bappajubcse05

This will not work. You're trying to perform a search operation. An extended operation handler can only be used to process extended operations. The only way you're going to be able to do this with a search operation is to use the in-memory operation interceptor that I mentioned earlier to intercept the search operation and do the processing for yourself.

dirmgr avatar May 13 '19 17:05 dirmgr

Hello @dirmgr ,

Could you please guide me or provide me an example to process the search operation for :1.2.840.113556.1.4.803: in the interceptor.

bappajubcse05 avatar May 24 '19 13:05 bappajubcse05

First, let me say that the in-memory directory server is meant to be a generic standards-compliant LDAP server. It is not meant to reproduce proprietary functionality or the specific quirks of any particular LDAP server implementation (and Active Directory is definitely one of the quirkiest). If you're trying to emulate Active Directory with the in-memory directory server, you're probably going to run into all kinds of problems. Some of them might not be very easy to solve, and I probably won't provide any assistance in such cases because this kind of thing is very much outside the scope of what it's supposed to do.

Nevertheless, here's a stub of an in-memory operation interceptor that you can use to get started. You will definitely need to replace the body of the entryMatchesExtensibleMatchFilter method, but I haven't tested this at all, so it might require additional changes as well.

/*
 * Copyright 2019 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2019 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>.
 */
package com.example.exampleinterceptor;



import java.util.ArrayList;
import java.util.List;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchRequest;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;



/**
 * This class provides an example of an operation interceptor for the UnboundID
 * LDAP SDK for Java's in-memory directory server that can be used to intercept
 * search requests with components that use extensible matching filters,
 * process those filter components using custom code, to identify any
 * potential matching entries, and replaces the extensible matching component
 * with an OR filter that will match those entries.
 * <BR><BR>
 * Note that this interceptor needs access to the associated in-memory directory
 * server to do its work.  Since this interceptor has to be included in the
 * configuration that is used to create the in-memory directory server, the
 * reference to the in-memory directory server instance can't be provided in the
 * constructor, but has to be made available after the fact.  That should be
 * done by calling the {@link #setInMemoryDirectoryServer} method as soon as
 * the in-memory directory server instance has been created.
 */
public final class ExtensibleMatchSearchHandlingInMemoryOperationInterceptor
       extends InMemoryOperationInterceptor
{
  // The in-memory directory server instance with which this interceptor is
  // associated.
  private volatile InMemoryDirectoryServer ds;



  /**
   * Creates a new instance of this in-memory operation interceptor.
   */
  public ExtensibleMatchSearchHandlingInMemoryOperationInterceptor()
  {
    ds = null;
  }



  /**
   * Provides this interceptor with a reference to the in-memory directory
   * server with which the interceptor is associated.
   *
   * @param  ds  The in-memory directory server instance.  It must not be
   *             {@code null}.
   */
  public void setInMemoryDirectoryServer(final InMemoryDirectoryServer ds)
  {
    this.ds = ds;
  }



  /**
   * Invokes any processing that should be performed for the provided search
   * request before it is passed to the in-memory directory server.
   *
   * @param  request  Information about the request that was received from the
   *                  client.
   *
   * @throws  LDAPException  If the provided operation should not be passed onto
   *                         the in-memory directory server, but the result
   *                         represented by this exception should be used
   *                         instead.
   */
  @Override()
  public void processSearchRequest(
                   final InMemoryInterceptedSearchRequest request)
         throws LDAPException
  {
    final SearchRequest searchRequest = request.getRequest().duplicate();
    final Filter originalFilter = searchRequest.getFilter();
    final Filter updatedFilter = replaceExtensibleMatchingFilterComponents(
         searchRequest, originalFilter);
    searchRequest.setFilter(updatedFilter);
    request.setRequest(searchRequest);
  }



  /**
   * Examines the provided filter and determines whether it contains any
   * extensible matching filter components.  If it does, then that component
   * will be evaluated and replaced with an OR filter that contains the DNs of
   * the matching entries.
   *
   * @param  searchRequest  The search request being processed.  It must not be
   *                        {@code null}.
   * @param  filter         The filter to be processed.  It must not be
   *                        {@code null}.
   *
   * @return  The updated filter.  It may be the original filter if no changes
   *          were needed.
   *
   * @throws  LDAPException  If a problem is encountered while processing the
   *                         provided filter.
   */
  private Filter replaceExtensibleMatchingFilterComponents(
                      final SearchRequest searchRequest, final Filter filter)
          throws LDAPException
  {
    switch (filter.getFilterType())
    {
      case Filter.FILTER_TYPE_AND:
        // Process all of the components inside the AND and return a new AND
        // filter with the potentially updated components.
        final Filter[] originalANDComponents = filter.getComponents();
        final Filter[] newANDComponents =
             new Filter[originalANDComponents.length];
        for (int i=0; i < originalANDComponents.length; i++)
        {
          newANDComponents[i] = replaceExtensibleMatchingFilterComponents(
               searchRequest, originalANDComponents[i]);
        }
        return Filter.createANDFilter(newANDComponents);


      case Filter.FILTER_TYPE_OR:
        // Process all of the components inside the OR and return a new OR
        // filter with the potentially updated components.
        final Filter[] originalORComponents = filter.getComponents();
        final Filter[] newORComponents =
             new Filter[originalORComponents.length];
        for (int i=0; i < originalORComponents.length; i++)
        {
          newORComponents[i] = replaceExtensibleMatchingFilterComponents(
               searchRequest, originalORComponents[i]);
        }
        return Filter.createORFilter(newORComponents);


      case Filter.FILTER_TYPE_NOT:
        // Process the component inside the NOT and return a new NOT filter
        // with the potentially updated component.
        return Filter.createNOTFilter(replaceExtensibleMatchingFilterComponents(
             searchRequest, filter.getNOTComponent()));


      case Filter.FILTER_TYPE_EXTENSIBLE_MATCH:
        // Get a list of the DNs of the entries that match this extensible-match
        // filter component.
        final List<String> matchingEntryDNs =
             getDNsOfEntriesMatchingExtensibleMatchFilter(searchRequest,
                  filter);
        final List<Filter> entryDNFilterComponents =
             new ArrayList<>(matchingEntryDNs.size());
        for (final String dn : matchingEntryDNs)
        {
          entryDNFilterComponents.add(Filter.createEqualityFilter("entryDN",
               dn));
        }
        return Filter.createORFilter(entryDNFilterComponents);


      case Filter.FILTER_TYPE_PRESENCE:
      case Filter.FILTER_TYPE_EQUALITY:
      case Filter.FILTER_TYPE_SUBSTRING:
      case Filter.FILTER_TYPE_GREATER_OR_EQUAL:
      case Filter.FILTER_TYPE_LESS_OR_EQUAL:
      case Filter.FILTER_TYPE_APPROXIMATE_MATCH:
      default:
        // No special processing is required for any of these filter types, so
        // we can just return the original filter.
        return filter;
    }
  }



  /**
   * Retrieves a list of the DNs of the entries in the associated in-memory
   * directory server instance that match the provided extensible-match filter.
   *
   * @param  filter  The extensible-match filter to be evaluated.  It must not
   *                 be {@code null}.
   *
   * @return  A list of the DNs of the entries in the associated in-memory
   *          directory server instance that match the provided extensible-match
   *          filter.  It must not be {@code null} but may be empty.
   *
   * @throws  LDAPException  If a problem is encountered while processing the
   *                         provided filter.
   */
  private List<String> getDNsOfEntriesMatchingExtensibleMatchFilter(
                            final SearchRequest searchRequest,
                            final Filter filter)
          throws LDAPException
  {
    if (ds == null)
    {
      throw new LDAPException(ResultCode.LOCAL_ERROR,
           "The ExtensibleMatchSearchHandlingInMemoryOperationInterceptor " +
                "instance has not been updated with the in-memory directory " +
                "server instance.");
    }


    // Perform an internal search in the in-memory directory server to retrieve
    // all of the entries within the scope of the original search.  If the
    // search does not succeed, then throw an exception.
    final SearchResult searchResult = ds.search(searchRequest.getBaseDN(),
         searchRequest.getScope(), Filter.createPresenceFilter("objectClass"),
         SearchRequest.ALL_USER_ATTRIBUTES,
         SearchRequest.ALL_OPERATIONAL_ATTRIBUTES);
    if (searchResult.getResultCode() != ResultCode.SUCCESS)
    {
      throw new LDAPSearchException(searchResult);
    }


    // Iterate through all the matching entries found by the search operation
    // and determine which of them match the extensible match filter component.
    final List<String> matchingEntryDNs =
         new ArrayList<>(searchResult.getEntryCount());
    for (final SearchResultEntry entry : searchResult.getSearchEntries())
    {
      if (entryMatchesExtensibleMatchFilter(entry, filter))
      {
        matchingEntryDNs.add(entry.getDN());
      }
    }

    return matchingEntryDNs;
  }



  /**
   * Determines whether the provided entry matches the given extensible-match
   * filter.
   *
   * @param  entry   The entry for which to make the determination.  It must not
   *                 be {@code null}.
   * @param  filter  The extensible-match filter to be evaluated.  It must not
   *                 be {@code null}.
   *
   * @return  {@code true} if the provided entry matches the filter, or
   *          {@code false} if not.
   *
   * @throws  LDAPException  If a problem is encountered while making the
   *                         determination.
   */
  private boolean entryMatchesExtensibleMatchFilter(
                       final SearchResultEntry entry,
                       final Filter filter)
          throws LDAPException
  {
    // Replace this method with the appropriate logic.
    throw new LDAPException(ResultCode.LOCAL_ERROR,
         "The ExtensibleMatchSearchHandlingInMemoryOperationInterceptor." +
              "entryMatchesExtensibleMatchFilter method has not been updated " +
              "to include the real logic.");
  }
}

dirmgr avatar May 24 '19 19:05 dirmgr