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.Debug;
044import com.unboundid.util.InternalUseOnly;
045import com.unboundid.util.NotMutable;
046import com.unboundid.util.StaticUtils;
047import com.unboundid.util.ThreadSafety;
048import com.unboundid.util.ThreadSafetyLevel;
049import com.unboundid.util.Validator;
050
051import static com.unboundid.ldap.protocol.ProtocolMessages.*;
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          = StaticUtils.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      Validator.ensureNotNull(s);
186      if (s.isEmpty())
187      {
188        matchedDN = null;
189      }
190      else
191      {
192        matchedDN = s;
193      }
194
195      s = reader.readString();
196      Validator.ensureNotNull(s);
197      if (s.isEmpty())
198      {
199        diagnosticMessage = null;
200      }
201      else
202      {
203        diagnosticMessage = s;
204      }
205
206      ASN1OctetString creds = null;
207      final ArrayList<String> refs = new ArrayList<>(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(StaticUtils.toHex(type)));
227        }
228      }
229
230      referralURLs = Collections.unmodifiableList(refs);
231      serverSASLCredentials = creds;
232    }
233    catch (final LDAPException le)
234    {
235      Debug.debugException(le);
236      throw le;
237    }
238    catch (final Exception e)
239    {
240      Debug.debugException(e);
241      throw new LDAPException(ResultCode.DECODING_ERROR,
242           ERR_BIND_RESPONSE_CANNOT_DECODE.get(
243                StaticUtils.getExceptionMessage(e)),
244           e);
245    }
246  }
247
248
249
250  /**
251   * Retrieves the result code for this bind response.
252   *
253   * @return  The result code for this bind response.
254   */
255  public int getResultCode()
256  {
257    return resultCode;
258  }
259
260
261
262  /**
263   * Retrieves the matched DN for this bind response, if any.
264   *
265   * @return  The matched DN for this bind response, or {@code null} if there is
266   *          no matched DN.
267   */
268  public String getMatchedDN()
269  {
270    return matchedDN;
271  }
272
273
274
275  /**
276   * Retrieves the diagnostic message for this bind response, if any.
277   *
278   * @return  The diagnostic message for this bind response, or {@code null} if
279   *          there is no diagnostic message.
280   */
281  public String getDiagnosticMessage()
282  {
283    return diagnosticMessage;
284  }
285
286
287
288  /**
289   * Retrieves the list of referral URLs for this bind response.
290   *
291   * @return  The list of referral URLs for this bind response, or an empty list
292   *          if there are no referral URLs.
293   */
294  public List<String> getReferralURLs()
295  {
296    return referralURLs;
297  }
298
299
300
301  /**
302   * Retrieves the server SASL credentials for this bind response, if any.
303   *
304   * @return  The server SASL credentials for this bind response, or
305   *          {@code null} if there are no server SASL credentials.
306   */
307  public ASN1OctetString getServerSASLCredentials()
308  {
309    return serverSASLCredentials;
310  }
311
312
313
314  /**
315   * {@inheritDoc}
316   */
317  @Override()
318  public byte getProtocolOpType()
319  {
320    return LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE;
321  }
322
323
324
325  /**
326   * {@inheritDoc}
327   */
328  @Override()
329  public ASN1Element encodeProtocolOp()
330  {
331    final ArrayList<ASN1Element> elements = new ArrayList<>(5);
332    elements.add(new ASN1Enumerated(getResultCode()));
333
334    final String mDN = getMatchedDN();
335    if (mDN == null)
336    {
337      elements.add(new ASN1OctetString());
338    }
339    else
340    {
341      elements.add(new ASN1OctetString(mDN));
342    }
343
344    final String dm = getDiagnosticMessage();
345    if (dm == null)
346    {
347      elements.add(new ASN1OctetString());
348    }
349    else
350    {
351      elements.add(new ASN1OctetString(dm));
352    }
353
354    final List<String> refs = getReferralURLs();
355    if (! refs.isEmpty())
356    {
357      final ArrayList<ASN1Element> refElements = new ArrayList<>(refs.size());
358      for (final String r : refs)
359      {
360        refElements.add(new ASN1OctetString(r));
361      }
362      elements.add(new ASN1Sequence(GenericResponseProtocolOp.TYPE_REFERRALS,
363           refElements));
364    }
365
366    if (serverSASLCredentials != null)
367    {
368      elements.add(serverSASLCredentials);
369    }
370
371    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE,
372         elements);
373  }
374
375
376
377  /**
378   * Decodes the provided ASN.1 element as a bind response protocol op.
379   *
380   * @param  element  The ASN.1 element to be decoded.
381   *
382   * @return  The decoded bind response protocol op.
383   *
384   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
385   *                         a bind response protocol op.
386   */
387  public static BindResponseProtocolOp decodeProtocolOp(
388                                            final ASN1Element element)
389         throws LDAPException
390  {
391    try
392    {
393      final ASN1Element[] elements =
394           ASN1Sequence.decodeAsSequence(element).elements();
395      final int resultCode =
396           ASN1Enumerated.decodeAsEnumerated(elements[0]).intValue();
397
398      final String matchedDN;
399      final String md =
400           ASN1OctetString.decodeAsOctetString(elements[1]).stringValue();
401      if (! md.isEmpty())
402      {
403        matchedDN = md;
404      }
405      else
406      {
407        matchedDN = null;
408      }
409
410      final String diagnosticMessage;
411      final String dm =
412           ASN1OctetString.decodeAsOctetString(elements[2]).stringValue();
413      if (! dm.isEmpty())
414      {
415        diagnosticMessage = dm;
416      }
417      else
418      {
419        diagnosticMessage = null;
420      }
421
422      ASN1OctetString serverSASLCredentials = null;
423      List<String> referralURLs = null;
424      if (elements.length > 3)
425      {
426        for (int i=3; i < elements.length; i++)
427        {
428          switch (elements[i].getType())
429          {
430            case GenericResponseProtocolOp.TYPE_REFERRALS:
431              final ASN1Element[] refElements =
432                   ASN1Sequence.decodeAsSequence(elements[3]).elements();
433              referralURLs = new ArrayList<>(refElements.length);
434              for (final ASN1Element e : refElements)
435              {
436                referralURLs.add(
437                     ASN1OctetString.decodeAsOctetString(e).stringValue());
438              }
439              break;
440
441            case TYPE_SERVER_SASL_CREDENTIALS:
442              serverSASLCredentials =
443                   ASN1OctetString.decodeAsOctetString(elements[i]);
444              break;
445
446            default:
447              throw new LDAPException(ResultCode.DECODING_ERROR,
448                   ERR_BIND_RESPONSE_INVALID_ELEMENT.get(
449                        StaticUtils.toHex(elements[i].getType())));
450          }
451        }
452      }
453
454      return new BindResponseProtocolOp(resultCode, matchedDN,
455           diagnosticMessage, referralURLs, serverSASLCredentials);
456    }
457    catch (final LDAPException le)
458    {
459      Debug.debugException(le);
460      throw le;
461    }
462    catch (final Exception e)
463    {
464      Debug.debugException(e);
465      throw new LDAPException(ResultCode.DECODING_ERROR,
466           ERR_BIND_RESPONSE_CANNOT_DECODE.get(
467                StaticUtils.getExceptionMessage(e)),
468           e);
469    }
470  }
471
472
473
474  /**
475   * {@inheritDoc}
476   */
477  @Override()
478  public void writeTo(final ASN1Buffer buffer)
479  {
480    final ASN1BufferSequence opSequence =
481         buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE);
482    buffer.addEnumerated(resultCode);
483    buffer.addOctetString(matchedDN);
484    buffer.addOctetString(diagnosticMessage);
485
486    if (! referralURLs.isEmpty())
487    {
488      final ASN1BufferSequence refSequence =
489           buffer.beginSequence(GenericResponseProtocolOp.TYPE_REFERRALS);
490      for (final String s : referralURLs)
491      {
492        buffer.addOctetString(s);
493      }
494      refSequence.end();
495    }
496
497    if (serverSASLCredentials != null)
498    {
499      buffer.addElement(serverSASLCredentials);
500    }
501
502    opSequence.end();
503  }
504
505
506
507  /**
508   * Creates a new LDAP result object from this response protocol op.
509   *
510   * @param  controls  The set of controls to include in the LDAP result.  It
511   *                   may be empty or {@code null} if no controls should be
512   *                   included.
513   *
514   * @return  The LDAP result that was created.
515   */
516  public BindResult toBindResult(final Control... controls)
517  {
518    final String[] refs;
519    if (referralURLs.isEmpty())
520    {
521      refs = StaticUtils.NO_STRINGS;
522    }
523    else
524    {
525      refs = new String[referralURLs.size()];
526      referralURLs.toArray(refs);
527    }
528
529    return new BindResult(-1, ResultCode.valueOf(resultCode), diagnosticMessage,
530         matchedDN, refs, controls, serverSASLCredentials);
531  }
532
533
534
535  /**
536   * Retrieves a string representation of this protocol op.
537   *
538   * @return  A string representation of this protocol op.
539   */
540  @Override()
541  public String toString()
542  {
543    final StringBuilder buffer = new StringBuilder();
544    toString(buffer);
545    return buffer.toString();
546  }
547
548
549
550  /**
551   * {@inheritDoc}
552   */
553  @Override()
554  public void toString(final StringBuilder buffer)
555  {
556    buffer.append("BindResponseProtocolOp(resultCode=");
557    buffer.append(resultCode);
558
559    if (matchedDN != null)
560    {
561      buffer.append(", matchedDN='");
562      buffer.append(matchedDN);
563      buffer.append('\'');
564    }
565
566    if (diagnosticMessage != null)
567    {
568      buffer.append(", diagnosticMessage='");
569      buffer.append(diagnosticMessage);
570      buffer.append('\'');
571    }
572
573    if (! referralURLs.isEmpty())
574    {
575      buffer.append(", referralURLs={");
576
577      final Iterator<String> iterator = referralURLs.iterator();
578      while (iterator.hasNext())
579      {
580        buffer.append('\'');
581        buffer.append(iterator.next());
582        buffer.append('\'');
583        if (iterator.hasNext())
584        {
585          buffer.append(',');
586        }
587      }
588
589      buffer.append('}');
590    }
591    buffer.append(')');
592  }
593}