/* * ==================== * 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]" * ==================== */ package org.identityconnectors.ldap; import static java.util.Collections.min; import static org.identityconnectors.common.CollectionUtil.isEmpty; import static org.identityconnectors.common.StringUtil.isBlank; import static org.identityconnectors.ldap.LdapUtil.addStringAttrValues; import static org.identityconnectors.ldap.LdapUtil.quietCreateLdapName; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import org.identityconnectors.common.Base64; import org.identityconnectors.framework.common.exceptions.ConnectorException; import org.identityconnectors.ldap.GroupHelper.GroupMembership; import org.identityconnectors.ldap.search.LdapSearches; public abstract class LdapModifyOperation { protected final LdapConnection conn; protected final GroupHelper groupHelper; public LdapModifyOperation(LdapConnection conn) { this.conn = conn; groupHelper = new GroupHelper(conn); } protected final void hashPassword(Attribute passwordAttr, String entryDN) { String hashAlgorithm = conn.getConfiguration().getPasswordHashAlgorithm(); if (isBlank(hashAlgorithm) || "NONE".equalsIgnoreCase(hashAlgorithm)) { return; } try { byte[] password = (byte[]) passwordAttr.get(); if (password != null) { String newPassword = hashBytes(password, hashAlgorithm, entryDN != null ? entryDN.hashCode() : 0); passwordAttr.clear(); passwordAttr.add(newPassword); } } catch (NamingException e) { throw new ConnectorException(e); } } private String hashBytes(byte[] plain, String algorithm, long randSeed) { MessageDigest digest = null; try { if ( algorithm.equalsIgnoreCase("SSHA") || algorithm.equalsIgnoreCase("SHA") ) { digest = MessageDigest.getInstance("SHA-1"); } else if ( algorithm.equalsIgnoreCase("SMD5") || algorithm.equalsIgnoreCase("MD5") ) { digest = MessageDigest.getInstance("MD5"); } } catch (NoSuchAlgorithmException e) { throw new ConnectorException("Could not find MessageDigest algorithm (" + algorithm + ") implementation"); } if ( digest == null ) { throw new ConnectorException("Unsupported hash algorithm: " + algorithm); } byte[] salt = { }; if ( algorithm.equalsIgnoreCase("SSHA") || algorithm.equalsIgnoreCase("SMD5") ) { Random rand = new Random(); rand.setSeed(System.currentTimeMillis() + randSeed); // A RSA whitepaper <http://www.rsasecurity.com/solutions/developers/whitepapers/Article3-PBE.pdf> // suggested the salt length be the same as the output of the // hash function being used. The adapter uses the length of the input, // hoping that it is close enough an approximation. salt = new byte[8]; rand.nextBytes(salt); } digest.reset(); digest.update(plain); digest.update(salt); byte[] hash = digest.digest(); byte[] hashPlusSalt = new byte[hash.length + salt.length]; System.arraycopy(hash, 0, hashPlusSalt, 0, hash.length); System.arraycopy(salt, 0, hashPlusSalt, hash.length, salt.length); StringBuilder result = new StringBuilder(algorithm.length() + hashPlusSalt.length); result.append('{'); result.append(algorithm); result.append('}'); result.append(Base64.encode(hashPlusSalt)); return result.toString(); } protected final static Set<String> getAttributeValues(String attrName, LdapName entryDN, Attributes attrs) { Set<String> result = new HashSet<String>(); if (entryDN != null && !entryDN.isEmpty()) { Rdn rdn = entryDN.getRdn(entryDN.size() - 1); addStringAttrValues(rdn.toAttributes(), attrName, result); } Attribute attr = attrs.get(attrName); if (attr != null) { try { NamingEnumeration<?> attrEnum = attr.getAll(); while (attrEnum.hasMoreElements()) { result.add((String) attrEnum.nextElement()); } } catch (NamingException e) { throw new ConnectorException(e); } return result; } // If we got here, the attribute was not in the Attributes instance. So if the // result is empty, that means the attribute is not present in either // the entry DN or the attribute set. return result.isEmpty() ? null : result; } protected final String getFirstPosixRefAttr(String entryDN, Set<String> posixRefAttrs) { if (isEmpty(posixRefAttrs)) { throw new ConnectorException(conn.format("cannotAddToPosixGroup", null, entryDN, GroupHelper.getPosixRefAttribute())); } return min(posixRefAttrs); } /** * Holds the POSIX ref attributes and the respective group * memberships. Retrieves them lazily so that they are only * retrieved once, when they are needed. */ public final class PosixGroupMember { private final String entryDN; private LdapEntry entry; private Set<String> posixRefAttrs; private Set<GroupMembership> posixGroupMemberships; public PosixGroupMember(String entryDN) { this.entryDN = entryDN; } public Set<GroupMembership> getPosixGroupMemberships() { if (posixGroupMemberships == null) { posixGroupMemberships = groupHelper.getPosixGroupMemberships(getPosixRefAttributes()); } return posixGroupMemberships; } public Set<GroupMembership> getPosixGroupMembershipsByAttrs(Set<String> posixRefAttrs) { Set<GroupMembership> result = new HashSet<GroupMembership>(); for (GroupMembership member : getPosixGroupMemberships()) { if (posixRefAttrs.contains(member.getMemberRef())) { result.add(member); } } return result; } public Set<GroupMembership> getPosixGroupMembershipsByGroups(List<String> groupDNs) { Set<LdapName> groupNames = new HashSet<LdapName>(); for (String groupDN : groupDNs) { groupNames.add(quietCreateLdapName(groupDN)); } Set<GroupMembership> result = new HashSet<GroupMembership>(); for (GroupMembership member : getPosixGroupMemberships()) { if (groupNames.contains(quietCreateLdapName(member.getGroupDN()))) { result.add(member); } } return result; } public Set<String> getPosixRefAttributes() { if (posixRefAttrs == null) { posixRefAttrs = getAttributeValues(GroupHelper.getPosixRefAttribute(), null, getLdapEntry().getAttributes()); } return posixRefAttrs; } private LdapEntry getLdapEntry() { if (entry == null) { entry = LdapSearches.getEntry(conn, quietCreateLdapName(entryDN), GroupHelper.getPosixRefAttribute()); } return entry; } } }