/* * ==================== * 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]" * ==================== */ package org.identityconnectors.ldap.modify; import static org.identityconnectors.common.CollectionUtil.isEmpty; import static org.identityconnectors.common.CollectionUtil.newSet; import static org.identityconnectors.common.CollectionUtil.nullAsEmpty; import static org.identityconnectors.ldap.LdapUtil.checkedListByFilter; import static org.identityconnectors.ldap.LdapUtil.quietCreateLdapName; import static org.identityconnectors.ldap.LdapUtil.escapeDNValueOfJNDIReservedChars; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import org.identityconnectors.common.Pair; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.framework.common.exceptions.UnknownUidException; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.AttributeUtil; import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.Uid; import org.identityconnectors.ldap.GroupHelper; import org.identityconnectors.ldap.LdapConnection; import org.identityconnectors.ldap.LdapModifyOperation; import org.identityconnectors.ldap.LdapConstants; import org.identityconnectors.ldap.GroupHelper.GroupMembership; import org.identityconnectors.ldap.GroupHelper.Modification; import org.identityconnectors.ldap.schema.GuardedPasswordAttribute; import org.identityconnectors.ldap.schema.GuardedPasswordAttribute.Accessor; import org.identityconnectors.ldap.search.LdapSearches; public class LdapUpdate extends LdapModifyOperation { private final ObjectClass oclass; private Uid uid; public LdapUpdate(LdapConnection conn, ObjectClass oclass, Uid uid) { super(conn); this.oclass = oclass; this.uid = uid; } public Uid update(Set<Attribute> attrs) { String entryDN = escapeDNValueOfJNDIReservedChars(LdapSearches.getEntryDN(conn, oclass, uid)); PosixGroupMember posixMember = new PosixGroupMember(entryDN); // Extract the Name attribute if any, to be used to rename the entry later. Set<Attribute> updateAttrs = attrs; Name newName = (Name) AttributeUtil.find(Name.NAME, attrs); String newEntryDN = null; if (newName != null) { updateAttrs = newSet(attrs); updateAttrs.remove(newName); newEntryDN = conn.getSchemaMapping().getEntryDN(oclass, newName); } List<String> ldapGroups = getStringListValue(updateAttrs, LdapConstants.LDAP_GROUPS_NAME); List<String> posixGroups = getStringListValue(updateAttrs, LdapConstants.POSIX_GROUPS_NAME); Pair<Attributes, GuardedPasswordAttribute> attrToModify = getAttributesToModify(updateAttrs); Attributes ldapAttrs = attrToModify.first; // If we are removing all POSIX ref attributes, check they are not used // in POSIX groups. Note it is OK to update the POSIX ref attribute instead of // removing them -- we will update the groups to refer to the new attributes. Set<String> newPosixRefAttrs = getAttributeValues(GroupHelper.getPosixRefAttribute(), quietCreateLdapName(newEntryDN != null ? newEntryDN : entryDN), ldapAttrs); if (newPosixRefAttrs != null && newPosixRefAttrs.isEmpty()) { checkRemovedPosixRefAttrs(posixMember.getPosixRefAttributes(), posixMember.getPosixGroupMemberships()); } // Update the attributes. modifyAttributes(entryDN, attrToModify, DirContext.REPLACE_ATTRIBUTE); // Rename the entry if needed. String oldEntryDN = null; if (newName != null) { if (newPosixRefAttrs != null && conn.getConfiguration().isMaintainPosixGroupMembership() || posixGroups != null) { posixMember.getPosixRefAttributes(); } oldEntryDN = entryDN; entryDN = conn.getSchemaMapping().rename(oclass, oldEntryDN, newName); } // Update the LDAP groups. Modification<GroupMembership> ldapGroupMod = new Modification<GroupMembership>(); if (oldEntryDN != null && conn.getConfiguration().isMaintainLdapGroupMembership()) { Set<GroupMembership> members = groupHelper.getLdapGroupMemberships(oldEntryDN); ldapGroupMod.removeAll(members); for (GroupMembership member : members) { ldapGroupMod.add(new GroupMembership(entryDN, member.getGroupDN())); } } if (ldapGroups != null) { Set<GroupMembership> members = groupHelper.getLdapGroupMemberships(entryDN); ldapGroupMod.removeAll(members); ldapGroupMod.clearAdded(); // Since we will be replacing with the new groups. for (String ldapGroup : ldapGroups) { ldapGroupMod.add(new GroupMembership(entryDN, ldapGroup)); } } groupHelper.modifyLdapGroupMemberships(ldapGroupMod); // Update the POSIX groups. Modification<GroupMembership> posixGroupMod = new Modification<GroupMembership>(); if (newPosixRefAttrs != null && conn.getConfiguration().isMaintainPosixGroupMembership()) { Set<String> removedPosixRefAttrs = new HashSet<String>(posixMember.getPosixRefAttributes()); removedPosixRefAttrs.removeAll(newPosixRefAttrs); Set<GroupMembership> members = posixMember.getPosixGroupMembershipsByAttrs(removedPosixRefAttrs); posixGroupMod.removeAll(members); if (!members.isEmpty()) { String firstPosixRefAttr = getFirstPosixRefAttr(entryDN, newPosixRefAttrs); for (GroupMembership member : members) { posixGroupMod.add(new GroupMembership(firstPosixRefAttr, member.getGroupDN())); } } } if (posixGroups != null) { Set<GroupMembership> members = posixMember.getPosixGroupMemberships(); posixGroupMod.removeAll(members); posixGroupMod.clearAdded(); // Since we will be replacing with the new groups. if (!posixGroups.isEmpty()) { String firstPosixRefAttr = getFirstPosixRefAttr(entryDN, newPosixRefAttrs); for (String posixGroup : posixGroups) { posixGroupMod.add(new GroupMembership(firstPosixRefAttr, posixGroup)); } } } groupHelper.modifyPosixGroupMemberships(posixGroupMod); return conn.getSchemaMapping().createUid(oclass, entryDN); } public Uid addAttributeValues(Set<Attribute> attrs) { String entryDN = LdapSearches.findEntryDN(conn, oclass, uid); PosixGroupMember posixMember = new PosixGroupMember(entryDN); Pair<Attributes, GuardedPasswordAttribute> attrsToModify = getAttributesToModify(attrs); modifyAttributes(entryDN, attrsToModify, DirContext.ADD_ATTRIBUTE); List<String> ldapGroups = getStringListValue(attrs, LdapConstants.LDAP_GROUPS_NAME); if (!isEmpty(ldapGroups)) { groupHelper.addLdapGroupMemberships(entryDN, ldapGroups); } List<String> posixGroups = getStringListValue(attrs, LdapConstants.POSIX_GROUPS_NAME); if (!isEmpty(posixGroups)) { Set<String> posixRefAttrs = posixMember.getPosixRefAttributes(); String posixRefAttr = getFirstPosixRefAttr(entryDN, posixRefAttrs); groupHelper.addPosixGroupMemberships(posixRefAttr, posixGroups); } return uid; } public Uid removeAttributeValues(Set<Attribute> attrs) { String entryDN = LdapSearches.findEntryDN(conn, oclass, uid); PosixGroupMember posixMember = new PosixGroupMember(entryDN); Pair<Attributes, GuardedPasswordAttribute> attrsToModify = getAttributesToModify(attrs); Attributes ldapAttrs = attrsToModify.first; Set<String> removedPosixRefAttrs = getAttributeValues(GroupHelper.getPosixRefAttribute(), null, ldapAttrs); if (!isEmpty(removedPosixRefAttrs)) { checkRemovedPosixRefAttrs(removedPosixRefAttrs, posixMember.getPosixGroupMemberships()); } modifyAttributes(entryDN, attrsToModify, DirContext.REMOVE_ATTRIBUTE); List<String> ldapGroups = getStringListValue(attrs, LdapConstants.LDAP_GROUPS_NAME); if (!isEmpty(ldapGroups)) { groupHelper.removeLdapGroupMemberships(entryDN, ldapGroups); } List<String> posixGroups = getStringListValue(attrs, LdapConstants.POSIX_GROUPS_NAME); if (!isEmpty(posixGroups)) { Set<GroupMembership> members = posixMember.getPosixGroupMembershipsByGroups(posixGroups); groupHelper.removePosixGroupMemberships(members); } return uid; } private void checkRemovedPosixRefAttrs(Set<String> removedPosixRefAttrs, Set<GroupMembership> memberships) { for (GroupMembership membership : memberships) { if (removedPosixRefAttrs.contains(membership.getMemberRef())) { throw new ConnectorException(conn.format("cannotRemoveBecausePosixMember", GroupHelper.getPosixRefAttribute())); } } } private Pair<Attributes, GuardedPasswordAttribute> getAttributesToModify(Set<Attribute> attrs) { BasicAttributes ldapAttrs = new BasicAttributes(); GuardedPasswordAttribute pwdAttr = null; for (Attribute attr : attrs) { javax.naming.directory.Attribute ldapAttr = null; if (attr.is(Uid.NAME)) { throw new IllegalArgumentException("Unable to modify an object's uid"); } else if (attr.is(Name.NAME)) { // Such a change would have been handled in update() above. throw new IllegalArgumentException("Unable to modify an object's name"); } else if (LdapConstants.isLdapGroups(attr.getName())) { // Handled elsewhere. } else if (LdapConstants.isPosixGroups(attr.getName())) { // Handled elsewhere. } else if (attr.is(OperationalAttributes.PASSWORD_NAME)) { pwdAttr = conn.getSchemaMapping().encodePassword(oclass, attr); } else { ldapAttr = conn.getSchemaMapping().encodeAttribute(oclass, attr); } if (ldapAttr != null) { javax.naming.directory.Attribute existingAttr = ldapAttrs.get(ldapAttr.getID()); if (existingAttr != null) { try { NamingEnumeration<?> all = ldapAttr.getAll(); while (all.hasMoreElements()) { existingAttr.add(all.nextElement()); } } catch (NamingException e) { throw new ConnectorException(e); } } else { ldapAttrs.put(ldapAttr); } } } return new Pair<Attributes, GuardedPasswordAttribute>(ldapAttrs, pwdAttr); } private void modifyAttributes(final String entryDN, Pair<Attributes, GuardedPasswordAttribute> attrs, final int ldapModifyOp) { final List<ModificationItem> modItems = new ArrayList<ModificationItem>(attrs.first.size()); NamingEnumeration<? extends javax.naming.directory.Attribute> attrEnum = attrs.first.getAll(); while (attrEnum.hasMoreElements()) { modItems.add(new ModificationItem(ldapModifyOp, attrEnum.nextElement())); } if (attrs.second != null) { attrs.second.access(new Accessor() { public void access(javax.naming.directory.Attribute passwordAttr) { // Do not add the password to the result Attributes because // it is a guarded value. hashPassword(passwordAttr, entryDN); modItems.add(new ModificationItem(ldapModifyOp, passwordAttr)); modifyAttributes(entryDN, modItems); } }); } else { modifyAttributes(entryDN, modItems); } } private void modifyAttributes(String entryDN, List<ModificationItem> modItems) { try { conn.getInitialContext().modifyAttributes(entryDN, modItems.toArray(new ModificationItem[modItems.size()])); } catch (NameNotFoundException e) { throw (UnknownUidException) new UnknownUidException(uid, oclass).initCause(e); } catch (NamingException e) { throw new ConnectorException(e); } } private List<String> getStringListValue(Set<Attribute> attrs, String attrName) { Attribute attr = AttributeUtil.find(attrName, attrs); if (attr != null) { return checkedListByFilter(nullAsEmpty(attr.getValue()), String.class); } return null; } }