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