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-2015 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.opends.server.admin.server.ConfigurationChangeListener;
030import org.opends.server.admin.std.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
048
049  /**
050   * Creates a new instance of this unique characters password validator.
051   */
052  public UniqueCharactersPasswordValidator()
053  {
054    super();
055
056    // No implementation is required here.  All initialization should be
057    // performed in the initializePasswordValidator() method.
058  }
059
060
061
062  /** {@inheritDoc} */
063  @Override
064  public void initializePasswordValidator(
065                   UniqueCharactersPasswordValidatorCfg configuration)
066  {
067    configuration.addUniqueCharactersChangeListener(this);
068    currentConfig = configuration;
069  }
070
071
072
073  /** {@inheritDoc} */
074  @Override
075  public void finalizePasswordValidator()
076  {
077    currentConfig.removeUniqueCharactersChangeListener(this);
078  }
079
080
081
082  /** {@inheritDoc} */
083  @Override
084  public boolean passwordIsAcceptable(ByteString newPassword,
085                                      Set<ByteString> currentPasswords,
086                                      Operation operation, Entry userEntry,
087                                      LocalizableMessageBuilder invalidReason)
088  {
089    // Get a handle to the current configuration and see if we need to count
090    // the number of unique characters in the password.
091    UniqueCharactersPasswordValidatorCfg config = currentConfig;
092    int minUniqueCharacters = config.getMinUniqueCharacters();
093    if (minUniqueCharacters <= 0)
094    {
095      // We don't need to check anything, so the password will be acceptable.
096      return true;
097    }
098
099
100
101    // Create a set that will be used to keep track of the unique characters
102    // contained in the proposed password.
103    HashSet<Character> passwordCharacters = new HashSet<>();
104
105    // Iterate through the characters in the new password and place them in the
106    // set as needed.  If we should behave in a case-insensitive manner, then
107    // convert all the characters to lowercase first.
108    String passwordString = newPassword.toString();
109    if (! config.isCaseSensitiveValidation())
110    {
111      passwordString = passwordString.toLowerCase();
112    }
113
114    for (int i=0; i < passwordString.length(); i++)
115    {
116      passwordCharacters.add(passwordString.charAt(i));
117    }
118
119    // If the size of the password characters set is less than the minimum
120    // number of allowed unique characters, then we will reject the password.
121    if (passwordCharacters.size() < minUniqueCharacters)
122    {
123      LocalizableMessage message = ERR_UNIQUECHARS_VALIDATOR_NOT_ENOUGH_UNIQUE_CHARS.get(
124              minUniqueCharacters);
125      invalidReason.append(message);
126      return false;
127    }
128
129    return true;
130  }
131
132
133
134  /** {@inheritDoc} */
135  public boolean isConfigurationChangeAcceptable(
136                      UniqueCharactersPasswordValidatorCfg configuration,
137                      List<LocalizableMessage> unacceptableReasons)
138  {
139    // All of the necessary validation should have been performed automatically,
140    // so if we get to this point then the new configuration will be acceptable.
141    return true;
142  }
143
144
145
146  /** {@inheritDoc} */
147  public ConfigChangeResult applyConfigurationChange(
148                      UniqueCharactersPasswordValidatorCfg configuration)
149  {
150    final ConfigChangeResult ccr = new ConfigChangeResult();
151
152    // For this password validator, we will always be able to successfully apply
153    // the new configuration.
154    currentConfig = configuration;
155
156    return ccr;
157  }
158}
159