001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2010 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2014 ForgeRock AS. 026 */ 027package org.forgerock.opendj.ldap.controls; 028 029import static com.forgerock.opendj.util.StaticUtils.byteToHex; 030import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage; 031import static com.forgerock.opendj.ldap.CoreMessages.*; 032 033import java.io.IOException; 034 035import org.forgerock.i18n.LocalizableMessage; 036import org.forgerock.i18n.slf4j.LocalizedLogger; 037import org.forgerock.opendj.io.ASN1; 038import org.forgerock.opendj.io.ASN1Reader; 039import org.forgerock.opendj.io.ASN1Writer; 040import org.forgerock.opendj.ldap.ByteString; 041import org.forgerock.opendj.ldap.ByteStringBuilder; 042import org.forgerock.opendj.ldap.DecodeException; 043import org.forgerock.opendj.ldap.DecodeOptions; 044import org.forgerock.util.Reject; 045 046/** 047 * The password policy response control as defined in 048 * draft-behera-ldap-password-policy. 049 * 050 * <pre> 051 * Connection connection = ...; 052 * String DN = ...; 053 * char[] password = ...; 054 * 055 * try { 056 * BindRequest request = Requests.newSimpleBindRequest(DN, password) 057 * .addControl(PasswordPolicyRequestControl.newControl(true)); 058 * 059 * BindResult result = connection.bind(request); 060 * 061 * PasswordPolicyResponseControl control = 062 * result.getControl(PasswordPolicyResponseControl.DECODER, 063 * new DecodeOptions()); 064 * if (!(control == null) && !(control.getWarningType() == null)) { 065 * // Password policy warning, use control.getWarningType(), 066 * // and control.getWarningValue(). 067 * } 068 * } catch (LdapException e) { 069 * Result result = e.getResult(); 070 * try { 071 * PasswordPolicyResponseControl control = 072 * result.getControl(PasswordPolicyResponseControl.DECODER, 073 * new DecodeOptions()); 074 * if (!(control == null)) { 075 * // Password policy error, use control.getErrorType(). 076 * } 077 * } catch (DecodeException de) { 078 * // Failed to decode the response control. 079 * } 080 * } catch (DecodeException e) { 081 * // Failed to decode the response control. 082 * } 083 * </pre> 084 * 085 * If the client has sent a passwordPolicyRequest control, the server (when 086 * solicited by the inclusion of the request control) sends this control with 087 * the following operation responses: bindResponse, modifyResponse, addResponse, 088 * compareResponse and possibly extendedResponse, to inform of various 089 * conditions, and MAY be sent with other operations (in the case of the 090 * changeAfterReset error). 091 * 092 * @see PasswordPolicyRequestControl 093 * @see PasswordPolicyWarningType 094 * @see PasswordPolicyErrorType 095 * @see <a href="http://tools.ietf.org/html/draft-behera-ldap-password-policy"> 096 * draft-behera-ldap-password-policy - Password Policy for LDAP Directories 097 * </a> 098 */ 099public final class PasswordPolicyResponseControl implements Control { 100 101 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 102 /** 103 * The OID for the password policy control from 104 * draft-behera-ldap-password-policy. 105 */ 106 public static final String OID = PasswordPolicyRequestControl.OID; 107 108 private final int warningValue; 109 110 private final PasswordPolicyErrorType errorType; 111 112 private final PasswordPolicyWarningType warningType; 113 114 /** 115 * A decoder which can be used for decoding the password policy response 116 * control. 117 */ 118 public static final ControlDecoder<PasswordPolicyResponseControl> DECODER = 119 new ControlDecoder<PasswordPolicyResponseControl>() { 120 121 public PasswordPolicyResponseControl decodeControl(final Control control, 122 final DecodeOptions options) throws DecodeException { 123 Reject.ifNull(control); 124 125 if (control instanceof PasswordPolicyResponseControl) { 126 return (PasswordPolicyResponseControl) control; 127 } 128 129 if (!control.getOID().equals(OID)) { 130 final LocalizableMessage message = 131 ERR_PWPOLICYRES_CONTROL_BAD_OID.get(control.getOID(), OID); 132 throw DecodeException.error(message); 133 } 134 135 if (!control.hasValue()) { 136 // The response control must always have a value. 137 final LocalizableMessage message = ERR_PWPOLICYRES_NO_CONTROL_VALUE.get(); 138 throw DecodeException.error(message); 139 } 140 141 final ASN1Reader reader = ASN1.getReader(control.getValue()); 142 try { 143 PasswordPolicyWarningType warningType = null; 144 PasswordPolicyErrorType errorType = null; 145 int warningValue = -1; 146 147 reader.readStartSequence(); 148 149 if (reader.hasNextElement() && (reader.peekType() == TYPE_WARNING_ELEMENT)) { 150 // Its a CHOICE element. Read as sequence to 151 // retrieve 152 // nested element. 153 reader.readStartSequence(); 154 final int warningChoiceValue = (0x7F & reader.peekType()); 155 if (warningChoiceValue < 0 156 || warningChoiceValue >= PasswordPolicyWarningType.values().length) { 157 final LocalizableMessage message = 158 ERR_PWPOLICYRES_INVALID_WARNING_TYPE.get(byteToHex(reader 159 .peekType())); 160 throw DecodeException.error(message); 161 } else { 162 warningType = 163 PasswordPolicyWarningType.values()[warningChoiceValue]; 164 } 165 warningValue = (int) reader.readInteger(); 166 reader.readEndSequence(); 167 } 168 169 if (reader.hasNextElement() && (reader.peekType() == TYPE_ERROR_ELEMENT)) { 170 final int errorValue = reader.readEnumerated(); 171 if (errorValue < 0 172 || errorValue >= PasswordPolicyErrorType.values().length) { 173 final LocalizableMessage message = 174 ERR_PWPOLICYRES_INVALID_ERROR_TYPE.get(errorValue); 175 throw DecodeException.error(message); 176 } else { 177 errorType = PasswordPolicyErrorType.values()[errorValue]; 178 } 179 } 180 181 reader.readEndSequence(); 182 183 return new PasswordPolicyResponseControl(control.isCritical(), warningType, 184 warningValue, errorType); 185 } catch (final IOException e) { 186 logger.debug(LocalizableMessage.raw("%s", e)); 187 188 final LocalizableMessage message = 189 ERR_PWPOLICYRES_DECODE_ERROR.get(getExceptionMessage(e)); 190 throw DecodeException.error(message); 191 } 192 } 193 194 public String getOID() { 195 return OID; 196 } 197 }; 198 199 /** 200 * Creates a new password policy response control with the provided error. 201 * 202 * @param errorType 203 * The password policy error type. 204 * @return The new control. 205 * @throws NullPointerException 206 * If {@code errorType} was {@code null}. 207 */ 208 public static PasswordPolicyResponseControl newControl(final PasswordPolicyErrorType errorType) { 209 Reject.ifNull(errorType); 210 211 return new PasswordPolicyResponseControl(false, null, -1, errorType); 212 } 213 214 /** 215 * Creates a new password policy response control with the provided warning. 216 * 217 * @param warningType 218 * The password policy warning type. 219 * @param warningValue 220 * The password policy warning value. 221 * @return The new control. 222 * @throws IllegalArgumentException 223 * If {@code warningValue} was negative. 224 * @throws NullPointerException 225 * If {@code warningType} was {@code null}. 226 */ 227 public static PasswordPolicyResponseControl newControl( 228 final PasswordPolicyWarningType warningType, final int warningValue) { 229 Reject.ifNull(warningType); 230 Reject.ifFalse(warningValue >= 0, "warningValue is negative"); 231 232 return new PasswordPolicyResponseControl(false, warningType, warningValue, null); 233 } 234 235 /** 236 * Creates a new password policy response control with the provided warning 237 * and error. 238 * 239 * @param warningType 240 * The password policy warning type. 241 * @param warningValue 242 * The password policy warning value. 243 * @param errorType 244 * The password policy error type. 245 * @return The new control. 246 * @throws IllegalArgumentException 247 * If {@code warningValue} was negative. 248 * @throws NullPointerException 249 * If {@code warningType} or {@code errorType} was {@code null}. 250 */ 251 public static PasswordPolicyResponseControl newControl( 252 final PasswordPolicyWarningType warningType, final int warningValue, 253 final PasswordPolicyErrorType errorType) { 254 Reject.ifNull(warningType); 255 Reject.ifNull(errorType); 256 Reject.ifFalse(warningValue >= 0, "warningValue is negative"); 257 258 return new PasswordPolicyResponseControl(false, warningType, warningValue, errorType); 259 } 260 261 private final boolean isCritical; 262 263 /** 264 * The BER type value for the warning element of the control value. 265 */ 266 private static final byte TYPE_WARNING_ELEMENT = (byte) 0xA0; 267 268 /** 269 * The BER type value for the error element of the control value. 270 */ 271 private static final byte TYPE_ERROR_ELEMENT = (byte) 0x81; 272 273 private PasswordPolicyResponseControl(final boolean isCritical, 274 final PasswordPolicyWarningType warningType, final int warningValue, 275 final PasswordPolicyErrorType errorType) { 276 this.isCritical = isCritical; 277 this.warningType = warningType; 278 this.warningValue = warningValue; 279 this.errorType = errorType; 280 } 281 282 /** 283 * Returns the password policy error type, if available. 284 * 285 * @return The password policy error type, or {@code null} if this control 286 * does not contain a error. 287 */ 288 public PasswordPolicyErrorType getErrorType() { 289 return errorType; 290 } 291 292 /** {@inheritDoc} */ 293 public String getOID() { 294 return OID; 295 } 296 297 /** {@inheritDoc} */ 298 public ByteString getValue() { 299 final ByteStringBuilder buffer = new ByteStringBuilder(); 300 final ASN1Writer writer = ASN1.getWriter(buffer); 301 try { 302 writer.writeStartSequence(); 303 if (warningType != null) { 304 // Just write the CHOICE element as a single element SEQUENCE. 305 writer.writeStartSequence(TYPE_WARNING_ELEMENT); 306 writer.writeInteger((byte) (0x80 | warningType.intValue()), warningValue); 307 writer.writeEndSequence(); 308 } 309 310 if (errorType != null) { 311 writer.writeInteger(TYPE_ERROR_ELEMENT, errorType.intValue()); 312 } 313 writer.writeEndSequence(); 314 return buffer.toByteString(); 315 } catch (final IOException ioe) { 316 // This should never happen unless there is a bug somewhere. 317 throw new RuntimeException(ioe); 318 } 319 } 320 321 /** 322 * Returns the password policy warning type, if available. 323 * 324 * @return The password policy warning type, or {@code null} if this control 325 * does not contain a warning. 326 */ 327 public PasswordPolicyWarningType getWarningType() { 328 return warningType; 329 } 330 331 /** 332 * Returns the password policy warning value, if available. The value is 333 * undefined if this control does not contain a warning. 334 * 335 * @return The password policy warning value, or {@code -1} if this control 336 * does not contain a warning. 337 */ 338 public int getWarningValue() { 339 return warningValue; 340 } 341 342 /** {@inheritDoc} */ 343 public boolean hasValue() { 344 return true; 345 } 346 347 /** {@inheritDoc} */ 348 public boolean isCritical() { 349 return isCritical; 350 } 351 352 /** {@inheritDoc} */ 353 @Override 354 public String toString() { 355 final StringBuilder builder = new StringBuilder(); 356 builder.append("PasswordPolicyResponseControl(oid="); 357 builder.append(getOID()); 358 builder.append(", criticality="); 359 builder.append(isCritical()); 360 if (warningType != null) { 361 builder.append(", warningType="); 362 builder.append(warningType); 363 builder.append(", warningValue="); 364 builder.append(warningValue); 365 } 366 if (errorType != null) { 367 builder.append(", errorType="); 368 builder.append(errorType); 369 } 370 builder.append(")"); 371 return builder.toString(); 372 } 373}