directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Directory Server v1.5 > 6.2. Implementing a simple custom Interceptor
Date Sun, 07 Feb 2010 18:12:00 GMT
<html>
<head>
    <base href="http://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/1519/1/1/_/styles/combined.css?spaceKey=DIRxSRVx11&amp;forWysiwyg=true"
type="text/css">
    </head>
<body style="background-color: white" bgcolor="white">
<div id="pageContent">
<div id="notificationFormat">
<div class="wiki-content">
<div class="email">
     <h2><a href="http://cwiki.apache.org/confluence/display/DIRxSRVx11/6.2.+Implementing+a+simple+custom+Interceptor">6.2.
Implementing a simple custom Interceptor</a></h2>
     <h4>Page <b>edited</b> by             <a href="http://cwiki.apache.org/confluence/display/~szoerner">Stefan
Zoerner</a>
    </h4>
     
          <br/>
     <div class="notificationGreySide">
         <style type='text/css'>/*<![CDATA[*/
table.ScrollbarTable  {border: none;padding: 3px;width: 100%;padding: 3px;margin: 0px;background-color:
#f0f0f0}
table.ScrollbarTable td.ScrollbarPrevIcon {text-align: center;width: 16px;border: none;}
table.ScrollbarTable td.ScrollbarPrevName {text-align: left;border: none;}
table.ScrollbarTable td.ScrollbarParent {text-align: center;border: none;}
table.ScrollbarTable td.ScrollbarNextName {text-align: right;border: none;}
table.ScrollbarTable td.ScrollbarNextIcon {text-align: center;width: 16px;border: none;}

/*]]>*/</style><div class="Scrollbar"><table class='ScrollbarTable'><tr><td
class='ScrollbarPrevIcon'><a href="/confluence/display/DIRxSRVx11/6.1+How+to+write+a+simple+custom+partition+for+ApacheDS"><img
border='0' align='middle' src='/confluence/images/icons/back_16.gif' width='16' height='16'></a></td><td
width='33%' class='ScrollbarPrevName'><a href="/confluence/display/DIRxSRVx11/6.1+How+to+write+a+simple+custom+partition+for+ApacheDS">6.1
How to write a simple custom partition for ApacheDS</a>&nbsp;</td><td width='33%'
class='ScrollbarParent'><sup><a href="/confluence/display/DIRxSRVx11/6.+Extending+the+server"><img
border='0' align='middle' src='/confluence/images/icons/up_16.gif' width='8' height='8'></a></sup><a
href="/confluence/display/DIRxSRVx11/6.+Extending+the+server">6. Extending the server</a></td><td
width='33%' class='ScrollbarNextName'>&nbsp;</td></tr></table></div>

<h1><a name="6.2.ImplementingasimplecustomInterceptor-ImplementingasimplecustomInterceptorforApacheDS"></a>Implementing
a simple custom Interceptor for ApacheDS</h1>

<div class='panelMacro'><table class='tipMacro'><colgroup><col width='24'><col></colgroup><tr><td
valign='top'><img src="/confluence/images/icons/emoticons/check.gif" width="16" height="16"
align="absmiddle" alt="" border="0"></td><td><b>ApacheDS 1.5.5</b><br
/>This site was updated for ApacheDS 1.5.5.</td></tr></table></div>

<p>The following is for developers who plan to implement their own interceptors in order
to extend or modify the functionality of Apache Directory Server. It contains a simple example
as a starting point.</p>

<div>
<ul>
    <li><a href='#6.2.ImplementingasimplecustomInterceptor-Whatexactlyisaninterceptor%3F'>What
exactly is an interceptor?</a></li>
    <li><a href='#6.2.ImplementingasimplecustomInterceptor-Passwordhash.Asimpleinterceptor'>Password
hash. A simple interceptor</a></li>
<ul>
    <li><a href='#6.2.ImplementingasimplecustomInterceptor-Thesources'>The sources</a></li>
    <li><a href='#6.2.ImplementingasimplecustomInterceptor-ImplementingtheclassPasswordHashInterceptor'>Implementing
the class PasswordHashInterceptor</a></li>
    <li><a href='#6.2.ImplementingasimplecustomInterceptor-Usingtheinterceptor'>Using
the interceptor</a></li>
    <li><a href='#6.2.ImplementingasimplecustomInterceptor-Verification'>Verification</a></li>
    <li><a href='#6.2.ImplementingasimplecustomInterceptor-Limitationsoftheexample'>Limitations
of the example</a></li>
</ul>
    <li><a href='#6.2.ImplementingasimplecustomInterceptor-Furtherreading'>Further
reading</a></li>
</ul></div>

<h2><a name="6.2.ImplementingasimplecustomInterceptor-Whatexactlyisaninterceptor%3F"></a>What
exactly is an interceptor?</h2>

<p>An interceptor filters method calls performed on on the <em>DefaultPartitionNexus</em>
just like Servlet filters do. The ApacheDS configuration contains a chain of filters performing
several tasks. In order to illustrate this, here is the list of interceptors from the default
server configuration of ApacheDS 1.5.5</p>

<ul>
	<li>org.apache.directory.server.core.normalization.NormalizationInterceptor</li>
	<li>org.apache.directory.server.core.authn.AuthenticationInterceptor</li>
	<li>org.apache.directory.server.core.referral.ReferralInterceptor</li>
	<li>org.apache.directory.server.core.authz.AciAuthorizationInterceptor</li>
	<li>org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor</li>
	<li>org.apache.directory.server.core.exception.ExceptionInterceptor</li>
	<li>org.apache.directory.server.core.changelog.ChangeLogInterceptor</li>
	<li>org.apache.directory.server.core.operational.OperationalAttributeInterceptor</li>
	<li>org.apache.directory.server.core.schema.SchemaInterceptor</li>
	<li>org.apache.directory.server.core.subtree.SubentryInterceptor</li>
	<li>org.apache.directory.server.core.collective.CollectiveAttributeInterceptor</li>
	<li>org.apache.directory.server.core.event.EventInterceptor</li>
	<li>org.apache.directory.server.core.trigger.TriggerInterceptor</li>
	<li>org.apache.directory.server.core.journal.JournalInterceptor</li>
</ul>


<p>Interceptors should usually pass the control of current invocation to the next interceptor
by calling an appropriate method on <em>NextInterceptor</em>.  The flow control
is returned when the next  interceptor's filter method returns. You can therefore implement
pre-, post-, around- invocation handler by how you place the statement.</p>

<p>Interceptors are a powerful way to extend and modify the server behavior. But be
warned. A mistakenly written interceptor may lead to a dis-functional or corrupt server. </p>

<h2><a name="6.2.ImplementingasimplecustomInterceptor-Passwordhash.Asimpleinterceptor"></a>Password
hash. A simple interceptor</h2>

<p>In order to demonstrate how to write an interceptor, here is a simple but realistic
example. The following requirement should be fulfilled by an interceptor.</p>

<ul>
	<li>No user password should be stored in the directory in clear text.</li>
</ul>


<p>To be more concrete:</p>

<ul>
	<li>If a userpassword is set by an LDAP client in plain text, a <a href="http://en.wikipedia.org/wiki/Cryptographic_hash_function"
title="Wikipedia: Cryptographic hash function" rel="nofollow">message digest algorithm</a>
should be applied to the value, and the one-way encrypted value should be stored</li>
	<li>the algorithm should be applied if new entries are created or existing entries
are modified (hence modify and add operations will be intercepted)</li>
	<li>If the value given by the client is already provided in hashed form, nothing happens,
and the given value is stored in the directory without modification</li>
</ul>


<h3><a name="6.2.ImplementingasimplecustomInterceptor-Thesources"></a>The
sources</h3>

<p>Currently, the sources are checked in here</p>
<ul>
	<li><a href="http://svn.apache.org/repos/asf/directory/sandbox/szoerner/passwordHashInterceptor"
rel="nofollow">http://svn.apache.org/repos/asf/directory/sandbox/szoerner/passwordHashInterceptor</a></li>
</ul>


<p>In order to build it, simply check it out and type "mvn install". </p>

<h3><a name="6.2.ImplementingasimplecustomInterceptor-ImplementingtheclassPasswordHashInterceptor"></a>Implementing
the class PasswordHashInterceptor</h3>

<p>The following UML class diagram depicts the structure of the little example. Classes
in white are given by Apache Directory Server as extension points. The two gray classes comprise
the example interceptor.</p>

<p><img src="/confluence/download/attachments/102528/passwordHashInterceptor_UML.png"
align="absmiddle" border="0" /></p>

<p>The class <em>HashTools</em> contains two simple methods w.r.t. hashing.
<em>isAlreadyHashed</em> detects whether a value has already been hashed with
a known message digest algorithm. <em>applyHashAlgorithm</em> applies a hash algorithm
to a sequence of bytes. See the source code and the unit tests of this class for details,
it has not that much to do with the interceptor stuff.</p>

<p>The central class is <em>PasswordHashInterceptor</em>. Every interceptor
has to implement the <em>Interceptor</em> interface from package <em>org.apache.directory.server.core.interceptor</em>.
<em>PasswordHashInterceptor</em> does so by extended the convenience class <em>BaseInterceptor</em>
from the same package.</p>

<p>The property <em>hashAlgorithm</em> allows to configure the alhorithm
used for hashing the passwords. It defaults to <a href="http://en.wikipedia.org/wiki/MD5"
title="Wikipedia: MD5" rel="nofollow">MD5 (Message-Digest algorithm 5)</a>. The property
<em>passwordAttributeName</em> allows configuration of the attribute type which
stores the user password. Its value will be hashed if needed. The property defaults to "userPassword",
which is quite common and used for instance in the <a href="http://www.ietf.org/rfc/rfc2798.txt"
title="RFC 2798: Definition of the inetOrgPerson LDAP Object Class" rel="nofollow">inetOrgPerson</a>
object class.</p>

<p>The most interesting methods of the class are <em>add</em> and <em>modify</em>.
They intercept the requests ans modify the attribute values, if needed. See below the complete
source code of the class.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.apache.directory.samples.interceptor.pwdhash;

<span class="code-keyword">import</span> <span class="code-keyword">static</span>
org.apache.directory.samples.interceptor.pwdhash.HashTools.applyHashAlgorithm;
<span class="code-keyword">import</span> <span class="code-keyword">static</span>
org.apache.directory.samples.interceptor.pwdhash.HashTools.isAlreadyHashed;

<span class="code-keyword">import</span> java.util.List;

<span class="code-keyword">import</span> org.apache.directory.server.core.entry.ClonedServerEntry;
<span class="code-keyword">import</span> org.apache.directory.server.core.interceptor.BaseInterceptor;
<span class="code-keyword">import</span> org.apache.directory.server.core.interceptor.NextInterceptor;
<span class="code-keyword">import</span> org.apache.directory.server.core.interceptor.context.AddOperationContext;
<span class="code-keyword">import</span> org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
<span class="code-keyword">import</span> org.apache.directory.shared.ldap.entry.EntryAttribute;
<span class="code-keyword">import</span> org.apache.directory.shared.ldap.entry.Modification;
<span class="code-keyword">import</span> org.apache.directory.shared.ldap.entry.ModificationOperation;

<span class="code-keyword">public</span> class PasswordHashInterceptor <span
class="code-keyword">extends</span> BaseInterceptor {

    <span class="code-keyword">private</span> <span class="code-object">String</span>
hashAlgorithm = <span class="code-quote">"MD5"</span>;

    <span class="code-keyword">private</span> <span class="code-object">String</span>
passwordAttributeName = <span class="code-quote">"userPassword"</span>;

    <span class="code-keyword">public</span> void setHashAlgorithm(<span class="code-object">String</span>
hashAlgorithm) {
        <span class="code-keyword">this</span>.hashAlgorithm = hashAlgorithm;
    }

    <span class="code-keyword">public</span> void setPasswordAttributeName(<span
class="code-object">String</span> passwordAttributeName) {
        <span class="code-keyword">this</span>.passwordAttributeName = passwordAttributeName;
    }

    /**
     * Intercepts the add operation in order to replace plain password values
     * with hashed ones.
     */
    @Override
    <span class="code-keyword">public</span> void add(NextInterceptor next, AddOperationContext
opContext)
            <span class="code-keyword">throws</span> Exception {

        ClonedServerEntry entry = opContext.getEntry();
        EntryAttribute attribute = entry.get(passwordAttributeName);
        <span class="code-keyword">if</span> (attribute != <span class="code-keyword">null</span>)
{
            hashPasswordIfNeccessary(attribute);
        }

        <span class="code-keyword">super</span>.add(next, opContext);
    }

    /**
     * Intercepts the modify operation in order to replace plain password values
     * with hashed ones.
     */
    @Override
    <span class="code-keyword">public</span> void modify(NextInterceptor next,
ModifyOperationContext opContext)
            <span class="code-keyword">throws</span> Exception {

        List&lt;Modification&gt; items = opContext.getModItems();
        <span class="code-keyword">for</span> (Modification modification : items)
{
            ModificationOperation operation = modification.getOperation();
            <span class="code-keyword">if</span> (operation == ModificationOperation.ADD_ATTRIBUTE
                    || operation == ModificationOperation.REPLACE_ATTRIBUTE) {
                EntryAttribute attribute = modification.getAttribute();
                <span class="code-keyword">if</span> (attribute.getId().equalsIgnoreCase(passwordAttributeName))
{
                    hashPasswordIfNeccessary(attribute);
                }
            }
        }
        <span class="code-keyword">super</span>.modify(next, opContext);
    }

    <span class="code-keyword">protected</span> void hashPasswordIfNeccessary(EntryAttribute
attribute) {
        <span class="code-keyword">try</span> {
            <span class="code-object">byte</span>[] password = attribute.getBytes();
            <span class="code-keyword">if</span> (!isAlreadyHashed(password))
{
                <span class="code-object">byte</span>[] hashed = applyHashAlgorithm(hashAlgorithm,
password);
                attribute.clear();
                attribute.add(hashed);
            }
        } <span class="code-keyword">catch</span> (Exception e) {
            <span class="code-keyword">throw</span> <span class="code-keyword">new</span>
RuntimeException(<span class="code-quote">"Password hash failed"</span>, e);
        }
    }
}
</pre>
</div></div>

<h3><a name="6.2.ImplementingasimplecustomInterceptor-Usingtheinterceptor"></a>Using
the interceptor</h3>

<p>You may use a custom interceptor both in a standard ApacheDS installation and in
a server started embedded.</p>

<h4><a name="6.2.ImplementingasimplecustomInterceptor-Addingittoastandardserverinstallation%28server.xml%29"></a>Adding
it to a standard server installation (server.xml)</h4>

<p>In order to get the interceptor installed in a default installation of ApacheDS 1.5.5.,
just copy the jar-File resulting from the Maven build, which contains the custom classes,
to <em>APACHEDS_INSTALLDIR/lib/ext</em>. </p>

<p>After that, add the interceptor to the <em>server.xml</em> file in <em>APACHEDS_INSTALLDIR/conf/</em>.
Make sure to backup the file before your modifications. Within <em>server.xml</em>
find the XML elements which list the interceptors. The easiest way to add a custom interceptor
is to add a spring bean (namespace "s"). You mya set configuration properties to the interceptor
as well, if it supports some.</p>

<p>The following fragment shows the interceptor list with the example interceptor added
just behind normalization. For demonstration purposes, the hash algorithm is set to "MD5"
(which is the default of our interceptor anyway).</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-xml">
...
<span class="code-tag">&lt;interceptors&gt;</span>
  <span class="code-tag">&lt;normalizationInterceptor/&gt;</span>
  <span class="code-tag">&lt;s:bean class=<span class="code-quote">"org.apache.directory.samples.interceptor.pwdhash.PasswordHashInterceptor"</span>&gt;</span>

     <span class="code-tag">&lt;s:property name=<span class="code-quote">"hashAlgorithm"</span>
value=<span class="code-quote">"MD5"</span> /&gt;</span> 
  <span class="code-tag">&lt;/s:bean&gt;</span>
  <span class="code-tag">&lt;authenticationInterceptor/&gt;</span>
  <span class="code-tag">&lt;referralInterceptor/&gt;</span>
  <span class="code-tag">&lt;aciAuthorizationInterceptor/&gt;</span>
  <span class="code-tag">&lt;defaultAuthorizationInterceptor/&gt;</span>
  <span class="code-tag">&lt;exceptionInterceptor/&gt;</span>
  <span class="code-tag">&lt;operationalAttributeInterceptor/&gt;</span>
  ...
<span class="code-tag">&lt;/interceptors&gt;</span>
...
</pre>
</div></div>

<h4><a name="6.2.ImplementingasimplecustomInterceptor-Embeddedmode"></a>Embedded
mode</h4>

<p>As an alternative, the following Java code starts an ApacheDS embedded in a main
method. The list of interceptors is complemented with the example interceptor. We insert it
exactly behind the <em>NormalizingInterceptor</em> (the position is a little bit
tricky to determine).</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.apache.directory.samples.interceptor.pwdhash;

<span class="code-keyword">import</span> java.util.List;

<span class="code-keyword">import</span> org.apache.directory.server.core.DefaultDirectoryService;
<span class="code-keyword">import</span> org.apache.directory.server.core.DirectoryService;
<span class="code-keyword">import</span> org.apache.directory.server.core.interceptor.Interceptor;
<span class="code-keyword">import</span> org.apache.directory.server.core.normalization.NormalizationInterceptor;
<span class="code-keyword">import</span> org.apache.directory.server.ldap.LdapServer;
<span class="code-keyword">import</span> org.apache.directory.server.protocol.shared.transport.TcpTransport;

/**
 * Main class which starts an embedded server with the interceptor inserted into
 * the chain.
 */
<span class="code-keyword">public</span> class Main {

    <span class="code-keyword">public</span> <span class="code-keyword">static</span>
void main(<span class="code-object">String</span>[] args) <span class="code-keyword">throws</span>
Exception {

        DirectoryService directoryService = <span class="code-keyword">new</span>
DefaultDirectoryService();
        directoryService.setShutdownHookEnabled(<span class="code-keyword">true</span>);

        List&lt;Interceptor&gt; interceptors = directoryService.getInterceptors();

        <span class="code-comment">// Find Normalization interceptor in chain
</span>        <span class="code-object">int</span> insertionPosition =
-1;
        <span class="code-keyword">for</span> (<span class="code-object">int</span>
pos = 0; pos &lt; interceptors.size(); ++pos) {
            Interceptor interceptor = interceptors.get(pos);
            <span class="code-keyword">if</span> (interceptor <span class="code-keyword">instanceof</span>
NormalizationInterceptor) {
                insertionPosition = pos;
            }
        }

        <span class="code-comment">// insert our <span class="code-keyword">new</span>
interceptor just behind
</span>        interceptors.add(insertionPosition + 1, <span class="code-keyword">new</span>
PasswordHashInterceptor());
        directoryService.setInterceptors(interceptors);

        LdapServer ldapServer = <span class="code-keyword">new</span> LdapServer();
        ldapServer.setDirectoryService(directoryService);
        ldapServer.setAllowAnonymousAccess(<span class="code-keyword">true</span>);

        TcpTransport ldapTransport = <span class="code-keyword">new</span> TcpTransport(10389);
        ldapServer.setTransports(ldapTransport);

        directoryService.startup();
        ldapServer.start();
    }
}
</pre>
</div></div>



<h3><a name="6.2.ImplementingasimplecustomInterceptor-Verification"></a>Verification</h3>

<p>Let's check whether our new interceptor does its job! In order to do so, we use Apache
Directory Studio and connect to the server with the interceptor enabled (see above).</p>

<p>First we create a new entry with the following data, using "New Entry ..." within
Studio. </p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
dn: cn=Kate Bush,ou=users,ou=system
objectClass: person
objectClass: top
cn: Kate Bush
sn: Bush
</pre>
</div></div>

<p>Then we add a new attribute <em>userPassword</em> in the entry editor.
For the value, a special editor appears:</p>

<p><img src="/confluence/download/attachments/102528/passwordHashInterceptor_passwordEditor.png"
align="absmiddle" border="0" /></p>

<p>Select "Plaintext" as the hash method and enter a new password. We selected "secret"
(see screen shot above). After pressing OK, a modify operation is sent to the server, which
will be intercepted by our example class.</p>

<p><img src="/confluence/download/attachments/102528/passwordHashInterceptor_modificationLog.png"
align="absmiddle" border="0" /></p>

<p>After that, the value for <em>userPassword</em> is not "secret", but
the MD5 digested value of it.</p>

<p><img src="/confluence/download/attachments/102528/passwordHashInterceptor_entryEditor.png"
align="absmiddle" border="0" /></p>

<p>The user Kate Bush is still capable of authenticating with the password "secret",
because Apache Directory Server supports authentication with passwords hashed with this algorithm.
You can verify this by connecting with Studio and the using "cn=Kate Bush,ou=users,ou=system"
as bind DN.</p>

<p>Here it is demonstrated with the help of the <em>ldapsearch</em> command
line tool. The result also shows that the <em>userPassword</em> value is hashed
with MD5. </p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
$ ldapsearch -h localhost -p 10389 -D <span class="code-quote">"cn=Kate Bush,ou=users,ou=system"</span>
\\
    -w secret  -b <span class="code-quote">"ou=users,ou=system"</span> -s one
<span class="code-quote">"(objectClass=*)"</span>
version: 1
dn: cn=Kate Bush,ou=users,ou=system
objectClass: person
objectClass: top
cn: Kate Bush
sn: Bush
userPassword: {MD5}Xr4ilOzQ4PCOq3aQ0qbuaQ==
$
</pre>
</div></div>

<h3><a name="6.2.ImplementingasimplecustomInterceptor-Limitationsoftheexample"></a>Limitations
of the example</h3>

<p>This example is intended as a demonstration, on how to write your custom interceptor.
Don't consider it bullet proof. It has not been tested under production conditions, etc.</p>

<p>At least the following limitation should be mentioned</p>
<ul>
	<li>The default hash algorithm MD5 is considered weak.</li>
	<li>Exception handling is poor. E.g. if someone configures an unsupported hash algorithm,
the interceptor fails to create an appropriate LDAP error.</li>
	<li>If a multivalued password attribute is used, the interceptor will simply ignore
that fact (does not apply to userPassword as of RFC 2256).</li>
</ul>



<h2><a name="6.2.ImplementingasimplecustomInterceptor-Furtherreading"></a>Further
reading</h2>

<p>Learn more about interceptors in <a href="/confluence/display/DIRxSRVx11/1.2.+Interceptors"
title="1.2. Interceptors">ApacheDS Architecture Documentation</a>, check out the
source code of some implementations of the <em>Interceptor</em> interface, and/or
read the javadoc comments.</p>
     </div>
     <div id="commentsSection" class="wiki-content pageSection">
       <div style="float: right;">
            <a href="http://cwiki.apache.org/confluence/users/viewnotifications.action"
class="grey">Change Notification Preferences</a>
       </div>

       <a href="http://cwiki.apache.org/confluence/display/DIRxSRVx11/6.2.+Implementing+a+simple+custom+Interceptor">View
Online</a>
       |
       <a href="http://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=102528&revisedVersion=5&originalVersion=4">View
Change</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message