James Moger
2015-09-18 03223516b32758fe9d5602ffb4ce10a8a308f0e9
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();
313                 user = authenticate(username, password);
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                 } else {
JM 320                     logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}",
321                             username, httpRequest.getRemoteAddr()));
322                 }
323             }
324         }
325         return null;
9aa119 326     }
2c0555 327     
FB 328     /**
329      * Extract given attribute from the session and return it's content
330      * it return null if attributeMapping is empty, or if the value is
331      * empty
332      * 
333      * @param session The user session
334      * @param attributeMapping
335      * @return
336      */
337     private String resolveAttribute(HttpSession session, String attributeMapping) {
338         String attributeName = settings.getString(attributeMapping, null);
339         if(StringUtils.isEmpty(attributeName)) {
340             return null;
341         }
342         Object attributeValue = session.getAttribute(attributeName);
343         if(attributeValue == null) {
344             return null;
345         }
346         String value = attributeValue.toString();
347         if(value.isEmpty()) {
348             return null;
349         }
350         return value;
351     }
9aa119 352
JM 353     /**
44e2ee 354      * Authenticate a user based on a public key.
e3b636 355      *
44e2ee 356      * This implementation assumes that the authentication has already take place
JM 357      * (e.g. SSHDaemon) and that this is a validation/verification of the user.
358      *
359      * @param username
360      * @param key
e3b636 361      * @return a user object or null
DO 362      */
363     @Override
bcc8a0 364     public UserModel authenticate(String username, SshKey key) {
e3b636 365         if (username != null) {
DO 366             if (!StringUtils.isEmpty(username)) {
367                 UserModel user = userManager.getUserModel(username);
368                 if (user != null) {
369                     // existing user
44e2ee 370                     logger.debug(MessageFormat.format("{0} authenticated by {1} public key",
JM 371                             user.username, key.getAlgorithm()));
372                     return validateAuthentication(user, AuthenticationType.PUBLIC_KEY);
e3b636 373                 }
44e2ee 374                 logger.warn(MessageFormat.format("Failed to find UserModel for {0} during public key authentication",
JM 375                             username));
e3b636 376             }
DO 377         } else {
44e2ee 378             logger.warn("Empty user passed to AuthenticationManager.authenticate!");
e3b636 379         }
DO 380         return null;
381     }
382
383
384     /**
e97c01 385      * Return the UserModel for already authenticated user.
FB 386      *
387      * This implementation assumes that the authentication has already take place
388      * (e.g. SSHDaemon) and that this is a validation/verification of the user.
389      *
390      * @param username
391      * @return a user object or null
392      */
393     @Override
394     public UserModel authenticate(String username) {
395         if (username != null) {
396             if (!StringUtils.isEmpty(username)) {
397                 UserModel user = userManager.getUserModel(username);
398                 if (user != null) {
399                     // existing user
400                     logger.debug(MessageFormat.format("{0} authenticated externally", user.username));
401                     return validateAuthentication(user, AuthenticationType.CONTAINER);
402                 }
403                 logger.warn(MessageFormat.format("Failed to find UserModel for {0} during external authentication",
404                             username));
405             }
406         } else {
407             logger.warn("Empty user passed to AuthenticationManager.authenticate!");
408         }
409         return null;
410     }
411
412
413     /**
9aa119 414      * This method allows the authentication manager to reject authentication
JM 415      * attempts.  It is called after the username/secret have been verified to
416      * ensure that the authentication technique has been logged.
417      *
418      * @param user
419      * @return
420      */
421     protected UserModel validateAuthentication(UserModel user, AuthenticationType type) {
422         if (user == null) {
423             return null;
424         }
425         if (user.disabled) {
426             // user has been disabled
427             logger.warn("Rejected {} authentication attempt by disabled account \"{}\"",
428                     type, user.username);
429             return null;
430         }
431         return user;
04a985 432     }
JM 433
62e025 434     protected void flagRequest(HttpServletRequest httpRequest, AuthenticationType authenticationType, String authedUsername) {
JJ 435         httpRequest.setAttribute(Constants.ATTRIB_AUTHUSER,  authedUsername);
436         httpRequest.setAttribute(Constants.ATTRIB_AUTHTYPE,  authenticationType);
04a985 437     }
JM 438
439     /**
440      * Authenticate a user based on a username and password.
441      *
442      * @see IUserService.authenticate(String, char[])
443      * @param username
444      * @param password
445      * @return a user object or null
446      */
447     @Override
448     public UserModel authenticate(String username, char[] password) {
449         if (StringUtils.isEmpty(username)) {
450             // can not authenticate empty username
451             return null;
452         }
453
454         String usernameDecoded = StringUtils.decodeUsername(username);
455         String pw = new String(password);
456         if (StringUtils.isEmpty(pw)) {
457             // can not authenticate empty password
458             return null;
459         }
460
461         UserModel user = userManager.getUserModel(usernameDecoded);
1293c2 462
JM 463         // try local authentication
464         if (user != null && user.isLocalAccount()) {
b4a63a 465             return authenticateLocal(user, password);
04a985 466         }
JM 467
468         // try registered external authentication providers
b4a63a 469         for (AuthenticationProvider provider : authenticationProviders) {
JM 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);
04a985 476                 }
JM 477             }
478         }
bcc8a0 479
b4a63a 480         // could not authenticate locally or with a provider
JM 481         return null;
482     }
bcc8a0 483
b4a63a 484     /**
JM 485      * Returns a UserModel if local authentication succeeds.
bcc8a0 486      *
b4a63a 487      * @param user
JM 488      * @param password
489      * @return a UserModel if local authentication succeeds, null otherwise
490      */
491     protected UserModel authenticateLocal(UserModel user, char [] password) {
492         UserModel returnedUser = null;
493         if (user.password.startsWith(StringUtils.MD5_TYPE)) {
494             // password digest
495             String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
496             if (user.password.equalsIgnoreCase(md5)) {
497                 returnedUser = user;
498             }
499         } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
500             // username+password digest
501             String md5 = StringUtils.COMBINED_MD5_TYPE
502                     + StringUtils.getMD5(user.username.toLowerCase() + new String(password));
503             if (user.password.equalsIgnoreCase(md5)) {
504                 returnedUser = user;
505             }
506         } else if (user.password.equals(new String(password))) {
507             // plain-text password
508             returnedUser = user;
509         }
510         return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS);
04a985 511     }
JM 512
513     /**
7ab32b 514      * Returns the Gitlbit cookie in the request.
JM 515      *
516      * @param request
517      * @return the Gitblit cookie for the request or null if not found
518      */
519     @Override
520     public String getCookie(HttpServletRequest request) {
521         if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) {
522             Cookie[] cookies = request.getCookies();
523             if (cookies != null && cookies.length > 0) {
524                 for (Cookie cookie : cookies) {
525                     if (cookie.getName().equals(Constants.NAME)) {
526                         String value = cookie.getValue();
527                         return value;
528                     }
529                 }
530             }
531         }
532         return null;
533     }
534
535     /**
04a985 536      * Sets a cookie for the specified user.
JM 537      *
538      * @param response
539      * @param user
540      */
541     @Override
ec7ed8 542     @Deprecated
04a985 543     public void setCookie(HttpServletResponse response, UserModel user) {
ec7ed8 544         setCookie(null, response, user);
JM 545     }
546
547     /**
548      * Sets a cookie for the specified user.
549      *
550      * @param request
551      * @param response
552      * @param user
553      */
554     @Override
555     public void setCookie(HttpServletRequest request, HttpServletResponse response, UserModel user) {
04a985 556         if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) {
62e025 557             boolean standardLogin = true;
JJ 558
559             if (null != request) {
560                 // Pull the auth type from the request, it is set there if container managed
561                 AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE);
562
563                 if (null != authenticationType)
564                     standardLogin = authenticationType.isStandard();
565             }
04a985 566
JM 567             if (standardLogin) {
568                 Cookie userCookie;
569                 if (user == null) {
570                     // clear cookie for logout
571                     userCookie = new Cookie(Constants.NAME, "");
572                 } else {
573                     // set cookie for login
574                     String cookie = userManager.getCookie(user);
575                     if (StringUtils.isEmpty(cookie)) {
576                         // create empty cookie
577                         userCookie = new Cookie(Constants.NAME, "");
578                     } else {
579                         // create real cookie
580                         userCookie = new Cookie(Constants.NAME, cookie);
7ab32b 581                         // expire the cookie in 7 days
JM 582                         userCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(7));
04a985 583                     }
JM 584                 }
ec7ed8 585                 String path = "/";
JM 586                 if (request != null) {
587                     if (!StringUtils.isEmpty(request.getContextPath())) {
588                         path = request.getContextPath();
589                     }
590                 }
591                 userCookie.setPath(path);
04a985 592                 response.addCookie(userCookie);
JM 593             }
594         }
595     }
596
597     /**
598      * Logout a user.
599      *
ec7ed8 600      * @param response
04a985 601      * @param user
JM 602      */
603     @Override
ec7ed8 604     @Deprecated
04a985 605     public void logout(HttpServletResponse response, UserModel user) {
ec7ed8 606         setCookie(null, response,  null);
JM 607     }
608
609     /**
610      * Logout a user.
611      *
612      * @param request
613      * @param response
614      * @param user
615      */
616     @Override
617     public void logout(HttpServletRequest request, HttpServletResponse response, UserModel user) {
618         setCookie(request, response,  null);
04a985 619     }
JM 620
621     /**
622      * Returns true if the user's credentials can be changed.
623      *
624      * @param user
625      * @return true if the user service supports credential changes
626      */
627     @Override
628     public boolean supportsCredentialChanges(UserModel user) {
629         return (user != null && user.isLocalAccount()) || findProvider(user).supportsCredentialChanges();
630     }
631
632     /**
633      * Returns true if the user's display name can be changed.
634      *
635      * @param user
636      * @return true if the user service supports display name changes
637      */
638     @Override
639     public boolean supportsDisplayNameChanges(UserModel user) {
640         return (user != null && user.isLocalAccount()) || findProvider(user).supportsDisplayNameChanges();
641     }
642
643     /**
644      * Returns true if the user's email address can be changed.
645      *
646      * @param user
647      * @return true if the user service supports email address changes
648      */
649     @Override
650     public boolean supportsEmailAddressChanges(UserModel user) {
651         return (user != null && user.isLocalAccount()) || findProvider(user).supportsEmailAddressChanges();
652     }
653
654     /**
655      * Returns true if the user's team memberships can be changed.
656      *
657      * @param user
658      * @return true if the user service supports team membership changes
659      */
660     @Override
661     public boolean supportsTeamMembershipChanges(UserModel user) {
662         return (user != null && user.isLocalAccount()) || findProvider(user).supportsTeamMembershipChanges();
663     }
664
665     /**
666      * Returns true if the team memberships can be changed.
667      *
668      * @param user
669      * @return true if the team membership can be changed
670      */
671     @Override
672     public boolean supportsTeamMembershipChanges(TeamModel team) {
673         return (team != null && team.isLocalTeam()) || findProvider(team).supportsTeamMembershipChanges();
674     }
675
6e3481 676     /**
JM 677      * Returns true if the user's role can be changed.
678      *
679      * @param user
680      * @return true if the user's role can be changed
681      */
682     @Override
683     public boolean supportsRoleChanges(UserModel user, Role role) {
684         return (user != null && user.isLocalAccount()) || findProvider(user).supportsRoleChanges(user, role);
685     }
686
687     /**
688      * Returns true if the team's role can be changed.
689      *
690      * @param user
691      * @return true if the team's role can be changed
692      */
693     @Override
694     public boolean supportsRoleChanges(TeamModel team, Role role) {
695         return (team != null && team.isLocalTeam()) || findProvider(team).supportsRoleChanges(team, role);
696     }
697
04a985 698     protected AuthenticationProvider findProvider(UserModel user) {
JM 699         for (AuthenticationProvider provider : authenticationProviders) {
700             if (provider.getAccountType().equals(user.accountType)) {
701                 return provider;
702             }
703         }
704         return AuthenticationProvider.NULL_PROVIDER;
705     }
706
707     protected AuthenticationProvider findProvider(TeamModel team) {
708         for (AuthenticationProvider provider : authenticationProviders) {
709             if (provider.getAccountType().equals(team.accountType)) {
710                 return provider;
711             }
712         }
713         return AuthenticationProvider.NULL_PROVIDER;
714     }
715 }