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.List;
032import java.util.Set;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.forgerock.i18n.LocalizableMessageBuilder;
036import org.forgerock.opendj.config.server.ConfigChangeResult;
037import org.forgerock.opendj.config.server.ConfigException;
038import org.forgerock.opendj.ldap.ByteString;
039import org.opends.server.api.PasswordValidator;
040import org.opends.server.types.*;
041import org.opends.server.util.LevenshteinDistance;
042import org.opends.server.admin.std.server.SimilarityBasedPasswordValidatorCfg;
043import org.opends.server.admin.server.ConfigurationChangeListener;
044
045/**
046 * This class provides a password validator that can ensure that the provided
047 * password meets minimum similarity requirements.
048 */
049public class SimilarityBasedPasswordValidator extends
050    PasswordValidator<SimilarityBasedPasswordValidatorCfg> implements
051    ConfigurationChangeListener<SimilarityBasedPasswordValidatorCfg>
052{
053
054  /** The current configuration for this password validator. */
055  private SimilarityBasedPasswordValidatorCfg currentConfig;
056
057
058  /**
059   * Creates a new instance of this password validator.
060   */
061  public SimilarityBasedPasswordValidator()
062  {
063    super();
064
065
066    // All initialization must be done in the initializePasswordValidator
067    // method.
068  }
069
070  /** {@inheritDoc} */
071  @Override
072  public void initializePasswordValidator(
073                   SimilarityBasedPasswordValidatorCfg configuration)
074         throws ConfigException, InitializationException
075  {
076    configuration.addSimilarityBasedChangeListener(this);
077
078    currentConfig = configuration;
079  }
080
081  /** {@inheritDoc} */
082  @Override
083  public void finalizePasswordValidator()
084  {
085    currentConfig.removeSimilarityBasedChangeListener(this);
086  }
087
088
089
090  /** {@inheritDoc} */
091  @Override
092  public boolean passwordIsAcceptable(ByteString newPassword,
093                                      Set<ByteString> currentPasswords,
094                                      Operation operation, Entry userEntry,
095                                      LocalizableMessageBuilder invalidReason)  {
096
097    int minDifference = currentConfig.getMinPasswordDifference();
098    ByteString passwd = newPassword == null
099                        ? ByteString.empty()
100                        : newPassword;
101
102    if (currentPasswords == null || currentPasswords.isEmpty()) {
103      // This validator requires access to at least one current password.
104      // If we don't have a current password, then we can't validate it, so
105      // we'll have to assume it is OK.  Ideally, the password policy should be
106      // configured to always require the current password, but even then the
107      // current password probably won't be available during an administrative
108      // password reset.
109      return true;
110    }
111
112    for (ByteString bs : currentPasswords) {
113        if (bs == null) {
114            continue;
115        }
116        int ldistance = LevenshteinDistance.calculate(passwd.toString(),
117                                                      bs.toString());
118        if (ldistance < minDifference) {
119          invalidReason.append(ERR_PWDIFFERENCEVALIDATOR_TOO_SMALL.get(
120                  minDifference));
121          return false;
122        }
123    }
124
125    return true;
126  }
127
128  /** {@inheritDoc} */
129  public boolean isConfigurationChangeAcceptable(
130                      SimilarityBasedPasswordValidatorCfg configuration,
131                      List<LocalizableMessage> unacceptableReasons)
132  {
133    return true;
134  }
135
136  /** {@inheritDoc} */
137  public ConfigChangeResult applyConfigurationChange(
138              SimilarityBasedPasswordValidatorCfg configuration)
139  {
140    currentConfig = configuration;
141    return new ConfigChangeResult();
142  }
143}