/* * ==================== * 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.racf; import java.text.ParseException; import java.text.SimpleDateFormat; import static org.identityconnectors.racf.RacfConstants.*; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.LimitExceededException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import org.identityconnectors.common.CollectionUtil; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.exceptions.AlreadyExistsException; 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.AttributeBuilder; import org.identityconnectors.framework.common.objects.AttributeInfo; 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.ObjectClassInfo; import org.identityconnectors.framework.common.objects.OperationOptions; import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.PredefinedAttributes; import org.identityconnectors.framework.common.objects.Schema; import org.identityconnectors.framework.common.objects.Uid; class LdapUtil { private final Pattern _connectionPattern = Pattern.compile("racfuserid=(.*)\\+racfgroupid=([^,]*),.*"); private RacfConnector _connector; private Schema _schema; private RACFPasswordEnvelopeUtilities _passwdEnvDecrypter; public LdapUtil(RacfConnector connector) { _connector = connector; _schema = _connector.schema(); StringBuffer pemcert = loadBuffer(( (RacfConfiguration) _connector.getConfiguration() ).getActiveSyncCertificate()); StringBuffer pemkey = loadBuffer(( (RacfConfiguration) _connector.getConfiguration() ).getActiveSyncPrivateKey()); String decryptorClass = ( (RacfConfiguration) _connector.getConfiguration() ).getActiveSyncPasswordDecryptorClass(); if (pemcert.length() > 0 && pemkey.length() > 0) { _passwdEnvDecrypter = RACFPasswordEnvelopeUtilities.newRACFPasswordEnvelopeDecryptor(decryptorClass, pemcert.toString(), pemkey.toString()); } } static private Pattern _uidPattern = Pattern.compile("racfid=(\\w+),([^,]+).+", Pattern.CASE_INSENSITIVE); public String createUniformUid(Uid oldUid) { return createUniformUid(oldUid.getUidValue()); } public String createUniformUid(String oldUidString) { return createUniformUid(oldUidString, ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix()); } static public String createUniformUid(String oldUidString, String suffix) { Matcher matcher = _uidPattern.matcher(oldUidString); if (matcher.matches()) { return "racfid=" + matcher.group(1).toUpperCase() + "," + matcher.group(2).toLowerCase() + "," + suffix; } else { return oldUidString; } } private StringBuffer loadBuffer(String[] vals) { StringBuffer buffer = new StringBuffer(); if (vals != null) { for (int i = 0; i < vals.length; i++) { buffer.append((String) vals[i]); buffer.append("\n"); } } return buffer; } public Uid createViaLdap(ObjectClass objectClass, Set<Attribute> attrs, OperationOptions options) { Map<String, Attribute> attributes = CollectionUtil.newCaseInsensitiveMap(); attributes.putAll(AttributeUtil.toMap(attrs)); if (objectClass.is(RacfConnector.RACF_CONNECTION_NAME)) { try { Name name = AttributeUtil.getNameFromAttributes(attrs); Map<String, Attribute> newAttributes = AttributeUtil.toMap(attrs); ( (RacfConnection) _connector.getConnection() ).getDirContext().createSubcontext(name.getNameValue(), createLdapAttributesFromConnectorAttributes(newAttributes)); return new Uid(createUniformUid(name.getNameValue())); } catch (NamingException e) { throw new ConnectorException(e); } } else if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { Name name = AttributeUtil.getNameFromAttributes(attrs); try { Attribute groups = attributes.remove(ATTR_LDAP_GROUPS); Attribute owners = attributes.remove(ATTR_LDAP_CONNECT_OWNER); Attribute expired = attributes.remove(ATTR_LDAP_EXPIRED); Attribute password = attributes.get(ATTR_LDAP_PASSWORD); _connector.throwErrorIfNull(groups); _connector.throwErrorIfNullOrEmpty(expired); _connector.throwErrorIfNullOrEmpty(password); _connector.checkConnectionConsistency(groups, owners); if (expired != null && password == null) { throw new ConnectorException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.EXPIRED_NO_PASSWORD)); } if (userExists(name.getNameValue())) { throw new AlreadyExistsException(); } // Some attributes cannot be specified during create, only modify. // Save these off to the side. // Set<Attribute> changes = new HashSet<Attribute>(); Attribute enable = attributes.remove(ATTR_LDAP_ENABLED); Attribute enableDate = attributes.remove(ATTR_LDAP_RESUME_DATE); Attribute disableDate = attributes.remove(ATTR_LDAP_REVOKE_DATE); if (expired != null) { changes.add(expired); } if (password != null) { changes.add(password); } if (enable != null) { changes.add(enable); } if (enableDate != null) { changes.add(enableDate); } if (disableDate != null) { changes.add(disableDate); } String id = name.getNameValue(); Uid uid = new Uid(id); Map<String, Attribute> newAttributes = CollectionUtil.newCaseInsensitiveMap(); newAttributes.putAll(attributes); addObjectClass(objectClass, newAttributes); ( (RacfConnection) _connector.getConnection() ).getDirContext().createSubcontext(createDnFromName(objectClass, id), createLdapAttributesFromConnectorAttributes(objectClass, newAttributes)); if (groups != null) { if (attributes.get(ATTR_LDAP_DEFAULT_GROUP) != null) { _connector.setGroupMembershipsForUser(uid.getUidValue(), groups, owners, (String) attributes.get(ATTR_LDAP_DEFAULT_GROUP).getValue().get(0)); } else { _connector.setGroupMembershipsForUser(uid.getUidValue(), groups, owners); } } // Now, process the deferred attributes // if (changes.size() > 0) { changes.add(uid); updateViaLdap(objectClass, changes, options); } return uid; } catch (NamingException e) { if (e.toString().contains("INVALID USER")) { throw new AlreadyExistsException(); } else { throw new ConnectorException(e); } } } else if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { Name name = AttributeUtil.getNameFromAttributes(attrs); try { Attribute members = attributes.remove(ATTR_LDAP_GROUP_USERIDS); Attribute groupOwners = attributes.remove(ATTR_LDAP_CONNECT_OWNER); String id = name.getNameValue(); Uid uid = new Uid(id); Map<String, Attribute> newAttributes = new HashMap<String, Attribute>(attributes); addObjectClass(objectClass, newAttributes); ( (RacfConnection) _connector.getConnection() ).getDirContext().createSubcontext(createDnFromName(objectClass, id), createLdapAttributesFromConnectorAttributes(objectClass, newAttributes)); if (members != null) { _connector.setGroupMembershipsForGroups(id, members, groupOwners); } return uid; } catch (NamingException e) { if (e.toString().contains("INVALID GROUP")) { throw new AlreadyExistsException(); } else { throw new ConnectorException(e); } } } else { throw new IllegalArgumentException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.UNSUPPORTED_OBJECT_CLASS, objectClass.getObjectClassValue())); } } public void deleteViaLdap(ObjectClass objectClass, Uid uid) { try { ( (RacfConnection) _connector.getConnection() ).getDirContext().destroySubcontext(createDnFromName(objectClass, uid.getUidValue())); } catch (NamingException e) { if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { if (e.toString().contains("INVALID USER")) { throw new UnknownUidException(); } else { throw new ConnectorException(e); } } else if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { if (e.toString().contains("INVALID GROUP")) { throw new UnknownUidException(); } else { throw new ConnectorException(e); } } else { throw ConnectorException.wrap(e); } } } public List<String> getGroupsForUserViaLdap(String user) { //return getConnectionInfo("racfuserid=" + RacfConnector.extractRacfIdFromLdapId(user), 2); return getConnectionInfo("racfuserid=" + user, 2); } public List<String> getMembersOfGroupViaLdap(String group) { //return getConnectionInfo("racfgroupid=" + RacfConnector.extractRacfIdFromLdapId(group), 1); return getConnectionInfo("racfgroupid=" + group, 1); } private List<String> getConnectionInfo(String query, int index) { SearchControls subTreeControls = new SearchControls(SearchControls.SUBTREE_SCOPE, 4095, 0, null, true, true); List<String> objects = new LinkedList<String>(); try { String search = "profileType=connect," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); NamingEnumeration<SearchResult> connections = ( (RacfConnection) _connector.getConnection() ).getDirContext().search(search, query, subTreeControls); while (connections.hasMore()) { SearchResult userRoot = connections.next(); String name = userRoot.getNameInNamespace(); Matcher matcher = _connectionPattern.matcher(name); if (matcher.matches()) { objects.add(matcher.group(index)); } else { throw new ConnectorException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.PATTERN_FAILED, name)); } } return objects; } catch (LimitExceededException e) { // GAEL - This one is really annoying... //TODO: cope with this throw ConnectorException.wrap(e); } catch (NamingException e) { throw new ConnectorException(e); } } public List<String> getUsersViaLdap(String query) { RacfConfiguration configuration = (RacfConfiguration) _connector.getConfiguration(); String[] queries = configuration.getUserQueries(); // If we are querying ALL users, and we have a partitioned Query, // we will use it instead // if (( "*".equals(query) || query == null ) && queries != null && queries.length > 0) { Set<String> users = new HashSet<String>(); for (String subquery : queries) { users.addAll(getUsersViaLdap0(subquery)); } List<String> userList = new ArrayList<String>(users.size()); userList.addAll(users); return userList; } else { return getUsersViaLdap0(query); } } private List<String> getUsersViaLdap0(String query) { SearchControls subTreeControls = new SearchControls(SearchControls.ONELEVEL_SCOPE, 4095, 0, null, true, true); List<String> userNames = new LinkedList<String>(); try { String search = "profileType=user," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); NamingEnumeration<SearchResult> users = ( (RacfConnection) _connector.getConnection() ).getDirContext().search(search, getLdapFilterForFilterString(query), subTreeControls); while (users.hasMore()) { SearchResult userRoot = users.next(); String name = userRoot.getNameInNamespace(); if (name.startsWith("racfid=irrcerta,") || name.startsWith("racfid=irrmulti,") || name.startsWith("racfid=irrsitec,")) { // Ignore } else { userNames.add(RacfConnector.extractRacfIdFromLdapId(name)); } } } catch (LimitExceededException e) { //TODO: cope with this throw ConnectorException.wrap(e); } catch (NamingException e) { if (!e.toString().contains("NO ENTRIES MEET SEARCH CRITERIA")) { throw new ConnectorException(e); } } return userNames; } public List<String> getGroupsViaLdap(String query) { RacfConfiguration configuration = (RacfConfiguration) _connector.getConfiguration(); String[] queries = configuration.getGroupQueries(); // If we are querying ALL groups, and we have a partitioned Query, // we will use it instead // if (( "*".equals(query) || query == null ) && queries != null && queries.length > 0) { Set<String> groups = new HashSet<String>(); for (String subquery : queries) { groups.addAll(getGroupsViaLdap0(subquery)); } List<String> groupList = new ArrayList<String>(groups.size()); groupList.addAll(groups); return groupList; } else { return getGroupsViaLdap0(query); } } public List<String> getGroupsViaLdap0(String query) { SearchControls subTreeControls = new SearchControls(SearchControls.ONELEVEL_SCOPE, 4095, 0, null, true, true); List<String> groupNames = new LinkedList<String>(); try { String search = "profileType=group," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); NamingEnumeration<SearchResult> groups = ( (RacfConnection) _connector.getConnection() ).getDirContext().search(search, getLdapFilterForFilterString(query), subTreeControls); while (groups.hasMore()) { SearchResult userRoot = groups.next(); String name = userRoot.getNameInNamespace(); groupNames.add(RacfConnector.extractRacfIdFromLdapId(name)); } } catch (LimitExceededException e) { //TODO: cope with this throw ConnectorException.wrap(e); } catch (NamingException e) { if (!e.toString().contains("NO ENTRIES MEET SEARCH CRITERIA")) { throw new ConnectorException(e); } } return groupNames; } private String getLdapFilterForFilterString(String filter) { String filterText = "(objectclass=*)"; if (filter != null) { filterText = filter; } return filterText; } private boolean userExists(String user) { List<String> users = getUsersViaLdap("racfid=" + user.toUpperCase()); return users.size() == 1; } private boolean groupExists(String group) { List<String> groups = getGroupsViaLdap("racfid=" + group.toUpperCase()); return groups.size() == 1; } public Map<String, Object> getAttributesFromLdap(ObjectClass objectClass, String name, Set<String> originalAttributesToGet) throws NamingException { Map<String, Object> attributesRead = CollectionUtil.newCaseInsensitiveMap(); Set<String> attributesToGet = new HashSet<String>(originalAttributesToGet); // This is artificial, so remove it // attributesToGet.remove(ATTR_LDAP_ACCOUNTID); attributesToGet.remove(Name.NAME); // Now special case 'name-only', since that includes accountId // if (attributesToGet.isEmpty()) { Uid uid = new Uid(name); attributesRead.put(Uid.NAME, uid); attributesRead.put(Name.NAME, name); if (_connector.getConfiguration().getIsSunIdm()) { attributesRead.put(ATTR_LDAP_ACCOUNTID, name); } return attributesRead; } // If we need to get the connection owner, we need to lookup the connect branch // boolean owners = attributesToGet.remove(ATTR_LDAP_CONNECT_OWNER); // Since Enable is indicated by ATTRIBUTES attribute containing REVOKE // we must ensure we fetch the Attribute // boolean enable = attributesToGet.remove(OperationalAttributes.ENABLE_NAME); if (enable && !attributesToGet.contains(ATTR_LDAP_ATTRIBUTES)) { attributesToGet.add(ATTR_LDAP_ATTRIBUTES); } SearchResult ldapObject = getAttributesFromLdap(createDnFromName(objectClass, name), attributesRead, attributesToGet); Uid uid = new Uid(name); attributesRead.put(Uid.NAME, uid); attributesRead.put(Name.NAME, name); if (_connector.getConfiguration().getIsSunIdm()) { attributesRead.put(ATTR_LDAP_ACCOUNTID, name); } // For Users, we need to do a separate query against the connections to pick up // Connection info // if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { if (owners) { // First, get the connections for the user // String query = "profileType=Connect," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); SearchControls subTreeControls = new SearchControls(SearchControls.SUBTREE_SCOPE, 4095, 0, attributesToGet.toArray(new String[0]), true, true); NamingEnumeration<SearchResult> results = _connector.getConnection().getDirContext().search(query, "(racfuserid=" + name + ")", subTreeControls); List<String> groupsForUser = new ArrayList<String>(); while (results.hasMore()) { SearchResult result = results.next(); String connection = result.getNameInNamespace(); String[] ids = RacfConnector.extractRacfIdAndGroupIdFromLdapId(connection); String group = ids[1]; groupsForUser.add(group); } attributesRead.put(ATTR_LDAP_GROUPS, groupsForUser); List<String> ownersForUser = new ArrayList<String>(); Set<String> connectAttributesToGet = new HashSet<String>(); connectAttributesToGet.add(ATTR_LDAP_CONNECT_OWNER); for (String group : groupsForUser) { String root = "racfuserid=" + name + "+racfgroupid=" + group + ",profileType=Connect," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); ownersForUser.add(getConnectOwner(root)); } attributesRead.put(ATTR_LDAP_CONNECT_OWNER, ownersForUser); } } // For Groups, we need to do a separate query against the connections to pick up // Connection info // // TODO: Gael - a lot here. We need to handle universal groups. if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { if (owners && ( attributesRead.get(ATTR_LDAP_GROUP_USERIDS) != null )) { List<String> ownersForGroup = new ArrayList<String>(); List<String> usersForGroup = new ArrayList<String>(); //Set<String> connectAttributesToGet = new HashSet<String>(); // connectAttributesToGet.add(ATTR_LDAP_OWNER); if (attributesRead.get(ATTR_LDAP_GROUP_USERIDS) instanceof String) { usersForGroup.add((String) attributesRead.get(ATTR_LDAP_GROUP_USERIDS)); } else { usersForGroup.addAll((List<String>) attributesRead.get(ATTR_LDAP_GROUP_USERIDS)); } for (String user : usersForGroup) { user = RacfConnector.extractRacfIdFromLdapId(user); String root = "racfuserid=" + user + "+racfgroupid=" + name + ",profileType=Connect," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); ownersForGroup.add(RacfConnector.extractRacfIdFromLdapId(getConnectOwner(root))); } attributesRead.put(ATTR_LDAP_CONNECT_OWNER, ownersForGroup); } } // Remap ACCOUNT attributes as needed // if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { // 'racfattributes' comes back as a String, we need to transform it into a list // Gael: This code handles well the diff between ISS LDAP and TDS LDAP // TDS LDAP returns RACF Attributes as a LIST whereas ISS LDAP sometimes // returns a single value with multiple values separated by a space. Object racfAttributes = attributesRead.get(ATTR_LDAP_ATTRIBUTES); if (racfAttributes != null) { List<String> realRacfAttributes = new ArrayList<String>(); if (racfAttributes instanceof String) { for (String attribute : ( (String) racfAttributes ).split("\\s+")) { realRacfAttributes.add(attribute); } } else if (racfAttributes instanceof List) { for (Object attribute : ( (List) racfAttributes )) { for (String attributePart : attribute.toString().split("\\s+")) { realRacfAttributes.add(attributePart); } } } // TODO: Gael - Until I find the meaning of PASSWORD in the racfattributes, // I remove it (I see it in 1.12, not in 1.5) realRacfAttributes.remove("PASSWORD"); // __ENABLE__ is indicated by the REVOKED ATTRIBUTE // We also remove REVOKED from attribute list, since we display it separately // boolean revoked = realRacfAttributes.remove("REVOKED"); attributesRead.put(OperationalAttributes.ENABLE_NAME, !revoked); // avoid empty list if (!realRacfAttributes.isEmpty()) { attributesRead.put(ATTR_LDAP_ATTRIBUTES, realRacfAttributes); } } // Last Access date must be converted // TODO: Gael - All these dates convert are wrong with TDS. // Formats have changed // 1.5 : yy.ddd // 1.11, 1.12: mm/dd/yy if (attributesRead.containsKey(ATTR_LDAP_LAST_ACCESS)) { Object value = attributesRead.get(ATTR_LDAP_LAST_ACCESS); Long converted = null; if (_connector.getConfiguration().getIsTivoliDirectoryServer()) { try { SimpleDateFormat lastAccessTDS = new SimpleDateFormat("MM/dd/yy"); converted = lastAccessTDS.parse(value.toString()).getTime(); } catch (ParseException pe) { converted = null; } } else { converted = _connector.convertFromRacfTimestamp(value); } attributesRead.put(PredefinedAttributes.LAST_LOGIN_DATE_NAME, converted); } // TSO SIZE must be converted // if (attributesRead.containsKey(ATTR_LDAP_TSO_LOGON_SIZE)) { Object value = attributesRead.get(ATTR_LDAP_TSO_LOGON_SIZE); Integer converted = Integer.parseInt((String) value); attributesRead.put(ATTR_LDAP_TSO_LOGON_SIZE, converted); } // password envelope must be converted // if (attributesRead.containsKey(ATTR_LDAP_PASSWORD_ENVELOPE)) { Object value = attributesRead.get(ATTR_LDAP_PASSWORD_ENVELOPE); byte[] encrypted = (byte[]) value; if (_passwdEnvDecrypter != null) { byte[] decrypted = _passwdEnvDecrypter.decrypt(encrypted); String pw = _passwdEnvDecrypter.getPassword(decrypted); if (pw != null) { attributesRead.put(OperationalAttributes.PASSWORD_NAME, AttributeBuilder.buildPassword(pw.toCharArray()).getValue().get(0)); } } } // TSO MAXSIZE must be converted // if (attributesRead.containsKey(ATTR_LDAP_TSO_MAX_REGION_SIZE)) { Object value = attributesRead.get(ATTR_LDAP_TSO_MAX_REGION_SIZE); Integer converted = Integer.parseInt((String) value); attributesRead.put(ATTR_LDAP_TSO_MAX_REGION_SIZE, converted); } // password change date must be converted if (attributesRead.containsKey(ATTR_LDAP_PASSWORD_CHANGE)) { Object value = attributesRead.get(ATTR_LDAP_PASSWORD_CHANGE); Long converted = null; if (_connector.getConfiguration().getIsTivoliDirectoryServer()) { try { SimpleDateFormat pwdChangedTDS = new SimpleDateFormat("MM/dd/yy"); converted = pwdChangedTDS.parse(value.toString()).getTime(); } catch (ParseException pe) { converted = null; } } else { converted = _connector.convertFromRacfTimestamp(value); } attributesRead.put(PredefinedAttributes.LAST_PASSWORD_CHANGE_DATE_NAME, converted); // password change date is 00.000 if expired // Boolean expired = "00.000".equals(value); attributesRead.put(OperationalAttributes.PASSWORD_EXPIRED_NAME, expired); } else { //If no password change date attr in TDS => means pwd expired attributesRead.put(OperationalAttributes.PASSWORD_EXPIRED_NAME, true); } // Revoke date must be converted // long now = new Date().getTime(); if (attributesRead.containsKey(ATTR_LDAP_REVOKE_DATE)) { Object value = attributesRead.get(ATTR_LDAP_REVOKE_DATE); Long converted = null; if (_connector.getConfiguration().getIsTivoliDirectoryServer()) { try { SimpleDateFormat resumeRevokeFormatTDS = new SimpleDateFormat("MM/dd/yy"); converted = resumeRevokeFormatTDS.parse(value.toString()).getTime(); } catch (ParseException pe) { converted = null; } } else { converted = _connector.convertFromResumeRevokeFormat(value); } if (converted == null || converted < now) { attributesRead.put(OperationalAttributes.DISABLE_DATE_NAME, null); } else { attributesRead.put(OperationalAttributes.DISABLE_DATE_NAME, converted); } } // Resume date must be converted // if (attributesRead.containsKey(ATTR_LDAP_RESUME_DATE)) { Object value = attributesRead.get(ATTR_LDAP_RESUME_DATE); Long converted = null; if (_connector.getConfiguration().getIsTivoliDirectoryServer()) { try { SimpleDateFormat resumeRevokeFormatTDS = new SimpleDateFormat("MM/dd/yy"); converted = resumeRevokeFormatTDS.parse(value.toString()).getTime(); } catch (ParseException pe) { converted = null; } } else { converted = _connector.convertFromResumeRevokeFormat(value); } if (converted == null || converted < now) { attributesRead.put(OperationalAttributes.ENABLE_DATE_NAME, null); } else { attributesRead.put(OperationalAttributes.ENABLE_DATE_NAME, converted); } } // Groups must be filled in if null // if (!attributesRead.containsKey(ATTR_LDAP_GROUPS)) { attributesRead.put(ATTR_LDAP_GROUPS, new LinkedList<Object>()); } // Group Owners must be filled in if null // if (!attributesRead.containsKey(ATTR_LDAP_CONNECT_OWNER)) { attributesRead.put(ATTR_LDAP_CONNECT_OWNER, new LinkedList<Object>()); } // Names/Uids must be made uniform // makeUniformUid(attributesRead, ATTR_LDAP_GROUPS); makeUniformUid(attributesRead, ATTR_LDAP_DEFAULT_GROUP); makeUniformUid(attributesRead, ATTR_LDAP_OWNER); makeUniformUid(attributesRead, ATTR_LDAP_CONNECT_OWNER); } // Remap GROUP attributes as needed // if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { if (attributesRead.containsKey(ATTR_LDAP_SUP_GROUP)) { Object value = attributesRead.get(ATTR_LDAP_SUP_GROUP); if (value instanceof String) { if (( (String) value ).startsWith("racfid=NONE,")) { attributesRead.put(ATTR_LDAP_SUP_GROUP, null); } } } // Groups must be filled in if null // if (!attributesRead.containsKey(ATTR_LDAP_SUB_GROUPS)) { attributesRead.put(ATTR_LDAP_SUB_GROUPS, new LinkedList<Object>()); } // Members must be filled in if null // if (!attributesRead.containsKey(ATTR_LDAP_GROUP_USERIDS)) { attributesRead.put(ATTR_LDAP_GROUP_USERIDS, new LinkedList<Object>()); } // Group Owners must be filled in if null // if (!attributesRead.containsKey(ATTR_LDAP_CONNECT_OWNER)) { attributesRead.put(ATTR_LDAP_CONNECT_OWNER, new LinkedList<Object>()); } // Names/Uids must be made uniform // makeUniformUid(attributesRead, ATTR_LDAP_OWNER); makeUniformUid(attributesRead, ATTR_LDAP_SUP_GROUP); makeUniformUid(attributesRead, ATTR_LDAP_SUB_GROUPS); makeUniformUid(attributesRead, ATTR_LDAP_CONNECT_OWNER); makeUniformUid(attributesRead, ATTR_LDAP_GROUP_USERIDS); } return attributesRead; } static boolean isUidValued(String name) { return ATTR_LDAP_GROUPS.equalsIgnoreCase(name) || ATTR_LDAP_DEFAULT_GROUP.equalsIgnoreCase(name) || ATTR_LDAP_OWNER.equalsIgnoreCase(name) || ATTR_LDAP_CONNECT_OWNER.equalsIgnoreCase(name) || ATTR_LDAP_SUP_GROUP.equalsIgnoreCase(name) || ATTR_LDAP_SUB_GROUPS.equalsIgnoreCase(name) || ATTR_LDAP_CONNECT_OWNER.equalsIgnoreCase(name) || ATTR_LDAP_GROUP_USERIDS.equalsIgnoreCase(name); } private void makeUniformUid(Map<String, Object> attributesRead, String attributename) { if (attributesRead.containsKey(attributename)) { Object value = attributesRead.get(attributename); if (value instanceof List) { List list = (List) value; for (int i = 0; i < list.size(); i++) { list.set(i, RacfConnector.extractRacfIdFromLdapId(list.get(i).toString())); } } else if (value != null) { value = RacfConnector.extractRacfIdFromLdapId(value.toString()); } attributesRead.put(attributename, value); } } private String getConnectOwner(String query) throws NamingException { Map<String, Object> attributesRead = CollectionUtil.newCaseInsensitiveMap(); Set<String> attributesToGet = new HashSet<String>(); attributesToGet.add(ATTR_LDAP_CONNECT_OWNER); getAttributesFromLdap(query, attributesRead, attributesToGet); if (!attributesRead.isEmpty()) { return createUniformUid((String) attributesRead.get(ATTR_LDAP_CONNECT_OWNER)); } else { return ""; } } private SearchResult getAttributesFromLdap(String ldapName, Map<String, Object> attributesRead, Set<String> attributesToGet) throws NamingException { SearchControls subTreeControls = new SearchControls(SearchControls.SUBTREE_SCOPE, 4095, 0, attributesToGet.toArray(new String[0]), true, true); SearchResult ldapObject = null; NamingEnumeration<SearchResult> results = _connector.getConnection().getDirContext().search(ldapName, "(objectclass=*)", subTreeControls); ldapObject = results.next(); Attributes attributes = ldapObject.getAttributes(); NamingEnumeration<? extends javax.naming.directory.Attribute> attributeEnum = attributes.getAll(); while (attributeEnum.hasMore()) { javax.naming.directory.Attribute attribute = attributeEnum.next(); // Some attributes expect Lists, but may return a singleton, // these must be mapped back into Lists Object value = getValueFromAttribute(attribute); // Gael: We need to get rid of some attributes if they have "NONE" or "NONE SPECIFIED" // as their value. This was before ZOS 1.11 and TDS if (!_connector.getConfiguration().getIsTivoliDirectoryServer()){ if ("NONE".equals(value) && isNoneAttribute(attribute.getID())) continue; else if ("NONE SPECIFIED".equals(value) && isNoneSpecifiedAttribute(attribute.getID())) continue; else if ("NO-INSTALLATION-DATA".equals(value) && ATTR_LDAP_DATA.equalsIgnoreCase(attribute.getID())) continue; else if ("NO INSTALLATION DATA".equals(value) && ATTR_LDAP_DATA.equalsIgnoreCase(attribute.getID())) continue; else if ("NO-MODEL-NAME".equals(value) && ATTR_LDAP_MODEL.equalsIgnoreCase(attribute.getID())) continue; else if ("NO MODEL DATA SET".equals(value) && ATTR_LDAP_MODEL.equalsIgnoreCase(attribute.getID())) continue; else if ("UNKNOWN".equals(value) && ATTR_LDAP_PROGRAMMER_NAME.equalsIgnoreCase(attribute.getID())) continue; else if ("UNKNOWN".equals(value) && ATTR_LDAP_LAST_ACCESS.equalsIgnoreCase(attribute.getID())) continue; else if ("ANYTIME".equals(value) && ATTR_LDAP_LOGON_TIME.equalsIgnoreCase(attribute.getID())) continue; else if ("NO SUBGROUPS".equals(value) && ATTR_LDAP_SUB_GROUPS.equalsIgnoreCase(attribute.getID())) continue; else{} } if (ATTR_LDAP_GROUPS.equalsIgnoreCase(attribute.getID())) { if (!( value instanceof List )) { List newValue = new LinkedList(); newValue.add(value); value = newValue; } attributesRead.put(attribute.getID(), value); } else { attributesRead.put(attribute.getID(), value); } } return ldapObject; } private boolean isNoneAttribute(String name) { return ATTR_LDAP_OMVS_MAX_CPUTIME.equalsIgnoreCase(name) || ATTR_LDAP_OMVS_MAX_ADDR_SPACE.equalsIgnoreCase(name) || ATTR_LDAP_OMVS_MAX_FILES.equalsIgnoreCase(name) || ATTR_LDAP_OMVS_MAX_MEMORY_MAP.equalsIgnoreCase(name) || ATTR_LDAP_OMVS_MAX_THREADS.equalsIgnoreCase(name) || ATTR_LDAP_OMVS_MAX_PROCESSES.equalsIgnoreCase(name) || ATTR_LDAP_OMVS_UID.equalsIgnoreCase(name) || ATTR_LDAP_OVM_UID.equalsIgnoreCase(name) || ATTR_LDAP_OMVS_GROUP_ID.equalsIgnoreCase(name) || ATTR_LDAP_OVM_GROUP_ID.equalsIgnoreCase(name) || ATTR_LDAP_CLASS_NAME.equalsIgnoreCase(name) || ATTR_LDAP_REVOKE_DATE.equalsIgnoreCase(name) || ATTR_LDAP_RESUME_DATE.equalsIgnoreCase(name) || ATTR_LDAP_ATTRIBUTES.equalsIgnoreCase(name); } private boolean isNoneSpecifiedAttribute(String name) { return ATTR_LDAP_SECURITY_LEVEL.equalsIgnoreCase(name) || ATTR_LDAP_SECURITY_CAT_LIST.equalsIgnoreCase(name) || ATTR_LDAP_SECURITY_LABEL.equalsIgnoreCase(name) || ATTR_LDAP_LANG_PRIMARY.equalsIgnoreCase(name) || ATTR_LDAP_LANG_SECONDARY.equalsIgnoreCase(name); } static Object getValueFromAttribute(javax.naming.directory.Attribute attribute) throws NamingException { switch (attribute.size()) { case 0: return null; case 1: return attribute.get(); default: { List<Object> values = new LinkedList<Object>(); NamingEnumeration ne = attribute.getAll(); while (ne.hasMore()) { values.add(ne.next()); } return values; } } } public Uid updateViaLdap(ObjectClass objectClass, Set<Attribute> attrs, OperationOptions options) { Map<String, Attribute> attributes = CollectionUtil.newCaseInsensitiveMap(); attributes.putAll(AttributeUtil.toMap(attrs)); Uid uid = AttributeUtil.getUidAttribute(attrs); String profileType = "group"; if (!objectClass.is(ObjectClass.ACCOUNT_NAME) && !objectClass.is(RacfConnector.RACF_GROUP_NAME)) { throw new ConnectorException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.UNSUPPORTED_OBJECT_CLASS, objectClass)); } if (uid != null) { if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { profileType = "user"; Attribute expired = attributes.get(ATTR_LDAP_EXPIRED); Attribute password = attributes.get(ATTR_LDAP_PASSWORD); if (expired != null && password == null) { throw new ConnectorException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.EXPIRED_NO_PASSWORD)); } } String[] attrToFetch = new String[attributes.size()]; int i = 0; for (Attribute attribute : attributes.values()) { attrToFetch[i] = attribute.getName().toLowerCase(); i++; } try { SearchControls subTreeControls = new SearchControls(SearchControls.OBJECT_SCOPE, 0, 0, attrToFetch, true, true); String search = "racfid=" + uid.getUidValue() + ",profileType=" + profileType + "," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); NamingEnumeration<SearchResult> entries = ( (RacfConnection) _connector.getConnection() ).getDirContext().search(search, "(objectclass=*)", subTreeControls); if (!entries.hasMoreElements()) { throw new UnknownUidException(); } Attributes curAttrs = entries.next().getAttributes(); Attributes diffValues = diffEntries(createLdapAttributesFromConnectorAttributes(objectClass, attributes), curAttrs); if (diffValues.size() > 0) { if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { Attribute groups = attributes.remove(ATTR_LDAP_GROUPS); Attribute groupOwners = attributes.remove(ATTR_LDAP_CONNECT_OWNER); if (( groups != null ) && ( diffValues.remove(ATTR_LDAP_GROUPS) != null )) { diffValues.remove(ATTR_LDAP_CONNECT_OWNER); _connector.throwErrorIfNull(groups); _connector.throwErrorIfNull(groupOwners); if (curAttrs.get(ATTR_LDAP_DEFAULT_GROUP.toUpperCase()) != null) { _connector.setGroupMembershipsForUser(uid.getUidValue(), groups, groupOwners, curAttrs.get(ATTR_LDAP_GROUPS), (String) curAttrs.get(ATTR_LDAP_DEFAULT_GROUP.toUpperCase()).get()); } else { _connector.setGroupMembershipsForUser(uid.getUidValue(), groups, groupOwners); } } ( (RacfConnection) _connector.getConnection() ).getDirContext().modifyAttributes(createDnFromName(objectClass, uid.getUidValue()), DirContext.REPLACE_ATTRIBUTE, diffValues); if (diffValues.get(ATTR_LDAP_DEFAULT_GROUP) != null) { boolean toRemove = true; String defGroup = RacfConnector.extractRacfIdFromLdapId((String)curAttrs.get(ATTR_LDAP_DEFAULT_GROUP.toUpperCase()).get()); for (Object gr : groups.getValue()) { if (((String)gr).equalsIgnoreCase(defGroup)) { toRemove = false; break; } } if (toRemove) { String dn = "racfgroupid="+ defGroup +"+racfuserid="+uid.getUidValue()+ ",profileType=connect," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); ( (RacfConnection) _connector.getConnection() ).getDirContext().destroySubcontext(dn); } } } else if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { Attribute members = attributes.remove(ATTR_LDAP_GROUP_USERIDS); Attribute groupOwners = attributes.remove(ATTR_LDAP_CONNECT_OWNER); ( (RacfConnection) _connector.getConnection() ).getDirContext().modifyAttributes(createDnFromName(objectClass, uid.getUidValue()), DirContext.REPLACE_ATTRIBUTE, diffValues); if (members != null) { _connector.setGroupMembershipsForGroups(uid.getUidValue(), members, groupOwners); } } } } catch (NamingException e) { if (e.toString().contains("INVALID USER") || e.toString().contains("INVALID GROUP")) { throw new AlreadyExistsException(); } else { throw new ConnectorException(e); } } } return uid; } protected void addObjectClass(ObjectClass objectClass, Map<String, Attribute> attrs) { List<Object> values = new ArrayList<Object>(); if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { for (String userObjectClass : ( (RacfConfiguration) _connector.getConfiguration() ).getUserObjectClasses()) { values.add(userObjectClass); } } else if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { for (String groupObjectClass : ( (RacfConfiguration) _connector.getConfiguration() ).getGroupObjectClasses()) { values.add(groupObjectClass); } } attrs.put("objectclass", AttributeBuilder.build("objectclass", values)); } private Attributes createLdapAttributesFromConnectorAttributes(ObjectClass objectClass, Map<String, Attribute> attributes) { Attributes basicAttributes = new BasicAttributes(true); Set<ObjectClassInfo> objectClassInfos = _schema.getObjectClassInfo(); ObjectClassInfo accountInfo = null; for (ObjectClassInfo objectClassInfo : objectClassInfos) { if (objectClassInfo.is(objectClass.getObjectClassValue())) { accountInfo = objectClassInfo; } } Set<AttributeInfo> attributeInfos = accountInfo.getAttributeInfo(); Set<String> racfAttributes = CollectionUtil.newCaseInsensitiveSet(); boolean setRacfAttributes = false; boolean negateAttributes = false; for (Attribute attribute : attributes.values()) { String attributeName = attribute.getName().toLowerCase(); if (attribute.getValue() == null) { // TODO Gael this is an issue (see INSTALLATION DATA or OMVSHOME) //throw new IllegalArgumentException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.BAD_ATTRIBUTE_VALUE, (String) null)); continue; } if (attribute.is(Name.NAME) || attribute.is(Uid.NAME)) { // Ignore Name, Uid continue; } else if (attribute.is("objectclass")) { BasicAttribute objectClassAttribute = new BasicAttribute("objectclass"); for (Object value : attribute.getValue()) { objectClassAttribute.add(value); } basicAttributes.put(objectClassAttribute); //} else if (attribute.is(ATTR_LDAP_SUP_GROUP)) { // TODO: GAEL - Need to verifiy superior group exists } else if (attribute.is(ATTR_LDAP_ATTRIBUTES)) { for (Object value : attribute.getValue()) { if (value == null) { // TODO: check if this is not too restrictive throw new IllegalArgumentException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.BAD_ATTRIBUTE_VALUE, (String) null)); } else { String string = value.toString(); if (!RacfConnector.POSSIBLE_ATTRIBUTES.contains(string)) { //TODO Gael: solve this issue with PASSWORD ATTRIBUTE, throws this exception on UPDATE op //This is also wrong since racfAttributes can be used to set many keywords for both altuser and adduser // add: racfattributes // racfattributes: resume norevoke => check this anyway... //throw new IllegalArgumentException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.BAD_ATTRIBUTE_VALUE, string)); } else { racfAttributes.add(string); setRacfAttributes = true; //TODO temporary, need to solve the RACF ATTRIBUTE update issue } } } negateAttributes = true; //setRacfAttributes = true; //TODO temporary, need to solve the RACF ATTRIBUTE update issue } else if (attribute.is(ATTR_LDAP_AUTHORIZATION_DATE) || attribute.is(ATTR_LDAP_PASSWORD_INTERVAL) || attribute.is(ATTR_LDAP_RACF_ID) || attribute.is(ATTR_LDAP_LAST_ACCESS) || attribute.is(ATTR_LDAP_PASSWORD_CHANGE) || attribute.is(ATTR_LDAP_SUB_GROUP) || attribute.is(ATTR_LDAP_GROUP_USERIDS)) { // Ignore read-only attrs // } else if (attribute.is(PredefinedAttributes.GROUPS_NAME)) { // Groups handled separately // } else if (attribute.is(OperationalAttributes.CURRENT_PASSWORD_NAME)) { // Ignore current password // } else if (attribute.is(ATTR_LDAP_EXPIRED)) { if (AttributeUtil.getBooleanValue(attribute)) { racfAttributes.add("Expired"); } else { racfAttributes.add("noExpired"); } setRacfAttributes = true; } else if (attribute.is(ATTR_LDAP_ENABLED)) { if (AttributeUtil.getBooleanValue(attribute)) { racfAttributes.add("RESUME"); } else { racfAttributes.add("REVOKE"); } setRacfAttributes = true; } else if (attribute.is(ATTR_LDAP_RESUME_DATE) || attribute.is(ATTR_LDAP_REVOKE_DATE)) { basicAttributes.put(attribute.getName(), AttributeUtil.getStringValue(attribute)); } else if (attribute.is(ATTR_LDAP_PASSWORD)) { // remap password // List<Object> value = attribute.getValue(); if (value == null || value.size() != 1) { throw new IllegalArgumentException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.MUST_BE_SINGLE_VALUE)); } GuardedString password = AttributeUtil.getGuardedStringValue(attribute); GuardedStringAccessor accessor = new GuardedStringAccessor(); password.access(accessor); basicAttributes.put(ATTR_LDAP_PASSWORD, new String(accessor.getArray())); } else { AttributeInfo attributeInfo = getAttributeInfo(attributeInfos, attributeName); if (attributeInfo == null) { throw new IllegalArgumentException(( (RacfConfiguration) _connector.getConfiguration() ).getMessage(RacfMessages.UNKNOWN_ATTRIBUTE, attributeName)); } basicAttributes.put(makeBasicAttribute(attributeName, attribute.getValue())); } } if (setRacfAttributes) { // Since RACF LDAP does't obey replace semantics, we need to patch in // any values that should be removed // //TODO: an alternative implementation would be to read the user object // and get the set of current values for ATTRIBUTE. This has the // advantage of getting the exact set of values, but the disadvantage // that it requires an extra read of the user. //NOTE: this probably also applies to 'RacfConnectAttributes', which we // are not currently supporting List<Object> finalValue = new LinkedList<Object>(); if (negateAttributes) { for (String attrValue : RacfConnector.POSSIBLE_ATTRIBUTES) { if (!racfAttributes.contains(attrValue)) { finalValue.add("NO" + attrValue); } } } finalValue.addAll(racfAttributes); basicAttributes.put(makeBasicAttribute(ATTR_LDAP_ATTRIBUTES, finalValue)); } return basicAttributes; } private Attributes createLdapAttributesFromConnectorAttributes(Map<String, Attribute> attributes) { Attributes basicAttributes = new BasicAttributes(); for (Attribute attribute : attributes.values()) { String attributeName = attribute.getName().toLowerCase(); if (attribute.is(Name.NAME) || attribute.is(Uid.NAME)) { // Ignore Name, Uid // } else { basicAttributes.put(makeBasicAttribute(attributeName, attribute.getValue())); } } return basicAttributes; } private BasicAttribute makeBasicAttribute(String name, List<Object> racfAttributes) { BasicAttribute attribute = new BasicAttribute(name); for (Object value : racfAttributes) { if (value != null) { value = value.toString(); } attribute.add(value); } return attribute; } private AttributeInfo getAttributeInfo(Set<AttributeInfo> attributeInfos, String name) { for (AttributeInfo attributeInfo : attributeInfos) { if (attributeInfo.getName().equalsIgnoreCase(name)) { return attributeInfo; } } return null; } /** * Create a RACF LDAP DN given a name. * * @param name * @return DN form */ String createDnFromName(ObjectClass objectClass, String name) { if (objectClass.is(ObjectClass.ACCOUNT_NAME)) { return "racfid=" + name.toUpperCase() + ",profiletype=user," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); } else if (objectClass.is(RacfConnector.RACF_GROUP_NAME)) { return "racfid=" + name.toUpperCase() + ",profiletype=group," + ( (RacfConfiguration) _connector.getConfiguration() ).getSuffix(); } else { return name; } } private Attributes diffEntries(Attributes newValues, Attributes curValues) { NamingEnumeration<javax.naming.directory.Attribute> attrEnum = (NamingEnumeration<javax.naming.directory.Attribute>) curValues.getAll(); try { while (attrEnum.hasMore()) { javax.naming.directory.Attribute curAttr = attrEnum.next(); javax.naming.directory.Attribute newAttr = newValues.get(curAttr.getID()); if (( newAttr != null ) && ( curAttr.size() == newAttr.size() )) { // ok.. we have the attribute both in current entry and new entry // lets compare. if (newAttr.size() == 0) { // Houston.. we have an empty one } if (newAttr.size() == 1) { Object newValue = newAttr.get(); Object curValue = curAttr.get(); if (( newValue instanceof String ) && ( curValue instanceof String )) { if (( (String) newValue ).equalsIgnoreCase(_connector.extractRacfIdFromLdapId((String) curValue))) { newValues.remove(newAttr.getID()); } } } else { //multi value attr NamingEnumeration newValue = newAttr.getAll(); NamingEnumeration curValue = curAttr.getAll(); ArrayList newList = new ArrayList(); ArrayList curList = new ArrayList(); while (newValue.hasMoreElements()) { newList.add(newValue.next()); } while (curValue.hasMoreElements()) { curList.add(curValue.next()); } if (newList.size() == curList.size()) { ArrayList safeCurList = new ArrayList(); for (Object s : curList) { safeCurList.add(_connector.extractRacfIdFromLdapId((String) s)); } newList.removeAll(safeCurList); if (newList.isEmpty()) { newValues.remove(newAttr.getID()); } } } } } } catch (Exception e) { } return newValues; } }