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 Sat, 06 Feb 2010 20:27: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>
</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 passwords should be stored in clear text in the directory</li>
	<li>If a userpassword is set by an LDAP client in clear text, a message digest algorithm
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</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.</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 todo 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>Afterwards 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 cabable 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>
     </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=19&originalVersion=18">View
Change</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message