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