001
014
015 package com.liferay.portal.security.pwd;
016
017 import com.liferay.portal.exception.UserPasswordException;
018 import com.liferay.portal.kernel.exception.PortalException;
019 import com.liferay.portal.kernel.security.RandomUtil;
020 import com.liferay.portal.kernel.security.SecureRandom;
021 import com.liferay.portal.kernel.security.pwd.BasicToolkit;
022 import com.liferay.portal.kernel.util.ArrayUtil;
023 import com.liferay.portal.kernel.util.PwdGenerator;
024 import com.liferay.portal.kernel.util.StringBundler;
025 import com.liferay.portal.kernel.util.Validator;
026 import com.liferay.portal.kernel.words.WordsUtil;
027 import com.liferay.portal.model.PasswordPolicy;
028 import com.liferay.portal.model.User;
029 import com.liferay.portal.service.PasswordTrackerLocalServiceUtil;
030 import com.liferay.portal.service.UserLocalServiceUtil;
031 import com.liferay.portal.util.PropsValues;
032
033 import java.util.Arrays;
034 import java.util.Date;
035 import java.util.Random;
036
037
041 public class PasswordPolicyToolkit extends BasicToolkit {
042
043 public PasswordPolicyToolkit() {
044 _generatorLowerCaseCharsetArray = getSortedCharArray(
045 PropsValues.
046 PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_LOWERCASE);
047 _generatorNumbersCharsetArray = getSortedCharArray(
048 PropsValues.
049 PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_NUMBERS);
050 _generatorSymbolsCharsetArray = getSortedCharArray(
051 PropsValues.
052 PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_SYMBOLS);
053 _generatorUpperCaseCharsetArray = getSortedCharArray(
054 PropsValues.
055 PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_UPPERCASE);
056
057 _generatorAlphanumericCharsetArray = ArrayUtil.append(
058 _generatorLowerCaseCharsetArray, _generatorUpperCaseCharsetArray,
059 _generatorNumbersCharsetArray);
060
061 Arrays.sort(_generatorAlphanumericCharsetArray);
062
063 StringBundler sb = new StringBundler(4);
064
065 sb.append(
066 PropsValues.
067 PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_LOWERCASE);
068 sb.append(
069 PropsValues.
070 PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_NUMBERS);
071 sb.append(
072 PropsValues.
073 PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_SYMBOLS);
074 sb.append(
075 PropsValues.
076 PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR_CHARSET_UPPERCASE);
077
078 _generatorCompleteCharset = sb.toString();
079
080 _validatorLowerCaseCharsetArray = getSortedCharArray(
081 PropsValues.
082 PASSWORDS_PASSWORDPOLICYTOOLKIT_VALIDATOR_CHARSET_LOWERCASE);
083 _validatorNumbersCharsetArray = getSortedCharArray(
084 PropsValues.
085 PASSWORDS_PASSWORDPOLICYTOOLKIT_VALIDATOR_CHARSET_NUMBERS);
086 _validatorSymbolsCharsetArray = getSortedCharArray(
087 PropsValues.
088 PASSWORDS_PASSWORDPOLICYTOOLKIT_VALIDATOR_CHARSET_SYMBOLS);
089 _validatorUpperCaseCharsetArray = getSortedCharArray(
090 PropsValues.
091 PASSWORDS_PASSWORDPOLICYTOOLKIT_VALIDATOR_CHARSET_UPPERCASE);
092
093 _validatorAlphanumericCharsetArray = ArrayUtil.append(
094 _validatorLowerCaseCharsetArray, _validatorUpperCaseCharsetArray,
095 _validatorNumbersCharsetArray);
096
097 Arrays.sort(_validatorAlphanumericCharsetArray);
098 }
099
100 @Override
101 public String generate(PasswordPolicy passwordPolicy) {
102 if (PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR.equals(
103 "static")) {
104
105 return generateStatic(passwordPolicy);
106 }
107 else {
108 return generateDynamic(passwordPolicy);
109 }
110 }
111
112 @Override
113 public void validate(
114 long userId, String password1, String password2,
115 PasswordPolicy passwordPolicy)
116 throws PortalException {
117
118 if (passwordPolicy.isCheckSyntax()) {
119 if (!passwordPolicy.isAllowDictionaryWords() &&
120 WordsUtil.isDictionaryWord(password1)) {
121
122 throw new UserPasswordException.MustNotContainDictionaryWords(
123 userId, WordsUtil.getDictionaryList());
124 }
125
126 if (password1.length() < passwordPolicy.getMinLength()) {
127 throw new UserPasswordException.MustBeLonger(
128 userId, passwordPolicy.getMinLength());
129 }
130
131 if ((getUsageCount(password1, _validatorAlphanumericCharsetArray) <
132 passwordPolicy.getMinAlphanumeric()) ||
133 (getUsageCount(password1, _validatorLowerCaseCharsetArray) <
134 passwordPolicy.getMinLowerCase()) ||
135 (getUsageCount(password1, _validatorNumbersCharsetArray) <
136 passwordPolicy.getMinNumbers()) ||
137 (getUsageCount(password1, _validatorSymbolsCharsetArray) <
138 passwordPolicy.getMinSymbols()) ||
139 (getUsageCount(password1, _validatorUpperCaseCharsetArray) <
140 passwordPolicy.getMinUpperCase())) {
141
142 throw new UserPasswordException.MustNotBeTrivial(userId);
143 }
144
145 String regex = passwordPolicy.getRegex();
146
147 if (Validator.isNotNull(regex) && !password1.matches(regex)) {
148 throw new UserPasswordException.MustComplyWithRegex(
149 userId, regex);
150 }
151 }
152
153 if (!passwordPolicy.isChangeable() && (userId != 0)) {
154 throw new UserPasswordException.MustNotBeChanged(userId);
155 }
156
157 if (userId == 0) {
158 return;
159 }
160
161 User user = UserLocalServiceUtil.getUserById(userId);
162
163 Date passwordModfiedDate = user.getPasswordModifiedDate();
164
165 if (passwordModfiedDate != null) {
166 Date now = new Date();
167
168 long passwordModificationElapsedTime =
169 now.getTime() - passwordModfiedDate.getTime();
170
171 long minAge = passwordPolicy.getMinAge() * 1000;
172
173 if ((passwordModificationElapsedTime < minAge) &&
174 !user.getPasswordReset()) {
175
176 throw new UserPasswordException.MustNotBeChangedYet(
177 userId, new Date(passwordModfiedDate.getTime() + minAge));
178 }
179 }
180
181 if (PasswordTrackerLocalServiceUtil.isSameAsCurrentPassword(
182 userId, password1)) {
183
184 throw new UserPasswordException.MustNotBeEqualToCurrent(userId);
185 }
186 else if (!PasswordTrackerLocalServiceUtil.isValidPassword(
187 userId, password1)) {
188
189 throw new UserPasswordException.MustNotBeRecentlyUsed(userId);
190 }
191 }
192
193 protected String generateDynamic(PasswordPolicy passwordPolicy) {
194 int alphanumericActualMinLength =
195 passwordPolicy.getMinLowerCase() + passwordPolicy.getMinNumbers() +
196 passwordPolicy.getMinUpperCase();
197
198 int alphanumericMinLength = Math.max(
199 passwordPolicy.getMinAlphanumeric(), alphanumericActualMinLength);
200 int passwordMinLength = Math.max(
201 passwordPolicy.getMinLength(),
202 alphanumericMinLength + passwordPolicy.getMinSymbols());
203
204 StringBundler sb = new StringBundler(6);
205
206 if (passwordPolicy.getMinLowerCase() > 0) {
207 sb.append(
208 getRandomString(
209 passwordPolicy.getMinLowerCase(),
210 _generatorLowerCaseCharsetArray));
211 }
212
213 if (passwordPolicy.getMinNumbers() > 0) {
214 sb.append(
215 getRandomString(
216 passwordPolicy.getMinNumbers(),
217 _generatorNumbersCharsetArray));
218 }
219
220 if (passwordPolicy.getMinSymbols() > 0) {
221 sb.append(
222 getRandomString(
223 passwordPolicy.getMinSymbols(),
224 _generatorSymbolsCharsetArray));
225 }
226
227 if (passwordPolicy.getMinUpperCase() > 0) {
228 sb.append(
229 getRandomString(
230 passwordPolicy.getMinUpperCase(),
231 _generatorUpperCaseCharsetArray));
232 }
233
234 if (alphanumericMinLength > alphanumericActualMinLength) {
235 int count = alphanumericMinLength - alphanumericActualMinLength;
236
237 sb.append(
238 getRandomString(count, _generatorAlphanumericCharsetArray));
239 }
240
241 if (passwordMinLength >
242 (alphanumericMinLength + passwordPolicy.getMinSymbols())) {
243
244 int count =
245 passwordMinLength -
246 (alphanumericMinLength + passwordPolicy.getMinSymbols());
247
248 sb.append(
249 PwdGenerator.getPassword(_generatorCompleteCharset, count));
250 }
251
252 if (sb.index() == 0) {
253 sb.append(
254 PwdGenerator.getPassword(
255 _generatorCompleteCharset,
256 PropsValues.PASSWORDS_DEFAULT_POLICY_MIN_LENGTH));
257 }
258
259 return RandomUtil.shuffle(new SecureRandom(), sb.toString());
260 }
261
262 protected String generateStatic(PasswordPolicy passwordPolicy) {
263 return PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_STATIC;
264 }
265
266 protected String getRandomString(int count, char[] chars) {
267 Random random = new SecureRandom();
268
269 StringBundler sb = new StringBundler(count);
270
271 for (int i = 0; i < count; i++) {
272 int index = random.nextInt(chars.length);
273
274 sb.append(chars[index]);
275 }
276
277 return sb.toString();
278 }
279
280 protected char[] getSortedCharArray(String s) {
281 char[] chars = s.toCharArray();
282
283 Arrays.sort(chars);
284
285 return chars;
286 }
287
288 protected int getUsageCount(String s, char[] chars) {
289 int count = 0;
290
291 for (int i = 0; i < s.length(); i++) {
292 if (Arrays.binarySearch(chars, s.charAt(i)) >= 0) {
293 count++;
294 }
295 }
296
297 return count;
298 }
299
300 private final char[] _generatorAlphanumericCharsetArray;
301 private final String _generatorCompleteCharset;
302 private final char[] _generatorLowerCaseCharsetArray;
303 private final char[] _generatorNumbersCharsetArray;
304 private final char[] _generatorSymbolsCharsetArray;
305 private final char[] _generatorUpperCaseCharsetArray;
306 private final char[] _validatorAlphanumericCharsetArray;
307 private final char[] _validatorLowerCaseCharsetArray;
308 private final char[] _validatorNumbersCharsetArray;
309 private final char[] _validatorSymbolsCharsetArray;
310 private final char[] _validatorUpperCaseCharsetArray;
311
312 }