1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.objects.classes

File PasswordClass.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart8.png
54% of files have more coverage

Code metrics

38
101
19
1
330
217
47
0.47
5.32
19
2.47

Classes

Class Line # Actions
PasswordClass 39 101 0% 47 43
0.727848172.8%
 

Contributing tests

This file is covered by 8 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.objects.classes;
21   
22    import java.security.MessageDigest;
23    import java.security.NoSuchAlgorithmException;
24    import java.security.SecureRandom;
25   
26    import org.apache.commons.lang3.StringUtils;
27    import org.apache.ecs.xhtml.input;
28    import org.slf4j.Logger;
29    import org.slf4j.LoggerFactory;
30   
31    import com.xpn.xwiki.XWikiContext;
32    import com.xpn.xwiki.internal.xml.XMLAttributeValueFilter;
33    import com.xpn.xwiki.objects.BaseCollection;
34    import com.xpn.xwiki.objects.BaseProperty;
35    import com.xpn.xwiki.objects.ElementInterface;
36    import com.xpn.xwiki.objects.meta.PasswordMetaClass;
37    import com.xpn.xwiki.objects.meta.PropertyMetaClass;
38   
 
39    public class PasswordClass extends StringClass
40    {
41    private static final String XCLASSNAME = "password";
42   
43    protected static Logger LOGGER = LoggerFactory.getLogger(PasswordClass.class);
44   
45    protected static final String DEFAULT_STORAGE = PasswordMetaClass.HASH;
46   
47    protected static final String DEFAULT_HASH_ALGORITHM = "SHA-512";
48   
49    protected static final String DEFAULT_CRYPT_ALGORITHM = "AES";
50   
51    protected static final String HASH_IDENTIFIER = "hash";
52   
53    protected static final String CRYPT_IDENTIFIER = "crypt";
54   
55    protected static final String SEPARATOR = ":";
56   
57    protected static final String FORM_PASSWORD_PLACEHODLER = "********";
58   
 
59  2981 toggle public PasswordClass(PropertyMetaClass wclass)
60    {
61  2981 super(XCLASSNAME, "Password", wclass);
62  2981 setxWikiClass(wclass);
63    }
64   
 
65  2981 toggle public PasswordClass()
66    {
67  2981 this(null);
68    }
69   
 
70  36 toggle @Override
71    public BaseProperty fromString(String value)
72    {
73  36 if (value.equals(FORM_PASSWORD_PLACEHODLER)) {
74  0 return null;
75    }
76  36 BaseProperty property = newProperty();
77  36 if (value.isEmpty() || value.startsWith(HASH_IDENTIFIER + SEPARATOR)
78    || value.startsWith(CRYPT_IDENTIFIER + SEPARATOR)) {
79  3 property.setValue(value);
80    } else {
81  33 property.setValue(getProcessedPassword(value));
82    }
83  36 return property;
84    }
85   
 
86  0 toggle @Override
87    public void displayHidden(StringBuffer buffer, String name, String prefix, BaseCollection object,
88    XWikiContext context)
89    {
90    // Passwords cannot go through the preview interface, so we don't do something here..
91    }
92   
 
93  0 toggle @Override
94    public void displayView(StringBuffer buffer, String name, String prefix, BaseCollection object, XWikiContext context)
95    {
96  0 ElementInterface prop = object.safeget(name);
97  0 if (prop != null) {
98  0 buffer.append(FORM_PASSWORD_PLACEHODLER);
99    }
100    }
101   
 
102  125 toggle @Override
103    public void displayEdit(StringBuffer buffer, String name, String prefix, BaseCollection object, XWikiContext context)
104    {
105  125 input input = new input();
106  125 input.setAttributeFilter(new XMLAttributeValueFilter());
107  125 BaseProperty prop = (BaseProperty) object.safeget(name);
108    // Only display the obfuscation placeholder is the value is not empty to not confuse users into thinking that
109    // the property is set.
110  125 if (prop != null && !StringUtils.isEmpty(prop.toText())) {
111  0 input.setValue(FORM_PASSWORD_PLACEHODLER);
112    }
113   
114  125 input.setType("password");
115  125 input.setName(prefix + name);
116  125 input.setID(prefix + name);
117  125 input.setSize(getSize());
118  125 input.setDisabled(isDisabled());
119  125 buffer.append(input.toString());
120    }
121   
122    /**
123    * @return One of 'Clear', 'Hash' or 'Encrypt'.
124    */
 
125  33 toggle public String getStorageType()
126    {
127  33 BaseProperty st = (BaseProperty) this.getField(PasswordMetaClass.STORAGE_TYPE);
128  33 if (st != null) {
129  2 String type = st.getValue().toString().trim();
130  2 if (!type.equals("")) {
131  2 return type;
132    }
133    }
134  31 return DEFAULT_STORAGE;
135    }
136   
137    /**
138    * @return The hash algorithm configured for this XProperty.
139    */
 
140  33 toggle public String getHashAlgorithm()
141    {
142  33 BaseProperty alg = (BaseProperty) this.getField(PasswordMetaClass.ALGORITHM_KEY);
143  33 if (alg != null && alg.getValue() != null && !alg.getValue().toString().trim().equals("")) {
144  0 return alg.getValue().toString();
145    }
146  33 return DEFAULT_HASH_ALGORITHM;
147    }
148   
149    /**
150    * @return The encryption algorithm configured for this XProperty.
151    */
 
152  0 toggle public String getCryptAlgorithm()
153    {
154  0 BaseProperty alg = (BaseProperty) this.getField(PasswordMetaClass.ALGORITHM_KEY);
155  0 if (alg != null && alg.getValue() != null && !alg.getValue().toString().trim().equals("")) {
156  0 return alg.getValue().toString();
157    }
158  0 return DEFAULT_CRYPT_ALGORITHM;
159    }
160   
161    /**
162    * @param password
163    * @return The algorithm used for the given password.
164    */
 
165  2643 toggle public String getAlgorithmFromPassword(String password)
166    {
167  2643 int beginIndex = password.indexOf(SEPARATOR) + 1;
168  2643 if (beginIndex >= 0) {
169  2643 int endIndex = password.indexOf(SEPARATOR, beginIndex);
170  2643 if (endIndex >= 0) {
171  2643 return password.substring(beginIndex, endIndex);
172    }
173    }
174  0 return DEFAULT_HASH_ALGORITHM;
175    }
176   
177    /**
178    * @param password
179    * @return The salt used for the given password. If this is an unsalted password, let it be known by returning "".
180    */
 
181  2643 toggle public String getSaltFromPassword(String password)
182    {
183  2643 String[] components = password.split(SEPARATOR);
184  2643 if (components.length == 4) {
185  2643 return components[2];
186    } else {
187  0 return "";
188    }
189    }
190   
191    /**
192    * Transforms a plain text password so that it has the same encryption as a password stored in the database. The
193    * current configuration for this password XProperty cannot be used, as the user might have a different encryption
194    * mechanism (for example, if the user was imported, or the password was not yet upgraded).
195    *
196    * @param storedPassword The stored password, which gives the storage type and algorithm.
197    * @param plainPassword The plain text password to be encrypted.
198    * @return The input password, encrypted with the same mechanism as the stored password.
199    */
 
200  2648 toggle public String getEquivalentPassword(String storedPassword, String plainPassword)
201    {
202  2648 String result = plainPassword;
203  2648 if (storedPassword.startsWith(HASH_IDENTIFIER + SEPARATOR)) {
204  2643 result =
205    getPasswordHash(result, getAlgorithmFromPassword(storedPassword), getSaltFromPassword(storedPassword));
206  5 } else if (storedPassword.startsWith(CRYPT_IDENTIFIER + SEPARATOR)) {
207  0 result = getPasswordCrypt(result, getAlgorithmFromPassword(storedPassword));
208    }
209  2648 return result;
210    }
211   
 
212  33 toggle public String getProcessedPassword(String password)
213    {
214  33 String storageType = getStorageType();
215  33 String result = password;
216  33 if (storageType.equals(PasswordMetaClass.HASH)) {
217  33 result = getPasswordHash(result);
218  0 } else if (storageType.equals(PasswordMetaClass.ENCRYPTED)) {
219  0 result = getPasswordCrypt(result);
220    }
221  33 return result;
222    }
223   
 
224  0 toggle public String getPasswordCrypt(String password)
225    {
226  0 return getPasswordCrypt(password, getCryptAlgorithm());
227    }
228   
 
229  0 toggle public String getPasswordCrypt(String password, String algorithmName)
230    {
231    // TODO Write me!
232  0 return password;
233    }
234   
235    /**
236    * @param password the password to hash.
237    * @return a string of the form {@code hash:<algorithmName>:<salt>:<hexStrignHash>}, where {@code <algorithmName>} is
238    * the default hashing algorithm (see {@link #DEFAULT_HASH_ALGORITHM}), {@code <salt>} is a random 64 character
239    * salt and {@code <hexStrignHash>} is the salted hash of the given password, using the given hashing algorithm.
240    */
 
241  33 toggle public String getPasswordHash(String password)
242    {
243  33 return getPasswordHash(password, getHashAlgorithm(), null);
244    }
245   
246    /**
247    * @param password the password to hash.
248    * @param algorithmName the name of the hashing algorithm to use. See {@link MessageDigest#getInstance(String)}.
249    * @return a string of the form {@code hash:<algorithmName>:<salt>:<hexStrignHash>}, where {@code <salt>} is a random
250    * 64 character salt and {@code <hexStrignHash>} is the salted hash of the given password, using the given
251    * hashing algorithm.
252    */
 
253  0 toggle public String getPasswordHash(String password, String algorithmName)
254    {
255  0 return getPasswordHash(password, algorithmName, null);
256    }
257   
258    /**
259    * @param password the password to hash.
260    * @param algorithmName the name of the hashing algorithm to use. See {@link MessageDigest#getInstance(String)}.
261    * @param salt the string to pad the password with before hashing. If {@code null}, a random 64 character salt will
262    * be used. To disable salting, use an empty ({@code ""}) salt string.
263    * @return a string of the form {@code hash:<algorithmName>:<salt>:<hexStrignHash>}, where {@code <hexStrignHash>} is
264    * the salted hash of the given password, using the given hashing algorithm.
265    * @since 6.3M2
266    */
 
267  2676 toggle public String getPasswordHash(String password, String algorithmName, String salt)
268    {
269    // If no salt given, let's generate one.
270  2676 if (salt == null) {
271  33 salt = randomSalt();
272    }
273   
274  2676 try {
275  2676 LOGGER.debug("Hashing password");
276   
277  2676 String saltedPassword = salt + password;
278   
279  2676 MessageDigest hashAlgorithm = MessageDigest.getInstance(algorithmName);
280  2676 hashAlgorithm.update(saltedPassword.getBytes());
281  2676 byte[] digest = hashAlgorithm.digest();
282   
283    // Build the result.
284  2676 StringBuilder sb = new StringBuilder();
285    // Metadata
286  2676 sb.append(HASH_IDENTIFIER);
287  2676 sb.append(SEPARATOR);
288  2676 sb.append(algorithmName);
289  2676 sb.append(SEPARATOR);
290    // Backward compatibility concern : let's keep unsalted password the way they are.
291  2676 if (!salt.equals("")) {
292  2676 sb.append(salt);
293  2676 sb.append(SEPARATOR);
294    }
295    // The actual password hash.
296  2676 for (byte element : digest) {
297  171264 int b = element & 0xFF;
298  171264 if (b < 0x10) {
299  8076 sb.append('0');
300    }
301  171264 sb.append(Integer.toHexString(b));
302    }
303   
304  2676 return sb.toString();
305    } catch (NoSuchAlgorithmException ex) {
306  0 LOGGER.error("Wrong hash algorithm [{}] specified in property [{}] of class [{}]", algorithmName,
307    getName(), getXClassReference(), ex);
308    } catch (NullPointerException ex) {
309  0 LOGGER.error("Error hashing password", ex);
310    }
311  0 return password;
312    }
313   
 
314  33 toggle public static String randomSalt()
315    {
316  33 StringBuilder salt = new StringBuilder();
317  33 SecureRandom random = new SecureRandom();
318  33 byte bytes[] = new byte[32];
319  33 random.nextBytes(bytes);
320  33 for (byte temp : bytes) {
321  1056 String s = Integer.toHexString(new Byte(temp));
322  1116 while (s.length() < 2) {
323  60 s = "0" + s;
324    }
325  1056 s = s.substring(s.length() - 2);
326  1056 salt.append(s);
327    }
328  33 return salt.toString();
329    }
330    }