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.List;
022import java.util.Set;
023
024import org.forgerock.opendj.config.server.ConfigChangeResult;
025import org.forgerock.opendj.ldap.ByteString;
026import org.forgerock.i18n.LocalizableMessageBuilder;
027import org.forgerock.i18n.LocalizableMessage;
028
029import org.opends.server.admin.server.ConfigurationChangeListener;
030import org.opends.server.admin.std.server.
031            RepeatedCharactersPasswordValidatorCfg;
032import org.opends.server.api.PasswordValidator;
033import org.opends.server.types.*;
034
035/**
036 * This class provides an OpenDS password validator that may be used to ensure
037 * that proposed passwords are not allowed to have the same character appear
038 * several times consecutively.
039 */
040public class RepeatedCharactersPasswordValidator
041       extends PasswordValidator<RepeatedCharactersPasswordValidatorCfg>
042       implements ConfigurationChangeListener<
043                       RepeatedCharactersPasswordValidatorCfg>
044{
045  /** The current configuration for this password validator. */
046  private RepeatedCharactersPasswordValidatorCfg currentConfig;
047
048
049
050  /**
051   * Creates a new instance of this repeated characters password validator.
052   */
053  public RepeatedCharactersPasswordValidator()
054  {
055    super();
056
057    // No implementation is required here.  All initialization should be
058    // performed in the initializePasswordValidator() method.
059  }
060
061
062
063  /** {@inheritDoc} */
064  @Override
065  public void initializePasswordValidator(
066                   RepeatedCharactersPasswordValidatorCfg configuration)
067  {
068    configuration.addRepeatedCharactersChangeListener(this);
069    currentConfig = configuration;
070  }
071
072
073
074  /** {@inheritDoc} */
075  @Override
076  public void finalizePasswordValidator()
077  {
078    currentConfig.removeRepeatedCharactersChangeListener(this);
079  }
080
081
082
083  /** {@inheritDoc} */
084  @Override
085  public boolean passwordIsAcceptable(ByteString newPassword,
086                                      Set<ByteString> currentPasswords,
087                                      Operation operation, Entry userEntry,
088                                      LocalizableMessageBuilder invalidReason)
089  {
090    // Get a handle to the current configuration and see if we need to count
091    // the number of repeated characters in the password.
092    RepeatedCharactersPasswordValidatorCfg config = currentConfig;
093    int maxRepeats = config.getMaxConsecutiveLength();
094    if (maxRepeats <= 0)
095    {
096      // We don't need to check anything, so the password will be acceptable.
097      return true;
098    }
099
100
101    // Get the password as a string.  If we should use case-insensitive
102    // validation, then convert it to use all lowercase characters.
103    String passwordString = newPassword.toString();
104    if (! config.isCaseSensitiveValidation())
105    {
106      passwordString = passwordString.toLowerCase();
107    }
108
109
110    // Create variables to keep track of the last character we've seen and how
111    // many times we have seen it.
112    char lastCharacter    = '\u0000';
113    int  consecutiveCount = 0;
114
115
116    // Iterate through the characters in the password.  If the consecutive
117    // count ever gets too high, then fail.
118    for (int i=0; i < passwordString.length(); i++)
119    {
120      char currentCharacter = passwordString.charAt(i);
121      if (currentCharacter == lastCharacter)
122      {
123        consecutiveCount++;
124        if (consecutiveCount > maxRepeats)
125        {
126          LocalizableMessage message =
127                  ERR_REPEATEDCHARS_VALIDATOR_TOO_MANY_CONSECUTIVE.get(
128                          maxRepeats);
129          invalidReason.append(message);
130          return false;
131        }
132      }
133      else
134      {
135        lastCharacter    = currentCharacter;
136        consecutiveCount = 1;
137      }
138    }
139
140    return true;
141  }
142
143
144
145  /** {@inheritDoc} */
146  public boolean isConfigurationChangeAcceptable(
147                      RepeatedCharactersPasswordValidatorCfg configuration,
148                      List<LocalizableMessage> unacceptableReasons)
149  {
150    // All of the necessary validation should have been performed automatically,
151    // so if we get to this point then the new configuration will be acceptable.
152    return true;
153  }
154
155
156
157  /** {@inheritDoc} */
158  public ConfigChangeResult applyConfigurationChange(
159                      RepeatedCharactersPasswordValidatorCfg configuration)
160  {
161    // For this password validator, we will always be able to successfully apply
162    // the new configuration.
163    currentConfig = configuration;
164    return new ConfigChangeResult();
165  }
166}