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.Date; 041import java.util.GregorianCalendar; 042 043import com.unboundid.util.ByteStringBuffer; 044import com.unboundid.util.Debug; 045import com.unboundid.util.NotNull; 046import com.unboundid.util.StaticUtils; 047import com.unboundid.util.ThreadSafety; 048import com.unboundid.util.ThreadSafetyLevel; 049import com.unboundid.util.json.JSONBuffer; 050 051import static com.unboundid.ldap.sdk.unboundidds.logs.v2.syntax. 052 LogSyntaxMessages.*; 053 054 055 056/** 057 * This class defines a log field syntax for values that are timestamps 058 * represented in the ISO 8601 format described in RFC 3339. This syntax does 059 * not support redacting or tokenizing individual components within the 060 * timestamps. Redacted generalized time values will have a string 061 * representation of "9999-01-01T00:00:00.000Z", which corresponds to midnight 062 * UTC of January 1 in the year 9999. Tokenized values will have a year of 8888 063 * (in the UTC time zone). 064 * <BR> 065 * <BLOCKQUOTE> 066 * <B>NOTE:</B> This class, and other classes within the 067 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 068 * supported for use against Ping Identity, UnboundID, and 069 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 070 * for proprietary functionality or for external specifications that are not 071 * considered stable or mature enough to be guaranteed to work in an 072 * interoperable way with other types of LDAP servers. 073 * </BLOCKQUOTE> 074 */ 075@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 076public final class RFC3339TimestampLogFieldSyntax 077 extends LogFieldSyntax<Date> 078{ 079 /** 080 * The name for this syntax. 081 */ 082 @NotNull public static final String SYNTAX_NAME = "rfc-3339-timestamp"; 083 084 085 086 /** 087 * The string that will be used for completely redacted RFC 3339 timestamp 088 * values. 089 */ 090 @NotNull private static final String REDACTED_RFC_3339_TIMESTAMP_STRING = 091 "9999-01-01T00:00:00.000Z"; 092 093 094 095 /** 096 * The year that will be used for dates that represent tokenized RFC 3339 097 * timestamp values. 098 */ 099 private static final int TOKENIZED_DATE_YEAR = 8888; 100 101 102 103 /** 104 * A singleton instance of this log field syntax. 105 */ 106 @NotNull private static final RFC3339TimestampLogFieldSyntax INSTANCE = 107 new RFC3339TimestampLogFieldSyntax(); 108 109 110 111 /** 112 * Creates a new instance of this log field syntax implementation. 113 */ 114 private RFC3339TimestampLogFieldSyntax() 115 { 116 super(100); 117 } 118 119 120 121 /** 122 * Retrieves a singleton instance of this log field syntax. 123 * 124 * @return A singleton instance of this log field syntax. 125 */ 126 @NotNull() 127 public static RFC3339TimestampLogFieldSyntax getInstance() 128 { 129 return INSTANCE; 130 } 131 132 133 134 /** 135 * {@inheritDoc} 136 */ 137 @Override() 138 @NotNull() 139 public String getSyntaxName() 140 { 141 return SYNTAX_NAME; 142 } 143 144 145 146 /** 147 * {@inheritDoc} 148 */ 149 @Override() 150 public void valueToSanitizedString(@NotNull final Date value, 151 @NotNull final ByteStringBuffer buffer) 152 { 153 buffer.append(StaticUtils.encodeRFC3339Time(value)); 154 } 155 156 157 158 /** 159 * {@inheritDoc} 160 */ 161 @Override() 162 public void logSanitizedFieldToTextFormattedLog( 163 @NotNull final String fieldName, 164 @NotNull final Date fieldValue, 165 @NotNull final ByteStringBuffer buffer) 166 { 167 buffer.append(' '); 168 buffer.append(fieldName); 169 buffer.append("=\""); 170 valueToSanitizedString(fieldValue, buffer); 171 buffer.append('"'); 172 } 173 174 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override() 180 public void logSanitizedFieldToJSONFormattedLog( 181 @NotNull final String fieldName, 182 @NotNull final Date fieldValue, 183 @NotNull final JSONBuffer buffer) 184 { 185 buffer.appendString(fieldName, valueToSanitizedString(fieldValue)); 186 } 187 188 189 190 /** 191 * {@inheritDoc} 192 */ 193 @Override() 194 public void logSanitizedValueToJSONFormattedLog( 195 @NotNull final Date value, 196 @NotNull final JSONBuffer buffer) 197 { 198 buffer.appendString(valueToSanitizedString(value)); 199 } 200 201 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override() 207 @NotNull() 208 public Date parseValue(@NotNull final String valueString) 209 throws RedactedValueException, TokenizedValueException, 210 LogSyntaxException 211 { 212 try 213 { 214 return StaticUtils.decodeRFC3339Time(valueString); 215 } 216 catch (final Exception e) 217 { 218 Debug.debugException(e); 219 if (valueStringIncludesRedactedComponent(valueString)) 220 { 221 throw new RedactedValueException( 222 ERR_RFC_3339_LOG_SYNTAX_CANNOT_PARSE_REDACTED.get(), e); 223 } 224 else if (valueStringIncludesTokenizedComponent(valueString)) 225 { 226 throw new TokenizedValueException( 227 ERR_RFC_3339_LOG_SYNTAX_CANNOT_PARSE_TOKENIZED.get(), e); 228 } 229 else 230 { 231 throw new LogSyntaxException( 232 ERR_RFC_3339_LOG_SYNTAX_CANNOT_PARSE.get(), e); 233 } 234 } 235 } 236 237 238 239 /** 240 * {@inheritDoc} 241 */ 242 @Override() 243 public boolean valueStringIsCompletelyRedacted( 244 @NotNull final String valueString) 245 { 246 return valueString.equals(REDACTED_STRING) || 247 valueString.equals(REDACTED_RFC_3339_TIMESTAMP_STRING); 248 } 249 250 251 252 /** 253 * {@inheritDoc} 254 */ 255 @Override() 256 public void redactEntireValue(@NotNull final ByteStringBuffer buffer) 257 { 258 buffer.append(REDACTED_RFC_3339_TIMESTAMP_STRING); 259 } 260 261 262 263 /** 264 * {@inheritDoc} 265 */ 266 @Override() 267 public boolean completelyRedactedValueConformsToSyntax() 268 { 269 return true; 270 } 271 272 273 274 /** 275 * {@inheritDoc} 276 */ 277 @Override() 278 public void logCompletelyRedactedFieldToTextFormattedLog( 279 @NotNull final String fieldName, 280 @NotNull final ByteStringBuffer buffer) 281 { 282 buffer.append(' '); 283 buffer.append(fieldName); 284 buffer.append("=\""); 285 buffer.append(REDACTED_RFC_3339_TIMESTAMP_STRING); 286 buffer.append('"'); 287 } 288 289 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override() 295 public void logCompletelyRedactedFieldToJSONFormattedLog( 296 @NotNull final String fieldName, 297 @NotNull final JSONBuffer buffer) 298 { 299 buffer.appendString(fieldName, REDACTED_RFC_3339_TIMESTAMP_STRING); 300 } 301 302 303 304 /** 305 * {@inheritDoc} 306 */ 307 @Override() 308 public void logCompletelyRedactedValueToJSONFormattedLog( 309 @NotNull final JSONBuffer buffer) 310 { 311 buffer.appendString(REDACTED_RFC_3339_TIMESTAMP_STRING); 312 } 313 314 315 316 /** 317 * {@inheritDoc} 318 */ 319 @Override() 320 public boolean supportsRedactedComponents() 321 { 322 return false; 323 } 324 325 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override() 331 public boolean valueStringIncludesRedactedComponent( 332 @NotNull final String valueString) 333 { 334 return valueStringIsCompletelyRedacted(valueString); 335 } 336 337 338 339 /** 340 * {@inheritDoc} 341 */ 342 @Override() 343 public boolean valueWithRedactedComponentsConformsToSyntax() 344 { 345 return true; 346 } 347 348 349 350 /** 351 * {@inheritDoc} 352 */ 353 @Override() 354 public void logRedactedComponentsFieldToTextFormattedLog( 355 @NotNull final String fieldName, 356 @NotNull final Date fieldValue, 357 @NotNull final ByteStringBuffer buffer) 358 { 359 logCompletelyRedactedFieldToTextFormattedLog(fieldName, buffer); 360 } 361 362 363 364 /** 365 * {@inheritDoc} 366 */ 367 @Override() 368 public void logRedactedComponentsFieldToJSONFormattedLog( 369 @NotNull final String fieldName, 370 @NotNull final Date fieldValue, 371 @NotNull final JSONBuffer buffer) 372 { 373 logCompletelyRedactedFieldToJSONFormattedLog(fieldName, buffer); 374 } 375 376 377 378 /** 379 * {@inheritDoc} 380 */ 381 @Override() 382 public void logRedactedComponentsValueToJSONFormattedLog( 383 @NotNull final Date value, 384 @NotNull final JSONBuffer buffer) 385 { 386 logCompletelyRedactedValueToJSONFormattedLog(buffer); 387 } 388 389 390 391 /** 392 * {@inheritDoc} 393 */ 394 @Override() 395 public boolean valueStringIsCompletelyTokenized( 396 @NotNull final String valueString) 397 { 398 if (super.valueStringIsCompletelyTokenized(valueString)) 399 { 400 return true; 401 } 402 403 return valueString.startsWith("8888-"); 404 } 405 406 407 408 /** 409 * {@inheritDoc} 410 */ 411 @Override() 412 public boolean completelyTokenizedValueConformsToSyntax() 413 { 414 return true; 415 } 416 417 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override() 423 public void tokenizeEntireValue(@NotNull final Date value, 424 @NotNull final byte[] pepper, 425 @NotNull final ByteStringBuffer buffer) 426 { 427 // Concatenate the long value of the provided date and the pepper, and 428 // generate a SHA-256 digest from the result. 429 final byte[] tokenDigest; 430 final ByteStringBuffer tempBuffer = getTemporaryBuffer(); 431 try 432 { 433 tempBuffer.append(value.getTime()); 434 tempBuffer.append(pepper); 435 tokenDigest = sha256(tempBuffer); 436 } 437 finally 438 { 439 releaseTemporaryBuffer(tempBuffer); 440 } 441 442 443 // Generate a long value from the first eight digits of the digest. 444 long tokenizedTime = 0L; 445 for (int i=0; i < 8; i++) 446 { 447 tokenizedTime <<= 8; 448 tokenizedTime |= (tokenDigest[i] & 0xFFL); 449 } 450 451 452 // Create a Gregorian calendar in the UTC time zone, seed it with the 453 // tokenized time, and set the year to 8888. 454 final GregorianCalendar tokenCalendar = 455 new GregorianCalendar(StaticUtils.getUTCTimeZone()); 456 tokenCalendar.setTimeInMillis(tokenizedTime); 457 tokenCalendar.set(GregorianCalendar.YEAR, TOKENIZED_DATE_YEAR); 458 459 460 // Append an RFC 3339 timestamp representation of the calendar value to the 461 // provided buffer. 462 buffer.append(StaticUtils.encodeRFC3339Time(tokenCalendar.getTime())); 463 } 464 465 466 467 /** 468 * {@inheritDoc} 469 */ 470 @Override() 471 public void logCompletelyTokenizedFieldToTextFormattedLog( 472 @NotNull final String fieldName, 473 @NotNull final Date fieldValue, 474 @NotNull final byte[] pepper, 475 @NotNull final ByteStringBuffer buffer) 476 { 477 buffer.append(' '); 478 buffer.append(fieldName); 479 buffer.append("=\""); 480 tokenizeEntireValue(fieldValue, pepper, buffer); 481 buffer.append('"'); 482 } 483 484 485 486 /** 487 * {@inheritDoc} 488 */ 489 @Override() 490 public void logCompletelyTokenizedFieldToJSONFormattedLog( 491 @NotNull final String fieldName, 492 @NotNull final Date fieldValue, 493 @NotNull final byte[] pepper, 494 @NotNull final JSONBuffer buffer) 495 { 496 buffer.appendString(fieldName, tokenizeEntireValue(fieldValue, pepper)); 497 } 498 499 500 501 /** 502 * {@inheritDoc} 503 */ 504 @Override() 505 public void logCompletelyTokenizedValueToJSONFormattedLog( 506 @NotNull final Date value, 507 @NotNull final byte[] pepper, 508 @NotNull final JSONBuffer buffer) 509 { 510 buffer.appendString(tokenizeEntireValue(value, pepper)); 511 } 512 513 514 515 /** 516 * {@inheritDoc} 517 */ 518 @Override() 519 public boolean supportsTokenizedComponents() 520 { 521 return false; 522 } 523 524 525 526 /** 527 * {@inheritDoc} 528 */ 529 @Override() 530 public boolean valueStringIncludesTokenizedComponent( 531 @NotNull final String valueString) 532 { 533 return valueStringIsCompletelyTokenized(valueString); 534 } 535 536 537 538 /** 539 * {@inheritDoc} 540 */ 541 @Override() 542 public boolean valueWithTokenizedComponentsConformsToSyntax() 543 { 544 return true; 545 } 546 547 548 549 /** 550 * {@inheritDoc} 551 */ 552 @Override() 553 public void logTokenizedComponentsFieldToTextFormattedLog( 554 @NotNull final String fieldName, 555 @NotNull final Date fieldValue, 556 @NotNull final byte[] pepper, 557 @NotNull final ByteStringBuffer buffer) 558 { 559 logCompletelyTokenizedFieldToTextFormattedLog(fieldName, fieldValue, pepper, 560 buffer); 561 } 562 563 564 565 /** 566 * {@inheritDoc} 567 */ 568 @Override() 569 public void logTokenizedComponentsFieldToJSONFormattedLog( 570 @NotNull final String fieldName, 571 @NotNull final Date fieldValue, 572 @NotNull final byte[] pepper, 573 @NotNull final JSONBuffer buffer) 574 { 575 logCompletelyTokenizedFieldToJSONFormattedLog(fieldName, fieldValue, pepper, 576 buffer); 577 } 578 579 580 581 /** 582 * {@inheritDoc} 583 */ 584 @Override() 585 public void logTokenizedComponentsValueToJSONFormattedLog( 586 @NotNull final Date value, 587 @NotNull final byte[] pepper, 588 @NotNull final JSONBuffer buffer) 589 { 590 logCompletelyTokenizedValueToJSONFormattedLog(value, pepper, buffer); 591 } 592}