From 04a98505a4ab8f48aee22800fcac193d9367d0ae Mon Sep 17 00:00:00 2001 From: James Moger <james.moger@gitblit.com> Date: Fri, 29 Nov 2013 11:05:51 -0500 Subject: [PATCH] Refactor user services and separate authentication (issue-281) --- src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java | 219 ++++++++++++++++-------------------------------------- 1 files changed, 67 insertions(+), 152 deletions(-) diff --git a/src/main/java/com/gitblit/HtpasswdUserService.java b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java similarity index 60% rename from src/main/java/com/gitblit/HtpasswdUserService.java rename to src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java index ca5295c..559a0fa 100644 --- a/src/main/java/com/gitblit/HtpasswdUserService.java +++ b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.gitblit; +package com.gitblit.auth; import java.io.File; import java.io.FileInputStream; @@ -29,11 +29,11 @@ import org.apache.commons.codec.digest.Crypt; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.Md5Crypt; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.gitblit.Constants; import com.gitblit.Constants.AccountType; -import com.gitblit.manager.IRuntimeManager; +import com.gitblit.Keys; +import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.StringUtils; @@ -64,51 +64,24 @@ * @author Florian Zschocke * */ -public class HtpasswdUserService extends GitblitUserService -{ - - private static final String KEY_BACKING_US = Keys.realm.htpasswd.backingUserService; - private static final String DEFAULT_BACKING_US = "${baseFolder}/users.conf"; +public class HtpasswdAuthProvider extends UsernamePasswordAuthenticationProvider { private static final String KEY_HTPASSWD_FILE = Keys.realm.htpasswd.userfile; private static final String DEFAULT_HTPASSWD_FILE = "${baseFolder}/htpasswd"; - private static final String KEY_OVERRIDE_LOCALAUTH = Keys.realm.htpasswd.overrideLocalAuthentication; - private static final boolean DEFAULT_OVERRIDE_LOCALAUTH = true; - private static final String KEY_SUPPORT_PLAINTEXT_PWD = "realm.htpasswd.supportPlaintextPasswords"; - private final boolean SUPPORT_PLAINTEXT_PWD; + private boolean supportPlainTextPwd; - private IRuntimeManager runtimeManager; - private IStoredSettings settings; private File htpasswdFile; - - - private final Logger logger = LoggerFactory.getLogger(HtpasswdUserService.class); private final Map<String, String> htUsers = new ConcurrentHashMap<String, String>(); private volatile long lastModified; - private volatile boolean forceReload; - - - - public HtpasswdUserService() - { - super(); - - String os = System.getProperty("os.name").toLowerCase(); - if (os.startsWith("windows") || os.startsWith("netware")) { - SUPPORT_PLAINTEXT_PWD = true; - } - else { - SUPPORT_PLAINTEXT_PWD = false; - } + public HtpasswdAuthProvider() { + super("htpasswd"); } - - /** * Setup the user service. @@ -118,42 +91,40 @@ * In addition the setup tries to read and parse the htpasswd file to be used * for authentication. * - * @param runtimeManager - * @since 1.4.0 + * @param settings + * @since 0.7.0 */ @Override - public void setup(IRuntimeManager runtimeManager) - { - this.runtimeManager = runtimeManager; - this.settings = runtimeManager.getSettings(); - - // This is done in two steps in order to avoid calling GitBlit.getFileOrFolder(String, String) which will segfault for unit tests. - String file = settings.getString(KEY_BACKING_US, DEFAULT_BACKING_US); - File realmFile = runtimeManager.getFileOrFolder(file); - serviceImpl = createUserService(realmFile); - logger.info("Htpasswd User Service backed by " + serviceImpl.toString()); - + public void setup() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.startsWith("windows") || os.startsWith("netware")) { + supportPlainTextPwd = true; + } else { + supportPlainTextPwd = false; + } read(); - logger.debug("Read " + htUsers.size() + " users from htpasswd file: " + this.htpasswdFile); } - - - /** - * For now, credentials are defined in the htpasswd file and can not be manipulated - * from Gitblit. - * - * @return false - * @since 1.0.0 - */ @Override - public boolean supportsCredentialChanges() - { + public boolean supportsCredentialChanges() { return false; } + @Override + public boolean supportsDisplayNameChanges() { + return true; + } + @Override + public boolean supportsEmailAddressChanges() { + return true; + } + + @Override + public boolean supportsTeamMembershipChanges() { + return true; + } /** * Authenticate a user based on a username and password. @@ -168,14 +139,7 @@ * @return a user object or null */ @Override - public UserModel authenticate(String username, char[] password) - { - if (isLocalAccount(username)) { - // local account, bypass htpasswd authentication - return super.authenticate(username, password); - } - - + public UserModel authenticate(String username, char[] password) { read(); String storedPwd = htUsers.get(username); if (storedPwd != null) { @@ -183,27 +147,27 @@ final String passwd = new String(password); // test Apache MD5 variant encrypted password - if ( storedPwd.startsWith("$apr1$") ) { - if ( storedPwd.equals(Md5Crypt.apr1Crypt(passwd, storedPwd)) ) { + if (storedPwd.startsWith("$apr1$")) { + if (storedPwd.equals(Md5Crypt.apr1Crypt(passwd, storedPwd))) { logger.debug("Apache MD5 encoded password matched for user '" + username + "'"); authenticated = true; } } // test unsalted SHA password - else if ( storedPwd.startsWith("{SHA}") ) { + else if (storedPwd.startsWith("{SHA}")) { String passwd64 = Base64.encodeBase64String(DigestUtils.sha1(passwd)); - if ( storedPwd.substring("{SHA}".length()).equals(passwd64) ) { + if (storedPwd.substring("{SHA}".length()).equals(passwd64)) { logger.debug("Unsalted SHA-1 encoded password matched for user '" + username + "'"); authenticated = true; } } // test libc crypt() encoded password - else if ( supportCryptPwd() && storedPwd.equals(Crypt.crypt(passwd, storedPwd)) ) { + else if (supportCryptPwd() && storedPwd.equals(Crypt.crypt(passwd, storedPwd))) { logger.debug("Libc crypt encoded password matched for user '" + username + "'"); authenticated = true; } // test clear text - else if ( supportPlaintextPwd() && storedPwd.equals(passwd) ){ + else if (supportPlaintextPwd() && storedPwd.equals(passwd)){ logger.debug("Clear text password matched for user '" + username + "'"); authenticated = true; } @@ -212,10 +176,13 @@ if (authenticated) { logger.debug("Htpasswd authenticated: " + username); - UserModel user = getUserModel(username); - if (user == null) { + UserModel curr = userManager.getUserModel(username); + UserModel user; + if (curr == null) { // create user object for new authenticated user user = new UserModel(username); + } else { + user = curr; } // create a user cookie @@ -228,7 +195,7 @@ user.accountType = getAccountType(); // Push the looked up values to backing file - super.updateUserModel(user); + updateUser(user); return user; } @@ -237,70 +204,29 @@ return null; } - - - /** - * Determine if the account is to be treated as a local account. - * - * This influences authentication. A local account will be authenticated - * by the backing user service while an external account will be handled - * by this user service. - * <br/> - * The decision also depends on the setting of the key - * realm.htpasswd.overrideLocalAuthentication. - * If it is set to true, then passwords will first be checked against the - * htpasswd store. If an account exists and is marked as local in the backing - * user service, that setting will be overwritten by the result. This - * means that an account that looks local to the backing user service will - * be turned into an external account upon valid login of a user that has - * an entry in the htpasswd file. - * If the key is set to false, then it is determined if the account is local - * according to the logic of the GitblitUserService. - */ - @Override - protected boolean isLocalAccount(String username) - { - if ( settings.getBoolean(KEY_OVERRIDE_LOCALAUTH, DEFAULT_OVERRIDE_LOCALAUTH) ) { - read(); - if ( htUsers.containsKey(username) ) return false; - } - return super.isLocalAccount(username); - } - - - /** * Get the account type used for this user service. * * @return AccountType.HTPASSWD */ @Override - public AccountType getAccountType() - { + public AccountType getAccountType() { return AccountType.HTPASSWD; } - - - private String htpasswdFilePath = null; /** * Reads the realm file and rebuilds the in-memory lookup tables. */ - protected synchronized void read() - { - - // This is done in two steps in order to avoid calling GitBlit.getFileOrFolder(String, String) which will segfault for unit tests. - String file = settings.getString(KEY_HTPASSWD_FILE, DEFAULT_HTPASSWD_FILE); - if ( !file.equals(htpasswdFilePath) ) { - // The htpasswd file setting changed. Rediscover the file. - this.htpasswdFilePath = file; - this.htpasswdFile = runtimeManager.getFileOrFolder(file); + protected synchronized void read() { + boolean forceReload = false; + File file = getFileOrFolder(KEY_HTPASSWD_FILE, DEFAULT_HTPASSWD_FILE); + if (!file.equals(htpasswdFile)) { + this.htpasswdFile = file; this.htUsers.clear(); - this.forceReload = true; + forceReload = true; } if (htpasswdFile.exists() && (forceReload || (htpasswdFile.lastModified() != lastModified))) { - forceReload = false; lastModified = htpasswdFile.lastModified(); htUsers.clear(); @@ -309,53 +235,42 @@ Scanner scanner = null; try { scanner = new Scanner(new FileInputStream(htpasswdFile)); - while( scanner.hasNextLine()) { + while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); - if ( !line.isEmpty() && !line.startsWith("#") ) { + if (!line.isEmpty() && !line.startsWith("#")) { Matcher m = entry.matcher(line); - if ( m.matches() ) { + if (m.matches()) { htUsers.put(m.group(1), m.group(2)); } } } } catch (Exception e) { logger.error(MessageFormat.format("Failed to read {0}", htpasswdFile), e); - } - finally { - if (scanner != null) scanner.close(); + } finally { + if (scanner != null) { + scanner.close(); + } } } } - - - private boolean supportPlaintextPwd() - { - return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, SUPPORT_PLAINTEXT_PWD); + private boolean supportPlaintextPwd() { + return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, supportPlainTextPwd); } - - private boolean supportCryptPwd() - { + private boolean supportCryptPwd() { return !supportPlaintextPwd(); } - - - - @Override - public String toString() - { - return getClass().getSimpleName() + "(" + ((htpasswdFile != null) ? htpasswdFile.getAbsolutePath() : "null") + ")"; - } - - - /* * Method only used for unit tests. Return number of users read from htpasswd file. */ - public int getNumberHtpasswdUsers() - { + public int getNumberHtpasswdUsers() { return this.htUsers.size(); } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + ((htpasswdFile != null) ? htpasswdFile.getAbsolutePath() : "null") + ")"; + } } -- Gitblit v1.9.1