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.opends.server.admin.server.ConfigurationChangeListener; 035import org.opends.server.admin.std.server.ExactMatchIdentityMapperCfg; 036import org.opends.server.admin.std.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 079 /** 080 * Creates a new instance of this exact match identity mapper. All 081 * initialization should be performed in the {@code initializeIdentityMapper} 082 * method. 083 */ 084 public ExactMatchIdentityMapper() 085 { 086 super(); 087 088 // Don't do any initialization here. 089 } 090 091 092 093 /** {@inheritDoc} */ 094 @Override 095 public void initializeIdentityMapper( 096 ExactMatchIdentityMapperCfg configuration) 097 throws ConfigException, InitializationException 098 { 099 configuration.addExactMatchChangeListener(this); 100 101 currentConfig = configuration; 102 configEntryDN = currentConfig.dn(); 103 104 105 // Get the attribute types to use for the searches. Ensure that they are 106 // all indexed for equality. 107 attributeTypes = 108 currentConfig.getMatchAttribute().toArray(new AttributeType[0]); 109 110 Set<DN> cfgBaseDNs = configuration.getMatchBaseDN(); 111 if (cfgBaseDNs == null || cfgBaseDNs.isEmpty()) 112 { 113 cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 114 } 115 116 for (AttributeType t : attributeTypes) 117 { 118 for (DN baseDN : cfgBaseDNs) 119 { 120 Backend b = DirectoryServer.getBackend(baseDN); 121 if (b != null && ! b.isIndexed(t, IndexType.EQUALITY)) 122 { 123 throw new ConfigException(ERR_EXACTMAP_ATTR_UNINDEXED.get( 124 configuration.dn(), t.getNameOrOID(), b.getBackendID())); 125 } 126 } 127 } 128 129 130 // Create the attribute list to include in search requests. We want to 131 // include all user and operational attributes. 132 requestedAttributes = newLinkedHashSet("*", "+"); 133 } 134 135 136 137 /** 138 * Performs any finalization that may be necessary for this identity mapper. 139 */ 140 @Override 141 public void finalizeIdentityMapper() 142 { 143 currentConfig.removeExactMatchChangeListener(this); 144 } 145 146 147 148 /** 149 * Retrieves the user entry that was mapped to the provided identification 150 * string. 151 * 152 * @param id The identification string that is to be mapped to a user. 153 * 154 * @return The user entry that was mapped to the provided identification, or 155 * <CODE>null</CODE> if no users were found that could be mapped to 156 * the provided ID. 157 * 158 * @throws DirectoryException If a problem occurs while attempting to map 159 * the given ID to a user entry, or if there are 160 * multiple user entries that could map to the 161 * provided ID. 162 */ 163 @Override 164 public Entry getEntryForID(String id) 165 throws DirectoryException 166 { 167 ExactMatchIdentityMapperCfg config = currentConfig; 168 AttributeType[] attributeTypes = this.attributeTypes; 169 170 171 // Construct the search filter to use to make the determination. 172 SearchFilter filter; 173 if (attributeTypes.length == 1) 174 { 175 ByteString value = ByteString.valueOfUtf8(id); 176 filter = SearchFilter.createEqualityFilter(attributeTypes[0], value); 177 } 178 else 179 { 180 ArrayList<SearchFilter> filterComps = new ArrayList<>(attributeTypes.length); 181 for (AttributeType t : attributeTypes) 182 { 183 ByteString value = ByteString.valueOfUtf8(id); 184 filterComps.add(SearchFilter.createEqualityFilter(t, value)); 185 } 186 187 filter = SearchFilter.createORFilter(filterComps); 188 } 189 190 191 // Iterate through the set of search bases and process an internal search 192 // to find any matching entries. Since we'll only allow a single match, 193 // then use size and time limits to constrain costly searches resulting from 194 // non-unique or inefficient criteria. 195 Collection<DN> baseDNs = config.getMatchBaseDN(); 196 if (baseDNs == null || baseDNs.isEmpty()) 197 { 198 baseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 199 } 200 201 SearchResultEntry matchingEntry = null; 202 InternalClientConnection conn = getRootConnection(); 203 for (DN baseDN : baseDNs) 204 { 205 final SearchRequest request = newSearchRequest(baseDN, SearchScope.WHOLE_SUBTREE, filter) 206 .setSizeLimit(1) 207 .setTimeLimit(10) 208 .addAttribute(requestedAttributes); 209 InternalSearchOperation internalSearch = conn.processSearch(request); 210 211 switch (internalSearch.getResultCode().asEnum()) 212 { 213 case SUCCESS: 214 // This is fine. No action needed. 215 break; 216 217 case NO_SUCH_OBJECT: 218 // The search base doesn't exist. Not an ideal situation, but we'll 219 // ignore it. 220 break; 221 222 case SIZE_LIMIT_EXCEEDED: 223 // Multiple entries matched the filter. This is not acceptable. 224 LocalizableMessage message = ERR_EXACTMAP_MULTIPLE_MATCHING_ENTRIES.get(id); 225 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 226 227 case TIME_LIMIT_EXCEEDED: 228 case ADMIN_LIMIT_EXCEEDED: 229 // The search criteria was too inefficient. 230 message = ERR_EXACTMAP_INEFFICIENT_SEARCH. 231 get(id, internalSearch.getErrorMessage()); 232 throw new DirectoryException(internalSearch.getResultCode(), message); 233 234 default: 235 // Just pass on the failure that was returned for this search. 236 message = ERR_EXACTMAP_SEARCH_FAILED. 237 get(id, internalSearch.getErrorMessage()); 238 throw new DirectoryException(internalSearch.getResultCode(), message); 239 } 240 241 LinkedList<SearchResultEntry> searchEntries = internalSearch.getSearchEntries(); 242 if (searchEntries != null && ! searchEntries.isEmpty()) 243 { 244 if (matchingEntry == null) 245 { 246 Iterator<SearchResultEntry> iterator = searchEntries.iterator(); 247 matchingEntry = iterator.next(); 248 if (iterator.hasNext()) 249 { 250 LocalizableMessage message = ERR_EXACTMAP_MULTIPLE_MATCHING_ENTRIES.get(id); 251 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 252 } 253 } 254 else 255 { 256 LocalizableMessage message = ERR_EXACTMAP_MULTIPLE_MATCHING_ENTRIES.get(id); 257 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 258 } 259 } 260 } 261 262 return matchingEntry; 263 } 264 265 266 267 /** {@inheritDoc} */ 268 @Override 269 public boolean isConfigurationAcceptable(IdentityMapperCfg configuration, 270 List<LocalizableMessage> unacceptableReasons) 271 { 272 ExactMatchIdentityMapperCfg config = 273 (ExactMatchIdentityMapperCfg) configuration; 274 return isConfigurationChangeAcceptable(config, unacceptableReasons); 275 } 276 277 278 279 /** {@inheritDoc} */ 280 @Override 281 public boolean isConfigurationChangeAcceptable( 282 ExactMatchIdentityMapperCfg configuration, 283 List<LocalizableMessage> unacceptableReasons) 284 { 285 boolean configAcceptable = true; 286 287 // Make sure that all of the configured attributes are indexed for equality 288 // in all appropriate backends. 289 Set<DN> cfgBaseDNs = configuration.getMatchBaseDN(); 290 if (cfgBaseDNs == null || cfgBaseDNs.isEmpty()) 291 { 292 cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); 293 } 294 295 for (AttributeType t : configuration.getMatchAttribute()) 296 { 297 for (DN baseDN : cfgBaseDNs) 298 { 299 Backend b = DirectoryServer.getBackend(baseDN); 300 if (b != null && ! b.isIndexed(t, IndexType.EQUALITY)) 301 { 302 unacceptableReasons.add(ERR_EXACTMAP_ATTR_UNINDEXED.get( 303 configuration.dn(), t.getNameOrOID(), b.getBackendID())); 304 configAcceptable = false; 305 } 306 } 307 } 308 309 return configAcceptable; 310 } 311 312 313 314 /** {@inheritDoc} */ 315 @Override 316 public ConfigChangeResult applyConfigurationChange( 317 ExactMatchIdentityMapperCfg configuration) 318 { 319 final ConfigChangeResult ccr = new ConfigChangeResult(); 320 321 attributeTypes = 322 configuration.getMatchAttribute().toArray(new AttributeType[0]); 323 currentConfig = configuration; 324 325 return ccr; 326 } 327}