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 2008-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS
026 */
027package org.opends.server.extensions;
028
029import java.util.*;
030
031import org.forgerock.i18n.slf4j.LocalizedLogger;
032import org.forgerock.opendj.ldap.ByteString;
033import org.forgerock.opendj.ldap.ConditionResult;
034import org.forgerock.opendj.ldap.DecodeException;
035import org.forgerock.opendj.ldap.SearchScope;
036import org.opends.server.admin.std.server.EntryDNVirtualAttributeCfg;
037import org.forgerock.opendj.ldap.schema.MatchingRule;
038import org.opends.server.api.VirtualAttributeProvider;
039import org.opends.server.core.DirectoryServer;
040import org.opends.server.core.SearchOperation;
041import org.opends.server.types.*;
042
043import static org.opends.server.util.ServerConstants.*;
044
045/**
046 * This class implements a virtual attribute provider that is meant to serve the
047 * entryDN operational attribute as described in draft-zeilenga-ldap-entrydn.
048 */
049public class EntryDNVirtualAttributeProvider
050       extends VirtualAttributeProvider<EntryDNVirtualAttributeCfg>
051{
052  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
053
054  /**
055   * Creates a new instance of this entryDN virtual attribute provider.
056   */
057  public EntryDNVirtualAttributeProvider()
058  {
059    super();
060
061    // All initialization should be performed in the
062    // initializeVirtualAttributeProvider method.
063  }
064
065  /** {@inheritDoc} */
066  @Override
067  public boolean isMultiValued()
068  {
069    return false;
070  }
071
072  /** {@inheritDoc} */
073  @Override
074  public Attribute getValues(Entry entry, VirtualAttributeRule rule)
075  {
076    String dnString = entry.getName().toString();
077    return Attributes.create(rule.getAttributeType(), dnString);
078  }
079
080  /** {@inheritDoc} */
081  @Override
082  public boolean hasValue(Entry entry, VirtualAttributeRule rule)
083  {
084    // This virtual attribute provider will always generate a value.
085    return true;
086  }
087
088  /** {@inheritDoc} */
089  @Override
090  public boolean hasValue(Entry entry, VirtualAttributeRule rule, ByteString value)
091  {
092    try
093    {
094      MatchingRule eqRule = rule.getAttributeType().getEqualityMatchingRule();
095      ByteString dn = ByteString.valueOfUtf8(entry.getName().toString());
096      ByteString normalizedDN = eqRule.normalizeAttributeValue(dn);
097      ByteString normalizedValue = eqRule.normalizeAttributeValue(value);
098      return normalizedDN.equals(normalizedValue);
099    }
100    catch (DecodeException e)
101    {
102      logger.traceException(e);
103      return false;
104    }
105  }
106
107  /** {@inheritDoc} */
108  @Override
109  public ConditionResult matchesSubstring(Entry entry,
110                                          VirtualAttributeRule rule,
111                                          ByteString subInitial,
112                                          List<ByteString> subAny,
113                                          ByteString subFinal)
114  {
115    // DNs cannot be used in substring matching.
116    return ConditionResult.UNDEFINED;
117  }
118
119  /** {@inheritDoc} */
120  @Override
121  public ConditionResult greaterThanOrEqualTo(Entry entry,
122                              VirtualAttributeRule rule,
123                              ByteString value)
124  {
125    // DNs cannot be used in ordering matching.
126    return ConditionResult.UNDEFINED;
127  }
128
129  /** {@inheritDoc} */
130  @Override
131  public ConditionResult lessThanOrEqualTo(Entry entry,
132                              VirtualAttributeRule rule,
133                              ByteString value)
134  {
135    // DNs cannot be used in ordering matching.
136    return ConditionResult.UNDEFINED;
137  }
138
139  /** {@inheritDoc} */
140  @Override
141  public ConditionResult approximatelyEqualTo(Entry entry,
142                              VirtualAttributeRule rule,
143                              ByteString value)
144  {
145    // DNs cannot be used in approximate matching.
146    return ConditionResult.UNDEFINED;
147  }
148
149
150
151  /**
152   * {@inheritDoc}.  This virtual attribute will support search operations only
153   * if one of the following is true about the search filter:
154   * <UL>
155   *   <LI>It is an equality filter targeting the associated attribute
156   *       type.</LI>
157   *   <LI>It is an AND filter in which at least one of the components is an
158   *       equality filter targeting the associated attribute type.</LI>
159   *   <LI>It is an OR filter in which all of the components are equality
160   *       filters targeting the associated attribute type.</LI>
161   * </UL>
162   * This virtual attribute also can be optimized as pre-indexed.
163   */
164  @Override
165  public boolean isSearchable(VirtualAttributeRule rule,
166                              SearchOperation searchOperation,
167                              boolean isPreIndexed)
168  {
169    return isSearchable(rule.getAttributeType(), searchOperation.getFilter(), 0);
170  }
171
172
173
174
175  /**
176   * Indicates whether the provided search filter is one that may be used with
177   * this virtual attribute provider, optionally operating in a recursive manner
178   * to make the determination.
179   *
180   * @param  attributeType  The attribute type used to hold the entryDN value.
181   * @param  searchFilter   The search filter for which to make the
182   *                        determination.
183   * @param  depth          The current recursion depth for this processing.
184   *
185   * @return  {@code true} if the provided filter may be used with this virtual
186   *          attribute provider, or {@code false} if not.
187   */
188  private boolean isSearchable(AttributeType attributeType, SearchFilter filter,
189                               int depth)
190  {
191    switch (filter.getFilterType())
192    {
193      case AND:
194        if (depth >= MAX_NESTED_FILTER_DEPTH)
195        {
196          return false;
197        }
198
199        for (SearchFilter f : filter.getFilterComponents())
200        {
201          if (isSearchable(attributeType, f, depth+1))
202          {
203            return true;
204          }
205        }
206        return false;
207
208      case OR:
209        if (depth >= MAX_NESTED_FILTER_DEPTH)
210        {
211          return false;
212        }
213
214        for (SearchFilter f : filter.getFilterComponents())
215        {
216          if (! isSearchable(attributeType, f, depth+1))
217          {
218            return false;
219          }
220        }
221        return true;
222
223      case EQUALITY:
224        return filter.getAttributeType().equals(attributeType);
225
226      default:
227        return false;
228    }
229  }
230
231  /** {@inheritDoc} */
232  @Override
233  public void processSearch(VirtualAttributeRule rule,
234                            SearchOperation searchOperation)
235  {
236    SearchFilter      filter = searchOperation.getFilter();
237    LinkedHashSet<DN> dnSet  = new LinkedHashSet<>();
238    extractDNs(rule.getAttributeType(), filter, dnSet);
239
240    if (dnSet.isEmpty())
241    {
242      return;
243    }
244
245    DN          baseDN = searchOperation.getBaseDN();
246    SearchScope scope  = searchOperation.getScope();
247    for (DN dn : dnSet)
248    {
249      if (! dn.matchesBaseAndScope(baseDN, scope))
250      {
251        continue;
252      }
253
254      try
255      {
256        Entry entry = DirectoryServer.getEntry(dn);
257        if (entry != null && filter.matchesEntry(entry))
258        {
259          searchOperation.returnEntry(entry, null);
260        }
261      }
262      catch (Exception e)
263      {
264        logger.traceException(e);
265      }
266    }
267  }
268
269
270
271  /**
272   * Extracts the user DNs from the provided filter, operating recursively as
273   * necessary, and adds them to the provided set.
274   *
275   * @param  attributeType  The attribute type holding the entryDN value.
276   * @param  filter         The search filter to be processed.
277   * @param  dnSet          The set into which the identified DNs should be
278   *                        placed.
279   */
280  private void extractDNs(AttributeType attributeType, SearchFilter filter,
281                          LinkedHashSet<DN> dnSet)
282  {
283    switch (filter.getFilterType())
284    {
285      case AND:
286      case OR:
287        for (SearchFilter f : filter.getFilterComponents())
288        {
289          extractDNs(attributeType, f, dnSet);
290        }
291        break;
292
293      case EQUALITY:
294        if (filter.getAttributeType().equals(attributeType))
295        {
296          try
297          {
298            dnSet.add(DN.decode(filter.getAssertionValue()));
299          }
300          catch (Exception e)
301          {
302            logger.traceException(e);
303          }
304        }
305        break;
306    }
307  }
308}
309