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 2008 Sun Microsystems, Inc. 015 * Portions Copyright 2012-2016 ForgeRock AS. 016 */ 017package org.opends.server.extensions; 018 019import org.forgerock.i18n.LocalizableMessage; 020 021import java.util.List; 022import java.util.Set; 023 024import org.opends.server.admin.server.ConfigurationChangeListener; 025import org.opends.server.admin.std.server.AttributeValuePasswordValidatorCfg; 026import org.opends.server.admin.std.server.PasswordValidatorCfg; 027import org.opends.server.api.PasswordValidator; 028import org.forgerock.opendj.ldap.schema.AttributeType; 029import org.opends.server.types.*; 030import org.forgerock.opendj.config.server.ConfigChangeResult; 031import org.forgerock.opendj.ldap.ByteString; 032import static org.opends.messages.ExtensionMessages.*; 033import org.forgerock.i18n.LocalizableMessageBuilder; 034 035/** 036 * This class provides an OpenDS password validator that may be used to ensure 037 * that proposed passwords are not contained in another attribute in the user's 038 * entry. 039 */ 040public class AttributeValuePasswordValidator 041 extends PasswordValidator<AttributeValuePasswordValidatorCfg> 042 implements ConfigurationChangeListener< 043 AttributeValuePasswordValidatorCfg> 044{ 045 /** The current configuration for this password validator. */ 046 private AttributeValuePasswordValidatorCfg currentConfig; 047 048 049 050 /** 051 * Creates a new instance of this attribute value password validator. 052 */ 053 public AttributeValuePasswordValidator() 054 { 055 super(); 056 057 // No implementation is required here. All initialization should be 058 // performed in the initializePasswordValidator() method. 059 } 060 061 062 063 /** {@inheritDoc} */ 064 @Override 065 public void initializePasswordValidator( 066 AttributeValuePasswordValidatorCfg configuration) 067 { 068 configuration.addAttributeValueChangeListener(this); 069 currentConfig = configuration; 070 } 071 072 073 074 /** {@inheritDoc} */ 075 @Override 076 public void finalizePasswordValidator() 077 { 078 currentConfig.removeAttributeValueChangeListener(this); 079 } 080 081 082 083 /** 084 * Search for substrings of the password in an Attribute. The search is 085 * case-insensitive. 086 * 087 * @param password the password 088 * @param minSubstringLength the minimum substring length to check 089 * @param a the attribute to search 090 * @return true if an attribute value matches a substring of the password, 091 * false otherwise. 092 */ 093 private boolean containsSubstring(String password, int minSubstringLength, 094 Attribute a) 095 { 096 final int passwordLength = password.length(); 097 098 for (int i = 0; i < passwordLength; i++) 099 { 100 for (int j = i + minSubstringLength; j <= passwordLength; j++) 101 { 102 Attribute substring = Attributes.create(a.getAttributeDescription().getAttributeType(), 103 password.substring(i, j)); 104 for (ByteString val : a) 105 { 106 if (substring.contains(val)) 107 { 108 return true; 109 } 110 } 111 } 112 } 113 return false; 114 } 115 116 117 118 /** {@inheritDoc} */ 119 @Override 120 public boolean passwordIsAcceptable(ByteString newPassword, 121 Set<ByteString> currentPasswords, 122 Operation operation, Entry userEntry, 123 LocalizableMessageBuilder invalidReason) 124 { 125 // Get a handle to the current configuration. 126 AttributeValuePasswordValidatorCfg config = currentConfig; 127 128 129 // Get the string representation (both forward and reversed) for the password. 130 final String password = newPassword.toString(); 131 final String reversed = new StringBuilder(password).reverse().toString(); 132 133 // Check to see if we should verify the whole password or the substrings. 134 int minSubstringLength = password.length(); 135 if (config.isCheckSubstrings() 136 // We apply the minimal substring length only if the provided value 137 // is smaller then the actual password length 138 && config.getMinSubstringLength() < password.length()) 139 { 140 minSubstringLength = config.getMinSubstringLength(); 141 } 142 143 // If we should check a specific set of attributes, then do that now. 144 // Otherwise, check all user attributes. 145 Set<AttributeType> matchAttributes = config.getMatchAttribute(); 146 if (matchAttributes == null || matchAttributes.isEmpty()) 147 { 148 matchAttributes = userEntry.getUserAttributes().keySet(); 149 } 150 151 final ByteString vf = ByteString.valueOfUtf8(password); 152 final ByteString vr = ByteString.valueOfUtf8(reversed); 153 for (AttributeType t : matchAttributes) 154 { 155 for (Attribute a : userEntry.getAttribute(t)) 156 { 157 if (a.contains(vf) || 158 (config.isTestReversedPassword() && a.contains(vr)) || 159 (config.isCheckSubstrings() && 160 containsSubstring(password, minSubstringLength, a))) 161 { 162 invalidReason.append(ERR_ATTRVALUE_VALIDATOR_PASSWORD_IN_ENTRY.get()); 163 return false; 164 } 165 } 166 } 167 168 169 // If we've gotten here, then the password is acceptable. 170 return true; 171 } 172 173 174 175 /** {@inheritDoc} */ 176 @Override 177 public boolean isConfigurationAcceptable(PasswordValidatorCfg configuration, 178 List<LocalizableMessage> unacceptableReasons) 179 { 180 AttributeValuePasswordValidatorCfg config = 181 (AttributeValuePasswordValidatorCfg) configuration; 182 return isConfigurationChangeAcceptable(config, unacceptableReasons); 183 } 184 185 186 187 /** {@inheritDoc} */ 188 public boolean isConfigurationChangeAcceptable( 189 AttributeValuePasswordValidatorCfg configuration, 190 List<LocalizableMessage> unacceptableReasons) 191 { 192 // If we've gotten this far, then we'll accept the change. 193 return true; 194 } 195 196 197 198 /** {@inheritDoc} */ 199 public ConfigChangeResult applyConfigurationChange( 200 AttributeValuePasswordValidatorCfg configuration) 201 { 202 currentConfig = configuration; 203 return new ConfigChangeResult(); 204 } 205}