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}