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