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}