();
// Use a custom initial context factory if specified. Otherwise, use the default.
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
env.put(Context.PROVIDER_URL, getProviderURL(alternateBaseDN));
if (sslEnabled) {
env.put("java.naming.ldap.factory.socket", "org.jivesoftware.util.SimpleSSLSocketFactory");
env.put(Context.SECURITY_PROTOCOL, "ssl");
}
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, userDN + "," + alternateBaseDN);
env.put(Context.SECURITY_CREDENTIALS, password);
// Specify timeout to be 10 seconds, only on non SSL since SSL connections
// break with a teimout.
if (!sslEnabled) {
env.put("com.sun.jndi.ldap.connect.timeout", "10000");
}
if (ldapDebugEnabled) {
env.put("com.sun.jndi.ldap.trace.ber", System.err);
}
if (connectionPoolEnabled) {
env.put("com.sun.jndi.ldap.connect.pool", "true");
}
if (followReferrals) {
env.put(Context.REFERRAL, "follow");
}
if (debug) {
Log.debug("Created context values, attempting to create context...");
}
ctx = new InitialDirContext(env);
}
catch (NamingException e) {
if (debug) {
Log.debug("Caught a naming exception when creating InitialContext", ne);
}
return false;
}
}
else {
if (debug) {
Log.debug("Caught a naming exception when creating InitialContext", ne);
}
return false;
}
}
finally {
try { ctx.close(); }
catch (Exception ignored) {
// Ignore.
}
}
return true;
}
/**
* Finds a user's dn using their username. Normally, this search will
* be performed using the field "uid", but this can be changed by setting
* the usernameField property.
*
* Searches are performed over all subtrees relative to the baseDN.
* If the search fails in the baseDN then another search will be
* performed in the alternateBaseDN. For example, if the baseDN
* is "o=jivesoftware, o=com" and we do a search for "mtucker", then we might
* find a userDN of "uid=mtucker,ou=People". This kind of searching is a good
* thing since it doesn't make the assumption that all user records are stored
* in a flat structure. However, it does add the requirement that "uid" field
* (or the other field specified) must be unique over the entire subtree from
* the baseDN. For example, it's entirely possible to create two dn's
* in your LDAP directory with the same uid: "uid=mtucker,ou=People" and
* "uid=mtucker,ou=Administrators". In such a case, it's not possible to
* uniquely identify a user, so this method will throw an error.
*
* The dn that's returned is relative to the default baseDN.
*
* @param username the username to lookup the dn for.
* @return the dn associated with username.
* @throws Exception if the search for the dn fails.
*/
public String findUserDN(String username) throws Exception {
try {
return findUserDN(username, baseDN);
}
catch (Exception e) {
if (alternateBaseDN != null) {
return findUserDN(username, alternateBaseDN);
}
else {
throw e;
}
}
}
/**
* Finds a user's dn using their username in the specified baseDN. Normally, this search
* will be performed using the field "uid", but this can be changed by setting
* the usernameField property.
*
* Searches are performed over all subtrees relative to the baseDN.
* For example, if the baseDN is "o=jivesoftware, o=com" and we
* do a search for "mtucker", then we might find a userDN of
* "uid=mtucker,ou=People". This kind of searching is a good thing since
* it doesn't make the assumption that all user records are stored in a flat
* structure. However, it does add the requirement that "uid" field (or the
* other field specified) must be unique over the entire subtree from the
* baseDN. For example, it's entirely possible to create two dn's
* in your LDAP directory with the same uid: "uid=mtucker,ou=People" and
* "uid=mtucker,ou=Administrators". In such a case, it's not possible to
* uniquely identify a user, so this method will throw an error.
*
* The dn that's returned is relative to the baseDN.
*
* @param username the username to lookup the dn for.
* @param baseDN the base DN to use for this search.
* @return the dn associated with username.
* @throws Exception if the search for the dn fails.
* @see #findUserDN(String) to search using the default baseDN and alternateBaseDN.
*/
public String findUserDN(String username, String baseDN) throws Exception {
boolean debug = Log.isDebugEnabled();
if (debug) {
Log.debug("Trying to find a user's DN based on their username. " + usernameField + ": " + username
+ ", Base DN: " + baseDN + "...");
}
DirContext ctx = null;
try {
ctx = getContext(baseDN);
if (debug) {
Log.debug("Starting LDAP search...");
}
// Search for the dn based on the username.
SearchControls constraints = new SearchControls();
// If sub-tree searching is enabled (default is true) then search the entire tree.
if (subTreeSearch) {
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
}
// Otherwise, only search a single level.
else {
constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
}
constraints.setReturningAttributes(new String[] { usernameField });
NamingEnumeration answer = ctx.search("", searchFilter, new String[] {username},
constraints);
if (debug) {
Log.debug("... search finished");
}
if (answer == null || !answer.hasMoreElements()) {
if (debug) {
Log.debug("User DN based on username '" + username + "' not found.");
}
throw new UserNotFoundException("Username " + username + " not found");
}
String userDN = ((SearchResult)answer.next()).getName();
// Make sure there are no more search results. If there are, then
// the username isn't unique on the LDAP server (a perfectly possible
// scenario since only fully qualified dn's need to be unqiue).
// There really isn't a way to handle this, so throw an exception.
// The baseDN must be set correctly so that this doesn't happen.
if (answer.hasMoreElements()) {
if (debug) {
Log.debug("Search for userDN based on username '" + username + "' found multiple " +
"responses, throwing exception.");
}
throw new UserNotFoundException("LDAP username lookup for " + username +
" matched multiple entries.");
}
//All other methods assume that userDN is not a full LDAP string.
//However if a referal was followed this is not the case. The
//following code converts a referral back to a "partial" LDAP string.
if (userDN.startsWith("ldap://")) {
userDN = userDN.replace("," + baseDN, "");
userDN = userDN.substring(userDN.lastIndexOf("/") + 1);
userDN = java.net.URLDecoder.decode(userDN);
return userDN;
}
else {
return userDN;
}
}
catch (Exception e) {
if (debug) {
Log.debug("Exception thrown when searching for userDN based on username '" + username + "'", e);
}
throw e;
}
finally {
try { ctx.close(); }
catch (Exception ignored) {
// Ignore.
}
}
}
/**
* Returns a properly encoded URL for use as the PROVIDER_URL.
* If the encoding fails then the URL will contain the raw base dn.
*
* @param baseDN the base dn to use in the URL.
* @return the properly encoded URL for use in as PROVIDER_URL.
*/
private String getProviderURL(String baseDN) {
StringBuffer ldapURL = new StringBuffer();
try {
baseDN = URLEncoder.encode(baseDN, "UTF-8");
// The java.net.URLEncoder class encodes spaces as +, but they need to be %20
baseDN = baseDN.replaceAll("\\+", "%20");
}
catch (java.io.UnsupportedEncodingException e) {
// UTF-8 is not supported, fall back to using raw baseDN
}
for (String host : hosts) {
// Create a correctly-encoded ldap URL for the PROVIDER_URL
ldapURL.append("ldap://");
ldapURL.append(host);
ldapURL.append(":");
ldapURL.append(port);
ldapURL.append("/");
ldapURL.append(baseDN);
ldapURL.append(" ");
}
return ldapURL.toString();
}
/**
* Returns the LDAP servers hosts; e.g. localhost or
* machine.example.com, etc. This value is stored as the Jive
* Property ldap.host.
*
* @return the LDAP server host name.
*/
public Collection getHosts() {
return hosts;
}
/**
* Sets the list of LDAP servers host; e.g., localhost or
* machine.example.com, etc. This value is store as the Jive
* Property ldap.host using a comma as a delimiter for each host.
*
* Note that all LDAP servers have to share the same configuration.
*
* @param hosts the LDAP servers host names.
*/
public void setHosts(Collection hosts) {
this.hosts = hosts;
StringBuilder hostProperty = new StringBuilder();
for (String host : hosts) {
hostProperty.append(host).append(",");
}
if (!hosts.isEmpty()) {
// Remove the last comma
hostProperty.setLength(hostProperty.length()-1);
}
JiveGlobals.setXMLProperty("ldap.host", hostProperty.toString());
}
/**
* Returns the LDAP server port number. The default is 389. This value is
* stored as the Jive Property ldap.port.
*
* @return the LDAP server port number.
*/
public int getPort() {
return port;
}
/**
* Sets the LDAP server port number. The default is 389. This value is
* stored as the Jive property ldap.port.
*
* @param port the LDAP server port number.
*/
public void setPort(int port) {
this.port = port;
JiveGlobals.setXMLProperty("ldap.port", ""+port);
}
/**
* Returns true if LDAP connection debugging is turned on. When on, trace
* information about BER buffers sent and received by the LDAP provider is
* written to System.out. Debugging is turned off by default.
*
* @return true if LDAP debugging is turned on.
*/
public boolean isDebugEnabled() {
return ldapDebugEnabled;
}
/**
* Sets whether LDAP connection debugging is turned on. When on, trace
* information about BER buffers sent and received by the LDAP provider is
* written to System.out. Debugging is turned off by default.
*
* @param debugEnabled true if debugging should be turned on.
*/
public void setDebugEnabled(boolean debugEnabled) {
this.ldapDebugEnabled = debugEnabled;
JiveGlobals.setXMLProperty("ldap.ldapDebugEnabled", ""+debugEnabled);
}
/**
* Returns true if LDAP connection is via SSL or not. SSL is turned off by default.
*
* @return true if SSL connections are enabled or not.
*/
public boolean isSslEnabled() {
return sslEnabled;
}
/**
* Sets whether the connection to the LDAP server should be made via ssl or not.
*
* @param sslEnabled true if ssl should be enabled, false otherwise.
*/
public void setSslEnabled(boolean sslEnabled) {
this.sslEnabled = sslEnabled;
JiveGlobals.setXMLProperty("ldap.sslEnabled", ""+sslEnabled);
}
/**
* Returns the LDAP field name that the username lookup will be performed
* on. By default this is "uid".
*
* @return the LDAP field that the username lookup will be performed on.
*/
public String getUsernameField() {
return usernameField;
}
/**
* Sets the LDAP field name that the username lookup will be performed on.
* By default this is "uid".
*
* @param usernameField the LDAP field that the username lookup will be
* performed on.
*/
public void setUsernameField(String usernameField) {
this.usernameField = usernameField;
if (usernameField == null) {
JiveGlobals.deleteXMLProperty("ldap.usernameField");
}
else {
JiveGlobals.setXMLProperty("ldap.usernameField", usernameField);
}
}
/**
* Returns the LDAP field name that the user's name is stored in. By default
* this is "cn". Another common value is "displayName".
*
* @return the LDAP field that that correspond's to the user's name.
*/
public String getNameField() {
return nameField;
}
/**
* Sets the LDAP field name that the user's name is stored in. By default
* this is "cn". Another common value is "displayName".
*
* @param nameField the LDAP field that that correspond's to the user's name.
*/
public void setNameField(String nameField) {
this.nameField = nameField;
if (nameField == null) {
JiveGlobals.deleteXMLProperty("ldap.nameField");
}
else {
JiveGlobals.setXMLProperty("ldap.nameField", nameField);
}
}
/**
* Returns the LDAP field name that the user's email address is stored in.
* By default this is "mail".
*
* @return the LDAP field that that correspond's to the user's email
* address.
*/
public String getEmailField() {
return emailField;
}
/**
* Sets the LDAP field name that the user's email address is stored in.
* By default this is "mail".
*
* @param emailField the LDAP field that that correspond's to the user's
* email address.
*/
public void setEmailField(String emailField) {
this.emailField = emailField;
if (emailField == null) {
JiveGlobals.deleteXMLProperty("ldap.emailField");
}
else {
JiveGlobals.setXMLProperty("ldap.emailField", emailField);
}
}
/**
* Returns the starting DN that searches for users will performed with.
* Searches will performed on the entire sub-tree under the base DN.
*
* @return the starting DN used for performing searches.
*/
public String getBaseDN() {
return baseDN;
}
/**
* Sets the starting DN that searches for users will performed with.
* Searches will performed on the entire sub-tree under the base DN.
*
* @param baseDN the starting DN used for performing searches.
*/
public void setBaseDN(String baseDN) {
this.baseDN = baseDN;
JiveGlobals.setXMLProperty("ldap.baseDN", baseDN);
}
/**
* Returns the alternate starting DN that searches for users will performed with.
* Searches will performed on the entire sub-tree under the alternate base DN after
* they are performed on the main base DN.
*
* @return the alternate starting DN used for performing searches. If no alternate
* DN is set, this method will return null.
*/
public String getAlternateBaseDN() {
return alternateBaseDN;
}
/**
* Sets the alternate starting DN that searches for users will performed with.
* Searches will performed on the entire sub-tree under the alternate base DN after
* they are performed on the main base dn.
*
* @param alternateBaseDN the alternate starting DN used for performing searches.
*/
public void setAlternateBaseDN(String alternateBaseDN) {
this.alternateBaseDN = alternateBaseDN;
if (alternateBaseDN == null) {
JiveGlobals.deleteXMLProperty("ldap.alternateBaseDN");
}
else {
JiveGlobals.setXMLProperty("ldap.alternateBaseDN", alternateBaseDN);
}
}
/**
* Returns the BaseDN for the given username
*
* @return the BaseDN for the given username. If no baseDN is found,
* this method will return null.
*/
public String getUsersBaseDN(String username) {
try {
findUserDN(username, baseDN);
return(baseDN);
}
catch (Exception e) {
try
{
if (alternateBaseDN != null) {
findUserDN(username, alternateBaseDN);
return(alternateBaseDN);
}
}
catch (Exception ex)
{}
}
return(null);
}
/**
* Returns the starting admin DN that searches for admins will performed with.
* Searches will performed on the entire sub-tree under the admin DN.
*
* @return the starting DN used for performing searches.
*/
public String getAdminDN() {
return adminDN;
}
/**
* Sets the starting admin DN that searches for admins will performed with.
* Searches will performed on the entire sub-tree under the admins DN.
*
* @param adminDN the starting DN used for performing admin searches.
*/
public void setAdminDN(String adminDN) {
this.adminDN = adminDN;
JiveGlobals.setXMLProperty("ldap.adminDN", adminDN);
}
/**
* Returns the starting admin DN that searches for admins will performed with.
* Searches will performed on the entire sub-tree under the admin DN.
*
* @return the starting DN used for performing searches.
*/
public String getAdminPassword() {
return adminPassword;
}
/**
* Sets the admin password for the LDAP server we're connecting to.
*
* @param adminPassword the admin password for the LDAP server we're
* connecting to.
*/
public void setAdminPassword(String adminPassword) {
this.adminPassword = adminPassword;
JiveGlobals.setXMLProperty("ldap.adminPassword", adminPassword);
}
/**
* Returns the filter used for searching the directory for users.
*
* @return the search filter.
*/
public String getSearchFilter() {
return searchFilter;
}
/**
* Sets the filter used for searching the directory for users. The filter should
* contain a single token "{0}" that will be dynamically replaced with the
* user's unique ID.
*
* @param searchFilter the search filter.
*/
public void setSearchFilter(String searchFilter) {
if (searchFilter == null || "".equals(searchFilter)) {
StringBuilder filter = new StringBuilder();
filter.append("(").append(usernameField).append("={0})");
this.searchFilter = filter.toString();
JiveGlobals.deleteXMLProperty("ldap.searchFilter");
}
else {
this.searchFilter = searchFilter;
JiveGlobals.setXMLProperty("ldap.searchFilter", searchFilter);
}
}
/**
* Returns true if the entire tree under the base DN will be searched (recursive search)
* when doing LDAP queries (finding users, groups, etc). When false, only a single level
* under the base DN will be searched. The default is true which is the best
* option for most LDAP setups. In only a few cases will the directory be setup in such
* a way that it's better to do single level searching.
*
* @return true if the entire tree under the base DN will be searched.
*/
public boolean isSubTreeSearch() {
return subTreeSearch;
}
/**
* Sets whether the entire tree under the base DN will be searched (recursive search)
* when doing LDAP queries (finding users, groups, etc). When false, only a single level
* under the base DN will be searched. The default is true which is the best
* option for most LDAP setups. In only a few cases will the directory be setup in such
* a way that it's better to do single level searching.
*
* @param subTreeSearch true if the entire tree under the base DN will be searched.
*/
public void setSubTreeSearch(boolean subTreeSearch) {
this.subTreeSearch = subTreeSearch;
JiveGlobals.setXMLProperty("ldap.subTreeSearch", String.valueOf(subTreeSearch));
}
/**
* Returns the field name used for groups.
* Value of groupNameField defaults to "cn".
*
* @return the field used for groups.
*/
public String getGroupNameField() {
return groupNameField;
}
/**
* Sets the field name used for groups.
*
* @param groupNameField the field used for groups.
*/
public void setGroupNameField(String groupNameField) {
this.groupNameField = groupNameField;
JiveGlobals.setXMLProperty("ldap.groupNameField", groupNameField);
}
/**
* Return the field used to list members within a group.
* Value of groupMemberField defaults to "member".
*
* @return the field used to list members within a group.
*/
public String getGroupMemberField() {
return groupMemberField;
}
/**
* Sets the field used to list members within a group.
* Value of groupMemberField defaults to "member".
*
* @param groupMemberField the field used to list members within a group.
*/
public void setGroupmemberField(String groupMemberField) {
this.groupMemberField = groupMemberField;
JiveGlobals.setXMLProperty("ldap.groupMemberField", groupMemberField);
}
/**
* Return the field used to describe a group.
* Value of groupDescriptionField defaults to "description".
*
* @return the field used to describe a group.
*/
public String getGroupDescriptionField() {
return groupDescriptionField;
}
/**
* Sets the field used to describe a group.
* Value of groupDescriptionField defaults to "description".
*
* @param groupDescriptionField the field used to describe a group.
*/
public void setGroupDescriptionField(String groupDescriptionField) {
this.groupDescriptionField = groupDescriptionField;
JiveGlobals.setXMLProperty("ldap.groupDescriptionField", groupDescriptionField);
}
/**
* Return true if the LDAP server is operating in Posix mode. By default
* false is returned. When in Posix mode, users are stored within a group
* by their username alone. When not enabled, users are stored in a group using
* their entire DN.
*
* @return true if posix mode is being used by the LDAP server.
*/
public boolean isPosixMode() {
return posixMode;
}
/**
* Sets whether the LDAP server is operating in Posix mode. When in Posix mode,
* users are stored within a group by their username alone. When not enabled,
* users are stored in a group using their entire DN.
*
* @param posixMode true if posix mode is being used by the LDAP server.
*/
public void setPosixMode(boolean posixMode) {
this.posixMode = posixMode;
JiveGlobals.setXMLProperty("ldap.posixMode", String.valueOf(posixMode));
}
/**
* Return the field used as the search filter when searching for groups.
* Value of groupSearchFilter defaults "(groupMemberField=*)".
*
* @return the field used as the search filter when searching for groups.
*/
public String getGroupSearchFilter() {
return groupSearchFilter;
}
/**
* Sets the field used as the search filter when searching for groups.
* Value of groupSearchFilter defaults "(groupMemberField=*)".
*
* @param groupSearchFilter the field used as the search filter when searching for groups.
*/
public void setGroupSearchFilter(String groupSearchFilter) {
this.groupSearchFilter = groupSearchFilter;
JiveGlobals.setXMLProperty("ldap.groupSearchFilter", groupSearchFilter);
}
}