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.Collection;
041import java.util.Set;
042
043import com.unboundid.ldap.sdk.DN;
044import com.unboundid.ldap.sdk.DNEscapingStrategy;
045import com.unboundid.ldap.sdk.RDN;
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 distinguished name values.  This
063 * syntax allows individual attribute values to be redacted or tokenized within
064 * the DNs.  If a DN is completely redacted, then the redacted representation
065 * will be "<code>redacted={REDACTED}</code>".  If a DN is completely tokenized,
066 * 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 DNLogFieldSyntax
082       extends LogFieldSyntax<DN>
083{
084  /**
085   * The name for this syntax.
086   */
087  @NotNull public static final String SYNTAX_NAME = "dn";
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_DN_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 schema to use in processing.
105  @Nullable private final Schema schema;
106
107  // The set of the names and OIDs for the specific attributes whose values
108  // should not be redacted or tokenized.
109  @NotNull private final Set<String> excludedSensitiveAttributes;
110
111  // The set of the names and OIDs for the specific attributes whose values
112  // should be redacted or tokenized.
113  @NotNull private final Set<String> includedSensitiveAttributes;
114
115
116
117  /**
118   * Creates a new DN log field syntax instance that can optionally define
119   * specific attributes to include in or exclude from redaction or
120   * tokenization.  If any include attributes are specified, then only the
121   * values of those attributes will be considered sensitive and will have
122   * their values tokenized or redacted.  If any exclude
123   * attributes are specified, then the values of any attributes except those
124   * will be considered sensitive.  If no include attributes and no exclude
125   * attributes are specified, then all attributes will be considered sensitive
126   * and will have their values tokenized or redacted.
127   *
128   * @param  maxStringLengthCharacters    The maximum length (in characters) to
129   *                                      use for strings within values.
130   *                                      Strings that are longer than this
131   *                                      should be truncated before inclusion
132   *                                      in the log.  This value must be
133   *                                      greater than or equal to zero.
134   * @param  schema                       The schema to use in processing.  It
135   *                                      may optionally be {@code null} if no
136   *                                      schema should be used.
137   * @param  includedSensitiveAttributes  The set of names and OIDs for the
138   *                                      specific attributes whose values
139   *                                      should be considered sensitive and
140   *                                      should have their values redacted or
141   *                                      tokenized by methods that operate on
142   *                                      value components.  This may be
143   *                                      {@code null} or empty if no included
144   *                                      sensitive attributes should be
145   *                                      defined.
146   * @param  excludedSensitiveAttributes  The set of names and OIDs for the
147   *                                      specific attributes whose values
148   *                                      should not be considered sensitive and
149   *                                      should not have their values redacted
150   *                                      or tokenized by methods that operate
151   *                                      on value components.  This may be
152   *                                      {@code null} or empty if no excluded
153   *                                      sensitive attributes should be
154   *                                      defined.
155   */
156  public DNLogFieldSyntax(
157              final int maxStringLengthCharacters,
158              @Nullable final Schema schema,
159              @Nullable final Collection<String> includedSensitiveAttributes,
160              @Nullable final Collection<String> excludedSensitiveAttributes)
161  {
162    super(maxStringLengthCharacters);
163
164    this.schema = schema;
165    this.includedSensitiveAttributes =
166         AttributeBasedLogFieldSyntaxHelper.getAttributeSet(schema,
167              includedSensitiveAttributes);
168    this.excludedSensitiveAttributes =
169         AttributeBasedLogFieldSyntaxHelper.getAttributeSet(schema,
170              excludedSensitiveAttributes);
171
172    allAttributesAreSensitive = this.includedSensitiveAttributes.isEmpty() &&
173         this.excludedSensitiveAttributes.isEmpty();
174  }
175
176
177
178  /**
179   * Retrieves a set containing the names and/or OIDs of the attributes that
180   * will be considered sensitive and will have their values redacted or
181   * tokenized in methods that operate on DN components.
182   *
183   * @return  A set containing the names and/or OIDs of the attributes that will
184   *          be considered sensitive, or an empty list if no included sensitive
185   *          attributes are defined.
186   */
187  @NotNull()
188  public Set<String> getIncludedSensitiveAttributes()
189  {
190    return includedSensitiveAttributes;
191  }
192
193
194
195  /**
196   * Retrieves a set containing the names and/or OIDs of the attributes that
197   * will not be considered sensitive and will have not their values redacted or
198   * tokenized in methods that operate on DN components.
199   *
200   * @return  A set containing the names and/or OIDs of the attributes that will
201   *          not be considered sensitive, or an empty list if no excluded
202   *          sensitive attributes are defined.
203   */
204  @NotNull()
205  public Set<String> getExcludedSensitiveAttributes()
206  {
207    return excludedSensitiveAttributes;
208  }
209
210
211
212  /**
213   * {@inheritDoc}
214   */
215  @Override()
216  @NotNull()
217  public String getSyntaxName()
218  {
219    return SYNTAX_NAME;
220  }
221
222
223
224  /**
225   * {@inheritDoc}
226   */
227  @Override()
228  public void valueToSanitizedString(@NotNull final DN value,
229                                     @NotNull final ByteStringBuffer buffer)
230  {
231    final RDN[] originalRDNs = value.getRDNs();
232    final RDN[] sanitizedRDNs = new RDN[originalRDNs.length];
233    for (int i=0; i < originalRDNs.length; i++)
234    {
235      final String[] attributeNames = originalRDNs[i].getAttributeNames();
236      final String[] originalValues = originalRDNs[i].getAttributeValues();
237      final String[] sanitizedValues = new String[originalValues.length];
238      for (int j=0; j < originalValues.length; j++)
239      {
240        sanitizedValues[j] = sanitize(originalValues[j]);
241      }
242
243      sanitizedRDNs[i] = new RDN(attributeNames, sanitizedValues, schema);
244    }
245
246    final DN sanitizedDN = new DN(sanitizedRDNs);
247    sanitizedDN.toString(buffer, DNEscapingStrategy.DEFAULT);
248  }
249
250
251
252  /**
253   * {@inheritDoc}
254   */
255  @Override()
256  public void logSanitizedFieldToTextFormattedLog(
257                   @NotNull final String fieldName,
258                   @NotNull final DN fieldValue,
259                   @NotNull final ByteStringBuffer buffer)
260  {
261    buffer.append(' ');
262    buffer.append(fieldName);
263    buffer.append("=\"");
264    valueToSanitizedString(fieldValue, buffer);
265    buffer.append('"');
266  }
267
268
269
270  /**
271   * {@inheritDoc}
272   */
273  @Override()
274  public void logSanitizedFieldToJSONFormattedLog(
275                   @NotNull final String fieldName,
276                   @NotNull final DN fieldValue,
277                   @NotNull final JSONBuffer buffer)
278  {
279    buffer.appendString(fieldName, valueToSanitizedString(fieldValue));
280  }
281
282
283
284  /**
285   * {@inheritDoc}
286   */
287  @Override()
288  public void logSanitizedValueToJSONFormattedLog(
289              @NotNull final DN value,
290              @NotNull final JSONBuffer buffer)
291  {
292    buffer.appendString(valueToSanitizedString(value));
293  }
294
295
296
297  /**
298   * {@inheritDoc}
299   */
300  @Override()
301  @NotNull()
302  public DN parseValue(@NotNull final String valueString)
303         throws RedactedValueException, TokenizedValueException,
304                LogSyntaxException
305  {
306    try
307    {
308      return new DN(valueString, schema);
309    }
310    catch (final Exception e)
311    {
312      Debug.debugException(e);
313
314      if (valueStringIsCompletelyRedacted(valueString))
315      {
316        throw new RedactedValueException(
317             ERR_DN_LOG_SYNTAX_CANNOT_PARSE_REDACTED.get(), e);
318      }
319      else if (valueStringIsCompletelyTokenized(valueString))
320      {
321        throw new TokenizedValueException(
322             ERR_DN_LOG_SYNTAX_CANNOT_PARSE_TOKENIZED.get(), e);
323      }
324      else
325      {
326        throw new LogSyntaxException(
327             ERR_DN_LOG_SYNTAX_CANNOT_PARSE.get(), e);
328      }
329    }
330  }
331
332
333
334  /**
335   * {@inheritDoc}
336   */
337  @Override()
338  public boolean valueStringIsCompletelyRedacted(
339                      @NotNull final String valueString)
340  {
341    return valueString.equals(REDACTED_STRING) ||
342         valueString.equals(REDACTED_DN_STRING);
343  }
344
345
346
347  /**
348   * {@inheritDoc}
349   */
350  @Override()
351  public boolean completelyRedactedValueConformsToSyntax()
352  {
353    return true;
354  }
355
356
357
358  /**
359   * {@inheritDoc}
360   */
361  @Override()
362  public void redactEntireValue(@NotNull final ByteStringBuffer buffer)
363  {
364    buffer.append(REDACTED_DN_STRING);
365  }
366
367
368
369  /**
370   * {@inheritDoc}
371   */
372  @Override()
373  public void logCompletelyRedactedFieldToTextFormattedLog(
374                   @NotNull final String fieldName,
375                   @NotNull final ByteStringBuffer buffer)
376  {
377    buffer.append(' ');
378    buffer.append(fieldName);
379    buffer.append("=\"");
380    buffer.append(REDACTED_DN_STRING);
381    buffer.append('"');
382  }
383
384
385
386  /**
387   * {@inheritDoc}
388   */
389  @Override()
390  public void logCompletelyRedactedFieldToJSONFormattedLog(
391                   @NotNull final String fieldName,
392                   @NotNull final JSONBuffer buffer)
393  {
394    buffer.appendString(fieldName, REDACTED_DN_STRING);
395  }
396
397
398
399  /**
400   * {@inheritDoc}
401   */
402  @Override()
403  public void logCompletelyRedactedValueToJSONFormattedLog(
404                   @NotNull final JSONBuffer buffer)
405  {
406    buffer.appendString(REDACTED_DN_STRING);
407  }
408
409
410
411  /**
412   * {@inheritDoc}
413   */
414  @Override()
415  public boolean supportsRedactedComponents()
416  {
417    return true;
418  }
419
420
421
422  /**
423   * {@inheritDoc}
424   */
425  @Override()
426  public boolean valueWithRedactedComponentsConformsToSyntax()
427  {
428    return true;
429  }
430
431
432
433  /**
434   * {@inheritDoc}
435   */
436  @Override()
437  public void redactComponents(@NotNull final DN value,
438                               @NotNull final ByteStringBuffer buffer)
439  {
440    final RDN[] originalRDNs = value.getRDNs();
441    final RDN[] redactedRDNs = new RDN[originalRDNs.length];
442    for (int i=0; i < originalRDNs.length; i++)
443    {
444      final RDN rdn = originalRDNs[i];
445      final String[] attributeNames = rdn.getAttributeNames();
446      final String[] originalValues = rdn.getAttributeValues();
447      final String[] redactedValues = rdn.getAttributeValues();
448      for (int j=0; j < attributeNames.length; j++)
449      {
450        if (shouldRedactOrTokenize(attributeNames[j]))
451        {
452          redactedValues[j] = REDACTED_STRING;
453        }
454        else
455        {
456          redactedValues[j] = sanitize(originalValues[j]);
457        }
458      }
459
460      redactedRDNs[i] = new RDN(attributeNames, redactedValues, schema);
461    }
462
463    final DN redactedDN = new DN(redactedRDNs);
464    redactedDN.toString(buffer, DNEscapingStrategy.DEFAULT);
465  }
466
467
468
469  /**
470   * Indicates whether values of the specified attribute should be redacted or
471   * tokenized.
472   *
473   * @param  attributeName  The name or OID of the attribute for which to make
474   *                        the determination.  It must not be {@code null}.
475   *
476   * @return  {@code true} if values of the specified attribute should be
477   *          redacted or tokenized, or {@code false} if not.
478   */
479  private boolean shouldRedactOrTokenize(@NotNull final String attributeName)
480  {
481    if (allAttributesAreSensitive)
482    {
483      return true;
484    }
485
486    final String lowerName = StaticUtils.toLowerCase(attributeName);
487    if (includedSensitiveAttributes.contains(lowerName))
488    {
489      return true;
490    }
491
492    if (excludedSensitiveAttributes.isEmpty())
493    {
494      return false;
495    }
496    else
497    {
498      return (! excludedSensitiveAttributes.contains(lowerName));
499    }
500  }
501
502
503
504  /**
505   * {@inheritDoc}
506   */
507  @Override()
508  public void logRedactedComponentsFieldToTextFormattedLog(
509                   @NotNull final String fieldName,
510                   @NotNull final DN fieldValue,
511                   @NotNull final ByteStringBuffer buffer)
512  {
513    buffer.append(' ');
514    buffer.append(fieldName);
515    buffer.append("=\"");
516    redactComponents(fieldValue, buffer);
517    buffer.append('"');
518  }
519
520
521
522  /**
523   * {@inheritDoc}
524   */
525  @Override()
526  public void logRedactedComponentsFieldToJSONFormattedLog(
527                   @NotNull final String fieldName,
528                   @NotNull final DN fieldValue,
529                   @NotNull final JSONBuffer buffer)
530  {
531    buffer.appendString(fieldName, redactComponents(fieldValue));
532  }
533
534
535
536  /**
537   * {@inheritDoc}
538   */
539  @Override()
540  public void logRedactedComponentsValueToJSONFormattedLog(
541                   @NotNull final DN value,
542                   @NotNull final JSONBuffer buffer)
543  {
544    buffer.appendString(redactComponents(value));
545  }
546
547
548
549  /**
550   * {@inheritDoc}
551   */
552  @Override()
553  public boolean valueStringIsCompletelyTokenized(
554                      @NotNull final String valueString)
555  {
556    return super.valueStringIsCompletelyTokenized(valueString) ||
557         (valueString.startsWith("tokenized="+ TOKEN_PREFIX_STRING) &&
558              valueString.endsWith(TOKEN_SUFFIX_STRING));
559  }
560
561
562
563  /**
564   * {@inheritDoc}
565   */
566  @Override()
567  public boolean completelyTokenizedValueConformsToSyntax()
568  {
569    return true;
570  }
571
572
573
574  /**
575   * {@inheritDoc}
576   */
577  @Override()
578  public void tokenizeEntireValue(@NotNull final DN value,
579                                  @NotNull final byte[] pepper,
580                                  @NotNull final ByteStringBuffer buffer)
581  {
582    buffer.append("tokenized=");
583    tokenize(value.toNormalizedString(), pepper, buffer);
584  }
585
586
587
588  /**
589   * {@inheritDoc}
590   */
591  @Override()
592  public void logCompletelyTokenizedFieldToTextFormattedLog(
593                   @NotNull final String fieldName,
594                   @NotNull final DN fieldValue,
595                   @NotNull final byte[] pepper,
596                   @NotNull final ByteStringBuffer buffer)
597  {
598    buffer.append(' ');
599    buffer.append(fieldName);
600    buffer.append("=\"");
601    tokenizeEntireValue(fieldValue, pepper, buffer);
602    buffer.append('"');
603  }
604
605
606
607  /**
608   * {@inheritDoc}
609   */
610  @Override()
611  public void logCompletelyTokenizedFieldToJSONFormattedLog(
612                   @NotNull final String fieldName,
613                   @NotNull final DN fieldValue,
614                   @NotNull final byte[] pepper,
615                   @NotNull final JSONBuffer buffer)
616  {
617    buffer.appendString(fieldName, tokenizeEntireValue(fieldValue, pepper));
618  }
619
620
621
622  /**
623   * {@inheritDoc}
624   */
625  @Override()
626  public void logCompletelyTokenizedValueToJSONFormattedLog(
627                   @NotNull final DN value,
628                   @NotNull final byte[] pepper,
629                   @NotNull final JSONBuffer buffer)
630  {
631    buffer.appendString(tokenizeEntireValue(value, pepper));
632  }
633
634
635
636  /**
637   * {@inheritDoc}
638   */
639  @Override()
640  public boolean supportsTokenizedComponents()
641  {
642    return true;
643  }
644
645
646
647  /**
648   * {@inheritDoc}
649   */
650  @Override()
651  public boolean valueWithTokenizedComponentsConformsToSyntax()
652  {
653    return true;
654  }
655
656
657
658  /**
659   * {@inheritDoc}
660   */
661  @Override()
662  public void tokenizeComponents(@NotNull final DN value,
663                                 @NotNull final byte[] pepper,
664                                 @NotNull final ByteStringBuffer buffer)
665  {
666    final RDN[] originalRDNs = value.getRDNs();
667    final RDN[] tokenizedRDNs = new RDN[originalRDNs.length];
668    for (int i=0; i < originalRDNs.length; i++)
669    {
670      final RDN rdn = originalRDNs[i];
671      final String[] attributeNames = rdn.getAttributeNames();
672      final String[] tokenizedValues = new String[attributeNames.length];
673      for (int j=0; j < attributeNames.length; j++)
674      {
675        if (shouldRedactOrTokenize(attributeNames[j]))
676        {
677          tokenizedValues[j] =
678               AttributeBasedLogFieldSyntaxHelper.tokenizeValue(this, schema,
679                    attributeNames[j], rdn.getByteArrayAttributeValues()[j],
680                    pepper);
681        }
682        else
683        {
684          tokenizedValues[j] = sanitize(rdn.getAttributeValues()[j]);
685        }
686      }
687
688      tokenizedRDNs[i] = new RDN(attributeNames, tokenizedValues, schema);
689    }
690
691    final DN tokenizedDN = new DN(tokenizedRDNs);
692    tokenizedDN.toString(buffer, DNEscapingStrategy.DEFAULT);
693  }
694
695
696
697  /**
698   * {@inheritDoc}
699   */
700  @Override()
701  public void logTokenizedComponentsFieldToTextFormattedLog(
702                   @NotNull final String fieldName,
703                   @NotNull final DN fieldValue,
704                   @NotNull final byte[] pepper,
705                   @NotNull final ByteStringBuffer buffer)
706  {
707    buffer.append(' ');
708    buffer.append(fieldName);
709    buffer.append("=\"");
710    tokenizeComponents(fieldValue, pepper, buffer);
711    buffer.append('"');
712  }
713
714
715
716  /**
717   * {@inheritDoc}
718   */
719  @Override()
720  public void logTokenizedComponentsFieldToJSONFormattedLog(
721                   @NotNull final String fieldName,
722                   @NotNull final DN fieldValue,
723                   @NotNull final byte[] pepper,
724                   @NotNull final JSONBuffer buffer)
725  {
726    buffer.appendString(fieldName, tokenizeComponents(fieldValue, pepper));
727  }
728
729
730
731  /**
732   * {@inheritDoc}
733   */
734  @Override()
735  public void logTokenizedComponentsValueToJSONFormattedLog(
736                   @NotNull final DN value,
737                   @NotNull final byte[] pepper,
738                   @NotNull final JSONBuffer buffer)
739  {
740    buffer.appendString(tokenizeComponents(value, pepper));
741  }
742}