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