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
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.extensions;
028 import org.opends.messages.Message;
029
030
031
032 import java.util.ArrayList;
033 import java.util.HashMap;
034 import java.util.HashSet;
035 import java.util.List;
036 import java.util.Set;
037
038 import org.opends.server.admin.server.ConfigurationChangeListener;
039 import org.opends.server.admin.std.server.CharacterSetPasswordValidatorCfg;
040 import org.opends.server.admin.std.server.PasswordValidatorCfg;
041 import org.opends.server.api.PasswordValidator;
042 import org.opends.server.config.ConfigException;
043 import org.opends.server.types.ConfigChangeResult;
044 import org.opends.server.types.ByteString;
045 import org.opends.server.types.DirectoryConfig;
046 import org.opends.server.types.Entry;
047 import org.opends.server.types.Operation;
048 import org.opends.server.types.ResultCode;
049
050 import static org.opends.messages.ExtensionMessages.*;
051 import org.opends.messages.MessageBuilder;
052 import static org.opends.server.util.StaticUtils.*;
053
054
055
056 /**
057 * This class provides an OpenDS password validator that may be used to ensure
058 * that proposed passwords contain at least a specified number of characters
059 * from one or more user-defined character sets.
060 */
061 public class CharacterSetPasswordValidator
062 extends PasswordValidator<CharacterSetPasswordValidatorCfg>
063 implements ConfigurationChangeListener<CharacterSetPasswordValidatorCfg>
064 {
065 // The current configuration for this password validator.
066 private CharacterSetPasswordValidatorCfg currentConfig;
067
068 // A mapping between the character sets and the minimum number of characters
069 // required for each.
070 private HashMap<String,Integer> characterSets;
071
072
073
074 /**
075 * Creates a new instance of this character set password validator.
076 */
077 public CharacterSetPasswordValidator()
078 {
079 super();
080
081 // No implementation is required here. All initialization should be
082 // performed in the initializePasswordValidator() method.
083 }
084
085
086
087 /**
088 * {@inheritDoc}
089 */
090 @Override()
091 public void initializePasswordValidator(
092 CharacterSetPasswordValidatorCfg configuration)
093 throws ConfigException
094 {
095 configuration.addCharacterSetChangeListener(this);
096 currentConfig = configuration;
097
098 // Make sure that each of the character set definitions are acceptable.
099 characterSets = processCharacterSets(configuration);
100 }
101
102
103
104 /**
105 * {@inheritDoc}
106 */
107 @Override()
108 public void finalizePasswordValidator()
109 {
110 currentConfig.removeCharacterSetChangeListener(this);
111 }
112
113
114
115 /**
116 * {@inheritDoc}
117 */
118 @Override()
119 public boolean passwordIsAcceptable(ByteString newPassword,
120 Set<ByteString> currentPasswords,
121 Operation operation, Entry userEntry,
122 MessageBuilder invalidReason)
123 {
124 // Get a handle to the current configuration.
125 CharacterSetPasswordValidatorCfg config = currentConfig;
126 HashMap<String,Integer> characterSets = this.characterSets;
127
128
129 // Process the provided password.
130 String password = newPassword.stringValue();
131 HashMap<String,Integer> counts = new HashMap<String,Integer>();
132 for (int i=0; i < password.length(); i++)
133 {
134 char c = password.charAt(i);
135 boolean found = false;
136 for (String characterSet : characterSets.keySet())
137 {
138 if (characterSet.indexOf(c) >= 0)
139 {
140 Integer count = counts.get(characterSet);
141 if (count == null)
142 {
143 counts.put(characterSet, 1);
144 }
145 else
146 {
147 counts.put(characterSet, count+1);
148 }
149
150 found = true;
151 break;
152 }
153 }
154
155 if ((! found) && (! config.isAllowUnclassifiedCharacters()))
156 {
157 invalidReason.append(ERR_CHARSET_VALIDATOR_ILLEGAL_CHARACTER.get(
158 String.valueOf(c)));
159 return false;
160 }
161 }
162
163 for (String characterSet : characterSets.keySet())
164 {
165 int minimumCount = characterSets.get(characterSet);
166 Integer passwordCount = counts.get(characterSet);
167 if ((passwordCount == null) || (passwordCount < minimumCount))
168 {
169 invalidReason.append(ERR_CHARSET_VALIDATOR_TOO_FEW_CHARS_FROM_SET.get(
170 characterSet, minimumCount));
171 return false;
172 }
173 }
174
175
176 // If we've gotten here, then the password is acceptable.
177 return true;
178 }
179
180
181
182 /**
183 * Parses the provided configuration and extracts the character set
184 * definitions and associated minimum counts from them.
185 *
186 * @param configuration the configuration for this password validator.
187 *
188 * @return The mapping between strings of character set values and the
189 * minimum number of characters required from those sets.
190 *
191 * @throws ConfigException If any of the character set definitions cannot be
192 * parsed, or if there are any characters present in
193 * multiple sets.
194 */
195 private HashMap<String,Integer>
196 processCharacterSets(
197 CharacterSetPasswordValidatorCfg configuration)
198 throws ConfigException
199 {
200 HashMap<String,Integer> characterSets = new HashMap<String,Integer>();
201 HashSet<Character> usedCharacters = new HashSet<Character>();
202
203 for (String definition : configuration.getCharacterSet())
204 {
205 int colonPos = definition.indexOf(':');
206 if (colonPos <= 0)
207 {
208 Message message = ERR_CHARSET_VALIDATOR_NO_COLON.get(definition);
209 throw new ConfigException(message);
210 }
211 else if (colonPos == (definition.length() - 1))
212 {
213 Message message = ERR_CHARSET_VALIDATOR_NO_CHARS.get(definition);
214 throw new ConfigException(message);
215 }
216
217 int minCount;
218 try
219 {
220 minCount = Integer.parseInt(definition.substring(0, colonPos));
221 }
222 catch (Exception e)
223 {
224 Message message = ERR_CHARSET_VALIDATOR_INVALID_COUNT.get(definition);
225 throw new ConfigException(message);
226 }
227
228 if (minCount <= 0)
229 {
230 Message message = ERR_CHARSET_VALIDATOR_INVALID_COUNT.get(definition);
231 throw new ConfigException(message);
232 }
233
234 String characterSet = definition.substring(colonPos+1);
235 for (int i=0; i < characterSet.length(); i++)
236 {
237 char c = characterSet.charAt(i);
238 if (usedCharacters.contains(c))
239 {
240 Message message = ERR_CHARSET_VALIDATOR_DUPLICATE_CHAR.get(
241 definition, String.valueOf(c));
242 throw new ConfigException(message);
243 }
244
245 usedCharacters.add(c);
246 }
247
248 characterSets.put(characterSet, minCount);
249 }
250
251 return characterSets;
252 }
253
254
255
256 /**
257 * {@inheritDoc}
258 */
259 @Override()
260 public boolean isConfigurationAcceptable(PasswordValidatorCfg configuration,
261 List<Message> unacceptableReasons)
262 {
263 CharacterSetPasswordValidatorCfg config =
264 (CharacterSetPasswordValidatorCfg) configuration;
265 return isConfigurationChangeAcceptable(config, unacceptableReasons);
266 }
267
268
269
270 /**
271 * {@inheritDoc}
272 */
273 public boolean isConfigurationChangeAcceptable(
274 CharacterSetPasswordValidatorCfg configuration,
275 List<Message> unacceptableReasons)
276 {
277 // Make sure that we can process the defined character sets. If so, then
278 // we'll accept the new configuration.
279 try
280 {
281 processCharacterSets(configuration);
282 }
283 catch (ConfigException ce)
284 {
285 unacceptableReasons.add(ce.getMessageObject());
286 return false;
287 }
288
289 return true;
290 }
291
292
293
294 /**
295 * {@inheritDoc}
296 */
297 public ConfigChangeResult applyConfigurationChange(
298 CharacterSetPasswordValidatorCfg configuration)
299 {
300 ResultCode resultCode = ResultCode.SUCCESS;
301 boolean adminActionRequired = false;
302 ArrayList<Message> messages = new ArrayList<Message>();
303
304
305 // Make sure that we can process the defined character sets. If so, then
306 // activate the new configuration.
307 try
308 {
309 characterSets = processCharacterSets(configuration);
310 currentConfig = configuration;
311 }
312 catch (Exception e)
313 {
314 resultCode = DirectoryConfig.getServerErrorResultCode();
315 messages.add(getExceptionMessage(e));
316 }
317
318 return new ConfigChangeResult(resultCode, adminActionRequired, messages);
319 }
320 }
321