001    /**
002     * Copyright (c) 2000-2013 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.UserPasswordException;
018    import com.liferay.portal.kernel.exception.PortalException;
019    import com.liferay.portal.kernel.exception.SystemException;
020    import com.liferay.portal.kernel.security.SecureRandomUtil;
021    import com.liferay.portal.kernel.util.ArrayUtil;
022    import com.liferay.portal.kernel.util.StringBundler;
023    import com.liferay.portal.kernel.util.Validator;
024    import com.liferay.portal.model.PasswordPolicy;
025    import com.liferay.portal.model.User;
026    import com.liferay.portal.service.PasswordTrackerLocalServiceUtil;
027    import com.liferay.portal.service.UserLocalServiceUtil;
028    import com.liferay.portal.util.PropsValues;
029    import com.liferay.portal.words.WordsUtil;
030    import com.liferay.util.PwdGenerator;
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                    _lowerCaseCharsetArray = getSortedCharArray(
044                            PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_CHARSET_LOWERCASE);
045                    _numbersCharsetArray = getSortedCharArray(
046                            PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_CHARSET_NUMBERS);
047                    _symbolsCharsetArray = getSortedCharArray(
048                            PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_CHARSET_SYMBOLS);
049                    _upperCaseCharsetArray = getSortedCharArray(
050                            PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_CHARSET_UPPERCASE);
051    
052                    _alphanumericCharsetArray = ArrayUtil.append(
053                            _lowerCaseCharsetArray, _upperCaseCharsetArray,
054                            _numbersCharsetArray);
055    
056                    Arrays.sort(_alphanumericCharsetArray);
057    
058                    StringBundler sb = new StringBundler(4);
059    
060                    sb.append(
061                            PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_CHARSET_LOWERCASE);
062                    sb.append(PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_CHARSET_NUMBERS);
063                    sb.append(PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_CHARSET_SYMBOLS);
064                    sb.append(
065                            PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_CHARSET_UPPERCASE);
066    
067                    _completeCharset = sb.toString();
068            }
069    
070            @Override
071            public String generate(PasswordPolicy passwordPolicy) {
072                    if (PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_GENERATOR.equals(
073                                    "static")) {
074    
075                            return generateStatic(passwordPolicy);
076                    }
077                    else {
078                            return generateDynamic(passwordPolicy);
079                    }
080            }
081    
082            @Override
083            public void validate(
084                            long userId, String password1, String password2,
085                            PasswordPolicy passwordPolicy)
086                    throws PortalException, SystemException {
087    
088                    if (passwordPolicy.isCheckSyntax()) {
089                            if (!passwordPolicy.isAllowDictionaryWords() &&
090                                    WordsUtil.isDictionaryWord(password1)) {
091    
092                                    throw new UserPasswordException(
093                                            UserPasswordException.PASSWORD_CONTAINS_TRIVIAL_WORDS);
094                            }
095    
096                            if (password1.length() < passwordPolicy.getMinLength()) {
097                                    throw new UserPasswordException(
098                                            UserPasswordException.PASSWORD_LENGTH);
099                            }
100    
101                            if ((getUsageCount(password1, _alphanumericCharsetArray) <
102                                            passwordPolicy.getMinAlphanumeric()) ||
103                                    (getUsageCount(password1, _lowerCaseCharsetArray) <
104                                            passwordPolicy.getMinLowerCase()) ||
105                                    (getUsageCount(password1, _numbersCharsetArray) <
106                                            passwordPolicy.getMinNumbers()) ||
107                                    (getUsageCount(password1, _symbolsCharsetArray) <
108                                            passwordPolicy.getMinSymbols()) ||
109                                    (getUsageCount(password1, _upperCaseCharsetArray) <
110                                            passwordPolicy.getMinUpperCase())) {
111    
112                                    throw new UserPasswordException(
113                                            UserPasswordException.PASSWORD_TOO_TRIVIAL);
114                            }
115    
116                            if (Validator.isNotNull(passwordPolicy.getRegex()) &&
117                                    !password1.matches(passwordPolicy.getRegex())) {
118    
119                                    throw new UserPasswordException(
120                                            UserPasswordException.PASSWORD_INVALID);
121                            }
122                    }
123    
124                    if (!passwordPolicy.isChangeable()) {
125                            throw new UserPasswordException(
126                                    UserPasswordException.PASSWORD_NOT_CHANGEABLE);
127                    }
128    
129                    if (userId == 0) {
130                            return;
131                    }
132    
133                    User user = UserLocalServiceUtil.getUserById(userId);
134    
135                    Date passwordModfiedDate = user.getPasswordModifiedDate();
136    
137                    if (passwordModfiedDate != null) {
138    
139                            // LEP-2961
140    
141                            Date now = new Date();
142    
143                            long passwordModificationElapsedTime =
144                                    now.getTime() - passwordModfiedDate.getTime();
145    
146                            long userCreationElapsedTime =
147                                    now.getTime() - user.getCreateDate().getTime();
148    
149                            long minAge = passwordPolicy.getMinAge() * 1000;
150    
151                            if ((passwordModificationElapsedTime < minAge) &&
152                                    (userCreationElapsedTime > minAge)) {
153    
154                                    throw new UserPasswordException(
155                                            UserPasswordException.PASSWORD_TOO_YOUNG);
156                            }
157                    }
158    
159                    if (PasswordTrackerLocalServiceUtil.isSameAsCurrentPassword(
160                                    userId, password1)) {
161    
162                            throw new UserPasswordException(
163                                    UserPasswordException.PASSWORD_SAME_AS_CURRENT);
164                    }
165                    else if (!PasswordTrackerLocalServiceUtil.isValidPassword(
166                                            userId, password1)) {
167    
168                            throw new UserPasswordException(
169                                    UserPasswordException.PASSWORD_ALREADY_USED);
170                    }
171            }
172    
173            protected String generateDynamic(PasswordPolicy passwordPolicy) {
174                    int alphanumericActualMinLength =
175                            passwordPolicy.getMinLowerCase() + passwordPolicy.getMinNumbers() +
176                                    passwordPolicy.getMinUpperCase();
177    
178                    int alphanumericMinLength = Math.max(
179                            passwordPolicy.getMinAlphanumeric(), alphanumericActualMinLength);
180                    int passwordMinLength = Math.max(
181                            passwordPolicy.getMinLength(),
182                            alphanumericMinLength + passwordPolicy.getMinSymbols());
183    
184                    StringBundler sb = new StringBundler(6);
185    
186                    if (passwordPolicy.getMinLowerCase() > 0) {
187                            sb.append(
188                                    getRandomString(
189                                            passwordPolicy.getMinLowerCase(), _lowerCaseCharsetArray));
190                    }
191    
192                    if (passwordPolicy.getMinNumbers() > 0) {
193                            sb.append(
194                                    getRandomString(
195                                            passwordPolicy.getMinNumbers(), _numbersCharsetArray));
196                    }
197    
198                    if (passwordPolicy.getMinSymbols() > 0) {
199                            sb.append(
200                                    getRandomString(
201                                            passwordPolicy.getMinSymbols(), _symbolsCharsetArray));
202                    }
203    
204                    if (passwordPolicy.getMinUpperCase() > 0) {
205                            sb.append(
206                                    getRandomString(
207                                            passwordPolicy.getMinUpperCase(), _upperCaseCharsetArray));
208                    }
209    
210                    if (alphanumericMinLength > alphanumericActualMinLength) {
211                            int count = alphanumericMinLength - alphanumericActualMinLength;
212    
213                            sb.append(getRandomString(count, _alphanumericCharsetArray));
214                    }
215    
216                    if (passwordMinLength >
217                                    (alphanumericMinLength + passwordPolicy.getMinSymbols())) {
218    
219                            int count =
220                                    passwordMinLength -
221                                            (alphanumericMinLength + passwordPolicy.getMinSymbols());
222    
223                            sb.append(PwdGenerator.getPassword(_completeCharset, count));
224                    }
225    
226                    if (sb.index() == 0) {
227                            sb.append(
228                                    PwdGenerator.getPassword(
229                                            _completeCharset,
230                                            PropsValues.PASSWORDS_DEFAULT_POLICY_MIN_LENGTH));
231                    }
232    
233                    return PwdGenerator.shuffle(
234                            new Random(SecureRandomUtil.nextLong()), sb.toString());
235            }
236    
237            protected String generateStatic(PasswordPolicy passwordPolicy) {
238                    return PropsValues.PASSWORDS_PASSWORDPOLICYTOOLKIT_STATIC;
239            }
240    
241            protected String getRandomString(int count, char[] chars) {
242                    StringBundler sb = new StringBundler(count);
243    
244                    for (int i = 0; i < count; i++) {
245                            int index = Math.abs(SecureRandomUtil.nextInt()) % chars.length;
246    
247                            sb.append(chars[index]);
248                    }
249    
250                    return sb.toString();
251            }
252    
253            protected char[] getSortedCharArray(String s) {
254                    char[] chars = s.toCharArray();
255    
256                    Arrays.sort(chars);
257    
258                    return chars;
259            }
260    
261            protected int getUsageCount(String s, char[] chars) {
262                    int count = 0;
263    
264                    for (int i = 0; i < s.length(); i++) {
265                            if (Arrays.binarySearch(chars, s.charAt(i)) >= 0) {
266                                    count++;
267                            }
268                    }
269    
270                    return count;
271            }
272    
273            private char[] _alphanumericCharsetArray;
274            private String _completeCharset;
275            private char[] _lowerCaseCharsetArray;
276            private char[] _numbersCharsetArray;
277            private char[] _symbolsCharsetArray;
278            private char[] _upperCaseCharsetArray;
279    
280    }