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