directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Directory SandBox > Implementing a simple interceptor
Date Sun, 07 Feb 2010 09:17:00 GMT
<html>
<head>
    <base href="http://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/1519/1/5/_/styles/combined.css?spaceKey=DIRxSBOX&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/DIRxSBOX/Implementing+a+simple+interceptor">Implementing
a simple 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">
         <div class='panelMacro'><table class='noteMacro'><colgroup><col
width='24'><col></colgroup><tr><td valign='top'><img src="/confluence/images/icons/emoticons/warning.gif"
width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b>Be
Careful</b><br /><p>Work in progress. Any feedback highly appreciated!</p></td></tr></table></div>

<h1><a name="Implementingasimpleinterceptor-Implementingasimpleinterceptor"></a>Implementing
a simple interceptor</h1>

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

<h2><a name="Implementingasimpleinterceptor-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="Implementingasimpleinterceptor-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="Implementingasimpleinterceptor-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="Implementingasimpleinterceptor-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/13271374/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>

<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> java.util.Set;

<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">import</span> org.apache.directory.shared.ldap.schema.AttributeType;

<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>
passwordAttributeName = <span class="code-quote">"userPassword"</span>;

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

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

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

    /**
     * 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)
{
            <span class="code-keyword">if</span> (modification.getOperation()
== ModificationOperation.ADD_ATTRIBUTE
                    || modification.getOperation() == 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);
    }

    /**
     * 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();
        Set&lt;AttributeType&gt; attributeTypes = entry.getAttributeTypes();
        <span class="code-keyword">for</span> (AttributeType attributeType : attributeTypes)
{
            <span class="code-keyword">if</span> (attributeType.getName().equalsIgnoreCase(passwordAttributeName))
{
                EntryAttribute attribute = entry.get(attributeType);
                hashPasswordIfNeccessary(attribute);
            }
        }

        <span class="code-keyword">super</span>.add(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="Implementingasimpleinterceptor-Usingtheinterceptor"></a>Using
the interceptor</h3>

<h4><a name="Implementingasimpleinterceptor-Embeddedmode"></a>Embedded mode</h4>


<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.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 added to 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>);

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

        List&lt;Interceptor&gt; is = directoryService.getInterceptors();
        is.add(<span class="code-keyword">new</span> PasswordHashInterceptor());
        directoryService.setInterceptors(is);

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

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

<h4><a name="Implementingasimpleinterceptor-Addingittoaserver.xmlfile"></a>Adding
it to a server.xml file</h4>

<p>TBD.</p>

<h3><a name="Implementingasimpleinterceptor-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 </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/13271374/passwordHashInterceptor_passwordEditor.png"
align="absmiddle" border="0" /></p>

<p>select Plaintext as the hash method and enter a new password. We selected "secret"
(see 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/13271374/passwordHashInterceptor_modificationLog.png"
align="absmiddle" border="0" /></p>

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

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

<p>Kate Bush is still capable of authenticating with the password "secret", because
Apache Directory Server supports storing passwords hashed with this algorithm.</p>


<h3><a name="Implementingasimpleinterceptor-Limitationsoftheexample"></a>Limitations
of the example</h3>

<p>TBD.</p>

<h2><a name="Implementingasimpleinterceptor-Furtherreading"></a>Further
reading</h2>
     </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/DIRxSBOX/Implementing+a+simple+interceptor">View
Online</a>
       |
       <a href="http://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=13271374&revisedVersion=22&originalVersion=21">View
Change</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message