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