diff options
Diffstat (limited to 'src')
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" |