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.
- Writing an object class in a ldif (ldap data interchange format) file and importing it to the LDAP schema.
- 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:
- Structural: used to create entries--an entry should have at least one structural object class. (can have more than one as well)
- 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.
- 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:
- Save the above two samples in two files with the extension .ldif.
- Start ApacheDS and ApacheDS Studio.
- Connect to ApacheDS through ApacheDS studio.
- 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:
- Get the wizard to create a new entry: (right click 'ou=system'-->New-->NewEntry.)
- 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... :)