| | |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | import javax.servlet.http.Cookie; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import javax.servlet.http.HttpSession; |
| | | |
| | | import org.apache.wicket.RequestCycle; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import com.gitblit.Constants; |
| | | import com.gitblit.Constants.AccountType; |
| | | import com.gitblit.Constants.AuthenticationType; |
| | | import com.gitblit.Constants.Role; |
| | | import com.gitblit.IStoredSettings; |
| | | import com.gitblit.Keys; |
| | | import com.gitblit.auth.AuthenticationProvider; |
| | |
| | | import com.gitblit.auth.WindowsAuthProvider; |
| | | import com.gitblit.models.TeamModel; |
| | | import com.gitblit.models.UserModel; |
| | | import com.gitblit.transport.ssh.SshKey; |
| | | import com.gitblit.utils.Base64; |
| | | import com.gitblit.utils.HttpUtils; |
| | | import com.gitblit.utils.StringUtils; |
| | | import com.gitblit.utils.X509Utils.X509Metadata; |
| | | import com.gitblit.wicket.GitBlitWebSession; |
| | | import com.google.inject.Inject; |
| | | import com.google.inject.Singleton; |
| | | |
| | | /** |
| | | * The authentication manager handles user login & logout. |
| | |
| | | * @author James Moger |
| | | * |
| | | */ |
| | | @Singleton |
| | | public class AuthenticationManager implements IAuthenticationManager { |
| | | |
| | | private final Logger logger = LoggerFactory.getLogger(getClass()); |
| | |
| | | |
| | | private final Map<String, String> legacyRedirects; |
| | | |
| | | @Inject |
| | | public AuthenticationManager( |
| | | IRuntimeManager runtimeManager, |
| | | IUserManager userManager) { |
| | |
| | | String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.conf"); |
| | | if (legacyRedirects.containsKey(realm)) { |
| | | logger.warn(""); |
| | | logger.warn("#################################################################"); |
| | | logger.warn(Constants.BORDER2); |
| | | logger.warn(" IUserService '{}' is obsolete!", realm); |
| | | logger.warn(" Please set '{}={}'", "realm.authenticationProviders", legacyRedirects.get(realm)); |
| | | logger.warn("#################################################################"); |
| | | logger.warn(Constants.BORDER2); |
| | | logger.warn(""); |
| | | |
| | | // conditionally override specified authentication providers |
| | |
| | | |
| | | @Override |
| | | public AuthenticationManager stop() { |
| | | for (AuthenticationProvider provider : authenticationProviders) { |
| | | try { |
| | | provider.stop(); |
| | | } catch (Exception e) { |
| | | logger.error("Failed to stop " + provider.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | return this; |
| | | } |
| | | |
| | | public void addAuthenticationProvider(AuthenticationProvider prov) { |
| | | authenticationProviders.add(prov); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Override |
| | | public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { |
| | | |
| | | // Check if this request has already been authenticated, and trust that instead of re-processing |
| | | String reqAuthUser = (String) httpRequest.getAttribute(Constants.ATTRIB_AUTHUSER); |
| | | if (!StringUtils.isEmpty(reqAuthUser)) { |
| | | logger.warn("Called servlet authenticate when request is already authenticated."); |
| | | return userManager.getUserModel(reqAuthUser); |
| | | } |
| | | |
| | | // try to authenticate by servlet container principal |
| | | if (!requiresCertificate) { |
| | | Principal principal = httpRequest.getUserPrincipal(); |
| | | if (principal != null) { |
| | | String username = principal.getName(); |
| | | if (!StringUtils.isEmpty(username)) { |
| | | boolean internalAccount = isInternalAccount(username); |
| | | boolean internalAccount = userManager.isInternalAccount(username); |
| | | UserModel user = userManager.getUserModel(username); |
| | | if (user != null) { |
| | | // existing user |
| | | flagWicketSession(AuthenticationType.CONTAINER); |
| | | flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username); |
| | | logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}", |
| | | user.username, httpRequest.getRemoteAddr())); |
| | | return user; |
| | | return validateAuthentication(user, AuthenticationType.CONTAINER); |
| | | } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false) |
| | | && !internalAccount) { |
| | | // auto-create user from an authenticated container principal |
| | |
| | | user.displayName = username; |
| | | user.password = Constants.EXTERNAL_ACCOUNT; |
| | | user.accountType = AccountType.CONTAINER; |
| | | |
| | | // Try to extract user's informations for the session |
| | | // it uses "realm.container.autoAccounts.*" as the attribute name to look for |
| | | HttpSession session = httpRequest.getSession(); |
| | | String emailAddress = resolveAttribute(session, Keys.realm.container.autoAccounts.emailAddress); |
| | | if(emailAddress != null) { |
| | | user.emailAddress = emailAddress; |
| | | } |
| | | String displayName = resolveAttribute(session, Keys.realm.container.autoAccounts.displayName); |
| | | if(displayName != null) { |
| | | user.displayName = displayName; |
| | | } |
| | | String userLocale = resolveAttribute(session, Keys.realm.container.autoAccounts.locale); |
| | | if(userLocale != null) { |
| | | user.getPreferences().setLocale(userLocale); |
| | | } |
| | | String adminRole = settings.getString(Keys.realm.container.autoAccounts.adminRole, null); |
| | | if(adminRole != null && ! adminRole.isEmpty()) { |
| | | if(httpRequest.isUserInRole(adminRole)) { |
| | | user.canAdmin = true; |
| | | } |
| | | } |
| | | |
| | | userManager.updateUserModel(user); |
| | | flagWicketSession(AuthenticationType.CONTAINER); |
| | | flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username); |
| | | logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}", |
| | | user.username, httpRequest.getRemoteAddr())); |
| | | return user; |
| | | return validateAuthentication(user, AuthenticationType.CONTAINER); |
| | | } else if (!internalAccount) { |
| | | logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}", |
| | | principal.getName(), httpRequest.getRemoteAddr())); |
| | |
| | | UserModel user = userManager.getUserModel(model.username); |
| | | X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest); |
| | | if (user != null) { |
| | | flagWicketSession(AuthenticationType.CERTIFICATE); |
| | | flagRequest(httpRequest, AuthenticationType.CERTIFICATE, user.username); |
| | | logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}", |
| | | user.username, metadata.serialNumber, httpRequest.getRemoteAddr())); |
| | | return user; |
| | | return validateAuthentication(user, AuthenticationType.CERTIFICATE); |
| | | } else { |
| | | logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}", |
| | | model.username, metadata.serialNumber, httpRequest.getRemoteAddr())); |
| | |
| | | return null; |
| | | } |
| | | |
| | | UserModel user = null; |
| | | |
| | | // try to authenticate by cookie |
| | | UserModel user = authenticate(httpRequest.getCookies()); |
| | | if (user != null) { |
| | | flagWicketSession(AuthenticationType.COOKIE); |
| | | logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}", |
| | | String cookie = getCookie(httpRequest); |
| | | if (!StringUtils.isEmpty(cookie)) { |
| | | user = userManager.getUserModel(cookie.toCharArray()); |
| | | if (user != null) { |
| | | flagRequest(httpRequest, AuthenticationType.COOKIE, user.username); |
| | | logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}", |
| | | user.username, httpRequest.getRemoteAddr())); |
| | | return user; |
| | | return validateAuthentication(user, AuthenticationType.COOKIE); |
| | | } |
| | | } |
| | | |
| | | // try to authenticate by BASIC |
| | |
| | | if (values.length == 2) { |
| | | String username = values[0]; |
| | | char[] password = values[1].toCharArray(); |
| | | user = authenticate(username, password); |
| | | user = authenticate(username, password, httpRequest.getRemoteAddr()); |
| | | if (user != null) { |
| | | flagWicketSession(AuthenticationType.CREDENTIALS); |
| | | flagRequest(httpRequest, AuthenticationType.CREDENTIALS, user.username); |
| | | logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}", |
| | | user.username, httpRequest.getRemoteAddr())); |
| | | return user; |
| | | } else { |
| | | logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", |
| | | username, httpRequest.getRemoteAddr())); |
| | | return validateAuthentication(user, AuthenticationType.CREDENTIALS); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Extract given attribute from the session and return it's content |
| | | * it return null if attributeMapping is empty, or if the value is |
| | | * empty |
| | | * |
| | | * @param session The user session |
| | | * @param attributeMapping |
| | | * @return |
| | | */ |
| | | private String resolveAttribute(HttpSession session, String attributeMapping) { |
| | | String attributeName = settings.getString(attributeMapping, null); |
| | | if(StringUtils.isEmpty(attributeName)) { |
| | | return null; |
| | | } |
| | | Object attributeValue = session.getAttribute(attributeName); |
| | | if(attributeValue == null) { |
| | | return null; |
| | | } |
| | | String value = attributeValue.toString(); |
| | | if(value.isEmpty()) { |
| | | return null; |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | /** |
| | | * Authenticate a user based on their cookie. |
| | | * Authenticate a user based on a public key. |
| | | * |
| | | * @param cookies |
| | | * This implementation assumes that the authentication has already take place |
| | | * (e.g. SSHDaemon) and that this is a validation/verification of the user. |
| | | * |
| | | * @param username |
| | | * @param key |
| | | * @return a user object or null |
| | | */ |
| | | protected UserModel authenticate(Cookie[] cookies) { |
| | | if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) { |
| | | if (cookies != null && cookies.length > 0) { |
| | | for (Cookie cookie : cookies) { |
| | | if (cookie.getName().equals(Constants.NAME)) { |
| | | String value = cookie.getValue(); |
| | | return userManager.getUserModel(value.toCharArray()); |
| | | } |
| | | @Override |
| | | public UserModel authenticate(String username, SshKey key) { |
| | | if (username != null) { |
| | | if (!StringUtils.isEmpty(username)) { |
| | | UserModel user = userManager.getUserModel(username); |
| | | if (user != null) { |
| | | // existing user |
| | | logger.debug(MessageFormat.format("{0} authenticated by {1} public key", |
| | | user.username, key.getAlgorithm())); |
| | | return validateAuthentication(user, AuthenticationType.PUBLIC_KEY); |
| | | } |
| | | logger.warn(MessageFormat.format("Failed to find UserModel for {0} during public key authentication", |
| | | username)); |
| | | } |
| | | } else { |
| | | logger.warn("Empty user passed to AuthenticationManager.authenticate!"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | protected void flagWicketSession(AuthenticationType authenticationType) { |
| | | RequestCycle requestCycle = RequestCycle.get(); |
| | | if (requestCycle != null) { |
| | | // flag the Wicket session, if this is a Wicket request |
| | | GitBlitWebSession session = GitBlitWebSession.get(); |
| | | session.authenticationType = authenticationType; |
| | | |
| | | /** |
| | | * Return the UserModel for already authenticated user. |
| | | * |
| | | * This implementation assumes that the authentication has already take place |
| | | * (e.g. SSHDaemon) and that this is a validation/verification of the user. |
| | | * |
| | | * @param username |
| | | * @return a user object or null |
| | | */ |
| | | @Override |
| | | public UserModel authenticate(String username) { |
| | | if (username != null) { |
| | | if (!StringUtils.isEmpty(username)) { |
| | | UserModel user = userManager.getUserModel(username); |
| | | if (user != null) { |
| | | // existing user |
| | | logger.debug(MessageFormat.format("{0} authenticated externally", user.username)); |
| | | return validateAuthentication(user, AuthenticationType.CONTAINER); |
| | | } |
| | | logger.warn(MessageFormat.format("Failed to find UserModel for {0} during external authentication", |
| | | username)); |
| | | } |
| | | } else { |
| | | logger.warn("Empty user passed to AuthenticationManager.authenticate!"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * This method allows the authentication manager to reject authentication |
| | | * attempts. It is called after the username/secret have been verified to |
| | | * ensure that the authentication technique has been logged. |
| | | * |
| | | * @param user |
| | | * @return |
| | | */ |
| | | protected UserModel validateAuthentication(UserModel user, AuthenticationType type) { |
| | | if (user == null) { |
| | | return null; |
| | | } |
| | | if (user.disabled) { |
| | | // user has been disabled |
| | | logger.warn("Rejected {} authentication attempt by disabled account \"{}\"", |
| | | type, user.username); |
| | | return null; |
| | | } |
| | | return user; |
| | | } |
| | | |
| | | protected void flagRequest(HttpServletRequest httpRequest, AuthenticationType authenticationType, String authedUsername) { |
| | | httpRequest.setAttribute(Constants.ATTRIB_AUTHUSER, authedUsername); |
| | | httpRequest.setAttribute(Constants.ATTRIB_AUTHTYPE, authenticationType); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return a user object or null |
| | | */ |
| | | @Override |
| | | public UserModel authenticate(String username, char[] password) { |
| | | public UserModel authenticate(String username, char[] password, String remoteIP) { |
| | | if (StringUtils.isEmpty(username)) { |
| | | // can not authenticate empty username |
| | | return null; |
| | |
| | | // can not authenticate empty password |
| | | return null; |
| | | } |
| | | // check to see if this is the federation user |
| | | // if (canFederate()) { |
| | | // if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) { |
| | | // List<String> tokens = getFederationTokens(); |
| | | // if (tokens.contains(pw)) { |
| | | // return getFederationUser(); |
| | | // } |
| | | // } |
| | | // } |
| | | |
| | | UserModel user = userManager.getUserModel(usernameDecoded); |
| | | |
| | | // try local authentication |
| | | UserModel user = userManager.getUserModel(usernameDecoded); |
| | | if (user != null) { |
| | | UserModel returnedUser = null; |
| | | if (user.password.startsWith(StringUtils.MD5_TYPE)) { |
| | | // password digest |
| | | String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); |
| | | if (user.password.equalsIgnoreCase(md5)) { |
| | | returnedUser = user; |
| | | } |
| | | } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { |
| | | // username+password digest |
| | | String md5 = StringUtils.COMBINED_MD5_TYPE |
| | | + StringUtils.getMD5(username.toLowerCase() + new String(password)); |
| | | if (user.password.equalsIgnoreCase(md5)) { |
| | | returnedUser = user; |
| | | } |
| | | } else if (user.password.equals(new String(password))) { |
| | | // plain-text password |
| | | returnedUser = user; |
| | | if (user != null && user.isLocalAccount()) { |
| | | UserModel returnedUser = authenticateLocal(user, password); |
| | | if (returnedUser != null) { |
| | | // user authenticated |
| | | return returnedUser; |
| | | } |
| | | return returnedUser; |
| | | } |
| | | |
| | | // try registered external authentication providers |
| | | if (user == null) { |
| | | } else { |
| | | // try registered external authentication providers |
| | | for (AuthenticationProvider provider : authenticationProviders) { |
| | | if (provider instanceof UsernamePasswordAuthenticationProvider) { |
| | | user = provider.authenticate(usernameDecoded, password); |
| | | if (user != null) { |
| | | UserModel returnedUser = provider.authenticate(usernameDecoded, password); |
| | | if (returnedUser != null) { |
| | | // user authenticated |
| | | user.accountType = provider.getAccountType(); |
| | | return user; |
| | | returnedUser.accountType = provider.getAccountType(); |
| | | return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return user; |
| | | |
| | | // could not authenticate locally or with a provider |
| | | logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", username, |
| | | remoteIP != null ? remoteIP : "unknown")); |
| | | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Returns a UserModel if local authentication succeeds. |
| | | * |
| | | * @param user |
| | | * @param password |
| | | * @return a UserModel if local authentication succeeds, null otherwise |
| | | */ |
| | | protected UserModel authenticateLocal(UserModel user, char [] password) { |
| | | UserModel returnedUser = null; |
| | | if (user.password.startsWith(StringUtils.MD5_TYPE)) { |
| | | // password digest |
| | | String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); |
| | | if (user.password.equalsIgnoreCase(md5)) { |
| | | returnedUser = user; |
| | | } |
| | | } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { |
| | | // username+password digest |
| | | String md5 = StringUtils.COMBINED_MD5_TYPE |
| | | + StringUtils.getMD5(user.username.toLowerCase() + new String(password)); |
| | | if (user.password.equalsIgnoreCase(md5)) { |
| | | returnedUser = user; |
| | | } |
| | | } else if (user.password.equals(new String(password))) { |
| | | // plain-text password |
| | | returnedUser = user; |
| | | } |
| | | return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS); |
| | | } |
| | | |
| | | /** |
| | | * Returns the Gitlbit cookie in the request. |
| | | * |
| | | * @param request |
| | | * @return the Gitblit cookie for the request or null if not found |
| | | */ |
| | | @Override |
| | | public String getCookie(HttpServletRequest request) { |
| | | if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) { |
| | | Cookie[] cookies = request.getCookies(); |
| | | if (cookies != null && cookies.length > 0) { |
| | | for (Cookie cookie : cookies) { |
| | | if (cookie.getName().equals(Constants.NAME)) { |
| | | String value = cookie.getValue(); |
| | | return value; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param user |
| | | */ |
| | | @Override |
| | | @Deprecated |
| | | public void setCookie(HttpServletResponse response, UserModel user) { |
| | | setCookie(null, response, user); |
| | | } |
| | | |
| | | /** |
| | | * Sets a cookie for the specified user. |
| | | * |
| | | * @param request |
| | | * @param response |
| | | * @param user |
| | | */ |
| | | @Override |
| | | public void setCookie(HttpServletRequest request, HttpServletResponse response, UserModel user) { |
| | | if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) { |
| | | GitBlitWebSession session = GitBlitWebSession.get(); |
| | | boolean standardLogin = session.authenticationType.isStandard(); |
| | | boolean standardLogin = true; |
| | | |
| | | if (null != request) { |
| | | // Pull the auth type from the request, it is set there if container managed |
| | | AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE); |
| | | |
| | | if (null != authenticationType) |
| | | standardLogin = authenticationType.isStandard(); |
| | | } |
| | | |
| | | if (standardLogin) { |
| | | Cookie userCookie; |
| | |
| | | } else { |
| | | // create real cookie |
| | | userCookie = new Cookie(Constants.NAME, cookie); |
| | | userCookie.setMaxAge(Integer.MAX_VALUE); |
| | | // expire the cookie in 7 days |
| | | userCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(7)); |
| | | } |
| | | } |
| | | userCookie.setPath("/"); |
| | | String path = "/"; |
| | | if (request != null) { |
| | | if (!StringUtils.isEmpty(request.getContextPath())) { |
| | | path = request.getContextPath(); |
| | | } |
| | | } |
| | | userCookie.setPath(path); |
| | | response.addCookie(userCookie); |
| | | } |
| | | } |
| | |
| | | /** |
| | | * Logout a user. |
| | | * |
| | | * @param response |
| | | * @param user |
| | | */ |
| | | @Override |
| | | @Deprecated |
| | | public void logout(HttpServletResponse response, UserModel user) { |
| | | setCookie(response, null); |
| | | setCookie(null, response, null); |
| | | } |
| | | |
| | | /** |
| | | * Logout a user. |
| | | * |
| | | * @param request |
| | | * @param response |
| | | * @param user |
| | | */ |
| | | @Override |
| | | public void logout(HttpServletRequest request, HttpServletResponse response, UserModel user) { |
| | | setCookie(request, response, null); |
| | | } |
| | | |
| | | /** |
| | |
| | | return (team != null && team.isLocalTeam()) || findProvider(team).supportsTeamMembershipChanges(); |
| | | } |
| | | |
| | | /** |
| | | * Returns true if the user's role can be changed. |
| | | * |
| | | * @param user |
| | | * @return true if the user's role can be changed |
| | | */ |
| | | @Override |
| | | public boolean supportsRoleChanges(UserModel user, Role role) { |
| | | return (user != null && user.isLocalAccount()) || findProvider(user).supportsRoleChanges(user, role); |
| | | } |
| | | |
| | | /** |
| | | * Returns true if the team's role can be changed. |
| | | * |
| | | * @param user |
| | | * @return true if the team's role can be changed |
| | | */ |
| | | @Override |
| | | public boolean supportsRoleChanges(TeamModel team, Role role) { |
| | | return (team != null && team.isLocalTeam()) || findProvider(team).supportsRoleChanges(team, role); |
| | | } |
| | | |
| | | protected AuthenticationProvider findProvider(UserModel user) { |
| | | for (AuthenticationProvider provider : authenticationProviders) { |
| | | if (provider.getAccountType().equals(user.accountType)) { |
| | |
| | | } |
| | | return AuthenticationProvider.NULL_PROVIDER; |
| | | } |
| | | |
| | | /** |
| | | * Returns true if the username represents an internal account |
| | | * |
| | | * @param username |
| | | * @return true if the specified username represents an internal account |
| | | */ |
| | | protected boolean isInternalAccount(String username) { |
| | | return !StringUtils.isEmpty(username) |
| | | && (username.equalsIgnoreCase(Constants.FEDERATION_USER) |
| | | || username.equalsIgnoreCase(UserModel.ANONYMOUS.username)); |
| | | } |
| | | |
| | | // protected UserModel getFederationUser() { |
| | | // // the federation user is an administrator |
| | | // UserModel federationUser = new UserModel(Constants.FEDERATION_USER); |
| | | // federationUser.canAdmin = true; |
| | | // return federationUser; |
| | | // } |
| | | } |