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.opendj.config.server.ConfigChangeResult; 035import org.forgerock.opendj.ldap.ByteString; 036import org.forgerock.i18n.LocalizableMessageBuilder; 037import org.forgerock.i18n.LocalizableMessage; 038 039import org.opends.server.admin.server.ConfigurationChangeListener; 040import org.opends.server.admin.std.server. 041 RepeatedCharactersPasswordValidatorCfg; 042import org.opends.server.api.PasswordValidator; 043import org.opends.server.types.*; 044 045/** 046 * This class provides an OpenDS password validator that may be used to ensure 047 * that proposed passwords are not allowed to have the same character appear 048 * several times consecutively. 049 */ 050public class RepeatedCharactersPasswordValidator 051 extends PasswordValidator<RepeatedCharactersPasswordValidatorCfg> 052 implements ConfigurationChangeListener< 053 RepeatedCharactersPasswordValidatorCfg> 054{ 055 /** The current configuration for this password validator. */ 056 private RepeatedCharactersPasswordValidatorCfg currentConfig; 057 058 059 060 /** 061 * Creates a new instance of this repeated characters password validator. 062 */ 063 public RepeatedCharactersPasswordValidator() 064 { 065 super(); 066 067 // No implementation is required here. All initialization should be 068 // performed in the initializePasswordValidator() method. 069 } 070 071 072 073 /** {@inheritDoc} */ 074 @Override 075 public void initializePasswordValidator( 076 RepeatedCharactersPasswordValidatorCfg configuration) 077 { 078 configuration.addRepeatedCharactersChangeListener(this); 079 currentConfig = configuration; 080 } 081 082 083 084 /** {@inheritDoc} */ 085 @Override 086 public void finalizePasswordValidator() 087 { 088 currentConfig.removeRepeatedCharactersChangeListener(this); 089 } 090 091 092 093 /** {@inheritDoc} */ 094 @Override 095 public boolean passwordIsAcceptable(ByteString newPassword, 096 Set<ByteString> currentPasswords, 097 Operation operation, Entry userEntry, 098 LocalizableMessageBuilder invalidReason) 099 { 100 // Get a handle to the current configuration and see if we need to count 101 // the number of repeated characters in the password. 102 RepeatedCharactersPasswordValidatorCfg config = currentConfig; 103 int maxRepeats = config.getMaxConsecutiveLength(); 104 if (maxRepeats <= 0) 105 { 106 // We don't need to check anything, so the password will be acceptable. 107 return true; 108 } 109 110 111 // Get the password as a string. If we should use case-insensitive 112 // validation, then convert it to use all lowercase characters. 113 String passwordString = newPassword.toString(); 114 if (! config.isCaseSensitiveValidation()) 115 { 116 passwordString = passwordString.toLowerCase(); 117 } 118 119 120 // Create variables to keep track of the last character we've seen and how 121 // many times we have seen it. 122 char lastCharacter = '\u0000'; 123 int consecutiveCount = 0; 124 125 126 // Iterate through the characters in the password. If the consecutive 127 // count ever gets too high, then fail. 128 for (int i=0; i < passwordString.length(); i++) 129 { 130 char currentCharacter = passwordString.charAt(i); 131 if (currentCharacter == lastCharacter) 132 { 133 consecutiveCount++; 134 if (consecutiveCount > maxRepeats) 135 { 136 LocalizableMessage message = 137 ERR_REPEATEDCHARS_VALIDATOR_TOO_MANY_CONSECUTIVE.get( 138 maxRepeats); 139 invalidReason.append(message); 140 return false; 141 } 142 } 143 else 144 { 145 lastCharacter = currentCharacter; 146 consecutiveCount = 1; 147 } 148 } 149 150 return true; 151 } 152 153 154 155 /** {@inheritDoc} */ 156 public boolean isConfigurationChangeAcceptable( 157 RepeatedCharactersPasswordValidatorCfg configuration, 158 List<LocalizableMessage> unacceptableReasons) 159 { 160 // All of the necessary validation should have been performed automatically, 161 // so if we get to this point then the new configuration will be acceptable. 162 return true; 163 } 164 165 166 167 /** {@inheritDoc} */ 168 public ConfigChangeResult applyConfigurationChange( 169 RepeatedCharactersPasswordValidatorCfg configuration) 170 { 171 // For this password validator, we will always be able to successfully apply 172 // the new configuration. 173 currentConfig = configuration; 174 return new ConfigChangeResult(); 175 } 176}