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