You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 5 Next »

Work in progress

This site is in the process of being reviewed and updated.

What is it?

The mechanism is a means for injecting and isolating orthogonal services into calls against the nexus. The nexus is the hub used to route calls to partitions to perform CRUD operations upon entries. By injecting these services at this level, partition implementators need not duplicate fuctionality. Services such as authentication, authorization, schema checking, normalization, operational attribute maintenance and more are introduced using Interceptors. By using interceptors, partition implementors need not be concerned with these aspects and can focus on raw CRUD operations against their backing stores what ever they may be.

How does it work?

Before we talk more about interceptors we must quickly cover the JNDI provider implementation since it is somewhat related.

JNDI Implementation

The JNDI implementation is composed of a set of JNDI Context implementations, a ContextFactory implementation and a set of helper classes.

  • DeadContext
  • JavaLdapSupport
  • ServerContext
  • ServerDirContext
  • ServerLdapContext
  • AbstractContextFactory
  • CoreContextFactory
  • ServerDirObjectFactory
  • ServerDirStateFactory

Every JNDI Context implementation in the provider holds a dedicated reference to a nexus proxy object. This proxy contains all the operations that the nexus contains. The proxy object is at the heart of the mechanism. We will disuss it more after covering the rest of the JNDI provider.

Calls made against JNDI Contexts take relative names as arguments. These names are relative to the distinguished name of the JNDI Context. Within the Context implementations these relative names are transformed into absolute distinguished names. The transformed names are used to make calls against the proxy.

Additional processing may occur before or after a call is made by a context on its proxy to manage JNDI provider specific functions. One such example is the handling of Java objects for serialization and the use of object and state factories.

The nexus proxy object

As mentioned above, each Context that is created has a nexus proxy. The proxy maintains a handle on the context as well.

The primary job of the proxy is to inject Interceptor based services. It does so by invoking a chain of Interceptors managed by the system. Interceptors mirror the methods that are intercepted on the nexus interface. When an intercepted method is invoked on the proxy, the proxy pushes an Invocation object on to the InvocationStack associated with the current executing Thread. The proxy then calls the same method on a chain of Interceptors. The results of the call are returned after the InvocationStack is popped.

The InvocationStack is used to track the calls being intercepted. Invocation objects pushed onto the stack track the context making the call to the proxy, the name of the intercepted call and its arguments. A stack is used because in the case of Triggers, stored procedures may be invoked which operate against the DIT using JNDI. These JNDI calls will also be intercepted. Their Invocation object will be stacked on top of the Invocation which raised the Trigger. This way identities and context of operations can be tracked and used by the Trigger management system to prevent runnaway cascades or to limit the cascade depth. There are other areas besides just triggers where this stack will serve a purpose.

The InterceptorChain is a container of Interceptors which has the same or analogous methods as do Interceptors. These are for the interceptable methods. A call against the chain invokes the first Interceptor which then usually invokes the next interceptor in the chain. An Interceptor need not call the next interceptor however. It can raise an exception before making the call to the next interceptor or it can completely bypass the rest of the chain by just returning before calling the next Interceptor. Interceptors can preprocess the arguments, or perform other tasks before they invoke the next Interceptor. They can also catch exceptions raised by other downstream interceptors and respond to them to handle errors. Finally they can perform post processing operations on the results of returned values from downstream Interceptors.

One might ask when is the call made against the actual nexus. This happens using a special Interceptor which resides at the end of the chain. It actually makes the call against the nexus and returns the results.

Interceptors can be seen as Servlet Filters : they can be added, removed, bypassed either by configuration or, for embeded servers, on the fly. 

 

The folllowing picture describe the Interceptors mechanisms : 

 

Warning

This page needs to be overworked

Operation handling within interceptors

Each operation is associated with a method in each interceptors, even if it does nothing else than calling the next interceptor.

The base idea is to allow pre and post actions to be executed before and after the call of the next interceptors :

Each interceptor process the pre action, call the next interceptor, wait for the response, execute the post action, and returns. We have to implement this chain of interceptors in a way which allows us to add new interceptors, or new pre or post actions, without having to modify the existing code or mechanism. 

 

Bind Operation

The Bind  operation call the interceptor chain in the PartitionNexusProxy class, where we can found a bind method :

public void bind( LdapDN bindDn, byte[] credentials, List mechanisms, String saslAuthId, Collection bypass ) throws NamingException
    {    ...
            this.configuration.getInterceptorChain().bind( bindDn, credentials, mechanisms, saslAuthId );    ...

this will call the first configured interceptor from a chain which is declared in the configuration file server.xml. The first interceptor is the NormalizationService.

The information which are passed are :

  • The DN used to bind
  • The password (credentials)
  • The list of supported mechanisms 
  • The SASL authent
    We will often use only the two first elements.

Normalization interceptor

This interceptor will just normalize the DN used to bind. If the DN is invalid, an exception will be thrown.

It is the first interceptor in the chain because as we will manupulate the DN through all interceptors, it is important that we normalize it as soon as possible.

The normalized DN will be stored in an special form, usefull for internal comparizons. This operation can be costly, but as the DN has already been parsed, this is quite efficient.

We can call the next interceptor :

Authentication interceptor

We must check that this bind request is valid, that is the DN and the associated password are known by the server. We have two cases :

  1. The user have already been authenticated
  2. This is the first time this user try to bind

 What we call user is the DN of a known entry stored in the server.

In the first case, we will have to search the password in the backend, and this will be a lookup operation, which will be applied through another chain of interceptors.

Let's assume we are in the second case, because if we are in the first case, we will have to ask the backend about the entry which DN is equal to the one we received, to get its associated password, thus callaing a specific chain of interceptors (FINAL_INTERCEPTOR).

The password is compared using the given mechanism (which should be simple on a new server), and if it matches, we create a principal object which will be stored in the connection context for future usage.

We are done with the bind operation.

 Add operation

An add operation is more complex. What we need to do is to check if the current user has enough right to add an entry, and that the entry can be added.

A new entry is a composition of three elements :

  • A partition name
  • A path from this partition
  • An entry name

For instance, when adding an entry which DN is cn=acme, ou=users, ou=system , we will have :

  • Partition = "ou=system"
  • Path = "ou=users, ou=system"
  • Entry name = "cn=acme"
    The two first elements must exist in the base. We can't add an entry in an not existing partition, and we can't add an entry which path is not existing.
  • No labels