001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2016 ForgeRock AS. 016 */ 017package org.opends.server.extensions; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Iterator; 022import java.util.LinkedHashSet; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Set; 026 027import org.forgerock.i18n.LocalizableMessage; 028import org.forgerock.opendj.config.server.ConfigChangeResult; 029import org.forgerock.opendj.config.server.ConfigException; 030import org.forgerock.opendj.ldap.ByteString; 031import org.forgerock.opendj.ldap.DN; 032import org.forgerock.opendj.ldap.ResultCode; 033import org.forgerock.opendj.ldap.SearchScope; 034import org.forgerock.opendj.config.server.ConfigurationChangeListener; 035import org.forgerock.opendj.server.config.server.ExactMatchIdentityMapperCfg; 036import org.forgerock.opendj.server.config.server.IdentityMapperCfg; 037import org.opends.server.api.Backend; 038import org.opends.server.api.IdentityMapper; 039import org.opends.server.core.DirectoryServer; 040import org.opends.server.protocols.internal.InternalClientConnection; 041import org.opends.server.protocols.internal.InternalSearchOperation; 042import org.opends.server.protocols.internal.SearchRequest; 043import static org.opends.server.protocols.internal.Requests.*; 044import org.forgerock.opendj.ldap.schema.AttributeType; 045import org.opends.server.types.*; 046 047import static org.opends.messages.ExtensionMessages.*; 048import static org.opends.server.protocols.internal.InternalClientConnection.*; 049import static org.opends.server.util.CollectionUtils.*; 050 051/** 052 * This class provides an implementation of a Directory Server identity mapper 053 * that looks for the exact value provided as the ID string to appear in an 054 * attribute of a user's entry. This mapper may be configured to look in one or 055 * more attributes using zero or more search bases. In order for the mapping to 056 * be established properly, exactly one entry must have an attribute that 057 * exactly matches (according to the equality matching rule associated with that 058 * attribute) the ID value. 059 */ 060public class ExactMatchIdentityMapper 061 extends IdentityMapper<ExactMatchIdentityMapperCfg> 062 implements ConfigurationChangeListener< 063 ExactMatchIdentityMapperCfg> 064{ 065 /** The set of attribute types to use when performing lookups. */ 066 private AttributeType[] attributeTypes; 067 068 /** The DN of the configuration entry for this identity mapper. */ 069 private DN configEntryDN; 070 071 /** The current configuration for this identity mapper. */ 072 private ExactMatchIdentityMapperCfg currentConfig; 073 074 /** The set of attributes to return in search result entries. */ 075 private LinkedHashSet<String> requestedAttributes; 076 077 /** 078 * Creates a new instance of this exact match identity mapper. All 079 * initialization should be performed in the {@code initializeIdentityMapper} 080 * method. 081 */ 082 public ExactMatchIdentityMapper() 083 { 084 super(); 085 086 // Don't do any initialization here. 087 } 088 089 @Override 090 public void initializeIdentityMapper( 091 ExactMatchIdentityMapperCfg configuration) 092 throws ConfigException, InitializationException 093 { 094 configuration.addExactMatchChangeListener(this); 095 096 currentConfig = configuration; 097 configEntryDN = currentConfig.dn(); 098 099 // Get the attribute types to use for the searches. Ensure that they are 100 // all indexed for equality. 101 attributeTypes = 102 currentConfig.getMatchAttribute().toArray(new AttributeType[0]); 103 104 Set<DN> cfgBaseDNs = configuration.getMatchBaseDN(); 105 if (cfgBaseDNs == null || cfgBaseDNs.isEmpty()) 106 { 107 cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 108 } 109 110 for (AttributeType t : attributeTypes) 111 { 112 for (DN baseDN : cfgBaseDNs) 113 { 114 Backend b = DirectoryServer.getBackend(baseDN); 115 if (b != null && ! b.isIndexed(t, IndexType.EQUALITY)) 116 { 117 throw new ConfigException(ERR_EXACTMAP_ATTR_UNINDEXED.get( 118 configuration.dn(), t.getNameOrOID(), b.getBackendID())); 119 } 120 } 121 } 122 123 // Create the attribute list to include in search requests. We want to 124 // include all user and operational attributes. 125 requestedAttributes = newLinkedHashSet("*", "+"); 126 } 127 128 @Override 129 public void finalizeIdentityMapper() 130 { 131 currentConfig.removeExactMatchChangeListener(this); 132 } 133 134 /** 135 * Retrieves the user entry that was mapped to the provided identification 136 * string. 137 * 138 * @param id The identification string that is to be mapped to a user. 139 * 140 * @return The user entry that was mapped to the provided identification, or 141 * <CODE>null</CODE> if no users were found that could be mapped to 142 * the provided ID. 143 * 144 * @throws DirectoryException If a problem occurs while attempting to map 145 * the given ID to a user entry, or if there are 146 * multiple user entries that could map to the 147 * provided ID. 148 */ 149 @Override 150 public Entry getEntryForID(String id) 151 throws DirectoryException 152 { 153 ExactMatchIdentityMapperCfg config = currentConfig; 154 AttributeType[] attributeTypes = this.attributeTypes; 155 156 // Construct the search filter to use to make the determination. 157 SearchFilter filter; 158 if (attributeTypes.length == 1) 159 { 160 ByteString value = ByteString.valueOfUtf8(id); 161 filter = SearchFilter.createEqualityFilter(attributeTypes[0], value); 162 } 163 else 164 { 165 ArrayList<SearchFilter> filterComps = new ArrayList<>(attributeTypes.length); 166 for (AttributeType t : attributeTypes) 167 { 168 ByteString value = ByteString.valueOfUtf8(id); 169 filterComps.add(SearchFilter.createEqualityFilter(t, value)); 170 } 171 172 filter = SearchFilter.createORFilter(filterComps); 173 } 174 175 // Iterate through the set of search bases and process an internal search 176 // to find any matching entries. Since we'll only allow a single match, 177 // then use size and time limits to constrain costly searches resulting from 178 // non-unique or inefficient criteria. 179 Collection<DN> baseDNs = config.getMatchBaseDN(); 180 if (baseDNs == null || baseDNs.isEmpty()) 181 { 182 baseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 183 } 184 185 SearchResultEntry matchingEntry = null; 186 InternalClientConnection conn = getRootConnection(); 187 for (DN baseDN : baseDNs) 188 { 189 final SearchRequest request = newSearchRequest(baseDN, SearchScope.WHOLE_SUBTREE, filter) 190 .setSizeLimit(1) 191 .setTimeLimit(10) 192 .addAttribute(requestedAttributes); 193 InternalSearchOperation internalSearch = conn.processSearch(request); 194 195 switch (internalSearch.getResultCode().asEnum()) 196 { 197 case SUCCESS: 198 // This is fine. No action needed. 199 break; 200 201 case NO_SUCH_OBJECT: 202 // The search base doesn't exist. Not an ideal situation, but we'll 203 // ignore it. 204 break; 205 206 case SIZE_LIMIT_EXCEEDED: 207 // Multiple entries matched the filter. This is not acceptable. 208 LocalizableMessage message = ERR_EXACTMAP_MULTIPLE_MATCHING_ENTRIES.get(id); 209 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 210 211 case TIME_LIMIT_EXCEEDED: 212 case ADMIN_LIMIT_EXCEEDED: 213 // The search criteria was too inefficient. 214 message = ERR_EXACTMAP_INEFFICIENT_SEARCH. 215 get(id, internalSearch.getErrorMessage()); 216 throw new DirectoryException(internalSearch.getResultCode(), message); 217 218 default: 219 // Just pass on the failure that was returned for this search. 220 message = ERR_EXACTMAP_SEARCH_FAILED. 221 get(id, internalSearch.getErrorMessage()); 222 throw new DirectoryException(internalSearch.getResultCode(), message); 223 } 224 225 LinkedList<SearchResultEntry> searchEntries = internalSearch.getSearchEntries(); 226 if (searchEntries != null && ! searchEntries.isEmpty()) 227 { 228 if (matchingEntry == null) 229 { 230 Iterator<SearchResultEntry> iterator = searchEntries.iterator(); 231 matchingEntry = iterator.next(); 232 if (iterator.hasNext()) 233 { 234 LocalizableMessage message = ERR_EXACTMAP_MULTIPLE_MATCHING_ENTRIES.get(id); 235 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 236 } 237 } 238 else 239 { 240 LocalizableMessage message = ERR_EXACTMAP_MULTIPLE_MATCHING_ENTRIES.get(id); 241 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 242 } 243 } 244 } 245 246 return matchingEntry; 247 } 248 249 @Override 250 public boolean isConfigurationAcceptable(IdentityMapperCfg configuration, 251 List<LocalizableMessage> unacceptableReasons) 252 { 253 ExactMatchIdentityMapperCfg config = 254 (ExactMatchIdentityMapperCfg) configuration; 255 return isConfigurationChangeAcceptable(config, unacceptableReasons); 256 } 257 258 @Override 259 public boolean isConfigurationChangeAcceptable( 260 ExactMatchIdentityMapperCfg configuration, 261 List<LocalizableMessage> unacceptableReasons) 262 { 263 boolean configAcceptable = true; 264 265 // Make sure that all of the configured attributes are indexed for equality 266 // in all appropriate backends. 267 Set<DN> cfgBaseDNs = configuration.getMatchBaseDN(); 268 if (cfgBaseDNs == null || cfgBaseDNs.isEmpty()) 269 { 270 cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 271 } 272 273 for (AttributeType t : configuration.getMatchAttribute()) 274 { 275 for (DN baseDN : cfgBaseDNs) 276 { 277 Backend b = DirectoryServer.getBackend(baseDN); 278 if (b != null && ! b.isIndexed(t, IndexType.EQUALITY)) 279 { 280 unacceptableReasons.add(ERR_EXACTMAP_ATTR_UNINDEXED.get( 281 configuration.dn(), t.getNameOrOID(), b.getBackendID())); 282 configAcceptable = false; 283 } 284 } 285 } 286 287 return configAcceptable; 288 } 289 290 @Override 291 public ConfigChangeResult applyConfigurationChange( 292 ExactMatchIdentityMapperCfg configuration) 293 { 294 final ConfigChangeResult ccr = new ConfigChangeResult(); 295 296 attributeTypes = 297 configuration.getMatchAttribute().toArray(new AttributeType[0]); 298 currentConfig = configuration; 299 300 return ccr; 301 } 302}