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.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.forgerock.opendj.config.server.ConfigurationChangeListener;
030import org.forgerock.opendj.server.config.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  /** Creates a new instance of this repeated characters password validator. */
049  public RepeatedCharactersPasswordValidator()
050  {
051    super();
052
053    // No implementation is required here.  All initialization should be
054    // performed in the initializePasswordValidator() method.
055  }
056
057  @Override
058  public void initializePasswordValidator(
059                   RepeatedCharactersPasswordValidatorCfg configuration)
060  {
061    configuration.addRepeatedCharactersChangeListener(this);
062    currentConfig = configuration;
063  }
064
065  @Override
066  public void finalizePasswordValidator()
067  {
068    currentConfig.removeRepeatedCharactersChangeListener(this);
069  }
070
071  @Override
072  public boolean passwordIsAcceptable(ByteString newPassword,
073                                      Set<ByteString> currentPasswords,
074                                      Operation operation, Entry userEntry,
075                                      LocalizableMessageBuilder invalidReason)
076  {
077    // Get a handle to the current configuration and see if we need to count
078    // the number of repeated characters in the password.
079    RepeatedCharactersPasswordValidatorCfg config = currentConfig;
080    int maxRepeats = config.getMaxConsecutiveLength();
081    if (maxRepeats <= 0)
082    {
083      // We don't need to check anything, so the password will be acceptable.
084      return true;
085    }
086
087    // Get the password as a string.  If we should use case-insensitive
088    // validation, then convert it to use all lowercase characters.
089    String passwordString = newPassword.toString();
090    if (! config.isCaseSensitiveValidation())
091    {
092      passwordString = passwordString.toLowerCase();
093    }
094
095    // Create variables to keep track of the last character we've seen and how
096    // many times we have seen it.
097    char lastCharacter    = '\u0000';
098    int  consecutiveCount = 0;
099
100    // Iterate through the characters in the password.  If the consecutive
101    // count ever gets too high, then fail.
102    for (int i=0; i < passwordString.length(); i++)
103    {
104      char currentCharacter = passwordString.charAt(i);
105      if (currentCharacter == lastCharacter)
106      {
107        consecutiveCount++;
108        if (consecutiveCount > maxRepeats)
109        {
110          LocalizableMessage message =
111                  ERR_REPEATEDCHARS_VALIDATOR_TOO_MANY_CONSECUTIVE.get(
112                          maxRepeats);
113          invalidReason.append(message);
114          return false;
115        }
116      }
117      else
118      {
119        lastCharacter    = currentCharacter;
120        consecutiveCount = 1;
121      }
122    }
123
124    return true;
125  }
126
127  @Override
128  public boolean isConfigurationChangeAcceptable(
129                      RepeatedCharactersPasswordValidatorCfg configuration,
130                      List<LocalizableMessage> unacceptableReasons)
131  {
132    // All of the necessary validation should have been performed automatically,
133    // so if we get to this point then the new configuration will be acceptable.
134    return true;
135  }
136
137  @Override
138  public ConfigChangeResult applyConfigurationChange(
139                      RepeatedCharactersPasswordValidatorCfg configuration)
140  {
141    // For this password validator, we will always be able to successfully apply
142    // the new configuration.
143    currentConfig = configuration;
144    return new ConfigChangeResult();
145  }
146}