From: James Moger Date: Tue, 14 Jun 2011 20:55:13 +0000 (-0400) Subject: Added AccessRestrictionFilter and simplified authentication. X-Git-Tag: v0.5.0~11 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=8c9a2037b5c0fed881a3ad6dd9cff364eed603d9;p=gitblit.git 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. --- 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 attributes = new HashMap(); + Enumeration 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 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/FileLoginService.java b/src/com/gitblit/FileLoginService.java new file mode 100644 index 00000000..b59a7763 --- /dev/null +++ b/src/com/gitblit/FileLoginService.java @@ -0,0 +1,373 @@ +/* + * 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.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.models.UserModel; +import com.gitblit.utils.StringUtils; + +public class FileLoginService extends FileSettings implements ILoginService { + + private final Logger logger = LoggerFactory.getLogger(FileLoginService.class); + + public FileLoginService(File realmFile) { + super(realmFile.getAbsolutePath()); + } + + @Override + public UserModel authenticate(String username, char[] password) { + Properties allUsers = read(); + String userInfo = allUsers.getProperty(username); + if (StringUtils.isEmpty(userInfo)) { + return null; + } + 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; + } + } + if (user.password.equals(new String(password))) { + returnedUser = user; + } + return returnedUser; + } + + @Override + public UserModel getUserModel(String username) { + Properties allUsers = read(); + String userInfo = allUsers.getProperty(username); + if (userInfo == null) { + return null; + } + UserModel model = new UserModel(username); + 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); + } + } + return model; + } + + @Override + public boolean updateUserModel(UserModel model) { + return updateUserModel(model.username, model); + } + + @Override + public boolean updateUserModel(String username, UserModel model) { + try { + Properties allUsers = read(); + ArrayList roles = new ArrayList(model.repositories); + + // Permissions + if (model.canAdmin) { + roles.add(Constants.ADMIN_ROLE); + } + + StringBuilder sb = new StringBuilder(); + sb.append(model.password); + sb.append(','); + for (String role : roles) { + sb.append(role); + sb.append(','); + } + // trim trailing comma + sb.setLength(sb.length() - 1); + allUsers.remove(username); + allUsers.put(model.username, sb.toString()); + + write(allUsers); + return true; + } catch (Throwable t) { + logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), + t); + } + return false; + } + + @Override + public boolean deleteUserModel(UserModel model) { + return deleteUser(model.username); + } + + @Override + public boolean deleteUser(String username) { + try { + // Read realm file + Properties allUsers = read(); + allUsers.remove(username); + write(allUsers); + return true; + } catch (Throwable t) { + logger.error(MessageFormat.format("Failed to delete user {0}!", username), t); + } + return false; + } + + @Override + public List getAllUsernames() { + Properties allUsers = read(); + List list = new ArrayList(allUsers.stringPropertyNames()); + return list; + } + + @Override + public List getUsernamesForRole(String role) { + List list = new ArrayList(); + try { + Properties allUsers = read(); + for (String username : allUsers.stringPropertyNames()) { + String value = allUsers.getProperty(username); + String[] values = value.split(","); + // skip first value (password) + for (int i = 1; i < values.length; i++) { + String r = values[i]; + if (r.equalsIgnoreCase(role)) { + list.add(username); + break; + } + } + } + } catch (Throwable t) { + logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t); + } + return list; + } + + @Override + public boolean setUsernamesForRole(String role, List usernames) { + try { + Set specifiedUsers = new HashSet(usernames); + Set needsAddRole = new HashSet(specifiedUsers); + Set needsRemoveRole = new HashSet(); + + // identify users which require add and remove role + Properties allUsers = read(); + for (String username : allUsers.stringPropertyNames()) { + String value = allUsers.getProperty(username); + String[] values = value.split(","); + // skip first value (password) + for (int i = 1; i < values.length; i++) { + String r = values[i]; + if (r.equalsIgnoreCase(role)) { + // user has role, check against revised user list + if (specifiedUsers.contains(username)) { + needsAddRole.remove(username); + } else { + // remove role from user + needsRemoveRole.add(username); + } + break; + } + } + } + + // add roles to users + for (String user : needsAddRole) { + String userValues = allUsers.getProperty(user); + userValues += "," + role; + allUsers.put(user, userValues); + } + + // remove role from user + for (String user : needsRemoveRole) { + String[] values = allUsers.getProperty(user).split(","); + String password = values[0]; + StringBuilder sb = new StringBuilder(); + sb.append(password); + sb.append(','); + List revisedRoles = new ArrayList(); + // skip first value (password) + for (int i = 1; i < values.length; i++) { + String value = values[i]; + if (!value.equalsIgnoreCase(role)) { + revisedRoles.add(value); + sb.append(value); + sb.append(','); + } + } + sb.setLength(sb.length() - 1); + + // update properties + allUsers.put(user, sb.toString()); + } + + // persist changes + write(allUsers); + return true; + } catch (Throwable t) { + logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t); + } + return false; + } + + @Override + public boolean renameRole(String oldRole, String newRole) { + try { + Properties allUsers = read(); + Set needsRenameRole = new HashSet(); + + // identify users which require role rename + for (String username : allUsers.stringPropertyNames()) { + String value = allUsers.getProperty(username); + String[] roles = value.split(","); + // skip first value (password) + for (int i = 1; i < roles.length; i++) { + String r = roles[i]; + if (r.equalsIgnoreCase(oldRole)) { + needsRenameRole.remove(username); + break; + } + } + } + + // rename role for identified users + for (String user : needsRenameRole) { + String userValues = allUsers.getProperty(user); + String[] values = userValues.split(","); + String password = values[0]; + StringBuilder sb = new StringBuilder(); + sb.append(password); + sb.append(','); + List revisedRoles = new ArrayList(); + revisedRoles.add(newRole); + // skip first value (password) + for (int i = 1; i < values.length; i++) { + String value = values[i]; + if (!value.equalsIgnoreCase(oldRole)) { + revisedRoles.add(value); + sb.append(value); + sb.append(','); + } + } + sb.setLength(sb.length() - 1); + + // update properties + allUsers.put(user, sb.toString()); + } + + // persist changes + write(allUsers); + return true; + } catch (Throwable t) { + logger.error( + MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t); + } + return false; + } + + @Override + public boolean deleteRole(String role) { + try { + Properties allUsers = read(); + Set needsDeleteRole = new HashSet(); + + // identify users which require role rename + for (String username : allUsers.stringPropertyNames()) { + String value = allUsers.getProperty(username); + String[] roles = value.split(","); + // skip first value (password) + for (int i = 1; i < roles.length; i++) { + String r = roles[i]; + if (r.equalsIgnoreCase(role)) { + needsDeleteRole.remove(username); + break; + } + } + } + + // delete role for identified users + for (String user : needsDeleteRole) { + String userValues = allUsers.getProperty(user); + String[] values = userValues.split(","); + String password = values[0]; + StringBuilder sb = new StringBuilder(); + sb.append(password); + sb.append(','); + List revisedRoles = new ArrayList(); + // skip first value (password) + for (int i = 1; i < values.length; i++) { + String value = values[i]; + if (!value.equalsIgnoreCase(role)) { + revisedRoles.add(value); + sb.append(value); + sb.append(','); + } + } + sb.setLength(sb.length() - 1); + + // update properties + allUsers.put(user, sb.toString()); + } + + // persist changes + write(allUsers); + return true; + } catch (Throwable t) { + logger.error(MessageFormat.format("Failed to delete role {0}!", role), t); + } + return false; + } + + private void write(Properties properties) throws IOException { + // Update realm file + 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 (propertiesFile.delete()) { + if (!realmFileCopy.renameTo(propertiesFile)) { + throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!", + realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath())); + } + } else { + throw new IOException(MessageFormat.format("Failed to delete (0)!", + propertiesFile.getAbsolutePath())); + } + } else { + throw new IOException(MessageFormat.format("Failed to save {0}!", + realmFileCopy.getAbsolutePath())); + } + } +} 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 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 map = new HashMap(); + // 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 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 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/JettyLoginService.java b/src/com/gitblit/JettyLoginService.java deleted file mode 100644 index 22f9ce31..00000000 --- a/src/com/gitblit/JettyLoginService.java +++ /dev/null @@ -1,471 +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.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; - -public class JettyLoginService extends MappedLoginService implements ILoginService { - - private final Logger logger = LoggerFactory.getLogger(JettyLoginService.class); - - private final File realmFile; - - public JettyLoginService(File realmFile) { - super(); - setName(Constants.NAME); - this.realmFile = realmFile; - } - - @Override - public UserModel authenticate(String username, char[] password) { - UserIdentity identity = login(username, new String(password)); - if (identity == null || identity.equals(UserIdentity.UNAUTHENTICATED_IDENTITY)) { - 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); - } - } - } - return user; - } - - @Override - public UserModel getUserModel(String username) { - UserIdentity identity = _users.get(username); - if (identity == 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); - } - } - } - // 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; - } - - @Override - public boolean updateUserModel(UserModel model) { - return updateUserModel(model.username, model); - } - - @Override - public boolean updateUserModel(String username, UserModel model) { - try { - Properties allUsers = readRealmFile(); - ArrayList roles = new ArrayList(model.repositories); - - // Permissions - if (model.canAdmin) { - roles.add(Constants.ADMIN_ROLE); - } - - StringBuilder sb = new StringBuilder(); - sb.append(model.password); - sb.append(','); - for (String role : roles) { - sb.append(role); - sb.append(','); - } - // trim trailing comma - sb.setLength(sb.length() - 1); - 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])); - return true; - } catch (Throwable t) { - logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), - t); - } - return false; - } - - @Override - public boolean deleteUserModel(UserModel model) { - return deleteUser(model.username); - } - - @Override - public boolean deleteUser(String username) { - try { - // Read realm file - Properties allUsers = readRealmFile(); - allUsers.remove(username); - writeRealmFile(allUsers); - - // Drop user from map - removeUser(username); - return true; - } catch (Throwable t) { - logger.error(MessageFormat.format("Failed to delete user {0}!", username), t); - } - return false; - } - - @Override - public List getAllUsernames() { - List list = new ArrayList(); - list.addAll(_users.keySet()); - return list; - } - - @Override - public List getUsernamesForRole(String role) { - List list = new ArrayList(); - try { - Properties allUsers = readRealmFile(); - for (String username : allUsers.stringPropertyNames()) { - String value = allUsers.getProperty(username); - String[] values = value.split(","); - // skip first value (password) - for (int i = 1; i < values.length; i++) { - String r = values[i]; - if (r.equalsIgnoreCase(role)) { - list.add(username); - break; - } - } - } - } catch (Throwable t) { - logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t); - } - return list; - } - - @Override - public boolean setUsernamesForRole(String role, List usernames) { - try { - Set specifiedUsers = new HashSet(usernames); - Set needsAddRole = new HashSet(specifiedUsers); - Set needsRemoveRole = new HashSet(); - - // identify users which require add and remove role - Properties allUsers = readRealmFile(); - for (String username : allUsers.stringPropertyNames()) { - String value = allUsers.getProperty(username); - String[] values = value.split(","); - // skip first value (password) - for (int i = 1; i < values.length; i++) { - String r = values[i]; - if (r.equalsIgnoreCase(role)) { - // user has role, check against revised user list - if (specifiedUsers.contains(username)) { - needsAddRole.remove(username); - } else { - // remove role from user - needsRemoveRole.add(username); - } - break; - } - } - } - - // add roles to users - for (String user : needsAddRole) { - 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 - for (String user : needsRemoveRole) { - String[] values = allUsers.getProperty(user).split(","); - String password = values[0]; - StringBuilder sb = new StringBuilder(); - sb.append(password); - sb.append(','); - List revisedRoles = new ArrayList(); - // skip first value (password) - for (int i = 1; i < values.length; i++) { - String value = values[i]; - if (!value.equalsIgnoreCase(role)) { - revisedRoles.add(value); - sb.append(value); - sb.append(','); - } - } - sb.setLength(sb.length() - 1); - - // update properties - allUsers.put(user, sb.toString()); - - // update memory - putUser(user, Credential.getCredential(password), - revisedRoles.toArray(new String[0])); - } - - // persist changes - writeRealmFile(allUsers); - return true; - } catch (Throwable t) { - logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t); - } - return false; - } - - @Override - public boolean renameRole(String oldRole, String newRole) { - try { - Properties allUsers = readRealmFile(); - Set needsRenameRole = new HashSet(); - - // identify users which require role rename - for (String username : allUsers.stringPropertyNames()) { - String value = allUsers.getProperty(username); - String[] roles = value.split(","); - // skip first value (password) - for (int i = 1; i < roles.length; i++) { - String r = roles[i]; - if (r.equalsIgnoreCase(oldRole)) { - needsRenameRole.remove(username); - break; - } - } - } - - // rename role for identified users - for (String user : needsRenameRole) { - String userValues = allUsers.getProperty(user); - String[] values = userValues.split(","); - String password = values[0]; - StringBuilder sb = new StringBuilder(); - sb.append(password); - sb.append(','); - List revisedRoles = new ArrayList(); - revisedRoles.add(newRole); - // skip first value (password) - for (int i = 1; i < values.length; i++) { - String value = values[i]; - if (!value.equalsIgnoreCase(oldRole)) { - revisedRoles.add(value); - sb.append(value); - sb.append(','); - } - } - sb.setLength(sb.length() - 1); - - // update properties - allUsers.put(user, sb.toString()); - - // update memory - putUser(user, Credential.getCredential(password), - revisedRoles.toArray(new String[0])); - } - - // persist changes - writeRealmFile(allUsers); - return true; - } catch (Throwable t) { - logger.error( - MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t); - } - return false; - } - - @Override - public boolean deleteRole(String role) { - try { - Properties allUsers = readRealmFile(); - Set needsDeleteRole = new HashSet(); - - // identify users which require role rename - for (String username : allUsers.stringPropertyNames()) { - String value = allUsers.getProperty(username); - String[] roles = value.split(","); - // skip first value (password) - for (int i = 1; i < roles.length; i++) { - String r = roles[i]; - if (r.equalsIgnoreCase(role)) { - needsDeleteRole.remove(username); - break; - } - } - } - - // delete role for identified users - for (String user : needsDeleteRole) { - String userValues = allUsers.getProperty(user); - String[] values = userValues.split(","); - String password = values[0]; - StringBuilder sb = new StringBuilder(); - sb.append(password); - sb.append(','); - List revisedRoles = new ArrayList(); - // skip first value (password) - for (int i = 1; i < values.length; i++) { - String value = values[i]; - if (!value.equalsIgnoreCase(role)) { - revisedRoles.add(value); - sb.append(value); - sb.append(','); - } - } - sb.setLength(sb.length() - 1); - - // update properties - allUsers.put(user, sb.toString()); - - // update memory - putUser(user, Credential.getCredential(password), - revisedRoles.toArray(new String[0])); - } - - // persist changes - writeRealmFile(allUsers); - return true; - } catch (Throwable t) { - logger.error(MessageFormat.format("Failed to delete role {0}!", role), t); - } - 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 { - // Update realm file - File realmFileCopy = new File(realmFile.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)) { - throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!", - realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath())); - } - } else { - throw new IOException(MessageFormat.format("Failed to delete (0)!", - realmFile.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 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/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 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; @@ -41,6 +42,11 @@ public class UserModel implements Serializable { repositories.add(name.toLowerCase()); } + @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 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 getStringsFromValue(String value) { return getStringsFromValue(value, " "); } - + public static List getStringsFromValue(String value, String separator) { List strings = new ArrayList(); 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 commits, OutputStream os) - throws IOException, FeedException { + public static void toRSS(String hostUrl, String title, String description, String repository, + List 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 entries = new ArrayList(); 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(""); + 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 map = new HashMap(); - // 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 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 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 @@ Owner Last Change - + @@ -80,7 +80,12 @@ [repository owner] [last change] - + + + + + + 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()) {