001package com.identityworksllc.iiq.common;
002
003import sailpoint.tools.Util;
004
005import javax.naming.InvalidNameException;
006import javax.naming.ldap.LdapName;
007import java.util.List;
008
009/**
010 * Utilities for dealing with LDAP DNs and other similar concepts
011 */
012public class LdapUtilities {
013    /**
014     * Extracts the name from an LDAP formatted group name. For example, if given CN=AD Group Name,OU=Groups,OU=Security,DC=client,DC=example,DC=com, this method would return "AD Group Name".
015     *
016     * @param groupDN The group DN
017     * @return the group name
018     * @throws InvalidNameException if this is not an LDAP name
019     */
020    public static String ldapCleanGroupName(String groupDN) throws InvalidNameException {
021        String groupName = groupDN;
022        // Handle goofy OIM format if needed
023        if (groupName.contains("~")) {
024            groupName = groupName.substring(groupName.indexOf("~") + 1);
025        }
026        LdapName parser = new LdapName(groupName);
027        String firstElem = parser.get(parser.size() - 1);
028        if (firstElem.length() > 0 && firstElem.contains("=")) {
029            return firstElem.substring(firstElem.indexOf("=") + 1);
030        }
031        throw new IllegalArgumentException("Group name " + groupName + " does not appear to be in LDAP format");
032    }
033
034    /**
035     * Returns true if the given list of DNs contains a matching DN by RDN.
036     * This is useful for searching a list of AD groups (e.g., user entitlements)
037     * for a given value, without having to worry about differing domain suffixes
038     * across dev, test, and prod.
039     *
040     * Equivalent to {@link #ldapContains(List, String, int)} with a depth of 1.
041     *
042     * @param container A list of candidate DNs
043     * @param seeking The DN (whole or partial) to match
044     * @return True if the list contains a matching DN, false if not
045     */
046    public static boolean ldapContains(List<String> container, String seeking) {
047        return ldapContains(container, seeking, 1);
048    }
049
050    /**
051     * Returns true if the given list of DNs contains a value matching the given
052     * 'seeking' DN, up to the given depth.
053     *
054     * @param container A list of candidate DNs
055     * @param seeking The DN (whole or partial) to match
056     * @param depth The depth of search
057     * @return True if the list contains a matching DN, false if not
058     */
059    public static boolean ldapContains(List<String> container, String seeking, int depth) {
060        for(String dn : Util.safeIterable(container)) {
061            if (ldapMatches(dn, seeking, depth)) {
062                return true;
063            }
064        }
065        return false;
066    }
067
068    /**
069     * Given a list of possible matching DNs (the container), finds the first
070     * one that matches the RDN of the 'seeking' string.
071     *
072     * @param container A list of candidate DNs
073     * @param seeking The DN we are seeking to match
074     * @return The DN matching the search, or null if none is found
075     */
076    public static String ldapGetMatch(List<String> container, String seeking) {
077        return ldapGetMatch(container, seeking, 1);
078    }
079
080    /**
081     * Given a list of possible matching DNs (the container), finds the first
082     * one that matches the 'seeking' string up to the given depth.
083     *
084     * @param container A list of candidate DNs
085     * @param seeking The DN we are seeking
086     * @param depth The number of RDN components to match
087     * @return The DN matching the search, or null if none is found
088     */
089    public static String ldapGetMatch(List<String> container, String seeking, int depth) {
090        for(String dn : Util.safeIterable(container)) {
091            if (ldapMatches(dn, seeking, depth)) {
092                return dn;
093            }
094        }
095        return null;
096    }
097
098    /**
099     * Extracts the first N RDNs from an LDAP formatted DN. For example,
100     * if given CN=AD Group Name,OU=Groups,OU=Security,DC=client,DC=example,DC=com,
101     * and a size of 1, this method would return "CN=AD Group Name". A size of 2
102     * would produce "CN=AD Group Name,OU=Groups".
103     *
104     * @param dn The object's distinguishedName
105     * @param size The number of RDN elements to return
106     * @return the first 'size' RDNs of the DN
107     * @throws InvalidNameException if this is not an LDAP name
108     */
109    public static String ldapGetRdn(String dn, int size) throws InvalidNameException {
110        LdapName parser = new LdapName(dn);
111        return ldapGetRdn(parser, size);
112    }
113
114    /**
115     * Extracts the first N RDNs from an LDAP formatted DN. For example,
116     * if given CN=AD Group Name,OU=Groups,OU=Security,DC=client,DC=example,DC=com,
117     * and a size of 1, this method would return "CN=AD Group Name". A size of 2
118     * would produce "CN=AD Group Name,OU=Groups".
119     *
120     * @param name The already-parsed LdapName object
121     * @param size The number of RDN elements to return
122     * @return the first 'size' RDNs of the DN
123     * @throws InvalidNameException if this is not an LDAP name
124     */
125    public static String ldapGetRdn(LdapName name, int size) throws InvalidNameException {
126        StringBuilder builder = new StringBuilder();
127        for(int i = 1; i <= size && i <= name.size(); i++) {
128            if (builder.length() > 0) {
129                builder.append(",");
130            }
131
132            // LDAP names are sorted like a file path, with the RDN last
133            builder.append(name.get(name.size() - i));
134        }
135        return builder.toString();
136    }
137
138    /**
139     * Returns true if the first element of the given LDAP name matches the value provided.
140     *
141     * Equivalent to ldapMatches(name, otherName, 1).
142     *
143     * @param name The LDAP name to check
144     * @param otherName The other name to compare against
145     * @return True if the names are LDAP DNs and equal ignoring case, otherwise false
146     */
147    public static boolean ldapMatches(String name, String otherName) {
148        return ldapMatches(name, otherName, 1);
149    }
150
151    /**
152     * Returns true if the given objects match by comparing them as LDAP DNs up
153     * to the depth specified. For example, the following two DNs will match at
154     * a depth of 1, but not a depth of 2.
155     *
156     * CN=Group Name,OU=Groups,DC=test,DC=example,DC=com
157     * cn=group name,OU=Administrators,DC=test,DC=example,DC=com
158     *
159     * This is primarily useful with AD environments where the group names will
160     * have a suffix varying by domain.
161     *
162     * @param name The first LDAP name
163     * @param otherName The second LDAP name
164     * @param depth The number of DN elements to search for a match (with 1 being the RDN only)
165     * @return True if the names are indeed LDAP DNs and equal ignoring case, false otherwise
166     */
167    public static boolean ldapMatches(String name, String otherName, int depth) {
168        if (Util.nullSafeCaseInsensitiveEq(name, otherName)) {
169            return true;
170        }
171        try {
172            return Util.nullSafeCaseInsensitiveEq(ldapGetRdn(name, depth), ldapGetRdn(otherName, depth));
173        } catch(Exception e) {
174            return false;
175        }
176    }
177
178    /**
179     * Private utility constructor
180     */
181    private LdapUtilities() {
182
183    }
184}