Skip to main content

LDAP server with your schema on your laptop


To choose is to live...

Jacques Brel said "choisir c'est perdre": to choose is to loose. There would indeed be no choice to make if an option was definitely superior to the others. If you choose you will have to balance pros and cons and you will then have to loose.
My great colleague, Achilleas Voutsas, reacted on less than a split second on that one. He straight away answered that: "To choose is to LIVE". He is right: if you have no choice, it is monotone. Choice is creativity. Choice is life.

Going back to the theme. In 2005 Romain Eude and Gonzague Huet devised a solution allowing one to run an OpenLDAP server on a windows PC. That was brilliant and it helped tremendously the development of an LDAP based system. I could run an entire system on my laptop: database, application sever and OpenLDAP with 20000 entries.

If you run Linux, OpenLDAP is no brainer choice to use. On Windows, the setup from that we used in 2005 is very easy but was not kept up-to-date. The problem is not with the LDAP functionility but with the core ldap schemas that are pre-installed. They are present but are missing attributes or syntaxes here and there that have been included in the core LDAP schemas.

With years passing, there is a new product that may help. The Apache foundation has developed tooling around LDAP in java: the Apache Directory Project.
It proposes an LDAP server: ApacheDS and an Eclipse based tool for administrating LDAP servers: Apache Directory Studio.

The intent of that post is to look at how a custom schema can be added to ApacheDS. For completeness, we will also cover quickly the installation on Windows. At the time of writing this blog, intermediary version 2.0 milestones are already out but the version 2.0 documentation does not yet cover the aspect of installing a custom schema. Therefore the version used in this article will be 1.5.7 . 

Installation

Installing with the installer (setup.exe) you are prompted to choose a folder for the server: <apacheds> and another folder for the instances: <instances>, and your jdk home directory <java_home>. It is done!

Uninstalling on Windows requires uninstalling the windows service manually. See http://techie-buzz.com/how-to/delete-services-in-windows.html .

For me, the service name is:
apacheds-default

I then execute the command
sc delete apacheds-default


Example setup

We are creating a directory context based on a fictitious company URL: acme.com . Its distinguish name will follow the URL pattern:
dc=acme,dc=com

ApacheDS requires the new context to be defined with an LDIF file. The acme-context.ldif LDIF file defines the root element for the new context:
dn: dc=acme,dc=com
dc: acme
objectClass: top
objectClass: dcObject
objectClass: domain
description: The context entry for suffix dc=acme,dc=com

Some ldap servers create systematically sub-entries for their data under your context. Because we don't want to mix our data with server's entries, we will create a root node below the new defined context. We call this node the "ACME Shared Directory", asd in short. The LDIF defining this entry is:

dn: dc=asd,dc=acme,dc=com
dc: asd
objectClass: top
objectClass: dcObject
objectClass: domain
description: Root node for all ACME Shared Directory entries

The data will store in our ACME Shared Directory (ASD in the rest of the article) will users and organisations. We create
sub nodes under our root for those two kinds of records:

asd
|
+- users
|
+- orgs

The new context is defined below in LDIF format. Save following in a file named acme_context.ldif:
dn: dc=asd,dc=acme,dc=com
dc: asd
objectClass: top
objectClass: dcObject
objectClass: domain
description: Root node for all ACME Shared Directory entries

dn: dc=users,dc=asd,dc=acme,dc=com
dc: users
objectClass: top
objectClass: dcObject
objectClass: domain
description: All ACME Shared Directory user entries

dn: dc=orgs,dc=asd,dc=acme,dc=com
dc: orgs
objectClass: top
objectClass: dcObject
objectClass: domain
description: All ACME Shared Directory organisations
Be careful, LDIF is syntax is strict about spacing. In this case the file must end with an empty line.


To make this example a bit more meaningful, we will consider that users can be sponsored by an organisation. We will store for users a link to the sponsoring organisation. This link could be achieved by storing users under their sponsoring organisation but let's assume that sponsors might not be known at the time of the user creation.
For organisations, we want to be able to record the short name if it exists.

We then define a schema for the ASD LDAP. We will create:
  • New attribute types: asdSponsor and asdAcronym
  • an object class asdUser that defines what attributes such a record may have
  • an object class asdOrg that defines what attributes such a record may have

Each new element should be recorded with a valid OID (a number with dots that identifies any LDAP element worldwide).
For this example we will consider that ACME holds the number 1.2.3.4.5.6.7.8.9.999 . We then allocate 1.2.3.4.5.6.7.8.9.999.1 for all new definitions introduced for ASD.

Here is the schema for the ASD directory that we will store in a asd.schema file:
attributetype ( 1.2.3.4.5.6.7.8.9.999.1.2.1 NAME 'asdSponsor'
 DESC 'Organisation that sponsored that entry'
 SUP distinguishedName )

attributetype ( 1.2.3.4.5.6.7.8.9.999.1.2.2 NAME 'asdAcronym'
 DESC 'Acronym'
 EQUALITY caseIgnoreMatch
 ORDERING caseIgnoreOrderingMatch
 SUBSTR caseIgnoreSubstringsMatch
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )

objectclass ( 1.2.3.4.5.6.7.8.9.999.1.1.1 NAME 'asdUser'
 DESC 'User in ASD directory'
 SUP top AUXILIARY
 MUST displayName
 MAY ( c $ asdSponsor $ uid $ userPassword ) )

objectclass ( 1.2.3.4.5.6.7.8.9.999.1.1.2 NAME 'asdOrg'
 DESC 'Organisation is ASD directory'
 SUP top AUXILIARY
 MUST displayName
 MAY ( uniqueIdentifier $ c $ mail $ asdAcronym ) )


Adding custom root context

Configuring a special suffix:
1) Edit
<apacheds>\instances\default\conf\server.xml

and add in <partitions> the partition definition:

<jdbmPartition id="acme" suffix="dc=acme,dc=com" />

2) Restart server: restart service.


3) Add context for root element using the acme_context.ldif defined earlier.
Using Apache DS, right click on DIT and go to Import/LDIF Import, browse to the acme_context.ldif file and click on Finish.

4) the new partition should now be showing under Root DSE.

Adding a custom schema defined with open ldap syntax

5) Open apache directory studio

6) Open schema editor perspective

7) Create new schema project
name: acmeSchema
type: Offline Schema

8) When asked to choose 'core' schemas to include, select only 'system'

9) Add other 'core' schemas, only one at a time and in order: core, cosine, inetOrgPerson. To do so, righ click on the Schema tree-view in the left panel of Apache Studio. Go to 'Import .../Core schemas files'.

10) At last add ASD schema definition. To do so, right click on the Schema tree view in the left panel of Apache Studio. Go to 'Import .../Schemas from OpenLDAP files' the asd.schema file.

You should now see a node for the asd schema on the Schema tree-view. Trop cool!!!



11) Next export the ASD schema in an ApacheDS format. To do so, right click on the asd node on the Schema tree-view.
Go for 'Export ... / Schemas for Apache DS', select the asd schema and a folder for export. This will generate the asd.ldif file that contains the schema definition in an ApacheDS format.

12) You can go back to the LDAP perspective in the Apache studio. Select the acme context node and Import the LDIF asd.ldif (Import / LDIF Import...). To validate that the operation is successful, open the node ou=schema and you should see below an entry for cn=asd . You can verify that the schema was imported correctly by importing the following LDIF file:
dn: uid=testuser,dc=users,dc=asd,dc=acme,dc=com
uid: testuser
objectClass: top
objectClass: inetOrgPerson
objectClass: asdUser
displayName: Test user to verify the import of the ASD schema
sn: Test
cn: Test user
userPassword: secret

Viewing the entry after the import, shows what you imported with your custom schema. Isn't that beautiful?


Configuring size limit with ApacheDS

Changing the default maximum number of search results allowed has no relation with defining a schema. I include it here because it is a useful basic configuration.
Please note that this maximum does not apply to the SUPER user for which the search results are unlimited.

It consists of setting maxSizeLimit in <apacheds>\instances\default\conf\server.xml to the desired value:
  <ldapServer id="ldapServer"
            allowAnonymousAccess="false"
            saslHost="ldap.example.com"
            saslPrincipal="ldap/ldap.example.com@EXAMPLE.COM"
            searchBaseDn="ou=users,ou=system"
            maxTimeLimit="15000"
            maxSizeLimit="1000">

Comments

Popular posts from this blog

Websites about using JPA 2.0 with Google app engine

Maven archetype to generate project (found by googling maven jpa 2 google app engine Archetype ): http://webapplicationdeveloper.blogspot.co.uk/2012/07/google-datastore-with-jpa-and-spring.html For Maven and Google App Engine, see also: http://code.google.com/p/maven-gae-plugin/ Google App engine documentation about JPA 2 support: https://developers.google.com/appengine/docs/java/datastore/jpa/overview-dn2 Google App engine documentation about the java datastore: https://developers.google.com/appengine/docs/java/gettingstarted/usingdatastore Another cool article but not to JPA or google app engine: https://code.google.com/p/guava-libraries/wiki/CachesExplained  

Recursivity and Observables: yes it is possible

When writing a loop using a paging API to fetch data for an Angular app, I felt so frustrated  that I was very close to going back to Promises. I came across lot of questions on the web but no convincing answers: some even proposing to subscribe to an Observable within the service implementation. However, I did not abandon  Observables to return using Promises thanks to this very informative comparison . From there I picked an  excellent article on reactive programming  with RxJava which gave me hope. So I kept on trying... Achieving both reactivity and recursivity without understanding the fundamentals of RxJs operators, was a struggle. After focussing on the use of the map and mergeMap operators, I managed a first implementation very similar to how I would write a solution to the same problem using Promises. To help me and others who have the same questions, I created a repository of angular code snippets . One is about  RxJs Recursive Observable . Expand operator to the