
com.novell.ldapchai.util.ChaiUtility Maven / Gradle / Ivy
/*
* LDAP Chai API
* Copyright (c) 2006-2010 Novell, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.novell.ldapchai.util;
import com.novell.ldapchai.ChaiConstant;
import com.novell.ldapchai.ChaiEntry;
import com.novell.ldapchai.ChaiFactory;
import com.novell.ldapchai.ChaiGroup;
import com.novell.ldapchai.ChaiPasswordPolicy;
import com.novell.ldapchai.ChaiPasswordRule;
import com.novell.ldapchai.ChaiUser;
import com.novell.ldapchai.exception.ChaiOperationException;
import com.novell.ldapchai.exception.ChaiUnavailableException;
import com.novell.ldapchai.impl.generic.entry.GenericEntryFactory;
import com.novell.ldapchai.provider.ChaiConfiguration;
import com.novell.ldapchai.provider.ChaiProvider;
import com.novell.ldapchai.provider.ChaiProviderFactory;
import com.novell.ldapchai.provider.ChaiSetting;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* A collection of static helper methods used by the LDAP Chai API.
*
* Generally, consumers of the LDAP Chai API should avoid calling these methods directly. Where possible,
* use the {@link com.novell.ldapchai.ChaiEntry} wrappers instead.
*
* @author Jason D. Rivard
*/
public class ChaiUtility {
// ----------------------------- CONSTANTS ----------------------------
// ------------------------------ FIELDS ------------------------------
private static final ChaiLogger LOGGER = ChaiLogger.getLogger(ChaiUtility.class);
// -------------------------- STATIC METHODS --------------------------
/**
* Creates a new group entry in the ldap directory. A new "groupOfNames" object is created.
* The "cn" and "description" ldap attributes are set to the supplied name.
*
* @param parentDN the entryDN of the new group.
* @param name name of the group
* @param provider a ldap provider be used to create the group.
* @return an instance of the ChaiGroup entry
* @throws ChaiOperationException If there is an error during the operation
* @throws ChaiUnavailableException If the directory server(s) are unavailable
*/
public static ChaiGroup createGroup(final String parentDN, final String name, final ChaiProvider provider)
throws ChaiOperationException, ChaiUnavailableException
{
//Get a good CN for it
final String objectCN = findUniqueName(name, parentDN, provider);
//Concantonate the entryDN
final StringBuilder entryDN = new StringBuilder();
entryDN.append("cn=");
entryDN.append(objectCN);
entryDN.append(',');
entryDN.append(parentDN);
//First create the base group.
provider.createEntry(entryDN.toString(), ChaiConstant.OBJECTCLASS_BASE_LDAP_GROUP, Collections.emptyMap());
//Now build an ldapentry object to add attributes to it
final ChaiEntry theObject = ChaiFactory.createChaiEntry(entryDN.toString(), provider);
//Add the description
theObject.writeStringAttribute(ChaiConstant.ATTR_LDAP_DESCRIPTION, name);
//Return the newly created group.
return ChaiFactory.createChaiGroup(entryDN.toString(), provider);
}
/**
* Derives a unique entry name for an ldap container. Assumes CN as the naming attribute.
*
* @param baseName A text name that will be used for the base of the obejct name. Punctuation and spaces will be stripped.
* @param containerDN Directory container in which to check for a unique name
* @param provider ChaiProvider to use for ldap connection
* @return Fully qualified unique object name for the container specified.
* @throws ChaiOperationException If there is an error during the operation
* @throws ChaiUnavailableException If the directory server(s) are unavailable
*/
public static String findUniqueName(final String baseName, final String containerDN, final ChaiProvider provider)
throws ChaiOperationException, ChaiUnavailableException
{
char ch;
final StringBuilder cnStripped = new StringBuilder();
final String effectiveBasename = (baseName == null)
? ""
: baseName;
// First boil down the root name. Preserve only the alpha-numerics.
for (int i = 0; i < effectiveBasename.length(); i++) {
ch = effectiveBasename.charAt(i);
if (Character.isLetterOrDigit(ch)) {
cnStripped.append(ch);
}
}
if (cnStripped.length() == 0) {
// Generate a random seed to runServer with, how about the current date
cnStripped.append(System.currentTimeMillis());
}
// Now we have a base name, let's runServer testing it...
String uniqueCN;
StringBuilder filter;
final Random randomNumber = new Random();
String stringCounter = null;
int counter = randomNumber.nextInt() % 1000; // Start with a random 3 digit number
while (true) {
// Initialize the String Buffer and Unique DN.
filter = new StringBuilder(64);
if (stringCounter != null) {
uniqueCN = cnStripped.append(stringCounter).toString();
} else {
uniqueCN = cnStripped.toString();
}
filter.append("(").append(ChaiConstant.ATTR_LDAP_COMMON_NAME).append("=").append(uniqueCN).append(")");
final Map> results = provider.search(containerDN, filter.toString(), null, ChaiProvider.SEARCH_SCOPE.ONE);
if (results.size() == 0) {
// No object found!
break;
} else {
// Increment it every time
stringCounter = Integer.toString(counter++);
}
}
return uniqueCN;
}
/**
* Creates a new user entry in the ldap directory. A new "inetOrgPerson" object is created in the
* ldap directory. Generally, calls to this method will typically be followed by a call to the returned
* {@link ChaiUser}'s write methods to add additional data to the ldap user entry.
*
* @param userDN the new userDN.
* @param sn the last name of
* @param provider a ldap provider be used to create the group.
* @return an instance of the ChaiUser entry
* @throws ChaiOperationException If there is an error during the operation
* @throws ChaiUnavailableException If the directory server(s) are unavailable
*/
public static ChaiUser createUser(final String userDN, final String sn, final ChaiProvider provider)
throws ChaiOperationException, ChaiUnavailableException
{
final Map createAttributes = new HashMap();
createAttributes.put(ChaiConstant.ATTR_LDAP_SURNAME, sn);
provider.createEntry(userDN, ChaiConstant.OBJECTCLASS_BASE_LDAP_USER, createAttributes);
//lets create a user object
return ChaiFactory.createChaiUser(userDN, provider);
}
/**
* Convert to an LDIF format. Useful for debugging or other purposes
*
* @param theEntry A valid {@code ChaiEntry}
* @return A string containing a properly formated LDIF view of the entry.
* @throws ChaiOperationException If there is an error during the operation
* @throws ChaiUnavailableException If the directory server(s) are unavailable
*/
public static String entryToLDIF(final ChaiEntry theEntry)
throws ChaiUnavailableException, ChaiOperationException
{
final StringBuilder sb = new StringBuilder();
sb.append("dn: ").append(theEntry.getEntryDN()).append("\n");
final Map>> results = theEntry.getChaiProvider().searchMultiValues(theEntry.getEntryDN(), "(objectClass=*)", null, ChaiProvider.SEARCH_SCOPE.BASE);
final Map> props = results.get(theEntry.getEntryDN());
for (final String attrName : props.keySet()) {
final List values = props.get(attrName);
for (final String value : values) {
sb.append(attrName).append(": ").append(value).append('\n');
}
}
return sb.toString();
}
private static ChaiEntry findPartitionRoot(final ChaiEntry theEntry)
throws ChaiUnavailableException, ChaiOperationException
{
ChaiEntry loopEntry = theEntry;
while (loopEntry != null) {
final Set objClasses = loopEntry.readMultiStringAttribute(ChaiConstant.ATTR_LDAP_OBJECTCLASS);
if (objClasses.contains(ChaiConstant.OBJECTCLASS_BASE_LDAP_PARTITION)) {
return loopEntry;
}
loopEntry = loopEntry.getParentEntry();
}
return null;
}
/**
* Test the replication of an attribute. It is left to the implementation to determine the means and criteria for
* this operation. Typically this method would be used just after a write operation in some type of time delayed loop.
*
* Typical implementations will do the following:
*
* - issue {@link com.novell.ldapchai.ChaiEntry#readStringAttribute(String)} to read a value
* - establish an LDAP connection to all known replicas
* - issue {@link com.novell.ldapchai.ChaiEntry#compareStringAttribute(String, String)} to to each server directly
* - return true if each server contacted has the same value, false if not
*
*
* Target servers that are unreachable or return errors are ignored, and do not influence the results. It is entirely
* possible that no matter how many times this method is called, false will always be returned, so the caller should
* take care not to repeat a test indefinitly.
*
* This operation is potentially expensive, as it may establish new LDAP level connections to each target server each
* time it is invoked.
*
* The following sample shows how this method might be used. There are a few important attributes of the sample:
*
* - Multiple ldap servers are specified
* - There is a pause time between each replication check (the test can be expensive)
* - There is a timeout period (the test may never successfully complete)
*
*
* ChaiUser theUser = // create a new chai user.
* ChaiFactory.quickProvider("ldap://ldaphost,ldap://ldaphost2","cn=admin,ou=ou,o=o","novell");
*
* theUser.writeStringAttributes("description","testValue" + (new Random()).nextInt()); // write a random value to an attribute
*
* final int maximumWaitTime = 120 * 1000; // maximum time to wait for replication
* final int pauseTime = 3 * 1000; // time between iterations
*
* final long startTime = System.currentTimeMillis(); // timestamp of beginning of wait
* boolean replicated = false;
* while (System.currentTimeMillis() - startTime < maximumWaitTime) { // loop until
* try { Thread.sleep(pauseTime); } catch (InterruptedException e) {} // sleep between iterations
* replicated = ChaiUtility.testAttributeReplication(theUser,"description",null); // check if data replicated yet
* if (replicated) {
* break; // break if data has replicated
* }
* }
* System.out.println("Attribute replication successful: " + replicated); // report success
*
*
* @param chaiEntry A valid entry
* @param attribute A valid attribute on the entry
* @param value The value to test for. If {@code null}, a value is read from the active server
* @return true if the attribute is the same on all servers
* @throws ChaiOperationException If an error is encountered during the operation
* @throws ChaiUnavailableException If no directory servers are reachable
* @throws IllegalStateException If the underlying connection is not in an available state
*/
public static boolean testAttributeReplication(final ChaiEntry chaiEntry, final String attribute, final String value)
throws ChaiOperationException, ChaiUnavailableException
{
final String effectiveValue = (value == null || value.length() < 1)
? chaiEntry.readStringAttribute(attribute)
: value;
if (effectiveValue == null) {
throw ChaiOperationException.forErrorMessage("unreadable to read test attribute from primary ChaiProvider");
}
final ChaiConfiguration chaiConfiguration = chaiEntry.getChaiProvider().getChaiConfiguration();
final List ldapURLs = chaiConfiguration.bindURLsAsList();
LOGGER.trace("testAttributeReplication, will test the following ldap urls: " + ldapURLs);
int testCount = 0;
int successCount = 0;
final Collection perReplicaProviders = splitConfigurationPerReplica(
chaiEntry.getChaiProvider().getChaiConfiguration(),
Collections.singletonMap(ChaiSetting.FAILOVER_CONNECT_RETRIES, "1")
);
for (final ChaiConfiguration loopConfiguration : perReplicaProviders) {
ChaiProvider loopProvider = null;
try {
loopProvider = ChaiProviderFactory.createProvider(loopConfiguration);
if (loopProvider.compareStringAttribute(chaiEntry.getEntryDN(), attribute, effectiveValue)) {
successCount++;
}
testCount++;
} catch (ChaiUnavailableException e) {
//disregard
} catch (ChaiOperationException e) {
//disregard
} finally {
try {
if (loopProvider != null) {
loopProvider.close();
}
} catch (Exception e) {
//already closed, whatever.
}
}
}
if (LOGGER.isDebugEnabled()) {
final StringBuilder debugMsg = new StringBuilder();
debugMsg.append("testAttributeReplication for ").append(chaiEntry).append(":").append(attribute);
debugMsg.append(" ").append(testCount).append(" up,");
debugMsg.append(" ").append(ldapURLs.size() - testCount).append(" down,");
debugMsg.append(" ").append(successCount).append(" in sync");
LOGGER.debug(debugMsg);
}
return testCount > 0 && testCount == successCount;
}
public static Collection splitConfigurationPerReplica(
final ChaiConfiguration chaiConfiguration,
final Map additionalSettings
) {
final Collection returnProviders = new ArrayList();
final List ldapURLs = chaiConfiguration.bindURLsAsList();
for (final String loopURL : ldapURLs) {
final ChaiConfiguration loopConfig = new ChaiConfiguration(chaiConfiguration);
loopConfig.setSetting(ChaiSetting.BIND_URLS, loopURL);
if (additionalSettings != null) {
for (final ChaiSetting setting : additionalSettings.keySet()) {
final String value = additionalSettings.get(setting);
loopConfig.setSetting(setting,value);
}
}
returnProviders.add(loopConfig);
}
return returnProviders;
}
// --------------------------- CONSTRUCTORS ---------------------------
private ChaiUtility()
{
}
public static String passwordPolicyToString(final ChaiPasswordPolicy policy) {
if (policy == null) {
throw new NullPointerException("null ChaiPasswordPolicy can not be converted to string");
}
final StringBuilder sb = new StringBuilder();
sb.append("ChaiPasswordPolicy: ");
if (!policy.getKeys().isEmpty()) {
sb.append("{");
for (final String key : policy.getKeys()) {
final ChaiPasswordRule rule = ChaiPasswordRule.forKey(key);
sb.append(rule == null ? key : rule);
sb.append("=");
sb.append(policy.getValue(key));
sb.append(", ");
}
sb.delete(sb.length() - 2,sb.length());
sb.append("}");
} else {
sb.append("[empty]");
}
return sb.toString();
}
/**
* Determines the vendor of a the ldap directory by reading RootDSE attributes
* @param rootDSE A valid entry
* @return the proper directory vendor, or {@link com.novell.ldapchai.provider.ChaiProvider.DIRECTORY_VENDOR#GENERIC} if the vendor can not be determined.
* @throws ChaiUnavailableException If the directory is unreachable
* @throws ChaiOperationException If there is an error reading values from the Root DSE entry
*/
public static ChaiProvider.DIRECTORY_VENDOR determineDirectoryVendor(final ChaiEntry rootDSE)
throws ChaiUnavailableException, ChaiOperationException
{
final String[] interestingAttributes = {
"vendorVersion",
"vendorName",
"rootDomainNamingContext",
"objectClass",
};
final SearchHelper searchHelper = new SearchHelper();
searchHelper.setAttributes(interestingAttributes);
searchHelper.setFilter("(objectClass=*)");
searchHelper.setMaxResults(1);
searchHelper.setSearchScope(ChaiProvider.SEARCH_SCOPE.BASE);
final Map>> results = rootDSE.getChaiProvider().searchMultiValues("",searchHelper);
if (results != null && results.size() == 1) {
final Map> rootDseSearchResults = results.get("");
if (rootDseSearchResults != null) {
final List vendorVersions = rootDseSearchResults.get("vendorVersion") == null ? Collections.emptyList() : rootDseSearchResults.get("vendorVersion");
final List vendorNames = rootDseSearchResults.get("vendorName") == null ? Collections.emptyList() : rootDseSearchResults.get("vendorName");
final List rootDomainNamingContexts = rootDseSearchResults.get("rootDomainNamingContext") == null ? Collections.emptyList() : rootDseSearchResults.get("rootDomainNamingContext");
final List objectClasses = rootDseSearchResults.get("objectClass") == null ? Collections.emptyList() : rootDseSearchResults.get("objectClass");
{ // try to detect Novell eDirectory
for (final String vendorVersionValue : vendorVersions) {
if (vendorVersionValue.contains("eDirectory")) {
return ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY;
}
}
}
{ // try to detect ms-active directory
for (final String rootDomainNamingContextValue : rootDomainNamingContexts) {
if (rootDomainNamingContextValue.contains("DC=")) {
return ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY;
}
}
}
{ // try to detect oracle ds
for (final String vendorVersion : vendorVersions) {
if (vendorVersion.contains("Sun-Directory-Server") || vendorVersion.contains("Oracle-Directory-Server")) {
return ChaiProvider.DIRECTORY_VENDOR.ORACLE_DS;
}
}
}
{ // try to detect 389 Directory
for (final String vendorNamesValue : vendorNames) {
if (vendorNamesValue.contains("389 Project")) {
return ChaiProvider.DIRECTORY_VENDOR.DIRECTORY_SERVER_389;
}
}
for (final String vendorVersionsValue : vendorVersions) {
if (vendorVersionsValue.contains("389-Directory")) {
return ChaiProvider.DIRECTORY_VENDOR.DIRECTORY_SERVER_389;
}
}
}
{ // try to detect openLDAP
for (final String objectClassValue : objectClasses) {
if (objectClassValue.contains("OpenLDAProotDSE")) {
return ChaiProvider.DIRECTORY_VENDOR.OPEN_LDAP;
}
}
}
}
}
return ChaiProvider.DIRECTORY_VENDOR.GENERIC;
}
public static ChaiEntry getRootDSE(final ChaiProvider provider)
throws ChaiUnavailableException
{
final ChaiConfiguration rootDSEChaiConfig = new ChaiConfiguration(provider.getChaiConfiguration());
final String ldapUrls = rootDSEChaiConfig.getSetting(ChaiSetting.BIND_URLS);
final String[] splitUrls = ldapUrls.split(ChaiConfiguration.LDAP_URL_SEPERATOR_REGEX_PATTERN);
final StringBuilder newUrlConfig = new StringBuilder();
boolean currentURLsHavePath = false;
for (final String splitUrl : splitUrls) {
final URI uri = URI.create(splitUrl);
final String newURI = uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort();
newUrlConfig.append(newURI);
if (uri.getPath() != null && uri.getPath().length() > 0) {
currentURLsHavePath = true;
}
newUrlConfig.append(",");
}
rootDSEChaiConfig.setSetting(ChaiSetting.BIND_URLS,newUrlConfig.toString());
final ChaiProvider rootDseProvider = currentURLsHavePath ? ChaiProviderFactory.createProvider(rootDSEChaiConfig) : provider;
// can not call the ChaiFactory here, because ChaiFactory in turn calls this method to get the
// directory vendor. Instead, we will go directly to the Generic ChaiFactory
final GenericEntryFactory genericEntryFactory = new GenericEntryFactory();
return genericEntryFactory.createChaiEntry("",rootDseProvider);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy