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 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.plugins;
028
029
030import java.util.LinkedHashSet;
031import java.util.List;
032import java.util.Set;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.opends.server.admin.server.ConfigurationChangeListener;
036import org.opends.server.admin.std.meta.PluginCfgDefn;
037import org.opends.server.admin.std.server.LDAPAttributeDescriptionListPluginCfg;
038import org.opends.server.admin.std.server.PluginCfg;
039import org.opends.server.api.plugin.DirectoryServerPlugin;
040import org.opends.server.api.plugin.PluginType;
041import org.opends.server.api.plugin.PluginResult;
042import org.forgerock.opendj.config.server.ConfigException;
043import org.opends.server.types.AttributeType;
044import org.forgerock.opendj.config.server.ConfigChangeResult;
045import org.opends.server.types.DirectoryConfig;
046import org.opends.server.types.ObjectClass;
047import org.opends.server.types.operation.PreParseSearchOperation;
048
049import org.forgerock.i18n.slf4j.LocalizedLogger;
050import static org.opends.messages.PluginMessages.*;
051
052import static org.opends.server.types.DirectoryConfig.getObjectClass;
053import static org.opends.server.util.ServerConstants.*;
054import static org.opends.server.util.StaticUtils.*;
055
056
057/**
058 * This pre-parse plugin modifies the operation to allow an object class
059 * identifier to be specified in attributes lists, such as in Search requests,
060 * to request the return all attributes belonging to an object class as per the
061 * specification in RFC 4529.  The "@" character is used to distinguish an
062 * object class identifier from an attribute descriptions.
063 */
064public final class LDAPADListPlugin
065       extends DirectoryServerPlugin<LDAPAttributeDescriptionListPluginCfg>
066       implements ConfigurationChangeListener<
067                       LDAPAttributeDescriptionListPluginCfg>
068{
069  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
070
071
072
073  /**
074   * Filters the set of attributes provided in a search request or pre- / post-
075   * read controls according to RFC 4529. More specifically, this method
076   * iterates through the requested attributes to see if any of them reference
077   * an object class, as indicated by a "@" prefix, and substitutes the object
078   * class reference with the attribute types contained in the object class, as
079   * well as any of the attribute types contained in any superior object
080   * classes.
081   *
082   * @param attributes
083   *          The attribute list to be normalized.
084   * @return The normalized attribute list.
085   */
086  public static Set<String> normalizedObjectClasses(Set<String> attributes)
087  {
088    boolean foundOC = false;
089    for (String attrName : attributes)
090    {
091      if (attrName.startsWith("@"))
092      {
093        foundOC = true;
094        break;
095      }
096    }
097
098    if (foundOC)
099    {
100      final LinkedHashSet<String> newAttrs = new LinkedHashSet<>();
101      for (final String attrName : attributes)
102      {
103        if (attrName.startsWith("@"))
104        {
105          final String lowerName = toLowerCase(attrName.substring(1));
106          final ObjectClass oc = getObjectClass(lowerName, false);
107          if (oc == null)
108          {
109            if (logger.isTraceEnabled())
110            {
111              logger.trace("Cannot replace unknown objectclass %s",
112                                  lowerName);
113            }
114          }
115          else
116          {
117            if (logger.isTraceEnabled())
118            {
119              logger.trace("Replacing objectclass %s", lowerName);
120            }
121
122            for (final AttributeType at : oc.getRequiredAttributeChain())
123            {
124              newAttrs.add(at.getNameOrOID());
125            }
126
127            for (final AttributeType at : oc.getOptionalAttributeChain())
128            {
129              newAttrs.add(at.getNameOrOID());
130            }
131          }
132        }
133        else
134        {
135          newAttrs.add(attrName);
136        }
137      }
138      attributes = newAttrs;
139    }
140
141    return attributes;
142  }
143
144
145
146  /** The current configuration for this plugin. */
147  private LDAPAttributeDescriptionListPluginCfg currentConfig;
148
149
150
151  /**
152   * Creates a new instance of this Directory Server plugin.  Every plugin must
153   * implement a default constructor (it is the only one that will be used to
154   * create plugins defined in the configuration), and every plugin constructor
155   * must call <CODE>super()</CODE> as its first element.
156   */
157  public LDAPADListPlugin()
158  {
159    super();
160  }
161
162
163
164  /** {@inheritDoc} */
165  @Override
166  public final void initializePlugin(Set<PluginType> pluginTypes,
167                         LDAPAttributeDescriptionListPluginCfg configuration)
168         throws ConfigException
169  {
170    currentConfig = configuration;
171    configuration.addLDAPAttributeDescriptionListChangeListener(this);
172
173    // The set of plugin types must contain only the pre-parse search element.
174    if (pluginTypes.isEmpty())
175    {
176      throw new ConfigException(ERR_PLUGIN_ADLIST_NO_PLUGIN_TYPES.get(configuration.dn()));
177    }
178    else
179    {
180      for (PluginType t : pluginTypes)
181      {
182        if (t != PluginType.PRE_PARSE_SEARCH)
183        {
184          throw new ConfigException(ERR_PLUGIN_ADLIST_INVALID_PLUGIN_TYPE.get(configuration.dn(), t));
185        }
186      }
187    }
188
189
190    // Register the appropriate supported feature with the Directory Server.
191    DirectoryConfig.registerSupportedFeature(OID_LDAP_ADLIST_FEATURE);
192  }
193
194
195
196  /** {@inheritDoc} */
197  @Override
198  public final void finalizePlugin()
199  {
200    currentConfig.removeLDAPAttributeDescriptionListChangeListener(this);
201  }
202
203
204
205  /** {@inheritDoc} */
206  @Override
207  public final PluginResult.PreParse doPreParse(
208      PreParseSearchOperation searchOperation)
209  {
210    searchOperation.setAttributes(normalizedObjectClasses(searchOperation
211        .getAttributes()));
212    return PluginResult.PreParse.continueOperationProcessing();
213  }
214
215
216
217  /** {@inheritDoc} */
218  @Override
219  public boolean isConfigurationAcceptable(PluginCfg configuration,
220                                           List<LocalizableMessage> unacceptableReasons)
221  {
222    LDAPAttributeDescriptionListPluginCfg cfg =
223         (LDAPAttributeDescriptionListPluginCfg) configuration;
224    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
225  }
226
227
228
229  /** {@inheritDoc} */
230  public boolean isConfigurationChangeAcceptable(
231                      LDAPAttributeDescriptionListPluginCfg configuration,
232                      List<LocalizableMessage> unacceptableReasons)
233  {
234    boolean configAcceptable = true;
235
236    // Ensure that the set of plugin types contains only pre-parse search.
237    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
238    {
239      switch (pluginType)
240      {
241        case PREPARSESEARCH:
242          // This is acceptable.
243          break;
244
245
246        default:
247          unacceptableReasons.add(ERR_PLUGIN_ADLIST_INVALID_PLUGIN_TYPE.get(configuration.dn(), pluginType));
248          configAcceptable = false;
249      }
250    }
251
252    return configAcceptable;
253  }
254
255
256
257  /** {@inheritDoc} */
258  public ConfigChangeResult applyConfigurationChange(
259                                 LDAPAttributeDescriptionListPluginCfg
260                                      configuration)
261  {
262    currentConfig = configuration;
263    return new ConfigChangeResult();
264  }
265}
266