Java tutorial
/* * Licensed to the University Corporation for Advanced Internet Development, * Inc. (UCAID) under one or more contributor license agreements. See the * NOTICE file distributed with this work for additional information regarding * copyright ownership. The UCAID licenses this file to You under the Apache * License, Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.internet2.middleware.psp.ldap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.InvalidNameException; import javax.naming.NameAlreadyBoundException; import javax.naming.NameNotFoundException; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import javax.naming.directory.SchemaViolationException; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.LdapName; import org.opensaml.xml.util.DatatypeHelper; import org.openspml.v2.msg.spml.AddRequest; import org.openspml.v2.msg.spml.AddResponse; import org.openspml.v2.msg.spml.DeleteRequest; import org.openspml.v2.msg.spml.DeleteResponse; import org.openspml.v2.msg.spml.ErrorCode; import org.openspml.v2.msg.spml.Extensible; import org.openspml.v2.msg.spml.LookupRequest; import org.openspml.v2.msg.spml.LookupResponse; import org.openspml.v2.msg.spml.Modification; import org.openspml.v2.msg.spml.ModificationMode; import org.openspml.v2.msg.spml.ModifyRequest; import org.openspml.v2.msg.spml.ModifyResponse; import org.openspml.v2.msg.spml.PSO; import org.openspml.v2.msg.spml.PSOIdentifier; import org.openspml.v2.msg.spml.QueryClause; import org.openspml.v2.msg.spml.ReturnData; import org.openspml.v2.msg.spml.StatusCode; import org.openspml.v2.msg.spmlref.HasReference; import org.openspml.v2.msg.spmlref.Reference; import org.openspml.v2.msg.spmlsearch.Query; import org.openspml.v2.msg.spmlsearch.Scope; import org.openspml.v2.msg.spmlsearch.SearchRequest; import org.openspml.v2.msg.spmlsearch.SearchResponse; import org.openspml.v2.profiles.dsml.DSMLAttr; import org.openspml.v2.profiles.dsml.DSMLModification; import org.openspml.v2.profiles.dsml.DSMLProfileException; import org.openspml.v2.profiles.dsml.DSMLValue; import org.openspml.v2.profiles.dsml.EqualityMatch; import org.openspml.v2.profiles.dsml.Filter; import org.openspml.v2.profiles.dsml.FilterItem; import org.openspml.v2.util.Spml2Exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import edu.internet2.middleware.psp.PspException; import edu.internet2.middleware.psp.spml.config.Pso; import edu.internet2.middleware.psp.spml.config.PsoIdentifyingAttribute; import edu.internet2.middleware.psp.spml.config.PsoReferences; import edu.internet2.middleware.psp.spml.provider.BaseSpmlTarget; import edu.internet2.middleware.psp.spml.request.AlternateIdentifier; import edu.internet2.middleware.psp.util.PSPUtil; import edu.internet2.middleware.shibboleth.common.service.ServiceException; import edu.internet2.middleware.subject.Source; import edu.internet2.middleware.subject.provider.LdapSourceAdapter; import edu.internet2.middleware.subject.provider.SourceManager; import edu.vt.middleware.ldap.Ldap; import edu.vt.middleware.ldap.SearchFilter; import edu.vt.middleware.ldap.bean.LdapAttribute; import edu.vt.middleware.ldap.bean.LdapAttributes; import edu.vt.middleware.ldap.bean.LdapEntry; import edu.vt.middleware.ldap.bean.LdapResult; import edu.vt.middleware.ldap.bean.OrderedLdapBeanFactory; import edu.vt.middleware.ldap.bean.SortedLdapBeanFactory; import edu.vt.middleware.ldap.ldif.Ldif; import edu.vt.middleware.ldap.ldif.LdifResultConverter; import edu.vt.middleware.ldap.pool.LdapPool; import edu.vt.middleware.ldap.pool.LdapPoolException; /** An (incomplete) spmlv2 provisioning target which provisions an ldap directory. */ public class LdapSpmlTarget extends BaseSpmlTarget { /** Pattern matching an escaped JNDI special forward slash character. */ private static Pattern escapedforwardSlashPattern = Pattern.compile("\\\\/"); /** Pattern matching the JNDI special forward slash character. */ private static Pattern forwardSlashPattern = Pattern.compile("([^\\\\])/"); /** The OpenLDAP error code returned when removing the last value of the groupOfNames attribute. */ public static final String GROUP_OF_NAMES_ERROR = "[LDAP: error code 65 - object class 'groupOfNames' requires attribute 'member']"; /** The OpenLDAP error code returned when removing the last value of the groupOfUniqueNames attribute. */ public static final String GROUP_OF_UNIQUE_NAMES_ERROR = "[LDAP: error code 65 - object class 'groupOfUniqueNames' requires attribute 'uniqueMember']"; /** The logger. */ private static final Logger LOG = LoggerFactory.getLogger(LdapSpmlTarget.class); /** * Normalize LDAP DN using {@link org.apache.directory.shared.ldap.name.LdapDN}. This will convert RDN * attributeTypes to lowercase, which is of interest since Active Directory usually (?) returns attributeTypes * uppercased. * * @param dn the ldap dn * @return the lowercased and normalized dn * @throws InvalidNameException if the dn is not a valid ldap name */ public static String canonicalizeDn(String dn) throws InvalidNameException { return new LdapName(unescapeForwardSlash(dn)).toString(); } /** * Escape all forward slashes "/" with "\/". * * @param dn the ldap dn * @return the resultant string with / replaced with \/ */ public static String escapeForwardSlash(final String dn) { Matcher matcher = forwardSlashPattern.matcher(dn); if (matcher.find()) { return matcher.replaceAll("$1\\\\/"); } return dn; } /** * Remove the escape character "\" from all escaped forward slashes "\/", returning "/". * * @param dn the ldap dn * @return the resultant string */ public static String unescapeForwardSlash(final String dn) { Matcher matcher = escapedforwardSlashPattern.matcher(dn); if (matcher.find()) { return matcher.replaceAll("/"); } return dn; } /** the ldap pool. */ private LdapPool<Ldap> ldapPool; /** The id of the ldap pool. */ private String ldapPoolId; /** The source of the ldap pool id. */ private String ldapPoolIdSource; /** Whether or not log log ldif. */ private boolean logLdif; /** Constructor */ public LdapSpmlTarget() { } /** {@inheritDoc} */ public void execute(AddRequest addRequest, AddResponse addResponse) { try { handleEmptyReferences(addRequest); } catch (DSMLProfileException e) { fail(addResponse, ErrorCode.CUSTOM_ERROR, e); return; } catch (PspException e) { fail(addResponse, ErrorCode.CUSTOM_ERROR, e); return; } Ldap ldap = null; try { SortedLdapBeanFactory ldapBeanFactory = new SortedLdapBeanFactory(); LdapAttributes ldapAttributes = ldapBeanFactory.newLdapAttributes(); Extensible data = addRequest.getData(); // data Map<String, DSMLAttr> dsmlAttrs = PSPUtil.getDSMLAttrMap(data); for (DSMLAttr dsmlAttr : dsmlAttrs.values()) { BasicAttribute basicAttribute = new BasicAttribute(dsmlAttr.getName()); for (DSMLValue dsmlValue : dsmlAttr.getValues()) { basicAttribute.add(dsmlValue.getValue()); } LdapAttribute ldapAttribute = ldapBeanFactory.newLdapAttribute(); ldapAttribute.setAttribute(basicAttribute); ldapAttributes.addAttribute(ldapAttribute); } // references Map<String, List<Reference>> references = PSPUtil.getReferences(addRequest.getCapabilityData()); for (String typeOfReference : references.keySet()) { BasicAttribute basicAttribute = new BasicAttribute(typeOfReference); for (Reference reference : references.get(typeOfReference)) { if (reference.getToPsoID().getTargetID().equals(getId())) { String id = reference.getToPsoID().getID(); // fake empty string since the spml toolkit ignores an empty string psoID if (id == null) { id = ""; } basicAttribute.add(id); } } LdapAttribute ldapAttribute = ldapBeanFactory.newLdapAttribute(); ldapAttribute.setAttribute(basicAttribute); ldapAttributes.addAttribute(ldapAttribute); } // create // assume the psoID is a DN String dn = addRequest.getPsoID().getID(); String escapedDn = LdapSpmlTarget.escapeForwardSlash(dn); ldap = ldapPool.checkOut(); LOG.debug("Target '{}' - Create '{}'", getId(), PSPUtil.toString(addRequest)); LOG.debug("Target '{}' - Create DN '{}'", getId(), escapedDn); ldap.create(escapedDn, ldapAttributes.toAttributes()); LOG.info("Target '{}' - Created '{}'", getId(), PSPUtil.toString(addRequest)); if (this.isLogLdif()) { LdapEntry ldapEntry = ldapBeanFactory.newLdapEntry(); ldapEntry.setDn(dn); ldapEntry.setLdapAttributes(ldapAttributes); LdapResult result = ldapBeanFactory.newLdapResult(); result.addEntry(ldapEntry); Ldif ldif = new Ldif(); LOG.info("Target '{}' - LDIF\n{}", getId(), ldif.createLdif(result)); } // response PSO if (addRequest.getReturnData().equals(ReturnData.IDENTIFIER)) { PSO responsePSO = new PSO(); responsePSO.setPsoID(addRequest.getPsoID()); addResponse.setPso(responsePSO); } else { LookupRequest lookupRequest = new LookupRequest(); lookupRequest.setPsoID(addRequest.getPsoID()); lookupRequest.setReturnData(addRequest.getReturnData()); LookupResponse lookupResponse = this.execute(lookupRequest); if (lookupResponse.getStatus() == StatusCode.SUCCESS) { addResponse.setPso(lookupResponse.getPso()); } else { fail(addResponse, lookupResponse.getError(), "Unable to lookup object after create."); } } } catch (LdapPoolException e) { fail(addResponse, ErrorCode.CUSTOM_ERROR, e); } catch (NameAlreadyBoundException e) { fail(addResponse, ErrorCode.ALREADY_EXISTS, e); } catch (NamingException e) { fail(addResponse, ErrorCode.CUSTOM_ERROR, e); } catch (PspException e) { // from PSO.getReferences, an unhandled capability data fail(addResponse, ErrorCode.CUSTOM_ERROR, e); } finally { ldapPool.checkIn(ldap); } } /** {@inheritDoc} */ public void execute(DeleteRequest deleteRequest, DeleteResponse deleteResponse) { // TODO support recursive delete requests if (deleteRequest.isRecursive()) { fail(deleteResponse, ErrorCode.UNSUPPORTED_OPERATION, "Recursive delete requests are not yet supported."); return; } Ldap ldap = null; try { // delete String dn = deleteRequest.getPsoID().getID(); String escapedDn = LdapSpmlTarget.escapeForwardSlash(dn); ldap = ldapPool.checkOut(); LOG.debug("Target '{}' - Delete '{}'", getId(), PSPUtil.toString(deleteRequest)); LOG.debug("Target '{}' - Delete DN '{}'", getId(), escapedDn); ldap.delete(escapedDn); LOG.info("Target '{}' - Deleted '{}'", getId(), PSPUtil.toString(deleteRequest)); } catch (LdapPoolException e) { fail(deleteResponse, ErrorCode.CUSTOM_ERROR, e); } catch (NameNotFoundException e) { fail(deleteResponse, ErrorCode.NO_SUCH_IDENTIFIER, e); } catch (NamingException e) { fail(deleteResponse, ErrorCode.CUSTOM_ERROR, e); } finally { ldapPool.checkIn(ldap); } } /** {@inheritDoc} */ public void execute(LookupRequest lookupRequest, LookupResponse lookupResponse) { Ldap ldap = null; try { // will not return AD Range option attrs // Attributes attributes = ldap.getAttributes(escapedDn, retAttrs); SearchFilter sf = new SearchFilter(); sf.setFilter("objectclass=*"); SearchControls sc = new SearchControls(); sc.setSearchScope(SearchControls.OBJECT_SCOPE); // This lookup requests attributes defined for *all* objects. // Perhaps there should be two searches, one for the identifier // and a second for attributes. String[] retAttrs = getPSP().getNames(getId(), lookupRequest.getReturnData()).toArray(new String[] {}); sc.setReturningAttributes(retAttrs); // TODO logging String dn = lookupRequest.getPsoID().getID(); String escapedDn = LdapSpmlTarget.escapeForwardSlash(dn); ldap = ldapPool.checkOut(); LOG.debug("Target '{}' - Searching '{}'", getId(), PSPUtil.toString(lookupRequest)); Iterator<SearchResult> searchResults = ldap.search(escapedDn, sf, sc); LOG.debug("Target '{}' - Searched '{}'", getId(), PSPUtil.toString(lookupRequest)); if (!searchResults.hasNext()) { fail(lookupResponse, ErrorCode.NO_SUCH_IDENTIFIER); return; } SearchResult result = searchResults.next(); if (searchResults.hasNext()) { fail(lookupResponse, ErrorCode.CUSTOM_ERROR, "More than one result found."); return; } Attributes attributes = result.getAttributes(); // return attributes in order defined by config OrderedLdapBeanFactory orderedLdapBeanFactory = new OrderedLdapBeanFactory(); // sort values SortedLdapBeanFactory sortedLdapBeanFactory = new SortedLdapBeanFactory(); LdapAttributes ldapAttributes = orderedLdapBeanFactory.newLdapAttributes(); for (String retAttr : retAttrs) { Attribute attr = attributes.get(retAttr); if (attr != null) { LdapAttribute ldapAttribute = sortedLdapBeanFactory.newLdapAttribute(); ldapAttribute.setAttribute(attr); ldapAttributes.addAttribute(ldapAttribute); } } LdapEntry entry = sortedLdapBeanFactory.newLdapEntry(); entry.setDn(dn); entry.setLdapAttributes(ldapAttributes); if (this.isLogLdif()) { LdapResult lr = sortedLdapBeanFactory.newLdapResult(); lr.addEntry(entry); LdifResultConverter lrc = new LdifResultConverter(); LOG.info("Target '{}' - LDIF\n{}", getId(), lrc.toLdif(lr)); } // build pso lookupResponse.setPso(getPSO(entry, lookupRequest.getReturnData())); } catch (NameNotFoundException e) { fail(lookupResponse, ErrorCode.NO_SUCH_IDENTIFIER); } catch (LdapPoolException e) { fail(lookupResponse, ErrorCode.CUSTOM_ERROR, e); } catch (InvalidNameException e) { fail(lookupResponse, ErrorCode.CUSTOM_ERROR, e); } catch (NamingException e) { fail(lookupResponse, ErrorCode.CUSTOM_ERROR, e); } catch (DSMLProfileException e) { fail(lookupResponse, ErrorCode.CUSTOM_ERROR, e); } catch (Spml2Exception e) { fail(lookupResponse, ErrorCode.CUSTOM_ERROR, e); } catch (PspException e) { fail(lookupResponse, ErrorCode.CUSTOM_ERROR, e); } finally { if (ldap != null) { ldapPool.checkIn(ldap); } } } /** {@inheritDoc} */ public void execute(ModifyRequest modifyRequest, ModifyResponse modifyResponse) { execute(modifyRequest, modifyResponse, true); } /** * Execute a modify request and optionally retry with the empty reference value if adding an empty reference is a * schema violation. * * @param modifyRequest the modify request * @param modifyResponse the modify response * @param retry whether or not to retry with the empty reference value if adding an empty reference is a schema * violation */ public void execute(ModifyRequest modifyRequest, ModifyResponse modifyResponse, boolean retry) { Ldap ldap = null; try { String dn = modifyRequest.getPsoID().getID(); List<AlternateIdentifier> alternateIdentifiers = new ArrayList<AlternateIdentifier>(); List<ModificationItem> modificationItems = new ArrayList<ModificationItem>(); for (Modification modification : modifyRequest.getModifications()) { modificationItems.addAll(getDsmlMods(modification)); modificationItems.addAll(getReferenceMods(modification)); alternateIdentifiers.addAll(PSPUtil.getAlternateIdentifiers(modification)); } if (alternateIdentifiers.size() == 1) { AlternateIdentifier alternateIdentifier = alternateIdentifiers.get(0); if (!alternateIdentifier.getTargetID().equals(getId())) { fail(modifyResponse, ErrorCode.CUSTOM_ERROR, "Unable to rename object with a different target ID."); return; } } ldap = ldapPool.checkOut(); PSOIdentifier responseLookupPsoID = modifyRequest.getPsoID(); // rename if (alternateIdentifiers.size() == 1) { String oldDn = LdapSpmlTarget.escapeForwardSlash(dn); String newDn = LdapSpmlTarget.escapeForwardSlash(alternateIdentifiers.get(0).getID()); LOG.info("Target '{}' - Renaming '{}' to '{}'", new Object[] { getId(), oldDn, newDn }); ldap.rename(oldDn, newDn); dn = newDn; responseLookupPsoID = alternateIdentifiers.get(0).getPSOIdentifier(); } // modify LOG.debug("Target '{}' - Modifying '{}'", getId(), PSPUtil.toString(modifyRequest)); LOG.debug("Target '{}' - Modifications '{}'", getId(), modificationItems); String escapedDn = LdapSpmlTarget.escapeForwardSlash(dn); LOG.debug("Target '{}' - Modify DN '{}'", getId(), escapedDn); ldap.modifyAttributes(escapedDn, modificationItems.toArray(new ModificationItem[] {})); LOG.debug("Target '{}' - Modified '{}'", getId(), PSPUtil.toString(modifyRequest)); // response PSO if (modifyRequest.getReturnData().equals(ReturnData.IDENTIFIER)) { PSO responsePSO = new PSO(); responsePSO.setPsoID(responseLookupPsoID); // TODO entityName attribute ? modifyResponse.setPso(responsePSO); } else { LookupRequest lookupRequest = new LookupRequest(); lookupRequest.setPsoID(responseLookupPsoID); lookupRequest.setReturnData(modifyRequest.getReturnData()); LookupResponse lookupResponse = this.execute(lookupRequest); if (lookupResponse.getStatus() == StatusCode.SUCCESS) { modifyResponse.setPso(lookupResponse.getPso()); } else { fail(modifyResponse, lookupResponse.getError()); } } } catch (SchemaViolationException e) { // optionally retry after adding an empty reference if this is an openldap schema violation if (retry) { LOG.error("Target '{}' - A schema violation occurred {}", getId(), e); if (GROUP_OF_NAMES_ERROR.equals(e.getMessage()) || GROUP_OF_UNIQUE_NAMES_ERROR.equals(e.getMessage())) { ModifyRequest emptyReference = null; try { emptyReference = handleEmptyReferences(modifyRequest); } catch (PspException e1) { fail(modifyResponse, ErrorCode.CUSTOM_ERROR, e1); return; } if (emptyReference != null) { LOG.info("Target '{}' - Retrying modify request", getId(), PSPUtil.toString(emptyReference)); execute(emptyReference, modifyResponse, false); } } else { //send a failure up the chain due to objectClass violation we haven't trapped yet GRP-821 fail(modifyResponse, ErrorCode.CUSTOM_ERROR, e); return; } } else { // return the failure response fail(modifyResponse, ErrorCode.CUSTOM_ERROR, e); return; } } catch (LdapPoolException e) { fail(modifyResponse, ErrorCode.CUSTOM_ERROR, e); } catch (NamingException e) { fail(modifyResponse, ErrorCode.CUSTOM_ERROR, e); } catch (PspException e) { fail(modifyResponse, ErrorCode.CUSTOM_ERROR, e); } finally { ldapPool.checkIn(ldap); } } /** {@inheritDoc} */ public void execute(SearchRequest searchRequest, SearchResponse searchResponse) { // query Query query = searchRequest.getQuery(); // query filter // TODO support QueryClause other than our own String filter = null; for (QueryClause queryClause : query.getQueryClauses()) { if (queryClause instanceof HasReference) { HasReference hasReference = (HasReference) queryClause; if (hasReference.getTypeOfReference() != null && hasReference.getToPsoID() != null && hasReference.getToPsoID().getID() != null) { filter = "(" + hasReference.getTypeOfReference() + "=" + hasReference.getToPsoID().getID() + ")"; // TODO what do we do with hasReference.getReferenceData(); ? } } else if (queryClause instanceof Filter) { FilterItem filterItem = ((Filter) queryClause).getItem(); if (filterItem instanceof EqualityMatch) { String name = ((EqualityMatch) filterItem).getName(); String value = ((EqualityMatch) filterItem).getValue().getValue(); filter = "(" + name + "=" + value + ")"; } } else { fail(searchResponse, ErrorCode.MALFORMED_REQUEST, "Unsupported query."); return; } } if (DatatypeHelper.isEmpty(filter)) { fail(searchResponse, ErrorCode.MALFORMED_REQUEST, "A filter is required."); return; } // query base if (query.getBasePsoID() == null || query.getBasePsoID().getID() == null) { fail(searchResponse, ErrorCode.MALFORMED_REQUEST, "A basePsoID is required."); return; } String base = query.getBasePsoID().getID(); SearchControls searchControls = new SearchControls(); // query scope Scope scope = query.getScope(); if (scope != null) { searchControls.setSearchScope(PSPUtil.getScope(scope)); } ReturnData returnData = searchRequest.getReturnData(); if (returnData == null) { returnData = ReturnData.EVERYTHING; } // attributes to return String[] retAttrs = getPSP().getNames(getId(), returnData).toArray(new String[] {}); searchControls.setReturningAttributes(retAttrs); Ldap ldap = null; try { ldap = ldapPool.checkOut(); LOG.debug("Target '{}' - Search will return attributes '{}'", getId(), Arrays.asList(retAttrs)); LOG.debug("Target '{}' - Searching '{}'", getId(), PSPUtil.toString(searchRequest)); Iterator<SearchResult> searchResults = ldap.search(base, new SearchFilter(filter), searchControls); LOG.debug("Target '{}' - Searched '{}'", getId(), PSPUtil.toString(searchRequest)); SortedLdapBeanFactory ldapBeanFactory = new SortedLdapBeanFactory(); LdapResult ldapResult = ldapBeanFactory.newLdapResult(); ldapResult.addEntries(searchResults); Collection<LdapEntry> entries = ldapResult.getEntries(); LOG.debug("Target '{}' - Search found {} entries.", getId(), entries.size()); for (LdapEntry entry : entries) { searchResponse.addPSO(getPSO(entry, returnData)); } if (logLdif) { Ldif ldif = new Ldif(); LOG.info("Target '{}' - LDIF\n{}", getId(), ldif.createLdif(ldapResult)); } } catch (NameNotFoundException e) { fail(searchResponse, ErrorCode.NO_SUCH_IDENTIFIER, e); } catch (NamingException e) { fail(searchResponse, ErrorCode.CUSTOM_ERROR, e); } catch (LdapPoolException e) { fail(searchResponse, ErrorCode.CUSTOM_ERROR, e); } catch (Spml2Exception e) { fail(searchResponse, ErrorCode.CUSTOM_ERROR, e); } catch (PspException e) { fail(searchResponse, ErrorCode.CUSTOM_ERROR, e); } finally { ldapPool.checkIn(ldap); } } /** * Converts spml modifications to jndi modifications. * * @param modification the spml modification * @return the jndi modifications * @throws PspException if a psp error occurs */ protected List<ModificationItem> getDsmlMods(Modification modification) throws PspException { List<ModificationItem> mods = new ArrayList<ModificationItem>(); for (Object object : modification.getOpenContentElements(DSMLModification.class)) { DSMLModification dsmlModification = (DSMLModification) object; Attribute attribute = new BasicAttribute(dsmlModification.getName()); DSMLValue[] dsmlValues = dsmlModification.getValues(); for (DSMLValue dsmlValue : dsmlValues) { // for example, when <dsmlValue><dsmlValue/> and op is a replace if (!DatatypeHelper.isEmpty(dsmlValue.getValue())) { attribute.add(dsmlValue.getValue()); } } int op = -1; if (dsmlModification.getOperation().equals(ModificationMode.ADD)) { op = DirContext.ADD_ATTRIBUTE; } else if (dsmlModification.getOperation().equals(ModificationMode.DELETE)) { op = DirContext.REMOVE_ATTRIBUTE; } else if (dsmlModification.getOperation().equals(ModificationMode.REPLACE)) { op = DirContext.REPLACE_ATTRIBUTE; } else { throw new PspException("Unknown dsml modification operation : " + dsmlModification.getOperation()); } mods.add(new ModificationItem(op, attribute)); } return mods; } /** * Gets the ldap pool. * * @return the ldap pool */ public LdapPool<Ldap> getLdapPool() { return ldapPool; } /** * Gets the id of the ldap pool. * * @return the id of the ldap pool */ public String getLdapPoolId() { return ldapPoolId; } /** * Get the source of the ldap pool id. * * @return Returns the source of the ldap pool id */ public String getLdapPoolIdSource() { return ldapPoolIdSource; } /** * Gets the pso representation of the ldap entry. * * @param entry the ldap entry * @param returnData whether or not to include the identifier, data, and references * @return the pso representation of the ldap entry * @throws Spml2Exception if an spml error occurs * @throws PspException if a psp error occurs */ protected PSO getPSO(LdapEntry entry, ReturnData returnData) throws Spml2Exception, PspException { String msg = "get pso for '" + entry.getDn() + "' target '" + getId() + "'"; PSO pso = new PSO(); // determine schema entity, throws PSPException Pso psoDefinition = this.getPSODefinition(entry); LOG.debug("{} schema entity '{}'", msg, psoDefinition.getId()); pso.addOpenContentAttr(Pso.ENTITY_NAME_ATTRIBUTE, psoDefinition.getId()); PSOIdentifier psoID = new PSOIdentifier(); psoID.setTargetID(getId()); try { psoID.setID(LdapSpmlTarget.canonicalizeDn(entry.getDn())); } catch (InvalidNameException e) { LOG.error(msg + " Unable to canonicalize entry dn.", e); throw new Spml2Exception(e); } // TODO skipping container id for now // String baseId = psoDefinition.getPsoIdentifierDefinition().getBaseId(); // if (baseId != null) { // PSOIdentifier containerID = new PSOIdentifier(); // containerID.setID(baseId); // containerID.setTargetID(getId()); // psoID.setContainerID(containerID); // } pso.setPsoID(psoID); if (returnData.equals(ReturnData.DATA) || returnData.equals(ReturnData.EVERYTHING)) { LdapAttributes ldapAttributes = entry.getLdapAttributes(); // ldap attribute names are case insensitive Map<String, String> attributeNameMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); for (String attributeName : psoDefinition.getAttributeNames()) { attributeNameMap.put(attributeName, attributeName); } Map<String, String> referenceNameMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); if (returnData.equals(ReturnData.EVERYTHING)) { for (String referenceName : psoDefinition.getReferenceNames()) { referenceNameMap.put(referenceName, referenceName); } } // the order of the attributes and references in the pso should match the psp configuration // map psp attribute names to dsml attr Map<String, DSMLAttr> nameToAttr = new HashMap<String, DSMLAttr>(); // map psp reference names to references Map<String, List<Reference>> nameToRefs = new HashMap<String, List<Reference>>(); for (LdapAttribute ldapAttribute : ldapAttributes.getAttributes()) { if (attributeNameMap.containsKey(ldapAttribute.getName())) { String pspAttributeName = attributeNameMap.get(ldapAttribute.getName()); nameToAttr.put(pspAttributeName, getDsmlAttr(pspAttributeName, ldapAttribute.getStringValues())); } else if (returnData.equals(ReturnData.EVERYTHING) && referenceNameMap.containsKey(ldapAttribute.getName())) { String pspReferenceName = referenceNameMap.get(ldapAttribute.getName()); nameToRefs.put(pspReferenceName, getReferences(pspReferenceName, ldapAttribute.getStringValues())); } else { LOG.trace("{} ignoring attribute '{}'", msg, ldapAttribute.getName()); } } // data, in order defined in psp configuration Extensible data = new Extensible(); for (String attributeName : psoDefinition.getAttributeNames()) { if (nameToAttr.containsKey(attributeName)) { data.addOpenContentElement(nameToAttr.get(attributeName)); } } if (data.getOpenContentElements().length > 0) { pso.setData(data); } // references, in order defined in psp configuration if (returnData.equals(ReturnData.EVERYTHING)) { List<Reference> references = new ArrayList<Reference>(); for (String referenceName : psoDefinition.getReferenceNames()) { if (nameToRefs.containsKey(referenceName)) { references.addAll(nameToRefs.get(referenceName)); } } PSPUtil.setReferences(pso, references); } } return pso; } /** * Determine the schema entity appropriate for the given <code>LdapEntry</code>. * * @param entry the <code>LdapEntry</code> * @return the <code>PSODefintion</code> * @throws PspException if the schema entity cannot be determined. */ protected Pso getPSODefinition(LdapEntry entry) throws PspException { Attributes attributes = entry.getLdapAttributes().toAttributes(); Pso definition = null; for (Pso psoDefinition : getPSP().getPsos(getId())) { PsoIdentifyingAttribute ia = psoDefinition.getPsoIdentifyingAttribute(); if (ia == null) { continue; } String idAttrName = ia.getName(); String idAttrValue = ia.getValue(); Attribute attribute = attributes.get(idAttrName); if (attribute != null && attribute.contains(idAttrValue)) { if (definition != null) { LOG.error("More than one schema entity found for " + entry.getDn()); throw new PspException("More than one schema entity found for " + entry.getDn()); } definition = psoDefinition; } } if (definition == null) { LOG.error("Unable to determine schema entity for " + entry.getDn()); throw new PspException("Unable to determine schema entity for " + entry.getDn()); } return definition; } /** * Converts spml modifications to jndi modifications. * * @param modification the spml modification * @return the jndi modifications * @throws PspException if a psp error occurs */ protected List<ModificationItem> getReferenceMods(Modification modification) throws PspException { List<ModificationItem> mods = new ArrayList<ModificationItem>(); Map<String, List<Reference>> references = PSPUtil.getReferences(modification.getCapabilityData()); if (references.isEmpty()) { return mods; } for (String typeOfReference : references.keySet()) { List<String> ids = new ArrayList<String>(); for (Reference reference : references.get(typeOfReference)) { if (reference.getToPsoID().getTargetID().equals(getId())) { String id = reference.getToPsoID().getID(); // fake empty string since the spml toolkit ignores an empty string psoID // if (id.equals(PSOReferencesDefinition.EMPTY_STRING)) { // id = ""; // } if (id == null) { id = ""; } ids.add(id); } } Attribute attribute = new BasicAttribute(typeOfReference); for (String id : ids) { attribute.add(id); } int op = -1; if (modification.getModificationMode().equals(ModificationMode.ADD)) { op = DirContext.ADD_ATTRIBUTE; } else if (modification.getModificationMode().equals(ModificationMode.DELETE)) { op = DirContext.REMOVE_ATTRIBUTE; } else if (modification.getModificationMode().equals(ModificationMode.REPLACE)) { op = DirContext.REPLACE_ATTRIBUTE; } else { throw new PspException("Unknown modification operation : " + modification.getModificationMode()); } mods.add(new ModificationItem(op, attribute)); } return mods; } /** * Constructs references whose type is the given name and whose ids are the given values. * * @param name the type of references * @param values the ids of the references * @return the references whose type is the given name and whose ids are the given values * @throws Spml2Exception if an spml error occurs */ protected List<Reference> getReferences(String name, Collection<String> values) throws Spml2Exception { try { List<Reference> references = new ArrayList<Reference>(); for (String value : values) { Reference reference = new Reference(); PSOIdentifier toPSOId = new PSOIdentifier(); toPSOId.setID(LdapSpmlTarget.canonicalizeDn(value)); toPSOId.setTargetID(getId()); // TODO containerID ? // PSOIdentifier containerID = new PSOIdentifier(); // containerID.setID(LdapUtil.getParentDn(toPSOId.getID())); // containerID.setTargetID(getId()); // toPSOId.setContainerID(containerID); reference.setToPsoID(toPSOId); reference.setTypeOfReference(name); references.add(reference); } return references; } catch (InvalidNameException e) { LOG.error("Unable to canonicalize name", e); throw new Spml2Exception(e); } } /** * Handle provisioning add requests with no references to a target which requires references to not be empty, such * as OpenLDAP. * * @param addRequest the add request * @throws PspException if a psp error occurs * @throws DSMLProfileException if a dsml error occurs */ protected void handleEmptyReferences(AddRequest addRequest) throws PspException, DSMLProfileException { if (!addRequest.getReturnData().equals(ReturnData.DATA)) { return; } String entityName = addRequest.findOpenContentAttrValueByName(Pso.ENTITY_NAME_ATTRIBUTE); if (entityName == null) { throw new PspException("Null entity name."); } Pso psoDefinition = getPSP().getPso(getId(), entityName); if (psoDefinition == null) { throw new PspException("Unknown pso."); } Map<String, DSMLAttr> dsmlAttrs = PSPUtil.getDSMLAttrMap(addRequest.getData()); for (PsoReferences refsDef : psoDefinition.getReferences()) { String emptyValue = refsDef.getEmptyValue(); if (emptyValue != null) { DSMLAttr member = dsmlAttrs.get(refsDef.getName()); if (member == null || member.getValues().length == 0) { addRequest.getData() .addOpenContentElement(new DSMLAttr(refsDef.getName(), refsDef.getEmptyValue())); } } } } /** * Handle modify requests which attempt to delete the last value of the member or uniqueMember attribute of the * OpenLDAP groupOfNames and groupOfUniqueNames objectClass. If the modify request comprises one delete modification * of a reference attribute configured with an emptyValue, then first add a new reference whose ID is the empty * value before deleting the last value of the attribute. * * @param modifyRequest the modify request * @return the modified request suitable for retry or null if all preconditions are not met * * @throws PspException if capability data in the request which must be understood is not understood */ protected ModifyRequest handleEmptyReferences(ModifyRequest modifyRequest) throws PspException { LOG.debug("Modify request before:\n{}", toXML(modifyRequest)); // determine the pso definition name String entityName = modifyRequest.findOpenContentAttrValueByName(Pso.ENTITY_NAME_ATTRIBUTE); if (entityName == null) { LOG.error("Unable to determine entity " + PSPUtil.toString(modifyRequest)); return null; } // the pso definition Pso psoDefinition = getPSP().getPso(getId(), entityName); if (psoDefinition == null) { LOG.error("Unable to determine provisioned object " + PSPUtil.toString(modifyRequest)); return null; } // the original modification, should be a delete Modification[] modifications = modifyRequest.getModifications(); if (modifications.length != 1) { LOG.debug("Only one modification is supported " + PSPUtil.toString(modifyRequest)); return null; } Modification originalModification = modifications[0]; if (!originalModification.getModificationMode().equals(ModificationMode.DELETE)) { LOG.debug("Only the delete modification mode is supported " + PSPUtil.toString(modifyRequest)); return null; } // TODO exception // convert the modification into reference modifications List<ModificationItem> modificationItems = getReferenceMods(originalModification); if (modificationItems.size() != 1) { LOG.debug("Only one reference modification is supported " + PSPUtil.toString(modifyRequest)); return null; } // the reference modification item ModificationItem modItem = modificationItems.get(0); // the references definition matching the reference modification PsoReferences refsDef = psoDefinition.getReferences(modItem.getAttribute().getID()); if (refsDef == null) { LOG.debug("Unable to determine references definition " + PSPUtil.toString(modifyRequest)); return null; } // the configured empty value String emptyValue = refsDef.getEmptyValue(); if (emptyValue == null) { LOG.debug("An empty value is not configured for references definition '" + refsDef.getName() + "' " + PSPUtil.toString(modifyRequest)); return null; } // a reference to the empty value Reference reference = new Reference(); reference.setToPsoID(new PSOIdentifier(emptyValue, null, getId())); reference.setTypeOfReference(refsDef.getName()); // a modification adding the reference to the empty value Modification newModification = new Modification(); try { newModification.addCapabilityData(PSPUtil.fromReferences(Arrays.asList(new Reference[] { reference }))); } catch (Spml2Exception e) { LOG.error("Unable to add reference capability data " + PSPUtil.toString(modifyRequest), e); return null; } newModification.setModificationMode(ModificationMode.ADD); // clear modifications, add the reference to the empty value, and then re-add the original modification modifyRequest.clearModifications(); modifyRequest.addModification(newModification); modifyRequest.addModification(originalModification); LOG.debug("Modify request after:\n{}", toXML(modifyRequest)); return modifyRequest; } /** * Whether or not to log ldif. * * @return whether or not to log ldif */ public boolean isLogLdif() { return logLdif; } /** {@inheritDoc} */ protected void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException { LdapPool<Ldap> oldPool = ldapPool; try { LOG.debug("Target '{}' - Loading ldap pool '{}'", getId(), getLdapPoolId()); if (ldapPoolIdSource.equals("spring")) { LOG.debug("Target '{}' - Loading ldap pool '{}' from spring", getId(), getLdapPoolId()); ldapPool = (LdapPool<Ldap>) newServiceContext.getBean(getLdapPoolId()); } if (ldapPoolIdSource.equals("grouper")) { LOG.debug("Target '{}' - Loading ldap pool '{}' from grouper", getId(), getLdapPoolId()); Source source = SourceManager.getInstance().getSource(getLdapPoolId()); LdapSourceAdapter lsa = (LdapSourceAdapter) source; ldapPool = lsa.getLdapPool(); } } catch (Exception e) { ldapPool = oldPool; LOG.error(getId() + " configuration is not valid, retaining old configuration", e); throw new ServiceException(getId() + " configuration is not valid, retaining old configuration", e); } } /** {@inheritDoc} */ public Set<PSOIdentifier> orderForDeletion(final Set<PSOIdentifier> psoIdentifiers) throws PspException { // tree map keys are in ascending order, this will need to be reversed Map<LdapName, PSOIdentifier> map = new TreeMap<LdapName, PSOIdentifier>(); try { for (PSOIdentifier psoIdentifier : psoIdentifiers) { LdapName ldapName = new LdapName(psoIdentifier.getID()); map.put(ldapName, psoIdentifier); } } catch (InvalidNameException e) { LOG.error("An error occurred ordering the PSO identifiers.", e); throw new PspException(e); } // linked hash set to preserver insertion order Set<PSOIdentifier> psoIdsOrderedForDeletion = new LinkedHashSet<PSOIdentifier>(); ArrayList<LdapName> ldapNames = new ArrayList<LdapName>(map.keySet()); // reverse the order of the keys, suitable for deletion Collections.reverse(ldapNames); for (LdapName ldapName : ldapNames) { psoIdsOrderedForDeletion.add(map.get(ldapName)); } if (LOG.isTraceEnabled()) { for (PSOIdentifier psoId : psoIdsOrderedForDeletion) { LOG.trace("correct pso id '{}'", PSPUtil.toString(psoId)); } } return psoIdsOrderedForDeletion; } /** * Sets the id of the ldap pool. * * @param ldapPoolId the id of the ldap pool */ public void setLdapPoolId(String ldapPoolId) { this.ldapPoolId = ldapPoolId; } /** * Sets the source of the ldap pool id. * * @param ldapPoolIdSource the source of the ldap pool id */ public void setLdapPoolIdSource(String ldapPoolIdSource) { this.ldapPoolIdSource = ldapPoolIdSource; } /** * Sets whether or not to log ldif. * * @param logLdif whether or not to log ldif */ public void setLogLdif(boolean logLdif) { this.logLdif = logLdif; } }