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