Even in a not so big company there comes the time when you want to consolidate all your user databases into a central user directory. Be it a Microsoft Active Directory or an ordinary LDAP server, you need to have a place where you can manage your users. As a Liferay partner and integrator your first thought is to use Liferay for that. Liferay is extensible, hooks are pretty powerful aren't they? It should be a breeze to patch here and there if you happen to need some specific functionality. But why would you need it anyways? Liferay has LDAP import/export support built in. It's configurable. You just have to make sure to configure it correctly.
Yeah, that cake is a lie.
Let's see a not so ambitious LDAP setup and the bumps you'll experience when you take Liferay for a ride on that road.
The problem starts when you want to have an LDAP scheme that involves object classes with mandatory attributes such as posixAccount. You might need that for any number of reasons, one being an LDAP integrated SAMBA server. You may even have custom object classes. You want Liferay to create new users in that directory, so you enable export. For the sake of a complete picture let's suppose there are some legacy systems that want to write information back to the LDAP server, which of course you want to be reflected in Liferay as well, so you enable import as well.
The configuration is not too hard, you add the mapping for the user properties in the portal-ext.properties file and off you go. Or not.
PROBLEM 1: user mapping is a one-to-one mapping between user attributes or expando attributes and LDAP attributes
What if there are attributes in LDAP (for different systems that are not very configurable) that map to same user attribute. Let's see one example. The attribute displayName that is part of multiple object classes (inetOrgPerson, SambaSamAccount), though not mandatory, usually contains the same data as cn. The starightforward thing would be to map both to the user's full name. Not so fast though! You can map either the one or the other, according to Liferay's configration scheme. Well not having displayName filled might not be the end of the world, but still a bummer.
PROBLEM 2: Liferay has a two phase write to LDAP
Previously I already mentioned that the problems start with extra mandatory attributes. Let's see. The object class posixAccount has uidNumber and gidNUmber both as mandatory attributes. The Liferay way to create a user in the directory is first to create a mapping solely based on the main user attributes, i.e. the ones in 'ldap.user.mappings'. These are directly mapped to properties in UserModel. However that doesn't really work because the LDAP server will not allow Liferay to create an entry with empty mandatory attributes. That's a bummer. An even bigger bummer than the previous one. You could of course in most directory servers turn off schema checking. But in a production environment. Would you really want to?
Setting up expando fields won't help, because they would be written after the first phase, which never completes. Fixing Liferay's LDAP export classes just doesn't work because we cannot and we're not supposed to create patches (fix packs) for ourselves. Not sure I know why because at Liferay support, the support engineers still reproduce the issues so even if you hack the patchinfo file you need to provide, if the problem is caused by your modificiations they won't be able to reproduce it anyways. Oh, I never mentioned but I'm talking about the Enterprise version of course. For CE, you can do whatever you want, but the more changes you make, the harder it will be to follow up with official Liferay, and let's be honest, CE might not be the best version for some clients in production.
"SOLUTION" 1-2: Hook it real good
If you have the balls, and your scenario ends with just trying to export users to LDAP, you might be lucky. It is possible to rewrite all the LDAP export functionality and hook it up with your own user model listener. Of course you have to make sure you use very different properties or you remove the original listener in you portal-ext.properties and hook it up in your plugin, otherwise both the built-in version and yours will be run making your life very miserable.
This way there is an other part of Liferay you cannot use even though it's there, but at least you'll be able to customize this functionality however you want. You can even solve PROBLEM 1 if you need to.
...to be continued…
* the cake is a lie
Liferay extensibility - the cake is a lie - Part #2