PROBLEM 3: Liferay is greedy
Let's go forward and suppose we want Liferay to import users as well (or rather update in case someone made changes to the LDAP version of the user). At first, the import functionality seems all right. It creates the user, groups as well if you want. Of course you don't have too much control over groups. Liferay has various group mechanics that you might want to have in your LDAP: user groups, organizations, teams. Then there is roles. If you really want to use LDAP to control other systems, you might want to have your roles mapped in LDAP. Since LDAP is not very fancy you will probably do that as a groupOfNames (or something similar) with a specific name or some specific attribute using an extensibleObject class. Because of this, you would need some way to tell Liferay which groups are organizations and which ones are user groups or even roles. This is also a problem for the export functionality but if you're going with your own solution, you can implement something for this too.
The problem with import starts when you don't want your export code to run during import. That makes sense, doesn't it? Well it makes so much sense that it is actually implemented in Liferay's own LDAP export and supported by the import process via a thread local. Liferay imports (or updates) users from LDAP during the authentication process. LDAPAuth, the authenticator in the pre auth pipeline (auth.pipeline.pre) is responsible for setting that thread local before importing or updating the user from LDAP.
The first problem you will bump into is that the thread local Liferay uses (LDAPUserTransactionThreadLocal) is not in the kernel. That's it. You tought yourself to be so clever and wanted to use the same mechanism Liferay uses, but you can't. Hands off. That's Liferay's precious.
PROBLEM 4: you can only 'add' to pipelines and other chains
The ways things are implemented in Liferay, you are able to extend process chains such as the auth pipeline, but only by adding your implementation to end of the line. You can be greedy and be the only one in the chain by overriding the original settings in portal-ext.properties with an empty setting and then registering your implementation in your plugin. However in many cases you want to be the first in the chain, but you also want the original implementations to run as well. You can't do it. Bummer.
The problem here is class loading. If you specify the whole chain in your plugin, your plugin's class loader won't know how to load 'com.liferay.portal.security.auth.LDAPAuth' that is again not in the kernel. If you insert your implementation into portal-ext.properties, the portal class loader will not be able instantiate your class especially in the beginning when your plugin is not even deployed. Not to mention when you redeploy your plugin.
"SOLUTION" 3-4: Rewrite the whole thing. Really?
At the time of writing this, I'm not sure if there is a better solution to this than to rewrite LDAP import as well. The icing on the cake (that is a lie) is that you need to rewrite LDAPAuth too because that is where you have to set that ominous thread local.
CONCLUSION: the cake is sometimes a lie
By using a platform you might think, 'it's great, there are lots of things the platform will do for me'. Many times that's a mirage and when you get closer you realize you're better off going back to square one and doing it yourself. The problem with this is that you wanted it to be true to save time in the first place. Now you threw some out of the window just to realize you'll have to spend some more to make it yourself.
When the cake turns out to be a lie, it hurts. Badly.
Liferay extensibility - the cake is a lie - Part #1