Menu

How could I add new objectclasses to the directory ?

2019-09-16
2019-09-17
  • Nicolas DUMINIL

    Nicolas DUMINIL - 2019-09-16

    Hello,
    In a Spring Boot 2.1.6 project I'm using the embedded LDAP service (UnboundID LDAP). I need to define new attribute types and object classes. My LDIF file contains something like that:

    ...
    dn: cn=schema
    changetype: modify
    add: attributetypes
    attributetypes: ( 2.25.128424792425578037463837247958458780603.1 NAME 'customAttribute' DESC 'a custom attribute' EQUALITY caseIgnoreMatch SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
    ...
    

    However, when I deploy my WAR on tomcat I get this exception:

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'directoryServer' defined in class path resource [org/springframework/boot/autoconfigure/ldap/embedded/EmbeddedLdapAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.unboundid.ldap.listener.InMemoryDirectoryServer]: Factory method 'directoryServer' threw exception; nested exception is java.lang.IllegalStateException: Unable to load LDIF classpath:test.ldif
    ...
    Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.unboundid.ldap.listener.InMemoryDirectoryServer]: Factory method 'directoryServer' threw exception; nested exception is java.lang.IllegalStateException: Unable to load LDIF classpath:test.ldif
    ...
    Caused by: LDAPException(resultCode=68 (entry already exists), errorMessage='Unable to add an entry with a DN that is the same as or subordinate to the subschema subentry DN 'cn=schema'.', ldapSDKVersion=4.0.11, revision=34e39aab27ea4fb92659a6888933db08099c7e41)
    at com.unboundid.ldap.listener.InMemoryRequestHandler.addEntry(InMemoryRequestHandler.java:4916)
    at com.unboundid.ldap.listener.InMemoryRequestHandler.importFromLDIF(InMemoryRequestHandler.java:4624)
    at com.unboundid.ldap.listener.InMemoryDirectoryServer.importFromLDIF(InMemoryDirectoryServer.java:1255)
    at org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration.importLdif(EmbeddedLdapAutoConfiguration.java:164)
    ... 76 more
    

    What happens obviously is that, for some reason, the LDAP service "thinks" that I'm trying to ad the entry "cn=schema", and refuses the operation as the entry already exists, while I'm trying to modify it, hence the statememnt "changetype: modify" in the LDIF file.

    Could someone please advise me what I'm doing wrong ?

    Many thanks in advance.

    Kind regards,

    Nicolas

     
  • Neil Wilson

    Neil Wilson - 2019-09-16

    It sounds like you're trying to import the file rather than apply the changes it contains. An LDIF import can only be used to create entries in the server, and cannot be used with change records.

    If you want to get custom schema into the in-memory directory server, you have a few options:

    • You could use use the InMemoryDirectoryServer.applyChangesFromLDIF method, which does allow you to apply changes read from an LDIF file.
    • If you'd rather have the changes in code rather than in an LDIF file, then you could use the InMemoryDirectoryServer.modify method to apply the change.
    • If you want to have custom schema loaded into the server when it starts, you can use the InMemoryDirectoryServerConfig.setSchema method before creating the directory server instance. Note that in that case, you need to provide the entire schema you want to use rather than just the custom elements.
     
  • Nicolas DUMINIL

    Nicolas DUMINIL - 2019-09-16

    Hi Neil,
    Many thanks for your answer. I think that the 3rd scenario would be the most appropriated, however, as mentioned in the post, I'm using Spring Boot. Accordingly, I'm not creating at any point any directory server as these details are automatically provided by the framework. The only thing I have to do is to set the following two properties:
    spring.ldap.embedded.ldif: the name of the file, on the classpath, containing the directory data
    spring.ldap.embedded.validate.schema: the name of the custom schema I want the embedded LDAP service to use.
    So I used the schema file that comes with you distribution (standard-schema. ldif) and I used it as the value of the spring.ldap.embedded.validate.schema property, after having adjusted such to add somme new attributes and object classes, for example:

    attributetypes: ( 2.25.128424792425578037463837247958458780603.1
    

    NAME 'customAttribute'
    EQUALITY caseIgnoreMatch
    SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
    ...
    objectClasses: ( 2.5.6.6
    NAME 'person'
    SUP top
    STRUCTURAL
    MUST ( sn $ cn )
    MAY ( userPassword $
    telephoneNumber $
    seeAlso $
    description $
    customAttribute )

    Here I added customAttribute to the person object class. I don't know whether the modified schema is loaded but using the new attribute in the directory data, for example:

    dn: uid=test-user,ou=people,dc=mycompany,dc=com
    objectclass: inetOrgPerson
    objectclass: organizationalPerson
    objectclass: person
    objectclass: simpleSecurityObject
    objectclass: top
    cn: test
    sn: Duminni
    uid: test-user
    userPassword:   $2a$12$qIaTIG3UVm0hfKRWbwO5EueXG.omG7FL0XmuVxlQ8UuJrozX8Tlk2
    customAttribute: toto
    

    raises an exception saying the customeAttribute is not defined in the current schema. So I would like to use the InMemoryDirectoryServerConfig.setSchema () as you advised but I don't know how to wire it as I'm not handling any instance of the directory service in the code.

    Could you please shade some light here ? Many thanks in advance.

    Kind regards,

    Nicolas

     
  • Neil Wilson

    Neil Wilson - 2019-09-16

    I'm sorry, but I don't know anything about Spring Boot or how they're using the LDAP SDK.

    However, assuming that you have the ability to get a connection to the server once it has been created, you can alter the schema after the fact by applying a modification, like:

    final AttributeTypeDefinition customAttributeType =
         new AttributeTypeDefinition(
              "2.25.128424792425578037463837247958458780603.1",
              "customAttribute",
              null, // No description needed
              "caseIgnoreMatch",
              null, // No ordering matching rule needed
              null, // No substring matching rule needed
              "1.3.6.1.4.1.1466.115.121.1.15",
              false, // Not single-valued
              null); // No extensions needed
    connection.modify("cn=schema",
         new Modification(ModificationType.ADD, "attributeTypes",
              customAttributeType.toString()));
    

    You can do the same with object classes (using the "objectClasses" attribute instead of "attributeTypes"). However, in your case it looks like you're trying to alter a standard object class, which is really not a good idea. You should create a custom object class that extends the standard one and then include that object class in your entry. In your case, it looks like you want your custom object class to inherit from inetOrgPerson rather than person. You could do that like:

    final ObjectClassDefinition customObjectClass = new ObjectClassDefinition(
         "custom-objectclass-oid",
         "customObjectClass",
         null, // No description needed
         "inetOrgPerson",
         ObjectClassType.STRUCTURAL,
         new String[0], // No attributes are required
         new String[] { "customAttribute" }, // customAttribute is optional
         null); // No extensions needed
    connection.modify("cn=schema",
         new Modification(ModificationType.ADD, "objectClasses",
              customObjectClass.toString()));
    

    And then you could add your test user like:

    connection.add("uid=test-user,ou=people,dc=mycompany,dc=com",
         new Attribute("objectClass", "top", "person",
              "organizationalPerson", "inetOrgPerson",
              "customObjectClass", "simpleSecurityObject"),
         new Attribute("cn", "test"),
         new Attribute("sn", "Duminni"),
         new Attribute("uid", "test-user"),
         new Attribute("userPassword",
              "$2a$12$qIaTIG3UVm0hfKRWbwO5EueXG.omG7FL0XmuVxlQ8UuJrozX8Tlk2"),
         new Attribute("customAttribute", "toto"));
    
     
  • Nicolas DUMINIL

    Nicolas DUMINIL - 2019-09-17

    Hi Neil,

    Thanks again for your reply. I'll try that but I would prefer to put the attributes/objectclasses definition in an LDIF file and load it. The applyChangesFromLdif() method seems to be what I need, right ? Supposing that I'm able to get the connection object, would it be a way to use it ?
    Kind regards,
    Nicolas

     
  • Neil Wilson

    Neil Wilson - 2019-09-17

    Unfortunately, the InMemoryDirectoryServer.applyChangesFromLDIF method isn't one that you can use with a connection. However, you can accomplish the same thing with code like the following:

    try (LDIFReader ldifReader = new LDIFReader("/path/to/ldif/file"))
    {
      while (true)
      {
        LDIFChangeRecord changeRecord = ldifReader.readChangeRecord(true);
        if (changeRecord == null)
        {
          break;
        }
    
        changeRecord.processChange(connection);
      }
    }
    
     
  • Nicolas DUMINIL

    Nicolas DUMINIL - 2019-09-17

    Thank you Neil.

    Kind regards,
    Nicolas

     

Log in to post a comment.