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 2012-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import org.forgerock.i18n.LocalizableMessage;
020
021import java.util.List;
022import java.util.Set;
023
024import org.forgerock.opendj.config.server.ConfigurationChangeListener;
025import org.forgerock.opendj.server.config.server.AttributeValuePasswordValidatorCfg;
026import org.forgerock.opendj.server.config.server.PasswordValidatorCfg;
027import org.opends.server.api.PasswordValidator;
028import org.forgerock.opendj.ldap.schema.AttributeType;
029import org.opends.server.types.*;
030import org.forgerock.opendj.config.server.ConfigChangeResult;
031import org.forgerock.opendj.ldap.ByteString;
032import static org.opends.messages.ExtensionMessages.*;
033import org.forgerock.i18n.LocalizableMessageBuilder;
034
035/**
036 * This class provides an OpenDS password validator that may be used to ensure
037 * that proposed passwords are not contained in another attribute in the user's
038 * entry.
039 */
040public class AttributeValuePasswordValidator
041       extends PasswordValidator<AttributeValuePasswordValidatorCfg>
042       implements ConfigurationChangeListener<
043                       AttributeValuePasswordValidatorCfg>
044{
045  /** The current configuration for this password validator. */
046  private AttributeValuePasswordValidatorCfg currentConfig;
047
048  /** Creates a new instance of this attribute value password validator. */
049  public AttributeValuePasswordValidator()
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                   AttributeValuePasswordValidatorCfg configuration)
060  {
061    configuration.addAttributeValueChangeListener(this);
062    currentConfig = configuration;
063  }
064
065  @Override
066  public void finalizePasswordValidator()
067  {
068    currentConfig.removeAttributeValueChangeListener(this);
069  }
070
071  /**
072   * Search for substrings of the password in an Attribute. The search is
073   * case-insensitive.
074   *
075   * @param password the password
076   * @param minSubstringLength the minimum substring length to check
077   * @param a the attribute to search
078   * @return true if an attribute value matches a substring of the password,
079   * false otherwise.
080   */
081  private boolean containsSubstring(String password, int minSubstringLength,
082      Attribute a)
083  {
084    final int passwordLength = password.length();
085
086    for (int i = 0; i < passwordLength; i++)
087    {
088      for (int j = i + minSubstringLength; j <= passwordLength; j++)
089      {
090        Attribute substring = Attributes.create(a.getAttributeDescription().getAttributeType(),
091            password.substring(i, j));
092        for (ByteString val : a)
093        {
094          if (substring.contains(val))
095          {
096            return true;
097          }
098        }
099      }
100    }
101    return false;
102  }
103
104  @Override
105  public boolean passwordIsAcceptable(ByteString newPassword,
106                                      Set<ByteString> currentPasswords,
107                                      Operation operation, Entry userEntry,
108                                      LocalizableMessageBuilder invalidReason)
109  {
110    // Get a handle to the current configuration.
111    AttributeValuePasswordValidatorCfg config = currentConfig;
112
113    // Get the string representation (both forward and reversed) for the password.
114    final String password = newPassword.toString();
115    final String reversed = new StringBuilder(password).reverse().toString();
116
117    // Check to see if we should verify the whole password or the substrings.
118    int minSubstringLength = password.length();
119    if (config.isCheckSubstrings()
120        // We apply the minimal substring length only if the provided value
121        // is smaller then the actual password length
122        && config.getMinSubstringLength() < password.length())
123    {
124      minSubstringLength = config.getMinSubstringLength();
125    }
126
127    // If we should check a specific set of attributes, then do that now.
128    // Otherwise, check all user attributes.
129    Set<AttributeType> matchAttributes = config.getMatchAttribute();
130    if (matchAttributes == null || matchAttributes.isEmpty())
131    {
132      matchAttributes = userEntry.getUserAttributes().keySet();
133    }
134
135    final ByteString vf = ByteString.valueOfUtf8(password);
136    final ByteString vr = ByteString.valueOfUtf8(reversed);
137    for (AttributeType t : matchAttributes)
138    {
139      for (Attribute a : userEntry.getAttribute(t))
140      {
141        if (a.contains(vf) ||
142            (config.isTestReversedPassword() && a.contains(vr)) ||
143            (config.isCheckSubstrings() &&
144                containsSubstring(password, minSubstringLength, a)))
145        {
146          invalidReason.append(ERR_ATTRVALUE_VALIDATOR_PASSWORD_IN_ENTRY.get());
147          return false;
148        }
149      }
150    }
151
152    // If we've gotten here, then the password is acceptable.
153    return true;
154  }
155
156  @Override
157  public boolean isConfigurationAcceptable(PasswordValidatorCfg configuration,
158                                           List<LocalizableMessage> unacceptableReasons)
159  {
160    AttributeValuePasswordValidatorCfg config =
161         (AttributeValuePasswordValidatorCfg) configuration;
162    return isConfigurationChangeAcceptable(config, unacceptableReasons);
163  }
164
165  @Override
166  public boolean isConfigurationChangeAcceptable(
167                      AttributeValuePasswordValidatorCfg configuration,
168                      List<LocalizableMessage> unacceptableReasons)
169  {
170    // If we've gotten this far, then we'll accept the change.
171    return true;
172  }
173
174  @Override
175  public ConfigChangeResult applyConfigurationChange(
176                      AttributeValuePasswordValidatorCfg configuration)
177  {
178    currentConfig = configuration;
179    return new ConfigChangeResult();
180  }
181}