001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
014    
015    package com.liferay.portal.security.pwd;
016    
017    import com.liferay.portal.UserPasswordException;
018    import com.liferay.portal.kernel.exception.PortalException;
019    import com.liferay.portal.kernel.exception.SystemException;
020    import com.liferay.portal.kernel.security.RandomUtil;
021    import com.liferay.portal.kernel.security.SecureRandom;
022    import com.liferay.portal.kernel.util.ArrayUtil;
023    import com.liferay.portal.kernel.util.StringBundler;
024    import com.liferay.portal.kernel.util.Validator;
025    import com.liferay.portal.model.PasswordPolicy;
026    import com.liferay.portal.model.User;
027    import com.liferay.portal.service.PasswordTrackerLocalServiceUtil;
028    import com.liferay.portal.service.UserLocalServiceUtil;
029    import com.liferay.portal.util.PropsValues;
030    import com.liferay.portal.words.WordsUtil;
031    import com.liferay.util.PwdGenerator;
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, SystemException {
117    
118                    if (passwordPolicy.isCheckSyntax()) {
119                            if (!passwordPolicy.isAllowDictionaryWords() &&
120                                    WordsUtil.isDictionaryWord(password1)) {
121    
122                                    throw new UserPasswordException(
123                                            UserPasswordException.PASSWORD_CONTAINS_TRIVIAL_WORDS);
124                            }
125    
126                            if (password1.length() < passwordPolicy.getMinLength()) {
127                                    throw new UserPasswordException(
128                                            UserPasswordException.PASSWORD_LENGTH);
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(
143                                            UserPasswordException.PASSWORD_TOO_TRIVIAL);
144                            }
145    
146                            if (Validator.isNotNull(passwordPolicy.getRegex()) &&
147                                    !password1.matches(passwordPolicy.getRegex())) {
148    
149                                    throw new UserPasswordException(
150                                            UserPasswordException.PASSWORD_INVALID);
151                            }
152                    }
153    
154                    if (!passwordPolicy.isChangeable() && (userId != 0)) {
155                            throw new UserPasswordException(
156                                    UserPasswordException.PASSWORD_NOT_CHANGEABLE);
157                    }
158    
159                    if (userId == 0) {
160                            return;
161                    }
162    
163                    User user = UserLocalServiceUtil.getUserById(userId);
164    
165                    Date passwordModfiedDate = user.getPasswordModifiedDate();
166    
167                    if (passwordModfiedDate != null) {
168                            Date now = new Date();
169    
170                            long passwordModificationElapsedTime =
171                                    now.getTime() - passwordModfiedDate.getTime();
172    
173                            long minAge = passwordPolicy.getMinAge() * 1000;
174    
175                            if ((passwordModificationElapsedTime < minAge) &&
176                                    !user.getPasswordReset()) {
177    
178                                    throw new UserPasswordException(
179                                            UserPasswordException.PASSWORD_TOO_YOUNG);
180                            }
181                    }
182    
183                    if (PasswordTrackerLocalServiceUtil.isSameAsCurrentPassword(
184                                    userId, password1)) {
185    
186                            throw new UserPasswordException(
187                                    UserPasswordException.PASSWORD_SAME_AS_CURRENT);
188                    }
189                    else if (!PasswordTrackerLocalServiceUtil.isValidPassword(
190                                            userId, password1)) {
191    
192                            throw new UserPasswordException(
193                                    UserPasswordException.PASSWORD_ALREADY_USED);
194                    }
195            }
196    
197            protected String generateDynamic(PasswordPolicy passwordPolicy) {
198                    int alphanumericActualMinLength =
199                            passwordPolicy.getMinLowerCase() + passwordPolicy.getMinNumbers() +
200                                    passwordPolicy.getMinUpperCase();
201    
202                    int alphanumericMinLength = Math.max(
203                            passwordPolicy.getMinAlphanumeric(), alphanumericActualMinLength);
204                    int passwordMinLength = Math.max(
205                            passwordPolicy.getMinLength(),
206                            alphanumericMinLength + passwordPolicy.getMinSymbols());
207    
208                    StringBundler sb = new StringBundler(6);
209    
210                    if (passwordPolicy.getMinLowerCase() > 0) {
211                            sb.append(
212                                    getRandomString(
213                                            passwordPolicy.getMinLowerCase(),
214                                            _generatorLowerCaseCharsetArray));
215                    }
216    
217                    if (passwordPolicy.getMinNumbers() > 0) {
218                            sb.append(
219                                    getRandomString(
220                                            passwordPolicy.getMinNumbers(),
221                                            _generatorNumbersCharsetArray));
222                    }
223    
224                    if (passwordPolicy.getMinSymbols() > 0) {
225                            sb.append(
226                                    getRandomString(
227                                            passwordPolicy.getMinSymbols(),
228                                            _generatorSymbolsCharsetArray));
229                    }
230    
231                    if (passwordPolicy.getMinUpperCase() > 0) {
232                            sb.append(
233                                    getRandomString(
234                                            passwordPolicy.getMinUpperCase(),
235                                            _generatorUpperCaseCharsetArray));
236                    }
237    
238                    if (alphanumericMinLength > alphanumericActualMinLength) {
239                            int count = alphanumericMinLength - alphanumericActualMinLength;
240    
241                            sb.append(
242                                    getRandomString(count, _generatorAlphanumericCharsetArray));
243                    }
244    
245                    if (passwordMinLength >
246                                    (alphanumericMinLength + passwordPolicy.getMinSymbols())) {
247    
248                            int count =
249                                    passwordMinLength -
250                                            (alphanumericMinLength + passwordPolicy.getMinSymbols());
251    
252                            sb.append(
253                                    PwdGenerator.getPassword(_generatorCompleteCharset, count));
254                    }
255    
256                    if (sb.index() == 0) {
257                            sb.append(
258                                    PwdGenerator.getPassword(
259                                            _generatorCompleteCharset,
260                                            PropsValues.PASSWORDS_DEFAULT_POLICY_MIN_LENGTH));
261                    }
262    
263                    return RandomUtil.shuffle(new SecureRandom(), sb.toString());
264            }
265    
266            protected String generateStatic(PasswordPolicy passwordPolicy) {
267                    return PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_STATIC;
268            }
269    
270            protected String getRandomString(int count, char[] chars) {
271                    Random random = new SecureRandom();
272    
273                    StringBundler sb = new StringBundler(count);
274    
275                    for (int i = 0; i < count; i++) {
276                            int index = random.nextInt(chars.length);
277    
278                            sb.append(chars[index]);
279                    }
280    
281                    return sb.toString();
282            }
283    
284            protected char[] getSortedCharArray(String s) {
285                    char[] chars = s.toCharArray();
286    
287                    Arrays.sort(chars);
288    
289                    return chars;
290            }
291    
292            protected int getUsageCount(String s, char[] chars) {
293                    int count = 0;
294    
295                    for (int i = 0; i < s.length(); i++) {
296                            if (Arrays.binarySearch(chars, s.charAt(i)) >= 0) {
297                                    count++;
298                            }
299                    }
300    
301                    return count;
302            }
303    
304            private char[] _generatorAlphanumericCharsetArray;
305            private String _generatorCompleteCharset;
306            private char[] _generatorLowerCaseCharsetArray;
307            private char[] _generatorNumbersCharsetArray;
308            private char[] _generatorSymbolsCharsetArray;
309            private char[] _generatorUpperCaseCharsetArray;
310            private char[] _validatorAlphanumericCharsetArray;
311            private char[] _validatorLowerCaseCharsetArray;
312            private char[] _validatorNumbersCharsetArray;
313            private char[] _validatorSymbolsCharsetArray;
314            private char[] _validatorUpperCaseCharsetArray;
315    
316    }