001/*
002 * Copyright 2016-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-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) 2016-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.experimental;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.Date;
043import java.util.List;
044
045import com.unboundid.asn1.ASN1Sequence;
046import com.unboundid.ldap.sdk.Control;
047import com.unboundid.ldap.sdk.Entry;
048import com.unboundid.ldap.sdk.LDAPException;
049import com.unboundid.ldap.sdk.LDAPResult;
050import com.unboundid.ldap.sdk.OperationType;
051import com.unboundid.ldap.sdk.ReadOnlyEntry;
052import com.unboundid.ldap.sdk.ResultCode;
053import com.unboundid.util.Debug;
054import com.unboundid.util.NotExtensible;
055import com.unboundid.util.NotNull;
056import com.unboundid.util.Nullable;
057import com.unboundid.util.StaticUtils;
058import com.unboundid.util.ThreadSafety;
059import com.unboundid.util.ThreadSafetyLevel;
060
061import static com.unboundid.ldap.sdk.experimental.ExperimentalMessages.*;
062
063
064
065/**
066 * This class serves as the base class for entries that hold information about
067 * operations processed by an LDAP server, much like LDAP-accessible access log
068 * messages.  The format for the entries used in this implementation is
069 * described in draft-chu-ldap-logschema-00.
070 */
071@NotExtensible()
072@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
073public abstract class DraftChuLDAPLogSchema00Entry
074       extends ReadOnlyEntry
075{
076  /**
077   * The name of the attribute used to hold the DN of the authorization identity
078   * for the operation.
079   */
080  @NotNull public static final String ATTR_AUTHORIZATION_IDENTITY_DN =
081       "reqAuthzID";
082
083
084
085  /**
086   * The name of the attribute used to hold the diagnostic message the server
087   * included in the response to the client.
088   */
089  @NotNull public static final String ATTR_DIAGNOSTIC_MESSAGE = "reqMessage";
090
091
092
093  /**
094   * The name of the attribute used to hold the type of operation that was
095   * processed.  For extended operation, the value will be
096   * "extended" followed by the OID of the extended request (e.g.,
097   * "extended1.3.6.1.4.1.1466.20037" to indicate the StartTLS extended
098   * request).  For all other operation types, this will be simply the name of
099   * the operation:  abandon, add, bind, compare, delete, modify, modrdn,
100   * search, or unbind.
101   */
102  @NotNull public static final String ATTR_OPERATION_TYPE = "reqType";
103
104
105
106  /**
107   * The name of the attribute used to hold the time the server completed
108   * processing the operation.  Values will be in generalized time format, but
109   * may be of a very high precision to ensure that each log entry has a
110   * unique end time.
111   */
112  @NotNull public static final String ATTR_PROCESSING_END_TIME = "reqEnd";
113
114
115
116  /**
117   * The name of the attribute used to hold the time the server started
118   * processing the operation.  Values will be in generalized time format, but
119   * may be of a very high precision to ensure that each log entry has a
120   * unique start time.
121   */
122  @NotNull public static final String ATTR_PROCESSING_START_TIME = "reqStart";
123
124
125
126  /**
127   * The name of the attribute used to hold a referral URL the server included
128   * in the response to the client.
129   */
130  @NotNull public static final String ATTR_REFERRAL_URL = "reqReferral";
131
132
133
134  /**
135   * The name of the attribute used to hold information about a request control
136   * included in the request received from the client.
137   */
138  @NotNull public static final String ATTR_REQUEST_CONTROL = "reqControls";
139
140
141
142  /**
143   * The name of the attribute used to hold information about a response control
144   * included in the result returned to the client.
145   */
146  @NotNull public static final String ATTR_RESPONSE_CONTROL = "reqRespControls";
147
148
149
150  /**
151   * The name of the attribute used to hold the integer value of the result code
152   * the server included in the response to the client.
153   */
154  @NotNull public static final String ATTR_RESULT_CODE = "reqResult";
155
156
157
158  /**
159   * The name of the attribute used to hold a session identifier for a sequence
160   * of operations received on the same connection.
161   */
162  @NotNull public static final String ATTR_SESSION_ID = "reqSession";
163
164
165
166  /**
167   * The name of the attribute used to hold the DN of the entry targeted by the
168   * operation.  For a search operation, this will be the search base DN.
169   */
170  @NotNull public static final String ATTR_TARGET_ENTRY_DN = "reqDN";
171
172
173
174  /**
175   * The serial version UID for this serializable class.
176   */
177  private static final long serialVersionUID = -7279669732772403236L;
178
179
180
181  // The parsed processing end time for the operation.
182  @Nullable private final Date processingEndTimeDate;
183
184  // The parsed processing start time for the operation.
185  @NotNull private final Date processingStartTimeDate;
186
187  // A list of controls included in the request from the client.
188  @NotNull private final List<Control> requestControls;
189
190  // A list of controls included in the request from the client.
191  @NotNull private final List<Control> responseControls;
192
193  // A list of referral URLs returned to the client.
194  @NotNull private final List<String> referralURLs;
195
196  // The operation type for the log entry.
197  @NotNull private final OperationType operationType;
198
199  // The result code returned to the client.
200  @Nullable private final ResultCode resultCode;
201
202  // The DN of the account used as the authorization identity for the operation.
203  @Nullable private final String authorizationIdentityDN;
204
205  // The diagnostic message returned to the client.
206  @Nullable private final String diagnosticMessage;
207
208  // The string representation of the processing end time for the operation.
209  @Nullable private final String processingEndTimeString;
210
211  // The string representation of the processing start time for the operation.
212  @NotNull private final String processingStartTimeString;
213
214  // The session ID for the sequence of operations received on the same
215  // connection.
216  @NotNull private final String sessionID;
217
218  // The DN of the entry targeted by the client.
219  @Nullable private final String targetEntryDN;
220
221
222
223  /**
224   * Creates a new instance of this access log entry from the provided entry.
225   *
226   * @param  entry          The entry used to create this access log entry.
227   * @param  operationType  The associated operation type.
228   *
229   * @throws  LDAPException  If the provided entry cannot be decoded as a valid
230   *                         access log entry as per the specification contained
231   *                         in draft-chu-ldap-logschema-00.
232   */
233  DraftChuLDAPLogSchema00Entry(@NotNull final Entry entry,
234                               @NotNull final OperationType operationType)
235       throws LDAPException
236  {
237    super(entry);
238
239    this.operationType = operationType;
240
241
242    // Get the processing start time.
243    processingStartTimeString =
244         entry.getAttributeValue(ATTR_PROCESSING_START_TIME);
245    if (processingStartTimeString == null)
246    {
247      throw new LDAPException(ResultCode.DECODING_ERROR,
248           ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(),
249                ATTR_PROCESSING_START_TIME));
250    }
251    else
252    {
253      try
254      {
255        processingStartTimeDate =
256             StaticUtils.decodeGeneralizedTime(processingStartTimeString);
257      }
258      catch (final Exception e)
259      {
260        Debug.debugException(e);
261        throw new LDAPException(ResultCode.DECODING_ERROR,
262             ERR_LOGSCHEMA_DECODE_CANNOT_DECODE_TIME.get(entry.getDN(),
263                  ATTR_PROCESSING_START_TIME, processingStartTimeString),
264             e);
265      }
266    }
267
268
269    // Get the processing end time.
270    processingEndTimeString =
271         entry.getAttributeValue(ATTR_PROCESSING_END_TIME);
272    if (processingEndTimeString == null)
273    {
274      processingEndTimeDate = null;
275    }
276    else
277    {
278      try
279      {
280        processingEndTimeDate =
281             StaticUtils.decodeGeneralizedTime(processingEndTimeString);
282      }
283      catch (final Exception e)
284      {
285        Debug.debugException(e);
286        throw new LDAPException(ResultCode.DECODING_ERROR,
287             ERR_LOGSCHEMA_DECODE_CANNOT_DECODE_TIME.get(entry.getDN(),
288                  ATTR_PROCESSING_END_TIME, processingEndTimeString),
289             e);
290      }
291    }
292
293
294    // Get the session ID.
295    sessionID = entry.getAttributeValue(ATTR_SESSION_ID);
296    if (sessionID == null)
297    {
298      throw new LDAPException(ResultCode.DECODING_ERROR,
299           ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(),
300                ATTR_SESSION_ID));
301    }
302
303
304    // Get the target DN.  It can only be null for abandon, extended, and unbind
305    // operation types.
306    targetEntryDN = entry.getAttributeValue(ATTR_TARGET_ENTRY_DN);
307    if (targetEntryDN == null)
308    {
309      if (! ((operationType == OperationType.ABANDON) ||
310             (operationType == OperationType.EXTENDED) ||
311             (operationType == OperationType.UNBIND)))
312      {
313        throw new LDAPException(ResultCode.DECODING_ERROR,
314             ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(),
315                  ATTR_TARGET_ENTRY_DN));
316      }
317    }
318
319
320    // Get the authorization identity.
321    authorizationIdentityDN =
322         entry.getAttributeValue(ATTR_AUTHORIZATION_IDENTITY_DN);
323
324
325    // Get the set of request controls, if any.
326    requestControls = decodeControls(entry, ATTR_REQUEST_CONTROL);
327
328
329    // Get the set of response controls, if any.
330    responseControls = decodeControls(entry, ATTR_RESPONSE_CONTROL);
331
332
333    // Get the result code, if any.
334    final String resultCodeString = entry.getAttributeValue(ATTR_RESULT_CODE);
335    if (resultCodeString == null)
336    {
337      resultCode = null;
338    }
339    else
340    {
341      try
342      {
343        resultCode = ResultCode.valueOf(Integer.parseInt(resultCodeString));
344      }
345      catch (final Exception e)
346      {
347        Debug.debugException(e);
348        throw new LDAPException(ResultCode.DECODING_ERROR,
349             ERR_LOGSCHEMA_DECODE_RESULT_CODE_ERROR.get(entry.getDN(),
350                  resultCodeString, ATTR_RESULT_CODE),
351             e);
352      }
353    }
354
355
356    // Get the diagnostic message, if any.
357    diagnosticMessage = entry.getAttributeValue(ATTR_DIAGNOSTIC_MESSAGE);
358
359
360    // Get the referral URLs, if any.
361    final String[] referralArray = entry.getAttributeValues(ATTR_REFERRAL_URL);
362    if (referralArray == null)
363    {
364      referralURLs = Collections.emptyList();
365    }
366    else
367    {
368      referralURLs =
369           Collections.unmodifiableList(StaticUtils.toList(referralArray));
370    }
371  }
372
373
374
375  /**
376   * Decodes a set of controls contained in the specified attribute of the
377   * provided entry.
378   *
379   * @param  entry          The entry containing the controls to decode.
380   * @param  attributeName  The name of the attribute expected to hold the set
381   *                        of controls to decode.
382   *
383   * @return  The decoded controls, or an empty list if the provided entry did
384   *          not include any controls in the specified attribute.
385   *
386   * @throws  LDAPException  If a problem is encountered while trying to decode
387   *                         the controls.
388   */
389  @NotNull()
390  private static List<Control> decodeControls(@NotNull final Entry entry,
391                                    @NotNull final String attributeName)
392          throws LDAPException
393  {
394    final byte[][] values = entry.getAttributeValueByteArrays(attributeName);
395    if ((values == null) || (values.length == 0))
396    {
397      return Collections.emptyList();
398    }
399
400    final ArrayList<Control> controls = new ArrayList<>(values.length);
401    for (final byte[] controlBytes : values)
402    {
403      try
404      {
405        controls.add(Control.decode(ASN1Sequence.decodeAsSequence(
406             controlBytes)));
407      }
408      catch (final Exception e)
409      {
410        Debug.debugException(e);
411        throw new LDAPException(ResultCode.DECODING_ERROR,
412             ERR_LOGSCHEMA_DECODE_CONTROL_ERROR.get(entry.getDN(),
413                  attributeName, StaticUtils.getExceptionMessage(e)),
414             e);
415      }
416    }
417
418    return Collections.unmodifiableList(controls);
419  }
420
421
422
423  /**
424   * Retrieves the type of operation represented by this access log entry.
425   *
426   * @return  The type of operation represented by this access log entry.
427   */
428  @NotNull()
429  public final OperationType getOperationType()
430  {
431    return operationType;
432  }
433
434
435
436  /**
437   * Retrieves the DN of the entry targeted by by the operation represented by
438   * this access log entry, if available.  Some types of operations, like
439   * abandon and extended operations, will not have a target entry DN.  For a
440   * search operation, this will be the base DN for the search request.  For a
441   * modify DN operation, this will be the DN of the entry before any processing
442   * was performed.
443   *
444   * @return  The DN of the entry targeted by the operation represented by this
445   *          access log entry, or {@code null} if no DN is available.
446   */
447  @Nullable()
448  public final String getTargetEntryDN()
449  {
450    return targetEntryDN;
451  }
452
453
454
455  /**
456   * Retrieves the string representation of the time that the server started
457   * processing the operation represented by this access log entry.  Note that
458   * the string representation of this start time may have a different precision
459   * than the parsed start time returned by the
460   * {@link #getProcessingStartTimeDate()} method.
461   *
462   * @return  The string representation of the time that the server started
463   *          processing the operation represented by this access log entry.
464   */
465  @NotNull()
466  public final String getProcessingStartTimeString()
467  {
468    return processingStartTimeString;
469  }
470
471
472
473  /**
474   * Retrieves a parsed representation of the time that the server started
475   * processing the operation represented by this access log entry.  Note that
476   * this parsed representation may have a different precision than the start
477   * time string returned by the {@link #getProcessingStartTimeString()} method.
478   *
479   * @return  A parsed representation of the time that the server started
480   *          processing the operation represented by this access log entry.
481   */
482  @NotNull()
483  public final Date getProcessingStartTimeDate()
484  {
485    return processingStartTimeDate;
486  }
487
488
489
490  /**
491   * Retrieves the string representation of the time that the server completed
492   * processing the operation represented by this access log entry, if
493   * available.  Note that the string representation of this end time may have a
494   * different precision than the parsed end time returned by the
495   * {@link #getProcessingEndTimeDate()} method.
496   *
497   * @return  The string representation of the time that the server completed
498   *          processing the operation represented by this access log entry, or
499   *          {@code null} if no end time is available.
500   */
501  @Nullable()
502  public final String getProcessingEndTimeString()
503  {
504    return processingEndTimeString;
505  }
506
507
508
509  /**
510   * Retrieves a parsed representation of the time that the server completed
511   * processing the operation represented by this access log entry, if
512   * available.  Note that this parsed representation may have a different
513   * precision than the end time string returned by the
514   * {@link #getProcessingEndTimeString()} method.
515   *
516   * @return  A parsed representation of the time that the server completed
517   *          processing the operation represented by this access log entry.
518   */
519  @Nullable()
520  public final Date getProcessingEndTimeDate()
521  {
522    return processingEndTimeDate;
523  }
524
525
526
527  /**
528   * Retrieves the session identifier that the server assigned to the operation
529   * represented by this access log entry and can be used to correlate that
530   * operation with other operations requested on the same client connection.
531   * The server will assign a unique session identifier to each client
532   * connection, and all requests received on that connection will share the
533   * same session ID.
534   *
535   * @return  The session identifier that the server assigned to the operation
536   *          represented by this access log entry.
537   */
538  @NotNull()
539  public final String getSessionID()
540  {
541    return sessionID;
542  }
543
544
545
546  /**
547   * Retrieves a list of the request controls for the operation represented by
548   * this access log entry, if any.
549   *
550   * @return  A list of the request controls for the operation represented by
551   *          this access log entry, or an empty list if there were no request
552   *          controls included in the access log entry.
553   */
554  @NotNull()
555  public final List<Control> getRequestControls()
556  {
557    return requestControls;
558  }
559
560
561
562  /**
563   * Retrieves the set of request controls as an array rather than a list.  This
564   * is a convenience method for subclasses that need to create LDAP requests
565   * whose constructors need an array of controls rather than a list.
566   *
567   * @return  The set of request controls as an array rather than a list.
568   */
569  @NotNull()
570  final Control[] getRequestControlArray()
571  {
572    return requestControls.toArray(StaticUtils.NO_CONTROLS);
573  }
574
575
576
577  /**
578   * Retrieves the result code for the operation represented by this access log
579   * entry, if any.
580   *
581   * @return  The result code for the operation represented by this access log
582   *          entry, or {@code null} if no result code was included in the
583   *          access log entry.
584   */
585  @Nullable()
586  public final ResultCode getResultCode()
587  {
588    return resultCode;
589  }
590
591
592
593  /**
594   * Retrieves the diagnostic message for the operation represented by this
595   * access log entry, if any.
596   *
597   * @return  The diagnostic message for the operation represented by this
598   *          access log entry, or {@code null} if no result code was included
599   *          in the access log entry.
600   */
601  @Nullable()
602  public final String getDiagnosticMessage()
603  {
604    return diagnosticMessage;
605  }
606
607
608
609  /**
610   * Retrieves the list of referral URLs for the operation represented by this
611   * access log entry, if any.
612   *
613   * @return  The list of referral URLs for the operation represented by this
614   *          access log entry, or an empty list if no referral URLs were
615   *          included in the access log entry.
616   */
617  @NotNull()
618  public final List<String> getReferralURLs()
619  {
620    return referralURLs;
621  }
622
623
624
625  /**
626   * Retrieves a list of the response controls for the operation represented by
627   * this access log entry, if any.
628   *
629   * @return  A list of the response controls for the operation represented by
630   *          this access log entry, or an empty list if there were no response
631   *          controls included in the access log entry.
632   */
633  @NotNull()
634  public final List<Control> getResponseControls()
635  {
636    return responseControls;
637  }
638
639
640
641  /**
642   * Retrieves the DN of the account that served as the authorization identity
643   * for the operation represented by this access log entry, if any.
644   *
645   * @return  The DN of the account that served as the authorization identity
646   *          for the operation represented by this access log entry, or
647   *          {@code null} if the authorization identity is not available.
648   */
649  @Nullable()
650  public final String getAuthorizationIdentityDN()
651  {
652    return authorizationIdentityDN;
653  }
654
655
656
657  /**
658   * Retrieves an {@code LDAPResult} object that represents the server response
659   * described by this access log entry, if any.  Note that for some types of
660   * operations, like abandon and unbind operations, the server will not return
661   * a result to the client.
662   *
663   * @return  An {@code LDAPResult} object that represents the server response
664   *          described by this access log entry, or {@code null} if no response
665   *          information is available.
666   */
667  @Nullable()
668  public final LDAPResult toLDAPResult()
669  {
670    if (resultCode == null)
671    {
672      return null;
673    }
674
675    return new LDAPResult(-1, resultCode, diagnosticMessage, null, referralURLs,
676         responseControls);
677  }
678
679
680
681  /**
682   * Decodes the provided entry as an access log entry of the appropriate type.
683   *
684   * @param  entry  The entry to decode as an access log entry.  It must not be
685   *                {@code null}.
686   *
687   * @return  The decoded access log entry.
688   *
689   * @throws  LDAPException  If the provided entry cannot be decoded as a valid
690   *                         access log entry as per the specification contained
691   *                         in draft-chu-ldap-logschema-00.
692   */
693  @NotNull()
694  public static DraftChuLDAPLogSchema00Entry decode(@NotNull final Entry entry)
695         throws LDAPException
696  {
697    final String opType = entry.getAttributeValue(ATTR_OPERATION_TYPE);
698    if (opType == null)
699    {
700      throw new LDAPException(ResultCode.DECODING_ERROR,
701           ERR_LOGSCHEMA_DECODE_NO_OP_TYPE.get(entry.getDN(),
702                ATTR_OPERATION_TYPE));
703    }
704
705    final String lowerOpType = StaticUtils.toLowerCase(opType);
706    if (lowerOpType.equals("abandon"))
707    {
708      return new DraftChuLDAPLogSchema00AbandonEntry(entry);
709    }
710    else if (lowerOpType.equals("add"))
711    {
712      return new DraftChuLDAPLogSchema00AddEntry(entry);
713    }
714    else if (lowerOpType.equals("bind"))
715    {
716      return new DraftChuLDAPLogSchema00BindEntry(entry);
717    }
718    else if (lowerOpType.equals("compare"))
719    {
720      return new DraftChuLDAPLogSchema00CompareEntry(entry);
721    }
722    else if (lowerOpType.equals("delete"))
723    {
724      return new DraftChuLDAPLogSchema00DeleteEntry(entry);
725    }
726    else if (lowerOpType.startsWith("extended"))
727    {
728      return new DraftChuLDAPLogSchema00ExtendedEntry(entry);
729    }
730    else if (lowerOpType.equals("modify"))
731    {
732      return new DraftChuLDAPLogSchema00ModifyEntry(entry);
733    }
734    else if (lowerOpType.equals("modrdn"))
735    {
736      return new DraftChuLDAPLogSchema00ModifyDNEntry(entry);
737    }
738    else if (lowerOpType.equals("search"))
739    {
740      return new DraftChuLDAPLogSchema00SearchEntry(entry);
741    }
742    else if (lowerOpType.equals("unbind"))
743    {
744      return new DraftChuLDAPLogSchema00UnbindEntry(entry);
745    }
746    else
747    {
748      throw new LDAPException(ResultCode.DECODING_ERROR,
749           ERR_LOGSCHEMA_DECODE_UNRECOGNIZED_OP_TYPE.get(
750                entry.getDN(), ATTR_OPERATION_TYPE, opType));
751    }
752  }
753}