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 2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import static org.opends.messages.ExtensionMessages.*;
020
021import java.util.HashSet;
022import java.util.List;
023import java.util.Set;
024
025import org.forgerock.opendj.config.server.ConfigChangeResult;
026import org.forgerock.opendj.ldap.ByteString;
027import org.forgerock.i18n.LocalizableMessage;
028import org.forgerock.i18n.LocalizableMessageBuilder;
029import org.forgerock.opendj.config.server.ConfigurationChangeListener;
030import org.forgerock.opendj.server.config.server.UniqueCharactersPasswordValidatorCfg;
031import org.opends.server.api.PasswordValidator;
032import org.opends.server.types.*;
033
034/**
035 * This class provides an OpenDS password validator that may be used to ensure
036 * that proposed passwords contain at least a specified number of different
037 * characters.
038 */
039public class UniqueCharactersPasswordValidator
040       extends PasswordValidator<UniqueCharactersPasswordValidatorCfg>
041       implements ConfigurationChangeListener<
042                       UniqueCharactersPasswordValidatorCfg>
043{
044  /** The current configuration for this password validator. */
045  private UniqueCharactersPasswordValidatorCfg currentConfig;
046
047  /** Creates a new instance of this unique characters password validator. */
048  public UniqueCharactersPasswordValidator()
049  {
050    super();
051
052    // No implementation is required here.  All initialization should be
053    // performed in the initializePasswordValidator() method.
054  }
055
056  @Override
057  public void initializePasswordValidator(
058                   UniqueCharactersPasswordValidatorCfg configuration)
059  {
060    configuration.addUniqueCharactersChangeListener(this);
061    currentConfig = configuration;
062  }
063
064  @Override
065  public void finalizePasswordValidator()
066  {
067    currentConfig.removeUniqueCharactersChangeListener(this);
068  }
069
070  @Override
071  public boolean passwordIsAcceptable(ByteString newPassword,
072                                      Set<ByteString> currentPasswords,
073                                      Operation operation, Entry userEntry,
074                                      LocalizableMessageBuilder invalidReason)
075  {
076    // Get a handle to the current configuration and see if we need to count
077    // the number of unique characters in the password.
078    UniqueCharactersPasswordValidatorCfg config = currentConfig;
079    int minUniqueCharacters = config.getMinUniqueCharacters();
080    if (minUniqueCharacters <= 0)
081    {
082      // We don't need to check anything, so the password will be acceptable.
083      return true;
084    }
085
086    // Create a set that will be used to keep track of the unique characters
087    // contained in the proposed password.
088    HashSet<Character> passwordCharacters = new HashSet<>();
089
090    // Iterate through the characters in the new password and place them in the
091    // set as needed.  If we should behave in a case-insensitive manner, then
092    // convert all the characters to lowercase first.
093    String passwordString = newPassword.toString();
094    if (! config.isCaseSensitiveValidation())
095    {
096      passwordString = passwordString.toLowerCase();
097    }
098
099    for (int i=0; i < passwordString.length(); i++)
100    {
101      passwordCharacters.add(passwordString.charAt(i));
102    }
103
104    // If the size of the password characters set is less than the minimum
105    // number of allowed unique characters, then we will reject the password.
106    if (passwordCharacters.size() < minUniqueCharacters)
107    {
108      LocalizableMessage message = ERR_UNIQUECHARS_VALIDATOR_NOT_ENOUGH_UNIQUE_CHARS.get(
109              minUniqueCharacters);
110      invalidReason.append(message);
111      return false;
112    }
113
114    return true;
115  }
116
117  @Override
118  public boolean isConfigurationChangeAcceptable(
119                      UniqueCharactersPasswordValidatorCfg configuration,
120                      List<LocalizableMessage> unacceptableReasons)
121  {
122    // All of the necessary validation should have been performed automatically,
123    // so if we get to this point then the new configuration will be acceptable.
124    return true;
125  }
126
127  @Override
128  public ConfigChangeResult applyConfigurationChange(
129                      UniqueCharactersPasswordValidatorCfg configuration)
130  {
131    final ConfigChangeResult ccr = new ConfigChangeResult();
132
133    // For this password validator, we will always be able to successfully apply
134    // the new configuration.
135    currentConfig = configuration;
136
137    return ccr;
138  }
139}