/*
* JBoss, a division of Red Hat
* Copyright 2006, Red Hat Middleware, LLC, and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.identity.idm.impl.store.ldap;
import org.jboss.identity.idm.common.exception.IdentityException;
import org.jboss.identity.idm.impl.NotYetImplementedException;
import org.jboss.identity.idm.impl.api.SimpleAttribute;
import org.jboss.identity.idm.impl.helper.Tools;
import org.jboss.identity.idm.impl.model.ldap.LDAPIdentityObjectImpl;
import org.jboss.identity.idm.impl.model.ldap.LDAPIdentityObjectRelationshipImpl;
import org.jboss.identity.idm.impl.store.FeaturesMetaDataImpl;
import org.jboss.identity.idm.spi.configuration.IdentityStoreConfigurationContext;
import org.jboss.identity.idm.spi.configuration.metadata.IdentityObjectAttributeMetaData;
import org.jboss.identity.idm.spi.configuration.metadata.IdentityObjectTypeMetaData;
import org.jboss.identity.idm.spi.configuration.metadata.IdentityStoreConfigurationMetaData;
import org.jboss.identity.idm.spi.exception.OperationNotSupportedException;
import org.jboss.identity.idm.spi.model.IdentityObject;
import org.jboss.identity.idm.spi.model.IdentityObjectAttribute;
import org.jboss.identity.idm.spi.model.IdentityObjectCredential;
import org.jboss.identity.idm.spi.model.IdentityObjectRelationship;
import org.jboss.identity.idm.spi.model.IdentityObjectRelationshipType;
import org.jboss.identity.idm.spi.model.IdentityObjectType;
import org.jboss.identity.idm.spi.search.IdentityObjectSearchCriteria;
import org.jboss.identity.idm.spi.store.FeaturesMetaData;
import org.jboss.identity.idm.spi.store.IdentityObjectSearchCriteriaType;
import org.jboss.identity.idm.spi.store.IdentityStore;
import org.jboss.identity.idm.spi.store.IdentityStoreInvocationContext;
import org.jboss.identity.idm.spi.store.IdentityStoreSession;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
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 javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.SortControl;
/**
* @author <a href="mailto:boleslaw.dawidowicz at redhat.com">Boleslaw Dawidowicz</a>
* @version : 0.1 $
*/
public class LDAPIdentityStoreImpl implements IdentityStore
{
//TODO: external JNDI
//TODO: more options for connection configuration
//TODO: JNDI connection credentials encoding (pluggable?)
private static Logger log = Logger.getLogger(LDAPIdentityStoreImpl.class.getName());
private final String id;
public static final String MEMBERSHIP_TYPE = "JBOSS_IDENTITY_MEMBERSHIP";
private FeaturesMetaData supportedFeatures;
LDAPIdentityStoreConfiguration configuration;
IdentityStoreConfigurationMetaData configurationMD;
private final Set<IdentityObjectSearchCriteriaType> supportedSearchCriteriaTypes =
new HashSet<IdentityObjectSearchCriteriaType>();
// <IdentityObjectType name, <Attribute name, MD>
private Map<String, Map<String, IdentityObjectAttributeMetaData>> attributesMetaData = new HashMap<String, Map<String, IdentityObjectAttributeMetaData>>();
public LDAPIdentityStoreImpl(String id)
{
this.id = id;
}
public void bootstrap(IdentityStoreConfigurationContext configurationContext) throws IdentityException
{
if (configurationContext == null)
{
throw new IllegalArgumentException("Configuration context is null");
}
this.configurationMD = configurationContext.getStoreConfigurationMetaData();
configuration = new SimpleLDAPIdentityStoreConfiguration(configurationMD);
Set<String> readOnlyObjectTypes = new HashSet<String>();
for (IdentityObjectType identityObjectType : configuration.getConfiguredTypes())
{
if (!configuration.getTypeConfiguration(identityObjectType.getName()).isAllowCreateEntry())
{
readOnlyObjectTypes.add(identityObjectType.getName());
}
}
supportedSearchCriteriaTypes.clear();
if (configuration.isSortExtensionSupported())
{
supportedSearchCriteriaTypes.add(IdentityObjectSearchCriteriaType.SORT);
}
supportedSearchCriteriaTypes.add(IdentityObjectSearchCriteriaType.PAGE);
supportedSearchCriteriaTypes.add(IdentityObjectSearchCriteriaType.NAME_FILTER);
supportedFeatures = new FeaturesMetaDataImpl(configurationMD, supportedSearchCriteriaTypes, false, false, readOnlyObjectTypes);
// Attribute mappings - helper structures
for (IdentityObjectTypeMetaData identityObjectTypeMetaData : configurationMD.getSupportedIdentityTypes())
{
Map<String, IdentityObjectAttributeMetaData> metadataMap = new HashMap<String, IdentityObjectAttributeMetaData>();
for (IdentityObjectAttributeMetaData attributeMetaData : identityObjectTypeMetaData.getAttributes())
{
metadataMap.put(attributeMetaData.getName(), attributeMetaData);
}
attributesMetaData.put(identityObjectTypeMetaData.getName(), metadataMap);
}
}
public IdentityStoreSession createIdentityStoreSession()
{
return new LDAPIdentityStoreSessionImpl(configuration);
}
public String getId()
{
return id;
}
public FeaturesMetaData getSupportedFeatures()
{
return supportedFeatures;
}
public IdentityObject createIdentityObject(IdentityStoreInvocationContext invocationCtx, String name, IdentityObjectType identityObjectType) throws IdentityException
{
return createIdentityObject(invocationCtx, name, identityObjectType, null);
}
public IdentityObject createIdentityObject(IdentityStoreInvocationContext invocationCtx,
String name,
IdentityObjectType type,
Map<String, String[]> attributes) throws IdentityException
{
if (name == null)
{
throw new IdentityException("Name cannot be null");
}
checkIOType(type);
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".createIdentityObject with name: " + name + " and type: " + type.getName());
}
LdapContext ldapContext = getLDAPContext(invocationCtx);
try
{
// If there are many contexts specified in the configuration the first one is used
LdapContext ctx = (LdapContext)ldapContext.lookup(getTypeConfiguration(invocationCtx, type).getCtxDNs()[0]);
//We store new entry using set of attributes. This should give more flexibility then
//extending identity object from ContextDir - configure what objectClass place there
Attributes attrs = new BasicAttributes(true);
//create attribute using provided configuration
Map<String, String[]> attributesToAdd = getTypeConfiguration(invocationCtx, type).getCreateEntryAttributeValues();
//merge
if (attributes != null)
{
for (Map.Entry<String, String[]> entry : attributes.entrySet())
{
if (!attributesToAdd.containsKey(entry.getKey()))
{
attributesToAdd.put(entry.getKey(), entry.getValue());
}
else
{
List<String> list1 = Arrays.asList(attributesToAdd.get(entry.getKey()));
List<String> list2 = Arrays.asList(entry.getValue());
list1.addAll(list2);
String[] vals = list1.toArray(new String[list1.size()]);
attributesToAdd.put(entry.getKey(), vals);
}
}
}
//attributes
for (Iterator it1 = attributesToAdd.keySet().iterator(); it1.hasNext();)
{
String attributeName = (String)it1.next();
Attribute attr = new BasicAttribute(attributeName);
String[] attributeValues = attributesToAdd.get(attributeName);
//values
for (String attrValue : attributeValues)
{
attr.add(attrValue);
}
attrs.put(attr);
}
// Make it RFC 2253 compliant
LdapName validLDAPName = new LdapName(getTypeConfiguration(invocationCtx, type).getIdAttributeName().concat("=").concat(name));
log.finer("creating ldap entry for: " + validLDAPName + "; " + attrs);
ctx.createSubcontext(validLDAPName, attrs);
}
catch (Exception e)
{
throw new IdentityException("Failed to create identity object", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
return findIdentityObject(invocationCtx, name, type);
}
public void removeIdentityObject(IdentityStoreInvocationContext invocationCtx, IdentityObject identity) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".removeIdentityObject: " + identity);
}
LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(invocationCtx, identity);
String dn = ldapIdentity.getDn();
if (dn == null)
{
throw new IdentityException("Cannot obtain DN of identity");
}
LdapContext ldapContext = getLDAPContext(invocationCtx);
try
{
log.finer("removing entry: " + dn);
ldapContext.unbind(dn);
}
catch (Exception e)
{
throw new IdentityException("Failed to remove identity: ", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
}
public int getIdentityObjectsCount(IdentityStoreInvocationContext ctx, IdentityObjectType identityType) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".getIdentityObjectsCount for type: " + identityType);
}
checkIOType(identityType);
try
{
String filter = getTypeConfiguration(ctx, identityType).getEntrySearchFilter();
if (filter != null && filter.length() > 0)
{
// chars are escaped in filterArgs so we must replace it manually
filter = filter.replaceAll("\\{0\\}", "*");
}
else
{
//search all entries
filter = "(".concat(getTypeConfiguration(ctx, identityType).getIdAttributeName()).concat("=").concat("*").concat(")");
}
String[] entryCtxs = getTypeConfiguration(ctx, identityType).getCtxDNs();
//log.debug("Search filter: " + filter);
List sr = searchIdentityObjects(ctx,
entryCtxs,
filter,
null,
new String[]{getTypeConfiguration(ctx, identityType).getIdAttributeName()},
null);
return sr.size();
}
catch (NoSuchElementException e)
{
//log.debug("No identity object found", e);
}
catch (Exception e)
{
throw new IdentityException("User search failed.", e);
}
return 0;
}
public IdentityObject findIdentityObject(IdentityStoreInvocationContext invocationCtx, String name, IdentityObjectType type) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".findIdentityObject with name: " + name + "; and type: " + type);
}
Context ctx = null;
checkIOType(type);
try
{
//log.debug("name = " + name);
if (name == null)
{
throw new IdentityException("Identity object name canot be null");
}
String filter = getTypeConfiguration(invocationCtx, type).getEntrySearchFilter();
List sr = null;
String[] entryCtxs = getTypeConfiguration(invocationCtx, type).getCtxDNs();
if (filter != null && filter.length() > 0)
{
Object[] filterArgs = {name};
sr = searchIdentityObjects(invocationCtx,
entryCtxs,
filter,
filterArgs,
new String[]{getTypeConfiguration(invocationCtx, type).getIdAttributeName()},
null);
}
else
{
//search all entries
filter = "(".concat(getTypeConfiguration(invocationCtx, type).getIdAttributeName()).concat("=").concat(name).concat(")");
sr = searchIdentityObjects(invocationCtx,
entryCtxs,
filter,
null,
new String[]{getTypeConfiguration(invocationCtx, type).getIdAttributeName()},
null);
}
//log.debug("Search filter: " + filter);
if (sr.size() > 1)
{
throw new IdentityException("Found more than one identity object with name: " + name +
"; Posible data inconsistency");
}
SearchResult res = (SearchResult)sr.iterator().next();
ctx = (Context)res.getObject();
String dn = ctx.getNameInNamespace();
IdentityObject io = createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn);
ctx.close();
return io;
}
catch (NoSuchElementException e)
{
//log.debug("No identity object found with name: " + name, e);
}
catch (NamingException e)
{
throw new IdentityException("IdentityObject search failed.", e);
}
finally
{
try
{
if (ctx != null)
{
ctx.close();
}
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
return null;
}
public IdentityObject findIdentityObject(IdentityStoreInvocationContext ctx, String id) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".findIdentityObject with id: " + id);
}
LdapContext ldapContext = getLDAPContext(ctx);
try
{
if (id == null)
{
throw new IdentityException("identity id cannot be null");
}
String dn = id;
IdentityObjectType type = null;
//Recognize the type by ctx DN
IdentityObjectType[] possibleTypes = getConfiguration(ctx).getConfiguredTypes();
for (IdentityObjectType possibleType : possibleTypes)
{
String[] typeCtxs = getTypeConfiguration(ctx, possibleType).getCtxDNs();
for (String typeCtx : typeCtxs)
{
if (dn.toLowerCase().endsWith(typeCtx.toLowerCase()))
{
type = possibleType;
break;
}
}
if (type != null)
{
break;
}
}
if (type == null)
{
throw new IdentityException("Cannot recognize identity object type by its DN: " + dn);
}
// Grab entry
Attributes attrs = ldapContext.getAttributes(dn);
if (attrs == null)
{
throw new IdentityException("Can't find identity entry with DN: " + dn);
}
return createIdentityObjectInstance(ctx, type, attrs, dn);
}
catch (NoSuchElementException e)
{
//log.debug("No identity object found with dn: " + dn, e);
}
catch (NamingException e)
{
throw new IdentityException("Identity object search failed.", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
return null;
}
public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext invocationCtx,
IdentityObjectType type,
IdentityObjectSearchCriteria criteria) throws IdentityException
{
//TODO: page control with LDAP request control
String nameFilter = "*";
//Filter by name
if (criteria != null && criteria.getFilter() != null)
{
nameFilter = criteria.getFilter();
}
LdapContext ctx = getLDAPContext(invocationCtx);
checkIOType(type);
LinkedList<IdentityObject> objects = new LinkedList<IdentityObject>();
LDAPIdentityObjectTypeConfiguration typeConfiguration = getTypeConfiguration(invocationCtx, type);
try
{
Control[] requestControls = null;
// Sort control
if (criteria != null && criteria.isSorted() && configuration.isSortExtensionSupported())
{
//TODO sort by attribute name
requestControls = new Control[]{
new SortControl(typeConfiguration.getIdAttributeName(), Control.CRITICAL)
};
}
StringBuilder af = new StringBuilder();
// Filter by attribute values
if (criteria != null && criteria.isFiltered())
{
af.append("(&");
for (Map.Entry<String, String[]> stringEntry : criteria.getValues().entrySet())
{
for (String value : stringEntry.getValue())
{
af.append("(")
.append(stringEntry.getKey())
.append("=")
.append(value)
.append(")");
}
}
af.append(")");
}
String filter = getTypeConfiguration(invocationCtx, type).getEntrySearchFilter();
List<SearchResult> sr = null;
String[] entryCtxs = getTypeConfiguration(invocationCtx, type).getCtxDNs();
if (filter != null && filter.length() > 0)
{
Object[] filterArgs = {nameFilter};
sr = searchIdentityObjects(invocationCtx,
entryCtxs,
"(&(" + filter + ")" + af.toString() + ")",
filterArgs,
new String[]{typeConfiguration.getIdAttributeName()},
requestControls);
}
else
{
filter = "(".concat(typeConfiguration.getIdAttributeName()).concat("=").concat(nameFilter).concat(")");
sr = searchIdentityObjects(invocationCtx,
entryCtxs,
"(&(" + filter + ")" + af.toString() + ")",
null,
new String[]{typeConfiguration.getIdAttributeName()},
requestControls);
}
for (SearchResult res : sr)
{
ctx = (LdapContext)res.getObject();
String dn = ctx.getNameInNamespace();
if (criteria != null && criteria.isSorted() && configuration.isSortExtensionSupported())
{
// It seams that the sort order is not configurable and
// sort control returns entries in descending order by default...
if (!criteria.isAscending())
{
objects.addFirst(createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn));
}
else
{
objects.addLast(createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn));
}
}
else
{
objects.add(createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn));
}
}
ctx.close();
}
catch (NoSuchElementException e)
{
//log.debug("No identity object found with name: " + name, e);
}
catch (Exception e)
{
throw new IdentityException("IdentityObject search failed.", e);
}
finally
{
try
{
if (ctx != null)
{
ctx.close();
}
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
// In case sort extension is not supported
if (criteria != null && criteria.isSorted() && !configuration.isSortExtensionSupported())
{
sortByName(objects, criteria.isAscending());
}
if (criteria != null && criteria.isPaged())
{
objects = (LinkedList)cutPageFromResults(objects, criteria);
}
return objects;
}
public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext invocationCtx, IdentityObjectType type) throws IdentityException
{
return findIdentityObject(invocationCtx, type, null);
}
public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext ctx,
IdentityObject identity,
IdentityObjectRelationshipType relationshipType,
boolean parent,
IdentityObjectSearchCriteria criteria) throws IdentityException
{
if (relationshipType != null && !relationshipType.getName().equals(MEMBERSHIP_TYPE))
{
throw new IdentityException("This store implementation supports only '" + MEMBERSHIP_TYPE +"' relationship type");
}
LDAPIdentityObjectImpl ldapIO = getSafeLDAPIO(ctx, identity);
LDAPIdentityObjectTypeConfiguration typeConfig = getTypeConfiguration(ctx, identity.getIdentityType());
LdapContext ldapContext = getLDAPContext(ctx);
List<IdentityObject> objects = new LinkedList<IdentityObject>();
try
{
// If parent simply look for all its members
if (parent)
{
if (typeConfig.getParentMembershipAttributeName() != null)
{
Attributes attrs = ldapContext.getAttributes(ldapIO.getDn());
Attribute member = attrs.get(typeConfig.getParentMembershipAttributeName());
if (member != null)
{
NamingEnumeration memberValues = member.getAll();
while (memberValues.hasMoreElements())
{
String memberRef = memberValues.nextElement().toString();
// Ignore placeholder value in memberships
String placeholder = typeConfig.getParentMembershipAttributePlaceholder();
if (placeholder != null && memberRef.equalsIgnoreCase(placeholder))
{
continue;
}
if (typeConfig.isParentMembershipAttributeDN())
{
//TODO: use direct LDAP query instead of other find method and add attributesFilter
if (criteria != null && criteria.getFilter() != null)
{
String name = Tools.stripDnToName(memberRef);
String regex = Tools.wildcardToRegex(criteria.getFilter());
if (Pattern.matches(regex, name))
{
objects.add(findIdentityObject(ctx, memberRef));
}
}
else
{
objects.add(findIdentityObject(ctx, memberRef));
}
}
else
{
//TODO: if relationships are not refered with DNs and only names its not possible to map
//TODO: them to proper IdentityType and keep name uniqnes per type. Workaround needed
throw new NotYetImplementedException("LDAP limitation. If relationship targets are not refered with FQDNs " +
"and only names, it's not possible to map them to proper IdentityType and keep name uniqnes per type. " +
"Workaround needed");
}
//break;
}
}
}
else
{
objects.addAll(findRelatedIdentityObjects(ctx, identity, ldapIO, criteria, false));
}
}
// if not parent then all parent entries need to be found
else
{
if (typeConfig.getChildMembershipAttributeName() == null)
{
objects.addAll(findRelatedIdentityObjects(ctx, identity, ldapIO, criteria, true));
}
else
{
Attributes attrs = ldapContext.getAttributes(ldapIO.getDn());
Attribute member = attrs.get(typeConfig.getChildMembershipAttributeName());
if (member != null)
{
NamingEnumeration memberValues = member.getAll();
while (memberValues.hasMoreElements())
{
String memberRef = memberValues.nextElement().toString();
if (typeConfig.isChildMembershipAttributeDN())
{
//TODO: use direct LDAP query instead of other find method and add attributesFilter
if (criteria != null && criteria.getFilter() != null)
{
String name = Tools.stripDnToName(memberRef);
String regex = Tools.wildcardToRegex(criteria.getFilter());
if (Pattern.matches(regex, name))
{
objects.add(findIdentityObject(ctx, memberRef));
}
}
else
{
objects.add(findIdentityObject(ctx, memberRef));
}
}
else
{
//TODO: if relationships are not refered with DNs and only names its not possible to map
//TODO: them to proper IdentityType and keep name uniqnes per type. Workaround needed
throw new NotYetImplementedException("LDAP limitation. If relationship targets are not refered with FQDNs " +
"and only names, it's not possible to map them to proper IdentityType and keep name uniqnes per type. " +
"Workaround needed");
}
//break;
}
}
}
}
}
catch (NamingException e)
{
throw new IdentityException("Failed to resolve relationship", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
if (criteria != null && criteria.isPaged())
{
objects = cutPageFromResults(objects, criteria);
}
if (criteria != null && criteria.isSorted())
{
sortByName(objects, criteria.isAscending());
}
return objects;
}
public List<IdentityObject> findRelatedIdentityObjects(IdentityStoreInvocationContext ctx,
IdentityObject identity,
LDAPIdentityObjectImpl ldapIO,
IdentityObjectSearchCriteria criteria,
boolean parents)
throws IdentityException, NamingException
{
List<IdentityObject> objects = new LinkedList<IdentityObject>();
LDAPIdentityObjectTypeConfiguration typeConfiguration = getTypeConfiguration(ctx, identity.getIdentityType());
List<String> allowedTypes = Arrays.asList(typeConfiguration.getAllowedMembershipTypes());
// Search in all other type contexts
for (IdentityObjectType checkedIOType : configuration.getConfiguredTypes())
{
checkIOType(checkedIOType);
LDAPIdentityObjectTypeConfiguration checkedTypeConfiguration = getTypeConfiguration(ctx, checkedIOType);
List<String> checkedAllowedTypes = Arrays.asList(checkedTypeConfiguration.getAllowedMembershipTypes());
if (parents)
{
// Check if given identity type can be parent
if (!checkedAllowedTypes.contains(identity.getIdentityType().getName()))
{
continue;
}
}
else
{
//Check if given identity type can be child
if (!allowedTypes.contains(checkedIOType.getName()))
{
continue;
}
}
// Check if this type is capable to keep needed reference
if (parents && checkedTypeConfiguration.getParentMembershipAttributeName() == null)
{
continue;
}
if (!parents && checkedTypeConfiguration.getChildMembershipAttributeName() == null)
{
continue;
}
String nameFilter = "*";
//Filter by name
if (criteria != null && criteria.getFilter() != null)
{
nameFilter = criteria.getFilter();
}
Control[] requestControls = null;
StringBuilder af = new StringBuilder();
// Filter by attribute values
if (criteria != null && criteria.isFiltered())
{
af.append("(&");
for (Map.Entry<String, String[]> stringEntry : criteria.getValues().entrySet())
{
for (String value : stringEntry.getValue())
{
af.append("(")
.append(stringEntry.getKey())
.append("=")
.append(value)
.append(")");
}
}
af.append(")");
}
if(parents)
{
// Add filter to search only parents of the given entry
af.append("(")
.append(checkedTypeConfiguration.getParentMembershipAttributeName())
.append("=");
if (checkedTypeConfiguration.isParentMembershipAttributeDN())
{
af.append(ldapIO.getDn());
}
else
{
//TODO: this doesn't make much sense unless parent/child are same identity types and resides in the same LDAP context
af.append(ldapIO.getName());
}
af.append(")");
}
else
{
// Add filter to search only childs of the given entry
af.append("(")
.append(checkedTypeConfiguration.getChildMembershipAttributeName())
.append("=");
if (checkedTypeConfiguration.isChildMembershipAttributeDN())
{
af.append(ldapIO.getDn());
}
else
{
//TODO: this doesn't make much sense unless parent/child are same identity types and resides in the same LDAP context
af.append(ldapIO.getName());
}
af.append(")");
}
String filter = checkedTypeConfiguration.getEntrySearchFilter();
List<SearchResult> sr = null;
String[] entryCtxs = checkedTypeConfiguration.getCtxDNs();
if (filter != null && filter.length() > 0)
{
Object[] filterArgs = {nameFilter};
sr = searchIdentityObjects(ctx,
entryCtxs,
"(&(" + filter + ")" + af.toString() + ")",
filterArgs,
new String[]{checkedTypeConfiguration.getIdAttributeName()},
requestControls);
}
else
{
filter = "(".concat(checkedTypeConfiguration.getIdAttributeName()).concat("=").concat(nameFilter).concat(")");
sr = searchIdentityObjects(ctx,
entryCtxs,
"(&(" + filter + ")" + af.toString() + ")",
null,
new String[]{checkedTypeConfiguration.getIdAttributeName()},
requestControls);
}
for (SearchResult res : sr)
{
LdapContext ldapCtx = (LdapContext)res.getObject();
String dn = ldapCtx.getNameInNamespace();
if (parents)
{
// Ignore placeholder value in memberships
String placeholder = checkedTypeConfiguration.getParentMembershipAttributePlaceholder();
if (placeholder != null && dn.equalsIgnoreCase(placeholder))
{
continue;
}
}
objects.add(createIdentityObjectInstance(ctx, checkedIOType, res.getAttributes(), dn));
}
}
return objects;
}
public Set<IdentityObjectRelationship> resolveRelationships(IdentityStoreInvocationContext ctx,
IdentityObject identity,
IdentityObjectRelationshipType type,
boolean parent,
boolean named,
String name) throws IdentityException
{
if (type == null || !type.getName().equals(MEMBERSHIP_TYPE))
{
throw new IdentityException("This store implementation supports only '" + MEMBERSHIP_TYPE +"' relationship type");
}
LDAPIdentityObjectImpl ldapIO = getSafeLDAPIO(ctx, identity);
LDAPIdentityObjectTypeConfiguration typeConfig = getTypeConfiguration(ctx, identity.getIdentityType());
LdapContext ldapContext = getLDAPContext(ctx);
Set<IdentityObjectRelationship> relationships = new HashSet<IdentityObjectRelationship>();
try
{
// If parent simply look for all its members
if (parent)
{
Attributes attrs = ldapContext.getAttributes(ldapIO.getDn());
if (typeConfig.getParentMembershipAttributeName() != null )
{
Attribute member = attrs.get(typeConfig.getParentMembershipAttributeName());
if (member != null)
{
NamingEnumeration memberValues = member.getAll();
while (memberValues.hasMoreElements())
{
String memberRef = memberValues.nextElement().toString();
// Ignore placeholder value in memberships
String placeholder = typeConfig.getParentMembershipAttributePlaceholder();
if (placeholder != null && memberRef.equalsIgnoreCase(placeholder))
{
continue;
}
if (typeConfig.isParentMembershipAttributeDN())
{
//TODO: use direct LDAP query instaed of other find method and add attributesFilter
relationships.add(new LDAPIdentityObjectRelationshipImpl(MEMBERSHIP_TYPE, ldapIO, findIdentityObject(ctx, memberRef)));
}
else
{
//TODO: if relationships are not refered with DNs and only names its not possible to map
//TODO: them to proper IdentityType and keep name uniqnes per type. Workaround needed
throw new NotYetImplementedException("LDAP limitation. If relationship targets are not refered with FQDNs " +
"and only names, it's not possible to map them to proper IdentityType and keep name uniqnes per type. " +
"Workaround needed");
}
//break;
}
}
}
else
{
relationships.addAll(findRelationships(ctx, identity, ldapIO, false));
}
}
// if not parent then all parent entries need to be found
else
{
Attributes attrs = ldapContext.getAttributes(ldapIO.getDn());
if (typeConfig.getChildMembershipAttributeName() != null)
{
Attribute member = attrs.get(typeConfig.getChildMembershipAttributeName());
if (member != null)
{
NamingEnumeration memberValues = member.getAll();
while (memberValues.hasMoreElements())
{
String memberRef = memberValues.nextElement().toString();
// Ignore placeholder value in memberships
if (typeConfig.isChildMembershipAttributeDN())
{
//TODO: use direct LDAP query instaed of other find method and add attributesFilter
relationships.add(new LDAPIdentityObjectRelationshipImpl(MEMBERSHIP_TYPE, findIdentityObject(ctx, memberRef), ldapIO));
}
else
{
//TODO: if relationships are not refered with DNs and only names its not possible to map
//TODO: them to proper IdentityType and keep name uniqnes per type. Workaround needed
throw new NotYetImplementedException("LDAP limitation. If relationship targets are not refered with FQDNs " +
"and only names, it's not possible to map them to proper IdentityType and keep name uniqnes per type. " +
"Workaround needed");
}
//break;
}
}
}
else
{
relationships.addAll(findRelationships(ctx, identity, ldapIO, true));
}
}
}
catch (NamingException e)
{
throw new IdentityException("Failed to resolve relationship", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
return relationships;
}
/**
* Search all configured LDAP contexts for possible Identity Object Types and lookup for parent/child references
* to form relationships
*
* @param ctx
* @param identity
* @param ldapIO
* @param parents if true will lookup parents to reference child, if false will look children to reference parent
* @return
* @throws IdentityException
* @throws NamingException
*/
private Collection<LDAPIdentityObjectRelationshipImpl> findRelationships(IdentityStoreInvocationContext ctx,
IdentityObject identity,
LDAPIdentityObjectImpl ldapIO,
boolean parents)
throws IdentityException, NamingException
{
Set<LDAPIdentityObjectRelationshipImpl> relationships = new HashSet<LDAPIdentityObjectRelationshipImpl>();
LDAPIdentityObjectTypeConfiguration typeConfiguration = getTypeConfiguration(ctx, identity.getIdentityType());
List<String> allowedTypes = Arrays.asList(typeConfiguration.getAllowedMembershipTypes());
//
// Search in all other type contexts
for (IdentityObjectType checkedIOType : configuration.getConfiguredTypes())
{
checkIOType(checkedIOType);
LDAPIdentityObjectTypeConfiguration checkedTypeConfiguration = getTypeConfiguration(ctx, checkedIOType);
List<String> checkedAllowedTypes = Arrays.asList(checkedTypeConfiguration.getAllowedMembershipTypes());
if (parents)
{
// Check if given identity type can be parent
if (!checkedAllowedTypes.contains(identity.getIdentityType().getName()))
{
continue;
}
}
else
{
//Check if given identity type can be child
if (!allowedTypes.contains(checkedIOType.getName()))
{
continue;
}
}
String nameFilter = "*";
//Filter by name
Control[] requestControls = null;
StringBuilder af = new StringBuilder();
if(parents)
{
// Add filter to search only parents of the given entry
af.append("(")
.append(checkedTypeConfiguration.getParentMembershipAttributeName())
.append("=");
if (checkedTypeConfiguration.isParentMembershipAttributeDN())
{
af.append(ldapIO.getDn());
}
else
{
//TODO: this doesn't make much sense unless parent/child are same identity types and resides in the same LDAP context
af.append(ldapIO.getName());
}
af.append(")");
}
else
{
// Add filter to search only childs of the given entry
af.append("(")
.append(checkedTypeConfiguration.getChildMembershipAttributeName())
.append("=");
if (checkedTypeConfiguration.isChildMembershipAttributeDN())
{
af.append(ldapIO.getDn());
}
else
{
//TODO: this doesn't make much sense unless parent/child are same identity types and resides in the same LDAP context
af.append(ldapIO.getName());
}
af.append(")");
}
String filter = checkedTypeConfiguration.getEntrySearchFilter();
List<SearchResult> sr = null;
String[] entryCtxs = checkedTypeConfiguration.getCtxDNs();
if (filter != null && filter.length() > 0)
{
Object[] filterArgs = {nameFilter};
sr = searchIdentityObjects(ctx,
entryCtxs,
"(&(" + filter + ")" + af.toString() + ")",
filterArgs,
new String[]{checkedTypeConfiguration.getIdAttributeName()},
requestControls);
}
else
{
filter = "(".concat(checkedTypeConfiguration.getIdAttributeName()).concat("=").concat(nameFilter).concat(")");
sr = searchIdentityObjects(ctx,
entryCtxs,
"(&(" + filter + ")" + af.toString() + ")",
null,
new String[]{checkedTypeConfiguration.getIdAttributeName()},
requestControls);
}
for (SearchResult res : sr)
{
LdapContext ldapCtx = (LdapContext)res.getObject();
String dn = ldapCtx.getNameInNamespace();
if (parents)
{
// Ignore placeholder values
String placeholder = checkedTypeConfiguration.getParentMembershipAttributePlaceholder();
if (placeholder != null && dn.equalsIgnoreCase(placeholder))
{
continue;
}
relationships.add(new LDAPIdentityObjectRelationshipImpl(MEMBERSHIP_TYPE, createIdentityObjectInstance(ctx, checkedIOType, res.getAttributes(), dn), ldapIO));
}
else
{
relationships.add(new LDAPIdentityObjectRelationshipImpl(MEMBERSHIP_TYPE, ldapIO, createIdentityObjectInstance(ctx, checkedIOType, res.getAttributes(), dn)));
}
}
}
return relationships;
}
public Collection<IdentityObject> findIdentityObject(IdentityStoreInvocationContext ctx,
IdentityObject identity,
IdentityObjectRelationshipType relationshipType,
boolean parent) throws IdentityException
{
return findIdentityObject(ctx, identity, relationshipType, parent, null);
}
public IdentityObjectRelationship createRelationship(IdentityStoreInvocationContext ctx, IdentityObject fromIdentity, IdentityObject toIdentity,
IdentityObjectRelationshipType relationshipType,
String name, boolean createNames) throws IdentityException
{
//TODO: relationshipType is ignored for now
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".createRelationship with "
+ "fromIdentity: " + fromIdentity
+ "; toIdentity: " + toIdentity
+ "; relationshipType: " + relationshipType
);
}
if (relationshipType == null || !relationshipType.getName().equals(MEMBERSHIP_TYPE))
{
throw new IdentityException("This store implementation supports only '" + MEMBERSHIP_TYPE +"' relationship type");
}
LDAPIdentityObjectRelationshipImpl relationship = null;
LDAPIdentityObjectImpl ldapFromIO = getSafeLDAPIO(ctx, fromIdentity);
LDAPIdentityObjectImpl ldapToIO = getSafeLDAPIO(ctx, toIdentity);
LDAPIdentityObjectTypeConfiguration fromTypeConfig = getTypeConfiguration(ctx, fromIdentity.getIdentityType());
LDAPIdentityObjectTypeConfiguration toTypeConfig = getTypeConfiguration(ctx, toIdentity.getIdentityType());
LdapContext ldapContext = getLDAPContext(ctx);
// Check posibilities
if (!getSupportedFeatures().isRelationshipTypeSupported(fromIdentity.getIdentityType(), toIdentity.getIdentityType(), relationshipType))
{
throw new IdentityException("Relationship not supported. RelationshipType[ " + relationshipType + " ] " +
"beetween: [ " + fromIdentity.getIdentityType().getName() + " ] and [ " + toIdentity.getIdentityType().getName() + " ]");
}
try
{
// Construct new member attribute values
Attributes attrs = new BasicAttributes(true);
if (fromTypeConfig.getParentMembershipAttributeName() != null)
{
Attribute member = new BasicAttribute(fromTypeConfig.getParentMembershipAttributeName());
if (fromTypeConfig.isParentMembershipAttributeDN())
{
member.add(ldapToIO.getDn());
}
else
{
member.add(toIdentity.getName());
}
attrs.put(member);
ldapContext.modifyAttributes(ldapFromIO.getDn(), DirContext.ADD_ATTRIBUTE, attrs);
}
if (toTypeConfig.getChildMembershipAttributeName() != null && !toTypeConfig.isChildMembershipAttributeVirtual())
{
Attribute member = new BasicAttribute(toTypeConfig.getChildMembershipAttributeName());
if (toTypeConfig.isChildMembershipAttributeDN())
{
member.add(ldapFromIO.getDn());
}
else
{
member.add(fromIdentity.getName());
}
attrs.put(member);
ldapContext.modifyAttributes(ldapToIO.getDn(), DirContext.ADD_ATTRIBUTE, attrs);
}
relationship = new LDAPIdentityObjectRelationshipImpl(name, ldapFromIO, ldapToIO);
}
catch (NamingException e)
{
throw new IdentityException("Failed to create relationship", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
return relationship;
}
public void removeRelationship(IdentityStoreInvocationContext ctx, IdentityObject fromIdentity, IdentityObject toIdentity, IdentityObjectRelationshipType relationshipType, String name) throws IdentityException
{
// relationshipType is ignored for now
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".removeRelationship with "
+ "fromIdentity: " + fromIdentity
+ "; toIdentity: " + toIdentity
+ "; relationshipType: " + relationshipType
);
}
LDAPIdentityObjectImpl ldapFromIO = getSafeLDAPIO(ctx, fromIdentity);
LDAPIdentityObjectImpl ldapToIO = getSafeLDAPIO(ctx, toIdentity);
LDAPIdentityObjectTypeConfiguration fromTypeConfig = getTypeConfiguration(ctx, fromIdentity.getIdentityType());
LDAPIdentityObjectTypeConfiguration toTypeConfig = getTypeConfiguration(ctx, toIdentity.getIdentityType());
// If relationship is not allowed simply return
//TODO: use features description instead
if (!Arrays.asList(fromTypeConfig.getAllowedMembershipTypes()).contains(ldapToIO.getIdentityType().getName()))
{
return;
}
LdapContext ldapContext = getLDAPContext(ctx);
// Check posibilities
//TODO: null RelationshipType passed from removeRelationships
if (relationshipType != null &&
!getSupportedFeatures().isRelationshipTypeSupported(fromIdentity.getIdentityType(), toIdentity.getIdentityType(), relationshipType))
{
throw new IdentityException("Relationship not supported");
}
try
{
//construct new member attribute values
Attributes attrs = new BasicAttributes(true);
if (fromTypeConfig.getParentMembershipAttributeName() != null)
{
Attribute member = new BasicAttribute(fromTypeConfig.getParentMembershipAttributeName());
if (fromTypeConfig.isParentMembershipAttributeDN())
{
member.add(ldapToIO.getDn());
}
else
{
member.add(toIdentity.getName());
}
attrs.put(member);
ldapContext.modifyAttributes(ldapFromIO.getDn(), DirContext.REMOVE_ATTRIBUTE, attrs);
}
if (toTypeConfig.getChildMembershipAttributeName() != null && !toTypeConfig.isChildMembershipAttributeVirtual())
{
Attribute member = new BasicAttribute(toTypeConfig.getChildMembershipAttributeName());
if (toTypeConfig.isChildMembershipAttributeDN())
{
member.add(ldapFromIO.getDn());
}
else
{
member.add(fromIdentity.getName());
}
attrs.put(member);
ldapContext.modifyAttributes(ldapToIO.getDn(), DirContext.REMOVE_ATTRIBUTE, attrs);
}
}
catch (NamingException e)
{
throw new IdentityException("Failed to remove relationship", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
}
public void removeRelationships(IdentityStoreInvocationContext ctx, IdentityObject identity1, IdentityObject identity2, boolean named) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".removeRelationships with "
+ "identity1: " + identity1
+ "; identity2: " + identity2
);
}
// as relationship type is ignored in this impl for now...
removeRelationship(ctx, identity1, identity2, null, null);
removeRelationship(ctx, identity2, identity1, null, null);
}
public Set<IdentityObjectRelationship> resolveRelationships(IdentityStoreInvocationContext ctx,
IdentityObject fromIdentity,
IdentityObject toIdentity,
IdentityObjectRelationshipType relationshipType) throws IdentityException
{
// relationshipType is ignored for now
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".resolveRelationships with "
+ "fromIdentity: " + fromIdentity
+ "; toIdentity: " + toIdentity
);
}
if (relationshipType != null && !relationshipType.getName().equals(MEMBERSHIP_TYPE))
{
throw new IdentityException("This store implementation supports only '" + MEMBERSHIP_TYPE +"' relationship type");
}
Set<IdentityObjectRelationship> relationships = new HashSet<IdentityObjectRelationship>();
LDAPIdentityObjectImpl ldapFromIO = getSafeLDAPIO(ctx, fromIdentity);
LDAPIdentityObjectImpl ldapToIO = getSafeLDAPIO(ctx, toIdentity);
LDAPIdentityObjectTypeConfiguration fromTypeConfig = getTypeConfiguration(ctx, fromIdentity.getIdentityType());
LDAPIdentityObjectTypeConfiguration toTypeConfig = getTypeConfiguration(ctx, toIdentity.getIdentityType());
// If relationship is not allowed return empty set
//TODO: use features description instead
if (!Arrays.asList(fromTypeConfig.getAllowedMembershipTypes()).contains(ldapToIO.getIdentityType().getName()))
{
return relationships;
}
LdapContext ldapContext = getLDAPContext(ctx);
try
{
Attributes attrs = ldapContext.getAttributes(ldapFromIO.getDn());
if (fromTypeConfig.getParentMembershipAttributeName() != null)
{
Attribute member = attrs.get(fromTypeConfig.getParentMembershipAttributeName());
if (member != null)
{
NamingEnumeration memberValues = member.getAll();
while (memberValues.hasMoreElements())
{
String memberRef = memberValues.nextElement().toString();
if ((fromTypeConfig.isParentMembershipAttributeDN() && memberRef.equals(ldapToIO.getDn())) ||
(!fromTypeConfig.isParentMembershipAttributeDN() && memberRef.equals(ldapToIO.getName())))
{
//TODO: impl lacks support for rel type
relationships.add(new LDAPIdentityObjectRelationshipImpl(MEMBERSHIP_TYPE, ldapFromIO, ldapToIO));
}
}
}
}
else if (toTypeConfig.getChildMembershipAttributeName() != null)
{
Attribute member = attrs.get(toTypeConfig.getChildMembershipAttributeName());
if (member != null)
{
NamingEnumeration memberValues = member.getAll();
while (memberValues.hasMoreElements())
{
String memberRef = memberValues.nextElement().toString();
if ((fromTypeConfig.isChildMembershipAttributeDN() && memberRef.equals(ldapFromIO.getDn())) ||
(!fromTypeConfig.isChildMembershipAttributeDN() && memberRef.equals(ldapFromIO.getName())))
{
//TODO: impl lacks support for rel type
relationships.add(new LDAPIdentityObjectRelationshipImpl(MEMBERSHIP_TYPE, ldapFromIO, ldapToIO));
}
}
}
}
}
catch (NamingException e)
{
throw new IdentityException("Failed to resolve relationship", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
return relationships;
}
public String createRelationshipName(IdentityStoreInvocationContext ctx, String name) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public String removeRelationshipName(IdentityStoreInvocationContext ctx, String name) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx, IdentityObjectSearchCriteria criteria) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx, IdentityObject identity, IdentityObjectSearchCriteria criteria) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public Set<String> getRelationshipNames(IdentityStoreInvocationContext ctx, IdentityObject identity) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public Map<String, String> getRelationshipNameProperties(IdentityStoreInvocationContext ctx, String name) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public void setRelationshipNameProperties(IdentityStoreInvocationContext ctx, String name, Map<String, String> properties) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public void removeRelationshipNameProperties(IdentityStoreInvocationContext ctx, String name, Set<String> properties) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Named relationships are not supported by this implementation of LDAP IdentityStore");
}
public Map<String, String> getRelationshipProperties(IdentityStoreInvocationContext ctx, IdentityObjectRelationship relationship) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Relationship properties are not supported by this implementation of LDAP IdentityStore");
}
public void setRelationshipProperties(IdentityStoreInvocationContext ctx, IdentityObjectRelationship relationship, Map<String, String> properties) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Relationship properties are not supported by this implementation of LDAP IdentityStore");
}
public void removeRelationshipProperties(IdentityStoreInvocationContext ctx, IdentityObjectRelationship relationship, Set<String> properties) throws IdentityException, OperationNotSupportedException
{
throw new OperationNotSupportedException("Relationship properties are not supported by this implementation of LDAP IdentityStore");
}
public boolean validateCredential(IdentityStoreInvocationContext ctx, IdentityObject identityObject, IdentityObjectCredential credential) throws IdentityException
{
if (credential == null)
{
throw new IllegalArgumentException();
}
LDAPIdentityObjectImpl ldapIO = getSafeLDAPIO(ctx, identityObject);
if (supportedFeatures.isCredentialSupported(ldapIO.getIdentityType(),credential.getType()))
{
String passwordString = null;
// Handle generic impl
if (credential.getValue() != null)
{
//TODO: support for empty password should be configurable
passwordString = credential.getValue().toString();
if (passwordString.length() == 0 && !getTypeConfiguration(ctx, identityObject.getIdentityType()).isAllowEmptyPassword())
{
return false;
}
}
else
{
if (!getTypeConfiguration(ctx, identityObject.getIdentityType()).isAllowEmptyPassword())
{
new IdentityException("Null password value");
}
passwordString = "";
}
LdapContext ldapContext = getLDAPContext(ctx);
try
{
Hashtable env = ldapContext.getEnvironment();
env.put(Context.SECURITY_PRINCIPAL, ldapIO.getDn());
env.put(Context.SECURITY_CREDENTIALS, passwordString);
InitialContext initialCtx = new InitialLdapContext(env, null);
if (initialCtx != null)
{
initialCtx.close();
return true;
}
}
catch (NamingException e)
{
//
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
return false;
}
else
{
throw new IdentityException("CredentialType not supported for a given IdentityObjectType");
}
}
public void updateCredential(IdentityStoreInvocationContext ctx, IdentityObject identityObject, IdentityObjectCredential credential) throws IdentityException
{
if (credential == null)
{
throw new IllegalArgumentException();
}
LDAPIdentityObjectImpl ldapIO = getSafeLDAPIO(ctx, identityObject);
if (supportedFeatures.isCredentialSupported(ldapIO.getIdentityType(),credential.getType()))
{
String passwordString = null;
// Handle generic impl
LDAPIdentityObjectTypeConfiguration typeConfig = getTypeConfiguration(ctx, identityObject.getIdentityType());
if (credential.getValue() != null)
{
//TODO: support for empty password should be configurable
passwordString = credential.getValue().toString();
if (passwordString.length() == 0 && !typeConfig.isAllowEmptyPassword())
{
new IdentityException("Empty password is not allowed by configuration");;
}
}
else
{
if (!typeConfig.isAllowEmptyPassword())
{
new IdentityException("Null password value");
}
passwordString = "";
}
if (typeConfig.getEnclosePasswordWith() != null)
{
String enc = typeConfig.getEnclosePasswordWith();
passwordString = enc + passwordString + enc;
}
byte[] encodedPassword = null;
if (typeConfig.getPasswordEncoding() != null)
{
try
{
encodedPassword = passwordString.getBytes(typeConfig.getPasswordEncoding());
}
catch (UnsupportedEncodingException e)
{
throw new IdentityException("Error while encoding password with configured setting: " + typeConfig.getPasswordEncoding(),
e);
}
}
String attributeName = getTypeConfiguration(ctx, ldapIO.getIdentityType()).getPasswordAttributeName();
if (attributeName == null)
{
throw new IdentityException("IdentityType doesn't have passwordAttributeName option set: "
+ ldapIO.getIdentityType().getName());
}
LdapContext ldapContext = getLDAPContext(ctx);
try
{
//TODO: maybe perform a schema check if this attribute is allowed for such entry
Attributes attrs = new BasicAttributes(true);
Attribute attr = new BasicAttribute(attributeName);
if (encodedPassword != null)
{
attr.add(encodedPassword);
}
else
{
attr.add(passwordString);
}
attrs.put(attr);
if(typeConfig.getUpdatePasswordAttributeValues().size() > 0)
{
Map<String, String[]> attributesToAdd = typeConfig.getUpdatePasswordAttributeValues();
for (Map.Entry<String, String[]> entry : attributesToAdd.entrySet())
{
Attribute additionalAttr = new BasicAttribute(entry.getKey());
for (String val : entry.getValue())
{
additionalAttr.add(val);
}
attrs.put(additionalAttr);
}
}
ldapContext.modifyAttributes(ldapIO.getDn(), DirContext.REPLACE_ATTRIBUTE, attrs);
}
catch (NamingException e)
{
throw new IdentityException("Cannot set identity password value.", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
}
else
{
throw new IdentityException("CredentialType not supported for a given IdentityObjectType");
}
}
// Attributes
public Set<String> getSupportedAttributeNames(IdentityStoreInvocationContext invocationContext, IdentityObjectType identityType) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".getSupportedAttributeNames with "
+ "identityType: " + identityType
);
}
checkIOType(identityType);
return getTypeConfiguration(invocationContext, identityType).getMappedAttributesNames();
}
public Map<String, IdentityObjectAttributeMetaData> getAttributesMetaData(IdentityStoreInvocationContext invocationContext, IdentityObjectType identityObjectType)
{
return attributesMetaData.get(identityObjectType.getName());
}
public IdentityObjectAttribute getAttribute(IdentityStoreInvocationContext invocationContext, IdentityObject identity, String name) throws IdentityException
{
//TODO: dummy temporary implementation
return getAttributes(invocationContext, identity).get(name);
}
public Map<String, IdentityObjectAttribute> getAttributes(IdentityStoreInvocationContext ctx, IdentityObject identity) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".getAttributes with "
+ "identity: " + identity
);
}
Map<String, IdentityObjectAttribute> attrsMap = new HashMap<String, IdentityObjectAttribute>();
LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(ctx, identity);
LdapContext ldapContext = getLDAPContext(ctx);
try
{
Set<String> mappedNames = getTypeConfiguration(ctx, identity.getIdentityType()).getMappedAttributesNames();
// as this is valid LDAPIdentityObjectImpl DN is obtained from the Id
String dn = ldapIdentity.getDn();
Attributes attrs = ldapContext.getAttributes(dn);
for (Iterator iterator = mappedNames.iterator(); iterator.hasNext();)
{
String name = (String)iterator.next();
String attrName = getTypeConfiguration(ctx, identity.getIdentityType()).getAttributeMapping(name);
Attribute attr = attrs.get(attrName);
if (attr != null)
{
IdentityObjectAttribute identityObjectAttribute = new SimpleAttribute(name);
NamingEnumeration values = attr.getAll();
while (values.hasMoreElements())
{
identityObjectAttribute.addValue(values.nextElement().toString());
}
attrsMap.put(name, identityObjectAttribute);
}
else
{
log.fine("No such attribute ('" + attrName + "') in entry: " + dn);
}
}
}
catch (NamingException e)
{
throw new IdentityException("Cannot get attributes value.", e);
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
return attrsMap;
}
public void updateAttributes(IdentityStoreInvocationContext ctx, IdentityObject identity, IdentityObjectAttribute[] attributes) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".updateAttributes with "
+ "identity: " + identity
+ "attributes: " + attributes
);
}
if (attributes == null)
{
throw new IllegalArgumentException("attributes is null");
}
LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(ctx, identity);
// as this is valid LDAPIdentityObjectImpl DN is obtained from the Id
String dn = ldapIdentity.getDn();
LdapContext ldapContext = getLDAPContext(ctx);
try
{
for (IdentityObjectAttribute attribute : attributes)
{
String name = attribute.getName();
String attributeName = getTypeConfiguration(ctx, identity.getIdentityType()).getAttributeMapping(name);
if (attributeName == null)
{
log.fine("Proper LDAP attribute mapping not found for such property name: " + name);
continue;
}
//TODO: maybe perform a schema check if this attribute is not required
Attributes attrs = new BasicAttributes(true);
Attribute attr = new BasicAttribute(attributeName);
Collection values = attribute.getValues();
Map<String, IdentityObjectAttributeMetaData> mdMap = attributesMetaData.get(identity.getIdentityType().getName());
if (mdMap != null)
{
IdentityObjectAttributeMetaData amd = mdMap.get(attributeName);
if (amd != null && !amd.isMultivalued() && values.size() > 1)
{
throw new IdentityException("Cannot assigned multiply values to single valued attribute: " + attributeName);
}
if (amd != null && amd.isReadonly())
{
throw new IdentityException("Cannot update readonly attribute: " + attributeName);
}
if (amd != null && amd.isUnique())
{
IdentityObject checkIdentity = findIdentityObjectByUniqueAttribute(ctx,
identity.getIdentityType(),
attribute);
if (checkIdentity != null && !checkIdentity.getName().equals(identity.getName()))
{
throw new IdentityException("Unique attribute '" + attribute.getName() + " value already set for identityObject: " +
checkIdentity);
}
}
}
if (values != null)
{
for (Object value : values)
{
attr.add(value);
}
attrs.put(attr);
try
{
ldapContext.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, attrs);
}
catch (NamingException e)
{
throw new IdentityException("Cannot add attribute", e);
}
}
}
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
}
public void addAttributes(IdentityStoreInvocationContext ctx, IdentityObject identity, IdentityObjectAttribute[] attributes) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".addAttributes with "
+ "identity: " + identity
+ "attributes: " + attributes
);
}
if (attributes == null)
{
throw new IllegalArgumentException("attributes is null");
}
LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(ctx, identity);
// as this is valid LDAPIdentityObjectImpl DN is obtained from the Id
String dn = ldapIdentity.getDn();
LdapContext ldapContext = getLDAPContext(ctx);
try
{
for (IdentityObjectAttribute attribute : attributes)
{
String name = attribute.getName();
String attributeName = getTypeConfiguration(ctx, identity.getIdentityType()).getAttributeMapping(name);
if (attributeName == null)
{
log.fine("Proper LDAP attribute mapping not found for such property name: " + name);
continue;
}
//TODO: maybe perform a schema check if this attribute is not required
Attributes attrs = new BasicAttributes(true);
Attribute attr = new BasicAttribute(attributeName);
Collection values = attribute.getValues();
Map<String, IdentityObjectAttributeMetaData> mdMap = attributesMetaData.get(identity.getIdentityType().getName());
if (mdMap != null)
{
IdentityObjectAttributeMetaData amd = mdMap.get(attribute.getName());
if (amd != null && !amd.isMultivalued() && values.size() > 1)
{
throw new IdentityException("Cannot assigned multiply values to single valued attribute: " + attributeName);
}
if (amd != null && amd.isReadonly())
{
throw new IdentityException("Cannot update readonly attribute: " + attributeName);
}
if (amd != null && amd.isUnique())
{
IdentityObject checkIdentity = findIdentityObjectByUniqueAttribute(ctx,
identity.getIdentityType(),
attribute);
if (checkIdentity != null && !checkIdentity.getName().equals(identity.getName()))
{
throw new IdentityException("Unique attribute '" + attribute.getName() + " value already set for identityObject: " +
checkIdentity);
}
}
}
if (values != null)
{
for (Object value : values)
{
attr.add(value);
}
attrs.put(attr);
try
{
ldapContext.modifyAttributes(dn, DirContext.ADD_ATTRIBUTE, attrs);
}
catch (NamingException e)
{
throw new IdentityException("Cannot add attribute", e);
}
}
}
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
}
public void removeAttributes(IdentityStoreInvocationContext ctx, IdentityObject identity, String[] attributeNames) throws IdentityException
{
if (log.isLoggable(Level.FINER))
{
log.finer(toString() + ".removeAttributes with "
+ "identity: " + identity
+ "attributeNames: " + attributeNames
);
}
if (attributeNames == null)
{
throw new IllegalArgumentException("attributes is null");
}
LDAPIdentityObjectImpl ldapIdentity = getSafeLDAPIO(ctx, identity);
// as this is valid LDAPIdentityObjectImpl DN is obtained from the Id
String dn = ldapIdentity.getDn();
LdapContext ldapContext = getLDAPContext(ctx);
try
{
for (String name : attributeNames)
{
String attributeName = getTypeConfiguration(ctx, identity.getIdentityType()).getAttributeMapping(name);
if (attributeName == null)
{
log.fine("Proper LDAP attribute mapping not found for such property name: " + name);
continue;
}
Map<String, IdentityObjectAttributeMetaData> mdMap = attributesMetaData.get(identity.getIdentityType().getName());
if (mdMap != null)
{
//TODO: maybe perform a schema check if this attribute is not required on the LDAP level
IdentityObjectAttributeMetaData amd = mdMap.get(name);
if (amd != null && amd.isRequired())
{
throw new IdentityException("Cannot remove required attribute: " + name);
}
}
Attributes attrs = new BasicAttributes(true);
Attribute attr = new BasicAttribute(attributeName);
attrs.put(attr);
try
{
ldapContext.modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, attrs);
}
catch (NamingException e)
{
throw new IdentityException("Cannot remove attribute", e);
}
}
}
finally
{
try
{
ldapContext.close();
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
}
public IdentityObject findIdentityObjectByUniqueAttribute(IdentityStoreInvocationContext invocationCtx, IdentityObjectType type, IdentityObjectAttribute attribute) throws IdentityException
{
String nameFilter = "*";
LdapContext ctx = getLDAPContext(invocationCtx);
checkIOType(type);
LinkedList<IdentityObject> objects = new LinkedList<IdentityObject>();
LDAPIdentityObjectTypeConfiguration typeConfiguration = getTypeConfiguration(invocationCtx, type);
String attributeName = getTypeConfiguration(invocationCtx, type).getAttributeMapping(attribute.getName());
try
{
Control[] requestControls = null;
StringBuilder af = new StringBuilder();
// Filter by attribute values
af.append("(")
.append(attributeName)
.append("=")
.append(attribute.getValue())
.append(")");
String filter = getTypeConfiguration(invocationCtx, type).getEntrySearchFilter();
List<SearchResult> sr = null;
String[] entryCtxs = getTypeConfiguration(invocationCtx, type).getCtxDNs();
if (filter != null && filter.length() > 0)
{
Object[] filterArgs = {nameFilter};
sr = searchIdentityObjects(invocationCtx,
entryCtxs,
"(&(" + filter + ")" + af.toString() + ")",
filterArgs,
new String[]{typeConfiguration.getIdAttributeName()},
requestControls);
}
else
{
filter = "(".concat(typeConfiguration.getIdAttributeName()).concat("=").concat(nameFilter).concat(")");
sr = searchIdentityObjects(invocationCtx,
entryCtxs,
"(&(" + filter + ")" + af.toString() + ")",
null,
new String[]{typeConfiguration.getIdAttributeName()},
requestControls);
}
for (SearchResult res : sr)
{
ctx = (LdapContext)res.getObject();
String dn = ctx.getNameInNamespace();
objects.add(createIdentityObjectInstance(invocationCtx, type, res.getAttributes(), dn));
}
ctx.close();
}
catch (NoSuchElementException e)
{
//log.debug("No identity object found with name: " + name, e);
}
catch (Exception e)
{
throw new IdentityException("IdentityObject search failed.", e);
}
finally
{
try
{
if (ctx != null)
{
ctx.close();
}
}
catch (NamingException e)
{
throw new IdentityException("Failed to close LDAP connection", e);
}
}
if (objects.size() == 0)
{
return null;
}
if (objects.size() > 1)
{
throw new IdentityException("Illegal state - more than one IdentityObject of same type share same unique attribute value");
}
return objects.get(0);
}
//Internal
public LDAPIdentityObjectImpl createIdentityObjectInstance(IdentityStoreInvocationContext ctx, IdentityObjectType type, Attributes attrs, String dn) throws IdentityException
{
LDAPIdentityObjectImpl ldapio = null;
try
{
String idAttrName = getTypeConfiguration(ctx, type).getIdAttributeName();
Attribute ida = attrs.get(idAttrName);
if (ida == null)
{
throw new IdentityException("LDAP entry doesn't contain proper attribute:" + idAttrName);
}
//make DN as user ID
ldapio = new LDAPIdentityObjectImpl(dn, ida.get().toString(), type);
}
catch (Exception e)
{
throw new IdentityException("Couldn't create LDAPIdentityObjectImpl object from ldap entry (SearchResult)", e);
}
return ldapio;
}
public List<SearchResult> searchIdentityObjects(IdentityStoreInvocationContext ctx,
String[] entryCtxs,
String filter,
Object[] filterArgs,
String[] returningAttributes,
Control[] requestControls) throws NamingException, IdentityException
{
LdapContext ldapContext = getLDAPContext(ctx);
if (ldapContext != null)
{
ldapContext.setRequestControls(requestControls);
}
NamingEnumeration results = null;
try
{
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
searchControls.setReturningObjFlag(true);
searchControls.setTimeLimit(getConfiguration(ctx).getSearchTimeLimit());
if (returningAttributes != null)
{
searchControls.setReturningAttributes(returningAttributes);
}
if (entryCtxs.length == 1)
{
if (filterArgs == null)
{
results = ldapContext.search(entryCtxs[0], filter, searchControls);
}
else
{
results = ldapContext.search(entryCtxs[0], filter, filterArgs, searchControls);
}
return Tools.toList(results);
}
else
{
List<SearchResult> merged = new LinkedList();
for (String entryCtx : entryCtxs)
{
if (filterArgs == null)
{
results = ldapContext.search(entryCtx, filter, searchControls);
}
else
{
results = ldapContext.search(entryCtx, filter, filterArgs, searchControls);
}
merged.addAll(Tools.toList(results));
results.close();
}
return merged;
}
}
finally
{
if (results != null)
{
results.close();
}
ldapContext.close();
}
}
// HELPER
private LDAPIdentityObjectImpl getSafeLDAPIO(IdentityStoreInvocationContext ctx, IdentityObject io) throws IdentityException
{
if (io == null)
{
throw new IllegalArgumentException("IdentityObject is null");
}
if (io instanceof LDAPIdentityObjectImpl)
{
return (LDAPIdentityObjectImpl)io;
}
else
{
try
{
return (LDAPIdentityObjectImpl)findIdentityObject(ctx, io.getName(), io.getIdentityType());
}
catch (IdentityException e)
{
throw new IdentityException("Provided IdentityObject is not present in the store. Cannot operate on not stored objects.", e);
}
}
}
private void checkIOType(IdentityObjectType iot) throws IdentityException
{
if (iot == null)
{
throw new IllegalArgumentException("IdentityObjectType is null");
}
if (!getSupportedFeatures().isIdentityObjectTypeSupported(iot))
{
throw new IdentityException("IdentityType not supported by this IdentityStore implementation: " + iot);
}
}
private LdapContext getLDAPContext(IdentityStoreInvocationContext ctx) throws IdentityException
{
LdapContext ldapContext = null;
try
{
ldapContext = (LdapContext)ctx.getIdentityStoreSession().getSessionContext();
}
catch (Exception e)
{
throw new IdentityException("Could not obtain LDAP connection: ", e);
}
if (ldapContext == null)
{
throw new IdentityException("IllegalState: - Could not obtain LDAP connection");
}
return ldapContext;
}
private LDAPIdentityStoreConfiguration getConfiguration(IdentityStoreInvocationContext ctx) throws IdentityException
{
return configuration;
}
private LDAPIdentityObjectTypeConfiguration getTypeConfiguration(IdentityStoreInvocationContext ctx, IdentityObjectType type) throws IdentityException
{
return getConfiguration(ctx).getTypeConfiguration(type.getName());
}
public String toString()
{
return this.getClass().getName() + "[" + getId() +"]";
}
private void sortByName(List<IdentityObject> objects, final boolean ascending)
{
Collections.sort(objects, new Comparator<IdentityObject>(){
public int compare(IdentityObject o1, IdentityObject o2)
{
if (ascending)
{
return o1.getName().compareTo(o2.getName());
}
else
{
return o2.getName().compareTo(o1.getName());
}
}
});
}
//TODO: dummy and inefficient temporary workaround. Need to be implemented with ldap request control
private List<IdentityObject> cutPageFromResults(List<IdentityObject> objects, IdentityObjectSearchCriteria criteria)
{
List<IdentityObject> results = new LinkedList<IdentityObject>();
for (int i = criteria.getFirstResult(); i < criteria.getFirstResult() + criteria.getMaxResults(); i++)
{
if (i < objects.size())
{
results.add(objects.get(i));
}
}
return results;
}
}