diff options
author | James Moger <james.moger@gitblit.com> | 2013-11-24 23:18:50 -0500 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2013-11-29 11:05:51 -0500 |
commit | 04a98505a4ab8f48aee22800fcac193d9367d0ae (patch) | |
tree | eb05bc77eeafda1c5b7af9d7b5b27012065f7a98 /src/main/java/com/gitblit/HtpasswdUserService.java | |
parent | f8f6aa4d07cdfaaf23e24bf9eaf0a5fb9b437dda (diff) | |
download | gitblit-04a98505a4ab8f48aee22800fcac193d9367d0ae.tar.gz gitblit-04a98505a4ab8f48aee22800fcac193d9367d0ae.zip |
Refactor user services and separate authentication (issue-281)
Change-Id: I336e005e02623fc5e11a4f8b4408bea5465a43fd
Diffstat (limited to 'src/main/java/com/gitblit/HtpasswdUserService.java')
-rw-r--r-- | src/main/java/com/gitblit/HtpasswdUserService.java | 361 |
1 files changed, 0 insertions, 361 deletions
diff --git a/src/main/java/com/gitblit/HtpasswdUserService.java b/src/main/java/com/gitblit/HtpasswdUserService.java deleted file mode 100644 index ca5295c9..00000000 --- a/src/main/java/com/gitblit/HtpasswdUserService.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright 2013 Florian Zschocke - * Copyright 2013 gitblit.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.File; -import java.io.FileInputStream; -import java.text.MessageFormat; -import java.util.Map; -import java.util.Scanner; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.codec.binary.Base64; -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.AccountType; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.models.UserModel; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.StringUtils; - - -/** - * Implementation of a user service using an Apache htpasswd file for authentication. - * - * This user service implement custom authentication using entries in a file created - * by the 'htpasswd' program of an Apache web server. All possible output - * options of the 'htpasswd' program version 2.2 are supported: - * plain text (only on Windows and Netware), - * glibc crypt() (not on Windows and NetWare), - * Apache MD5 (apr1), - * unsalted SHA-1. - * - * Configuration options: - * realm.htpasswd.backingUserService - Specify the backing user service that is used - * to keep the user data other than the password. - * The default is '${baseFolder}/users.conf'. - * realm.htpasswd.userfile - The text file with the htpasswd entries to be used for - * authentication. - * The default is '${baseFolder}/htpasswd'. - * realm.htpasswd.overrideLocalAuthentication - Specify if local accounts are overwritten - * when authentication matches for an - * external account. - * - * @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"; - - 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 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; - } - } - - - - /** - * Setup the user service. - * - * The HtpasswdUserService extends the GitblitUserService and is thus - * backed by the available user services provided by the GitblitUserService. - * In addition the setup tries to read and parse the htpasswd file to be used - * for authentication. - * - * @param runtimeManager - * @since 1.4.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()); - - 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() - { - return false; - } - - - - /** - * Authenticate a user based on a username and password. - * - * If the account is determined to be a local account, authentication - * will be done against the locally stored password. - * Otherwise, the configured htpasswd file is read. All current output options - * of htpasswd are supported: clear text, crypt(), Apache MD5 and unsalted SHA-1. - * - * @param username - * @param password - * @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); - } - - - read(); - String storedPwd = htUsers.get(username); - if (storedPwd != null) { - boolean authenticated = false; - final String passwd = new String(password); - - // test Apache MD5 variant encrypted password - 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}") ) { - String passwd64 = Base64.encodeBase64String(DigestUtils.sha1(passwd)); - 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)) ) { - logger.debug("Libc crypt encoded password matched for user '" + username + "'"); - authenticated = true; - } - // test clear text - else if ( supportPlaintextPwd() && storedPwd.equals(passwd) ){ - logger.debug("Clear text password matched for user '" + username + "'"); - authenticated = true; - } - - - if (authenticated) { - logger.debug("Htpasswd authenticated: " + username); - - UserModel user = getUserModel(username); - if (user == null) { - // create user object for new authenticated user - user = new UserModel(username); - } - - // create a user cookie - if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { - user.cookie = StringUtils.getSHA1(user.username + passwd); - } - - // Set user attributes, hide password from backing user service. - user.password = Constants.EXTERNAL_ACCOUNT; - user.accountType = getAccountType(); - - // Push the looked up values to backing file - super.updateUserModel(user); - - return user; - } - } - - 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() - { - 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); - this.htUsers.clear(); - this.forceReload = true; - } - - if (htpasswdFile.exists() && (forceReload || (htpasswdFile.lastModified() != lastModified))) { - forceReload = false; - lastModified = htpasswdFile.lastModified(); - htUsers.clear(); - - Pattern entry = Pattern.compile("^([^:]+):(.+)"); - - Scanner scanner = null; - try { - scanner = new Scanner(new FileInputStream(htpasswdFile)); - while( scanner.hasNextLine()) { - String line = scanner.nextLine().trim(); - if ( !line.isEmpty() && !line.startsWith("#") ) { - Matcher m = entry.matcher(line); - 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(); - } - } - } - - - - private boolean supportPlaintextPwd() - { - return this.settings.getBoolean(KEY_SUPPORT_PLAINTEXT_PWD, SUPPORT_PLAINTEXT_PWD); - } - - - 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() - { - return this.htUsers.size(); - } -} |