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}