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