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