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 * Portions Copyright 2011-2015 ForgeRock AS. 025 * Portions Copyright 2014 ForgeRock AS 026 */ 027package org.opends.server.api; 028 029import java.util.List; 030 031import org.forgerock.i18n.LocalizableMessage; 032import org.forgerock.i18n.slf4j.LocalizedLogger; 033import org.forgerock.opendj.ldap.ByteString; 034import org.forgerock.opendj.ldap.ConditionResult; 035import org.forgerock.opendj.ldap.GeneralizedTime; 036import org.forgerock.opendj.ldap.ResultCode; 037import org.opends.server.core.DirectoryServer; 038import org.opends.server.types.*; 039 040import static org.opends.messages.CoreMessages.*; 041import static org.opends.server.config.ConfigConstants.*; 042import static org.opends.server.util.StaticUtils.*; 043 044/** 045 * The authentication policy context associated with a user's entry, which is 046 * responsible for managing the user's account, their password, as well as 047 * authenticating the user. 048 */ 049public abstract class AuthenticationPolicyState 050{ 051 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 052 053 054 055 /** 056 * Returns the authentication policy state for the user provided user. This 057 * method is equivalent to the following: 058 * 059 * <pre> 060 * AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry, 061 * useDefaultOnError); 062 * AuthenticationPolicyState state = policy 063 * .createAuthenticationPolicyState(userEntry); 064 * </pre> 065 * 066 * See the documentation of {@link AuthenticationPolicy#forUser} for a 067 * description of the algorithm used to find a user's authentication policy. 068 * 069 * @param userEntry 070 * The user entry. 071 * @param useDefaultOnError 072 * Indicates whether the server should fall back to using the default 073 * password policy if there is a problem with the configured policy 074 * for the user. 075 * @return The password policy for the user. 076 * @throws DirectoryException 077 * If a problem occurs while attempting to determine the password 078 * policy for the user. 079 * @see AuthenticationPolicy#forUser(Entry, boolean) 080 */ 081 public static AuthenticationPolicyState forUser(final Entry userEntry, 082 final boolean useDefaultOnError) throws DirectoryException 083 { 084 final AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry, 085 useDefaultOnError); 086 return policy.createAuthenticationPolicyState(userEntry); 087 } 088 089 090 091 /** 092 * A utility method which may be used by implementations in order to obtain 093 * the value of the specified attribute from the provided entry as a boolean. 094 * 095 * @param entry 096 * The entry whose attribute is to be parsed as a boolean. 097 * @param attributeType 098 * The attribute type whose value should be parsed as a boolean. 099 * @return The attribute's value represented as a ConditionResult value, or 100 * ConditionResult.UNDEFINED if the specified attribute does not exist 101 * in the entry. 102 * @throws DirectoryException 103 * If the value cannot be decoded as a boolean. 104 */ 105 protected static ConditionResult getBoolean(final Entry entry, 106 final AttributeType attributeType) throws DirectoryException 107 { 108 final List<Attribute> attrList = entry.getAttribute(attributeType); 109 if (attrList != null) 110 { 111 for (final Attribute a : attrList) 112 { 113 if (a.isEmpty()) 114 { 115 continue; 116 } 117 118 final String valueString = toLowerCase(a.iterator().next().toString()); 119 120 if (valueString.equals("true") || valueString.equals("yes") 121 || valueString.equals("on") || valueString.equals("1")) 122 { 123 if (logger.isTraceEnabled()) 124 { 125 logger.trace("Attribute %s resolves to true for user entry %s", 126 attributeType.getNameOrOID(), entry.getName()); 127 } 128 129 return ConditionResult.TRUE; 130 } 131 132 if (valueString.equals("false") || valueString.equals("no") 133 || valueString.equals("off") || valueString.equals("0")) 134 { 135 if (logger.isTraceEnabled()) 136 { 137 logger.trace("Attribute %s resolves to false for user entry %s", 138 attributeType.getNameOrOID(), entry.getName()); 139 } 140 141 return ConditionResult.FALSE; 142 } 143 144 if (logger.isTraceEnabled()) 145 { 146 logger.trace("Unable to resolve value %s for attribute %s " 147 + "in user entry %s as a Boolean.", valueString, 148 attributeType.getNameOrOID(), entry.getName()); 149 } 150 151 final LocalizableMessage message = ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN 152 .get(valueString, attributeType.getNameOrOID(), entry.getName()); 153 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 154 message); 155 } 156 } 157 158 if (logger.isTraceEnabled()) 159 { 160 logger.trace("Returning %s because attribute %s does not exist " 161 + "in user entry %s", ConditionResult.UNDEFINED, 162 attributeType.getNameOrOID(), entry.getName()); 163 } 164 165 return ConditionResult.UNDEFINED; 166 } 167 168 169 170 /** 171 * A utility method which may be used by implementations in order to obtain 172 * the value of the specified attribute from the provided entry as a time in 173 * generalized time format. 174 * 175 * @param entry 176 * The entry whose attribute is to be parsed as a boolean. 177 * @param attributeType 178 * The attribute type whose value should be parsed as a generalized 179 * time value. 180 * @return The requested time, or -1 if it could not be determined. 181 * @throws DirectoryException 182 * If a problem occurs while attempting to decode the value as a 183 * generalized time. 184 */ 185 protected static long getGeneralizedTime(final Entry entry, 186 final AttributeType attributeType) throws DirectoryException 187 { 188 long timeValue = -1; 189 190 final List<Attribute> attrList = entry.getAttribute(attributeType); 191 if (attrList != null) 192 { 193 for (final Attribute a : attrList) 194 { 195 if (a.isEmpty()) 196 { 197 continue; 198 } 199 200 final ByteString v = a.iterator().next(); 201 try 202 { 203 timeValue = GeneralizedTime.valueOf(v.toString()).getTimeInMillis(); 204 } 205 catch (final Exception e) 206 { 207 logger.traceException(e, "Unable to decode value %s for attribute %s in user entry %s", 208 v, attributeType.getNameOrOID(), entry.getName()); 209 210 final LocalizableMessage message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME 211 .get(v, attributeType.getNameOrOID(), entry.getName(), e); 212 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 213 message, e); 214 } 215 break; 216 } 217 } 218 219 if (timeValue == -1 && logger.isTraceEnabled()) 220 { 221 logger.trace("Returning -1 because attribute %s does not " 222 + "exist in user entry %s", attributeType.getNameOrOID(), entry.getName()); 223 } 224 225 return timeValue; 226 } 227 228 229 230 /** 231 * A boolean indicating whether or not the account associated with this 232 * authentication state has been administratively disabled. 233 */ 234 protected ConditionResult isDisabled = ConditionResult.UNDEFINED; 235 236 /** 237 * The user entry associated with this authentication policy state. 238 */ 239 protected final Entry userEntry; 240 241 242 243 /** 244 * Creates a new abstract authentication policy context. 245 * 246 * @param userEntry 247 * The user's entry. 248 */ 249 protected AuthenticationPolicyState(final Entry userEntry) 250 { 251 this.userEntry = userEntry; 252 } 253 254 255 256 /** 257 * Performs any finalization required after a bind operation has completed. 258 * Implementations may perform internal operations in order to persist 259 * internal state to the user's entry if needed. 260 * 261 * @throws DirectoryException 262 * If a problem occurs during finalization. 263 */ 264 public void finalizeStateAfterBind() throws DirectoryException 265 { 266 // Do nothing by default. 267 } 268 269 270 271 /** 272 * Returns the authentication policy associated with this state. 273 * 274 * @return The authentication policy associated with this state. 275 */ 276 public abstract AuthenticationPolicy getAuthenticationPolicy(); 277 278 279 280 /** 281 * Returns {@code true} if this authentication policy state is associated with 282 * a user whose account has been administratively disabled. 283 * <p> 284 * The default implementation is use the value of the "ds-pwp-account-disable" 285 * attribute in the user's entry. 286 * 287 * @return {@code true} if this authentication policy state is associated with 288 * a user whose account has been administratively disabled. 289 */ 290 public boolean isDisabled() 291 { 292 final AttributeType type = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_ACCOUNT_DISABLED); 293 try 294 { 295 isDisabled = getBoolean(userEntry, type); 296 } 297 catch (final Exception e) 298 { 299 logger.traceException(e, "User %s is considered administratively " 300 + "disabled because an error occurred while " 301 + "attempting to make the determination.", userEntry.getName()); 302 303 isDisabled = ConditionResult.TRUE; 304 return true; 305 } 306 307 if (isDisabled == ConditionResult.UNDEFINED) 308 { 309 isDisabled = ConditionResult.FALSE; 310 if (logger.isTraceEnabled()) 311 { 312 logger.trace("User %s is not administratively disabled since " 313 + "the attribute \"%s\" is not present in the entry.", userEntry.getName(), OP_ATTR_ACCOUNT_DISABLED); 314 } 315 return false; 316 } 317 318 final boolean result = isDisabled == ConditionResult.TRUE; 319 if (logger.isTraceEnabled()) 320 { 321 logger.trace("User %s is%s administratively disabled.", userEntry.getName(), 322 result ? "" : " not"); 323 } 324 return result; 325 } 326 327 328 329 /** 330 * Returns {@code true} if this authentication policy state is associated with 331 * a password policy and the method {@link #getAuthenticationPolicy} will 332 * return a {@code PasswordPolicy}. 333 * 334 * @return {@code true} if this authentication policy state is associated with 335 * a password policy, otherwise {@code false}. 336 */ 337 public boolean isPasswordPolicy() 338 { 339 return getAuthenticationPolicy().isPasswordPolicy(); 340 } 341 342 343 344 /** 345 * Returns {@code true} if the provided password value matches any of the 346 * user's passwords. 347 * 348 * @param password 349 * The user-provided password to verify. 350 * @return {@code true} if the provided password value matches any of the 351 * user's passwords. 352 * @throws DirectoryException 353 * If verification unexpectedly failed. 354 */ 355 public abstract boolean passwordMatches(ByteString password) 356 throws DirectoryException; 357}