James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
src/main/java/com/gitblit/manager/AuthenticationManager.java
@@ -22,18 +22,20 @@
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;
@@ -46,11 +48,13 @@
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.
@@ -58,6 +62,7 @@
 * @author James Moger
 *
 */
@Singleton
public class AuthenticationManager implements IAuthenticationManager {
   private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -74,6 +79,7 @@
   private final Map<String, String> legacyRedirects;
   @Inject
   public AuthenticationManager(
         IRuntimeManager runtimeManager,
         IUserManager userManager) {
@@ -108,10 +114,10 @@
      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
@@ -149,7 +155,18 @@
   @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);
   }
   /**
@@ -177,20 +194,28 @@
    */
   @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
@@ -198,11 +223,34 @@
                  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()));
@@ -220,10 +268,10 @@
         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()));
@@ -235,13 +283,18 @@
         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
@@ -257,48 +310,127 @@
         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);
   }
   /**
@@ -310,7 +442,7 @@
    * @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;
@@ -322,54 +454,86 @@
         // 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;
   }
   /**
@@ -379,10 +543,30 @@
    * @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;
@@ -398,10 +582,17 @@
               } 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);
         }
      }
@@ -410,11 +601,25 @@
   /**
    * 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);
   }
   /**
@@ -472,6 +677,28 @@
      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)) {
@@ -489,23 +716,4 @@
      }
      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;
//   }
}