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