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

File MyPersistentLoginManager.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart6.png
69% 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 140
0.558359655.8%
 

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  32 toggle static {
88  32 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  36 toggle public MyPersistentLoginManager()
120    {
121  36 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  36 toggle public void setCookieDomains(String[] cdlist)
147    {
148  36 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  36 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  240 toggle private void setupCookie(Cookie cookie, boolean sessionCookie, boolean secureCookie, String cookieDomain,
195    HttpServletResponse response)
196    {
197  240 if (!sessionCookie) {
198  0 setMaxAge(cookie);
199    }
200  240 cookie.setSecure(secureCookie);
201  240 cookie.setPath(this.cookiePath);
202  240 if (cookieDomain != null) {
203  0 cookie.setDomain(cookieDomain);
204    }
205  240 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  60 toggle @Override
217    public void rememberLogin(HttpServletRequest request, HttpServletResponse response, String username, String password)
218    {
219  60 String protectedUsername = username;
220  60 String protectedPassword = password;
221  60 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_ENCRYPTION)) {
222  60 protectedUsername = encryptText(protectedUsername);
223  60 protectedPassword = encryptText(protectedPassword);
224  60 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  60 boolean sessionCookie = !(isTrue(request.getParameter("j_rememberme")));
234  60 boolean secureCookie = request.isSecure();
235  60 String cookieDomain = getCookieDomain(request);
236   
237    // Create client cookies to remember the login information.
238   
239    // Username
240  60 Cookie usernameCookie = new Cookie(getCookiePrefix() + COOKIE_USERNAME, protectedUsername);
241  60 setupCookie(usernameCookie, sessionCookie, secureCookie, cookieDomain, response);
242   
243    // Password
244  60 Cookie passwdCookie = new Cookie(getCookiePrefix() + COOKIE_PASSWORD, protectedPassword);
245  60 setupCookie(passwdCookie, sessionCookie, secureCookie, cookieDomain, response);
246   
247    // Remember me
248  60 Cookie rememberCookie = new Cookie(getCookiePrefix() + COOKIE_REMEMBERME, !sessionCookie + "");
249  60 setupCookie(rememberCookie, sessionCookie, secureCookie, cookieDomain, response);
250   
251  60 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_VALIDATION)) {
252  60 String validationHash = getValidationHash(protectedUsername, protectedPassword, getClientIP(request));
253  60 if (validationHash != null) {
254    // Validation
255  60 Cookie validationCookie = new Cookie(getCookiePrefix() + COOKIE_VALIDATION, validationHash);
256  60 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  60 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  0 toggle private void setMaxAge(Cookie cookie)
276    {
277  0 try {
278  0 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  244 toggle private void addCookie(HttpServletResponse response, Cookie cookie)
293    {
294  244 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  244 StringBuilder cookieValue = new StringBuilder(150);
302  244 cookieValue.append(cookie.getName() + "=");
303  244 if (StringUtils.isNotEmpty(cookie.getValue())) {
304  240 cookieValue.append("\"" + cookie.getValue() + "\"");
305    }
306  244 cookieValue.append("; Version=1");
307  244 if (cookie.getMaxAge() >= 0) {
308  4 cookieValue.append("; Max-Age=" + cookie.getMaxAge());
309    // IE is such a pain, it doesn't understand the modern, safer Max-Age
310  4 cookieValue.append("; Expires=");
311  4 if (cookie.getMaxAge() == 0) {
312  4 cookieValue.append(COOKIE_EXPIRE_NOW);
313    } else {
314  0 cookieValue.append(COOKIE_EXPIRE_FORMAT.format(new Date(System.currentTimeMillis() + cookie.getMaxAge()
315    * 1000L)));
316    }
317    }
318  244 if (StringUtils.isNotEmpty(cookie.getDomain())) {
319    // IE needs toLowerCase for the domain name
320  0 cookieValue.append("; Domain=" + cookie.getDomain().toLowerCase());
321    }
322  244 if (StringUtils.isNotEmpty(cookie.getPath())) {
323  244 cookieValue.append("; Path=" + cookie.getPath());
324    }
325    // Protect cookies from being used from JavaScript, see http://www.owasp.org/index.php/HttpOnly
326  244 cookieValue.append("; HttpOnly");
327    // Only send this cookie on HTTPS connections coming from a page in the same domain
328  244 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  244 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  64 toggle private String getCookieDomain(HttpServletRequest request)
352    {
353  64 String cookieDomain = null;
354  64 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  64 if (LOGGER.isDebugEnabled()) {
367  0 LOGGER.debug("Cookie domain is:" + cookieDomain);
368    }
369  64 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  60 toggle private String getValidationHash(String username, String password, String clientIP)
383    {
384  60 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  60 MessageDigest md5 = null;
392  60 StringBuffer sbValueBeforeMD5 = new StringBuffer();
393   
394  60 try {
395  60 md5 = MessageDigest.getInstance("MD5");
396   
397  60 sbValueBeforeMD5.append(username);
398  60 sbValueBeforeMD5.append(FIELD_SEPARATOR);
399  60 sbValueBeforeMD5.append(password.toString());
400  60 sbValueBeforeMD5.append(FIELD_SEPARATOR);
401  60 if (isTrue(this.useIP)) {
402  60 sbValueBeforeMD5.append(clientIP.toString());
403  60 sbValueBeforeMD5.append(FIELD_SEPARATOR);
404    }
405  60 sbValueBeforeMD5.append(this.validationKey.toString());
406   
407  60 this.valueBeforeMD5 = sbValueBeforeMD5.toString();
408  60 md5.update(this.valueBeforeMD5.getBytes());
409   
410  60 byte[] array = md5.digest();
411  60 StringBuffer sb = new StringBuffer();
412  60 for (byte element : array) {
413  960 int b = element & 0xFF;
414  960 if (b < 0x10) {
415  23 sb.append('0');
416    }
417  960 sb.append(Integer.toHexString(b));
418    }
419  60 this.valueAfterMD5 = sb.toString();
420    } catch (Exception e) {
421  0 LOGGER.error("Failed to get [" + MessageDigest.class.getName() + "] instance", e);
422    }
423   
424  60 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  120 toggle public String encryptText(String clearText)
436    {
437  120 try {
438  120 Cipher c1 = Cipher.getInstance(this.cipherParameters);
439  120 if (this.secretKey != null) {
440  120 c1.init(Cipher.ENCRYPT_MODE, this.secretKey);
441  120 byte[] clearTextBytes;
442  120 clearTextBytes = clearText.getBytes();
443  120 byte[] encryptedText = c1.doFinal(clearTextBytes);
444  120 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  120 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  1 toggle @Override
470    public void forgetLogin(HttpServletRequest request, HttpServletResponse response)
471    {
472  1 ((SecurityRequestWrapper) request).setUserPrincipal(null);
473  1 removeCookie(request, response, getCookiePrefix() + COOKIE_USERNAME);
474  1 removeCookie(request, response, getCookiePrefix() + COOKIE_PASSWORD);
475  1 removeCookie(request, response, getCookiePrefix() + COOKIE_REMEMBERME);
476  1 removeCookie(request, response, getCookiePrefix() + COOKIE_VALIDATION);
477  1 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  4 toggle private static Cookie getCookie(Cookie[] cookies, String cookieName)
489    {
490  4 if (cookies != null) {
491  4 for (Cookie cookie : cookies) {
492  14 if (cookieName.equals(cookie.getName())) {
493  4 return (cookie);
494    }
495    }
496    }
497  0 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  4 toggle private void removeCookie(HttpServletRequest request, HttpServletResponse response, String cookieName)
508    {
509  4 Cookie cookie = getCookie(request.getCookies(), cookieName);
510  4 if (cookie != null) {
511  4 cookie.setMaxAge(0);
512  4 cookie.setValue("");
513  4 cookie.setPath(this.cookiePath);
514  4 addCookie(response, cookie);
515  4 String cookieDomain = getCookieDomain(request);
516  4 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  120 toggle private static boolean isTrue(String text)
530    {
531  120 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  4847 toggle private static String getCookieValue(Cookie[] cookies, String cookieName, String defaultValue)
544    {
545  4848 String value = defaultValue;
546  4848 if (cookies != null) {
547  896 for (Cookie cookie : cookies) {
548  900 if (cookieName.equals(cookie.getName())) {
549  1 value = cookie.getValue();
550    }
551    }
552    }
553  4849 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  0 toggle private boolean checkValidation(HttpServletRequest request, HttpServletResponse response)
565    {
566  0 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_VALIDATION)) {
567  0 String username = getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_USERNAME, DEFAULT_VALUE);
568  0 String password = getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_PASSWORD, DEFAULT_VALUE);
569  0 String cookieHash =
570    getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_VALIDATION, DEFAULT_VALUE);
571  0 String calculatedHash = getValidationHash(username, password, getClientIP(request));
572  0 if (cookieHash.equals(calculatedHash)) {
573  0 return true;
574    } else {
575  0 LOGGER.warn("Login cookie validation hash mismatch! Cookies have been tampered with");
576  0 LOGGER.info("Login cookie is being deleted!");
577  0 forgetLogin(request, response);
578    }
579    }
580  0 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  2424 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  2424 String username = getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_USERNAME, DEFAULT_VALUE);
595   
596  2423 if (!username.equals(DEFAULT_VALUE)) {
597  0 if (checkValidation(request, response)) {
598  0 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_ENCRYPTION)) {
599  0 username = decryptText(username);
600    }
601  0 return username;
602    }
603    }
604  2424 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  2422 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  2423 String password = getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_PASSWORD, DEFAULT_VALUE);
619  2422 if (!password.equals(DEFAULT_VALUE)) {
620  0 if (checkValidation(request, response)) {
621  0 if (this.protection.equals(PROTECTION_ALL) || this.protection.equals(PROTECTION_ENCRYPTION)) {
622  0 password = decryptText(password);
623    }
624  0 return password;
625    }
626    }
627  2422 return null;
628    }
629   
 
630  1 toggle @Override
631    public boolean rememberingLogin(HttpServletRequest request)
632    {
633  1 if (getCookieValue(request.getCookies(), getCookiePrefix() + COOKIE_REMEMBERME, "false").equals("true")) {
634  0 return true;
635    } else {
636  1 return false;
637    }
638    }
639   
640    /**
641    * Decrypt a string.
642    *
643    * @param encryptedText The encrypted value.
644    * @return encryptedText, decrypted
645    */
 
646  0 toggle private String decryptText(String encryptedText)
647    {
648  0 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  0 byte[] decodedEncryptedText =
655    Base64.decodeBase64(encryptedText.replaceAll("_", "=").getBytes("ISO-8859-1"));
656  0 Cipher c1 = Cipher.getInstance(this.cipherParameters);
657  0 c1.init(Cipher.DECRYPT_MODE, this.secretKey);
658  0 byte[] decryptedText = c1.doFinal(decodedEncryptedText);
659  0 String decryptedTextString = new String(decryptedText);
660  0 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  60 toggle protected String getClientIP(HttpServletRequest request)
676    {
677  60 String remoteIP = request.getHeader("X-Forwarded-For");
678  60 if (remoteIP == null || "".equals(remoteIP)) {
679  60 remoteIP = request.getRemoteAddr();
680  0 } else if (remoteIP.indexOf(',') != -1) {
681  0 remoteIP = remoteIP.substring(0, remoteIP.indexOf(','));
682    }
683  60 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  5091 toggle public String getCookiePrefix()
704    {
705  5092 return this.cookiePrefix;
706    }
707    }