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