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.opends.server.admin.server.ConfigurationChangeListener;
025import org.opends.server.admin.std.server.AttributeValuePasswordValidatorCfg;
026import org.opends.server.admin.std.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
049
050  /**
051   * Creates a new instance of this attribute value password validator.
052   */
053  public AttributeValuePasswordValidator()
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                   AttributeValuePasswordValidatorCfg configuration)
067  {
068    configuration.addAttributeValueChangeListener(this);
069    currentConfig = configuration;
070  }
071
072
073
074  /** {@inheritDoc} */
075  @Override
076  public void finalizePasswordValidator()
077  {
078    currentConfig.removeAttributeValueChangeListener(this);
079  }
080
081
082
083  /**
084   * Search for substrings of the password in an Attribute. The search is
085   * case-insensitive.
086   *
087   * @param password the password
088   * @param minSubstringLength the minimum substring length to check
089   * @param a the attribute to search
090   * @return true if an attribute value matches a substring of the password,
091   * false otherwise.
092   */
093  private boolean containsSubstring(String password, int minSubstringLength,
094      Attribute a)
095  {
096    final int passwordLength = password.length();
097
098    for (int i = 0; i < passwordLength; i++)
099    {
100      for (int j = i + minSubstringLength; j <= passwordLength; j++)
101      {
102        Attribute substring = Attributes.create(a.getAttributeDescription().getAttributeType(),
103            password.substring(i, j));
104        for (ByteString val : a)
105        {
106          if (substring.contains(val))
107          {
108            return true;
109          }
110        }
111      }
112    }
113    return false;
114  }
115
116
117
118  /** {@inheritDoc} */
119  @Override
120  public boolean passwordIsAcceptable(ByteString newPassword,
121                                      Set<ByteString> currentPasswords,
122                                      Operation operation, Entry userEntry,
123                                      LocalizableMessageBuilder invalidReason)
124  {
125    // Get a handle to the current configuration.
126    AttributeValuePasswordValidatorCfg config = currentConfig;
127
128
129    // Get the string representation (both forward and reversed) for the password.
130    final String password = newPassword.toString();
131    final String reversed = new StringBuilder(password).reverse().toString();
132
133    // Check to see if we should verify the whole password or the substrings.
134    int minSubstringLength = password.length();
135    if (config.isCheckSubstrings()
136        // We apply the minimal substring length only if the provided value
137        // is smaller then the actual password length
138        && config.getMinSubstringLength() < password.length())
139    {
140      minSubstringLength = config.getMinSubstringLength();
141    }
142
143    // If we should check a specific set of attributes, then do that now.
144    // Otherwise, check all user attributes.
145    Set<AttributeType> matchAttributes = config.getMatchAttribute();
146    if (matchAttributes == null || matchAttributes.isEmpty())
147    {
148      matchAttributes = userEntry.getUserAttributes().keySet();
149    }
150
151    final ByteString vf = ByteString.valueOfUtf8(password);
152    final ByteString vr = ByteString.valueOfUtf8(reversed);
153    for (AttributeType t : matchAttributes)
154    {
155      for (Attribute a : userEntry.getAttribute(t))
156      {
157        if (a.contains(vf) ||
158            (config.isTestReversedPassword() && a.contains(vr)) ||
159            (config.isCheckSubstrings() &&
160                containsSubstring(password, minSubstringLength, a)))
161        {
162          invalidReason.append(ERR_ATTRVALUE_VALIDATOR_PASSWORD_IN_ENTRY.get());
163          return false;
164        }
165      }
166    }
167
168
169    // If we've gotten here, then the password is acceptable.
170    return true;
171  }
172
173
174
175  /** {@inheritDoc} */
176  @Override
177  public boolean isConfigurationAcceptable(PasswordValidatorCfg configuration,
178                                           List<LocalizableMessage> unacceptableReasons)
179  {
180    AttributeValuePasswordValidatorCfg config =
181         (AttributeValuePasswordValidatorCfg) configuration;
182    return isConfigurationChangeAcceptable(config, unacceptableReasons);
183  }
184
185
186
187  /** {@inheritDoc} */
188  public boolean isConfigurationChangeAcceptable(
189                      AttributeValuePasswordValidatorCfg configuration,
190                      List<LocalizableMessage> unacceptableReasons)
191  {
192    // If we've gotten this far, then we'll accept the change.
193    return true;
194  }
195
196
197
198  /** {@inheritDoc} */
199  public ConfigChangeResult applyConfigurationChange(
200                      AttributeValuePasswordValidatorCfg configuration)
201  {
202    currentConfig = configuration;
203    return new ConfigChangeResult();
204  }
205}