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.kernel.exception.PortalException;
018    import com.liferay.portal.kernel.exception.UserPasswordException;
019    import com.liferay.portal.kernel.model.PasswordPolicy;
020    import com.liferay.portal.kernel.model.User;
021    import com.liferay.portal.kernel.security.RandomUtil;
022    import com.liferay.portal.kernel.security.SecureRandom;
023    import com.liferay.portal.kernel.security.pwd.BasicToolkit;
024    import com.liferay.portal.kernel.service.PasswordTrackerLocalServiceUtil;
025    import com.liferay.portal.kernel.service.UserLocalServiceUtil;
026    import com.liferay.portal.kernel.util.ArrayUtil;
027    import com.liferay.portal.kernel.util.PwdGenerator;
028    import com.liferay.portal.kernel.util.StringBundler;
029    import com.liferay.portal.kernel.util.Validator;
030    import com.liferay.portal.kernel.words.WordsUtil;
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    /**
038     * @author Scott Lee
039     * @author Mika Koivisto
040     */
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    }