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