James Moger
2016-01-16 b180e500aadc1eb48779754055545a4f271c12a0
commit | author | age
04a985 1 /*
JM 2  * Copyright 2013 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.manager;
17
18 import java.nio.charset.Charset;
19 import java.security.Principal;
20 import java.text.MessageFormat;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
7ab32b 25 import java.util.concurrent.TimeUnit;
04a985 26
JM 27 import javax.servlet.http.Cookie;
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
efdb2b 30 import javax.servlet.http.HttpSession;
04a985 31
JM 32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.gitblit.Constants;
36 import com.gitblit.Constants.AccountType;
37 import com.gitblit.Constants.AuthenticationType;
6e3481 38 import com.gitblit.Constants.Role;
04a985 39 import com.gitblit.IStoredSettings;
JM 40 import com.gitblit.Keys;
41 import com.gitblit.auth.AuthenticationProvider;
42 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
43 import com.gitblit.auth.HtpasswdAuthProvider;
46f61d 44 import com.gitblit.auth.HttpHeaderAuthProvider;
04a985 45 import com.gitblit.auth.LdapAuthProvider;
JM 46 import com.gitblit.auth.PAMAuthProvider;
47 import com.gitblit.auth.RedmineAuthProvider;
48 import com.gitblit.auth.SalesforceAuthProvider;
49 import com.gitblit.auth.WindowsAuthProvider;
50 import com.gitblit.models.TeamModel;
51 import com.gitblit.models.UserModel;
bcc8a0 52 import com.gitblit.transport.ssh.SshKey;
04a985 53 import com.gitblit.utils.Base64;
JM 54 import com.gitblit.utils.HttpUtils;
55 import com.gitblit.utils.StringUtils;
56 import com.gitblit.utils.X509Utils.X509Metadata;
f9980e 57 import com.google.inject.Inject;
JM 58 import com.google.inject.Singleton;
04a985 59
JM 60 /**
61  * The authentication manager handles user login & logout.
62  *
63  * @author James Moger
64  *
65  */
f9980e 66 @Singleton
04a985 67 public class AuthenticationManager implements IAuthenticationManager {
JM 68
69     private final Logger logger = LoggerFactory.getLogger(getClass());
70
71     private final IStoredSettings settings;
72
73     private final IRuntimeManager runtimeManager;
74
75     private final IUserManager userManager;
76
77     private final List<AuthenticationProvider> authenticationProviders;
78
79     private final Map<String, Class<? extends AuthenticationProvider>> providerNames;
80
81     private final Map<String, String> legacyRedirects;
82
1b34b0 83     @Inject
04a985 84     public AuthenticationManager(
JM 85             IRuntimeManager runtimeManager,
86             IUserManager userManager) {
87
88         this.settings = runtimeManager.getSettings();
89         this.runtimeManager = runtimeManager;
90         this.userManager = userManager;
91         this.authenticationProviders = new ArrayList<AuthenticationProvider>();
92
93         // map of shortcut provider names
94         providerNames = new HashMap<String, Class<? extends AuthenticationProvider>>();
95         providerNames.put("htpasswd", HtpasswdAuthProvider.class);
46f61d 96         providerNames.put("httpheader", HttpHeaderAuthProvider.class);
04a985 97         providerNames.put("ldap", LdapAuthProvider.class);
JM 98         providerNames.put("pam", PAMAuthProvider.class);
99         providerNames.put("redmine", RedmineAuthProvider.class);
100         providerNames.put("salesforce", SalesforceAuthProvider.class);
101         providerNames.put("windows", WindowsAuthProvider.class);
102
103         // map of legacy external user services
104         legacyRedirects = new HashMap<String, String>();
105         legacyRedirects.put("com.gitblit.HtpasswdUserService", "htpasswd");
106         legacyRedirects.put("com.gitblit.LdapUserService", "ldap");
107         legacyRedirects.put("com.gitblit.PAMUserService", "pam");
108         legacyRedirects.put("com.gitblit.RedmineUserService", "redmine");
109         legacyRedirects.put("com.gitblit.SalesforceUserService", "salesforce");
110         legacyRedirects.put("com.gitblit.WindowsUserService", "windows");
111     }
112
113     @Override
114     public AuthenticationManager start() {
115         // automatically adjust legacy configurations
116         String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
117         if (legacyRedirects.containsKey(realm)) {
118             logger.warn("");
088b6f 119             logger.warn(Constants.BORDER2);
04a985 120             logger.warn(" IUserService '{}' is obsolete!", realm);
JM 121             logger.warn(" Please set '{}={}'", "realm.authenticationProviders", legacyRedirects.get(realm));
088b6f 122             logger.warn(Constants.BORDER2);
04a985 123             logger.warn("");
JM 124
125             // conditionally override specified authentication providers
126             if (StringUtils.isEmpty(settings.getString(Keys.realm.authenticationProviders, null))) {
127                 settings.overrideSetting(Keys.realm.authenticationProviders, legacyRedirects.get(realm));
128             }
129         }
130
131         // instantiate and setup specified authentication providers
132         List<String> providers = settings.getStrings(Keys.realm.authenticationProviders);
133         if (providers.isEmpty()) {
134             logger.info("External authentication disabled.");
135         } else {
136             for (String provider : providers) {
137                 try {
138                     Class<?> authClass;
139                     if (providerNames.containsKey(provider)) {
140                         // map the name -> class
141                         authClass = providerNames.get(provider);
142                     } else {
143                         // reflective lookup
144                         authClass = Class.forName(provider);
145                     }
146                     logger.info("setting up {}", authClass.getName());
147                     AuthenticationProvider authImpl = (AuthenticationProvider) authClass.newInstance();
148                     authImpl.setup(runtimeManager, userManager);
149                     authenticationProviders.add(authImpl);
150                 } catch (Exception e) {
151                     logger.error("", e);
152                 }
153             }
154         }
155         return this;
156     }
157
158     @Override
159     public AuthenticationManager stop() {
6659fa 160         for (AuthenticationProvider provider : authenticationProviders) {
JM 161             try {
162                 provider.stop();
163             } catch (Exception e) {
164                 logger.error("Failed to stop " + provider.getClass().getSimpleName(), e);
165             }
166         }
04a985 167         return this;
JM 168     }
bcc8a0 169
b4a63a 170     public void addAuthenticationProvider(AuthenticationProvider prov) {
JM 171         authenticationProviders.add(prov);
172     }
04a985 173
JM 174     /**
46f61d 175      * Used to handle authentication for page requests.
JJ 176      *
177      * This allows authentication to occur based on the contents of the request
178      * itself. If no configured @{AuthenticationProvider}s authenticate succesffully,
179      * a request for login will be shown.
04a985 180      *
JM 181      * Authentication by X509Certificate is tried first and then by cookie.
182      *
183      * @param httpRequest
184      * @return a user object or null
185      */
186     @Override
187     public UserModel authenticate(HttpServletRequest httpRequest) {
188         return authenticate(httpRequest, false);
189     }
190
191     /**
192      * Authenticate a user based on HTTP request parameters.
193      *
46f61d 194      * Authentication by custom HTTP header, servlet container principal, X509Certificate, cookie,
04a985 195      * and finally BASIC header.
JM 196      *
197      * @param httpRequest
198      * @param requiresCertificate
199      * @return a user object or null
200      */
201     @Override
202     public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
62e025 203
JJ 204         // Check if this request has already been authenticated, and trust that instead of re-processing
205         String reqAuthUser = (String) httpRequest.getAttribute(Constants.ATTRIB_AUTHUSER);
206         if (!StringUtils.isEmpty(reqAuthUser)) {
db09ef 207             logger.debug("Called servlet authenticate when request is already authenticated.");
62e025 208             return userManager.getUserModel(reqAuthUser);
JJ 209         }
210
04a985 211         // try to authenticate by servlet container principal
JM 212         if (!requiresCertificate) {
213             Principal principal = httpRequest.getUserPrincipal();
214             if (principal != null) {
215                 String username = principal.getName();
216                 if (!StringUtils.isEmpty(username)) {
23e08c 217                     boolean internalAccount = userManager.isInternalAccount(username);
04a985 218                     UserModel user = userManager.getUserModel(username);
JM 219                     if (user != null) {
220                         // existing user
62e025 221                         flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username);
04a985 222                         logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
JM 223                                 user.username, httpRequest.getRemoteAddr()));
9aa119 224                         return validateAuthentication(user, AuthenticationType.CONTAINER);
04a985 225                     } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false)
JM 226                             && !internalAccount) {
227                         // auto-create user from an authenticated container principal
228                         user = new UserModel(username.toLowerCase());
229                         user.displayName = username;
230                         user.password = Constants.EXTERNAL_ACCOUNT;
231                         user.accountType = AccountType.CONTAINER;
2c0555 232                         
FB 233                         // Try to extract user's informations for the session
234                         // it uses "realm.container.autoAccounts.*" as the attribute name to look for
235                         HttpSession session = httpRequest.getSession();
236                         String emailAddress = resolveAttribute(session, Keys.realm.container.autoAccounts.emailAddress);
237                         if(emailAddress != null) {
238                             user.emailAddress = emailAddress;
239                         }
240                         String displayName = resolveAttribute(session, Keys.realm.container.autoAccounts.displayName);
241                         if(displayName != null) {
242                             user.displayName = displayName;
243                         }
244                         String userLocale = resolveAttribute(session, Keys.realm.container.autoAccounts.locale);
245                         if(userLocale != null) {
246                             user.getPreferences().setLocale(userLocale);
247                         }
248                         String adminRole = settings.getString(Keys.realm.container.autoAccounts.adminRole, null);
249                         if(adminRole != null && ! adminRole.isEmpty()) {
250                             if(httpRequest.isUserInRole(adminRole)) {
251                                 user.canAdmin = true;
252                             }
253                         }
254                         
04a985 255                         userManager.updateUserModel(user);
62e025 256                         flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username);
04a985 257                         logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
JM 258                                 user.username, httpRequest.getRemoteAddr()));
9aa119 259                         return validateAuthentication(user, AuthenticationType.CONTAINER);
04a985 260                     } else if (!internalAccount) {
JM 261                         logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}",
262                                 principal.getName(), httpRequest.getRemoteAddr()));
263                     }
264                 }
265             }
266         }
267
268         // try to authenticate by certificate
269         boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
270         String [] oids = settings.getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
271         UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
272         if (model != null) {
273             // grab real user model and preserve certificate serial number
274             UserModel user = userManager.getUserModel(model.username);
275             X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
276             if (user != null) {
62e025 277                 flagRequest(httpRequest, AuthenticationType.CERTIFICATE, user.username);
04a985 278                 logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
JM 279                         user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
9aa119 280                 return validateAuthentication(user, AuthenticationType.CERTIFICATE);
04a985 281             } else {
JM 282                 logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}",
283                         model.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
284             }
285         }
286
287         if (requiresCertificate) {
288             // caller requires client certificate authentication (e.g. git servlet)
289             return null;
290         }
291
7ab32b 292         UserModel user = null;
JM 293
04a985 294         // try to authenticate by cookie
7ab32b 295         String cookie = getCookie(httpRequest);
JM 296         if (!StringUtils.isEmpty(cookie)) {
297             user = userManager.getUserModel(cookie.toCharArray());
298             if (user != null) {
62e025 299                 flagRequest(httpRequest, AuthenticationType.COOKIE, user.username);
7ab32b 300                 logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}",
04a985 301                     user.username, httpRequest.getRemoteAddr()));
9aa119 302                 return validateAuthentication(user, AuthenticationType.COOKIE);
7ab32b 303             }
04a985 304         }
JM 305
306         // try to authenticate by BASIC
307         final String authorization = httpRequest.getHeader("Authorization");
308         if (authorization != null && authorization.startsWith("Basic")) {
309             // Authorization: Basic base64credentials
310             String base64Credentials = authorization.substring("Basic".length()).trim();
311             String credentials = new String(Base64.decode(base64Credentials),
312                     Charset.forName("UTF-8"));
313             // credentials = username:password
314             final String[] values = credentials.split(":", 2);
315
316             if (values.length == 2) {
317                 String username = values[0];
318                 char[] password = values[1].toCharArray();
0d7c65 319                 user = authenticate(username, password, httpRequest.getRemoteAddr());
04a985 320                 if (user != null) {
62e025 321                     flagRequest(httpRequest, AuthenticationType.CREDENTIALS, user.username);
04a985 322                     logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
JM 323                             user.username, httpRequest.getRemoteAddr()));
9aa119 324                     return validateAuthentication(user, AuthenticationType.CREDENTIALS);
04a985 325                 }
JM 326             }
327         }
46f61d 328
JJ 329         // Check each configured AuthenticationProvider
330         for (AuthenticationProvider ap : authenticationProviders) {
331             UserModel authedUser = ap.authenticate(httpRequest);
332             if (null != authedUser) {
333                 flagRequest(httpRequest, ap.getAuthenticationType(), authedUser.username);
334                 logger.debug(MessageFormat.format("{0} authenticated by {1} from {2} for {3}",
335                         authedUser.username, ap.getServiceName(), httpRequest.getRemoteAddr(),
336                         httpRequest.getPathInfo()));
337                 return validateAuthentication(authedUser, ap.getAuthenticationType());
338             }
339         }
04a985 340         return null;
9aa119 341     }
2c0555 342     
FB 343     /**
344      * Extract given attribute from the session and return it's content
345      * it return null if attributeMapping is empty, or if the value is
346      * empty
347      * 
348      * @param session The user session
349      * @param attributeMapping
350      * @return
351      */
352     private String resolveAttribute(HttpSession session, String attributeMapping) {
353         String attributeName = settings.getString(attributeMapping, null);
354         if(StringUtils.isEmpty(attributeName)) {
355             return null;
356         }
357         Object attributeValue = session.getAttribute(attributeName);
358         if(attributeValue == null) {
359             return null;
360         }
361         String value = attributeValue.toString();
362         if(value.isEmpty()) {
363             return null;
364         }
365         return value;
366     }
9aa119 367
JM 368     /**
44e2ee 369      * Authenticate a user based on a public key.
e3b636 370      *
44e2ee 371      * This implementation assumes that the authentication has already take place
JM 372      * (e.g. SSHDaemon) and that this is a validation/verification of the user.
373      *
374      * @param username
375      * @param key
e3b636 376      * @return a user object or null
DO 377      */
378     @Override
bcc8a0 379     public UserModel authenticate(String username, SshKey key) {
e3b636 380         if (username != null) {
DO 381             if (!StringUtils.isEmpty(username)) {
382                 UserModel user = userManager.getUserModel(username);
383                 if (user != null) {
384                     // existing user
44e2ee 385                     logger.debug(MessageFormat.format("{0} authenticated by {1} public key",
JM 386                             user.username, key.getAlgorithm()));
387                     return validateAuthentication(user, AuthenticationType.PUBLIC_KEY);
e3b636 388                 }
44e2ee 389                 logger.warn(MessageFormat.format("Failed to find UserModel for {0} during public key authentication",
JM 390                             username));
e3b636 391             }
DO 392         } else {
44e2ee 393             logger.warn("Empty user passed to AuthenticationManager.authenticate!");
e3b636 394         }
DO 395         return null;
396     }
397
398
399     /**
e97c01 400      * Return the UserModel for already authenticated user.
FB 401      *
402      * This implementation assumes that the authentication has already take place
403      * (e.g. SSHDaemon) and that this is a validation/verification of the user.
404      *
405      * @param username
406      * @return a user object or null
407      */
408     @Override
409     public UserModel authenticate(String username) {
410         if (username != null) {
411             if (!StringUtils.isEmpty(username)) {
412                 UserModel user = userManager.getUserModel(username);
413                 if (user != null) {
414                     // existing user
415                     logger.debug(MessageFormat.format("{0} authenticated externally", user.username));
416                     return validateAuthentication(user, AuthenticationType.CONTAINER);
417                 }
418                 logger.warn(MessageFormat.format("Failed to find UserModel for {0} during external authentication",
419                             username));
420             }
421         } else {
422             logger.warn("Empty user passed to AuthenticationManager.authenticate!");
423         }
424         return null;
425     }
426
427
428     /**
9aa119 429      * This method allows the authentication manager to reject authentication
JM 430      * attempts.  It is called after the username/secret have been verified to
431      * ensure that the authentication technique has been logged.
432      *
433      * @param user
434      * @return
435      */
436     protected UserModel validateAuthentication(UserModel user, AuthenticationType type) {
437         if (user == null) {
438             return null;
439         }
440         if (user.disabled) {
441             // user has been disabled
442             logger.warn("Rejected {} authentication attempt by disabled account \"{}\"",
443                     type, user.username);
444             return null;
445         }
446         return user;
04a985 447     }
JM 448
62e025 449     protected void flagRequest(HttpServletRequest httpRequest, AuthenticationType authenticationType, String authedUsername) {
JJ 450         httpRequest.setAttribute(Constants.ATTRIB_AUTHUSER,  authedUsername);
451         httpRequest.setAttribute(Constants.ATTRIB_AUTHTYPE,  authenticationType);
04a985 452     }
JM 453
454     /**
455      * Authenticate a user based on a username and password.
456      *
457      * @see IUserService.authenticate(String, char[])
458      * @param username
459      * @param password
460      * @return a user object or null
461      */
462     @Override
0d7c65 463     public UserModel authenticate(String username, char[] password, String remoteIP) {
04a985 464         if (StringUtils.isEmpty(username)) {
JM 465             // can not authenticate empty username
466             return null;
467         }
468
469         String usernameDecoded = StringUtils.decodeUsername(username);
470         String pw = new String(password);
471         if (StringUtils.isEmpty(pw)) {
472             // can not authenticate empty password
473             return null;
474         }
475
476         UserModel user = userManager.getUserModel(usernameDecoded);
1293c2 477
JM 478         // try local authentication
479         if (user != null && user.isLocalAccount()) {
0d7c65 480             UserModel returnedUser = authenticateLocal(user, password);
PM 481             if (returnedUser != null) {
482                 // user authenticated
483                 return returnedUser;
484             }
485         } else {
486             // try registered external authentication providers
487             for (AuthenticationProvider provider : authenticationProviders) {
488                 if (provider instanceof UsernamePasswordAuthenticationProvider) {
489                     UserModel returnedUser = provider.authenticate(usernameDecoded, password);
490                     if (returnedUser != null) {
491                         // user authenticated
492                         returnedUser.accountType = provider.getAccountType();
493                         return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS);
494                     }
04a985 495                 }
JM 496             }
497         }
bcc8a0 498
b4a63a 499         // could not authenticate locally or with a provider
0d7c65 500         logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", username, 
PM 501                 remoteIP != null ? remoteIP : "unknown"));
502         
b4a63a 503         return null;
JM 504     }
bcc8a0 505
b4a63a 506     /**
JM 507      * Returns a UserModel if local authentication succeeds.
bcc8a0 508      *
b4a63a 509      * @param user
JM 510      * @param password
511      * @return a UserModel if local authentication succeeds, null otherwise
512      */
513     protected UserModel authenticateLocal(UserModel user, char [] password) {
514         UserModel returnedUser = null;
515         if (user.password.startsWith(StringUtils.MD5_TYPE)) {
516             // password digest
517             String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
518             if (user.password.equalsIgnoreCase(md5)) {
519                 returnedUser = user;
520             }
521         } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
522             // username+password digest
523             String md5 = StringUtils.COMBINED_MD5_TYPE
524                     + StringUtils.getMD5(user.username.toLowerCase() + new String(password));
525             if (user.password.equalsIgnoreCase(md5)) {
526                 returnedUser = user;
527             }
528         } else if (user.password.equals(new String(password))) {
529             // plain-text password
530             returnedUser = user;
531         }
532         return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS);
04a985 533     }
JM 534
535     /**
7ab32b 536      * Returns the Gitlbit cookie in the request.
JM 537      *
538      * @param request
539      * @return the Gitblit cookie for the request or null if not found
540      */
541     @Override
542     public String getCookie(HttpServletRequest request) {
543         if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) {
544             Cookie[] cookies = request.getCookies();
545             if (cookies != null && cookies.length > 0) {
546                 for (Cookie cookie : cookies) {
547                     if (cookie.getName().equals(Constants.NAME)) {
548                         String value = cookie.getValue();
549                         return value;
550                     }
551                 }
552             }
553         }
554         return null;
555     }
556
557     /**
04a985 558      * Sets a cookie for the specified user.
JM 559      *
560      * @param response
561      * @param user
562      */
563     @Override
ec7ed8 564     @Deprecated
04a985 565     public void setCookie(HttpServletResponse response, UserModel user) {
ec7ed8 566         setCookie(null, response, user);
JM 567     }
568
569     /**
570      * Sets a cookie for the specified user.
571      *
572      * @param request
573      * @param response
574      * @param user
575      */
576     @Override
577     public void setCookie(HttpServletRequest request, HttpServletResponse response, UserModel user) {
04a985 578         if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) {
62e025 579             boolean standardLogin = true;
JJ 580
581             if (null != request) {
582                 // Pull the auth type from the request, it is set there if container managed
583                 AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE);
584
585                 if (null != authenticationType)
586                     standardLogin = authenticationType.isStandard();
587             }
04a985 588
JM 589             if (standardLogin) {
590                 Cookie userCookie;
591                 if (user == null) {
592                     // clear cookie for logout
593                     userCookie = new Cookie(Constants.NAME, "");
594                 } else {
595                     // set cookie for login
596                     String cookie = userManager.getCookie(user);
597                     if (StringUtils.isEmpty(cookie)) {
598                         // create empty cookie
599                         userCookie = new Cookie(Constants.NAME, "");
600                     } else {
601                         // create real cookie
602                         userCookie = new Cookie(Constants.NAME, cookie);
7ab32b 603                         // expire the cookie in 7 days
JM 604                         userCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(7));
04a985 605                     }
JM 606                 }
ec7ed8 607                 String path = "/";
JM 608                 if (request != null) {
609                     if (!StringUtils.isEmpty(request.getContextPath())) {
610                         path = request.getContextPath();
611                     }
612                 }
613                 userCookie.setPath(path);
04a985 614                 response.addCookie(userCookie);
JM 615             }
616         }
617     }
618
619     /**
620      * Logout a user.
621      *
ec7ed8 622      * @param response
04a985 623      * @param user
JM 624      */
625     @Override
ec7ed8 626     @Deprecated
04a985 627     public void logout(HttpServletResponse response, UserModel user) {
ec7ed8 628         setCookie(null, response,  null);
JM 629     }
630
631     /**
632      * Logout a user.
633      *
634      * @param request
635      * @param response
636      * @param user
637      */
638     @Override
639     public void logout(HttpServletRequest request, HttpServletResponse response, UserModel user) {
640         setCookie(request, response,  null);
04a985 641     }
JM 642
643     /**
644      * Returns true if the user's credentials can be changed.
645      *
646      * @param user
647      * @return true if the user service supports credential changes
648      */
649     @Override
650     public boolean supportsCredentialChanges(UserModel user) {
651         return (user != null && user.isLocalAccount()) || findProvider(user).supportsCredentialChanges();
652     }
653
654     /**
655      * Returns true if the user's display name can be changed.
656      *
657      * @param user
658      * @return true if the user service supports display name changes
659      */
660     @Override
661     public boolean supportsDisplayNameChanges(UserModel user) {
662         return (user != null && user.isLocalAccount()) || findProvider(user).supportsDisplayNameChanges();
663     }
664
665     /**
666      * Returns true if the user's email address can be changed.
667      *
668      * @param user
669      * @return true if the user service supports email address changes
670      */
671     @Override
672     public boolean supportsEmailAddressChanges(UserModel user) {
673         return (user != null && user.isLocalAccount()) || findProvider(user).supportsEmailAddressChanges();
674     }
675
676     /**
677      * Returns true if the user's team memberships can be changed.
678      *
679      * @param user
680      * @return true if the user service supports team membership changes
681      */
682     @Override
683     public boolean supportsTeamMembershipChanges(UserModel user) {
684         return (user != null && user.isLocalAccount()) || findProvider(user).supportsTeamMembershipChanges();
685     }
686
687     /**
688      * Returns true if the team memberships can be changed.
689      *
690      * @param user
691      * @return true if the team membership can be changed
692      */
693     @Override
694     public boolean supportsTeamMembershipChanges(TeamModel team) {
695         return (team != null && team.isLocalTeam()) || findProvider(team).supportsTeamMembershipChanges();
696     }
697
6e3481 698     /**
JM 699      * Returns true if the user's role can be changed.
700      *
701      * @param user
702      * @return true if the user's role can be changed
703      */
704     @Override
705     public boolean supportsRoleChanges(UserModel user, Role role) {
706         return (user != null && user.isLocalAccount()) || findProvider(user).supportsRoleChanges(user, role);
707     }
708
709     /**
710      * Returns true if the team's role can be changed.
711      *
712      * @param user
713      * @return true if the team's role can be changed
714      */
715     @Override
716     public boolean supportsRoleChanges(TeamModel team, Role role) {
717         return (team != null && team.isLocalTeam()) || findProvider(team).supportsRoleChanges(team, role);
718     }
719
04a985 720     protected AuthenticationProvider findProvider(UserModel user) {
JM 721         for (AuthenticationProvider provider : authenticationProviders) {
722             if (provider.getAccountType().equals(user.accountType)) {
723                 return provider;
724             }
725         }
726         return AuthenticationProvider.NULL_PROVIDER;
727     }
728
729     protected AuthenticationProvider findProvider(TeamModel team) {
730         for (AuthenticationProvider provider : authenticationProviders) {
731             if (provider.getAccountType().equals(team.accountType)) {
732                 return provider;
733             }
734         }
735         return AuthenticationProvider.NULL_PROVIDER;
736     }
737 }