/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. * * You can obtain a copy of the License at * http://IdentityConnectors.dev.java.net/legal/license.txt * See the License for the specific language governing permissions and limitations * under the License. * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at identityconnectors/legal/license.txt. * If applicable, add the following below this CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * * Portions Copyrighted 2013-2014 ForgeRock AS */ package org.identityconnectors.ldap; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Set; import javax.naming.InvalidNameException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapName; import org.identityconnectors.common.CollectionUtil; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.logging.Log; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.objects.AttributeBuilder; import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder; import org.identityconnectors.framework.common.objects.ObjectClass; import static org.identityconnectors.framework.common.objects.ObjectClassUtil.createSpecialName; import org.identityconnectors.framework.common.objects.ResultsHandler; import static org.identityconnectors.ldap.LdapEntry.isDNAttribute; import org.identityconnectors.ldap.search.LdapInternalSearch; public class LdapUtil { private static final Log log = Log.getLog(LdapUtil.class); private static final String LDAP_BINARY_OPTION = ";binary"; public static final String SERVER_INFO_NAME = createSpecialName("SERVER_INFO"); public static final ObjectClass SERVER_INFO_OBJCLASS = new ObjectClass(SERVER_INFO_NAME); private static final String UNKNOWN_OBJCLASS_NAME = createSpecialName("UNKNOWN"); public static final ObjectClass UNKNOWN_OBJCLASS = new ObjectClass(UNKNOWN_OBJCLASS_NAME); private LdapUtil() { } /** * Returns true if the names of the given LDAP attributes are equal. Deals * with null values as a convenience. */ public static boolean attrNameEquals(String name1, String name2) { if (name1 == null) { return name2 == null; } return name1.equalsIgnoreCase(name2); } /** * Returns {@code true} if the attribute has the binary option, * e.g., {@code userCertificate;binary}. */ public static boolean hasBinaryOption(String ldapAttrName) { return ldapAttrName.toLowerCase(Locale.US).endsWith(LDAP_BINARY_OPTION); } /** * Adds the binary option to the attribute if not present already. */ public static String addBinaryOption(String ldapAttrName) { if (!hasBinaryOption(ldapAttrName)) { return ldapAttrName + LDAP_BINARY_OPTION; } return ldapAttrName; } /** * Removes the binary option from the attribute. */ public static String removeBinaryOption(String ldapAttrName) { if (hasBinaryOption(ldapAttrName)) { return ldapAttrName.substring(0, ldapAttrName.length() - LDAP_BINARY_OPTION.length()); } return ldapAttrName; } /** * Return the value of the {@code ldapAttrName} parameter cast to a String. */ public static String getStringAttrValue(Attributes ldapAttrs, String ldapAttrName) { Attribute attr = ldapAttrs.get(ldapAttrName); if (attr != null) { try { return (String) attr.get(); } catch (NamingException e) { throw new ConnectorException(e); } } return null; } /** * Return the <b>case insensitive</b> set of values of the {@code * ldapAttrName} parameter cast to a String. */ public static Set<String> getStringAttrValues(Attributes ldapAttrs, String ldapAttrName) { Set<String> result = new HashSet<String>(); addStringAttrValues(ldapAttrs, ldapAttrName, result); return result; } public static void addStringAttrValues(Attributes ldapAttrs, String ldapAttrName, Set<String> toSet) { Attribute attr = ldapAttrs.get(ldapAttrName); if (attr == null) { return; } try { NamingEnumeration<?> attrEnum = attr.getAll(); while (attrEnum.hasMore()) { toSet.add((String) attrEnum.next()); } } catch (NamingException e) { throw new ConnectorException(e); } } /** * Escape DN value of JNDI reserved characters * forward slash (/) */ public static String escapeDNValueOfJNDIReservedChars(String DN) { StringBuilder toBuilder = new StringBuilder(); for (int i = 0; i < DN.length(); i++) { char ch = DN.charAt(i); switch (ch) { case '/': toBuilder.append("\\2f"); break; default: toBuilder.append(ch); } } return toBuilder.toString(); } /** * Escapes the given attribute value to the given {@code StringBuilder}. * Returns {@code true} iff anything was written to the builder. */ public static boolean escapeAttrValue(Object value, StringBuilder toBuilder) { if (value == null) { return false; } if (value instanceof byte[]) { return escapeByteArrayAttrValue((byte[]) value, toBuilder); } else { return escapeStringAttrValue(value.toString(), toBuilder); } } /** * Normalize the DN string * @param ldapString DN string to normalize * @return The normalized DN string */ public static String normalizeLdapString(String ldapString) { StringBuilder normalPath = new StringBuilder(); String[] parts = ldapString.split(","); for (int i = 0; i < parts.length; i++) { normalPath.append(parts[i].trim()); // append a comma after each part (except the last one) if (i < (parts.length - 1)) { normalPath.append(","); } } return normalPath.toString(); } private static boolean escapeByteArrayAttrValue(byte[] bytes, StringBuilder toBuilder) { if (bytes.length == 0) { return false; } for (byte b : bytes) { toBuilder.append('\\'); String hex = Integer.toHexString(b & 0xff); // Make a negative byte positive. if (hex.length() < 2) { toBuilder.append('0'); } toBuilder.append(hex); } return true; } private static boolean escapeStringAttrValue(String string, StringBuilder toBuilder) { if (StringUtil.isEmpty(string)) { return false; } for (int i = 0; i < string.length(); i++) { char ch = string.charAt(i); switch (ch) { case '*': toBuilder.append("\\2a"); break; case '(': toBuilder.append("\\28"); break; case ')': toBuilder.append("\\29"); break; case '\\': toBuilder.append("\\5c"); break; case '\0': toBuilder.append("\\00"); break; default: toBuilder.append(ch); } } return true; } public static LdapName quietCreateLdapName(String ldapName) { try { return new LdapName(ldapName); } catch (InvalidNameException e) { throw new ConnectorException(e); } } public static boolean isUnderContexts(LdapName entry, List<LdapName> contexts) { for (LdapName context : contexts) { if (entry.startsWith(context)) { return true; } } return false; } public static String[] nullAsEmpty(String[] array) { if (array == null) { return new String[0]; } return array; } @SuppressWarnings("unchecked") public static <T> List<T> checkedListByFilter(List list, Class<T> clazz) { return new CheckedListByFilter<T>(list, clazz); } @SuppressWarnings("unchecked") private static final class CheckedListByFilter<E> implements List<E> { private final List list; private final Class<E> clazz; public CheckedListByFilter(List list, Class<E> clazz) { this.clazz = clazz; this.list = list; } private E cast(Object o) { return clazz.cast(o); } public boolean add(E o) { return list.add(o); } public void add(int index, E element) { list.add(index, element); } public boolean addAll(Collection<? extends E> c) { return list.addAll(c); } public boolean addAll(int index, Collection<? extends E> c) { return list.addAll(index, c); } public void clear() { list.clear(); } public boolean contains(Object o) { return list.contains(o); } public boolean containsAll(Collection<?> c) { return list.containsAll(c); } public E get(int index) { return cast(list.get(index)); } public int indexOf(Object o) { return list.indexOf(o); } public boolean isEmpty() { return list.isEmpty(); } public Iterator<E> iterator() { return new Itr(list.iterator()); } public int lastIndexOf(Object o) { return list.lastIndexOf(o); } public ListIterator<E> listIterator() { return new ListItr(list.listIterator()); } public ListIterator<E> listIterator(int index) { return new ListItr(list.listIterator(index)); } public boolean remove(Object o) { return list.remove(o); } public E remove(int index) { return cast(list.remove(index)); } public boolean removeAll(Collection<?> c) { return list.removeAll(c); } public boolean retainAll(Collection<?> c) { return list.retainAll(c); } public E set(int index, E element) { return cast(list.set(index, element)); } public int size() { return list.size(); } public List<E> subList(int fromIndex, int toIndex) { return list.subList(fromIndex, toIndex); } public Object[] toArray() { return list.toArray(); } public <T> T[] toArray(T[] a) { Object[] result = list.toArray(a); for (Object o : result) { cast(o); } return (T[]) result; } @Override public boolean equals(Object obj) { return list.equals(obj); } @Override public int hashCode() { return list.hashCode(); } @Override public String toString() { return list.toString(); } private final class Itr implements Iterator<E> { private final Iterator iter; public Itr(Iterator iter) { this.iter = iter; } public boolean hasNext() { return iter.hasNext(); } public E next() { return cast(iter.next()); } public void remove() { iter.remove(); } @Override public boolean equals(Object obj) { return iter.equals(obj); } @Override public int hashCode() { return iter.hashCode(); } @Override public String toString() { return iter.toString(); } } private final class ListItr implements ListIterator<E> { private final ListIterator iter; public ListItr(ListIterator iter) { this.iter = iter; } public void add(E o) { iter.add(o); } public boolean hasNext() { return iter.hasNext(); } public boolean hasPrevious() { return iter.hasPrevious(); } public E next() { return cast(iter.next()); } public int nextIndex() { return iter.nextIndex(); } public E previous() { return cast(iter.previous()); } public int previousIndex() { return iter.previousIndex(); } public void remove() { iter.remove(); } public void set(E o) { iter.set(o); } @Override public boolean equals(Object obj) { return iter.equals(obj); } @Override public int hashCode() { return iter.hashCode(); } @Override public String toString() { return iter.toString(); } } } private static String getIDfromDN(LdapConnection conn, String dn) throws NamingException { if (isDNAttribute(conn.getConfiguration().getUidAttribute())) { return dn; } else { SearchControls controls = LdapInternalSearch.createDefaultSearchControls(); controls.setSearchScope(SearchControls.OBJECT_SCOPE); controls.setReturningAttributes(new String[]{conn.getConfiguration().getUidAttribute()}); LdapContext context = conn.getInitialContext().newInstance(null); NamingEnumeration<SearchResult> entries = context.search(escapeDNValueOfJNDIReservedChars(dn), "objectclass=*", controls); SearchResult res = entries.next(); String uidAttr = conn.getConfiguration().getUidAttribute(); if (LdapConstants.MS_GUID_ATTR.equalsIgnoreCase(uidAttr)) { return (ADLdapUtil.objectGUIDtoString(res.getAttributes().get(conn.getConfiguration().getUidAttribute()))); } else { return (res.getAttributes().get(conn.getConfiguration().getUidAttribute()).get(0).toString()); } } } // This function builds a _memberId attribute which is a helper // that contains the group members' GUID public static org.identityconnectors.framework.common.objects.Attribute buildMemberIdAttribute(LdapConnection conn, javax.naming.directory.Attribute attr) { ArrayList<String> membersIds = new ArrayList<String>(); try { if (attr != null) { NamingEnumeration<?> vals = attr.getAll(); while (vals.hasMore()) { membersIds.add(getIDfromDN(conn, vals.next().toString())); } } } catch (NamingException e) { log.warn(e,"Error reading group member attribute"); } return AttributeBuilder.build("_memberId", membersIds); } public static org.identityconnectors.framework.common.objects.Attribute buildMemberIdAttribute(LdapConnection conn, org.identityconnectors.framework.common.objects.Attribute attr) { ArrayList<String> membersIds = new ArrayList<String>(); try { if (attr != null) { for(Object val: attr.getValue()){ membersIds.add(getIDfromDN(conn, val.toString())); } } } catch (NamingException e) { log.warn(e,"Error reading group member attribute"); } return AttributeBuilder.build("_memberId", membersIds); } public static void getServerInfo(LdapConnection conn, ResultsHandler handler){ ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); builder.setObjectClass(SERVER_INFO_OBJCLASS); builder.setUid(conn.getServerType().name()); builder.setName(conn.getServerType().name()); handler.handle(builder.build()); } public static String getObjectClassFilter(String[] oclasses) { StringBuilder filter = new StringBuilder(); if (oclasses.length > 1) { filter.append("(|"); for (String oclasse : oclasses) { filter.append("(objectClass="); filter.append(oclasse); filter.append(")"); } filter.append(")"); } else { filter.append("(objectClass="); filter.append(oclasses[0]); filter.append(")"); } return filter.toString(); } public static ObjectClass guessObjectClass(LdapConnection lconn, javax.naming.directory.Attribute attr) throws NamingException{ NamingEnumeration<?> valuesEnum = attr.getAll(); Set values = CollectionUtil.newCaseInsensitiveSet(); while (valuesEnum.hasMore()) { values.add((String)valuesEnum.next()); } // Account for (String oc: lconn.getConfiguration().getAccountObjectClasses()) { if (values.contains(oc)) { log.info("Account ObjectClass found based on objectClass attribute value: {0}", oc); return ObjectClass.ACCOUNT; } } // Group for (String oc: lconn.getConfiguration().getGroupObjectClasses()) { if (values.contains(oc)) { log.info("Group ObjectClass found based on objectClass attribute value: {0}", oc); return ObjectClass.GROUP; } } // now... let's guess for the best... for (String oc: lconn.getConfiguration().getObjectClassesToSynchronize()) { if (values.contains(oc)) { log.info("{0} ObjectClass found based on objectClass attribute value", oc); return new ObjectClass(oc); } } log.info("No suitable ObjectClass found based on objectClass attribute value. Returning UNKNOWN ObjectClass"); return UNKNOWN_OBJCLASS; } public static boolean isSameDistinguishedName(String first, LdapContext context) { try { LdapName lfirst = new LdapName(first); LdapName lsecond = new LdapName(context.getEnvironment().get("java.naming.security.principal").toString()); return lfirst.equals(lsecond); } catch (NamingException ex) { log.info("Can't compare DN {0} with the operation's context DN", first); } return false; } }