001/*
002 * Copyright 2022-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2022-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2022-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.logs.v2.syntax;
037
038
039
040import java.util.Arrays;
041import java.util.Collection;
042import java.util.Set;
043
044import com.unboundid.ldap.sdk.Attribute;
045import com.unboundid.ldap.sdk.Filter;
046import com.unboundid.ldap.sdk.schema.Schema;
047import com.unboundid.util.ByteStringBuffer;
048import com.unboundid.util.Debug;
049import com.unboundid.util.NotNull;
050import com.unboundid.util.Nullable;
051import com.unboundid.util.StaticUtils;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.json.JSONBuffer;
055
056import static com.unboundid.ldap.sdk.unboundidds.logs.v2.syntax.
057                   LogSyntaxMessages.*;
058
059
060
061/**
062 * This class defines a log field syntax for search filter values.  This syntax
063 * allows individual attribute values to be redacted or tokenized within the
064 * filters.  If a filter is completely redacted, then the redacted
065 * representation will be "<code>(redacted={REDACTED})</code>".  If a filter is
066 * completely tokenized, then the tokenized representation will be
067 * "<code>(tokenized={TOKENIZED:token-value})</code>", where token-value will be
068 * replaced with a generated value.
069 * <BR>
070 * <BLOCKQUOTE>
071 *   <B>NOTE:</B>  This class, and other classes within the
072 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
073 *   supported for use against Ping Identity, UnboundID, and
074 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
075 *   for proprietary functionality or for external specifications that are not
076 *   considered stable or mature enough to be guaranteed to work in an
077 *   interoperable way with other types of LDAP servers.
078 * </BLOCKQUOTE>
079 */
080@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
081public final class FilterLogFieldSyntax
082       extends LogFieldSyntax<Filter>
083{
084  /**
085   * The name for this syntax.
086   */
087  @NotNull public static final String SYNTAX_NAME = "filter";
088
089
090
091  /**
092   * The string representation that will be used for a DN that is completely
093   * redacted.
094   */
095  @NotNull private static final String REDACTED_FILTER_STRING =
096       "(redacted={REDACTED})";
097
098
099
100  // Indicates whether all attributes should be considered sensitive when
101  // redacting or tokenizing components.
102  private final boolean allAttributesAreSensitive;
103
104  // The set of the names and OIDs for the specific attributes whose values
105  // should not be redacted or tokenized.
106  @NotNull private final Set<String> excludedSensitiveAttributes;
107
108  // The set of the names and OIDs for the specific attributes whose values
109  // should be redacted or tokenized.
110  @NotNull private final Set<String> includedSensitiveAttributes;
111
112
113
114  /**
115   * Creates a new filter log field syntax instance that can optionally
116   * define specific attributes to include in or exclude from redaction or
117   * tokenization.  If any include attributes are specified, then only the
118   * values of those attributes will be considered sensitive and will have
119   * their values tokenized or redacted.  If any exclude
120   * attributes are specified, then the values of any attributes except those
121   * will be considered sensitive.  If no include attributes and no exclude
122   * attributes are specified, then all attributes will be considered sensitive
123   * and will have their values tokenized or redacted.
124   *
125   * @param  maxStringLengthCharacters    The maximum length (in characters) to
126   *                                      use for strings within values.
127   *                                      Strings that are longer than this
128   *                                      should be truncated before inclusion
129   *                                      in the log.  This value must be
130   *                                      greater than or equal to zero.
131   * @param  schema                       The schema to use in processing.  It
132   *                                      may optionally be {@code null} if no
133   *                                      schema should be used.
134   * @param  includedSensitiveAttributes  The set of names and OIDs for the
135   *                                      specific attributes whose values
136   *                                      should be considered sensitive and
137   *                                      should have their values redacted or
138   *                                      tokenized by methods that operate on
139   *                                      value components.  This may be
140   *                                      {@code null} or empty if no included
141   *                                      sensitive attributes should be
142   *                                      defined.
143   * @param  excludedSensitiveAttributes  The set of names and OIDs for the
144   *                                      specific attributes whose values
145   *                                      should not be considered sensitive and
146   *                                      should not have their values redacted
147   *                                      or tokenized by methods that operate
148   *                                      on value components.  This may be
149   *                                      {@code null} or empty if no excluded
150   *                                      sensitive attributes should be
151   *                                      defined.
152   */
153  public FilterLogFieldSyntax(
154              final int maxStringLengthCharacters,
155              @Nullable final Schema schema,
156              @Nullable final Collection<String> includedSensitiveAttributes,
157              @Nullable final Collection<String> excludedSensitiveAttributes)
158  {
159    super(maxStringLengthCharacters);
160
161    this.includedSensitiveAttributes =
162         AttributeBasedLogFieldSyntaxHelper.getAttributeSet(schema,
163              includedSensitiveAttributes);
164    this.excludedSensitiveAttributes =
165         AttributeBasedLogFieldSyntaxHelper.getAttributeSet(schema,
166              excludedSensitiveAttributes);
167
168    allAttributesAreSensitive = this.includedSensitiveAttributes.isEmpty() &&
169         this.excludedSensitiveAttributes.isEmpty();
170  }
171
172
173
174  /**
175   * Retrieves a set containing the names and/or OIDs of the attributes that
176   * will be considered sensitive and will have their values redacted or
177   * tokenized in methods that operate on filter components.
178   *
179   * @return  A set containing the names and/or OIDs of the attributes that will
180   *          be considered sensitive, or an empty list if no included sensitive
181   *          attributes are defined.
182   */
183  @NotNull()
184  public Set<String> getIncludedSensitiveAttributes()
185  {
186    return includedSensitiveAttributes;
187  }
188
189
190
191  /**
192   * Retrieves a set containing the names and/or OIDs of the attributes that
193   * will not be considered sensitive and will have not their values redacted or
194   * tokenized in methods that operate on filter components.
195   *
196   * @return  A set containing the names and/or OIDs of the attributes that will
197   *          not be considered sensitive, or an empty list if no excluded
198   *          sensitive attributes are defined.
199   */
200  @NotNull()
201  public Set<String> getExcludedSensitiveAttributes()
202  {
203    return excludedSensitiveAttributes;
204  }
205
206
207
208  /**
209   * {@inheritDoc}
210   */
211  @Override()
212  @NotNull()
213  public String getSyntaxName()
214  {
215    return SYNTAX_NAME;
216  }
217
218
219
220  /**
221   * {@inheritDoc}
222   */
223  @Override()
224  public void valueToSanitizedString(@NotNull final Filter value,
225                                     @NotNull final ByteStringBuffer buffer)
226  {
227    buffer.append(sanitizeFilter(value).toString());
228  }
229
230
231
232  /**
233   * Retrieves a sanitized version of the provided filter.
234   *
235   * @param  filter  The filter to sanitize.  It must not be {@code null}.
236   *
237   * @return  A sanitized version of the provided filter.
238   */
239  @NotNull()
240  private Filter sanitizeFilter(@NotNull final Filter filter)
241  {
242    switch (filter.getFilterType())
243    {
244      case Filter.FILTER_TYPE_AND:
245        final Filter[] originalANDComponents = filter.getComponents();
246        final Filter[] sanitizedANDComponents =
247             new Filter[originalANDComponents.length];
248        for (int i=0; i < originalANDComponents.length; i++)
249        {
250          sanitizedANDComponents[i] = sanitizeFilter(originalANDComponents[i]);
251        }
252        return Filter.createANDFilter(sanitizedANDComponents);
253
254      case Filter.FILTER_TYPE_OR:
255        final Filter[] originalORComponents = filter.getComponents();
256        final Filter[] sanitizedORComponents =
257             new Filter[originalORComponents.length];
258        for (int i=0; i < originalORComponents.length; i++)
259        {
260          sanitizedORComponents[i] = sanitizeFilter(originalORComponents[i]);
261        }
262        return Filter.createORFilter(sanitizedORComponents);
263
264      case Filter.FILTER_TYPE_NOT:
265        return Filter.createNOTFilter(sanitizeFilter(filter.getNOTComponent()));
266
267      case Filter.FILTER_TYPE_EQUALITY:
268        return Filter.createEqualityFilter(filter.getAttributeName(),
269             sanitize(filter.getAssertionValue()));
270
271      case Filter.FILTER_TYPE_GREATER_OR_EQUAL:
272        return Filter.createGreaterOrEqualFilter(filter.getAttributeName(),
273             sanitize(filter.getAssertionValue()));
274
275      case Filter.FILTER_TYPE_LESS_OR_EQUAL:
276        return Filter.createLessOrEqualFilter(filter.getAttributeName(),
277             sanitize(filter.getAssertionValue()));
278
279      case Filter.FILTER_TYPE_APPROXIMATE_MATCH:
280        return Filter.createApproximateMatchFilter(filter.getAttributeName(),
281             sanitize(filter.getAssertionValue()));
282
283      case Filter.FILTER_TYPE_SUBSTRING:
284        final String originalSubInitial = filter.getSubInitialString();
285        final String sanitizedSubInitial =
286             (originalSubInitial == null) ? null : sanitize(originalSubInitial);
287
288        final String originalSubFinal = filter.getSubFinalString();
289        final String sanitizedSubFinal =
290             (originalSubFinal == null) ? null : sanitize(originalSubFinal);
291
292        final String[] originalSubAny = filter.getSubAnyStrings();
293        final String[] sanitizedSubAny = new String[originalSubAny.length];
294        for (int i=0; i < originalSubAny.length; i++)
295        {
296          sanitizedSubAny[i] = sanitize(originalSubAny[i]);
297        }
298
299        return Filter.createSubstringFilter(filter.getAttributeName(),
300             sanitizedSubInitial, sanitizedSubAny, sanitizedSubFinal);
301
302      case Filter.FILTER_TYPE_EXTENSIBLE_MATCH:
303        return Filter.createExtensibleMatchFilter(filter.getAttributeName(),
304             filter.getMatchingRuleID(), filter.getDNAttributes(),
305             sanitize(filter.getAssertionValue()));
306
307      case Filter.FILTER_TYPE_PRESENCE:
308      default:
309        return filter;
310    }
311  }
312
313
314
315  /**
316   * {@inheritDoc}
317   */
318  @Override()
319  public void logSanitizedFieldToTextFormattedLog(
320                   @NotNull final String fieldName,
321                   @NotNull final Filter fieldValue,
322                   @NotNull final ByteStringBuffer buffer)
323  {
324    buffer.append(' ');
325    buffer.append(fieldName);
326    buffer.append("=\"");
327    valueToSanitizedString(fieldValue, buffer);
328    buffer.append('"');
329  }
330
331
332
333  /**
334   * {@inheritDoc}
335   */
336  @Override()
337  public void logSanitizedFieldToJSONFormattedLog(
338                   @NotNull final String fieldName,
339                   @NotNull final Filter fieldValue,
340                   @NotNull final JSONBuffer buffer)
341  {
342    buffer.appendString(fieldName, valueToSanitizedString(fieldValue));
343  }
344
345
346
347  /**
348   * {@inheritDoc}
349   */
350  @Override()
351  public void logSanitizedValueToJSONFormattedLog(
352              @NotNull final Filter value,
353              @NotNull final JSONBuffer buffer)
354  {
355    buffer.appendString(valueToSanitizedString(value));
356  }
357
358
359
360  /**
361   * {@inheritDoc}
362   */
363  @Override()
364  @NotNull()
365  public Filter parseValue(@NotNull final String valueString)
366         throws RedactedValueException, TokenizedValueException,
367                LogSyntaxException
368  {
369    try
370    {
371      return Filter.create(valueString);
372    }
373    catch (final Exception e)
374    {
375      Debug.debugException(e);
376
377      if (valueStringIsCompletelyRedacted(valueString))
378      {
379        throw new RedactedValueException(
380             ERR_FILTER_LOG_SYNTAX_CANNOT_PARSE_REDACTED.get(), e);
381      }
382      else if (valueStringIsCompletelyTokenized(valueString))
383      {
384        throw new TokenizedValueException(
385             ERR_FILTER_LOG_SYNTAX_CANNOT_PARSE_TOKENIZED.get(), e);
386      }
387      else
388      {
389        throw new LogSyntaxException(
390             ERR_FILTER_LOG_SYNTAX_CANNOT_PARSE.get(), e);
391      }
392    }
393  }
394
395
396
397  /**
398   * {@inheritDoc}
399   */
400  @Override()
401  public boolean valueStringIsCompletelyRedacted(
402                      @NotNull final String valueString)
403  {
404    return valueString.equals(REDACTED_STRING) ||
405         valueString.equals(REDACTED_FILTER_STRING);
406  }
407
408
409
410  /**
411   * {@inheritDoc}
412   */
413  @Override()
414  public boolean completelyRedactedValueConformsToSyntax()
415  {
416    return true;
417  }
418
419
420
421  /**
422   * {@inheritDoc}
423   */
424  @Override()
425  public void redactEntireValue(@NotNull final ByteStringBuffer buffer)
426  {
427    buffer.append(REDACTED_FILTER_STRING);
428  }
429
430
431
432  /**
433   * {@inheritDoc}
434   */
435  @Override()
436  public void logCompletelyRedactedFieldToTextFormattedLog(
437                   @NotNull final String fieldName,
438                   @NotNull final ByteStringBuffer buffer)
439  {
440    buffer.append(' ');
441    buffer.append(fieldName);
442    buffer.append("=\"");
443    buffer.append(REDACTED_FILTER_STRING);
444    buffer.append('"');
445  }
446
447
448
449  /**
450   * {@inheritDoc}
451   */
452  @Override()
453  public void logCompletelyRedactedFieldToJSONFormattedLog(
454                   @NotNull final String fieldName,
455                   @NotNull final JSONBuffer buffer)
456  {
457    buffer.appendString(fieldName, REDACTED_FILTER_STRING);
458  }
459
460
461
462  /**
463   * {@inheritDoc}
464   */
465  @Override()
466  public void logCompletelyRedactedValueToJSONFormattedLog(
467                   @NotNull final JSONBuffer buffer)
468  {
469    buffer.appendString(REDACTED_FILTER_STRING);
470  }
471
472
473
474  /**
475   * {@inheritDoc}
476   */
477  @Override()
478  public boolean supportsRedactedComponents()
479  {
480    return true;
481  }
482
483
484
485  /**
486   * {@inheritDoc}
487   */
488  @Override()
489  public boolean valueWithRedactedComponentsConformsToSyntax()
490  {
491    return true;
492  }
493
494
495
496  /**
497   * {@inheritDoc}
498   */
499  @Override()
500  public void redactComponents(@NotNull final Filter value,
501                               @NotNull final ByteStringBuffer buffer)
502  {
503    buffer.append(redactFilter(value).toString());
504  }
505
506
507
508  /**
509   * Retrieves a redacted version of the provided filter.
510   *
511   * @param  filter  The filter to redact.  It must not be {@code null}.
512   *
513   * @return  A redacted version of the provided filter.
514   */
515  @NotNull()
516  private Filter redactFilter(@NotNull final Filter filter)
517  {
518    switch (filter.getFilterType())
519    {
520      case Filter.FILTER_TYPE_AND:
521        final Filter[] originalANDComponents = filter.getComponents();
522        final Filter[] redactedANDComponents =
523             new Filter[originalANDComponents.length];
524        for (int i=0; i < originalANDComponents.length; i++)
525        {
526          redactedANDComponents[i] = redactFilter(originalANDComponents[i]);
527        }
528        return Filter.createANDFilter(redactedANDComponents);
529
530      case Filter.FILTER_TYPE_OR:
531        final Filter[] originalORComponents = filter.getComponents();
532        final Filter[] redactedORComponents =
533             new Filter[originalORComponents.length];
534        for (int i=0; i < originalORComponents.length; i++)
535        {
536          redactedORComponents[i] = redactFilter(originalORComponents[i]);
537        }
538        return Filter.createORFilter(redactedORComponents);
539
540      case Filter.FILTER_TYPE_NOT:
541        return Filter.createNOTFilter(redactFilter(filter.getNOTComponent()));
542
543      case Filter.FILTER_TYPE_EQUALITY:
544        if (shouldRedactOrTokenize(filter.getAttributeName()))
545        {
546          return Filter.createEqualityFilter(filter.getAttributeName(),
547               REDACTED_STRING);
548        }
549        else
550        {
551          return Filter.createEqualityFilter(filter.getAttributeName(),
552               sanitize(filter.getAssertionValue()));
553        }
554
555      case Filter.FILTER_TYPE_GREATER_OR_EQUAL:
556        if (shouldRedactOrTokenize(filter.getAttributeName()))
557        {
558          return Filter.createGreaterOrEqualFilter(filter.getAttributeName(),
559               REDACTED_STRING);
560        }
561        else
562        {
563          return Filter.createGreaterOrEqualFilter(filter.getAttributeName(),
564               sanitize(filter.getAssertionValue()));
565        }
566
567      case Filter.FILTER_TYPE_LESS_OR_EQUAL:
568        if (shouldRedactOrTokenize(filter.getAttributeName()))
569        {
570          return Filter.createLessOrEqualFilter(filter.getAttributeName(),
571               REDACTED_STRING);
572        }
573        else
574        {
575          return Filter.createLessOrEqualFilter(filter.getAttributeName(),
576               sanitize(filter.getAssertionValue()));
577        }
578
579      case Filter.FILTER_TYPE_APPROXIMATE_MATCH:
580        if (shouldRedactOrTokenize(filter.getAttributeName()))
581        {
582          return Filter.createApproximateMatchFilter(filter.getAttributeName(),
583               REDACTED_STRING);
584        }
585        else
586        {
587          return Filter.createApproximateMatchFilter(filter.getAttributeName(),
588               sanitize(filter.getAssertionValue()));
589        }
590
591      case Filter.FILTER_TYPE_SUBSTRING:
592        final String redactedSubInitial;
593        final String redactedSubFinal;
594        final String originalSubInitial = filter.getSubInitialString();
595        final String originalSubFinal = filter.getSubFinalString();
596        final String[] originalSubAny = filter.getSubAnyStrings();
597        final String[] redactedSubAny = new String[originalSubAny.length];
598        if (shouldRedactOrTokenize(filter.getAttributeName()))
599        {
600          redactedSubInitial =
601               (originalSubInitial == null) ? null : REDACTED_STRING;
602          redactedSubFinal =
603               (originalSubFinal == null) ? null : REDACTED_STRING;
604          Arrays.fill(redactedSubAny, REDACTED_STRING);
605        }
606        else
607        {
608          redactedSubInitial = (originalSubInitial == null)
609               ? null
610               : sanitize(originalSubInitial);
611          redactedSubFinal = (originalSubFinal == null)
612               ? null
613               : sanitize(originalSubFinal);
614          for (int i=0; i < originalSubAny.length; i++)
615          {
616            redactedSubAny[i] = sanitize(originalSubAny[i]);
617          }
618        }
619
620        return Filter.createSubstringFilter(filter.getAttributeName(),
621             redactedSubInitial, redactedSubAny, redactedSubFinal);
622
623      case Filter.FILTER_TYPE_EXTENSIBLE_MATCH:
624        if (shouldRedactOrTokenize(filter.getAttributeName()))
625        {
626          return Filter.createExtensibleMatchFilter(filter.getAttributeName(),
627               filter.getMatchingRuleID(), filter.getDNAttributes(),
628               REDACTED_STRING);
629        }
630        else
631        {
632          return Filter.createExtensibleMatchFilter(filter.getAttributeName(),
633               filter.getMatchingRuleID(), filter.getDNAttributes(),
634               sanitize(filter.getAssertionValue()));
635        }
636
637      case Filter.FILTER_TYPE_PRESENCE:
638        return filter;
639
640      default:
641        // This should never happen.
642        return Filter.createEqualityFilter("redacted", "{REDACTED}");
643    }
644  }
645
646
647
648  /**
649   * Indicates whether values of the specified attribute should be redacted or
650   * tokenized.
651   *
652   * @param  attributeName  The name or OID of the attribute for which to make
653   *                        the determination.  It may be {@code null} if no
654   *                        attribute name is available.
655   *
656   * @return  {@code true} if values of the specified attribute should be
657   *          redacted or tokenized, or {@code false} if not.
658   */
659  private boolean shouldRedactOrTokenize(@Nullable final String attributeName)
660  {
661    if (allAttributesAreSensitive)
662    {
663      return true;
664    }
665
666    if (attributeName == null)
667    {
668      return (! excludedSensitiveAttributes.isEmpty());
669    }
670
671    final String lowerName =
672         StaticUtils.toLowerCase(Attribute.getBaseName(attributeName));
673    if (includedSensitiveAttributes.contains(lowerName))
674    {
675      return true;
676    }
677
678    if (excludedSensitiveAttributes.isEmpty())
679    {
680      return false;
681    }
682    else
683    {
684      return (! excludedSensitiveAttributes.contains(lowerName));
685    }
686  }
687
688
689
690  /**
691   * {@inheritDoc}
692   */
693  @Override()
694  public void logRedactedComponentsFieldToTextFormattedLog(
695                   @NotNull final String fieldName,
696                   @NotNull final Filter fieldValue,
697                   @NotNull final ByteStringBuffer buffer)
698  {
699    buffer.append(' ');
700    buffer.append(fieldName);
701    buffer.append("=\"");
702    redactComponents(fieldValue, buffer);
703    buffer.append('"');
704  }
705
706
707
708  /**
709   * {@inheritDoc}
710   */
711  @Override()
712  public void logRedactedComponentsFieldToJSONFormattedLog(
713                   @NotNull final String fieldName,
714                   @NotNull final Filter fieldValue,
715                   @NotNull final JSONBuffer buffer)
716  {
717    buffer.appendString(fieldName, redactComponents(fieldValue));
718  }
719
720
721
722  /**
723   * {@inheritDoc}
724   */
725  @Override()
726  public void logRedactedComponentsValueToJSONFormattedLog(
727                   @NotNull final Filter value,
728                   @NotNull final JSONBuffer buffer)
729  {
730    buffer.appendString(redactComponents(value));
731  }
732
733
734
735  /**
736   * {@inheritDoc}
737   */
738  @Override()
739  public boolean valueStringIsCompletelyTokenized(
740                      @NotNull final String valueString)
741  {
742    return super.valueStringIsCompletelyTokenized(valueString) ||
743         (valueString.startsWith("(tokenized="+ TOKEN_PREFIX_STRING) &&
744              valueString.endsWith(TOKEN_SUFFIX_STRING + ')'));
745  }
746
747
748
749  /**
750   * {@inheritDoc}
751   */
752  @Override()
753  public boolean completelyTokenizedValueConformsToSyntax()
754  {
755    return true;
756  }
757
758
759
760  /**
761   * {@inheritDoc}
762   */
763  @Override()
764  public void tokenizeEntireValue(@NotNull final Filter value,
765                                  @NotNull final byte[] pepper,
766                                  @NotNull final ByteStringBuffer buffer)
767  {
768    buffer.append("(tokenized=");
769    tokenize(value.toNormalizedString(), pepper, buffer);
770    buffer.append(')');
771  }
772
773
774
775  /**
776   * {@inheritDoc}
777   */
778  @Override()
779  public void logCompletelyTokenizedFieldToTextFormattedLog(
780                   @NotNull final String fieldName,
781                   @NotNull final Filter fieldValue,
782                   @NotNull final byte[] pepper,
783                   @NotNull final ByteStringBuffer buffer)
784  {
785    buffer.append(' ');
786    buffer.append(fieldName);
787    buffer.append("=\"");
788    tokenizeEntireValue(fieldValue, pepper, buffer);
789    buffer.append('"');
790  }
791
792
793
794  /**
795   * {@inheritDoc}
796   */
797  @Override()
798  public void logCompletelyTokenizedFieldToJSONFormattedLog(
799                   @NotNull final String fieldName,
800                   @NotNull final Filter fieldValue,
801                   @NotNull final byte[] pepper,
802                   @NotNull final JSONBuffer buffer)
803  {
804    buffer.appendString(fieldName, tokenizeEntireValue(fieldValue, pepper));
805  }
806
807
808
809  /**
810   * {@inheritDoc}
811   */
812  @Override()
813  public void logCompletelyTokenizedValueToJSONFormattedLog(
814                   @NotNull final Filter value,
815                   @NotNull final byte[] pepper,
816                   @NotNull final JSONBuffer buffer)
817  {
818    buffer.appendString(tokenizeEntireValue(value, pepper));
819  }
820
821
822
823  /**
824   * {@inheritDoc}
825   */
826  @Override()
827  public boolean supportsTokenizedComponents()
828  {
829    return true;
830  }
831
832
833
834  /**
835   * {@inheritDoc}
836   */
837  @Override()
838  public boolean valueWithTokenizedComponentsConformsToSyntax()
839  {
840    return true;
841  }
842
843
844
845  /**
846   * {@inheritDoc}
847   */
848  @Override()
849  public void tokenizeComponents(@NotNull final Filter value,
850                                 @NotNull final byte[] pepper,
851                                 @NotNull final ByteStringBuffer buffer)
852  {
853    buffer.append(tokenizeFilter(value, pepper).toString());
854  }
855
856
857
858  /**
859   * Retrieves a tokenized version of the provided filter.
860   *
861   * @param  filter  The filter to tokenize.  It must not be {@code null}.
862   * @param  pepper  A pepper used to provide brute-force protection for the
863   *                 resulting token.  The pepper value should be kept secret so
864   *                 that it is not available to unauthorized users who might be
865   *                 able to view log information, although the same pepper
866   *                 value should be consistently provided when tokenizing
867   *                 values so that the same value will consistently yield the
868   *                 same token.  It must not be {@code null} and should not be
869   *                 empty.
870   *
871   * @return  A tokenized version of the provided filter.
872   */
873  @NotNull()
874  private Filter tokenizeFilter(@NotNull final Filter filter,
875                                @NotNull final byte[] pepper)
876  {
877    switch (filter.getFilterType())
878    {
879      case Filter.FILTER_TYPE_AND:
880        final Filter[] originalANDComponents = filter.getComponents();
881        final Filter[] tokenizedANDComponents =
882             new Filter[originalANDComponents.length];
883        for (int i=0; i < originalANDComponents.length; i++)
884        {
885          tokenizedANDComponents[i] =
886               tokenizeFilter(originalANDComponents[i], pepper);
887        }
888        return Filter.createANDFilter(tokenizedANDComponents);
889
890      case Filter.FILTER_TYPE_OR:
891        final Filter[] originalORComponents = filter.getComponents();
892        final Filter[] tokenizedORComponents =
893             new Filter[originalORComponents.length];
894        for (int i=0; i < originalORComponents.length; i++)
895        {
896          tokenizedORComponents[i] =
897               tokenizeFilter(originalORComponents[i], pepper);
898        }
899        return Filter.createORFilter(tokenizedORComponents);
900
901      case Filter.FILTER_TYPE_NOT:
902        return Filter.createNOTFilter(
903             tokenizeFilter(filter.getNOTComponent(), pepper));
904
905      case Filter.FILTER_TYPE_EQUALITY:
906        if (shouldRedactOrTokenize(filter.getAttributeName()))
907        {
908          return Filter.createEqualityFilter(filter.getAttributeName(),
909               tokenize(filter.getAssertionValue(), pepper));
910        }
911        else
912        {
913          return Filter.createEqualityFilter(filter.getAttributeName(),
914               sanitize(filter.getAssertionValue()));
915        }
916
917      case Filter.FILTER_TYPE_GREATER_OR_EQUAL:
918        if (shouldRedactOrTokenize(filter.getAttributeName()))
919        {
920          return Filter.createGreaterOrEqualFilter(filter.getAttributeName(),
921               tokenize(filter.getAssertionValue(), pepper));
922        }
923        else
924        {
925          return Filter.createGreaterOrEqualFilter(filter.getAttributeName(),
926               sanitize(filter.getAssertionValue()));
927        }
928
929      case Filter.FILTER_TYPE_LESS_OR_EQUAL:
930        if (shouldRedactOrTokenize(filter.getAttributeName()))
931        {
932          return Filter.createLessOrEqualFilter(filter.getAttributeName(),
933               tokenize(filter.getAssertionValue(), pepper));
934        }
935        else
936        {
937          return Filter.createLessOrEqualFilter(filter.getAttributeName(),
938               sanitize(filter.getAssertionValue()));
939        }
940
941      case Filter.FILTER_TYPE_APPROXIMATE_MATCH:
942        if (shouldRedactOrTokenize(filter.getAttributeName()))
943        {
944          return Filter.createApproximateMatchFilter(filter.getAttributeName(),
945               tokenize(filter.getAssertionValue(), pepper));
946        }
947        else
948        {
949          return Filter.createApproximateMatchFilter(filter.getAttributeName(),
950               sanitize(filter.getAssertionValue()));
951        }
952
953      case Filter.FILTER_TYPE_SUBSTRING:
954        final String tokenizedSubInitial;
955        final String tokenizedSubFinal;
956        final String originalSubInitial = filter.getSubInitialString();
957        final String originalSubFinal = filter.getSubFinalString();
958        final String[] originalSubAny = filter.getSubAnyStrings();
959        final String[] tokenizedSubAny = new String[originalSubAny.length];
960        if (shouldRedactOrTokenize(filter.getAttributeName()))
961        {
962          tokenizedSubInitial = (originalSubInitial == null)
963               ? null
964               : tokenize(originalSubInitial, pepper);
965          tokenizedSubFinal = (originalSubFinal == null)
966               ? null
967               : tokenize(originalSubFinal, pepper);
968          for (int i=0; i < originalSubAny.length; i++)
969          {
970            tokenizedSubAny[i] = tokenize(originalSubAny[i], pepper);
971          }
972        }
973        else
974        {
975          tokenizedSubInitial = (originalSubInitial == null)
976               ? null
977               : sanitize(originalSubInitial);
978          tokenizedSubFinal = (originalSubFinal == null)
979               ? null
980               : sanitize(originalSubFinal);
981          for (int i=0; i < originalSubAny.length; i++)
982          {
983            tokenizedSubAny[i] = sanitize(originalSubAny[i]);
984          }
985        }
986
987        return Filter.createSubstringFilter(filter.getAttributeName(),
988             tokenizedSubInitial, tokenizedSubAny, tokenizedSubFinal);
989
990      case Filter.FILTER_TYPE_EXTENSIBLE_MATCH:
991        if (shouldRedactOrTokenize(filter.getAttributeName()))
992        {
993          return Filter.createExtensibleMatchFilter(filter.getAttributeName(),
994               filter.getMatchingRuleID(), filter.getDNAttributes(),
995               tokenize(filter.getAssertionValue(), pepper));
996        }
997        else
998        {
999          return Filter.createExtensibleMatchFilter(filter.getAttributeName(),
1000               filter.getMatchingRuleID(), filter.getDNAttributes(),
1001               sanitize(filter.getAssertionValue()));
1002        }
1003
1004      case Filter.FILTER_TYPE_PRESENCE:
1005      default:
1006        return filter;
1007    }
1008  }
1009
1010
1011
1012  /**
1013   * {@inheritDoc}
1014   */
1015  @Override()
1016  public void logTokenizedComponentsFieldToTextFormattedLog(
1017                   @NotNull final String fieldName,
1018                   @NotNull final Filter fieldValue,
1019                   @NotNull final byte[] pepper,
1020                   @NotNull final ByteStringBuffer buffer)
1021  {
1022    buffer.append(' ');
1023    buffer.append(fieldName);
1024    buffer.append("=\"");
1025    tokenizeComponents(fieldValue, pepper, buffer);
1026    buffer.append('"');
1027  }
1028
1029
1030
1031  /**
1032   * {@inheritDoc}
1033   */
1034  @Override()
1035  public void logTokenizedComponentsFieldToJSONFormattedLog(
1036                   @NotNull final String fieldName,
1037                   @NotNull final Filter fieldValue,
1038                   @NotNull final byte[] pepper,
1039                   @NotNull final JSONBuffer buffer)
1040  {
1041    buffer.appendString(fieldName, tokenizeComponents(fieldValue, pepper));
1042  }
1043
1044
1045
1046  /**
1047   * {@inheritDoc}
1048   */
1049  @Override()
1050  public void logTokenizedComponentsValueToJSONFormattedLog(
1051                   @NotNull final Filter value,
1052                   @NotNull final byte[] pepper,
1053                   @NotNull final JSONBuffer buffer)
1054  {
1055    buffer.appendString(tokenizeComponents(value, pepper));
1056  }
1057}