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 2006-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2016 ForgeRock AS. 016 */ 017package org.opends.server.extensions; 018 019import static org.opends.messages.ExtensionMessages.*; 020import static org.opends.server.util.StaticUtils.*; 021 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.List; 025import java.util.SortedSet; 026import java.util.StringTokenizer; 027 028import org.forgerock.i18n.LocalizableMessage; 029import org.forgerock.i18n.slf4j.LocalizedLogger; 030import org.forgerock.opendj.config.server.ConfigChangeResult; 031import org.forgerock.opendj.config.server.ConfigException; 032import org.forgerock.opendj.ldap.ByteString; 033import org.forgerock.opendj.ldap.DN; 034import org.forgerock.opendj.ldap.ResultCode; 035import org.forgerock.opendj.config.server.ConfigurationChangeListener; 036import org.forgerock.opendj.server.config.server.PasswordGeneratorCfg; 037import org.forgerock.opendj.server.config.server.RandomPasswordGeneratorCfg; 038import org.opends.server.api.PasswordGenerator; 039import org.opends.server.core.DirectoryServer; 040import org.opends.server.types.*; 041 042/** 043 * This class provides an implementation of a Directory Server password 044 * generator that will create random passwords based on fixed-length strings 045 * built from one or more character sets. 046 */ 047public class RandomPasswordGenerator 048 extends PasswordGenerator<RandomPasswordGeneratorCfg> 049 implements ConfigurationChangeListener<RandomPasswordGeneratorCfg> 050{ 051 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 052 053 /** The current configuration for this password validator. */ 054 private RandomPasswordGeneratorCfg currentConfig; 055 056 /** The encoded list of character sets defined for this password generator. */ 057 private SortedSet<String> encodedCharacterSets; 058 059 /** The DN of the configuration entry for this password generator. */ 060 private DN configEntryDN; 061 062 /** The total length of the password that will be generated. */ 063 private int totalLength; 064 065 /** The numbers of characters of each type that should be used to generate the passwords. */ 066 private int[] characterCounts; 067 068 /** The character sets that should be used to generate the passwords. */ 069 private NamedCharacterSet[] characterSets; 070 071 /** 072 * The lock to use to ensure that the character sets and counts are not 073 * altered while a password is being generated. 074 */ 075 private Object generatorLock; 076 077 /** The character set format string for this password generator. */ 078 private String formatString; 079 080 @Override 081 public void initializePasswordGenerator( 082 RandomPasswordGeneratorCfg configuration) 083 throws ConfigException, InitializationException 084 { 085 this.configEntryDN = configuration.dn(); 086 generatorLock = new Object(); 087 088 // Get the character sets for use in generating the password. At least one 089 // must have been provided. 090 HashMap<String,NamedCharacterSet> charsets = new HashMap<>(); 091 092 try 093 { 094 encodedCharacterSets = configuration.getPasswordCharacterSet(); 095 096 if (encodedCharacterSets.isEmpty()) 097 { 098 LocalizableMessage message = ERR_RANDOMPWGEN_NO_CHARSETS.get(configEntryDN); 099 throw new ConfigException(message); 100 } 101 for (NamedCharacterSet s : NamedCharacterSet 102 .decodeCharacterSets(encodedCharacterSets)) 103 { 104 if (charsets.containsKey(s.getName())) 105 { 106 LocalizableMessage message = ERR_RANDOMPWGEN_CHARSET_NAME_CONFLICT.get(configEntryDN, s.getName()); 107 throw new ConfigException(message); 108 } 109 else 110 { 111 charsets.put(s.getName(), s); 112 } 113 } 114 } 115 catch (ConfigException ce) 116 { 117 throw ce; 118 } 119 catch (Exception e) 120 { 121 logger.traceException(e); 122 123 LocalizableMessage message = 124 ERR_RANDOMPWGEN_CANNOT_DETERMINE_CHARSETS.get(getExceptionMessage(e)); 125 throw new InitializationException(message, e); 126 } 127 128 // Get the value that describes which character set(s) and how many 129 // characters from each should be used. 130 131 try 132 { 133 formatString = configuration.getPasswordFormat(); 134 StringTokenizer tokenizer = new StringTokenizer(formatString, ", "); 135 136 ArrayList<NamedCharacterSet> setList = new ArrayList<>(); 137 ArrayList<Integer> countList = new ArrayList<>(); 138 139 while (tokenizer.hasMoreTokens()) 140 { 141 String token = tokenizer.nextToken(); 142 143 try 144 { 145 int colonPos = token.indexOf(':'); 146 String name = token.substring(0, colonPos); 147 int count = Integer.parseInt(token.substring(colonPos + 1)); 148 149 NamedCharacterSet charset = charsets.get(name); 150 if (charset == null) 151 { 152 throw new ConfigException(ERR_RANDOMPWGEN_UNKNOWN_CHARSET.get(formatString, name)); 153 } 154 else 155 { 156 setList.add(charset); 157 countList.add(count); 158 } 159 } 160 catch (ConfigException ce) 161 { 162 throw ce; 163 } 164 catch (Exception e) 165 { 166 logger.traceException(e); 167 168 LocalizableMessage message = ERR_RANDOMPWGEN_INVALID_PWFORMAT.get(formatString); 169 throw new ConfigException(message, e); 170 } 171 } 172 173 characterSets = new NamedCharacterSet[setList.size()]; 174 characterCounts = new int[characterSets.length]; 175 176 totalLength = 0; 177 for (int i = 0; i < characterSets.length; i++) 178 { 179 characterSets[i] = setList.get(i); 180 characterCounts[i] = countList.get(i); 181 totalLength += characterCounts[i]; 182 } 183 } 184 catch (ConfigException ce) 185 { 186 throw ce; 187 } 188 catch (Exception e) 189 { 190 logger.traceException(e); 191 192 LocalizableMessage message = 193 ERR_RANDOMPWGEN_CANNOT_DETERMINE_PWFORMAT.get(getExceptionMessage(e)); 194 throw new InitializationException(message, e); 195 } 196 197 configuration.addRandomChangeListener(this) ; 198 currentConfig = configuration; 199 } 200 201 @Override 202 public void finalizePasswordGenerator() 203 { 204 currentConfig.removeRandomChangeListener(this); 205 } 206 207 /** 208 * Generates a password for the user whose account is contained in the 209 * specified entry. 210 * 211 * @param userEntry The entry for the user for whom the password is to be 212 * generated. 213 * 214 * @return The password that has been generated for the user. 215 * 216 * @throws DirectoryException If a problem occurs while attempting to 217 * generate the password. 218 */ 219 @Override 220 public ByteString generatePassword(Entry userEntry) 221 throws DirectoryException 222 { 223 StringBuilder buffer = new StringBuilder(totalLength); 224 225 synchronized (generatorLock) 226 { 227 for (int i=0; i < characterSets.length; i++) 228 { 229 characterSets[i].getRandomCharacters(buffer, characterCounts[i]); 230 } 231 } 232 233 return ByteString.valueOfUtf8(buffer); 234 } 235 236 @Override 237 public boolean isConfigurationAcceptable(PasswordGeneratorCfg configuration, 238 List<LocalizableMessage> unacceptableReasons) 239 { 240 RandomPasswordGeneratorCfg config = 241 (RandomPasswordGeneratorCfg) configuration; 242 return isConfigurationChangeAcceptable(config, unacceptableReasons); 243 } 244 245 @Override 246 public boolean isConfigurationChangeAcceptable( 247 RandomPasswordGeneratorCfg configuration, 248 List<LocalizableMessage> unacceptableReasons) 249 { 250 DN cfgEntryDN = configuration.dn(); 251 252 // Get the character sets for use in generating the password. 253 // At least one must have been provided. 254 HashMap<String,NamedCharacterSet> charsets = new HashMap<>(); 255 try 256 { 257 SortedSet<String> currentPasSet = configuration.getPasswordCharacterSet(); 258 if (currentPasSet.isEmpty()) 259 { 260 throw new ConfigException(ERR_RANDOMPWGEN_NO_CHARSETS.get(cfgEntryDN)); 261 } 262 263 for (NamedCharacterSet s : NamedCharacterSet 264 .decodeCharacterSets(currentPasSet)) 265 { 266 if (charsets.containsKey(s.getName())) 267 { 268 unacceptableReasons.add(ERR_RANDOMPWGEN_CHARSET_NAME_CONFLICT.get(cfgEntryDN, s.getName())); 269 return false; 270 } 271 else 272 { 273 charsets.put(s.getName(), s); 274 } 275 } 276 } 277 catch (ConfigException ce) 278 { 279 unacceptableReasons.add(ce.getMessageObject()); 280 return false; 281 } 282 catch (Exception e) 283 { 284 logger.traceException(e); 285 286 LocalizableMessage message = ERR_RANDOMPWGEN_CANNOT_DETERMINE_CHARSETS.get( 287 getExceptionMessage(e)); 288 unacceptableReasons.add(message); 289 return false; 290 } 291 292 // Get the value that describes which character set(s) and how many 293 // characters from each should be used. 294 try 295 { 296 String formatString = configuration.getPasswordFormat() ; 297 StringTokenizer tokenizer = new StringTokenizer(formatString, ", "); 298 299 while (tokenizer.hasMoreTokens()) 300 { 301 String token = tokenizer.nextToken(); 302 303 try 304 { 305 int colonPos = token.indexOf(':'); 306 String name = token.substring(0, colonPos); 307 308 NamedCharacterSet charset = charsets.get(name); 309 if (charset == null) 310 { 311 unacceptableReasons.add(ERR_RANDOMPWGEN_UNKNOWN_CHARSET.get(formatString, name)); 312 return false; 313 } 314 } 315 catch (Exception e) 316 { 317 logger.traceException(e); 318 319 unacceptableReasons.add(ERR_RANDOMPWGEN_INVALID_PWFORMAT.get(formatString)); 320 return false; 321 } 322 } 323 } 324 catch (Exception e) 325 { 326 logger.traceException(e); 327 328 LocalizableMessage message = ERR_RANDOMPWGEN_CANNOT_DETERMINE_PWFORMAT.get( 329 getExceptionMessage(e)); 330 unacceptableReasons.add(message); 331 return false; 332 } 333 334 // If we've gotten here, then everything looks OK. 335 return true; 336 } 337 338 @Override 339 public ConfigChangeResult applyConfigurationChange( 340 RandomPasswordGeneratorCfg configuration) 341 { 342 final ConfigChangeResult ccr = new ConfigChangeResult(); 343 344 // Get the character sets for use in generating the password. At least one 345 // must have been provided. 346 SortedSet<String> newEncodedCharacterSets = null; 347 HashMap<String,NamedCharacterSet> charsets = new HashMap<>(); 348 try 349 { 350 newEncodedCharacterSets = configuration.getPasswordCharacterSet(); 351 if (newEncodedCharacterSets.isEmpty()) 352 { 353 ccr.addMessage(ERR_RANDOMPWGEN_NO_CHARSETS.get(configEntryDN)); 354 ccr.setResultCodeIfSuccess(ResultCode.OBJECTCLASS_VIOLATION); 355 } 356 else 357 { 358 for (NamedCharacterSet s : 359 NamedCharacterSet.decodeCharacterSets(newEncodedCharacterSets)) 360 { 361 if (charsets.containsKey(s.getName())) 362 { 363 ccr.addMessage(ERR_RANDOMPWGEN_CHARSET_NAME_CONFLICT.get(configEntryDN, s.getName())); 364 ccr.setResultCodeIfSuccess(ResultCode.CONSTRAINT_VIOLATION); 365 } 366 else 367 { 368 charsets.put(s.getName(), s); 369 } 370 } 371 } 372 } 373 catch (ConfigException ce) 374 { 375 ccr.addMessage(ce.getMessageObject()); 376 ccr.setResultCodeIfSuccess(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 377 } 378 catch (Exception e) 379 { 380 logger.traceException(e); 381 382 ccr.addMessage(ERR_RANDOMPWGEN_CANNOT_DETERMINE_CHARSETS.get(getExceptionMessage(e))); 383 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 384 } 385 386 // Get the value that describes which character set(s) and how many 387 // characters from each should be used. 388 ArrayList<NamedCharacterSet> newSetList = new ArrayList<>(); 389 ArrayList<Integer> newCountList = new ArrayList<>(); 390 String newFormatString = null; 391 392 try 393 { 394 newFormatString = configuration.getPasswordFormat(); 395 StringTokenizer tokenizer = new StringTokenizer(newFormatString, ", "); 396 397 while (tokenizer.hasMoreTokens()) 398 { 399 String token = tokenizer.nextToken(); 400 401 try 402 { 403 int colonPos = token.indexOf(':'); 404 String name = token.substring(0, colonPos); 405 int count = Integer.parseInt(token.substring(colonPos + 1)); 406 407 NamedCharacterSet charset = charsets.get(name); 408 if (charset == null) 409 { 410 ccr.addMessage(ERR_RANDOMPWGEN_UNKNOWN_CHARSET.get(newFormatString, name)); 411 ccr.setResultCodeIfSuccess(ResultCode.CONSTRAINT_VIOLATION); 412 } 413 else 414 { 415 newSetList.add(charset); 416 newCountList.add(count); 417 } 418 } 419 catch (Exception e) 420 { 421 logger.traceException(e); 422 423 ccr.addMessage(ERR_RANDOMPWGEN_INVALID_PWFORMAT.get(newFormatString)); 424 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 425 } 426 } 427 } 428 catch (Exception e) 429 { 430 logger.traceException(e); 431 432 ccr.addMessage(ERR_RANDOMPWGEN_CANNOT_DETERMINE_PWFORMAT.get(getExceptionMessage(e))); 433 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 434 } 435 436 // If everything looks OK, then apply the changes. 437 if (ccr.getResultCode() == ResultCode.SUCCESS) 438 { 439 synchronized (generatorLock) 440 { 441 encodedCharacterSets = newEncodedCharacterSets; 442 formatString = newFormatString; 443 444 characterSets = new NamedCharacterSet[newSetList.size()]; 445 characterCounts = new int[characterSets.length]; 446 447 totalLength = 0; 448 for (int i=0; i < characterCounts.length; i++) 449 { 450 characterSets[i] = newSetList.get(i); 451 characterCounts[i] = newCountList.get(i); 452 totalLength += characterCounts[i]; 453 } 454 } 455 } 456 457 return ccr; 458 } 459}