mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Nicolas Capponi
28.34.2014 1d5d1a6a4a0a58d6bb4803527dacb6641c027816
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2011 profiq s.r.o.
 *      Portions Copyright 2011-2014 ForgeRock AS.
 */
package org.opends.server.plugins;
 
 
 
import static org.opends.messages.PluginMessages.*;
import static org.opends.server.util.StaticUtils.toLowerCase;
 
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
 
import org.forgerock.i18n.LocalizableMessage;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.AttributeCleanupPluginCfg;
import org.opends.server.admin.std.server.PluginCfg;
import org.opends.server.api.plugin.DirectoryServerPlugin;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.api.plugin.PluginType;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.types.*;
import org.opends.server.types.operation.PreParseAddOperation;
import org.opends.server.types.operation.PreParseModifyOperation;
 
 
 
/**
 * The attribute cleanup plugin implementation class. The plugin removes and/or
 * renames the configured parameters from the incoming ADD and MODIFY requests.
 */
public class AttributeCleanupPlugin extends
    DirectoryServerPlugin<AttributeCleanupPluginCfg> implements
    ConfigurationChangeListener<AttributeCleanupPluginCfg>
{
 
  /**
   * Plugin configuration.
   */
  private AttributeCleanupPluginCfg config;
 
  /**
   * Debug tracer.
   */
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
  /**
   * A table of attributes to be renamed.
   */
  private Map<String, String> attributesToRename;
 
  /**
   * The set of attributes to be removed.
   */
  private Set<String> attributesToRemove;
 
  /**
   * This lock prevents concurrent updates to the configuration while operations
   * are being processed.
   */
  private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  private final ReadLock sharedLock = lock.readLock();
  private final WriteLock exclusiveLock = lock.writeLock();
 
 
 
  /**
   * Default constructor.
   */
  public AttributeCleanupPlugin()
  {
    super();
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public ConfigChangeResult applyConfigurationChange(
      final AttributeCleanupPluginCfg config)
  {
    exclusiveLock.lock();
    try
    {
      /* Apply the change, as at this point is has been validated. */
      this.config = config;
 
      attributesToRename = new HashMap<String, String>();
      for (final String mapping : config.getRenameInboundAttributes())
      {
        final int colonPos = mapping.lastIndexOf(":");
        final String fromAttr = mapping.substring(0, colonPos).trim();
        final String toAttr = mapping.substring(colonPos + 1).trim();
        attributesToRename.put(toLowerCase(fromAttr), toLowerCase(toAttr));
      }
 
      attributesToRemove = new HashSet<String>();
      for (final String attr : config.getRemoveInboundAttributes())
      {
        attributesToRemove.add(toLowerCase(attr.trim()));
      }
 
      /* Update was successful, no restart required. */
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    finally
    {
      exclusiveLock.unlock();
    }
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public PluginResult.PreParse doPreParse(
      final PreParseAddOperation addOperation)
  {
    sharedLock.lock();
    try
    {
      /*
       * First strip the listed attributes, then rename the ones that remain.
       */
      processInboundRemove(addOperation);
      processInboundRename(addOperation);
 
      return PluginResult.PreParse.continueOperationProcessing();
    }
    finally
    {
      sharedLock.unlock();
    }
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public PluginResult.PreParse doPreParse(
      final PreParseModifyOperation modifyOperation)
  {
    sharedLock.lock();
    try
    {
      /*
       * First strip the listed attributes, then rename the ones that remain.
       */
      processInboundRemove(modifyOperation);
      processInboundRename(modifyOperation);
 
      /*
       * If the MODIFY request has been stripped of ALL modifications, stop the
       * processing and return SUCCESS to the client.
       */
      if (modifyOperation.getRawModifications().isEmpty())
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("The AttributeCleanupPlugin has eliminated all "
              + "modifications. The processing should be stopped.");
        }
        return PluginResult.PreParse.stopProcessing(ResultCode.SUCCESS, null);
      }
 
      return PluginResult.PreParse.continueOperationProcessing();
    }
    finally
    {
      sharedLock.unlock();
    }
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public void finalizePlugin()
  {
    /*
     * It's not essential to take the lock here, but we will anyhow for
     * consistency with other methods.
     */
    exclusiveLock.lock();
    try
    {
      /* Deregister change listeners. */
      config.removeAttributeCleanupChangeListener(this);
    }
    finally
    {
      exclusiveLock.unlock();
    }
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializePlugin(final Set<PluginType> pluginTypes,
      final AttributeCleanupPluginCfg configuration) throws ConfigException,
      InitializationException
  {
    /*
     * The plugin should be invoked only for pre-parse ADD and MODIFY
     * operations.
     */
    for (final PluginType t : pluginTypes)
    {
      switch (t)
      {
      case PRE_PARSE_ADD:
        break;
      case PRE_PARSE_MODIFY:
        break;
      default:
        final LocalizableMessage message = ERR_PLUGIN_ATTR_CLEANUP_INITIALIZE_PLUGIN
            .get(String.valueOf(t));
        throw new ConfigException(message);
      }
    }
 
    /* Verify the current configuration. */
    final List<LocalizableMessage> messages = new LinkedList<LocalizableMessage>();
    if (!isConfigurationChangeAcceptable(configuration, messages))
    {
      throw new ConfigException(messages.get(0));
    }
 
    /* Register change listeners. */
    configuration.addAttributeCleanupChangeListener(this);
 
    /* Save the configuration. */
    applyConfigurationChange(configuration);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isConfigurationAcceptable(final PluginCfg configuration,
      final List<LocalizableMessage> unacceptableReasons)
  {
    final AttributeCleanupPluginCfg cfg =
      (AttributeCleanupPluginCfg) configuration;
    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isConfigurationChangeAcceptable(
      final AttributeCleanupPluginCfg config, final List<LocalizableMessage> messages)
  {
    /*
     * The admin framework will ensure that there are no duplicate attributes to
     * be removed.
     */
    boolean isValid = true;
 
    /*
     * Verify that there are no duplicate mappings and that attributes are
     * renamed to valid attribute types.
     */
    final Set<String> fromAttrs = new HashSet<String>();
    for (final String attr : config.getRenameInboundAttributes())
    {
      /*
       * The format is: from:to where each 'from' and 'to' are attribute
       * descriptions. The admin framework ensures that the format is correct.
       */
      final int colonPos = attr.lastIndexOf(":");
      final String fromAttr = attr.substring(0, colonPos).trim();
      final String toAttr = attr.substring(colonPos + 1).trim();
 
      /*
       * Make sure that toAttr is defined within the server, being careful to
       * ignore attribute options.
       */
      final int semicolonPos = toAttr.indexOf(";");
      final String toAttrType = (semicolonPos < 0)
          && (semicolonPos < (toAttr.length() - 1)) ? toAttr : toAttr
          .substring(semicolonPos + 1);
 
      if (DirectoryServer.getAttributeType(toLowerCase(toAttrType)) == null)
      {
        messages.add(ERR_PLUGIN_ATTR_CLEANUP_ATTRIBUTE_MISSING.get(toAttr));
        isValid = false;
      }
 
      /*
       * Check for duplicates.
       */
      final String nfromAttr = toLowerCase(fromAttr);
      if (fromAttrs.contains(nfromAttr))
      {
        messages.add(ERR_PLUGIN_ATTR_CLEANUP_DUPLICATE_VALUE.get(fromAttr));
        isValid = false;
      }
      else
      {
        fromAttrs.add(nfromAttr);
      }
 
      /*
       * Check that attribute does not map to itself.
       */
      if (nfromAttr.equals(toLowerCase(toAttr)))
      {
        messages
            .add(ERR_PLUGIN_ATTR_CLEANUP_EQUAL_VALUES.get(fromAttr, toAttr));
        isValid = false;
      }
 
    }
 
    return isValid;
  }
 
 
 
  /**
   * Remove the attributes listed in the configuration under
   * ds-cfg-remove-inbound-attributes from the incoming ADD request.
   *
   * @param addOperation
   *          Current ADD operation.
   */
  private void processInboundRemove(final PreParseAddOperation addOperation)
  {
    final List<RawAttribute> inAttrs = new LinkedList<RawAttribute>(
        addOperation.getRawAttributes());
    final ListIterator<RawAttribute> iterator = inAttrs.listIterator();
    while (iterator.hasNext())
    {
      final RawAttribute rawAttr = iterator.next();
      final String attrName = toLowerCase(rawAttr.getAttributeType().trim());
      if (attributesToRemove.contains(attrName))
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("AttributeCleanupPlugin removing '%s'",
              rawAttr.getAttributeType());
        }
        iterator.remove();
      }
    }
    addOperation.setRawAttributes(inAttrs);
  }
 
 
 
  /**
   * Remove the attributes listed in the configuration under
   * ds-cfg-remove-inbound-attributes from the incoming MODIFY request.
   *
   * @param modifyOperation
   *          Current MODIFY operation.
   */
  private void processInboundRemove(
      final PreParseModifyOperation modifyOperation)
  {
    final List<RawModification> rawMods = new LinkedList<RawModification>(
        modifyOperation.getRawModifications());
    final ListIterator<RawModification> iterator = rawMods.listIterator();
    while (iterator.hasNext())
    {
      final RawModification rawMod = iterator.next();
      final RawAttribute rawAttr = rawMod.getAttribute();
      final String attrName = toLowerCase(rawAttr.getAttributeType().trim());
      if (attributesToRemove.contains(attrName))
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("AttributeCleanupPlugin removing '%s'",
              rawAttr.getAttributeType());
        }
        iterator.remove();
      }
    }
    modifyOperation.setRawModifications(rawMods);
  }
 
 
 
  /**
   * Map the incoming attributes to the local ones.
   *
   * @param addOperation
   *          Current ADD operation.
   */
  private void processInboundRename(final PreParseAddOperation addOperation)
  {
    final List<RawAttribute> inAttrs = new LinkedList<RawAttribute>(
        addOperation.getRawAttributes());
    final ListIterator<RawAttribute> iterator = inAttrs.listIterator();
    while (iterator.hasNext())
    {
      final RawAttribute rawAttr = iterator.next();
      final String fromName = toLowerCase(rawAttr.getAttributeType().trim());
      final String toName = attributesToRename.get(fromName);
      if (toName != null)
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("AttributeCleanupPlugin renaming '%s' to '%s'",
              rawAttr.getAttributeType(), toName);
        }
        rawAttr.setAttributeType(toName);
      }
    }
    addOperation.setRawAttributes(inAttrs);
  }
 
 
 
  /**
   * Rename the attributes in the incoming MODIFY request to names that exist in
   * the local schema as defined in the configuration.
   *
   * @param modifyOperation
   *          Current MODIFY operation.
   */
  private void processInboundRename(
      final PreParseModifyOperation modifyOperation)
  {
    final List<RawModification> rawMods = new LinkedList<RawModification>(
        modifyOperation.getRawModifications());
    final ListIterator<RawModification> iterator = rawMods.listIterator();
    while (iterator.hasNext())
    {
      final RawModification rawMod = iterator.next();
      final RawAttribute rawAttr = rawMod.getAttribute();
      final String fromName = toLowerCase(rawAttr.getAttributeType().trim());
      final String toName = attributesToRename.get(fromName);
      if (toName != null)
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("AttributeCleanupPlugin renaming '%s' to '%s'",
              rawAttr.getAttributeType(), toName);
        }
        rawAttr.setAttributeType(toName);
      }
    }
    modifyOperation.setRawModifications(rawMods);
  }
}