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