001/*
002 * Copyright 2009-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-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) 2009-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.protocol;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.Iterator;
043import java.util.List;
044
045import com.unboundid.asn1.ASN1Buffer;
046import com.unboundid.asn1.ASN1BufferSequence;
047import com.unboundid.asn1.ASN1Element;
048import com.unboundid.asn1.ASN1Enumerated;
049import com.unboundid.asn1.ASN1OctetString;
050import com.unboundid.asn1.ASN1Sequence;
051import com.unboundid.asn1.ASN1StreamReader;
052import com.unboundid.asn1.ASN1StreamReaderSequence;
053import com.unboundid.ldap.sdk.BindResult;
054import com.unboundid.ldap.sdk.Control;
055import com.unboundid.ldap.sdk.LDAPException;
056import com.unboundid.ldap.sdk.LDAPResult;
057import com.unboundid.ldap.sdk.ResultCode;
058import com.unboundid.util.Debug;
059import com.unboundid.util.InternalUseOnly;
060import com.unboundid.util.NotMutable;
061import com.unboundid.util.NotNull;
062import com.unboundid.util.Nullable;
063import com.unboundid.util.StaticUtils;
064import com.unboundid.util.ThreadSafety;
065import com.unboundid.util.ThreadSafetyLevel;
066import com.unboundid.util.Validator;
067
068import static com.unboundid.ldap.protocol.ProtocolMessages.*;
069
070
071
072/**
073 * This class provides an implementation of a bind response protocol op.
074 */
075@InternalUseOnly()
076@NotMutable()
077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078public final class BindResponseProtocolOp
079       implements ProtocolOp
080{
081  /**
082   * The BER type for the server SASL credentials element.
083   */
084  public static final byte TYPE_SERVER_SASL_CREDENTIALS = (byte) 0x87;
085
086
087
088  /**
089   * The serial version UID for this serializable class.
090   */
091  private static final long serialVersionUID = -7757619031268544913L;
092
093
094
095  // The server SASL credentials for this bind response.
096  @Nullable private final ASN1OctetString serverSASLCredentials;
097
098  // The result code for this bind response.
099  private final int resultCode;
100
101  // The referral URLs for this bind response.
102  @NotNull private final List<String> referralURLs;
103
104  // The diagnostic message for this bind response.
105  @Nullable private final String diagnosticMessage;
106
107  // The matched DN for this bind response.
108  @Nullable private final String matchedDN;
109
110
111
112  /**
113   * Creates a new instance of this bind response protocol op with the provided
114   * information.
115   *
116   * @param  resultCode             The result code for this response.
117   * @param  matchedDN              The matched DN for this response, if
118   *                                available.
119   * @param  diagnosticMessage      The diagnostic message for this response, if
120   *                                any.
121   * @param  referralURLs           The list of referral URLs for this response,
122   *                                if any.
123   * @param  serverSASLCredentials  The server SASL credentials for this
124   *                                response, if available.
125   */
126  public BindResponseProtocolOp(final int resultCode,
127              @Nullable final String matchedDN,
128              @Nullable final String diagnosticMessage,
129              @Nullable final List<String> referralURLs,
130              @Nullable final ASN1OctetString serverSASLCredentials)
131  {
132    this.resultCode            = resultCode;
133    this.matchedDN             = matchedDN;
134    this.diagnosticMessage     = diagnosticMessage;
135
136    if (referralURLs == null)
137    {
138      this.referralURLs = Collections.emptyList();
139    }
140    else
141    {
142      this.referralURLs = Collections.unmodifiableList(referralURLs);
143    }
144
145    if (serverSASLCredentials == null)
146    {
147      this.serverSASLCredentials = null;
148    }
149    else
150    {
151      this.serverSASLCredentials = new ASN1OctetString(
152           TYPE_SERVER_SASL_CREDENTIALS, serverSASLCredentials.getValue());
153    }
154  }
155
156
157
158  /**
159   * Creates a new bind response protocol op from the provided bind result
160   * object.
161   *
162   * @param  result  The LDAP result object to use to create this protocol op.
163   */
164  public BindResponseProtocolOp(@NotNull final LDAPResult result)
165  {
166    resultCode            = result.getResultCode().intValue();
167    matchedDN             = result.getMatchedDN();
168    diagnosticMessage     = result.getDiagnosticMessage();
169    referralURLs          = StaticUtils.toList(result.getReferralURLs());
170
171    if (result instanceof BindResult)
172    {
173      final BindResult br = (BindResult) result;
174      serverSASLCredentials = br.getServerSASLCredentials();
175    }
176    else
177    {
178      serverSASLCredentials = null;
179    }
180  }
181
182
183
184  /**
185   * Creates a new bind response protocol op read from the provided ASN.1 stream
186   * reader.
187   *
188   * @param  reader  The ASN.1 stream reader from which to read the bind
189   *                 response.
190   *
191   * @throws  LDAPException  If a problem occurs while reading or parsing the
192   *                         bind response.
193   */
194  BindResponseProtocolOp(@NotNull final ASN1StreamReader reader)
195       throws LDAPException
196  {
197    try
198    {
199      final ASN1StreamReaderSequence opSequence = reader.beginSequence();
200      resultCode = reader.readEnumerated();
201
202      String s = reader.readString();
203      Validator.ensureNotNull(s);
204      if (s.isEmpty())
205      {
206        matchedDN = null;
207      }
208      else
209      {
210        matchedDN = s;
211      }
212
213      s = reader.readString();
214      Validator.ensureNotNull(s);
215      if (s.isEmpty())
216      {
217        diagnosticMessage = null;
218      }
219      else
220      {
221        diagnosticMessage = s;
222      }
223
224      ASN1OctetString creds = null;
225      final ArrayList<String> refs = new ArrayList<>(1);
226      while (opSequence.hasMoreElements())
227      {
228        final byte type = (byte) reader.peek();
229        if (type == GenericResponseProtocolOp.TYPE_REFERRALS)
230        {
231          final ASN1StreamReaderSequence refSequence = reader.beginSequence();
232          while (refSequence.hasMoreElements())
233          {
234            refs.add(reader.readString());
235          }
236        }
237        else if (type == TYPE_SERVER_SASL_CREDENTIALS)
238        {
239          creds = new ASN1OctetString(type, reader.readBytes());
240        }
241        else
242        {
243          throw new LDAPException(ResultCode.DECODING_ERROR,
244               ERR_BIND_RESPONSE_INVALID_ELEMENT.get(StaticUtils.toHex(type)));
245        }
246      }
247
248      referralURLs = Collections.unmodifiableList(refs);
249      serverSASLCredentials = creds;
250    }
251    catch (final LDAPException le)
252    {
253      Debug.debugException(le);
254      throw le;
255    }
256    catch (final Exception e)
257    {
258      Debug.debugException(e);
259      throw new LDAPException(ResultCode.DECODING_ERROR,
260           ERR_BIND_RESPONSE_CANNOT_DECODE.get(
261                StaticUtils.getExceptionMessage(e)),
262           e);
263    }
264  }
265
266
267
268  /**
269   * Retrieves the result code for this bind response.
270   *
271   * @return  The result code for this bind response.
272   */
273  public int getResultCode()
274  {
275    return resultCode;
276  }
277
278
279
280  /**
281   * Retrieves the matched DN for this bind response, if any.
282   *
283   * @return  The matched DN for this bind response, or {@code null} if there is
284   *          no matched DN.
285   */
286  @Nullable()
287  public String getMatchedDN()
288  {
289    return matchedDN;
290  }
291
292
293
294  /**
295   * Retrieves the diagnostic message for this bind response, if any.
296   *
297   * @return  The diagnostic message for this bind response, or {@code null} if
298   *          there is no diagnostic message.
299   */
300  @Nullable()
301  public String getDiagnosticMessage()
302  {
303    return diagnosticMessage;
304  }
305
306
307
308  /**
309   * Retrieves the list of referral URLs for this bind response.
310   *
311   * @return  The list of referral URLs for this bind response, or an empty list
312   *          if there are no referral URLs.
313   */
314  @NotNull()
315  public List<String> getReferralURLs()
316  {
317    return referralURLs;
318  }
319
320
321
322  /**
323   * Retrieves the server SASL credentials for this bind response, if any.
324   *
325   * @return  The server SASL credentials for this bind response, or
326   *          {@code null} if there are no server SASL credentials.
327   */
328  @Nullable()
329  public ASN1OctetString getServerSASLCredentials()
330  {
331    return serverSASLCredentials;
332  }
333
334
335
336  /**
337   * {@inheritDoc}
338   */
339  @Override()
340  public byte getProtocolOpType()
341  {
342    return LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE;
343  }
344
345
346
347  /**
348   * {@inheritDoc}
349   */
350  @Override()
351  @NotNull()
352  public ASN1Element encodeProtocolOp()
353  {
354    final ArrayList<ASN1Element> elements = new ArrayList<>(5);
355    elements.add(new ASN1Enumerated(getResultCode()));
356
357    final String mDN = getMatchedDN();
358    if (mDN == null)
359    {
360      elements.add(new ASN1OctetString());
361    }
362    else
363    {
364      elements.add(new ASN1OctetString(mDN));
365    }
366
367    final String dm = getDiagnosticMessage();
368    if (dm == null)
369    {
370      elements.add(new ASN1OctetString());
371    }
372    else
373    {
374      elements.add(new ASN1OctetString(dm));
375    }
376
377    final List<String> refs = getReferralURLs();
378    if (! refs.isEmpty())
379    {
380      final ArrayList<ASN1Element> refElements = new ArrayList<>(refs.size());
381      for (final String r : refs)
382      {
383        refElements.add(new ASN1OctetString(r));
384      }
385      elements.add(new ASN1Sequence(GenericResponseProtocolOp.TYPE_REFERRALS,
386           refElements));
387    }
388
389    if (serverSASLCredentials != null)
390    {
391      elements.add(serverSASLCredentials);
392    }
393
394    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE,
395         elements);
396  }
397
398
399
400  /**
401   * Decodes the provided ASN.1 element as a bind response protocol op.
402   *
403   * @param  element  The ASN.1 element to be decoded.
404   *
405   * @return  The decoded bind response protocol op.
406   *
407   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
408   *                         a bind response protocol op.
409   */
410  @NotNull()
411  public static BindResponseProtocolOp decodeProtocolOp(
412                                            @NotNull final ASN1Element element)
413         throws LDAPException
414  {
415    try
416    {
417      final ASN1Element[] elements =
418           ASN1Sequence.decodeAsSequence(element).elements();
419      final int resultCode =
420           ASN1Enumerated.decodeAsEnumerated(elements[0]).intValue();
421
422      final String matchedDN;
423      final String md =
424           ASN1OctetString.decodeAsOctetString(elements[1]).stringValue();
425      if (! md.isEmpty())
426      {
427        matchedDN = md;
428      }
429      else
430      {
431        matchedDN = null;
432      }
433
434      final String diagnosticMessage;
435      final String dm =
436           ASN1OctetString.decodeAsOctetString(elements[2]).stringValue();
437      if (! dm.isEmpty())
438      {
439        diagnosticMessage = dm;
440      }
441      else
442      {
443        diagnosticMessage = null;
444      }
445
446      ASN1OctetString serverSASLCredentials = null;
447      List<String> referralURLs = null;
448      if (elements.length > 3)
449      {
450        for (int i=3; i < elements.length; i++)
451        {
452          switch (elements[i].getType())
453          {
454            case GenericResponseProtocolOp.TYPE_REFERRALS:
455              final ASN1Element[] refElements =
456                   ASN1Sequence.decodeAsSequence(elements[3]).elements();
457              referralURLs = new ArrayList<>(refElements.length);
458              for (final ASN1Element e : refElements)
459              {
460                referralURLs.add(
461                     ASN1OctetString.decodeAsOctetString(e).stringValue());
462              }
463              break;
464
465            case TYPE_SERVER_SASL_CREDENTIALS:
466              serverSASLCredentials =
467                   ASN1OctetString.decodeAsOctetString(elements[i]);
468              break;
469
470            default:
471              throw new LDAPException(ResultCode.DECODING_ERROR,
472                   ERR_BIND_RESPONSE_INVALID_ELEMENT.get(
473                        StaticUtils.toHex(elements[i].getType())));
474          }
475        }
476      }
477
478      return new BindResponseProtocolOp(resultCode, matchedDN,
479           diagnosticMessage, referralURLs, serverSASLCredentials);
480    }
481    catch (final LDAPException le)
482    {
483      Debug.debugException(le);
484      throw le;
485    }
486    catch (final Exception e)
487    {
488      Debug.debugException(e);
489      throw new LDAPException(ResultCode.DECODING_ERROR,
490           ERR_BIND_RESPONSE_CANNOT_DECODE.get(
491                StaticUtils.getExceptionMessage(e)),
492           e);
493    }
494  }
495
496
497
498  /**
499   * {@inheritDoc}
500   */
501  @Override()
502  public void writeTo(@NotNull final ASN1Buffer buffer)
503  {
504    final ASN1BufferSequence opSequence =
505         buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE);
506    buffer.addEnumerated(resultCode);
507    buffer.addOctetString(matchedDN);
508    buffer.addOctetString(diagnosticMessage);
509
510    if (! referralURLs.isEmpty())
511    {
512      final ASN1BufferSequence refSequence =
513           buffer.beginSequence(GenericResponseProtocolOp.TYPE_REFERRALS);
514      for (final String s : referralURLs)
515      {
516        buffer.addOctetString(s);
517      }
518      refSequence.end();
519    }
520
521    if (serverSASLCredentials != null)
522    {
523      buffer.addElement(serverSASLCredentials);
524    }
525
526    opSequence.end();
527  }
528
529
530
531  /**
532   * Creates a new LDAP result object from this response protocol op.
533   *
534   * @param  controls  The set of controls to include in the LDAP result.  It
535   *                   may be empty or {@code null} if no controls should be
536   *                   included.
537   *
538   * @return  The LDAP result that was created.
539   */
540  @NotNull()
541  public BindResult toBindResult(@Nullable final Control... controls)
542  {
543    final String[] refs;
544    if (referralURLs.isEmpty())
545    {
546      refs = StaticUtils.NO_STRINGS;
547    }
548    else
549    {
550      refs = new String[referralURLs.size()];
551      referralURLs.toArray(refs);
552    }
553
554    return new BindResult(-1, ResultCode.valueOf(resultCode), diagnosticMessage,
555         matchedDN, refs, controls, serverSASLCredentials);
556  }
557
558
559
560  /**
561   * Retrieves a string representation of this protocol op.
562   *
563   * @return  A string representation of this protocol op.
564   */
565  @Override()
566  @NotNull()
567  public String toString()
568  {
569    final StringBuilder buffer = new StringBuilder();
570    toString(buffer);
571    return buffer.toString();
572  }
573
574
575
576  /**
577   * {@inheritDoc}
578   */
579  @Override()
580  public void toString(@NotNull final StringBuilder buffer)
581  {
582    buffer.append("BindResponseProtocolOp(resultCode=");
583    buffer.append(resultCode);
584
585    if (matchedDN != null)
586    {
587      buffer.append(", matchedDN='");
588      buffer.append(matchedDN);
589      buffer.append('\'');
590    }
591
592    if (diagnosticMessage != null)
593    {
594      buffer.append(", diagnosticMessage='");
595      buffer.append(diagnosticMessage);
596      buffer.append('\'');
597    }
598
599    if (! referralURLs.isEmpty())
600    {
601      buffer.append(", referralURLs={");
602
603      final Iterator<String> iterator = referralURLs.iterator();
604      while (iterator.hasNext())
605      {
606        buffer.append('\'');
607        buffer.append(iterator.next());
608        buffer.append('\'');
609        if (iterator.hasNext())
610        {
611          buffer.append(',');
612        }
613      }
614
615      buffer.append('}');
616    }
617    buffer.append(')');
618  }
619}