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}