Internal operations in OpenDS

If you're interested in writing an extension to OpenDS (like a plugin, password validator, identity mapper, virtual attribute provider, etc.), then there's a decent chance that you'll want to be able to search for or make changes to content in the directory as part of your processing. And if you intend on using OpenDS in an embedded manner, then it's almost certainly going to be a requirement for you. One option could be to simply establish a connection to the LDAP listener and issue a request, but that's inefficient and can also have undesirable side effects (e.g., if your plugin creates a new LDAP operation, then the server will try to invoke plugins on that operation, which can create a kind of infinite recursion loop). A better alternative, however, is to perform internal operations within the server. Performing an internal operation is more efficient than creating a new external operation, and internal operations can do things that aren't allowed for external operations (e.g., update attributes marked as NO-USER-MODIFICATION) and they are also marked as internal operations so that it's possible to do things like skip plugin processing for them.

The Internal Client Connection Object

For quite a while now, OpenDS has provided the org.opends.server.protocols.internal.InternalClientConnection class, which offers a relatively simple way to perform internal operations in the server. To use it, you first need to obtain an internal client connection, which you can do in one of the following ways:
  • If you don't need to worry about enforcing access control for the internal operation, you can use the InternalClientConnection.getRootConnection() method. This will give you a connection established as an internal user with root privileges (bypass-acl, modify-acl, config-read, config-write, ldif-import, ldif-export, backend-backup, backend-restore, server-shutdown, server-restart, disconnect-client, cancel-request, password-reset, privilege-change, and unindexed-search) that can do just about anything in the server.

  • If you do want access control enforced for the internal operation (e.g., only do this operation if the authenticated user has the right to do it), then you will want to get an internal client connection as that user. To do that, you can create a new internal client connection with either the InternalClientConnection(DN) or InternalClientConnection(AuthenticationInfo) constructor.

Once you have a handle to the internal client connection, then you can use it to perform internal operations in the server. For example, to perform a simple search, you can use something like:
InternalSearchOperation searchOperation =
     internalClientConnection.processSearch("dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
for (SearchResultEntry matchingEntry : searchOperation.getSearchEntries())
  // Do something with the entry.

We've added lots of convenience methods in the InternalClientConnection class to help make performing internal operations easy, and we hope to have official documentation on this in the near future. For now, however, you can look at the Javadoc from our latest daily build to see what's available.

The Internal LDAP Socket

The internal operations API referenced above should be easy to use, and should allow you to do pretty much anything that you need. I would expect that someone who has ever done any programming involving an existing LDAP SDK (e.g., JDNI or the Mozilla LDAP SDK for Java) should find it especially easy to pick up. However, last week I was talking with some people who were interested in embedding OpenDS and had a bit of a dilemma because they wanted to be able to embed OpenDS and communicate efficiently with it, but they also wanted to have the ability for their application to communicate with external directory servers, and they wanted to avoid having to write all of their client code twice (once using the OpenDS internal API, and another time using the Mozilla LDAP SDK for Java which is their API of choice for external LDAP communication). I gave this a little thought and came up with a solution that I think is a rather elegant approach to the problem: I created a custom socket implementation that is able to convert LDAP requests into internal operations, process the operation, and then convert the response back into LDAP.

This code is implemented in the org.opends.server.protocols.internal.InternalLDAPSocket class, which extends the superclass. Going along with this socket (and doing all the real work behind the scenes) are the InternalLDAPInputStream and InternaLDAPOutputStream classes. As long as the LDAP SDK that you are using supports the use of a custom socket factory, you can use this custom socket implementation to allow you to use that LDAP SDK to perform internal operations. Both JNDI and the Mozilla LDAP SDK for Java support this, and I've tested my implementation with both of them.

For JNDI, if you want to use this custom socket implementation, the only thing that you have to do out of the ordinary is to make sure that your environment properties have the "java.naming.ldap.factory.socket" property set to "org.opends.server.protocols.internal.InternalLDAPSocketFactory". For example:
Hashtable env = new Hashtable();
env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
env.put(Context.SECURITY_CREDENTIALS, "password");

DirContext context = new InitialDirContext(env);
// Do whatever you want with the connection here

For the Mozilla LDAP SDK for Java, there's just a tiny bit more that you have to do, because it uses its own socket factory implementation (netscape.ldap.LDAPSocketFactory) instead of the one provided by the JDK (, although to be fair that class didn't exist when the Mozilla SDK was written). That interface requires a single makeSocket method, which you can implement as follows:
public Socket makeSocket(String host, int port)
  return new InternalLDAPSocket();

When you're creating a connection with the Mozilla LDAP SDK for Java, then all you need to do is to use the LDAPConnection(LDAPSocketFactory) constructor to have it use that socket factory for its communication. As an example, I have written a simple EmbeddedMozillaLDAPSDKTest program that can be used to start an embedded OpenDS instance, perform some internal operations using the Mozilla LDAP SDK for Java, and shut down the server. In order to create an appropriate environment for this test, all I needed to do was:
  1. Create an empty directory to use as the server root.
  2. Copy the config directory structure from an OpenDS installation into the new server root.
  3. Create a lib directory below the new server root and copy all of the lib/\*.jar files from an OpenDS installation into it.
  4. Create empty db, logs, and locks directories below the new server root
Then, when I compiled and ran the program, it started the server, interacted with it using the Mozilla SDK, and shut it down again. Note that I could have trimmed things down even more if I wanted to put more effort into it (e.g., we don't need everything under the config directory, and if we disable loggers then we wouldn't have needed the logs directory, etc.), but I wanted to keep this example simple.

Limitations of the Internal LDAP Socket Implementation

In general, if you're using the internal LDAP socket implementation you can do pretty much anything that you could do if you were talking to OpenDS using actual LDAP network communication. However, there are a few important things to note about this implementation:
  • You can only use clear-text communication when interacting with the Directory Server. This implementation doesn't support the use of SSL or StartTLS to secure the communication, but that really shouldn't be a problem since there's no actual communication and the operations never leave the JVM.

  • This implementation does not support the use of SASL authentication, so only simple binds are allowed. Although technically it would have worked with some mechanisms (e.g., ANONYMOUS, CRAM-MD5, DIGEST-MD5, and PLAIN), others (in particular, EXTERNAL and GSSAPI) would not have worked. Again, given that all the communication is purely internal to the JVM, I really didn't see a need to use anything other than simple authentication, so that's all that is exposed.

  • Abandon operations don't do anything. OpenDS doesn't provide a mechanism for abandoning internal operations, so any abandon operation requested in this manner will simply be ignored. Although I haven't tested it, I would expect that attempts to use the LDAP cancel extended operation would probably be rejected with a "cannot cancel" result.


Post a Comment:
Comments are closed for this entry.



Top Tags
« June 2016