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