1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.user.impl.xwiki

File MyPersistentLoginManager.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart7.png
66% of files have more coverage

Code metrics

90
201
26
1
707
382
84
0.42
7.73
26
3.23

Classes

Class Line # Actions
MyPersistentLoginManager 60 201 0% 84 93
0.706624670.7%
 

Contributing tests

No tests hitting this source file were found.

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.user.impl.xwiki;
21   
22    import java.security.MessageDigest;
23    import java.text.DateFormat;
24    import java.text.SimpleDateFormat;
25    import java.util.Date;
26    import java.util.Locale;
27    import java.util.TimeZone;
28   
29    import javax.crypto.Cipher;
30    import javax.servlet.http.Cookie;
31    import javax.servlet.http.HttpServletRequest;
32    import javax.servlet.http.HttpServletResponse;
33   
34    import org.apache.commons.codec.binary.Base64;
35    import org.apache.commons.lang3.StringUtils;
36    import org.securityfilter.authenticator.persistent.DefaultPersistentLoginManager;
37    import org.securityfilter.filter.SecurityRequestWrapper;
38    import org.slf4j.Logger;
39    import org.slf4j.LoggerFactory;
40   
41    /**
42    * Class responsible for remembering the login information between requests. It uses (encrypted) cookies for this. The
43    * encryption key is stored in xwiki.cfg, xwiki.authentication.encryptionKey parameter. The cookies used are:
44    * <dl>
45    * <dt>username</dt>
46    * <dd>The logged in username</dd>
47    * <dt>password</dt>
48    * <dd>The password</dd>
49    * <dt>rememberme</dt>
50    * <dd>Whether or not the authentication information should be preserved across sessions</dd>
51    * <dt>validation</dt>
52    * <dd>Token used for validating the cookie information. It contains hashed information about the other cookies and a
53    * secret paramete, optionally binding with the current IP of the user (so that the cookie cannot be reused on another
54    * computer). This binding is enabled by the parameter xwiki.authentication.useip . The secret parameter is specified in
55    * xwiki.authentication.validationKey</dd>
56    * </dl>
57    *
58    * @version $Id: 34f0af0174f5115b73bd243f392263eb8dcfb2f0 $
59    */
 
60    public class MyPersistentLoginManager extends DefaultPersistentLoginManager
61    {
62    private static final long serialVersionUID = -8454351828032103173L;
63   
64    /**
65    * The string used to separate the fields in the hashed validation message.
66    */
67    private static final String FIELD_SEPARATOR = ":";
68   
69    /**
70    * The string used to prefix cookie domain to conform to RFC 2109.
71    */
72    private static final String COOKIE_DOT_PFX = ".";
73   
74    /**
75    * Log4J logger object to log messages in this class.
76    */
77    private static final Logger LOGGER = LoggerFactory.getLogger(MyPersistentLoginManager.class);
78   
79    /**
80    * Default value to use when getting the authentication cookie values.
81    */
82    private static final String DEFAULT_VALUE = "false";
83   
84    /** Date formatter for the cookie "Expires" value. */
85    private static final DateFormat COOKIE_EXPIRE_FORMAT = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z",
86    Locale.US);
 
87  39 toggle static {
88  39 COOKIE_EXPIRE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
89    }
90   
91    /** For performance, cache the often used epoch date which forces a cookie to be removed. */
92    private static final String COOKIE_EXPIRE_NOW = COOKIE_EXPIRE_FORMAT.format(new Date(0));
93   
94    /**
95    * The domain generalization for which the cookies are active. Configured by the xwiki.authentication.cookiedomains
96    * parameter. If a request comes from a host not in this list, then the cookie is valid only for the requested
97    * domain. If a request comes from a host that partially matches a domain in this list (meaning that the value in
98    * the list is contained in the requested domain), then the cookie is set for the more general value found in the
99    * list. This is useful for using the same account across multiple virtual wikis, for example.
100    */
101    protected String[] cookieDomains;
102   
103    /**
104    * The path for which the cookies are active. By default the cookie is active for all paths in the configured
105    * domains.
106    */
107    protected String cookiePath = "/";
108   
109    /**
110    * The prefix that should be used for cookie names.
111    */
112    protected String cookiePrefix = "";
113   
114    /**
115    * Default constructor. The configuration is done outside, in
116    * {@link XWikiAuthServiceImpl#getAuthenticator(com.xpn.xwiki.XWikiContext)}, so no parameters are needed at this
117    * point.
118    */
 
119  57 toggle public MyPersistentLoginManager()
120    {
121  56 super();
122    }
123   
124    /**
125    * Ensure cookie domains are prefixed with a dot to conform to RFC 2109.
126    *
127    * @param domain a cookie domain.
128    * @return a conform cookie domain.
129    */
 
130  0 toggle private String conformCookieDomain(String domain)
131    {
132  0 if (domain != null && !domain.startsWith(COOKIE_DOT_PFX)) {
133  0 return COOKIE_DOT_PFX.concat(domain);
134    } else {
135  0 return domain;
136    }
137    }
138   
139    /**
140    * Setter for the {@link #cookieDomains} parameter.
141    *
142    * @param cdlist The new value for {@link #cookieDomains}. The list is processed, so that any value not starting
143    * with a dot is prefixed with one, to respect the RFC 2109.
144    * @see #cookieDomains
145    */
 
146  58 toggle public void setCookieDomains(String[] cdlist)
147    {
148  56 if (cdlist != null && cdlist.length > 0) {
149  0 this.cookieDomains = new String[cdlist.length];
150  0 for (int i = 0; i < cdlist.length; ++i) {
151  0 this.cookieDomains[i] = conformCookieDomain(cdlist[i]);
152    }
153    } else {
154  55 this.cookieDomains = null;
155    }
156    }
157   
158    /**
159    * Setter for the {@link #cookiePath} parameter.
160    *
161    * @param cp The new value for {@link #cookiePath}.
162    * @see #cookiePath
163    */
 
164  0 toggle public void setCookiePath(String cp)
165    {
166  0 this.cookiePath = cp;
167    }
168   
169    /**
170    * Setup a cookie: expiration date, path, domain + send it to the response.
171    *
172    * @param cookie The cookie to setup.
173    * @param sessionCookie Whether the cookie is only for this session, or for a longer period.
174    * @param cookieDomain The domain for which the cookie is set.
175    * @param response The servlet response.
176    * @deprecated this shouldn't have been public, use
177    * {@link #setupCookie(Cookie, boolean, boolean, String, HttpServletResponse)}
178    */
 
179  0 toggle @Deprecated
180    public void setupCookie(Cookie cookie, boolean sessionCookie, String cookieDomain, HttpServletResponse response)
181    {
182  0 setupCookie(cookie, sessionCookie, false, cookieDomain, response);
183    }
184   
185    /**
186    * Setup a cookie: expiration date, path, domain + send it to the response.
187    *
188    * @param cookie the cookie to setup
189    * @param sessionCookie whether the cookie is only for this session, or for a longer period
190    * @param secureCookie whether the cookie should be marked as secure or not
191    * @param cookieDomain the domain for which the cookie is set
192    * @param response the servlet response
193    */
 
194  752 toggle private void setupCookie(Cookie cookie, boolean sessionCookie, boolean secureCookie, String cookieDomain,
195    HttpServletResponse response)
196    {
197  752 if (!sessionCookie) {
198  24 setMaxAge(cookie);
199    }
200  752 cookie.setSecure(secureCookie);
201  752 cookie.setPath(this.cookiePath);
202  752 if (cookieDomain != null) {
203  0 cookie.setDomain(cookieDomain);
204    }
205  752 addCookie(response, cookie);
206    }
207   
208    /**
209    * Remember a specific login using cookies.
210    *
211    * @param request The servlet request.
212    * @param response The servlet response.
213    * @param username The username that's being remembered.
214    * @param password The password that's being remembered.
215    */
 
216  188 toggle @Override
217    public void rememberLogin(HttpServletRequest request, HttpServletResponse response, String username, String password)
218    {
219  188 String protectedUsername = username;
220  188 String protectedPassword = password;
221  188 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_ENCRYPTION)) {
222  188 protectedUsername = encryptText(protectedUsername);
223  188 protectedPassword = encryptText(protectedPassword);
224  188 if (protectedUsername == null || protectedPassword == null) {
225  0 LOGGER.error("ERROR!!");
226  0 LOGGER.error("There was a problem encrypting the username or password!!");
227  0 LOGGER.error("Remember Me function will be disabled!!");
228  0 return;
229    }
230    }
231   
232    // Let's check if the cookies should be session cookies or persistent ones.
233  188 boolean sessionCookie = !(isTrue(request.getParameter("j_rememberme")));
234  188 boolean secureCookie = request.isSecure();
235  188 String cookieDomain = getCookieDomain(request);
236   
237    // Create client cookies to remember the login information.
238   
239    // Username
240  188 Cookie usernameCookie = new Cookie(getCookiePrefix() + COOKIE_USERNAME, protectedUsername);
241  188 setupCookie(usernameCookie, sessionCookie, secureCookie, cookieDomain, response);
242   
243    // Password
244  188 Cookie passwdCookie = new Cookie(getCookiePrefix() + COOKIE_PASSWORD, protectedPassword);
245  188 setupCookie(passwdCookie, sessionCookie, secureCookie, cookieDomain, response);
246   
247    // Remember me
248  188 Cookie rememberCookie = new Cookie(getCookiePrefix() + COOKIE_REMEMBERME, !sessionCookie + "");
249  188 setupCookie(rememberCookie, sessionCookie, secureCookie, cookieDomain, response);
250   
251  188 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_VALIDATION)) {
252  188 String validationHash = getValidationHash(protectedUsername, protectedPassword, getClientIP(request));
253  188 if (validationHash != null) {
254    // Validation
255  188 Cookie validationCookie = new Cookie(getCookiePrefix() + COOKIE_VALIDATION, validationHash);
256  188 setupCookie(validationCookie, sessionCookie, secureCookie, cookieDomain, response);
257    } else {
258  0 if (LOGGER.isErrorEnabled()) {
259  0 LOGGER.error("WARNING!!! WARNING!!!");
260  0 LOGGER.error("PROTECTION=ALL or PROTECTION=VALIDATION was specified");
261  0 LOGGER.error("but Validation Hash could NOT be generated");
262  0 LOGGER.error("Validation has been disabled!!!!");
263    }
264    }
265    }
266  188 return;
267    }
268   
269    /**
270    * Sets the maximum age for cookies. The maximum age is configured in xwiki.cfg using the
271    * xwiki.authentication.cookielife parameter (number of days). The default age is 14 days.
272    *
273    * @param cookie The cookie for which the expiration date is configured.
274    */
 
275  24 toggle private void setMaxAge(Cookie cookie)
276    {
277  24 try {
278  24 cookie.setMaxAge(Math.round(60 * 60 * 24 * Float.parseFloat(this.cookieLife)));
279    } catch (Exception e) {
280  0 if (LOGGER.isErrorEnabled()) {
281  0 LOGGER.error("Failed setting cookie Max age with duration " + this.cookieLife);
282    }
283    }
284    }
285   
286    /**
287    * Adds a cookie to the response.
288    *
289    * @param response The servlet response.
290    * @param cookie The cookie to be sent.
291    */
 
292  786 toggle private void addCookie(HttpServletResponse response, Cookie cookie)
293    {
294  786 if (LOGGER.isDebugEnabled()) {
295  0 LOGGER.debug("Adding cookie: " + cookie.getDomain() + cookie.getPath() + " " + cookie.getName() + "="
296    + cookie.getValue());
297    }
298    // We don't use the container's response.addCookie, since the HttpOnly cookie flag was introduced only recently
299    // in the servlet specification, and we're still using the older 2.4 specification as a minimal requirement for
300    // compatibility with as many containers as possible. Instead, we write the cookie manually as a HTTP header.
301  786 StringBuilder cookieValue = new StringBuilder(150);
302  786 cookieValue.append(cookie.getName() + "=");
303  786 if (StringUtils.isNotEmpty(cookie.getValue())) {
304  752 cookieValue.append("\"" + cookie.getValue() + "\"");
305    }
306  786 cookieValue.append("; Version=1");
307  786 if (cookie.getMaxAge() >= 0) {
308  58 cookieValue.append("; Max-Age=" + cookie.getMaxAge());
309    // IE is such a pain, it doesn't understand the modern, safer Max-Age
310  58 cookieValue.append("; Expires=");
311  58 if (cookie.getMaxAge() == 0) {
312  34 cookieValue.append(COOKIE_EXPIRE_NOW);
313    } else {
314  24 cookieValue.append(COOKIE_EXPIRE_FORMAT.format(new Date(System.currentTimeMillis() + cookie.getMaxAge()
315    * 1000L)));
316    }
317    }
318  786 if (StringUtils.isNotEmpty(cookie.getDomain())) {
319    // IE needs toLowerCase for the domain name
320  0 cookieValue.append("; Domain=" + cookie.getDomain().toLowerCase());
321    }
322  786 if (StringUtils.isNotEmpty(cookie.getPath())) {
323  786 cookieValue.append("; Path=" + cookie.getPath());
324    }
325    // Protect cookies from being used from JavaScript, see http://www.owasp.org/index.php/HttpOnly
326  786 cookieValue.append("; HttpOnly");
327    // Only send this cookie on HTTPS connections coming from a page in the same domain
328  786 if (cookie.getSecure()) {
329  0 cookieValue.append("; Secure");
330    }
331   
332    // Session cookies should be discarded.
333    // FIXME Safari 5 can't handle properly "Discard", as it really discards all the response header data after the
334    // first "Discard" encountered, so it will only see the first such cookie. Disabled for the moment until Safari
335    // gets fixed, or a better idea comes to mind.
336    // Since we don't set a Max-Age, the rfc2109 behavior will kick in, and recognize this as a session cookie.
337    // if (cookie.getMaxAge() < 0) {
338    // cookieValue.append("; Discard");
339    // }
340  786 response.addHeader("Set-Cookie", cookieValue.toString());
341    }
342   
343    /**
344    * Compute the actual domain the cookie is supposed to be set for. Search through the list of generalized domains
345    * for a partial match. If no match is found, then no specific domain is used, which means that the cookie will be
346    * valid only for the requested host.
347    *
348    * @param request The servlet request.
349    * @return The configured domain generalization that matches the request, or null if no match is found.
350    */
 
351  222 toggle private String getCookieDomain(HttpServletRequest request)
352    {
353  222 String cookieDomain = null;
354  222 if (this.cookieDomains != null) {
355    // Conform the server name like we conform cookie domain by prefixing with a dot.
356    // This will ensure both localhost.localdomain and any.localhost.localdomain will match
357    // the same cookie domain.
358  0 String servername = conformCookieDomain(request.getServerName());
359  0 for (String domain : this.cookieDomains) {
360  0 if (servername.endsWith(domain)) {
361  0 cookieDomain = domain;
362  0 break;
363    }
364    }
365    }
366  222 if (LOGGER.isDebugEnabled()) {
367  0 LOGGER.debug("Cookie domain is:" + cookieDomain);
368    }
369  222 return cookieDomain;
370    }
371   
372    /**
373    * Get validation hash for the specified parameters. The hash includes a secret password, and optionally binds the
374    * cookie to the requester's IP. The hash secret is configured using the xwiki.authentication.validationKey
375    * parameter. The IP binding is enabled using the xwiki.authentication.useip parameter.
376    *
377    * @param username The remembered username.
378    * @param password The remembered password.
379    * @param clientIP The client IP of the request.
380    * @return Validation hash.
381    */
 
382  196 toggle private String getValidationHash(String username, String password, String clientIP)
383    {
384  196 if (this.validationKey == null) {
385  0 if (LOGGER.isErrorEnabled()) {
386  0 LOGGER.error("ERROR! >> validationKey not specified...");
387  0 LOGGER.error("you are REQUIRED to specify the validatonkey in xwiki.cfg");
388    }
389  0 return null;
390    }
391  196 MessageDigest md5 = null;
392  196 StringBuffer sbValueBeforeMD5 = new StringBuffer();
393   
394  196 try {
395  196 md5 = MessageDigest.getInstance("MD5");
396   
397  196 sbValueBeforeMD5.append(username);
398  196 sbValueBeforeMD5.append(FIELD_SEPARATOR);
399  196 sbValueBeforeMD5.append(password.toString());
400  196 sbValueBeforeMD5.append(FIELD_SEPARATOR);
401  196 if (isTrue(this.useIP)) {
402  196 sbValueBeforeMD5.append(clientIP.toString());
403  196 sbValueBeforeMD5.append(FIELD_SEPARATOR);
404    }
405  196 sbValueBeforeMD5.append(this.validationKey.toString());
406   
407  196 this.valueBeforeMD5 = sbValueBeforeMD5.toString();
408  196 md5.update(this.valueBeforeMD5.getBytes());
409   
410  196 byte[] array = md5.digest();
411  196 StringBuffer sb = new StringBuffer();
412  196 for (byte element : array) {
413  3136 int b = element & 0xFF;
414  3136 if (b < 0x10) {
415  57 sb.append('0');
416    }
417  3136 sb.append(Integer.toHexString(b));
418    }
419  196 this.valueAfterMD5 = sb.toString();
420    } catch (Exception e) {
421  0 LOGGER.error("Failed to get [" + MessageDigest.class.getName() + "] instance", e);
422    }
423   
424  196 return this.valueAfterMD5;
425    }
426   
427    /**
428    * Encrypt a string. The encryption is password-based. The password can be configured using the
429    * xwiki.authentication.encryptionKey parameter.
430    *
431    * @param clearText The text to be encrypted.
432    * @return clearText, encrypted.
433    */
434    // TODO: Optimize this code by creating the Cipher only once.
 
435  376 toggle public String encryptText(String clearText)
436    {
437  376 try {
438  376 Cipher c1 = Cipher.getInstance(this.cipherParameters);
439  376 if (this.secretKey != null) {
440  376 c1.init(Cipher.ENCRYPT_MODE, this.secretKey);
441  376 byte[] clearTextBytes;
442  376 clearTextBytes = clearText.getBytes();
443  376 byte[] encryptedText = c1.doFinal(clearTextBytes);
444  376 String encryptedEncodedText = new String(Base64.encodeBase64(encryptedText));
445    // Since the cookie spec does not allow = in the cookie value, it must be replaced
446    // with something else. Bas64 does not use _, and it is allowed in cookies, so
447    // we're using that instead of =. In decryptText the reverse operation is perfomed.
448    // See XWIKI-2211
449  376 return encryptedEncodedText.replaceAll("=", "_");
450    }
451  0 if (LOGGER.isErrorEnabled()) {
452  0 LOGGER.error("ERROR! >> SecretKey not generated...");
453  0 LOGGER.error("you are REQUIRED to specify the encryptionKey in xwiki.cfg");
454    }
455    } catch (Exception e) {
456  0 if (LOGGER.isErrorEnabled()) {
457  0 LOGGER.error("Failed to encrypt text: " + clearText, e);
458    }
459    }
460  0 return null;
461    }
462   
463    /**
464    * Forget a login by removing the authentication cookies.
465    *
466    * @param request The servlet request.
467    * @param response The servlet response.
468    */
 
469  10 toggle @Override
470    public void forgetLogin(HttpServletRequest request, HttpServletResponse response)
471    {
472  10 ((SecurityRequestWrapper) request).setUserPrincipal(null);
473  10 removeCookie(request, response, getCookiePrefix() + COOKIE_USERNAME);
474  10 removeCookie(request, response, getCookiePrefix() + COOKIE_PASSWORD);
475  10 removeCookie(request, response, getCookiePrefix() + COOKIE_REMEMBERME);
476  10 removeCookie(request, response, getCookiePrefix() + COOKIE_VALIDATION);
477  10 return;
478    }
479   
480    /**
481    * Given an array of cookies and a name, this method tries to find and return the cookie from the array that has the
482    * given name. If there is no cookie matching the name in the array, null is returned.
483    *
484    * @param cookies The list of cookies sent by the client.
485    * @param cookieName The name of the cookie to be retrieved.
486    * @return The requested cookie, or null if no cookie with the given name was found.
487    */
 
488  40 toggle private static Cookie getCookie(Cookie[] cookies, String cookieName)
489    {
490  40 if (cookies != null) {
491  40 for (Cookie cookie : cookies) {
492  136 if (cookieName.equals(cookie.getName())) {
493  34 return (cookie);
494    }
495    }
496    }
497  6 return null;
498    }
499   
500    /**
501    * Remove a cookie.
502    *
503    * @param request The servlet request.
504    * @param response The servlet response.
505    * @param cookieName The name of the cookie that must be removed.
506    */
 
507  40 toggle private void removeCookie(HttpServletRequest request, HttpServletResponse response, String cookieName)
508    {
509  40 Cookie cookie = getCookie(request.getCookies(), cookieName);
510  40 if (cookie != null) {
511  34 cookie.setMaxAge(0);
512  34 cookie.setValue("");
513  34 cookie.setPath(this.cookiePath);
514  34 addCookie(response, cookie);
515  34 String cookieDomain = getCookieDomain(request);
516  34 if (cookieDomain != null) {
517  0 cookie.setDomain(cookieDomain);
518  0 addCookie(response, cookie);
519    }
520    }
521    }
522   
523    /**
524    * Check if a text is supposed to be an affirmative value ("true", "yes" or "1").
525    *
526    * @param text The text to check.
527    * @return true if the text is one of "true", "yes" or "1", false otherwise.
528    */
 
529  384 toggle private static boolean isTrue(String text)
530    {
531  384 return "true".equals(text) || "1".equals(text) || "yes".equals(text);
532    }
533   
534    /**
535    * Given an array of Cookies, a name, and a default value, this method tries to find the value of the cookie with
536    * the given name. If there is no cookie matching the name in the array, then the default value is returned instead.
537    *
538    * @param cookies The list of cookies to search.
539    * @param cookieName The name of the cookie whose value should be returned.
540    * @param defaultValue The default value that should be returned when no cookie with the given name was found.
541    * @return The value of the cookie with the given name, or defaultValue if no such cookie was found.
542    */
 
543  25862 toggle private static String getCookieValue(Cookie[] cookies, String cookieName, String defaultValue)
544    {
545  25863 String value = defaultValue;
546  25863 if (cookies != null) {
547  2898 for (Cookie cookie : cookies) {
548  3203 if (cookieName.equals(cookie.getName())) {
549  36 value = cookie.getValue();
550    }
551    }
552    }
553  25864 return value;
554    }
555   
556    /**
557    * Checks if the cookies are valid.
558    *
559    * @param request The servlet request.
560    * @param response The servlet response.
561    * @return True if the validation cookie holds a valid value or is not present, false otherwise.
562    * @todo Don't ignore it when set to "false", check the validation method.
563    */
 
564  8 toggle private boolean checkValidation(HttpServletRequest request, HttpServletResponse response)
565    {
566  8 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_VALIDATION)) {
567  8 String username = getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_USERNAME, DEFAULT_VALUE);
568  8 String password = getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_PASSWORD, DEFAULT_VALUE);
569  8 String cookieHash =
570    getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_VALIDATION, DEFAULT_VALUE);
571  8 String calculatedHash = getValidationHash(username, password, getClientIP(request));
572  8 if (cookieHash.equals(calculatedHash)) {
573  6 return true;
574    } else {
575  2 LOGGER.warn("Login cookie validation hash mismatch! Cookies have been tampered with");
576  2 LOGGER.info("Login cookie is being deleted!");
577  2 forgetLogin(request, response);
578    }
579    }
580  2 return false;
581    }
582   
583    /**
584    * Get the username stored (in a cookie) in the request. Also checks the validity of the cookie.
585    *
586    * @param request The servlet request.
587    * @param response The servlet response.
588    * @return The username value, or <tt>null</tt> if not found or the cookie isn't valid.
589    */
 
590  12916 toggle @Override
591    // TODO: Also use the URL, in case cookies are disabled [XWIKI-1071]
592    public String getRememberedUsername(HttpServletRequest request, HttpServletResponse response)
593    {
594  12916 String username = getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_USERNAME, DEFAULT_VALUE);
595   
596  12916 if (!username.equals(DEFAULT_VALUE)) {
597  3 if (checkValidation(request, response)) {
598  3 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_ENCRYPTION)) {
599  3 username = decryptText(username);
600    }
601  3 return username;
602    }
603    }
604  12913 return null;
605    }
606   
607    /**
608    * Get the password stored (in a cookie) in the request. Also checks the validity of the cookie.
609    *
610    * @param request The servlet request.
611    * @param response The servlet response.
612    * @return The password value, or <tt>null</tt> if not found or the cookie isn't valid.
613    */
 
614  12915 toggle @Override
615    // TODO: Also use the URL, in case cookies are disabled [XWIKI-1071]
616    public String getRememberedPassword(HttpServletRequest request, HttpServletResponse response)
617    {
618  12916 String password = getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_PASSWORD, DEFAULT_VALUE);
619  12916 if (!password.equals(DEFAULT_VALUE)) {
620  5 if (checkValidation(request, response)) {
621  3 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_ENCRYPTION)) {
622  3 password = decryptText(password);
623    }
624  3 return password;
625    }
626    }
627  12912 return null;
628    }
629   
 
630  8 toggle @Override
631    public boolean rememberingLogin(HttpServletRequest request)
632    {
633  8 if (getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_REMEMBERME, "false").equals("true")) {
634  0 return true;
635    } else {
636  8 return false;
637    }
638    }
639   
640    /**
641    * Decrypt a string.
642    *
643    * @param encryptedText The encrypted value.
644    * @return encryptedText, decrypted
645    */
 
646  6 toggle private String decryptText(String encryptedText)
647    {
648  6 try {
649    // Since the cookie spec does not allow = in the cookie value, it must be replaced
650    // with something else. Bas64 does not use _, and it is allowed in cookies, so
651    // we're using that instead of =. In encryptText the reverse operation was perfomed,
652    // so here we must re-introduce the = sign needed by Base64.
653    // See XWIKI-2211
654  6 byte[] decodedEncryptedText =
655    Base64.decodeBase64(encryptedText.replaceAll("_", "=").getBytes("ISO-8859-1"));
656  6 Cipher c1 = Cipher.getInstance(this.cipherParameters);
657  6 c1.init(Cipher.DECRYPT_MODE, this.secretKey);
658  6 byte[] decryptedText = c1.doFinal(decodedEncryptedText);
659  6 String decryptedTextString = new String(decryptedText);
660  6 return decryptedTextString;
661    } catch (Exception e) {
662  0 LOGGER.error("Error decypting text: " + encryptedText, e);
663  0 return null;
664    }
665    }
666   
667    /**
668    * Returns the original client IP. Needed because request.getRemoteAddr returns the address of the last requesting
669    * host, which can be either the real client, or a proxy. The original method prevents logging in when using a
670    * cluster of reverse proxies in front of XWiki.
671    *
672    * @param request The servlet request.
673    * @return The IP of the actual client.
674    */
 
675  196 toggle protected String getClientIP(HttpServletRequest request)
676    {
677  196 String remoteIP = request.getHeader("X-Forwarded-For");
678  196 if (remoteIP == null || "".equals(remoteIP)) {
679  196 remoteIP = request.getRemoteAddr();
680  0 } else if (remoteIP.indexOf(',') != -1) {
681  0 remoteIP = remoteIP.substring(0, remoteIP.indexOf(','));
682    }
683  196 return remoteIP;
684    }
685   
686    /**
687    * Setter for the {@link #cookiePrefix} parameter.
688    *
689    * @param prefix The new value for {@link #cookiePrefix}.
690    * @see #cookiePrefix
691    */
 
692  0 toggle public void setCookiePrefix(String prefix)
693    {
694  0 this.cookiePrefix = prefix;
695    }
696   
697    /**
698    * Getter for the {@link #cookiePrefix} parameter.
699    *
700    * @return The value for {@link #cookiePrefix}.
701    * @see #cookiePrefix
702    */
 
703  26654 toggle public String getCookiePrefix()
704    {
705  26656 return this.cookiePrefix;
706    }
707    }