001/* 002 * Copyright 2020-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020-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) 2020-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.sdk; 037 038 039 040import java.util.Collections; 041import java.util.Iterator; 042import java.util.LinkedHashSet; 043import java.util.Set; 044import java.util.StringTokenizer; 045 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.util.Debug; 048import com.unboundid.util.NotMutable; 049import com.unboundid.util.NotNull; 050import com.unboundid.util.Nullable; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053import com.unboundid.util.json.JSONObject; 054 055 056 057/** 058 * This class provides a bind result that can provide access to the details of 059 * a failed OAUTHBEARER SASL bind attempt. 060 * 061 * @see OAUTHBEARERBindRequest 062 */ 063@NotMutable() 064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 065public final class OAUTHBEARERBindResult 066 extends BindResult 067{ 068 /** 069 * The name of the failure details field that holds the authorization error 070 * code. 071 */ 072 @NotNull private static final String FAILURE_DETAILS_FIELD_AUTHZ_ERROR_CODE = 073 "status"; 074 075 076 077 /** 078 * The name of the failure details field that holds the OpenID configuration 079 * URL. 080 */ 081 @NotNull private static final String FAILURE_DETAILS_FIELD_OPENID_CONFIG_URL = 082 "openid-configuration"; 083 084 085 086 /** 087 * The name of the failure details field that holds the space-delimited set of 088 * scopes. 089 */ 090 @NotNull private static final String FAILURE_DETAILS_FIELD_SCOPE = "scope"; 091 092 093 094 /** 095 * The serial version UID for this serializable class. 096 */ 097 private static final long serialVersionUID = 6513765034667496311L; 098 099 100 101 // The final bind result received during bind processing. 102 @Nullable private final BindResult finalBindResult; 103 104 // The initial bind result received during bind processing. 105 @NotNull private final BindResult initialBindResult; 106 107 // A JSON object with additional details about a failed authentication 108 // attempt. 109 @Nullable private final JSONObject failureDetailsObject; 110 111 // The set of scopes included in the failure details object. 112 @NotNull private final Set<String> scopes; 113 114 // The authorization error code included in the failure details object. 115 @Nullable private final String authorizationErrorCode; 116 117 // The OpenID configuration URL included in the failure details object. 118 @Nullable private final String openIDConfigurationURL; 119 120 121 122 /** 123 * Creates a new OAUTHBEARER bind result from the provided single bind 124 * result. The provided result is not expected to contain server SASL 125 * credentials, but it will attempt to decode any credentials included in the 126 * provided result. 127 * 128 * @param bindResult The bind result to use to create this OAUTHBEARER bind 129 * result. It must not be {@code null}. 130 */ 131 public OAUTHBEARERBindResult(@NotNull final BindResult bindResult) 132 { 133 this(bindResult, null); 134 } 135 136 137 138 /** 139 * Creates a new OAUTHBEARER bind result from the provided pair of results, 140 * which correspond to the initial and final (if any) phases of bind 141 * processing. 142 * 143 * @param initialBindResult The result obtained in response to the initial 144 * OAUTHBEARER bind request. It must not be 145 * {@code null}. 146 * @param finalBindResult The result obtained in response to the final 147 * OAUTHBEARER bind request, if any. It may be 148 * {@code null} if the bind consisted of only a 149 * single request. 150 */ 151 public OAUTHBEARERBindResult(@NotNull final BindResult initialBindResult, 152 @Nullable final BindResult finalBindResult) 153 { 154 super(mergeBindResults(initialBindResult, finalBindResult)); 155 156 this.initialBindResult = initialBindResult; 157 this.finalBindResult = finalBindResult; 158 159 final ASN1OctetString serverSASLCredentials = 160 initialBindResult.getServerSASLCredentials(); 161 if (serverSASLCredentials == null) 162 { 163 failureDetailsObject = null; 164 authorizationErrorCode = null; 165 scopes = Collections.emptySet(); 166 openIDConfigurationURL = null; 167 return; 168 } 169 170 final JSONObject credentialsObject; 171 try 172 { 173 credentialsObject = new JSONObject(serverSASLCredentials.stringValue()); 174 } 175 catch (final Exception e) 176 { 177 Debug.debugException(e); 178 failureDetailsObject = null; 179 authorizationErrorCode = null; 180 scopes = Collections.emptySet(); 181 openIDConfigurationURL = null; 182 return; 183 } 184 185 failureDetailsObject = credentialsObject; 186 authorizationErrorCode = credentialsObject.getFieldAsString( 187 FAILURE_DETAILS_FIELD_AUTHZ_ERROR_CODE); 188 openIDConfigurationURL = credentialsObject.getFieldAsString( 189 FAILURE_DETAILS_FIELD_OPENID_CONFIG_URL); 190 191 final String scopeStr = 192 credentialsObject.getFieldAsString(FAILURE_DETAILS_FIELD_SCOPE); 193 if (scopeStr == null) 194 { 195 scopes = Collections.emptySet(); 196 return; 197 } 198 199 final Set<String> scopeSet = new LinkedHashSet<>(); 200 final StringTokenizer tokenizer = new StringTokenizer(scopeStr, " "); 201 while (tokenizer.hasMoreTokens()) 202 { 203 scopeSet.add(tokenizer.nextToken()); 204 } 205 scopes = Collections.unmodifiableSet(scopeSet); 206 } 207 208 209 210 /** 211 * Creates a bind result that is merged from the provided results. If the 212 * provided final result is {@code null}, then this will simply return the 213 * initial result. If both are non-{@code null}, then it will use all details 214 * from the final result except the server SASL credentials, which will come 215 * from the initial result. 216 * 217 * @param initialBindResult The result obtained in response to the initial 218 * OAUTHBEARER bind request. It must not be 219 * {@code null}. 220 * @param finalBindResult The result obtained in response to the final 221 * OAUTHBEARER bind request, if any. It may be 222 * {@code null} if the bind consisted of only a 223 * single request. 224 * 225 * @return The merged bind results. 226 */ 227 @NotNull() 228 private static BindResult mergeBindResults( 229 @NotNull final BindResult initialBindResult, 230 @Nullable final BindResult finalBindResult) 231 { 232 if (finalBindResult == null) 233 { 234 return initialBindResult; 235 } 236 237 return new BindResult(finalBindResult.getMessageID(), 238 finalBindResult.getResultCode(), 239 finalBindResult.getDiagnosticMessage(), 240 finalBindResult.getMatchedDN(), 241 finalBindResult.getReferralURLs(), 242 finalBindResult.getResponseControls(), 243 initialBindResult.getServerSASLCredentials()); 244 } 245 246 247 248 /** 249 * Retrieves the result obtained from the initial bind attempt in the 250 * OAUTHBEARER authentication process. For a successful authentication, there 251 * should only be a single bind. For a failed authentication attempt, there 252 * may be either one or two binds, based on whether credentials were included 253 * in the initial bind result. 254 * 255 * @return The result obtained from the initial bind attempt in the 256 * OAUTHBEARER authentication process. 257 */ 258 @NotNull() 259 public BindResult getInitialBindResult() 260 { 261 return initialBindResult; 262 } 263 264 265 266 /** 267 * Retrieves the result obtained from the final bind attempt in the 268 * OAUTHBEARER authentication process, if any. This should always be 269 * {@code null} for a successful bind, and it may or may not be {@code null} 270 * for a failed attempt, based on whether credentials were included in the 271 * initial bind result. 272 * 273 * @return The result obtained for the final bind attempt in the 274 * OAUTHBEARER authentication process, or {@code null} if the 275 * authentication process only included a single bind. 276 */ 277 @Nullable() 278 public BindResult getFinalBindResult() 279 { 280 return finalBindResult; 281 } 282 283 284 285 /** 286 * Retrieves a JSON object with additional information about a failed 287 * authentication attempt, if any. 288 * 289 * @return A JSON object with additional information about a failed 290 * authentication attempt, or {@code null} this is not available. 291 */ 292 @Nullable() 293 public JSONObject getFailureDetailsObject() 294 { 295 return failureDetailsObject; 296 } 297 298 299 300 /** 301 * Retrieves the authorization error code obtained from the failure details 302 * object, if available. 303 * 304 * @return The authorization error code obtained from the failure details 305 * object, or {@code null} if no failure details object was provided 306 * or if it did not include an authorization error code. 307 */ 308 @Nullable() 309 public String getAuthorizationErrorCode() 310 { 311 return authorizationErrorCode; 312 } 313 314 315 316 /** 317 * Retrieves the set of scopes included in the failure details object, if 318 * available. 319 * 320 * @return The set of scopes included in teh failure details object, or an 321 * empty set if no failure details object was provided or if it did 322 * not include any scopes. 323 */ 324 @NotNull() 325 public Set<String> getScopes() 326 { 327 return scopes; 328 } 329 330 331 332 /** 333 * Retrieves the OpenID configuration URL obtained from the failure details 334 * object, if available. 335 * 336 * @return The OpenID configuration URL obtained from the failure details 337 * object, or {@code null} if no failure details object was provided 338 * or if it did not include an OpenID configuration URL. 339 */ 340 @Nullable() 341 public String getOpenIDConfigurationURL() 342 { 343 return openIDConfigurationURL; 344 } 345 346 347 348 /** 349 * {@inheritDoc} 350 */ 351 @Override() 352 public void toString(@NotNull final StringBuilder buffer) 353 { 354 buffer.append("OAUTHBEARERBindResult(resultCode="); 355 buffer.append(getResultCode()); 356 357 final int messageID = getMessageID(); 358 if (messageID >= 0) 359 { 360 buffer.append(", messageID="); 361 buffer.append(messageID); 362 } 363 364 final String diagnosticMessage = getDiagnosticMessage(); 365 if (diagnosticMessage != null) 366 { 367 buffer.append(", diagnosticMessage='"); 368 buffer.append(diagnosticMessage); 369 buffer.append('\''); 370 } 371 372 final String matchedDN = getMatchedDN(); 373 if (matchedDN != null) 374 { 375 buffer.append(", matchedDN='"); 376 buffer.append(matchedDN); 377 buffer.append('\''); 378 } 379 380 final String[] referralURLs = getReferralURLs(); 381 if (referralURLs.length > 0) 382 { 383 buffer.append(", referralURLs={"); 384 for (int i=0; i < referralURLs.length; i++) 385 { 386 if (i > 0) 387 { 388 buffer.append(", "); 389 } 390 391 buffer.append('\''); 392 buffer.append(referralURLs[i]); 393 buffer.append('\''); 394 } 395 buffer.append('}'); 396 } 397 398 buffer.append(", hasServerSASLCredentials="); 399 buffer.append(getServerSASLCredentials() != null); 400 401 if (finalBindResult != null) 402 { 403 buffer.append(", initialBindResult="); 404 initialBindResult.toString(buffer); 405 406 buffer.append(", finalBindResult="); 407 finalBindResult.toString(buffer); 408 } 409 410 if (failureDetailsObject != null) 411 { 412 buffer.append(", failureDetailsObject="); 413 failureDetailsObject.toSingleLineString(buffer); 414 } 415 416 if (authorizationErrorCode != null) 417 { 418 buffer.append(", authorizationErrorCode='"); 419 buffer.append(authorizationErrorCode); 420 buffer.append('\''); 421 } 422 423 if (! scopes.isEmpty()) 424 { 425 buffer.append(", scopes={"); 426 427 final Iterator<String> iterator = scopes.iterator(); 428 while (iterator.hasNext()) 429 { 430 buffer.append(' '); 431 buffer.append(iterator.next()); 432 433 if (iterator.hasNext()) 434 { 435 buffer.append(','); 436 } 437 } 438 439 buffer.append(" }"); 440 } 441 442 if (openIDConfigurationURL != null) 443 { 444 buffer.append(", openIDConfigURL='"); 445 buffer.append(openIDConfigurationURL); 446 buffer.append('\''); 447 } 448 449 final Control[] responseControls = getResponseControls(); 450 if (responseControls.length > 0) 451 { 452 buffer.append(", responseControls={"); 453 for (int i=0; i < responseControls.length; i++) 454 { 455 if (i > 0) 456 { 457 buffer.append(", "); 458 } 459 460 buffer.append(responseControls[i]); 461 } 462 buffer.append('}'); 463 } 464 465 buffer.append(')'); 466 } 467}