/*
* Copyright 2007, 2008, 2009 Jakim Friant
*
* This file is part of ResetPassword.
*
* ResetPassword is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ResetPassword is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ResetPassword. If not, see <http://www.gnu.org/licenses/>.
*/
package org.cfcc.services;
import org.cfcc.PasswordConstraintException;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.*;
import javax.naming.directory.*;
import org.acegisecurity.providers.ldap.LdapShaPasswordEncoder;
import org.cfcc.CryptoException;
import org.cfcc.NoPersonIdException;
import org.cfcc.LdapChangeException;
import org.cfcc.SystemUnavailableException;
import org.cfcc.utils.BlindSSLSocketFactory;
import org.cfcc.utils.Crypto;
import org.cfcc.utils.PwGen;
/**
* Class for connecting to the LDAP server and performing common operations.
*
* @author Jakim Friant
*/
public class Ldap {
/** the URL to the ldap database, for example: ldap://ldap.example.com/ */
String url = null;
/** set to true to use TLS/SSL (independent of a port specified in the
* LDAP URL string) */
boolean use_ssl = false;
/** the bind DN with write access to the LDAP database */
String bind_dn = null;
/** holds the password for the bind DN */
String bind_pw = null;
/** the search base for the directory, for example: "dc=example,dc=com" */
String search_base = null;
/** the field to use for username searches (usally uid or cn), defaults to "uid" */
String username_field = "uid";
/** the last DN that was used in a search or modify operation */
String last_dn = null;
/** Email Domain for converting to a Posix Account (do not include an '@' symbol)*/
public static final String EMAIL_DOMAIN = "mail.cfcc.edu";
/** An option to bypass the SSL certificate validation (use only if absolutely necessary) */
private boolean ssl_validate_cert = true;
/** This encoding is used to convert the password bytes to a string */
private static final String LDAP_ENCODING = "US-ASCII";
private static final String NORMAL_ACCOUNT = "512";
/**
* Class constructor with no defaults, the calling code will specify
* everything.
*
* @param arg_url the ldap URL
* @param arg_use_ssl set to true to use SSL
* @param arg_bind_dn the DN used to bind to LDAP
* @param arg_bind_pw password to connect to the LDAP service
* @param arg_search_base the base DN of the tree, for example:
* "dc=example,dc=com"
* @param arg_username_field the LDAP field that holds the username
*/
public Ldap(String arg_url,
boolean arg_use_ssl,
String arg_bind_dn,
String arg_bind_pw,
String arg_search_base,
String arg_username_field) {
url = arg_url;
use_ssl = arg_use_ssl;
bind_dn = arg_bind_dn;
bind_pw = arg_bind_pw;
search_base = arg_search_base;
username_field = arg_username_field;
}
/**
* Class constructor that uses the OpenLDAP default "uid" for the username.
*
* @param arg_url the ldap URL
* @param arg_use_ssl set to true to use SSL
* @param arg_bind_dn the DN used to bind to LDAP
* @param arg_bind_pw password to connect to the LDAP service
* @param arg_search_base the base DN of the tree, for example:
* "dc=example,dc=com"
*/
public Ldap(String arg_url,
boolean arg_use_ssl,
String arg_bind_dn,
String arg_bind_pw,
String arg_search_base) {
url = arg_url;
use_ssl = arg_use_ssl;
bind_dn = arg_bind_dn;
bind_pw = arg_bind_pw;
search_base = arg_search_base;
}
/**
* Class constructor with no defaults that uses a string flag to specify
* SSL instead of a boolean parameter. This is here to make it conveient
* to set the parameter in a servlet.
*
* @param arg_url the ldap URL
* @param arg_ssl set string to "true" to use SSL
* @param arg_bind_dn the DN used to bind to LDAP
* @param arg_bind_pw password to connect to the LDAP service
* @param arg_search_base the base DN of the tree
* @param arg_username_field the field used in the filter for the username (ex: uid)
*/
public Ldap(String arg_url,
String arg_ssl,
String arg_bind_dn,
String arg_bind_pw,
String arg_search_base,
String arg_username_field) {
url = arg_url;
use_ssl = arg_ssl.equals("true");
bind_dn = arg_bind_dn;
bind_pw = arg_bind_pw;
search_base = arg_search_base;
username_field = arg_username_field;
}
/**
* Class constructor that has "uid" as the default username field and that
* uses a string flag to specify SSL instead of a boolean parameter. This
* is here to make it conveient to set the parameter in a servlet.
*
* @param arg_url the ldap URL
* @param arg_ssl set string to "true" to use SSL
* @param arg_bind_dn the DN used to bind to LDAP
* @param arg_bind_pw password to connect to the LDAP service
* @param arg_search_base the base DN of the tree
*/
public Ldap(String arg_url,
String arg_ssl,
String arg_bind_dn,
String arg_bind_pw,
String arg_search_base) {
url = arg_url;
use_ssl = arg_ssl.equals("true");
bind_dn = arg_bind_dn;
bind_pw = arg_bind_pw;
search_base = arg_search_base;
}
/**
* Class constructor that lets the caller request an unverified SSL
* connection. Has no defaults and also uses a string flag to specify
* SSL instead of a boolean parameter. This is here to make it conveient
* to set the parameter from the servlet config file.
*
* @param arg_url the ldap URL
* @param arg_ssl set string to "true" to use SSL
* @param arg_ssl_validate_cert set to "false" to bypass certificate validation
* @param arg_bind_dn the DN used to bind to LDAP
* @param arg_bind_pw password to connect to the LDAP service
* @param arg_search_base the base DN of the tree
* @param arg_username_field the field used in the filter for the username (ex: uid)
*/
public Ldap(String arg_url,
String arg_ssl,
String arg_ssl_validate_cert,
String arg_bind_dn,
String arg_bind_pw,
String arg_search_base,
String arg_username_field) {
url = arg_url;
use_ssl = arg_ssl.equals("true");
ssl_validate_cert = arg_ssl_validate_cert.equals("true");
bind_dn = arg_bind_dn;
bind_pw = arg_bind_pw;
search_base = arg_search_base;
username_field = arg_username_field;
}
/**
* Set up the LDAP login context and return a DirContext object. Uses the
* connection information specified when the object was created.
*
* @return a DirContext instance that is connected to a LDAP
* service/database.
* @throws NamingException If the connection cannot be made
*/
public DirContext getContext() throws NamingException {
Hashtable<String, String> env = new Hashtable<String, String>(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
if (use_ssl == true) {
env.put(Context.SECURITY_PROTOCOL, "ssl");
if (ssl_validate_cert == false) {
env.put("java.naming.ldap.factory.socket", BlindSSLSocketFactory.class.getName());
}
}
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bind_dn);
if (bind_pw.startsWith(Crypto.prefix())) {
Crypto c = new Crypto();
try {
bind_pw = c.decrypt(bind_pw);
} catch (CryptoException ex) {
throw new NamingException(ex.toString());
}
}
env.put(Context.SECURITY_CREDENTIALS, bind_pw);
DirContext ctx = new InitialDirContext(env);
return ctx;
}
/**
* Runs a search against the LDAP tree and returns the results.
*
* @param filter string with any valid LDAP search filter
* @return a NamingEnumeration with the records found in the search
* @throws NamingException if an error is encountered when connecting or
* searching
*/
public NamingEnumeration<?> search(String filter) throws NamingException {
NamingEnumeration<?> answer = null;
DirContext ctx = null;
// set up search criteria
SearchControls ctls = new SearchControls();
ctls.setReturningObjFlag(true);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctx = getContext();
answer = ctx.search(search_base, filter, ctls);
ctx.close();
return answer;
}
/**
* Alternate function that runs a search against the LDAP tree and returns
* a limited set of results.
*
* @param filter string with any valid LDAP search filter
* @param limit the maximum number of entries to return from the search
* @return a NamingEnumeration with the records found in the search
* @throws NamingException if an error is encountered when connecting or
* searching
*/
public NamingEnumeration<?> search(String filter, long limit)
throws NamingException {
NamingEnumeration<?> answer = null;
DirContext ctx = null;
// set up search criteria
SearchControls ctls = new SearchControls();
ctls.setReturningObjFlag(true);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setCountLimit(limit);
ctx = getContext();
answer = ctx.search(search_base, filter, ctls);
ctx.close();
return answer;
}
/**
* Alternate function that runs a search against a subtree in the LDAP
* tree and returns a limited set of results.
*
* @param filter string with any valid LDAP search filter
* @param base string with an alternate search base
* @param limit the maximum number of entries to return from the search
* @return a NamingEnumeration with the records found in the search
* @throws NamingException if an error is encountered when connecting or
* searching
*/
public NamingEnumeration<?> search(String filter, String base, long limit)
throws NamingException {
NamingEnumeration<?> answer = null;
DirContext ctx = null;
// set up search criteria
SearchControls ctls = new SearchControls();
ctls.setReturningObjFlag(true);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setCountLimit(limit);
ctx = getContext();
answer = ctx.search(base, filter, ctls);
ctx.close();
return answer;
}
/**
* Alternate function that runs a search against a subtree in the LDAP
* tree and returns unlimited results.
*
* @return a NamingEnumeration with the records found in the search
* @param filter string with any valid LDAP search filter
* @param base string with an alternate search base
* @throws NamingException if an error is encountered when connecting or
* searching
*/
public NamingEnumeration<?> search(String filter, String base)
throws NamingException {
NamingEnumeration<?> answer = null;
DirContext ctx = null;
// set up search criteria
SearchControls ctls = new SearchControls();
ctls.setReturningObjFlag(true);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctx = getContext();
answer = ctx.search(base, filter, ctls);
ctx.close();
return answer;
}
/**
* Use to set the username field after class initialization
* @param field_name a string with the name of the field to use
*/
public void setUsernameField(String field_name) {
username_field = field_name;
}
/**
* Returns the value of the username field.
* @return a string with the username
*/
public String getUsernameField() {
return username_field;
}
/**
* Searches for the username in the LDAP database to find the PERSON ID.
* Calling this function sets the last_dn member.
*
* This is the basic function that the next two functions call to do the actual
* work of finding and returning the number.
* @param username the system name for the user, for example: jmdoe25
* @return a String with the employee number
* @throws NoPersonIdException If the LDAP record is not found or the
* employeeNumber field is blank.
*/
public String getEmployeeNumber(String username) throws NoPersonIdException {
String empl_num = "";
String filter = "(" + username_field + "=" + username + ")";
try {
NamingEnumeration<?> answer = search(filter);
if (answer != null) {
if (answer.hasMore()) {
SearchResult sr = (SearchResult) answer.next();
last_dn = sr.getName();
Attributes attrs = sr.getAttributes();
empl_num = (String) attrs.get("employeeNumber").get();
}
if (empl_num.equals("")) {
throw new NoPersonIdException("Person ID blank", username);
}
} else {
throw new NoPersonIdException("Username not found", username);
}
} catch (NamingException e) {
throw new NoPersonIdException("Error getting employee number.", username, e);
} catch (NullPointerException e) {
throw new NoPersonIdException("Null Pointer Exception", username);
}
return empl_num;
}
/**
* Returns the employee number for the last DN used. Requires that the
* last_dn class member be set before this function is called.
*
* @return a string with the employee number
* @throws NoPersonIdException If an error occured when searching for the
* UID or retrieving the ID.
*/
public String getEmployeeNumber() throws NoPersonIdException {
String empl_num = "";
if (last_dn != null) {
if (last_dn.length() > 0) {
String uid = last_dn.substring(last_dn.indexOf("=") + 1,
last_dn.indexOf(","));
empl_num = getEmployeeNumber(uid);
}
}
return empl_num;
}
/**
* Searches the directory for a matching employee number (person ID) and
* returns the full DN.
* @return a string with the full DN
* @param person_id The Colleague person ID that is stored in the LDAP employeeNumber field
* @throws NamingException if the search fails
*/
public String getDnByEmployeeNumber(String person_id) throws NamingException {
String dn = "";
NamingEnumeration<?> enumr = search("(employeeNumber=" + person_id + ")");
if (enumr.hasMore()) {
SearchResult sr = (SearchResult) enumr.next();
dn = sr.getName() + "," + search_base;
} else {
throw new NamingException("getDnByEmployeeNumber(): No record found for " + person_id);
}
return dn;
}
/**
* Searches by UID for an entry and returns the full DN. While it may be
* possible to get more than one result in the search (by adding a wild
* card to the user_id for example) the function will only return the
* first result.
*
* @param user_id the user name to search for
* @return a string with the full DN
* @throws NamingException if the search fails
*/
public String getDN(String user_id) throws NamingException {
String dn = "";
NamingEnumeration<?> enumr = search("(" + username_field + "=" + user_id + ")");
if (enumr.hasMore()) {
SearchResult sr = (SearchResult) enumr.next();
dn = sr.getName() + "," + search_base;
}
return dn;
}
/**
* Returns the password from the current LDAP record (uses last_dn).
* @return password - the string stored in the LDAP record
* @throws UnsupportedEncodingException if it fails when converting the password bytes to a string
*/
public String getPassword() throws UnsupportedEncodingException {
String password = "";
try {
if (last_dn != null && last_dn.length() > 0) {
DirContext ctx = getContext();
String[] wanted_attrs = {"userPassword"};
Attributes attrs = ctx.getAttributes(getLastDn(), wanted_attrs);
byte[] passwd_enc = (byte[]) attrs.get("userPassword").get();
password = new String(passwd_enc, LDAP_ENCODING);
}
} catch (NamingException ex) {
Logger.getLogger(Ldap.class.getName()).log(Level.SEVERE, null, ex);
}
return password;
}
/**
* Generates a new password and saves it to the current user's LDAP record.
* Uses a random generator to create a 8-character alphanumeric password.
* @return an 8-character string with the password or an
* empty string if there was an error saving the password
* @throws org.cfcc.LdapChangeException when there is an error accessing the LDAP tree
* @throws SystemUnavailableException when the password generator cannot load the crypto module
*/
public String newPassword() throws LdapChangeException, SystemUnavailableException {
String new_passwd = PwGen.getPassword(8);
setPassword(new_passwd, true);
return new_passwd;
}
/**
* Generates the current timestamp in Unix shadow format.
* Note to convert to days we are dividing by 1000ms, 60s, 60m, 24h
* for a total of 86,400,000 milliseconds in a day.
* @return number of days since the Unix epoch
*/
public String ldapTimestamp() {
String ts_out = "";
int MS_IN_A_DAY = 86400000;
long uts = System.currentTimeMillis() / MS_IN_A_DAY;
ts_out = String.valueOf(uts);
return ts_out;
}
/**
* Replaces the password in the LDAP database for the last record accessed
* (as specified by the string member last_dn).
*
* @param new_passwd a string with the new password to save
* @param salted is a flag to determine if the SHA1 password should have a random salt
* @throws org.cfcc.LdapChangeException When there is an error saving the password
* @throws org.cfcc.SystemUnavailableException When the encryption subroutine fails
*/
public void setPassword(String new_passwd, boolean salted)
throws LdapChangeException, SystemUnavailableException {
try {
DirContext ctx = getContext();
String ts = ldapTimestamp();
byte[] salt = null;
// set password is a ldap modify operation
ModificationItem[] mods = new ModificationItem[2];
LdapShaPasswordEncoder pwd = new LdapShaPasswordEncoder();
String passwd_hash = pwd.encodePassword(new_passwd, salt);
Attribute passwd = new BasicAttribute("userPassword", passwd_hash);
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, passwd);
Attribute slchanged = new BasicAttribute("shadowLastChange", ts);
mods[1] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, slchanged);
ctx.modifyAttributes(last_dn + "," + search_base, mods);
ctx.close();
} catch (AttributeModificationException e) {
throw new LdapChangeException(last_dn, "Unable to change password");
} catch (NamingException e) {
Logger.getLogger(Ldap.class.getName()).log(Level.SEVERE, "Naming error at LDAP server", e);
throw new LdapChangeException(last_dn, "Naming error connecting to LDAP server");
}
}
/**
* Replace the password on an Microsoft Active Directory server via LDAP.
*
* <p>Examples: </p>
* <ul>
* <li><a href="http://forum.java.sun.com/thread.jspa?threadID=599638&messageID=3200093">User sets own password</a></li>
* <li><a href="http://forum.java.sun.com/thread.jspa?threadID=592611&tstart=0">Admin resets password</a></li>
* <li><a href="http://support.microsoft.com/kb/269190">How to change a Windows password through LDAP</a></li>
* <li><a href="http://support.microsoft.com/kb/247078/EN-US/">How To Enable Secure Socket Layer (SSL) Communication over LDAP...</a></li>
* <li><a href="http://support.microsoft.com/kb/321051">How to enable LDAP over SSL with a third-party certification authority</a></li>
* </ul>
*
* @param new_password a string with the new password
* @throws org.cfcc.LdapChangeException thrown if there is any problem setting the password
* @throws PasswordConstraintException
*/
public void setUnicodePassword(String new_password)
throws LdapChangeException, PasswordConstraintException {
try {
DirContext ctx = getContext();
// set password is a ldap modify operation
ModificationItem[] mods = new ModificationItem[1];
// Replace the "unicodePwd" attribute with a new value
// Password must be both Unicode and a quoted string
String newQuotedPassword = "\"" + new_password + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));
/* Debugging only */
//System.out.println("Setting unicodePwd for " + last_dn + "," + search_base);
ctx.modifyAttributes(last_dn + "," + search_base, mods);
// Now run a separate modification to clear the userAccountControl
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl", NORMAL_ACCOUNT));
ctx.modifyAttributes(last_dn + "," + search_base, mods);
ctx.close();
/* Catch the following exception raised by AD when the password
* does not match the constraints.
*
* Error updating LDAP for cn=Jakim Friant,ou=Headquarters, setUnicodePassword(): Naming
* error connecting to Active Directory server [javax.naming.OperationNotSupported
* Exception: [LDAP: error code 53 - 0000052D: SvcErr: DSID-031A0FBC, problem 5003
* (WILL_NOT_PERFORM), data 0
*/
} catch (javax.naming.OperationNotSupportedException ex) {
throw new PasswordConstraintException(ex);
} catch (UnsupportedEncodingException ex) {
throw new LdapChangeException(last_dn, "Error setting password on Active Directory server [" + ex + "]");
} catch (NamingException ex) {
// ex.printStackTrace();
throw new LdapChangeException(last_dn, "Naming error connecting to Active Directory server [" + ex + "]");
}
}
/**
* Explicitly sets the last_dn member string. It will strip off the base
* DN and only save the unique portion of the DN. For example, if the
* base DN is "dc=cfcc,dc=edu", then
* "uid=jdoe25,ou=Students,dc=cfcc,dc=edu" becomes
* "uid=jdoe25,ou=Students".
*
* @param dn a string with a full or partial DN
* @throws StringIndexOutOfBoundsException if the dn parameter cannot be
* parsed, such as a blank string
*/
public void setLastDn(String dn) throws StringIndexOutOfBoundsException {
try {
int base_loc = dn.indexOf(search_base) - 1;
if (base_loc > 0) {
last_dn = dn.substring(0, base_loc);
} else {
last_dn = dn;
}
} catch (StringIndexOutOfBoundsException e) {
System.err.println("ResetPassword - Unable to parse DN: " + dn + ". " + e.getMessage());
throw e;
}
}
/**
* Returns the full DN for the last_dn member.
*
* @return a string with the FQDN of the last DN used, or an empty string
* if the last_dn is null.
*/
public String getLastDn() {
StringBuffer dn_out = new StringBuffer("");
if (last_dn != null) {
dn_out.append(last_dn);
dn_out.append(",");
dn_out.append(search_base);
}
return dn_out.toString();
}
/**
* Returns just the username portion of the DN. Used to link the LDAP
* entry back to the Registry record.
*
* @return a string with that contains the username, or an empty string
*/
public String getLastUsername() {
String username = "";
try {
if (last_dn != null) {
username = last_dn.substring(last_dn.indexOf('=') + 1,
last_dn.indexOf(','));
}
} catch (StringIndexOutOfBoundsException e) { /* pass */ }
return username;
}
/**
* Sets the employee type (or multiple types) based on the Office Codes from
* Colleague
* @param office_codes A list of office code strings from Colleague
* @throws org.cfcc.LdapChangeException If there was an error updating the LDAP record
*/
public void setEmployeeType(ArrayList<?> office_codes)
throws LdapChangeException {
String staff_code;
try {
DirContext ctx = getContext();
Attributes attrs = new BasicAttributes(true);
Attribute oc = new BasicAttribute("employeeType", office_codes.get(0));
ListIterator<?> itr = office_codes.listIterator(1);
while (itr.hasNext()) {
staff_code = (String) itr.next();
if (staff_code.matches("[A-Z][A-Z]*")) {
oc.add(staff_code);
}
}
attrs.put(oc);
try {
ctx.modifyAttributes(last_dn + "," + search_base,
DirContext.ADD_ATTRIBUTE,
attrs);
} catch (NamingException e) {
// If we can't add, maybe we can replace...
ctx.modifyAttributes(last_dn + "," + search_base,
DirContext.REPLACE_ATTRIBUTE,
attrs);
}
ctx.close();
} catch (AttributeModificationException e) {
throw new LdapChangeException(last_dn, "Unable to add employee type");
} catch (NamingException e) {
throw new LdapChangeException(last_dn, "Naming exception");
}
}
/**
* Creates the necessary fields for the entry to function as a Posix/shadow
* account -- mainly for Linux authentication.
* @param person_id The Colleague person ID
* @param last is the last name of the user
* @param first is the first name of the user
* @throws LdapChangeException On an error creating or writing the new
* LDAP record.
*/
public void convertToPosixAccount(String person_id, String last, String first) throws LdapChangeException {
try {
DirContext ctx = getContext();
Attributes attrs = new BasicAttributes(true);
// build the full name for later
String full_name = first + " " + last;
System.out.println("Ldap.convertToPosixAccount(): " + person_id + ", " + full_name);
// we have to convert this to an integer and then back to a string
// so that the beginning zeros are removed
Integer per_id = new Integer(person_id);
// Add the proper OU's
Attribute oc = new BasicAttribute("objectClass");
oc.add("top");
oc.add("person");
oc.add("organizationalPerson");
oc.add("inetOrgPerson");
oc.add("posixAccount");
oc.add("shadowAccount");
attrs.put(oc);
// then add the default values
attrs.put("gidNumber", "1000");
attrs.put("uidNumber", per_id.toString());
attrs.put("homeDirectory", "/home/" + getLastUsername());
attrs.put("loginShell", "/bin/bash");
attrs.put("gecos", full_name);
attrs.put("sn", last);
attrs.put("givenName", first);
attrs.put("displayName", full_name);
attrs.put("mail", getLastUsername() + "@" + EMAIL_DOMAIN);
attrs.put("shadowMin", "0");
attrs.put("shadowMax", "999999");
attrs.put("shadowWarning", "7");
attrs.put("shadowInactive", "-1");
attrs.put("shadowExpire", "-1");
attrs.put("shadowFlag", "0");
try {
ctx.modifyAttributes(last_dn + "," + search_base,
DirContext.ADD_ATTRIBUTE,
attrs);
} catch (NamingException e) {
// If we can't add, maybe we can replace...
ctx.modifyAttributes(last_dn + "," + search_base,
DirContext.REPLACE_ATTRIBUTE,
attrs);
}
ctx.close();
} catch (AttributeModificationException e) {
throw new LdapChangeException(last_dn, "Unable to update to posixAccount: " + e);
} catch (NamingException e) {
throw new LdapChangeException(last_dn, "Naming exception: " + e);
} catch (NumberFormatException e) {
throw new LdapChangeException(last_dn, "Person ID is NULL: " + e);
}
}
/**
* Returns the value of the search base member
*
* @return a string with the search base DN
*/
public String getSearchBase() {
return search_base;
}
/**
* Runs a search and prints the result. Mainly used for testing
* @see #formatResults(NamingEnumeration)
* @param filter a string with a LDAP search construct
* @throws javax.naming.NamingException If there is an error with the search.
*/
public void searchResult(String filter) throws NamingException {
NamingEnumeration<?> result = search(filter);
formatResults(result);
}
/**
* Generic method to format and print the NamingEnumeration returned from
* a search. Used for testing.
*
* @param enumr the NamingEnumeration object returned from a search
* @see #formatAttributes(Attributes)
*/
public void formatResults(NamingEnumeration<?> enumr) {
int count = 0;
try {
while (enumr.hasMore()) {
SearchResult sr = (SearchResult) enumr.next();
System.out.println("SEARCH RESULT:" + sr.getName());
formatAttributes(sr.getAttributes());
System.out.println("====================================================");
count++;
}
System.out.println("Search returned " + count + " results");
} catch (NamingException e) {
e.printStackTrace();
}
}
/**
* Generic method to format and print the Attributes. Displays all the
* multiple values of each Attribute in the Attributes.
*
* @param attrs an Attribute collection returned by a search result object
* @see #formatResults(NamingEnumeration)
*/
public void formatAttributes(Attributes attrs) {
if (attrs == null) {
System.out.println("This result has no attributes");
} else {
try {
for (NamingEnumeration<?> enumr = attrs.getAll(); enumr.hasMore();) {
Attribute attrib = (Attribute) enumr.next();
System.out.println("ATTRIBUTE :" + attrib.getID());
for (NamingEnumeration<?> e = attrib.getAll(); e.hasMore();) {
System.out.println("\t\t = " + e.next());
}
}
} catch (NamingException e) {
e.printStackTrace();
}
}
}
}