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