diaries, triumphs, failures, and rants

pageicon Thursday Apr 23, 2009

Implementing a custom LDAP server validator in Struts for SLAMD

Struts2 provides a validation framework that is used to validate parameters from form submission. The validation framework is comprised of an interceptor called the validation interceptor, and a set of powerful bundled validators.

Validators - which must implement the Validator interface - are executed as a request travels through the interceptor stack. Validators are configured:

  • Annotations in Action classes
  • Configured in a file called [actionclassname]-validation.xml
  • via the validate() method
Any or all of the above methods can be used simultaneously.

The bundled validators include validators to ensure that a string is populated, that an integer is in a certain range, that a URL is a valid URL, that an email address is in a valid format, and so on.

In some web applications, the bundled validators are not sufficient. In these cases, it is easy to write a custom validator. In the case of SLAMD3, a validator was needed to verify that a Directory Server configuration is correct before proceeding with the execute portion of an Action. Here is how to write a Directory Server validator.

This Directory Server Connection validator is primitive in that it does not support SSL, but it is functional in the sense that it checks that parameters entered on the accessConfiguration.jsp page can be used to connect to a directory server.

accessConfiguration.jsp

The following section shows how an input text field is coded. This section presents a text field for the hostname/IP address of the directory server:


<%-- user directory host --%>
<div class="user_directory_host_entry textfield">
  <h2><s:text name="access_control.user_directory_host.label"/></h2>
		
  <div class="description">
    <s:text name="access_control.user_directory_host.description"/>
  </div>

  <s:textfield name="userDirectoryHost"
	       value="%{configuration.getString('user_directory_host')}"
	       required="false"
    	       labelposition="left"
	       size="60"
	       label="%{getText('access_control.user_directory_host.label')}"/>

</div>

This code has a number of instructive items. The name of the input field is "userDirectoryHost". The value is an OGNL expression that extracts the value of the object that is keyed to "user_directory_host" and populates the displayed input text with that value. The field is not required, the label - which is extracted from the resource bundles for the current locale by getText('access_control.user_directory_host.label') - is placed to the left of the input field and appended with a ":" by default. When the user hits return in this field or presses the Submit button the form is submitted to the SaveAccessConfiguration action. But before it reaches the Action, it passes through the interceptor stack, one of which is the validation interceptor. If validation fails, action can be taken, including giving the operator the opportunity to correct any input errors.

Assume that there are other text fields that collect other information needed to connect to the Directory Server. The list of necessary parameters is:

  • host
  • port
  • bind DN
  • bind DN credentials
  • search base

Here is a list of what is needed to build and deploy a custom validator:

  • code for the validator
  • registration of the validator
  • validator mapped to the Action

DirectoryServerParametersValidator.java

Here is the listing for the validator:

package com.slamd.struts2.validators;


import com.opensymphony.xwork2.validator.validators.ValidatorSupport;
import com.opensymphony.xwork2.validator.ValidationException;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchScope;
import org.apache.commons.logging.Log;


/**
 *
 * A "plain", or non-field validator.
 * simple authentication.
 * no SSL yet.
 *
 *
 * @author Terry J Gardner
 */
public class DirectoryServerParametersValidator extends ValidatorSupport
{
  /**
   * Define the name of the fields to extract from the object.
   * These are the fields that will constitute a connection
   * that will be validated.
   */
  private static final String FIELD_BASE_DN = "userDirectoryBaseDN";
  private static final String FIELD_BIND_DN = "userDirectoryBindDN";
  private static final String FIELD_BIND_PW = "userDirectoryBindPW";
  private static final String FIELD_HOST    = "userDirectoryHost";
  private static final String FIELD_PORT    = "userDirectoryPort";

  public void validate(Object object) throws ValidationException
  {
    String rdn;

    try
    {
      String host   = (String)getFieldValue(FIELD_HOST,object);
      Integer port  = (Integer)getFieldValue(FIELD_PORT,object);
      String bindDN = (String)getFieldValue(FIELD_BIND_DN,object);
      String bindPW = (String)getFieldValue(FIELD_BIND_PW,object);
      String baseDN = (String)getFieldValue(FIELD_BASE_DN,object);

      DN base = new DN(baseDN);
      rdn = base.getRDNString();
      if(rdn.contains("="))
      {
        LDAPConnection ldapConnection = connect(host,port,
                                                bindDN,bindPW);
        Filter filter = Filter.create(rdn);
        SearchRequest searchRequest =
          new SearchRequest(baseDN,SearchScope.BASE,filter,"ou");
        SearchResult searchResult = ldapConnection.search(searchRequest);
        ldapConnection.close();
      }
      else
      {
        logger.error("mal-formed RDN " + rdn);
        setMessageKey("directory_server_validator.malformed_rdn");
        addActionError(object);
      }
    }
    catch(ValidationException validationException)
    {
      logger.error(validationException);
      setMessageKey("directory_server_validator.validation_exception");
      addActionError(object);
    }
    catch(LDAPSearchException ldapSearchException)
    {
      logger.error(ldapSearchException);
      setMessageKey("directory_server_validator.search_failed");
      addActionError(object);
    }
    catch(LDAPException ldapException)
    {
      logger.error(ldapException);
      setMessageKey("directory_server_validator.connection_failed");
      addActionError(object);
    }
  }


  // connect to the directory
  private LDAPConnection connect(String host,
                                 Integer port,
                                 String bindDN,
                                 String bindPW) throws LDAPException
  {
    LDAPConnection ldapConnection =
      new LDAPConnection(host,port,bindDN,bindPW);
    return ldapConnection;
  }


  private final static transient Log logger =
    com.slamd.Logger.getInstance().getLogger();
}

The validators gets the fields from the object (which is SaveAccessConfigurationAction.class) by constructing getters, for example, the value for "userDirectoryHost" is obtained by searching the object (see java.lang.reflect) for a method called getUserDirectoryHost(). The validator attempts to connect to the directory server specified by the host and port, and does a one-level search for the base DN. If the connection fails or the search fails, or some other error occurs, the validator adds a message to the actionErrors list, then validation was successful.

Registration

The validator is registered by the validators.xml file, which must be placed in the classpath of the web application (WEB-INF/classes works fine):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
        "-//OpenSymphony Group//XWork Validator Config 1.0//EN"
        "http://www.opensymphony.com/xwork/xwork-validator-config-1.0.dtd">

<validators>
  <validator name="directoryServerParameters"
	     class="com.slamd.struts2.validators.DirectoryServerParametersValidator"/>
</validators>

Mapped to the action

The validator is mapped to the Action using an XML file that is named [ActionClassName]-validations.xml, which is located next to the class file:

  <validator type="directoryServerParameters">
    <message>Directory Server Connection Attempt Failed</message>
  </validator>
technorati tags
Comments:

Post a Comment:
Comments are closed for this entry.

« November 2009
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     
       
Today

Feeds

Search this blog

Links

Weblog menu

Today's referrers

Today's Page Hits: 2