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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.extensions; 028 import org.opends.messages.Message; 029 030 031 032 import java.util.ArrayList; 033 import java.util.HashMap; 034 import java.util.HashSet; 035 import java.util.List; 036 import java.util.Set; 037 038 import org.opends.server.admin.server.ConfigurationChangeListener; 039 import org.opends.server.admin.std.server.CharacterSetPasswordValidatorCfg; 040 import org.opends.server.admin.std.server.PasswordValidatorCfg; 041 import org.opends.server.api.PasswordValidator; 042 import org.opends.server.config.ConfigException; 043 import org.opends.server.types.ConfigChangeResult; 044 import org.opends.server.types.ByteString; 045 import org.opends.server.types.DirectoryConfig; 046 import org.opends.server.types.Entry; 047 import org.opends.server.types.Operation; 048 import org.opends.server.types.ResultCode; 049 050 import static org.opends.messages.ExtensionMessages.*; 051 import org.opends.messages.MessageBuilder; 052 import static org.opends.server.util.StaticUtils.*; 053 054 055 056 /** 057 * This class provides an OpenDS password validator that may be used to ensure 058 * that proposed passwords contain at least a specified number of characters 059 * from one or more user-defined character sets. 060 */ 061 public class CharacterSetPasswordValidator 062 extends PasswordValidator<CharacterSetPasswordValidatorCfg> 063 implements ConfigurationChangeListener<CharacterSetPasswordValidatorCfg> 064 { 065 // The current configuration for this password validator. 066 private CharacterSetPasswordValidatorCfg currentConfig; 067 068 // A mapping between the character sets and the minimum number of characters 069 // required for each. 070 private HashMap<String,Integer> characterSets; 071 072 073 074 /** 075 * Creates a new instance of this character set password validator. 076 */ 077 public CharacterSetPasswordValidator() 078 { 079 super(); 080 081 // No implementation is required here. All initialization should be 082 // performed in the initializePasswordValidator() method. 083 } 084 085 086 087 /** 088 * {@inheritDoc} 089 */ 090 @Override() 091 public void initializePasswordValidator( 092 CharacterSetPasswordValidatorCfg configuration) 093 throws ConfigException 094 { 095 configuration.addCharacterSetChangeListener(this); 096 currentConfig = configuration; 097 098 // Make sure that each of the character set definitions are acceptable. 099 characterSets = processCharacterSets(configuration); 100 } 101 102 103 104 /** 105 * {@inheritDoc} 106 */ 107 @Override() 108 public void finalizePasswordValidator() 109 { 110 currentConfig.removeCharacterSetChangeListener(this); 111 } 112 113 114 115 /** 116 * {@inheritDoc} 117 */ 118 @Override() 119 public boolean passwordIsAcceptable(ByteString newPassword, 120 Set<ByteString> currentPasswords, 121 Operation operation, Entry userEntry, 122 MessageBuilder invalidReason) 123 { 124 // Get a handle to the current configuration. 125 CharacterSetPasswordValidatorCfg config = currentConfig; 126 HashMap<String,Integer> characterSets = this.characterSets; 127 128 129 // Process the provided password. 130 String password = newPassword.stringValue(); 131 HashMap<String,Integer> counts = new HashMap<String,Integer>(); 132 for (int i=0; i < password.length(); i++) 133 { 134 char c = password.charAt(i); 135 boolean found = false; 136 for (String characterSet : characterSets.keySet()) 137 { 138 if (characterSet.indexOf(c) >= 0) 139 { 140 Integer count = counts.get(characterSet); 141 if (count == null) 142 { 143 counts.put(characterSet, 1); 144 } 145 else 146 { 147 counts.put(characterSet, count+1); 148 } 149 150 found = true; 151 break; 152 } 153 } 154 155 if ((! found) && (! config.isAllowUnclassifiedCharacters())) 156 { 157 invalidReason.append(ERR_CHARSET_VALIDATOR_ILLEGAL_CHARACTER.get( 158 String.valueOf(c))); 159 return false; 160 } 161 } 162 163 for (String characterSet : characterSets.keySet()) 164 { 165 int minimumCount = characterSets.get(characterSet); 166 Integer passwordCount = counts.get(characterSet); 167 if ((passwordCount == null) || (passwordCount < minimumCount)) 168 { 169 invalidReason.append(ERR_CHARSET_VALIDATOR_TOO_FEW_CHARS_FROM_SET.get( 170 characterSet, minimumCount)); 171 return false; 172 } 173 } 174 175 176 // If we've gotten here, then the password is acceptable. 177 return true; 178 } 179 180 181 182 /** 183 * Parses the provided configuration and extracts the character set 184 * definitions and associated minimum counts from them. 185 * 186 * @param configuration the configuration for this password validator. 187 * 188 * @return The mapping between strings of character set values and the 189 * minimum number of characters required from those sets. 190 * 191 * @throws ConfigException If any of the character set definitions cannot be 192 * parsed, or if there are any characters present in 193 * multiple sets. 194 */ 195 private HashMap<String,Integer> 196 processCharacterSets( 197 CharacterSetPasswordValidatorCfg configuration) 198 throws ConfigException 199 { 200 HashMap<String,Integer> characterSets = new HashMap<String,Integer>(); 201 HashSet<Character> usedCharacters = new HashSet<Character>(); 202 203 for (String definition : configuration.getCharacterSet()) 204 { 205 int colonPos = definition.indexOf(':'); 206 if (colonPos <= 0) 207 { 208 Message message = ERR_CHARSET_VALIDATOR_NO_COLON.get(definition); 209 throw new ConfigException(message); 210 } 211 else if (colonPos == (definition.length() - 1)) 212 { 213 Message message = ERR_CHARSET_VALIDATOR_NO_CHARS.get(definition); 214 throw new ConfigException(message); 215 } 216 217 int minCount; 218 try 219 { 220 minCount = Integer.parseInt(definition.substring(0, colonPos)); 221 } 222 catch (Exception e) 223 { 224 Message message = ERR_CHARSET_VALIDATOR_INVALID_COUNT.get(definition); 225 throw new ConfigException(message); 226 } 227 228 if (minCount <= 0) 229 { 230 Message message = ERR_CHARSET_VALIDATOR_INVALID_COUNT.get(definition); 231 throw new ConfigException(message); 232 } 233 234 String characterSet = definition.substring(colonPos+1); 235 for (int i=0; i < characterSet.length(); i++) 236 { 237 char c = characterSet.charAt(i); 238 if (usedCharacters.contains(c)) 239 { 240 Message message = ERR_CHARSET_VALIDATOR_DUPLICATE_CHAR.get( 241 definition, String.valueOf(c)); 242 throw new ConfigException(message); 243 } 244 245 usedCharacters.add(c); 246 } 247 248 characterSets.put(characterSet, minCount); 249 } 250 251 return characterSets; 252 } 253 254 255 256 /** 257 * {@inheritDoc} 258 */ 259 @Override() 260 public boolean isConfigurationAcceptable(PasswordValidatorCfg configuration, 261 List<Message> unacceptableReasons) 262 { 263 CharacterSetPasswordValidatorCfg config = 264 (CharacterSetPasswordValidatorCfg) configuration; 265 return isConfigurationChangeAcceptable(config, unacceptableReasons); 266 } 267 268 269 270 /** 271 * {@inheritDoc} 272 */ 273 public boolean isConfigurationChangeAcceptable( 274 CharacterSetPasswordValidatorCfg configuration, 275 List<Message> unacceptableReasons) 276 { 277 // Make sure that we can process the defined character sets. If so, then 278 // we'll accept the new configuration. 279 try 280 { 281 processCharacterSets(configuration); 282 } 283 catch (ConfigException ce) 284 { 285 unacceptableReasons.add(ce.getMessageObject()); 286 return false; 287 } 288 289 return true; 290 } 291 292 293 294 /** 295 * {@inheritDoc} 296 */ 297 public ConfigChangeResult applyConfigurationChange( 298 CharacterSetPasswordValidatorCfg configuration) 299 { 300 ResultCode resultCode = ResultCode.SUCCESS; 301 boolean adminActionRequired = false; 302 ArrayList<Message> messages = new ArrayList<Message>(); 303 304 305 // Make sure that we can process the defined character sets. If so, then 306 // activate the new configuration. 307 try 308 { 309 characterSets = processCharacterSets(configuration); 310 currentConfig = configuration; 311 } 312 catch (Exception e) 313 { 314 resultCode = DirectoryConfig.getServerErrorResultCode(); 315 messages.add(getExceptionMessage(e)); 316 } 317 318 return new ConfigChangeResult(resultCode, adminActionRequired, messages); 319 } 320 } 321