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