diaries, triumphs, failures, and rants
- All
- Animals
- Books
- Chess
- Cigars and the Man-Grill
- Collaboration
- Doctor Who
- General
- Internet
- iPhone
- Java
- LDAP
- mac
- oneLiners
- onThisDay
- slamd
- Star Trek
- Sun
- Travel
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
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 --%>
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):
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:
Directory Server Connection Attempt Failed
technorati tags
Thursday Apr 23, 2009