/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2007-2008 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 */ package org.identityconnectors.ldap; import static java.util.Collections.singletonList; import static org.identityconnectors.ldap.LdapUtil.escapeAttrValue; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.naming.NamingException; import javax.naming.directory.AttributeInUseException; import javax.naming.directory.BasicAttribute; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import javax.naming.directory.SearchResult; import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapName; import org.identityconnectors.common.logging.Log; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.ldap.search.LdapSearches; import org.identityconnectors.ldap.search.LdapSearchResultsHandler; public class GroupHelper { private static final Log log = Log.getLog(GroupHelper.class); private final LdapConnection conn; public GroupHelper(LdapConnection conn) { this.conn = conn; } /** * Returns the attribute which POSIX group references its members. * The members of a POSIX groups are held in the <code>memberUid</code> * attributes, and the values of this attributes are the <code>uid</code> * attributes of the group members. So this method returns <code>"uid"</code>. */ public static String getPosixRefAttribute() { return "uid"; } private String getLdapGroupMemberAttribute() { String memberAttr = conn.getConfiguration().getGroupMemberAttribute(); if (memberAttr == null) { memberAttr = "uniqueMember"; // For groupOfUniqueNames. } return memberAttr; } public List<String> getLdapGroups(String entryDN) { log.ok("Retrieving LDAP groups for {0}", entryDN); String filter = createAttributeFilter(getLdapGroupMemberAttribute(), singletonList(entryDN)); ToDNHandler handler = new ToDNHandler(); LdapSearches.findEntries(handler, conn, filter); return handler.getResults(); } public Set<GroupMembership> getLdapGroupMemberships(String entryDN) { log.ok("Retrieving LDAP group memberships for {0}", entryDN); String filter = createAttributeFilter(getLdapGroupMemberAttribute(), singletonList(entryDN)); ToGroupMembershipHandler handler = new ToGroupMembershipHandler(); handler.setMemberRef(entryDN); LdapSearches.findEntries(handler, conn, filter); return handler.getResults(); } public void addLdapGroupMemberships(String entryDN, Collection<String> groupDNs, LdapContext context) { log.ok("Adding {0} to LDAP groups {1}", entryDN, groupDNs); String ldapGroupMemberAttribute = getLdapGroupMemberAttribute(); for (String groupDN : groupDNs) { addMemberToGroup(ldapGroupMemberAttribute, entryDN, groupDN, context); } } public void removeLdapGroupMemberships(String entryDN, Collection<String> groupDNs, LdapContext context) { log.ok("Removing {0} from LDAP groups {1}", entryDN, groupDNs); String ldapGroupMemberAttribute = getLdapGroupMemberAttribute(); for (String groupDN : groupDNs) { removeMemberFromGroup(ldapGroupMemberAttribute, entryDN, groupDN, context); } } public void modifyLdapGroupMemberships(Modification<GroupMembership> mod, LdapContext context) { log.ok("Modifying LDAP group memberships: removing {0}, adding {1}", mod.getRemoved(), mod.getAdded()); String ldapGroupMemberAttribute = getLdapGroupMemberAttribute(); for (GroupMembership membership : mod.getRemoved()) { removeMemberFromGroup(ldapGroupMemberAttribute, membership.getMemberRef(), membership.getGroupDN(), context); } for (GroupMembership membership : mod.getAdded()) { addMemberToGroup(ldapGroupMemberAttribute, membership.getMemberRef(), membership.getGroupDN(), context); } } public List<String> getPosixGroups(Collection<String> posixRefAttrs) { log.ok("Retrieving POSIX groups for {0}", posixRefAttrs); String filter = createAttributeFilter("memberUid", posixRefAttrs); ToDNHandler handler = new ToDNHandler(); LdapSearches.findEntries(handler, conn, filter); return handler.getResults(); } public Set<GroupMembership> getPosixGroupMemberships(Collection<String> posixRefAttrs) { log.ok("Retrieving POSIX group memberships for {0}", posixRefAttrs); ToGroupMembershipHandler handler = new ToGroupMembershipHandler(); if (posixRefAttrs != null){ for (String posixRefAttr : posixRefAttrs) { String filter = createAttributeFilter("memberUid", singletonList(posixRefAttr)); handler.setMemberRef(posixRefAttr); LdapSearches.findEntries(handler, conn, filter); } } return handler.getResults(); } public void addPosixGroupMemberships(String posixRefAttr, Collection<String> groupDNs, LdapContext context) { log.ok("Adding {0} to POSIX groups {1}", posixRefAttr, groupDNs); for (String groupDN : groupDNs) { addMemberToGroup("memberUid", posixRefAttr, groupDN, context); } } public void removePosixGroupMemberships(Set<GroupMembership> memberships, LdapContext context) { log.ok("Removing POSIX group memberships {0}", memberships); for (GroupMembership membership : memberships) { removeMemberFromGroup("memberUid", membership.getMemberRef(), membership.getGroupDN(), context); } } public void modifyPosixGroupMemberships(Modification<GroupMembership> mod, LdapContext context) { log.ok("Modifying POSIX group memberships: removing {0}, adding {1}", mod.getRemoved(), mod.getAdded()); for (GroupMembership membership : mod.getRemoved()) { removeMemberFromGroup("memberUid", membership.getMemberRef(), membership.getGroupDN(), context); } for (GroupMembership membership : mod.getAdded()) { addMemberToGroup("memberUid", membership.getMemberRef(), membership.getGroupDN(), context); } } private String createAttributeFilter(String memberAttr, Collection<?> memberValues) { StringBuilder builder = new StringBuilder(); boolean multi = memberValues.size() > 1; if (multi) { builder.append("(|"); } for (Object memberValue : memberValues) { builder.append('('); builder.append(memberAttr); builder.append('='); escapeAttrValue(memberValue, builder); builder.append(')'); } if (multi) { builder.append(")"); } return builder.toString(); } private void addMemberToGroup(String memberAttr, String memberValue, String groupDN, LdapContext context) { BasicAttribute attr = new BasicAttribute(memberAttr, memberValue); ModificationItem item = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr); try { if (context != null){ context.modifyAttributes(groupDN, new ModificationItem[] { item }); } else { conn.getInitialContext().modifyAttributes(groupDN, new ModificationItem[] { item }); } } catch (AttributeInUseException e) { //throw new ConnectorException(conn.format("memberAlreadyInGroup", null, memberValue, groupDN), e); log.ok("Duplicate value when adding {0} to {1}",memberValue,groupDN); } catch (NamingException e) { throw new ConnectorException(e); } } private void removeMemberFromGroup(String memberAttr, String memberValue, String groupDN, LdapContext context) { BasicAttribute attr = new BasicAttribute(memberAttr, memberValue); ModificationItem item = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr); try { if (context != null){ context.modifyAttributes(groupDN, new ModificationItem[]{item}); } else { conn.getInitialContext().modifyAttributes(groupDN, new ModificationItem[]{item}); } } catch (NamingException e) { throw new ConnectorException(e); } } public static final class GroupMembership { private final String memberRef; private final String groupDN; public GroupMembership(String memberRef, String groupDn) { this.memberRef = memberRef; this.groupDN = groupDn; } public String getMemberRef() { return memberRef; } public String getGroupDN() { return groupDN; } public int hashCode() { return memberRef.hashCode() ^ groupDN.hashCode(); } public boolean equals(Object o) { if (o instanceof GroupMembership) { GroupMembership that = (GroupMembership)o; if (!memberRef.equals(that.memberRef)) { return false; } if (!groupDN.equals(that.groupDN)) { return false; } return true; } return false; } @Override public String toString() { return "GroupMembership[memberRef: " + memberRef + "; groupDN: " + groupDN + "]"; } } public static final class Modification<T> { private final Set<T> removed = new LinkedHashSet<T>(); private final Set<T> added = new LinkedHashSet<T>(); private Set<T> effectiveAdded; private Set<T> effectiveRemoved; public void add(T item) { added.add(item); invalidate(); } public void addAll(Collection<? extends T> items) { for (T item : items) { added.add(item); } invalidate(); } public void clearAdded() { added.clear(); invalidate(); } public Set<T> getAdded() { if (effectiveAdded == null) { effectiveAdded = new LinkedHashSet<T>(added); effectiveAdded.removeAll(removed); } return effectiveAdded; } public void remove(T item) { removed.add(item); invalidate(); } public void removeAll(Collection<? extends T> items) { for (T item : items) { removed.add(item); } invalidate(); } public Set<T> getRemoved() { if (effectiveRemoved == null) { effectiveRemoved = new LinkedHashSet<T>(removed); effectiveRemoved.removeAll(added); } return effectiveRemoved; } private void invalidate() { effectiveAdded = null; effectiveRemoved = null; } } private static final class ToDNHandler implements LdapSearchResultsHandler { private final List<String> results = new ArrayList<String>(); public boolean handle(String baseDN, SearchResult searchResult) throws NamingException { results.add(LdapEntry.create(baseDN, searchResult).getDN().toString()); return true; } public List<String> getResults() { return results; } } private static final class ToGroupMembershipHandler implements LdapSearchResultsHandler { private final Set<GroupMembership> results = new HashSet<GroupMembership>(); private String memberRef; public ToGroupMembershipHandler() { } public void setMemberRef(String memberRef) { this.memberRef = memberRef; } public boolean handle(String baseDN, SearchResult searchResult) throws NamingException { LdapName groupDN = LdapEntry.create(baseDN, searchResult).getDN(); results.add(new GroupMembership(memberRef, groupDN.toString())); return true; } public Set<GroupMembership> getResults() { return results; } } }