Saturday, February 12, 2011

How to introduce custom attributes to LDAP

I. WHY...?
In identity and user management, we come across the need to store various information regarding users in a user store. When we use LDAP as the user store, we store user information as 'attributes' related with user 'entries'.

Although there are common and default set of attributes supported by many LDAP implementations, they might not cater all our requirements. Hence, LDAP servers (like ApacheDS, OpenLDAP) give us the flexibility and extendibility to define our own attributes and introduce them to the LDAP server instance and use them as we need.

II. PROBLEM...
So let me briefly explain how I came across the requirement mentioned in the subject.

WSO2 Identity Server now uses embedded ApacheDS LDAP as its default user store. All the other features which were supported by default JDBC user store earlier, needed to be supported with LDAP as well. 

When supporting features such as signing up with open id and info card, we need to store the attributes defined in open id and info card profiles. In the aspect of storing user attributes, LDAP user store is little bit challenging than JDBC user store--because for an attribute to be used in LDAP entries, it should have been introduced to that particular LDAP instance by default or by us. 

For an example, open id user profile contains the attributes such as 'date of birth' ,'gender', 'postal code' etc..which are not supported by ApacheDS by default.

III. THEORY...
Now let me first take you though the relationship among attributes, object classes, schemas and entries in LDAP,  in order to provide some back ground knowledge.

We store some entity like a user, in an entry in the LDAP. To construct an entry, we use one or more object classes. An object class defines the structure of a particular entry such as what attributes it contains etc. Object classes are grouped into a schema which is known to LDAP server.

Well..let me put it in other words... To use an attribute in an entry, that attribute should be defined in an object class, and that object class must be used in the entry definition. Further, the object class should be included in the schema of the LDAP server.

That implies--> if we want to use an custom attribute/s we should write a custom object class including that attribute/s, and we need to introduce that object class to the schema of the LDAP server instance that we are going to use.
There are two ways that we can introduce a custom object class to a LDAP server.
  1. Writing an object class in a ldif (ldap data interchange format)  file and importing it to the LDAP schema.
  2. Programmatic way--you can use JNDI for that.
In the 'walk through' section of this post, I will describe the first way.
There are three types of object classes:
  1. Structural: used to create entries--an entry should have at least one structural object class. (can have more than one as well)
  2. Auxiliary: basically used to add additional attributes to an entry created with some other structural object class. an entry can have one or more auxiliary object classes. But an entry can not construct only with auxiliary object classes.
  3. Abstract: these basically sit at the top of the object class hierarchies. an example is: 'top' object class in any LDAP.
Another nice concept in object classes is 'inheritance' like in OOP. If we inherit our custom object class from an existing object class, we automatically get all the attributes defined in that object class.
For an example, if we take the famous object class 'inetOrgPerson' which is used to create user entries, it has a nice inheritance hierarchy as below:
             'top'-->'person'-->'organizationalPerson'-->'inetOrgPerson'

That's it..I think this background knowledge is pretty enough for the following walk through.

IV. WALK THROUGH...
Following is a simple walk through which includes steps on how to introduce  a custom object class with new set of attributes to ApacheDS LDAP and use them to store user entries.

Pre-requisites: install Apache Directroy Server and ApacheDS Studio

How to write a ldif file:
  • for a structural object class
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 2.25.128424792425578037463837247958458780603.1
        NAME 'dateOfBirth'
        EQUALITY caseIgnoreMatch
        SUBSTR caseIgnoreSubstringsMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
attributeTypes: ( 2.25.128424792425578037463837247958458780603.2
        NAME 'gender'
        EQUALITY caseIgnoreMatch
        SUBSTR caseIgnoreSubstringsMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )
-
add: objectClasses
objectClasses: ( 2.25.128424792425578037463837247958458780603.3
    NAME 'samplePerson'
    DESC 'samplePerson'
    SUP inetOrgPerson
    STRUCTURAL
    MAY  ( dateOfBirth $ gender)
 )
Well.. there are several properties that we have to specify when defining attributes and object classes for LDAP as you can see in the above sample ldif. You can learn about all of them in detail from the RFC here.

Here I will describe only the most important ones.
lines 4,9,16: specifies unique OIDs for each attribute and object class. Sole purpose of an OID is to avoid these attributes/object class being conflicted with any other in the LDAP instance. I describe how to obtain an OID in detail in this  post. You can use the above OID for experimental purpose or just google on 'how to obtain an OID'

line 19: specifies that 'samplePerson' object class inherits from 'inetOrgPerson' object class which usually already exists in any LDAP server.

line 20: specifies this is a structural object class so that you can use it to construct an entry in the LDAP.

Overall, we have defined a new object class called 'samplePerson' which groups two attributes called 'dateOfBirth' and 'gender' and the entries made out of this object class may contain all the attributes from 'inetOrgPerson' super class and the two attributes that we defined.
  • for an auxiliary object class
dn: cn=schema
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 2.25.128424792425578037463837247958458780603.4
        NAME 'role'
        EQUALITY caseIgnoreMatch
        SUBSTR caseIgnoreSubstringsMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )
-
add: objectClasses
objectClasses: ( 2.25.128424792425578037463837247958458780603.5
    NAME 'employedPerson'
    DESC 'empolyedPerson'
    SUP top
    AUXILIARY
    MAY  ( role )
 )
This defines an auxiliary (line 15) object class called 'employedPerson' which groups an attribute called 'role'.

How to add a custom object class to ApacheDS:
  1. Save the above two samples in two files with the extension .ldif.
  2. Start ApacheDS and ApacheDS Studio.
  3. Connect to ApacheDS through ApacheDS studio.
  4. Import two ldif files to the schema of ApacheD LDAP server through ApacheD studio's LDIF import wizard as shown in following image: right click on 'ou=schema'-->Import-->LDIF will give you the following wizard:
   5. Restart the ApacheDS and reconnect to it through ApacheDS Studio for the changes to make effect.    
  
How to use custom object classes/ attributes in user entries:
  1. Get the wizard to create a new entry: (right click 'ou=system'-->New-->NewEntry.)
  2. Search the custom object classes that we added under 'Available Object Classes' on the left hand side of the wizard. Then add both 'samplePerson' and 'employedPerson' object classes as shown in image below:
   There you can see all the inherited object classes are also listed when we added the two that we introduced.
   3. Complete adding the user entry as you usually do.
   4. Try to add attributes to the created user entry. In the attribute search list, now you will be able to see the newly introduced custom attributes.
    Well..now we have come to the end of our long blog post. Hope you got a clear understanding on how to introduce custom object classes and attributes to LDAP.
    Hmm...I'm tired writing this long post...but I hope you are not tired reading it which is my pleasure... :)

    12 comments:

    1. Really useful write up! Many thanks for sharing!

      ReplyDelete
    2. Hello,

      Really very nice article. Thanks for this.

      I have another doubt? Can we configure ApacheDS to throw error while adding user entry with duplicate email address?

      Thanks,
      -Krishna-

      ReplyDelete
    3. Thank you lots for your post. I was looking for the exact same thing you described here.
      You, kind Sir, just saved my day.

      ReplyDelete
    4. Hi Hasini,
      I am using just Apache Directory Studio Version: 1.5.3.v20100330. I think the exact same thing as you have mentioned above.. For some reason, I am NOT seeing the custom Object class. The LDIF import does not show any error ( or any positive results either ). But, still unable to use the class. Any Idea what could be going wrong?

      ReplyDelete
      Replies
      1. Typo : "I think I did the exact"

        Delete
      2. Hi,

        You might need to restart the server after importing the ldif file.

        Thanks,
        Hasini.

        Delete
    5. Extremely useful sharing, thanks in advance.

      ReplyDelete
    6. Hi, i am getting error

      org.apache.directory.shared.ldap.model.message.ModifyRequestImpl@cfeedf56: ERR_335 Oid 2.25.128424792425578037463837247958458780603.1 for new schema entity is not unique.]; remaining name 'cn=schema'

      What might the problem be?

      Thanks

      ReplyDelete
    7. Hi,
      I have 2 queries. Please can help or direct me regarding these: We are currently using WSO2 4.0 M8. We created some users and were using Apache Studio to view the Apache DS schema embedded in the WSO2 identity server
      We were able to see the user details, claims, uid etc. But, we are not able to see the gid (POSIX GID - Group id).
      And we were not able to add it as a new attribute as it is not displayed in the attribute Type list (drop down box). But the Schema Browser tab shows gidNumber attribute type in it.

      We want to view the GiDs of the users. Please do tell us a way to add gid as a new attribute so I can view the user’s Gid.

      2. Furthermore I also want to add PAM as an "ou" and use PAM for authentication and Mapping LDAP users to Linux user groups how can I go about this.
      Thanks in advance.

      ReplyDelete
    8. Hi hasini,
      I have a problem with windows active directory bind authentication, i am very glad if you help me to solve this problem, i have tried many days to solve my authentication problem. i have installed the light weight directory service and i can logging with simple bind without passwd and user name as distinguished name. and also anonymous bind and system authentication bind is successful. but i need to enter 'uid' attrib value only as user name and password. in here i need to clarify the base domain also. if u please help me, i can send you a mail.

      i checked with ldp.exe first and ADSI settings were successful as described the linked i referred below
      http://sharepoint-2010-world.blogspot.com/2011/03/installing-active-directory-lightweight.html
      http://www.novell.com/documentation/securelogin61/nsl61_installation_guide/data/b9mcutn.html
      http://www.thegeekispeak.com/archives/28
      http://docs.oracle.com/cd/E17904_01/oid.1111/e10031/odip_sync_prof_confg.htm
      http://docs.oracle.com/cd/E17904_01/oid.1111/e10031/odip_sync_prof_confg.htm#CHDCBAEE

      So hasini please guide me to do right authentication with uid and password at least through Bind dialog box in ldp.exe or show me the way or link with your experience.
      thank you so much hasini.

      ReplyDelete
    9. I get an error...

      #!RESULT ERROR
      #!CONNECTION ldap://localhost:10389
      #!DATE 2013-11-26T14:20:15.082
      #!ERROR [LDAP: error code 50 - INSUFFICIENT_ACCESS_RIGHTS: failed for MessageType : MODIFY_REQUEST Message ID : 12 Modify Request Object : 'cn=schema' Modification[0] Operation : add Modification attributeTypes: ( 2.25.128424792425578037463837247958458780603.1 NAME 'dateOfBirth' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) attributeTypes: ( 2.25.128424792425578037463837247958458780603.2 NAME 'gender' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} ) Modification[1] Operation : add Modification objectClasses: ( 2.25.128424792425578037463837247958458780603.3 NAME 'samplePerson' DESC 'samplePerson' SUP inetOrgPerson STRUCTURAL MAY ( dateOfBirth $ gender)) org.apache.directory.api.ldap.model.message.ModifyRequestImpl@ea4f9897: ERR_30 The ATTRIBUTE_TYPE ( 2.5.18.3 NAME 'creatorsName' DESC RFC2252: name of creator EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) operational attribute cannot be modified by a user]
      dn: cn=schema
      changetype: modify
      add: attributeTypes
      attributeTypes: ( 2.25.128424792425578037463837247958458780603.1 NAME
      'dateOfBirth' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstri
      ngsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
      attributeTypes: ( 2.25.128424792425578037463837247958458780603.2 NAME
      'gender' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMa
      tch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )
      -
      add: objectClasses
      objectClasses: ( 2.25.128424792425578037463837247958458780603.3 NAME 'samp
      lePerson' DESC 'samplePerson' SUP inetOrgPerson STRUCTURAL MAY ( d
      ateOfBirth $ gender))

      ReplyDelete
    10. I created the ldif file for an auxiliary object class and tried to import it using phpldapadmin and it gives me this error. How can I get rid of it?

      Error
      No DN or CONTAINER?
      PHP Debug Backtrace
      File /usr/share/phpldapadmin/lib/functions.php (444)
      Function error (a:5:{i:0;s:19:"No DN or CONTAINER?";i:1;s:4:"note"...)
      File /usr/share/phpldapadmin/lib/Template.php (341)
      Function debug_dump_backtrace (a:2:{i:0;s:19:"No DN or CONTAINER?";i:1;i:1;})
      File /usr/share/phpldapadmin/lib/import_functions.php (204)
      Function accept (a:0:{})
      File /usr/share/phpldapadmin/htdocs/import.php (54)
      Function readEntry (a:0:{})
      File /usr/share/phpldapadmin/htdocs/cmd.php (59)
      Function include (a:1:{i:0;s:41:"/usr/share/phpldapadmin/htdocs/impo...)

      ReplyDelete