diff options
author | James Moger <james.moger@gitblit.com> | 2011-06-14 16:55:13 -0400 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2011-06-14 16:55:13 -0400 |
commit | 8c9a2037b5c0fed881a3ad6dd9cff364eed603d9 (patch) | |
tree | 2144915a2eae2ad9180715da76ddc3ec058b331e | |
parent | c2272275ca990f3e12a5c1fa0a5de4c670a4d8b4 (diff) | |
download | gitblit-8c9a2037b5c0fed881a3ad6dd9cff364eed603d9.tar.gz gitblit-8c9a2037b5c0fed881a3ad6dd9cff364eed603d9.zip |
Added AccessRestrictionFilter and simplified authentication.
Replaced servlet container basic authentication with a custom servlet
filter which performs the same function. The advantage to this is
that the servlet container is now divorced from the webapp.
The login service (realm) also simplified a great deal and removes its
Jetty dependencies.
Additionally, the basic authorization pop-up will be displayed as
needed based on the repository's access restriction. This was
necessary for view-restricted repositories with the RSS feature. Its
also necessary for completely open repositories as before it would
prompt for credentials.
Improved feed syndication feature.
26 files changed, 1018 insertions, 418 deletions
diff --git a/docs/00_index.mkd b/docs/00_index.mkd index f84773eb..bcf41e11 100644 --- a/docs/00_index.mkd +++ b/docs/00_index.mkd @@ -61,14 +61,13 @@ sources @ [Github][gitbltsrc] - Gitblit may have security holes. Patches welcome. :)
### Todo List
-- Custom BASIC authentication servlet or servlet filter
- Code documentation
- Unit testing
- Update Build.java to JGit 1.0.0, when its released
+- WAR solution
### Idea List
- Consider clone remote repository feature
-- Consider [Apache Shiro](http://shiro.apache.org) for authentication
- Stronger Ticgit read-only integration
- activity/timeline
- query feature with paging support
diff --git a/src/com/gitblit/AccessRestrictionFilter.java b/src/com/gitblit/AccessRestrictionFilter.java new file mode 100644 index 00000000..3aca1039 --- /dev/null +++ b/src/com/gitblit/AccessRestrictionFilter.java @@ -0,0 +1,240 @@ +/*
+ * Copyright 2011 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.IOException;
+import java.security.Principal;
+import java.text.MessageFormat;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ *
+ * http://en.wikipedia.org/wiki/Basic_access_authentication
+ */
+public abstract class AccessRestrictionFilter implements Filter {
+
+ private static final String BASIC = "Basic";
+
+ private static final String CHALLENGE = BASIC + " realm=\"" + Constants.NAME + "\"";
+
+ private static final String SESSION_SECURED = "com.gitblit.secured";
+
+ protected transient Logger logger;
+
+ public AccessRestrictionFilter() {
+ logger = LoggerFactory.getLogger(getClass());
+ }
+
+ protected abstract String extractRepositoryName(String url);
+
+ protected abstract String getUrlRequestType(String url);
+
+ protected abstract boolean requiresAuthentication(RepositoryModel repository);
+
+ protected abstract boolean canAccess(RepositoryModel repository, UserModel user,
+ String restrictedUrl);
+
+ @Override
+ public void doFilter(final ServletRequest request, final ServletResponse response,
+ final FilterChain chain) throws IOException, ServletException {
+
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ // Wrap the HttpServletRequest with the AccessRestrictionRequest which
+ // overrides the servlet container user principal methods.
+ // JGit requires either:
+ //
+ // 1. servlet container authenticated user
+ // 2. http.receivepack = true in each repository's config
+ //
+ // Gitblit must conditionally authenticate users per-repository so just
+ // enabling http.receivepack is insufficient.
+
+ AccessRestrictionRequest accessRequest = new AccessRestrictionRequest(httpRequest);
+
+ String url = httpRequest.getRequestURI().substring(httpRequest.getServletPath().length());
+ String params = httpRequest.getQueryString();
+ if (url.length() > 0 && url.charAt(0) == '/') {
+ url = url.substring(1);
+ }
+ String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params));
+
+ String repository = extractRepositoryName(url);
+
+ // Determine if the request URL is restricted
+ String fullSuffix = fullUrl.substring(repository.length());
+ String urlRequestType = getUrlRequestType(fullSuffix);
+
+ // Load the repository model
+ RepositoryModel model = GitBlit.self().getRepositoryModel(repository);
+ if (model == null) {
+ // repository not found. send 404.
+ logger.info("ARF: " + fullUrl + " (" + HttpServletResponse.SC_NOT_FOUND + ")");
+ httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ // BASIC authentication challenge and response processing
+ if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model)) {
+ // look for client authorization credentials in header
+ final String authorization = httpRequest.getHeader("Authorization");
+ if (authorization != null && authorization.startsWith(BASIC)) {
+ // Authorization: Basic base64credentials
+ String base64Credentials = authorization.substring(BASIC.length()).trim();
+ String credentials = StringUtils.decodeBase64(base64Credentials);
+ if (GitBlit.isDebugMode()) {
+ logger.info(MessageFormat.format("AUTH: {0} ({1})", authorization, credentials));
+ }
+ // credentials = username:password
+ final String[] values = credentials.split(":");
+
+ if (values.length == 2) {
+ String username = values[0];
+ char[] password = values[1].toCharArray();
+ UserModel user = GitBlit.self().authenticate(username, password);
+ if (user != null) {
+ accessRequest.setUser(user);
+ if (user.canAdmin || canAccess(model, user, urlRequestType)) {
+ // authenticated request permitted.
+ // pass processing to the restricted servlet.
+ newSession(accessRequest, httpResponse);
+ logger.info("ARF: " + fullUrl + " (" + HttpServletResponse.SC_CONTINUE + ") authenticated");
+ chain.doFilter(accessRequest, httpResponse);
+ return;
+ }
+ // valid user, but not for requested access. send 403.
+ if (GitBlit.isDebugMode()) {
+ logger.info("ARF: " + fullUrl + " (" + HttpServletResponse.SC_FORBIDDEN
+ + ")");
+ logger.info(MessageFormat.format("AUTH: {0} forbidden to access {1}",
+ user.username, url));
+ }
+ httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ }
+ if (GitBlit.isDebugMode()) {
+ logger.info(MessageFormat
+ .format("AUTH: invalid credentials ({0})", credentials));
+ }
+ }
+
+ // challenge client to provide credentials. send 401.
+ if (GitBlit.isDebugMode()) {
+ logger.info("ARF: " + fullUrl + " (" + HttpServletResponse.SC_UNAUTHORIZED + ")");
+ logger.info("AUTH: Challenge " + CHALLENGE);
+ }
+ httpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+ httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ }
+
+ if (GitBlit.isDebugMode()) {
+ logger.info("ARF: " + fullUrl + " (" + HttpServletResponse.SC_CONTINUE + ") unauthenticated");
+ }
+ // unauthenticated request permitted.
+ // pass processing to the restricted servlet.
+ chain.doFilter(accessRequest, httpResponse);
+ }
+
+ /**
+ * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication()
+ */
+ protected void newSession(HttpServletRequest request, HttpServletResponse response) {
+ HttpSession oldSession = request.getSession(false);
+ if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) {
+ synchronized (this) {
+ Map<String, Object> attributes = new HashMap<String, Object>();
+ Enumeration<String> e = oldSession.getAttributeNames();
+ while (e.hasMoreElements()) {
+ String name = e.nextElement();
+ attributes.put(name, oldSession.getAttribute(name));
+ oldSession.removeAttribute(name);
+ }
+ oldSession.invalidate();
+
+ HttpSession newSession = request.getSession(true);
+ newSession.setAttribute(SESSION_SECURED, Boolean.TRUE);
+ for (Map.Entry<String, Object> entry : attributes.entrySet()) {
+ newSession.setAttribute(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(final FilterConfig config) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ /**
+ * Wraps a standard HttpServletRequest and overrides user principal methods.
+ */
+ public static class AccessRestrictionRequest extends ServletRequestWrapper {
+
+ private UserModel user;
+
+ public AccessRestrictionRequest(HttpServletRequest req) {
+ super(req);
+ user = new UserModel("anonymous");
+ }
+
+ void setUser(UserModel user) {
+ this.user = user;
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return user.username;
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ if (role.equals(Constants.ADMIN_ROLE)) {
+ return user.canAdmin;
+ }
+ return user.canAccessRepository(role);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return user;
+ }
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index 88b13e06..68e7b67e 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -38,6 +38,8 @@ public class Constants { public static final String ZIP_SERVLET_PATH = "/zip/";
public static final String SYNDICATION_SERVLET_PATH = "/feed/";
+
+ public static final String RESOURCE_PATH = "/com/gitblit/wicket/resources/";
public static final String BORDER = "***********************************************************";
diff --git a/src/com/gitblit/DownloadZipServlet.java b/src/com/gitblit/DownloadZipServlet.java index 17454746..3b02cbac 100644 --- a/src/com/gitblit/DownloadZipServlet.java +++ b/src/com/gitblit/DownloadZipServlet.java @@ -41,7 +41,7 @@ public class DownloadZipServlet extends HttpServlet { }
public static String asLink(String baseURL, String repository, String objectId, String path) {
- if (baseURL.charAt(baseURL.length() - 1) == '/') {
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
baseURL = baseURL.substring(0, baseURL.length() - 1);
}
return baseURL + Constants.ZIP_SERVLET_PATH + "?r=" + repository
diff --git a/src/com/gitblit/JettyLoginService.java b/src/com/gitblit/FileLoginService.java index 22f9ce31..b59a7763 100644 --- a/src/com/gitblit/JettyLoginService.java +++ b/src/com/gitblit/FileLoginService.java @@ -16,99 +16,73 @@ package com.gitblit;
import java.io.File;
-import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
-import java.security.Principal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Properties;
import java.util.Set;
-import javax.security.auth.Subject;
-
-import org.eclipse.jetty.http.security.Credential;
-import org.eclipse.jetty.security.IdentityService;
-import org.eclipse.jetty.security.MappedLoginService;
-import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.util.log.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
-public class JettyLoginService extends MappedLoginService implements ILoginService {
-
- private final Logger logger = LoggerFactory.getLogger(JettyLoginService.class);
+public class FileLoginService extends FileSettings implements ILoginService {
- private final File realmFile;
+ private final Logger logger = LoggerFactory.getLogger(FileLoginService.class);
- public JettyLoginService(File realmFile) {
- super();
- setName(Constants.NAME);
- this.realmFile = realmFile;
+ public FileLoginService(File realmFile) {
+ super(realmFile.getAbsolutePath());
}
@Override
public UserModel authenticate(String username, char[] password) {
- UserIdentity identity = login(username, new String(password));
- if (identity == null || identity.equals(UserIdentity.UNAUTHENTICATED_IDENTITY)) {
+ Properties allUsers = read();
+ String userInfo = allUsers.getProperty(username);
+ if (StringUtils.isEmpty(userInfo)) {
return null;
}
- UserModel user = new UserModel(username);
- user.canAdmin = identity.isUserInRole(Constants.ADMIN_ROLE, null);
-
- // Add repositories
- for (Principal principal : identity.getSubject().getPrincipals()) {
- if (principal instanceof RolePrincipal) {
- RolePrincipal role = (RolePrincipal) principal;
- String roleName = role.getName();
- if (roleName.charAt(0) != '#') {
- user.addRepository(roleName);
- }
+ UserModel returnedUser = null;
+ UserModel user = getUserModel(username);
+ if (user.password.startsWith(StringUtils.MD5_TYPE)) {
+ String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
+ if (user.password.equalsIgnoreCase(md5)) {
+ returnedUser = user;
}
}
- return user;
+ if (user.password.equals(new String(password))) {
+ returnedUser = user;
+ }
+ return returnedUser;
}
@Override
public UserModel getUserModel(String username) {
- UserIdentity identity = _users.get(username);
- if (identity == null) {
+ Properties allUsers = read();
+ String userInfo = allUsers.getProperty(username);
+ if (userInfo == null) {
return null;
}
UserModel model = new UserModel(username);
- Subject subject = identity.getSubject();
- for (Principal principal : subject.getPrincipals()) {
- if (principal instanceof RolePrincipal) {
- RolePrincipal role = (RolePrincipal) principal;
- String name = role.getName();
- switch (name.charAt(0)) {
- case '#':
- // Permissions
- if (name.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
- model.canAdmin = true;
- }
- break;
- default:
- model.addRepository(name);
+ String[] userValues = userInfo.split(",");
+ model.password = userValues[0];
+ for (int i = 1; i < userValues.length; i++) {
+ String role = userValues[i];
+ switch (role.charAt(0)) {
+ case '#':
+ // Permissions
+ if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
+ model.canAdmin = true;
}
+ break;
+ default:
+ model.addRepository(role);
}
}
- // Retrieve the password from the realm file.
- // Stupid, I know, but the password is buried within protected inner
- // classes in private variables. Too much work to reflectively retrieve.
- try {
- Properties allUsers = readRealmFile();
- String value = allUsers.getProperty(username);
- String password = value.split(",")[0];
- model.password = password;
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to read password for user {0}!", username), t);
- }
return model;
}
@@ -120,7 +94,7 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi @Override
public boolean updateUserModel(String username, UserModel model) {
try {
- Properties allUsers = readRealmFile();
+ Properties allUsers = read();
ArrayList<String> roles = new ArrayList<String>(model.repositories);
// Permissions
@@ -140,12 +114,7 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi allUsers.remove(username);
allUsers.put(model.username, sb.toString());
- writeRealmFile(allUsers);
-
- // Update login service
- removeUser(username);
- putUser(model.username, Credential.getCredential(model.password),
- roles.toArray(new String[0]));
+ write(allUsers);
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
@@ -163,12 +132,9 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi public boolean deleteUser(String username) {
try {
// Read realm file
- Properties allUsers = readRealmFile();
+ Properties allUsers = read();
allUsers.remove(username);
- writeRealmFile(allUsers);
-
- // Drop user from map
- removeUser(username);
+ write(allUsers);
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
@@ -178,8 +144,8 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi @Override
public List<String> getAllUsernames() {
- List<String> list = new ArrayList<String>();
- list.addAll(_users.keySet());
+ Properties allUsers = read();
+ List<String> list = new ArrayList<String>(allUsers.stringPropertyNames());
return list;
}
@@ -187,7 +153,7 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi public List<String> getUsernamesForRole(String role) {
List<String> list = new ArrayList<String>();
try {
- Properties allUsers = readRealmFile();
+ Properties allUsers = read();
for (String username : allUsers.stringPropertyNames()) {
String value = allUsers.getProperty(username);
String[] values = value.split(",");
@@ -214,7 +180,7 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi Set<String> needsRemoveRole = new HashSet<String>();
// identify users which require add and remove role
- Properties allUsers = readRealmFile();
+ Properties allUsers = read();
for (String username : allUsers.stringPropertyNames()) {
String value = allUsers.getProperty(username);
String[] values = value.split(",");
@@ -239,11 +205,6 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi String userValues = allUsers.getProperty(user);
userValues += "," + role;
allUsers.put(user, userValues);
- String[] values = userValues.split(",");
- String password = values[0];
- String[] roles = new String[values.length - 1];
- System.arraycopy(values, 1, roles, 0, values.length - 1);
- putUser(user, Credential.getCredential(password), roles);
}
// remove role from user
@@ -267,14 +228,10 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi // update properties
allUsers.put(user, sb.toString());
-
- // update memory
- putUser(user, Credential.getCredential(password),
- revisedRoles.toArray(new String[0]));
}
// persist changes
- writeRealmFile(allUsers);
+ write(allUsers);
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
@@ -285,7 +242,7 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi @Override
public boolean renameRole(String oldRole, String newRole) {
try {
- Properties allUsers = readRealmFile();
+ Properties allUsers = read();
Set<String> needsRenameRole = new HashSet<String>();
// identify users which require role rename
@@ -325,14 +282,10 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi // update properties
allUsers.put(user, sb.toString());
-
- // update memory
- putUser(user, Credential.getCredential(password),
- revisedRoles.toArray(new String[0]));
}
// persist changes
- writeRealmFile(allUsers);
+ write(allUsers);
return true;
} catch (Throwable t) {
logger.error(
@@ -344,7 +297,7 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi @Override
public boolean deleteRole(String role) {
try {
- Properties allUsers = readRealmFile();
+ Properties allUsers = read();
Set<String> needsDeleteRole = new HashSet<String>();
// identify users which require role rename
@@ -383,14 +336,10 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi // update properties
allUsers.put(user, sb.toString());
-
- // update memory
- putUser(user, Credential.getCredential(password),
- revisedRoles.toArray(new String[0]));
}
// persist changes
- writeRealmFile(allUsers);
+ write(allUsers);
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
@@ -398,74 +347,27 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi return false;
}
- private Properties readRealmFile() throws IOException {
- Properties allUsers = new Properties();
- FileReader reader = new FileReader(realmFile);
- allUsers.load(reader);
- reader.close();
- return allUsers;
- }
-
- private void writeRealmFile(Properties properties) throws IOException {
+ private void write(Properties properties) throws IOException {
// Update realm file
- File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
+ File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp");
FileWriter writer = new FileWriter(realmFileCopy);
properties
.store(writer,
"# Gitblit realm file format: username=password,\\#permission,repository1,repository2...");
writer.close();
if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
- if (realmFile.delete()) {
- if (!realmFileCopy.renameTo(realmFile)) {
+ if (propertiesFile.delete()) {
+ if (!realmFileCopy.renameTo(propertiesFile)) {
throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
- realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath()));
+ realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath()));
}
} else {
throw new IOException(MessageFormat.format("Failed to delete (0)!",
- realmFile.getAbsolutePath()));
+ propertiesFile.getAbsolutePath()));
}
} else {
throw new IOException(MessageFormat.format("Failed to save {0}!",
realmFileCopy.getAbsolutePath()));
}
}
-
- /* ------------------------------------------------------------ */
- @Override
- public void loadUsers() throws IOException {
- if (realmFile == null) {
- return;
- }
-
- if (Log.isDebugEnabled()) {
- Log.debug("Load " + this + " from " + realmFile);
- }
- Properties allUsers = readRealmFile();
-
- // Map Users
- for (Map.Entry<Object, Object> entry : allUsers.entrySet()) {
- String username = ((String) entry.getKey()).trim();
- String credentials = ((String) entry.getValue()).trim();
- String roles = null;
- int c = credentials.indexOf(',');
- if (c > 0) {
- roles = credentials.substring(c + 1).trim();
- credentials = credentials.substring(0, c).trim();
- }
-
- if (username != null && username.length() > 0 && credentials != null
- && credentials.length() > 0) {
- String[] roleArray = IdentityService.NO_ROLES;
- if (roles != null && roles.length() > 0) {
- roleArray = roles.split(",");
- }
- putUser(username, Credential.getCredential(credentials), roleArray);
- }
- }
- }
-
- @Override
- protected UserIdentity loadUser(String username) {
- return null;
- }
}
diff --git a/src/com/gitblit/FileSettings.java b/src/com/gitblit/FileSettings.java index b70daa0f..e213e80f 100644 --- a/src/com/gitblit/FileSettings.java +++ b/src/com/gitblit/FileSettings.java @@ -26,7 +26,7 @@ import java.util.Properties; */
public class FileSettings extends IStoredSettings {
- private final File propertiesFile;
+ protected final File propertiesFile;
private final Properties properties = new Properties();
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index 01326230..fa593f92 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -20,7 +20,10 @@ import java.io.IOException; import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
@@ -87,8 +90,8 @@ public class GitBlit implements ServletContextListener { return GITBLIT.storedSettings.getAllKeys(startingWith);
}
- public boolean isDebugMode() {
- return storedSettings.getBoolean(Keys.web.debugMode, false);
+ public static boolean isDebugMode() {
+ return GITBLIT.storedSettings.getBoolean(Keys.web.debugMode, false);
}
public List<String> getOtherCloneUrls(String repositoryName) {
@@ -312,6 +315,41 @@ public class GitBlit implements ServletContextListener { return false;
}
+ public String processCommitMessage(String repositoryName, String text) {
+ String html = StringUtils.breakLinesForHtml(text);
+ Map<String, String> map = new HashMap<String, String>();
+ // global regex keys
+ if (storedSettings.getBoolean(Keys.regex.global, false)) {
+ for (String key : storedSettings.getAllKeys(Keys.regex.global)) {
+ if (!key.equals(Keys.regex.global)) {
+ String subKey = key.substring(key.lastIndexOf('.') + 1);
+ map.put(subKey, storedSettings.getString(key, ""));
+ }
+ }
+ }
+
+ // repository-specific regex keys
+ List<String> keys = storedSettings.getAllKeys(Keys.regex._ROOT + "."
+ + repositoryName.toLowerCase());
+ for (String key : keys) {
+ String subKey = key.substring(key.lastIndexOf('.') + 1);
+ map.put(subKey, storedSettings.getString(key, ""));
+ }
+
+ for (Entry<String, String> entry : map.entrySet()) {
+ String definition = entry.getValue().trim();
+ String[] chunks = definition.split("!!!");
+ if (chunks.length == 2) {
+ html = html.replaceAll(chunks[0], chunks[1]);
+ } else {
+ logger.warn(entry.getKey()
+ + " improperly formatted. Use !!! to separate match from replacement: "
+ + definition);
+ }
+ }
+ return html;
+ }
+
public void configureContext(IStoredSettings settings) {
logger.info("Reading configuration from " + settings.toString());
this.storedSettings = settings;
@@ -323,7 +361,8 @@ public class GitBlit implements ServletContextListener { @Override
public void contextInitialized(ServletContextEvent contextEvent) {
if (storedSettings == null) {
- // for running gitblit as a traditional webapp in a servlet container
+ // for running gitblit as a traditional webapp in a servlet
+ // container
WebXmlSettings webxmlSettings = new WebXmlSettings(contextEvent.getServletContext());
configureContext(webxmlSettings);
}
diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java index 2495aeea..4b6df709 100644 --- a/src/com/gitblit/GitBlitServer.java +++ b/src/com/gitblit/GitBlitServer.java @@ -34,13 +34,7 @@ import org.apache.log4j.ConsoleAppender; import org.apache.log4j.PatternLayout;
import org.apache.wicket.protocol.http.ContextParamWebApplicationFactory;
import org.apache.wicket.protocol.http.WicketFilter;
-import org.eclipse.jetty.http.security.Constraint;
-import org.eclipse.jetty.security.ConstraintMapping;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.LoginService;
-import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.bio.SocketConnector;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
@@ -53,6 +47,7 @@ import org.eclipse.jetty.servlet.FilterMapping; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jgit.http.server.GitServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -234,77 +229,52 @@ public class GitBlitServer { wicketFilter.setInitParameter(ContextParamWebApplicationFactory.APP_CLASS_PARAM,
GitBlitWebApp.class.getName());
wicketFilter.setInitParameter(WicketFilter.FILTER_MAPPING_PARAM, wicketPathSpec);
- wicketFilter.setInitParameter(WicketFilter.IGNORE_PATHS_PARAM, "git/");
+ wicketFilter.setInitParameter(WicketFilter.IGNORE_PATHS_PARAM, "git/,feed/,zip/");
rootContext.addFilter(wicketFilter, wicketPathSpec, FilterMapping.DEFAULT);
- // Zip Servlet
- rootContext.addServlet(DownloadZipServlet.class, Constants.ZIP_SERVLET_PATH + "*");
-
- // Syndication Servlet
- rootContext.addServlet(SyndicationServlet.class, Constants.SYNDICATION_SERVLET_PATH + "*");
-
- // Git Servlet
- ServletHolder gitServlet = null;
- String gitServletPathSpec = Constants.GIT_SERVLET_PATH + "*";
+ // JGit Filter and Servlet
if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
- gitServlet = rootContext.addServlet(GitBlitServlet.class, gitServletPathSpec);
- gitServlet.setInitParameter("base-path", params.repositoriesFolder);
- gitServlet.setInitParameter("export-all",
+ String jgitPathSpec = Constants.GIT_SERVLET_PATH + "*";
+ rootContext.addFilter(GitFilter.class, jgitPathSpec, FilterMapping.DEFAULT);
+ ServletHolder jGitServlet = rootContext.addServlet(GitServlet.class, jgitPathSpec);
+ jGitServlet.setInitParameter("base-path", params.repositoriesFolder);
+ jGitServlet.setInitParameter("export-all",
settings.getBoolean(Keys.git.exportAll, true) ? "1" : "0");
}
+ // Syndication Filter and Servlet
+ String feedPathSpec = Constants.SYNDICATION_SERVLET_PATH + "*";
+ rootContext.addFilter(SyndicationFilter.class, feedPathSpec, FilterMapping.DEFAULT);
+ rootContext.addServlet(SyndicationServlet.class, feedPathSpec);
+
+ // Zip Servlet
+ rootContext.addServlet(DownloadZipServlet.class, Constants.ZIP_SERVLET_PATH + "*");
+
// Login Service
- LoginService loginService = null;
String realmUsers = params.realmFile;
- if (!StringUtils.isEmpty(realmUsers)) {
- File realmFile = new File(realmUsers);
- if (realmFile.exists()) {
- logger.info("Setting up login service from " + realmUsers);
- JettyLoginService jettyLoginService = new JettyLoginService(realmFile);
- GitBlit.self().setLoginService(jettyLoginService);
- loginService = jettyLoginService;
- }
+ if (StringUtils.isEmpty(realmUsers)) {
+ logger.error(MessageFormat.format("PLEASE SPECIFY {0}!!", Keys.realm.realmFile));
+ return;
}
-
- // Determine what handler to use
- Handler handler;
- if (gitServlet != null) {
- if (loginService != null) {
- // Authenticate Clone/Push
- logger.info("Setting up authenticated git servlet clone/push access");
-
- Constraint constraint = new Constraint();
- constraint.setAuthenticate(true);
- constraint.setRoles(new String[] { "*" });
-
- ConstraintMapping mapping = new ConstraintMapping();
- mapping.setPathSpec(gitServletPathSpec);
- mapping.setConstraint(constraint);
-
- ConstraintSecurityHandler security = new ConstraintSecurityHandler();
- security.addConstraintMapping(mapping);
- security.setAuthenticator(new BasicAuthenticator());
- security.setLoginService(loginService);
- security.setStrict(false);
-
- security.setHandler(rootContext);
-
- handler = security;
- } else {
- // Anonymous Pull/Push
- logger.info("Setting up anonymous git servlet pull/push access");
- handler = rootContext;
+ File realmFile = new File(realmUsers);
+ if (!realmFile.exists()) {
+ try {
+ realmFile.createNewFile();
+ } catch (IOException x) {
+ logger.error(MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmUsers),
+ x);
+ return;
}
- } else {
- logger.info("Git servlet clone/push disabled");
- handler = rootContext;
}
+ logger.info("Setting up login service from " + realmUsers);
+ FileLoginService loginService = new FileLoginService(realmFile);
+ GitBlit.self().setLoginService(loginService);
logger.info("Git repositories folder "
+ new File(params.repositoriesFolder).getAbsolutePath());
// Set the server's contexts
- server.setHandler(handler);
+ server.setHandler(rootContext);
// Setup the GitBlit context
GitBlit gitblit = GitBlit.self();
diff --git a/src/com/gitblit/GitBlitServlet.java b/src/com/gitblit/GitBlitServlet.java deleted file mode 100644 index a71012b4..00000000 --- a/src/com/gitblit/GitBlitServlet.java +++ /dev/null @@ -1,108 +0,0 @@ -/*
- * Copyright 2011 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.IOException;
-import java.text.MessageFormat;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.http.server.GitServlet;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.models.RepositoryModel;
-
-public class GitBlitServlet extends GitServlet {
-
- private static final long serialVersionUID = 1L;
-
- private transient Logger logger = LoggerFactory.getLogger(GitBlitServlet.class);
-
- public GitBlitServlet() {
- super();
- }
-
- @Override
- protected void service(final HttpServletRequest req, final HttpServletResponse rsp)
- throws ServletException, IOException {
- // admins have full git access to all repositories
- if (req.isUserInRole(Constants.ADMIN_ROLE)) {
- // admins can do whatever
- super.service(req, rsp);
- return;
- }
-
- // try to intercept repository names for authenticated access
- String url = req.getRequestURI().substring(req.getServletPath().length());
- if (url.charAt(0) == '/' && url.length() > 1) {
- url = url.substring(1);
- }
- int forwardSlash = url.indexOf('/');
- if (forwardSlash > -1) {
- String repository = url.substring(0, forwardSlash).toLowerCase();
- String function = url.substring(forwardSlash + 1);
- String query = req.getQueryString() == null ? "" : req.getQueryString();
- RepositoryModel model = GitBlit.self().getRepositoryModel(repository);
- if (model != null) {
- if (model.isFrozen || model.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
- boolean authorizedUser = req.isUserInRole(repository);
- if (function.startsWith("git-receive-pack")
- || (query.indexOf("service=git-receive-pack") > -1)) {
- // Push request
- if (!model.isFrozen && authorizedUser) {
- // clone-restricted or push-authorized
- super.service(req, rsp);
- return;
- } else {
- // user is unauthorized to push to this repository
- logger.warn(MessageFormat.format(
- "user {0} is not authorized to push to {1}", req
- .getUserPrincipal().getName(), repository));
- rsp.sendError(HttpServletResponse.SC_FORBIDDEN, MessageFormat.format(
- "you are not authorized to push to {0}", repository));
- return;
- }
- } else if (function.startsWith("git-upload-pack")
- || (query.indexOf("service=git-upload-pack") > -1)) {
- // Clone request
- boolean cloneRestricted = model.accessRestriction
- .atLeast(AccessRestrictionType.CLONE);
- if (!cloneRestricted || (cloneRestricted && authorizedUser)) {
- // push-restricted or clone-authorized
- super.service(req, rsp);
- return;
- } else {
- // user is unauthorized to clone this repository
- logger.warn(MessageFormat.format(
- "user {0} is not authorized to clone {1}", req
- .getUserPrincipal().getName(), repository));
- rsp.sendError(HttpServletResponse.SC_FORBIDDEN, MessageFormat.format(
- "you are not authorized to clone {0}", repository));
- return;
- }
- }
- }
- }
- }
-
- // pass-through to git servlet
- super.service(req, rsp);
- }
-}
diff --git a/src/com/gitblit/GitFilter.java b/src/com/gitblit/GitFilter.java new file mode 100644 index 00000000..5bd7b330 --- /dev/null +++ b/src/com/gitblit/GitFilter.java @@ -0,0 +1,98 @@ +/*
+ * Copyright 2011 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.text.MessageFormat;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+
+public class GitFilter extends AccessRestrictionFilter {
+
+ protected final String gitReceivePack = "/git-receive-pack";
+
+ protected final String gitUploadPack = "/git-upload-pack";
+
+ protected final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
+ "/objects" };
+
+ @Override
+ protected String extractRepositoryName(String url) {
+ String repository = url;
+ for (String urlSuffix : suffixes) {
+ if (repository.indexOf(urlSuffix) > -1) {
+ repository = repository.substring(0, repository.indexOf(urlSuffix));
+ }
+ }
+ return repository;
+ }
+
+ @Override
+ protected String getUrlRequestType(String suffix) {
+ if (!StringUtils.isEmpty(suffix)) {
+ if (suffix.startsWith(gitReceivePack)) {
+ return gitReceivePack;
+ } else if (suffix.startsWith(gitUploadPack)) {
+ return gitUploadPack;
+ } else if (suffix.contains("?service=git-receive-pack")) {
+ return gitReceivePack;
+ } else if (suffix.contains("?service=git-upload-pack")) {
+ return gitUploadPack;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean requiresAuthentication(RepositoryModel repository) {
+ return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+ }
+
+ @Override
+ protected boolean canAccess(RepositoryModel repository, UserModel user, String urlRequestType) {
+ if (repository.isFrozen || repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
+ boolean authorizedUser = user.canAccessRepository(repository.name);
+ if (urlRequestType.equals(gitReceivePack)) {
+ // Push request
+ if (!repository.isFrozen && authorizedUser) {
+ // clone-restricted or push-authorized
+ return true;
+ } else {
+ // user is unauthorized to push to this repository
+ logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}",
+ user.username, repository));
+ return false;
+ }
+ } else if (urlRequestType.equals(gitUploadPack)) {
+ // Clone request
+ boolean cloneRestricted = repository.accessRestriction
+ .atLeast(AccessRestrictionType.CLONE);
+ if (!cloneRestricted || (cloneRestricted && authorizedUser)) {
+ // push-restricted or clone-authorized
+ return true;
+ } else {
+ // user is unauthorized to clone this repository
+ logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}",
+ user.username, repository));
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/gitblit/ServletRequestWrapper.java b/src/com/gitblit/ServletRequestWrapper.java new file mode 100644 index 00000000..b97c395f --- /dev/null +++ b/src/com/gitblit/ServletRequestWrapper.java @@ -0,0 +1,311 @@ +/*
+ * Copyright 2011 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.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+public abstract class ServletRequestWrapper implements HttpServletRequest {
+
+ protected final HttpServletRequest req;
+
+ public ServletRequestWrapper(HttpServletRequest req) {
+ this.req = req;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return req.getAttribute(name);
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ return req.getAttributeNames();
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return req.getCharacterEncoding();
+ }
+
+ @Override
+ public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
+ req.setCharacterEncoding(env);
+ }
+
+ @Override
+ public int getContentLength() {
+ return req.getContentLength();
+ }
+
+ @Override
+ public String getContentType() {
+ return req.getContentType();
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ return req.getInputStream();
+ }
+
+ @Override
+ public String getParameter(String name) {
+ return req.getParameter(name);
+ }
+
+ @Override
+ public Enumeration getParameterNames() {
+ return req.getParameterNames();
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ return req.getParameterValues(name);
+ }
+
+ @Override
+ public Map getParameterMap() {
+ return req.getParameterMap();
+ }
+
+ @Override
+ public String getProtocol() {
+ return req.getProtocol();
+ }
+
+ @Override
+ public String getScheme() {
+ return req.getScheme();
+ }
+
+ @Override
+ public String getServerName() {
+ return req.getServerName();
+ }
+
+ @Override
+ public int getServerPort() {
+ return req.getServerPort();
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return req.getReader();
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return req.getRemoteAddr();
+ }
+
+ @Override
+ public String getRemoteHost() {
+ return req.getRemoteHost();
+ }
+
+ @Override
+ public void setAttribute(String name, Object o) {
+ req.setAttribute(name, o);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ req.removeAttribute(name);
+ }
+
+ @Override
+ public Locale getLocale() {
+ return req.getLocale();
+ }
+
+ @Override
+ public Enumeration getLocales() {
+ return req.getLocales();
+ }
+
+ @Override
+ public boolean isSecure() {
+ return req.isSecure();
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ return req.getRequestDispatcher(path);
+ }
+
+ @Override
+ @Deprecated
+ public String getRealPath(String path) {
+ return req.getRealPath(path);
+ }
+
+ @Override
+ public int getRemotePort() {
+ return req.getRemotePort();
+ }
+
+ @Override
+ public String getLocalName() {
+ return req.getLocalName();
+ }
+
+ @Override
+ public String getLocalAddr() {
+ return req.getLocalAddr();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return req.getLocalPort();
+ }
+
+ @Override
+ public String getAuthType() {
+ return req.getAuthType();
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ return req.getCookies();
+ }
+
+ @Override
+ public long getDateHeader(String name) {
+ return req.getDateHeader(name);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return req.getHeader(name);
+ }
+
+ @Override
+ public Enumeration getHeaders(String name) {
+ return req.getHeaders(name);
+ }
+
+ @Override
+ public Enumeration getHeaderNames() {
+ return req.getHeaderNames();
+ }
+
+ @Override
+ public int getIntHeader(String name) {
+ return req.getIntHeader(name);
+ }
+
+ @Override
+ public String getMethod() {
+ return req.getMethod();
+ }
+
+ @Override
+ public String getPathInfo() {
+ return req.getPathInfo();
+ }
+
+ @Override
+ public String getPathTranslated() {
+ return req.getPathTranslated();
+ }
+
+ @Override
+ public String getContextPath() {
+ return req.getContextPath();
+ }
+
+ @Override
+ public String getQueryString() {
+ return req.getQueryString();
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return req.getRemoteUser();
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return req.isUserInRole(role);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return req.getUserPrincipal();
+ }
+
+ @Override
+ public String getRequestedSessionId() {
+ return req.getRequestedSessionId();
+ }
+
+ @Override
+ public String getRequestURI() {
+ return req.getRequestURI();
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return req.getRequestURL();
+ }
+
+ @Override
+ public String getServletPath() {
+ return req.getServletPath();
+ }
+
+ @Override
+ public HttpSession getSession(boolean create) {
+ return req.getSession(create);
+ }
+
+ @Override
+ public HttpSession getSession() {
+ return req.getSession();
+ }
+
+ @Override
+ public boolean isRequestedSessionIdValid() {
+ return req.isRequestedSessionIdValid();
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromCookie() {
+ return req.isRequestedSessionIdFromCookie();
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromURL() {
+ return req.isRequestedSessionIdFromURL();
+ }
+
+ @Override
+ @Deprecated
+ public boolean isRequestedSessionIdFromUrl() {
+ return req.isRequestedSessionIdFromUrl();
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/SyndicationFilter.java b/src/com/gitblit/SyndicationFilter.java new file mode 100644 index 00000000..68f383b4 --- /dev/null +++ b/src/com/gitblit/SyndicationFilter.java @@ -0,0 +1,44 @@ +/*
+ * Copyright 2011 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 com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+public class SyndicationFilter extends AccessRestrictionFilter {
+
+ @Override
+ protected String extractRepositoryName(String url) {
+ return url;
+ }
+
+ @Override
+ protected String getUrlRequestType(String url) {
+ return "RESTRICTED";
+ }
+
+ @Override
+ protected boolean requiresAuthentication(RepositoryModel repository) {
+ return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
+ }
+
+ @Override
+ protected boolean canAccess(RepositoryModel repository, UserModel user, String restrictedURL) {
+ return user.canAccessRepository(repository.name);
+ }
+
+}
diff --git a/src/com/gitblit/SyndicationServlet.java b/src/com/gitblit/SyndicationServlet.java index d2b396ee..19865fe5 100644 --- a/src/com/gitblit/SyndicationServlet.java +++ b/src/com/gitblit/SyndicationServlet.java @@ -15,6 +15,7 @@ */
package com.gitblit;
+import java.text.MessageFormat;
import java.util.List;
import javax.servlet.http.HttpServlet;
@@ -28,6 +29,7 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.SyndicationUtils;
+import com.gitblit.wicket.WicketUtils;
public class SyndicationServlet extends HttpServlet {
@@ -36,20 +38,55 @@ public class SyndicationServlet extends HttpServlet { private transient Logger logger = LoggerFactory.getLogger(SyndicationServlet.class);
public static String asLink(String baseURL, String repository, String objectId, int length) {
- if (baseURL.charAt(baseURL.length() - 1) == '/') {
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
baseURL = baseURL.substring(0, baseURL.length() - 1);
}
- return baseURL + Constants.SYNDICATION_SERVLET_PATH + "?r=" + repository
- + (objectId == null ? "" : ("&h=" + objectId)) + (length > 0 ? "&l=" + length : "");
+ StringBuilder url = new StringBuilder();
+ url.append(baseURL);
+ url.append(Constants.SYNDICATION_SERVLET_PATH);
+ url.append(repository);
+ if (!StringUtils.isEmpty(objectId) || length > 0) {
+ StringBuilder parameters = new StringBuilder("?");
+ if (StringUtils.isEmpty(objectId)) {
+ parameters.append("l=");
+ parameters.append(length);
+ } else {
+ parameters.append("h=");
+ parameters.append(objectId);
+ if (length > 0) {
+ parameters.append("&l=");
+ parameters.append(length);
+ }
+ }
+ url.append(parameters);
+ }
+ return url.toString();
+ }
+
+ public static String getTitle(String repository, String objectId) {
+ String id = objectId;
+ if (!StringUtils.isEmpty(id)) {
+ if (id.startsWith(org.eclipse.jgit.lib.Constants.R_HEADS)) {
+ id = id.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length());
+ } else if (id.startsWith(org.eclipse.jgit.lib.Constants.R_REMOTES)) {
+ id = id.substring(org.eclipse.jgit.lib.Constants.R_REMOTES.length());
+ } else if (id.startsWith(org.eclipse.jgit.lib.Constants.R_TAGS)) {
+ id = id.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length());
+ }
+ }
+ return MessageFormat.format("{0} ({1})", repository, id);
}
private void processRequest(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
java.io.IOException {
- String hostUrl = request.getRequestURL().toString();
- String servlet = request.getServletPath();
- hostUrl = hostUrl.substring(0, hostUrl.indexOf(servlet));
- String repositoryName = request.getParameter("r");
+
+ String hostURL = WicketUtils.getHostURL(request);
+ String url = request.getRequestURI().substring(request.getServletPath().length());
+ if (url.charAt(0) == '/' && url.length() > 1) {
+ url = url.substring(1);
+ }
+ String repositoryName = url;
String objectId = request.getParameter("h");
String l = request.getParameter("l");
int length = GitBlit.getInteger(Keys.web.syndicationEntries, 25);
@@ -62,14 +99,13 @@ public class SyndicationServlet extends HttpServlet { } catch (NumberFormatException x) {
}
}
-
- // TODO confirm repository is accessible!!
Repository repository = GitBlit.self().getRepository(repositoryName);
RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
List<RevCommit> commits = JGitUtils.getRevLog(repository, objectId, 0, length);
try {
- SyndicationUtils.toRSS(hostUrl, model.name + " " + objectId, model.description, model.name, commits, response.getOutputStream());
+ SyndicationUtils.toRSS(hostURL, getTitle(model.name, objectId), model.description,
+ model.name, commits, response.getOutputStream());
} catch (Exception e) {
logger.error("An error occurred during feed generation", e);
}
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java index f23fd291..29647088 100644 --- a/src/com/gitblit/models/UserModel.java +++ b/src/com/gitblit/models/UserModel.java @@ -16,10 +16,11 @@ package com.gitblit.models;
import java.io.Serializable;
+import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
-public class UserModel implements Serializable {
+public class UserModel implements Principal, Serializable {
private static final long serialVersionUID = 1L;
@@ -42,6 +43,11 @@ public class UserModel implements Serializable { }
@Override
+ public String getName() {
+ return username;
+ }
+
+ @Override
public String toString() {
return username;
}
diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java index fa84fe8f..363efc9c 100644 --- a/src/com/gitblit/utils/StringUtils.java +++ b/src/com/gitblit/utils/StringUtils.java @@ -16,14 +16,20 @@ package com.gitblit.utils;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.PatternSyntaxException;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jgit.util.Base64;
+
public class StringUtils {
+ public static final String MD5_TYPE = "MD5:";
+
public static boolean isEmpty(String value) {
return value == null || value.trim().length() == 0;
}
@@ -56,6 +62,22 @@ public class StringUtils { return retStr.toString();
}
+ public static String encodeURL(String inStr) {
+ StringBuffer retStr = new StringBuffer();
+ int i = 0;
+ while (i < inStr.length()) {
+ if (inStr.charAt(i) == '/') {
+ retStr.append("%2F");
+ } else if (inStr.charAt(i) == ' ') {
+ retStr.append("%20");
+ } else {
+ retStr.append(inStr.charAt(i));
+ }
+ i++;
+ }
+ return retStr.toString();
+ }
+
public static String flattenStrings(List<String> values) {
return flattenStrings(values, " ");
}
@@ -116,20 +138,41 @@ public class StringUtils { try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(bytes, 0, bytes.length);
- byte[] sha1hash = md.digest();
- StringBuilder sb = new StringBuilder(sha1hash.length * 2);
- for (int i = 0; i < sha1hash.length; i++) {
- if (((int) sha1hash[i] & 0xff) < 0x10) {
- sb.append('0');
- }
- sb.append(Long.toString((int) sha1hash[i] & 0xff, 16));
- }
- return sb.toString();
+ byte[] digest = md.digest();
+ return toHex(digest);
} catch (NoSuchAlgorithmException t) {
throw new RuntimeException(t);
}
}
+ public static String getMD5(String string) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.reset();
+ md.update(string.getBytes("iso-8859-1"));
+ byte[] digest = md.digest();
+ return toHex(digest);
+ } catch (Exception e) {
+ Log.warn(e);
+ return null;
+ }
+ }
+
+ private static String toHex(byte[] bytes) {
+ StringBuilder sb = new StringBuilder(bytes.length * 2);
+ for (int i = 0; i < bytes.length; i++) {
+ if (((int) bytes[i] & 0xff) < 0x10) {
+ sb.append('0');
+ }
+ sb.append(Long.toString((int) bytes[i] & 0xff, 16));
+ }
+ return sb.toString();
+ }
+
+ public static String decodeBase64(String base64) {
+ return new String(Base64.decode(base64), Charset.forName("UTF-8"));
+ }
+
public static String getRootPath(String path) {
if (path.indexOf('/') > -1) {
return path.substring(0, path.lastIndexOf('/'));
@@ -144,11 +187,11 @@ public class StringUtils { }
return relativePath;
}
-
+
public static List<String> getStringsFromValue(String value) {
return getStringsFromValue(value, " ");
}
-
+
public static List<String> getStringsFromValue(String value, String separator) {
List<String> strings = new ArrayList<String>();
try {
diff --git a/src/com/gitblit/utils/SyndicationUtils.java b/src/com/gitblit/utils/SyndicationUtils.java index da937f9a..5763af3f 100644 --- a/src/com/gitblit/utils/SyndicationUtils.java +++ b/src/com/gitblit/utils/SyndicationUtils.java @@ -24,32 +24,41 @@ import java.util.List; import org.eclipse.jgit.revwalk.RevCommit;
+import com.gitblit.Constants;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
+import com.sun.syndication.feed.synd.SyndImageImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedOutput;
public class SyndicationUtils {
- public static void toRSS(String hostUrl, String title, String description, String repository, List<RevCommit> commits, OutputStream os)
- throws IOException, FeedException {
+ public static void toRSS(String hostUrl, String title, String description, String repository,
+ List<RevCommit> commits, OutputStream os) throws IOException, FeedException {
SyndFeed feed = new SyndFeedImpl();
- feed.setFeedType("rss_1.0");
+ feed.setFeedType("rss_2.0");
feed.setTitle(title);
- feed.setLink(MessageFormat.format("{0}/summary/{1}", hostUrl, repository));
+ feed.setLink(MessageFormat.format("{0}/summary/{1}", hostUrl,
+ StringUtils.encodeURL(repository)));
feed.setDescription(description);
+ SyndImageImpl image = new SyndImageImpl();
+ image.setTitle(Constants.NAME);
+ image.setUrl(hostUrl + Constants.RESOURCE_PATH + "gitblt_25.png");
+ image.setLink(hostUrl);
+ feed.setImage(image);
List<SyndEntry> entries = new ArrayList<SyndEntry>();
for (RevCommit commit : commits) {
SyndEntry entry = new SyndEntryImpl();
entry.setTitle(commit.getShortMessage());
entry.setAuthor(commit.getAuthorIdent().getName());
- entry.setLink(MessageFormat.format("{0}/commit/{1}/{2}", hostUrl, repository, commit.getName()));
+ entry.setLink(MessageFormat.format("{0}/commit/{1}/{2}", hostUrl,
+ StringUtils.encodeURL(repository), commit.getName()));
entry.setPublishedDate(commit.getCommitterIdent().getWhen());
SyndContent content = new SyndContentImpl();
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java index 472a11dc..cc54e003 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/com/gitblit/wicket/GitBlitWebApp.java @@ -122,7 +122,7 @@ public class GitBlitWebApp extends WebApplication { @Override
public final String getConfigurationType() {
- if (GitBlit.self().isDebugMode()) {
+ if (GitBlit.isDebugMode()) {
return Application.DEVELOPMENT;
}
return Application.DEPLOYMENT;
diff --git a/src/com/gitblit/wicket/WicketUtils.java b/src/com/gitblit/wicket/WicketUtils.java index 1d2a60fe..54f9648c 100644 --- a/src/com/gitblit/wicket/WicketUtils.java +++ b/src/com/gitblit/wicket/WicketUtils.java @@ -22,11 +22,18 @@ import java.util.Date; import java.util.List;
import java.util.TimeZone;
+import javax.servlet.http.HttpServletRequest;
+
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
+import org.apache.wicket.Request;
+import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.IHeaderContributor;
+import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.image.ContextImage;
+import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.resource.ContextRelativeResource;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Constants;
@@ -162,7 +169,7 @@ public class WicketUtils { }
public static ContextImage newImage(String wicketId, String file, String tooltip) {
- ContextImage img = new ContextImage(wicketId, "/com/gitblit/wicket/resources/" + file);
+ ContextImage img = new ContextImage(wicketId, com.gitblit.Constants.RESOURCE_PATH + file);
if (!StringUtils.isEmpty(tooltip)) {
setHtmlTooltip(img, tooltip);
}
@@ -170,7 +177,42 @@ public class WicketUtils { }
public static ContextRelativeResource getResource(String file) {
- return new ContextRelativeResource("/com/gitblit/wicket/resources/" + file);
+ return new ContextRelativeResource(com.gitblit.Constants.RESOURCE_PATH + file);
+ }
+
+ public static String getHostURL(Request request) {
+ HttpServletRequest req = ((WebRequest) request).getHttpServletRequest();
+ return getHostURL(req);
+ }
+
+ public static String getHostURL(HttpServletRequest request) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(request.getScheme());
+ sb.append("://");
+ sb.append(request.getServerName());
+ if ((request.getScheme().equals("http") && request.getServerPort() != 80)
+ || (request.getScheme().equals("https") && request.getServerPort() != 443)) {
+ sb.append(":" + request.getServerPort());
+ }
+ return sb.toString();
+ }
+
+ public static HeaderContributor syndicationDiscoveryLink(final String feedTitle,
+ final String url) {
+ return new HeaderContributor(new IHeaderContributor() {
+ private static final long serialVersionUID = 1L;
+
+ public void renderHead(IHeaderResponse response) {
+ String contentType = "application/rss+xml";
+
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("<link rel=\"alternate\" ");
+ buffer.append("type=\"").append(contentType).append("\" ");
+ buffer.append("title=\"").append(feedTitle).append("\" ");
+ buffer.append("href=\"").append(url).append("\" />");
+ response.renderString(buffer.toString());
+ }
+ });
}
public static PageParameters newUsernameParameter(String username) {
diff --git a/src/com/gitblit/wicket/pages/CommitPage.java b/src/com/gitblit/wicket/pages/CommitPage.java index 6da962ef..a34917b6 100644 --- a/src/com/gitblit/wicket/pages/CommitPage.java +++ b/src/com/gitblit/wicket/pages/CommitPage.java @@ -128,7 +128,7 @@ public class CommitPage extends RepositoryPage { SearchType.AUTHOR));
item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
.getAuthorIdent().getWhen(), getTimeZone()));
- item.add(new Label("noteContent", substituteText(entry.content))
+ item.add(new Label("noteContent", GitBlit.self().processCommitMessage(repositoryName, entry.content))
.setEscapeModelStrings(false));
}
};
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java index eafec05d..63916276 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/com/gitblit/wicket/pages/EditUserPage.java @@ -31,8 +31,6 @@ import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.Model;
import org.apache.wicket.model.util.CollectionModel;
import org.apache.wicket.model.util.ListModel;
-import org.eclipse.jetty.http.security.Credential.Crypt;
-import org.eclipse.jetty.http.security.Credential.MD5;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.GitBlit;
@@ -114,8 +112,7 @@ public class EditUserPage extends BasePage { return;
}
String password = userModel.password;
- if (!password.toUpperCase().startsWith(Crypt.__TYPE)
- && !password.toUpperCase().startsWith(MD5.__TYPE)) {
+ if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE)) {
// This is a plain text password.
// Check length.
int minLength = GitBlit.getInteger(Keys.realm.minPasswordLength, 5);
@@ -133,7 +130,7 @@ public class EditUserPage extends BasePage { String type = GitBlit.getString(Keys.realm.passwordStorage, "md5");
if (type.equalsIgnoreCase("md5")) {
// store MD5 digest of password
- userModel.password = MD5.digest(userModel.password);
+ userModel.password = StringUtils.MD5_TYPE + StringUtils.getMD5(userModel.password);
}
}
diff --git a/src/com/gitblit/wicket/pages/LogPage.java b/src/com/gitblit/wicket/pages/LogPage.java index 35f8a73f..2cd787c7 100644 --- a/src/com/gitblit/wicket/pages/LogPage.java +++ b/src/com/gitblit/wicket/pages/LogPage.java @@ -26,6 +26,8 @@ public class LogPage extends RepositoryPage { public LogPage(PageParameters params) {
super(params);
+ addSyndicationDiscoveryLink();
+
int pageNumber = WicketUtils.getPage(params);
int prevPage = Math.max(0, pageNumber - 1);
int nextPage = pageNumber + 1;
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java index c3a6b03b..cf14ee19 100644 --- a/src/com/gitblit/wicket/pages/RepositoryPage.java +++ b/src/com/gitblit/wicket/pages/RepositoryPage.java @@ -21,7 +21,6 @@ import java.util.Arrays; import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
@@ -159,7 +158,7 @@ public abstract class RepositoryPage extends BasePage { }
};
add(extrasView);
-
+
add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
.getRelativePathPrefixToContextRoot(), repositoryName, null, 0)));
@@ -189,6 +188,12 @@ public abstract class RepositoryPage extends BasePage { }
}
+ protected void addSyndicationDiscoveryLink() {
+ add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(repositoryName,
+ objectId), SyndicationServlet.asLink(getRequest()
+ .getRelativePathPrefixToContextRoot(), repositoryName, objectId, 0)));
+ }
+
protected Repository getRepository() {
if (r == null) {
Repository r = GitBlit.self().getRepository(repositoryName);
@@ -234,48 +239,13 @@ public abstract class RepositoryPage extends BasePage { protected void addFullText(String wicketId, String text, boolean substituteRegex) {
String html;
if (substituteRegex) {
- html = substituteText(text);
+ html = GitBlit.self().processCommitMessage(repositoryName, text);
} else {
html = StringUtils.breakLinesForHtml(text);
}
add(new Label(wicketId, html).setEscapeModelStrings(false));
}
- protected String substituteText(String text) {
- String html = StringUtils.breakLinesForHtml(text);
- Map<String, String> map = new HashMap<String, String>();
- // global regex keys
- if (GitBlit.getBoolean(Keys.regex.global, false)) {
- for (String key : GitBlit.getAllKeys(Keys.regex.global)) {
- if (!key.equals(Keys.regex.global)) {
- String subKey = key.substring(key.lastIndexOf('.') + 1);
- map.put(subKey, GitBlit.getString(key, ""));
- }
- }
- }
-
- // repository-specific regex keys
- List<String> keys = GitBlit.getAllKeys(Keys.regex._ROOT + "."
- + repositoryName.toLowerCase());
- for (String key : keys) {
- String subKey = key.substring(key.lastIndexOf('.') + 1);
- map.put(subKey, GitBlit.getString(key, ""));
- }
-
- for (Entry<String, String> entry : map.entrySet()) {
- String definition = entry.getValue().trim();
- String[] chunks = definition.split("!!!");
- if (chunks.length == 2) {
- html = html.replaceAll(chunks[0], chunks[1]);
- } else {
- logger.warn(entry.getKey()
- + " improperly formatted. Use !!! to separate match from replacement: "
- + definition);
- }
- }
- return html;
- }
-
protected abstract String getPageName();
protected Component createPersonPanel(String wicketId, PersonIdent identity,
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java index e85901ab..0d0db86b 100644 --- a/src/com/gitblit/wicket/pages/SummaryPage.java +++ b/src/com/gitblit/wicket/pages/SummaryPage.java @@ -22,12 +22,9 @@ import java.text.ParseException; import java.util.ArrayList;
import java.util.List;
-import javax.servlet.http.HttpServletRequest;
-
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.protocol.http.WebRequest;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.wicketstuff.googlecharts.Chart;
@@ -81,6 +78,8 @@ public class SummaryPage extends RepositoryPage { metrics = MetricUtils.getDateMetrics(r, null, true, null);
metricsTotal = metrics.remove(0);
}
+
+ addSyndicationDiscoveryLink();
// repository description
add(new Label("repositoryDescription", getRepositoryModel().description));
@@ -121,17 +120,8 @@ public class SummaryPage extends RepositoryPage { default:
add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
}
-
- HttpServletRequest req = ((WebRequest) getRequestCycle().getRequest())
- .getHttpServletRequest();
StringBuilder sb = new StringBuilder();
- sb.append(req.getScheme());
- sb.append("://");
- sb.append(req.getServerName());
- if ((req.getScheme().equals("http") && req.getServerPort() != 80)
- || (req.getScheme().equals("https") && req.getServerPort() != 443)) {
- sb.append(":" + req.getServerPort());
- }
+ sb.append(WicketUtils.getHostURL(getRequestCycle().getRequest()));
sb.append(Constants.GIT_SERVLET_PATH);
sb.append(repositoryName);
repositoryUrls.add(sb.toString());
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/com/gitblit/wicket/panels/RepositoriesPanel.html index a599d226..1e609e10 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.html +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.html @@ -50,7 +50,7 @@ <th wicket:id="orderByOwner"><wicket:message key="gb.owner">Owner</wicket:message></th>
<th></th>
<th wicket:id="orderByDate"><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
- <th clas="right"></th>
+ <th class="right"></th>
</tr>
</wicket:fragment>
@@ -80,7 +80,12 @@ <td class="author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
<td style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
<td><span wicket:id="repositoryLastChange">[last change]</span></td>
- <td class="rightAlign"><span wicket:id="repositoryLinks"></span></td>
+ <td class="rightAlign">
+ <span wicket:id="repositoryLinks"></span>
+ <a style="text-decoration: none;" wicket:id="syndication">
+ <img style="border:0px;vertical-align:middle;" src="/com/gitblit/wicket/resources/feed_16x16.png"></img>
+ </a>
+ </td>
</wicket:fragment>
</wicket:panel>
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/com/gitblit/wicket/panels/RepositoriesPanel.java index a0c9e132..c7441487 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java @@ -31,6 +31,7 @@ import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
@@ -43,6 +44,7 @@ import org.apache.wicket.model.Model; import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
+import com.gitblit.SyndicationServlet;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
@@ -215,6 +217,8 @@ public class RepositoriesPanel extends BasePanel { } else {
row.add(new Label("repositoryLinks"));
}
+ row.add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
+ .getRelativePathPrefixToContextRoot(), entry.name, null, 0)));
WicketUtils.setAlternatingBackground(item, counter);
counter++;
}
diff --git a/tests/com/gitblit/tests/GitBlitSuite.java b/tests/com/gitblit/tests/GitBlitSuite.java index e13e1bbf..c9e383e7 100644 --- a/tests/com/gitblit/tests/GitBlitSuite.java +++ b/tests/com/gitblit/tests/GitBlitSuite.java @@ -24,10 +24,10 @@ import junit.framework.TestSuite; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepository;
+import com.gitblit.FileLoginService;
import com.gitblit.FileSettings;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
-import com.gitblit.JettyLoginService;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.JGitUtils;
@@ -72,8 +72,7 @@ public class GitBlitSuite extends TestSetup { protected void setUp() throws Exception {
FileSettings settings = new FileSettings("distrib/gitblit.properties");
GitBlit.self().configureContext(settings);
- JettyLoginService loginService = new JettyLoginService(new File("distrib/users.properties"));
- loginService.loadUsers();
+ FileLoginService loginService = new FileLoginService(new File("distrib/users.properties"));
GitBlit.self().setLoginService(loginService);
if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) {
|