summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/distrib/data/gitblit.properties38
-rw-r--r--src/main/java/com/gitblit/Constants.java2
-rw-r--r--src/main/java/com/gitblit/GitBlit.java84
-rw-r--r--src/main/java/com/gitblit/HtpasswdUserService.java356
-rw-r--r--src/main/java/com/gitblit/LdapUserService.java19
-rw-r--r--src/main/java/com/gitblit/authority/GitblitAuthority.java9
-rw-r--r--src/main/java/com/gitblit/utils/RefLogUtils.java34
-rw-r--r--src/main/java/com/gitblit/wicket/WicketUtils.java154
-rw-r--r--src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm4
-rw-r--r--src/main/java/com/gitblit/wicket/pages/BasePage.java4
-rw-r--r--src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java4
-rw-r--r--src/main/java/com/gitblit/wicket/pages/RawPage.java26
-rw-r--r--src/main/java/com/gitblit/wicket/pages/RootPage.java8
-rw-r--r--src/main/java/com/gitblit/wicket/panels/BranchesPanel.java51
-rw-r--r--src/site/design.mkd1
-rw-r--r--src/site/setup_authentication.mkd8
-rw-r--r--src/test/java/com/gitblit/tests/GitBlitSuite.java2
-rw-r--r--src/test/java/com/gitblit/tests/GitBlitTest.java8
-rw-r--r--src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java556
-rw-r--r--src/test/resources/htpasswdUSTest/htpasswd-user.in15
-rw-r--r--src/test/resources/htpasswdUSTest/htpasswd.in31
-rw-r--r--src/test/resources/htpasswdUSTest/users.conf.in26
22 files changed, 1294 insertions, 146 deletions
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
index 7f2f8b48..9be7f645 100644
--- a/src/main/distrib/data/gitblit.properties
+++ b/src/main/distrib/data/gitblit.properties
@@ -502,6 +502,7 @@ web.projectsFile = ${baseFolder}/projects.conf
# com.gitblit.SalesforceUserService
# com.gitblit.WindowsUserService
# com.gitblit.PAMUserService
+# com.gitblit.HtpasswdUserService
#
# Any custom user service implementation must have a public default constructor.
#
@@ -882,6 +883,11 @@ web.activityDuration = 7
# SINCE 1.3.0
web.activityDurationChoices = 1 3 7 14 21 28
+# Maximum number of days of activity that may be displayed on the activity page.
+#
+# SINCE 1.3.2
+web.activityDurationMaximum = 30
+
# The number of days of commits to cache in memory for the dashboard, activity,
# and project pages. A value of 0 will disable all caching and will parse commits
# in each repository per-request. If the value > 0 these pages will try to fulfill
@@ -1228,6 +1234,38 @@ realm.pam.backingUserService = ${baseFolder}/users.conf
# SINCE 1.3.1
realm.pam.serviceName = system-auth
+# The HtpasswdUserService must be backed by another user service for standard user
+# and team management and attributes. This can be one of the local Gitblit user services.
+# default: users.conf
+#
+# RESTART REQUIRED
+# BASEFOLDER
+# SINCE 1.3.2
+realm.htpasswd.backingUserService = ${baseFolder}/users.conf
+
+# The Apache htpasswd file that contains the users and passwords.
+# default: ${baseFolder}/htpasswd
+#
+# RESTART REQUIRED
+# BASEFOLDER
+# SINCE 1.3.2
+realm.htpasswd.userfile = ${baseFolder}/htpasswd
+
+# Determines how accounts are looked up upon login.
+#
+# If set to false, then authentication for local accounts is done against
+# the backing user service.
+# If set to true, then authentication will first be checked against the
+# htpasswd store, even if the account appears as a local account in the
+# backing user service. If the user is found in the htpasswd store, then
+# an already existing local account will be turned into an external account.
+# In this case an initial local password is never used and gets overwritten
+# by the externally stored password upon login.
+# default: false
+#
+# SINCE 1.3.2
+realm.htpasswd.overrideLocalAuthentication = false
+
# The SalesforceUserService must be backed by another user service for standard user
# and team management.
# default: users.conf
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java
index c180bafb..2c67bfff 100644
--- a/src/main/java/com/gitblit/Constants.java
+++ b/src/main/java/com/gitblit/Constants.java
@@ -480,7 +480,7 @@ public class Constants {
}
public static enum AccountType {
- LOCAL, EXTERNAL, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM;
+ LOCAL, EXTERNAL, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD;
public boolean isLocal() {
return this == LOCAL;
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index 5512296f..6ea348d5 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -1497,22 +1497,13 @@ public class GitBlit implements ServletContextListener {
} else {
// we are caching this list
String msg = "{0} repositories identified in {1} msecs";
-
- // optionally (re)calculate repository sizes
if (getBoolean(Keys.web.showRepositorySizes, true)) {
- ByteFormat byteFormat = new ByteFormat();
+ // optionally (re)calculate repository sizes
msg = "{0} repositories identified with calculated folder sizes in {1} msecs";
- for (String repository : repositories) {
- RepositoryModel model = getRepositoryModel(repository);
- if (!model.skipSizeCalculation) {
- model.size = byteFormat.format(calculateSize(model));
- }
- }
- } else {
- // update cache
- for (String repository : repositories) {
- getRepositoryModel(repository);
- }
+ }
+
+ for (String repository : repositories) {
+ getRepositoryModel(repository);
}
// rebuild fork networks
@@ -1607,23 +1598,6 @@ public class GitBlit implements ServletContextListener {
}
}
}
- if (getBoolean(Keys.web.showRepositorySizes, true)) {
- int repoCount = 0;
- long startTime = System.currentTimeMillis();
- ByteFormat byteFormat = new ByteFormat();
- for (RepositoryModel model : repositories) {
- if (!model.skipSizeCalculation) {
- repoCount++;
- model.size = byteFormat.format(calculateSize(model));
- }
- }
- long duration = System.currentTimeMillis() - startTime;
- if (duration > 250) {
- // only log calcualtion time if > 250 msecs
- logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs",
- repoCount, duration));
- }
- }
long duration = System.currentTimeMillis() - methodStart;
logger.info(MessageFormat.format("{0} repository models loaded for {1} in {2} msecs",
repositories.size(), user == null ? "anonymous" : user.username, duration));
@@ -1670,7 +1644,7 @@ public class GitBlit implements ServletContextListener {
return null;
}
addToCachedRepositoryList(model);
- return model;
+ return DeepCopier.copy(model);
}
// cached model
@@ -1706,13 +1680,7 @@ public class GitBlit implements ServletContextListener {
model.hasCommits = JGitUtils.hasCommits(r);
}
- LastChange lc = JGitUtils.getLastChange(r);
- model.lastChange = lc.when;
- model.lastChangeAuthor = lc.who;
- if (!model.skipSizeCalculation) {
- ByteFormat byteFormat = new ByteFormat();
- model.size = byteFormat.format(calculateSize(model));
- }
+ updateLastChangeFields(r, model);
}
r.close();
@@ -2011,10 +1979,6 @@ public class GitBlit implements ServletContextListener {
// is symlinked. Use the provided repository name.
model.name = repositoryName;
}
- model.hasCommits = JGitUtils.hasCommits(r);
- LastChange lc = JGitUtils.getLastChange(r);
- model.lastChange = lc.when;
- model.lastChangeAuthor = lc.who;
model.projectPath = StringUtils.getFirstPathElement(repositoryName);
StoredConfig config = r.getConfig();
@@ -2084,6 +2048,8 @@ public class GitBlit implements ServletContextListener {
model.HEAD = JGitUtils.getHEADRef(r);
model.availableRefs = JGitUtils.getAvailableHeadTargets(r);
model.sparkleshareId = JGitUtils.getSparkleshareId(r);
+ model.hasCommits = JGitUtils.hasCommits(r);
+ updateLastChangeFields(r, model);
r.close();
if (StringUtils.isEmpty(model.originRepository) && model.origin != null && model.origin.startsWith("file://")) {
@@ -2279,21 +2245,31 @@ public class GitBlit implements ServletContextListener {
}
/**
- * Returns the size in bytes of the repository. Gitblit caches the
- * repository sizes to reduce the performance penalty of recursive
- * calculation. The cache is updated if the repository has been changed
- * since the last calculation.
+ * Updates the last changed fields and optionally calculates the size of the
+ * repository. Gitblit caches the repository sizes to reduce the performance
+ * penalty of recursive calculation. The cache is updated if the repository
+ * has been changed since the last calculation.
*
* @param model
- * @return size in bytes
+ * @return size in bytes of the repository
*/
- public long calculateSize(RepositoryModel model) {
- if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
- return repositorySizeCache.getObject(model.name);
+ public long updateLastChangeFields(Repository r, RepositoryModel model) {
+ LastChange lc = JGitUtils.getLastChange(r);
+ model.lastChange = lc.when;
+ model.lastChangeAuthor = lc.who;
+
+ if (!getBoolean(Keys.web.showRepositorySizes, true) || model.skipSizeCalculation) {
+ model.size = null;
+ return 0L;
+ }
+ if (!repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
+ File gitDir = r.getDirectory();
+ long sz = com.gitblit.utils.FileUtils.folderSize(gitDir);
+ repositorySizeCache.updateObject(model.name, model.lastChange, sz);
}
- File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED);
- long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
- repositorySizeCache.updateObject(model.name, model.lastChange, size);
+ long size = repositorySizeCache.getObject(model.name);
+ ByteFormat byteFormat = new ByteFormat();
+ model.size = byteFormat.format(size);
return size;
}
diff --git a/src/main/java/com/gitblit/HtpasswdUserService.java b/src/main/java/com/gitblit/HtpasswdUserService.java
new file mode 100644
index 00000000..62198f4a
--- /dev/null
+++ b/src/main/java/com/gitblit/HtpasswdUserService.java
@@ -0,0 +1,356 @@
+/*
+ * 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.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 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 settings
+ * @since 0.7.0
+ */
+ @Override
+ public void setup(IStoredSettings settings)
+ {
+ this.settings = settings;
+
+ // 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 = GitBlit.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.
+ */
+ 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
+ */
+ protected 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 = GitBlit.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();
+ }
+}
diff --git a/src/main/java/com/gitblit/LdapUserService.java b/src/main/java/com/gitblit/LdapUserService.java
index 39d564dd..b65f9bb7 100644
--- a/src/main/java/com/gitblit/LdapUserService.java
+++ b/src/main/java/com/gitblit/LdapUserService.java
@@ -170,13 +170,24 @@ public class LdapUserService extends GitblitUserService {
if (ldapPort == -1) // Default Port
ldapPort = 636;
- SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
- return new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
+ LDAPConnection conn;
+ SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
+ if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) {
+ conn = new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort);
+ } else {
+ conn = new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
+ }
+ return conn;
} else {
if (ldapPort == -1) // Default Port
ldapPort = 389;
-
- LDAPConnection conn = new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
+
+ LDAPConnection conn;
+ if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) {
+ conn = new LDAPConnection(ldapUrl.getHost(), ldapPort);
+ } else {
+ conn = new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
+ }
if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
diff --git a/src/main/java/com/gitblit/authority/GitblitAuthority.java b/src/main/java/com/gitblit/authority/GitblitAuthority.java
index e0b079ea..bddb1cfc 100644
--- a/src/main/java/com/gitblit/authority/GitblitAuthority.java
+++ b/src/main/java/com/gitblit/authority/GitblitAuthority.java
@@ -261,12 +261,9 @@ public class GitblitAuthority extends JFrame implements X509Log {
String us = gitblitSettings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();
IUserService service = null;
- if (!ext.equals("conf") && !ext.equals("properties")) {
- if (us.equals("com.gitblit.LdapUserService")) {
- us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
- } else if (us.equals("com.gitblit.LdapUserService")) {
- us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
- }
+ if (!ext.equals("conf") && !ext.equals("properties") && ext.contains("userservice")) {
+ String realm = ext.substring(0, ext.indexOf("userservice"));
+ us = gitblitSettings.getString(MessageFormat.format("realm.{0}.backingUserService", realm), "${baseFolder}/users.conf");
}
if (us.endsWith(".conf")) {
diff --git a/src/main/java/com/gitblit/utils/RefLogUtils.java b/src/main/java/com/gitblit/utils/RefLogUtils.java
index dfb5f56d..df09a76f 100644
--- a/src/main/java/com/gitblit/utils/RefLogUtils.java
+++ b/src/main/java/com/gitblit/utils/RefLogUtils.java
@@ -180,22 +180,22 @@ public class RefLogUtils {
* @param ref
* @return true, if the update was successful
*/
- public static boolean deleteRef(UserModel user, Repository repository, String ref) {
+ public static boolean deleteRef(UserModel user, Repository repository, Ref ref) {
try {
- Ref refObj = repository.getRef(ref);
- if (refObj == null && !ref.startsWith(Constants.R_HEADS) && ref.startsWith(Constants.R_TAGS)) {
- // find fully qualified ref
- refObj = repository.getRef(Constants.R_HEADS + ref);
- if (refObj == null) {
- refObj = repository.getRef(Constants.R_TAGS + ref);
- }
+ if (ref == null) {
+ return false;
}
-
- if (refObj == null) {
+ RefModel reflogBranch = getRefLogBranch(repository);
+ if (reflogBranch == null) {
return false;
}
- ReceiveCommand cmd = new ReceiveCommand(refObj.getObjectId(), ObjectId.zeroId(), refObj.getName());
+ List<RevCommit> log = JGitUtils.getRevLog(repository, reflogBranch.getName(), ref.getName(), 0, 1);
+ if (log.isEmpty()) {
+ // this ref is not in the reflog branch
+ return false;
+ }
+ ReceiveCommand cmd = new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName());
return updateRefLog(user, repository, Arrays.asList(cmd));
} catch (Throwable t) {
error(t, repository, "Failed to commit reflog entry to {0}");
@@ -442,9 +442,13 @@ public class RefLogUtils {
UserModel user = newUserModelFrom(push.getAuthorIdent());
Date date = push.getAuthorIdent().getWhen();
- RefLogEntry log = new RefLogEntry(repositoryName, date, user);
- list.add(log);
+ RefLogEntry log = new RefLogEntry(repositoryName, date, user);
List<PathChangeModel> changedRefs = JGitUtils.getFilesInCommit(repository, push);
+ if (changedRefs.isEmpty()) {
+ // skip empty commits
+ continue;
+ }
+ list.add(log);
for (PathChangeModel change : changedRefs) {
switch (change.changeType) {
case DELETE:
@@ -458,6 +462,10 @@ public class RefLogUtils {
String oldId = fields[1];
String newId = fields[2];
log.updateRef(change.path, ReceiveCommand.Type.valueOf(fields[0]), oldId, newId);
+ if (ObjectId.zeroId().getName().equals(newId)) {
+ // ref deletion
+ continue;
+ }
List<RevCommit> pushedCommits = JGitUtils.getRevLog(repository, oldId, newId);
for (RevCommit pushedCommit : pushedCommits) {
RepositoryCommit repoCommit = log.addCommit(change.path, pushedCommit);
diff --git a/src/main/java/com/gitblit/wicket/WicketUtils.java b/src/main/java/com/gitblit/wicket/WicketUtils.java
index 87f2f3ff..aa86686c 100644
--- a/src/main/java/com/gitblit/wicket/WicketUtils.java
+++ b/src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -20,7 +20,9 @@ import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
@@ -190,7 +192,7 @@ public class WicketUtils {
return newImage(wicketId, "file_excel_16x16.png");
} else if (filename.endsWith(".doc") || filename.endsWith(".docx")) {
return newImage(wicketId, "file_doc_16x16.png");
- } else if (filename.endsWith(".ppt")) {
+ } else if (filename.endsWith(".ppt") || filename.endsWith(".pptx")) {
return newImage(wicketId, "file_ppt_16x16.png");
} else if (filename.endsWith(".zip")) {
return newImage(wicketId, "file_zip_16x16.png");
@@ -285,104 +287,170 @@ public class WicketUtils {
}
public static PageParameters newTokenParameter(String token) {
- return new PageParameters("t=" + token);
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ parameterMap.put("t", token);
+ return new PageParameters(parameterMap);
}
- public static PageParameters newRegistrationParameter(String url, String name) {
- return new PageParameters("u=" + url + ",n=" + name);
+ public static PageParameters newRegistrationParameter(String url,
+ String name) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ parameterMap.put("u", url);
+ parameterMap.put("n", name);
+ return new PageParameters(parameterMap);
}
public static PageParameters newUsernameParameter(String username) {
- return new PageParameters("user=" + username);
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ parameterMap.put("user", username);
+ return new PageParameters(parameterMap);
}
public static PageParameters newTeamnameParameter(String teamname) {
- return new PageParameters("team=" + teamname);
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ parameterMap.put("team", teamname);
+ return new PageParameters(parameterMap);
}
public static PageParameters newProjectParameter(String projectName) {
- return new PageParameters("p=" + projectName);
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ parameterMap.put("p", projectName);
+ return new PageParameters(parameterMap);
}
public static PageParameters newRepositoryParameter(String repositoryName) {
- return new PageParameters("r=" + repositoryName);
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ parameterMap.put("r", repositoryName);
+ return new PageParameters(parameterMap);
}
public static PageParameters newObjectParameter(String objectId) {
- return new PageParameters("h=" + objectId);
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ parameterMap.put("h", objectId);
+ return new PageParameters(parameterMap);
}
- public static PageParameters newObjectParameter(String repositoryName, String objectId) {
+ public static PageParameters newObjectParameter(String repositoryName,
+ String objectId) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
if (StringUtils.isEmpty(objectId)) {
return newRepositoryParameter(repositoryName);
}
- return new PageParameters("r=" + repositoryName + ",h=" + objectId);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", objectId);
+ return new PageParameters(parameterMap);
}
- public static PageParameters newRangeParameter(String repositoryName, String startRange, String endRange) {
- return new PageParameters("r=" + repositoryName + ",h=" + startRange + ".." + endRange);
+ public static PageParameters newRangeParameter(String repositoryName,
+ String startRange, String endRange) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", startRange + ".." + endRange);
+ return new PageParameters(parameterMap);
}
- public static PageParameters newPathParameter(String repositoryName, String objectId,
- String path) {
+ public static PageParameters newPathParameter(String repositoryName,
+ String objectId, String path) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
if (StringUtils.isEmpty(path)) {
return newObjectParameter(repositoryName, objectId);
}
if (StringUtils.isEmpty(objectId)) {
- return new PageParameters("r=" + repositoryName + ",f=" + path);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("f", path);
+ return new PageParameters(parameterMap);
}
- return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", objectId);
+ parameterMap.put("f", path);
+ return new PageParameters(parameterMap);
}
- public static PageParameters newLogPageParameter(String repositoryName, String objectId,
- int pageNumber) {
+ public static PageParameters newLogPageParameter(String repositoryName,
+ String objectId, int pageNumber) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
if (pageNumber <= 1) {
return newObjectParameter(repositoryName, objectId);
}
if (StringUtils.isEmpty(objectId)) {
- return new PageParameters("r=" + repositoryName + ",pg=" + pageNumber);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("pg", String.valueOf(pageNumber));
+ return new PageParameters(parameterMap);
}
- return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",pg=" + pageNumber);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", objectId);
+ parameterMap.put("pg", String.valueOf(pageNumber));
+ return new PageParameters(parameterMap);
}
- public static PageParameters newHistoryPageParameter(String repositoryName, String objectId,
- String path, int pageNumber) {
+ public static PageParameters newHistoryPageParameter(String repositoryName,
+ String objectId, String path, int pageNumber) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
if (pageNumber <= 1) {
return newObjectParameter(repositoryName, objectId);
}
if (StringUtils.isEmpty(objectId)) {
- return new PageParameters("r=" + repositoryName + ",f=" + path + ",pg=" + pageNumber);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("f", path);
+ parameterMap.put("pg", String.valueOf(pageNumber));
+ return new PageParameters(parameterMap);
}
- return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path + ",pg="
- + pageNumber);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", objectId);
+ parameterMap.put("f", path);
+ parameterMap.put("pg", String.valueOf(pageNumber));
+ return new PageParameters(parameterMap);
}
- public static PageParameters newBlobDiffParameter(String repositoryName, String baseCommitId,
- String commitId, String path) {
+ public static PageParameters newBlobDiffParameter(String repositoryName,
+ String baseCommitId, String commitId, String path) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
if (StringUtils.isEmpty(commitId)) {
- return new PageParameters("r=" + repositoryName + ",f=" + path + ",hb=" + baseCommitId);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("f", path);
+ parameterMap.put("hb", baseCommitId);
+ return new PageParameters(parameterMap);
}
- return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",f=" + path + ",hb="
- + baseCommitId);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", commitId);
+ parameterMap.put("f", path);
+ parameterMap.put("hb", baseCommitId);
+ return new PageParameters(parameterMap);
}
- public static PageParameters newSearchParameter(String repositoryName, String commitId,
- String search, Constants.SearchType type) {
+ public static PageParameters newSearchParameter(String repositoryName,
+ String commitId, String search, Constants.SearchType type) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
if (StringUtils.isEmpty(commitId)) {
- return new PageParameters("r=" + repositoryName + ",s=" + search + ",st=" + type.name());
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("s", search);
+ parameterMap.put("st", type.name());
+ return new PageParameters(parameterMap);
}
- return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",s=" + search
- + ",st=" + type.name());
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", commitId);
+ parameterMap.put("s", search);
+ parameterMap.put("st", type.name());
+ return new PageParameters(parameterMap);
}
- public static PageParameters newSearchParameter(String repositoryName, String commitId,
- String search, Constants.SearchType type, int pageNumber) {
+ public static PageParameters newSearchParameter(String repositoryName,
+ String commitId, String search, Constants.SearchType type,
+ int pageNumber) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
if (StringUtils.isEmpty(commitId)) {
- return new PageParameters("r=" + repositoryName + ",s=" + search + ",st=" + type.name()
- + ",pg=" + pageNumber);
- }
- return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",s=" + search
- + ",st=" + type.name() + ",pg=" + pageNumber);
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("s", search);
+ parameterMap.put("st", type.name());
+ parameterMap.put("pg", String.valueOf(pageNumber));
+ return new PageParameters(parameterMap);
+ }
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", commitId);
+ parameterMap.put("s", search);
+ parameterMap.put("st", type.name());
+ parameterMap.put("pg", String.valueOf(pageNumber));
+ return new PageParameters(parameterMap);
}
public static String getProjectName(PageParameters params) {
diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm
index 691f089b..cfafdce3 100644
--- a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm
+++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm
@@ -6,10 +6,10 @@
</div>
<div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">
- <a href="project/{{item.p}}" title="{{item.i}}"><b>{{item.n}}</b></a>
+ <a href="project/?p={{item.p}}" title="{{item.i}}"><b>{{item.n}}</b></a>
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
<span class="pull-right">
<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;" wicket:message="title:gb.repositories">{{item.c | number}}</span>
</span>
</div>
-</div> \ No newline at end of file
+</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.java b/src/main/java/com/gitblit/wicket/pages/BasePage.java
index c9e11b08..2bab1183 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -325,6 +325,7 @@ public abstract class BasePage extends SessionPage {
String regex = WicketUtils.getRegEx(params);
String team = WicketUtils.getTeam(params);
int daysBack = params.getInt("db", 0);
+ int maxDaysBack = GitBlit.getInteger(Keys.web.activityDurationMaximum, 30);
List<ProjectModel> availableModels = getProjectModels();
Set<ProjectModel> models = new HashSet<ProjectModel>();
@@ -372,6 +373,9 @@ public abstract class BasePage extends SessionPage {
// time-filter the list
if (daysBack > 0) {
+ if (maxDaysBack > 0 && daysBack > maxDaysBack) {
+ daysBack = maxDaysBack;
+ }
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
diff --git a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
index 32c128da..0768b2ac 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
@@ -96,9 +96,13 @@ public class MyDashboardPage extends DashboardPage {
// parameters
int daysBack = params == null ? 0 : WicketUtils.getDaysBack(params);
+ int maxDaysBack = GitBlit.getInteger(Keys.web.activityDurationMaximum, 30);
if (daysBack < 1) {
daysBack = GitBlit.getInteger(Keys.web.activityDuration, 7);
}
+ if (maxDaysBack > 0 && daysBack > maxDaysBack) {
+ daysBack = maxDaysBack;
+ }
Calendar c = Calendar.getInstance();
c.add(Calendar.DATE, -1*daysBack);
Date minimumDate = c.getTime();
diff --git a/src/main/java/com/gitblit/wicket/pages/RawPage.java b/src/main/java/com/gitblit/wicket/pages/RawPage.java
index 27a01f96..d322e955 100644
--- a/src/main/java/com/gitblit/wicket/pages/RawPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RawPage.java
@@ -16,12 +16,15 @@
package com.gitblit.wicket.pages;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RequestCycle;
+import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.protocol.http.WebResponse;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -134,8 +137,27 @@ public class RawPage extends SessionPage {
// binary blobs (download)
byte[] binary = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
response.setContentLength(binary.length);
- response.setContentType("application/octet-stream");
- response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
+ response.setContentType("application/octet-stream; charset=UTF-8");
+
+ try {
+ WebRequest request = (WebRequest) requestCycle.getRequest();
+ String userAgent = request.getHttpServletRequest().getHeader("User-Agent");
+
+ if (userAgent != null && userAgent.indexOf("MSIE 5.5") > -1) {
+ response.setHeader("Content-Disposition", "filename=\""
+ + URLEncoder.encode(filename, "UTF-8") + "\"");
+ } else if (userAgent != null && userAgent.indexOf("MSIE") > -1) {
+ response.setHeader("Content-Disposition", "attachment; filename=\""
+ + URLEncoder.encode(filename, "UTF-8") + "\"");
+ } else {
+ response.setHeader("Content-Disposition", "attachment; filename=\""
+ + new String(filename.getBytes("UTF-8"), "latin1") + "\"");
+ }
+ }
+ catch (UnsupportedEncodingException e) {
+ response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
+ }
+
try {
response.getOutputStream().write(binary);
} catch (IOException e) {
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java
index 7739e6df..a81d63f4 100644
--- a/src/main/java/com/gitblit/wicket/pages/RootPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java
@@ -345,9 +345,13 @@ public abstract class RootPage extends BasePage {
protected List<DropDownMenuItem> getTimeFilterItems(PageParameters params) {
// days back choices - additive parameters
int daysBack = GitBlit.getInteger(Keys.web.activityDuration, 7);
+ int maxDaysBack = GitBlit.getInteger(Keys.web.activityDurationMaximum, 30);
if (daysBack < 1) {
daysBack = 7;
}
+ if (daysBack > maxDaysBack) {
+ daysBack = maxDaysBack;
+ }
PageParameters clonedParams;
if (params == null) {
clonedParams = new PageParameters();
@@ -397,6 +401,7 @@ public abstract class RootPage extends BasePage {
String regex = WicketUtils.getRegEx(params);
String team = WicketUtils.getTeam(params);
int daysBack = params.getInt("db", 0);
+ int maxDaysBack = GitBlit.getInteger(Keys.web.activityDurationMaximum, 30);
List<RepositoryModel> availableModels = getRepositoryModels();
Set<RepositoryModel> models = new HashSet<RepositoryModel>();
@@ -487,6 +492,9 @@ public abstract class RootPage extends BasePage {
// time-filter the list
if (daysBack > 0) {
+ if (maxDaysBack > 0 && daysBack > maxDaysBack) {
+ daysBack = maxDaysBack;
+ }
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
diff --git a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
index 7aa185bd..dba40897 100644
--- a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
@@ -15,11 +15,13 @@
*/
package com.gitblit.wicket.panels;
+import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
@@ -29,6 +31,9 @@ import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.protocol.http.RequestUtils;
+import org.apache.wicket.request.target.basic.RedirectRequestTarget;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import com.gitblit.Constants;
@@ -194,28 +199,40 @@ public class BranchesPanel extends BasePanel {
return;
}
final String branch = entry.getName();
- boolean success = JGitUtils.deleteBranchRef(r, branch);
- if (success) {
- // clear commit cache
- CommitCache.instance().clear(repositoryModel.name, branch);
+ Ref ref = null;
+ try {
+ ref = r.getRef(branch);
+ if (ref == null && !branch.startsWith(Constants.R_HEADS)) {
+ ref = r.getRef(Constants.R_HEADS + branch);
+ }
+ } catch (IOException e) {
+ }
+ if (ref != null) {
+ boolean success = JGitUtils.deleteBranchRef(r, ref.getName());
+ if (success) {
+ // clear commit cache
+ CommitCache.instance().clear(repositoryModel.name, branch);
+
+ // optionally update reflog
+ if (RefLogUtils.hasRefLogBranch(r)) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ RefLogUtils.deleteRef(user, r, ref);
+ }
+ }
- // optionally update reflog
- if (RefLogUtils.hasRefLogBranch(r)) {
- UserModel user = GitBlitWebSession.get().getUser();
- success = RefLogUtils.deleteRef(user, r, branch);
+ if (success) {
+ info(MessageFormat.format("Branch \"{0}\" deleted", branch));
+ } else {
+ error(MessageFormat.format("Failed to delete branch \"{0}\"", branch));
}
}
-
r.close();
- if (success) {
- info(MessageFormat.format("Branch \"{0}\" deleted", branch));
- // redirect to the owning page
- setResponsePage(getPage().getClass(), WicketUtils.newRepositoryParameter(repositoryModel.name));
- }
- else {
- error(MessageFormat.format("Failed to delete branch \"{0}\"", branch));
- }
+ // redirect to the owning page
+ PageParameters params = WicketUtils.newRepositoryParameter(repositoryModel.name);
+ String relativeUrl = urlFor(getPage().getClass(), params).toString();
+ String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
+ getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
}
};
diff --git a/src/site/design.mkd b/src/site/design.mkd
index 601d68ab..ce676201 100644
--- a/src/site/design.mkd
+++ b/src/site/design.mkd
@@ -52,6 +52,7 @@ The following dependencies are automatically downloaded by Gitblit GO (or alread
- [JNA](https://github.com/twall/jna) (LGPL 2.1)
- [Guava](https://code.google.com/p/guava-libraries) (Apache 2.0)
- [libpam4j](https://github.com/kohsuke/libpam4j) (MIT)
+- [commons-codec](http://commons.apache.org/proper/commons-codec) (Apache 2.0)
### Other Build Dependencies
- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)
diff --git a/src/site/setup_authentication.mkd b/src/site/setup_authentication.mkd
index 0ec07fa5..3fb4a6c1 100644
--- a/src/site/setup_authentication.mkd
+++ b/src/site/setup_authentication.mkd
@@ -7,6 +7,7 @@ Gitblit supports additional authentication mechanisms aside from it's internal o
* LDAP authentication
* Windows authentication
* PAM authentication
+* Htpasswd authentication
* Redmine auhentication
* Salesforce.com authentication
* Servlet container authentication
@@ -91,6 +92,13 @@ PAM authentication is based on the use of libpam4j and JNA. To use this service
realm.userService = com.gitblit.PAMUserService
realm.pam.serviceName = system-auth
+### Htpasswd Authentication
+
+Htpasswd authentication allows you to maintain your user credentials in an Apache htpasswd file thay may be shared with other htpasswd-capable servers.
+
+ realm.userService = com.gitblit.HtpasswdUserService
+ realm.htpasswd.userFile = /path/to/htpasswd
+
### Redmine Authentication
You may authenticate your users against a Redmine installation as long as your Redmine install has properly enabled [API authentication](http://www.redmine.org/projects/redmine/wiki/Rest_Api#Authentication). This user service only supports user authentication; it does not support team creation based on Redmine groups. Redmine administrators will also be Gitblit administrators.
diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java
index 6fff241c..64398b22 100644
--- a/src/test/java/com/gitblit/tests/GitBlitSuite.java
+++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -60,7 +60,7 @@ import com.gitblit.utils.JGitUtils;
DiffUtilsTest.class, MetricUtilsTest.class, TicgitUtilsTest.class, X509UtilsTest.class,
GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class,
GroovyScriptTest.class, LuceneExecutorTest.class, IssuesTest.class, RepositoryModelTest.class,
- FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class })
+ FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdUserServiceTest.class })
public class GitBlitSuite {
public static final File REPOSITORIES = new File("data/git");
diff --git a/src/test/java/com/gitblit/tests/GitBlitTest.java b/src/test/java/com/gitblit/tests/GitBlitTest.java
index ab23f4eb..c7ec939f 100644
--- a/src/test/java/com/gitblit/tests/GitBlitTest.java
+++ b/src/test/java/com/gitblit/tests/GitBlitTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertTrue;
import java.util.List;
+import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
import com.gitblit.Constants.AccessRestrictionType;
@@ -40,11 +41,12 @@ public class GitBlitTest {
"Missing Helloworld repository!",
repositories.contains(GitBlitSuite.getHelloworldRepository().getDirectory()
.getName()));
- RepositoryModel model = GitBlit.self().getRepositoryModel(
- GitBlitSuite.getHelloworldRepository().getDirectory().getName());
+ Repository r = GitBlitSuite.getHelloworldRepository();
+ RepositoryModel model = GitBlit.self().getRepositoryModel(r.getDirectory().getName());
assertTrue("Helloworld model is null!", model != null);
assertEquals(GitBlitSuite.getHelloworldRepository().getDirectory().getName(), model.name);
- assertTrue(GitBlit.self().calculateSize(model) > 22000L);
+ assertTrue(GitBlit.self().updateLastChangeFields(r, model) > 22000L);
+ r.close();
}
@Test
diff --git a/src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java b/src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java
new file mode 100644
index 00000000..ef9d35ff
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java
@@ -0,0 +1,556 @@
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.HashMap;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.gitblit.HtpasswdUserService;
+import com.gitblit.models.UserModel;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Test the Htpasswd user service.
+ *
+ */
+public class HtpasswdUserServiceTest {
+
+ private static final String RESOURCE_DIR = "src/test/resources/htpasswdUSTest/";
+ private static final String KEY_SUPPORT_PLAINTEXT_PWD = "realm.htpasswd.supportPlaintextPasswords";
+
+ private static final int NUM_USERS_HTPASSWD = 10;
+
+ private static final MemorySettings MS = new MemorySettings(new HashMap<String, Object>());
+
+ private HtpasswdUserService htpwdUserService;
+
+
+ private MemorySettings getSettings( String userfile, String groupfile, Boolean overrideLA)
+ {
+ MS.put("realm.htpasswd.backingUserService", RESOURCE_DIR + "users.conf");
+ MS.put("realm.htpasswd.userfile", (userfile == null) ? (RESOURCE_DIR+"htpasswd") : userfile);
+ MS.put("realm.htpasswd.groupfile", (groupfile == null) ? (RESOURCE_DIR+"htgroup") : groupfile);
+ MS.put("realm.htpasswd.overrideLocalAuthentication", (overrideLA == null) ? "false" : overrideLA.toString());
+ // Default to keep test the same on all platforms.
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
+
+ return MS;
+ }
+
+ private MemorySettings getSettings()
+ {
+ return getSettings(null, null, null);
+ }
+
+ private MemorySettings getSettings(boolean overrideLA)
+ {
+ return getSettings(null, null, new Boolean(overrideLA));
+ }
+
+
+ private void setupUS()
+ {
+ htpwdUserService = new HtpasswdUserService();
+ htpwdUserService.setup(getSettings());
+ }
+
+ private void setupUS(boolean overrideLA)
+ {
+ htpwdUserService = new HtpasswdUserService();
+ htpwdUserService.setup(getSettings(overrideLA));
+ }
+
+
+ private void copyInFiles() throws IOException
+ {
+ File dir = new File(RESOURCE_DIR);
+ FilenameFilter filter = new FilenameFilter() {
+ public boolean accept(File dir, String file) {
+ return file.endsWith(".in");
+ }
+ };
+ for (File inf : dir.listFiles(filter)) {
+ File dest = new File(inf.getParent(), inf.getName().substring(0, inf.getName().length()-3));
+ FileUtils.copyFile(inf, dest);
+ }
+ }
+
+
+ private void deleteGeneratedFiles()
+ {
+ File dir = new File(RESOURCE_DIR);
+ FilenameFilter filter = new FilenameFilter() {
+ public boolean accept(File dir, String file) {
+ return !(file.endsWith(".in"));
+ }
+ };
+ for (File file : dir.listFiles(filter)) {
+ file.delete();
+ }
+ }
+
+
+ @Before
+ public void setup() throws IOException
+ {
+ copyInFiles();
+ setupUS();
+ }
+
+
+ @After
+ public void tearDown()
+ {
+ deleteGeneratedFiles();
+ }
+
+
+
+ @Test
+ public void testSetup() throws IOException
+ {
+ assertEquals(NUM_USERS_HTPASSWD, htpwdUserService.getNumberHtpasswdUsers());
+ }
+
+
+ @Test
+ public void testAuthenticate()
+ {
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
+ UserModel user = htpwdUserService.authenticate("user1", "pass1".toCharArray());
+ assertNotNull(user);
+ assertEquals("user1", user.username);
+
+ user = htpwdUserService.authenticate("user2", "pass2".toCharArray());
+ assertNotNull(user);
+ assertEquals("user2", user.username);
+
+ // Test different encryptions
+ user = htpwdUserService.authenticate("plain", "passWord".toCharArray());
+ assertNotNull(user);
+ assertEquals("plain", user.username);
+
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
+ user = htpwdUserService.authenticate("crypt", "password".toCharArray());
+ assertNotNull(user);
+ assertEquals("crypt", user.username);
+
+ user = htpwdUserService.authenticate("md5", "password".toCharArray());
+ assertNotNull(user);
+ assertEquals("md5", user.username);
+
+ user = htpwdUserService.authenticate("sha", "password".toCharArray());
+ assertNotNull(user);
+ assertEquals("sha", user.username);
+
+
+ // Test leading and trailing whitespace
+ user = htpwdUserService.authenticate("trailing", "whitespace".toCharArray());
+ assertNotNull(user);
+ assertEquals("trailing", user.username);
+
+ user = htpwdUserService.authenticate("tabbed", "frontAndBack".toCharArray());
+ assertNotNull(user);
+ assertEquals("tabbed", user.username);
+
+ user = htpwdUserService.authenticate("leading", "whitespace".toCharArray());
+ assertNotNull(user);
+ assertEquals("leading", user.username);
+
+
+ // Test local account
+ user = htpwdUserService.authenticate("admin", "admin".toCharArray());
+ assertNotNull(user);
+ assertEquals("admin", user.username);
+ }
+
+
+ @Test
+ public void testAttributes()
+ {
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
+ UserModel user = htpwdUserService.authenticate("user1", "pass1".toCharArray());
+ assertNotNull(user);
+ assertEquals("El Capitan", user.displayName);
+ assertEquals("cheffe@example.com", user.emailAddress);
+ assertTrue(user.canAdmin);
+
+ user = htpwdUserService.authenticate("user2", "pass2".toCharArray());
+ assertNotNull(user);
+ assertEquals("User Two", user.displayName);
+ assertTrue(user.canCreate);
+ assertTrue(user.canFork);
+
+
+ user = htpwdUserService.authenticate("admin", "admin".toCharArray());
+ assertNotNull(user);
+ assertTrue(user.canAdmin);
+
+ user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray());
+ assertNotNull(user);
+ assertEquals("Local User", user.displayName);
+ assertFalse(user.canCreate);
+ assertFalse(user.canFork);
+ assertFalse(user.canAdmin);
+ }
+
+
+ @Test
+ public void testAuthenticateDenied()
+ {
+ UserModel user = null;
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
+ user = htpwdUserService.authenticate("user1", "".toCharArray());
+ assertNull("User 'user1' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("user1", "pass2".toCharArray());
+ assertNull("User 'user1' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("user2", "lalala".toCharArray());
+ assertNull("User 'user2' falsely authenticated.", user);
+
+
+ user = htpwdUserService.authenticate("user3", "disabled".toCharArray());
+ assertNull("User 'user3' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("user4", "disabled".toCharArray());
+ assertNull("User 'user4' falsely authenticated.", user);
+
+
+ user = htpwdUserService.authenticate("plain", "text".toCharArray());
+ assertNull("User 'plain' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("plain", "password".toCharArray());
+ assertNull("User 'plain' falsely authenticated.", user);
+
+
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
+
+ user = htpwdUserService.authenticate("crypt", "".toCharArray());
+ assertNull("User 'cyrpt' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("crypt", "passwd".toCharArray());
+ assertNull("User 'crypt' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("md5", "".toCharArray());
+ assertNull("User 'md5' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("md5", "pwd".toCharArray());
+ assertNull("User 'md5' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("sha", "".toCharArray());
+ assertNull("User 'sha' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate("sha", "letmein".toCharArray());
+ assertNull("User 'sha' falsely authenticated.", user);
+
+
+ user = htpwdUserService.authenticate(" tabbed", "frontAndBack".toCharArray());
+ assertNull("User 'tabbed' falsely authenticated.", user);
+
+ user = htpwdUserService.authenticate(" leading", "whitespace".toCharArray());
+ assertNull("User 'leading' falsely authenticated.", user);
+ }
+
+
+ @Test
+ public void testNewLocalAccount()
+ {
+ UserModel newUser = new UserModel("newlocal");
+ newUser.displayName = "Local User 2";
+ newUser.password = StringUtils.MD5_TYPE + StringUtils.getMD5("localPwd2");
+ assertTrue("Failed to add local account.", htpwdUserService.updateUserModel(newUser));
+
+ UserModel localAccount = htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray());
+ assertNotNull(localAccount);
+ assertEquals(newUser, localAccount);
+
+ localAccount = htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray());
+ assertNotNull(localAccount);
+ assertEquals(newUser, localAccount);
+
+ assertTrue("Failed to delete local account.", htpwdUserService.deleteUser(localAccount.username));
+ assertNull(htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray()));
+ }
+
+
+ @Test
+ public void testCleartextIntrusion()
+ {
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
+ assertNull(htpwdUserService.authenticate("md5", "$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0".toCharArray()));
+ assertNull(htpwdUserService.authenticate("sha", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=".toCharArray()));
+
+ assertNull(htpwdUserService.authenticate("user1", "#externalAccount".toCharArray()));
+
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
+ assertNull(htpwdUserService.authenticate("md5", "$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0".toCharArray()));
+ assertNull(htpwdUserService.authenticate("sha", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=".toCharArray()));
+
+ assertNull(htpwdUserService.authenticate("user1", "#externalAccount".toCharArray()));
+ }
+
+
+ @Test
+ public void testCryptVsPlaintext()
+ {
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
+ assertNull(htpwdUserService.authenticate("crypt", "6TmlbxqZ2kBIA".toCharArray()));
+ assertNotNull(htpwdUserService.authenticate("crypt", "password".toCharArray()));
+
+ MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
+ assertNotNull(htpwdUserService.authenticate("crypt", "6TmlbxqZ2kBIA".toCharArray()));
+ assertNull(htpwdUserService.authenticate("crypt", "password".toCharArray()));
+ }
+
+
+ /*
+ * Test case: User exists in user.conf with a local password and in htpasswd with an external password.
+ * If overrideLocalAuthentication is false, the local account takes precedence and is never updated.
+ */
+ @Test
+ public void testPreparedAccountPreferLocal() throws IOException
+ {
+ setupUS(false);
+
+ UserModel user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray());
+ assertNotNull(user);
+ assertEquals("staylocal", user.getName());
+
+
+ deleteGeneratedFiles();
+ copyInFiles();
+ setupUS(false);
+
+ user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray());
+ assertNotNull(user);
+ assertEquals("staylocal", user.getName());
+ }
+
+
+ /*
+ * Test case: User exists in user.conf with a local password and in htpasswd with an external password.
+ * If overrideLocalAuthentication is true, the external account takes precedence,
+ * the initial local password is never used and discarded.
+ */
+ @Test
+ public void testPreparedAccountPreferExternal() throws IOException
+ {
+ setupUS(true);
+
+ UserModel user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray());
+ assertNotNull(user);
+ assertEquals("staylocal", user.getName());
+
+
+ deleteGeneratedFiles();
+ copyInFiles();
+ setupUS(true);
+
+
+ user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray());
+ assertNotNull(user);
+ assertEquals("staylocal", user.getName());
+
+ // Make sure no authentication by using the string constant for external accounts is possible.
+ user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray());
+ assertNull(user);
+ }
+
+
+ /*
+ * Test case: User exists in user.conf with a local password and in htpasswd with an external password.
+ * If overrideLocalAuthentication is true, the external account takes precedence,
+ * the initial local password is never used and discarded.
+ */
+ @Test
+ public void testPreparedAccountChangeSetting() throws IOException
+ {
+ getSettings(false);
+
+ UserModel user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray());
+ assertNotNull(user);
+ assertEquals("staylocal", user.getName());
+
+
+ getSettings(true);
+
+
+ user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray());
+ assertNotNull(user);
+ assertEquals("staylocal", user.getName());
+
+ // Make sure no authentication by using the string constant for external accounts is possible.
+ user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray());
+ assertNull(user);
+
+
+ getSettings(false);
+ // The preference is now back to local accounts but since the prepared account got switched
+ // to an external account, it will stay this way.
+
+ user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray());
+ assertNotNull(user);
+ assertEquals("leaderred", user.getName());
+
+ user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray());
+ assertNotNull(user);
+ assertEquals("staylocal", user.getName());
+
+ // Make sure no authentication by using the string constant for external accounts is possible.
+ user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray());
+ assertNull(user);
+ }
+
+
+ @Test
+ public void testChangeHtpasswdFile()
+ {
+ UserModel user;
+
+ // User default set up.
+ user = htpwdUserService.authenticate("md5", "password".toCharArray());
+ assertNotNull(user);
+ assertEquals("md5", user.username);
+
+ user = htpwdUserService.authenticate("sha", "password".toCharArray());
+ assertNotNull(user);
+ assertEquals("sha", user.username);
+
+ user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray());
+ assertNull(user);
+
+
+ // Switch to different htpasswd file.
+ getSettings(RESOURCE_DIR + "htpasswd-user", null, null);
+
+ user = htpwdUserService.authenticate("md5", "password".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("sha", "password".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray());
+ assertNotNull(user);
+ assertEquals("blueone", user.username);
+
+ user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray());
+ assertNotNull(user);
+ assertEquals("bluetwo", user.username);
+ }
+
+
+ @Test
+ public void testChangeHtpasswdFileNotExisting()
+ {
+ UserModel user;
+
+ // User default set up.
+ user = htpwdUserService.authenticate("md5", "password".toCharArray());
+ assertNotNull(user);
+ assertEquals("md5", user.username);
+
+ user = htpwdUserService.authenticate("sha", "password".toCharArray());
+ assertNotNull(user);
+ assertEquals("sha", user.username);
+
+ user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray());
+ assertNull(user);
+
+
+ // Switch to different htpasswd file that doesn't exist.
+ // Currently we stop working with old users upon this change.
+ getSettings(RESOURCE_DIR + "no-such-file", null, null);
+
+ user = htpwdUserService.authenticate("md5", "password".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("sha", "password".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray());
+ assertNull(user);
+
+ user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray());
+ assertNull(user);
+ }
+
+}
diff --git a/src/test/resources/htpasswdUSTest/htpasswd-user.in b/src/test/resources/htpasswdUSTest/htpasswd-user.in
new file mode 100644
index 00000000..3ea87ede
--- /dev/null
+++ b/src/test/resources/htpasswdUSTest/htpasswd-user.in
@@ -0,0 +1,15 @@
+# User database
+
+# htpasswd generated entries
+
+# Plaintext
+redone:Yonder
+
+# Unix crypt() "GoRed!"
+redtwo:RMghf6oG.QwAs
+
+ # Apache MD5 "GoBlue!"
+blueone:$apr1$phRTn/7N$237Owfhw5wZTdTyP9NPvC1
+
+# SHA1 "YayBlue!"
+bluetwo:{SHA}ITMvZI9OU5+Rx324C4jpf+MHAL8=
diff --git a/src/test/resources/htpasswdUSTest/htpasswd.in b/src/test/resources/htpasswdUSTest/htpasswd.in
new file mode 100644
index 00000000..f2900e70
--- /dev/null
+++ b/src/test/resources/htpasswdUSTest/htpasswd.in
@@ -0,0 +1,31 @@
+# User database
+
+user1:pass1
+user2:pass2
+
+# "externalPassword"
+leaderred:{SHA}2VZsTsVQYmWAMfQUjNAScpaAlJI=
+
+#user3:disabled
+ # user4:disabled
+
+# htpasswd generated entries
+
+# Plaintext
+plain:passWord
+
+# Unix crypt() "password"
+crypt:6TmlbxqZ2kBIA
+
+ # Apache MD5 "password"
+md5:$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0
+
+
+# SHA1 "password"
+sha:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
+
+
+trailing:.dAxRAQiOOlN.
+
+ tabbed:$apr1$Is7zctsH$CMAXrGkgACQKgRYuQ5vHq.
+ leading:$apr1$O1nQtxjE$8gN15gMeuF3W1Nr8Yz/6J.
diff --git a/src/test/resources/htpasswdUSTest/users.conf.in b/src/test/resources/htpasswdUSTest/users.conf.in
new file mode 100644
index 00000000..142265a4
--- /dev/null
+++ b/src/test/resources/htpasswdUSTest/users.conf.in
@@ -0,0 +1,26 @@
+[user "admin"]
+ password = admin
+ cookie = dd94709528bb1c83d08f3088d4043f4742891f4f
+ role = "#admin"
+ role = "#notfederated"
+[user "user1"]
+ password = "#externalAccount"
+ cookie = 6c7d13cf0aa43054d0fb620546e3a4d79e3d3e89
+ displayName = El Capitan
+ emailAddress = cheffe@example.com
+ role = "#admin"
+[user "user2"]
+ password = "#externalAccount"
+ cookie = d15eabb3a83c44a05ccbdaf3bf5fd1402d971e99
+ displayName = User Two
+ role = "#create"
+ role = "#fork"
+[user "staylocal"]
+ password = localUser
+ cookie = 0a99767e0259dc06ccae5ee6349177be289968f3
+ displayName = Local User
+ role = "#none"
+[user "leaderRed"]
+ password = localPassword
+ displayName = Red Leader
+ role = "#create"