src/WEB-INF/web.xml | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/AccessRestrictionFilter.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/AuthenticationFilter.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/Constants.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/RpcFilter.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/RpcServlet.java | ●●●●● patch | view | raw | blame | history |
src/WEB-INF/web.xml
@@ -127,7 +127,22 @@ <filter-name>ZipFilter</filter-name> <url-pattern>/zip/*</url-pattern> </filter-mapping> <!-- Rpc Restriction Filter <url-pattern> MUST match: * RpcServlet * com.gitblit.Constants.RPC_PATH * Wicket Filter ignorePaths parameter --> <filter> <filter-name>RpcFilter</filter-name> <filter-class>com.gitblit.RpcFilter</filter-class> </filter> <filter-mapping> <filter-name>RpcFilter</filter-name> <url-pattern>/rpc/*</url-pattern> </filter-mapping> <!-- Wicket Filter --> <filter> @@ -152,6 +167,7 @@ * ZipServlet <url-pattern> * com.gitblit.Constants.ZIP_PATH * FederationServlet <url-pattern> * RpcFilter <url-pattern> * RpcServlet <url-pattern> --> <param-value>git/,feed/,zip/,federation/,rpc/</param-value> </init-param> src/com/gitblit/AccessRestrictionFilter.java
@@ -16,34 +16,23 @@ package com.gitblit; import java.io.IOException; import java.nio.charset.Charset; 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.eclipse.jgit.util.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.AuthenticationFilter.AuthenticatedRequest; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; /** * The AccessRestrictionFilter is a servlet filter that preprocesses requests * that match its url pattern definition in the web.xml file. * The AccessRestrictionFilter is an AuthenticationFilter that confirms that the * requested repository can be accessed by the anonymous or named user. * * The filter extracts the name of the repository from the url and determines if * the requested action for the repository requires a Basic authentication @@ -55,19 +44,7 @@ * @author James Moger * */ 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()); } public abstract class AccessRestrictionFilter extends AuthenticationFilter { /** * Extract the repository name from the url. @@ -118,26 +95,7 @@ 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 servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath(); String url = httpRequest.getRequestURI().substring(servletUrl.length()); String params = httpRequest.getQueryString(); if (url.length() > 0 && url.charAt(0) == '/') { url = url.substring(1); } String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params)); String fullUrl = getFullUrl(httpRequest); String repository = extractRepositoryName(fullUrl); // Determine if the request URL is restricted @@ -148,145 +106,64 @@ RepositoryModel model = GitBlit.self().getRepositoryModel(repository); if (model == null) { // repository not found. send 404. logger.info("ARF: " + fullUrl + " (" + HttpServletResponse.SC_NOT_FOUND + ")"); logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl, HttpServletResponse.SC_NOT_FOUND)); httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // 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. AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); UserModel user = getUser(httpRequest); if (user != null) { authenticatedRequest.setUser(user); } // 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 = new String(Base64.decode(base64Credentials), Charset.forName("UTF-8")); // 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 (user == null) { // challenge client to provide credentials. send 401. if (GitBlit.isDebugMode()) { logger.info(MessageFormat .format("AUTH: invalid credentials ({0})", credentials)); logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl)); } httpResponse.setHeader("WWW-Authenticate", CHALLENGE); httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } else { // check user access for request if (user.canAdmin || canAccess(model, user, urlRequestType)) { // authenticated request permitted. // pass processing to the restricted servlet. newSession(authenticatedRequest, httpResponse); logger.info(MessageFormat.format("ARF: {0} ({1}) authenticated", fullUrl, HttpServletResponse.SC_CONTINUE)); chain.doFilter(authenticatedRequest, httpResponse); return; } // valid user, but not for requested access. send 403. if (GitBlit.isDebugMode()) { logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}", user.username, fullUrl)); } httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); return; } // 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"); logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl, HttpServletResponse.SC_CONTINUE)); } // 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()); } } } } /** * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ @Override public void init(final FilterConfig config) throws ServletException { } /** * @see javax.servlet.Filter#destroy() */ @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; } chain.doFilter(authenticatedRequest, httpResponse); } } src/com/gitblit/AuthenticationFilter.java
New file @@ -0,0 +1,201 @@ /* * 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.nio.charset.Charset; 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.eclipse.jgit.util.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; /** * The AuthenticationFilter is a servlet filter that preprocesses requests that * match its url pattern definition in the web.xml file. * * http://en.wikipedia.org/wiki/Basic_access_authentication * * @author James Moger * */ public abstract class AuthenticationFilter implements Filter { protected static final String BASIC = "Basic"; protected static final String CHALLENGE = BASIC + " realm=\"" + Constants.NAME + "\""; protected static final String SESSION_SECURED = "com.gitblit.secured"; protected transient Logger logger = LoggerFactory.getLogger(getClass()); /** * doFilter does the actual work of preprocessing the request to ensure that * the user may proceed. * * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, * javax.servlet.ServletResponse, javax.servlet.FilterChain) */ @Override public abstract void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException; /** * Returns the full relative url of the request. * * @param httpRequest * @return url */ protected String getFullUrl(HttpServletRequest httpRequest) { String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath(); String url = httpRequest.getRequestURI().substring(servletUrl.length()); String params = httpRequest.getQueryString(); if (url.length() > 0 && url.charAt(0) == '/') { url = url.substring(1); } String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params)); return fullUrl; } /** * Returns the user making the request, if the user has authenticated. * * @param httpRequest * @return user */ protected UserModel getUser(HttpServletRequest httpRequest) { UserModel user = null; // 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 = new String(Base64.decode(base64Credentials), Charset.forName("UTF-8")); // credentials = username:password final String[] values = credentials.split(":"); if (values.length == 2) { String username = values[0]; char[] password = values[1].toCharArray(); user = GitBlit.self().authenticate(username, password); if (user != null) { return user; } } if (GitBlit.isDebugMode()) { logger.info(MessageFormat.format("AUTH: invalid credentials ({0})", credentials)); } } return null; } /** * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication() */ @SuppressWarnings("unchecked") 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()); } } } } /** * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ @Override public void init(final FilterConfig config) throws ServletException { } /** * @see javax.servlet.Filter#destroy() */ @Override public void destroy() { } /** * Wraps a standard HttpServletRequest and overrides user principal methods. */ public static class AuthenticatedRequest extends ServletRequestWrapper { private UserModel user; public AuthenticatedRequest(HttpServletRequest req) { super(req); user = new UserModel("anonymous"); } UserModel getUser() { return user; } 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; } } } src/com/gitblit/Constants.java
@@ -213,6 +213,10 @@ return LIST_REPOSITORIES; } public boolean exceeds(RpcRequest type) { return this.ordinal() > type.ordinal(); } @Override public String toString() { return name(); src/com/gitblit/RpcFilter.java
New file @@ -0,0 +1,131 @@ /* * 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.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.gitblit.Constants.RpcRequest; import com.gitblit.models.UserModel; /** * The RpcFilter is a servlet filter that secures the RpcServlet. * * The filter extracts the rpc request type from the url and determines if the * requested action requires a Basic authentication prompt. If authentication is * required and no credentials are stored in the "Authorization" header, then a * basic authentication challenge is issued. * * http://en.wikipedia.org/wiki/Basic_access_authentication * * @author James Moger * */ public class RpcFilter extends AuthenticationFilter { /** * doFilter does the actual work of preprocessing the request to ensure that * the user may proceed. * * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, * javax.servlet.ServletResponse, javax.servlet.FilterChain) */ @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; if (!GitBlit.getBoolean(Keys.web.enableRpcServlet, false)) { logger.warn(Keys.web.enableRpcServlet + " must be set TRUE for rpc requests."); httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); return; } String fullUrl = getFullUrl(httpRequest); RpcRequest requestType = RpcRequest.fromName(httpRequest.getParameter("req")); boolean adminRequest = requestType.exceeds(RpcRequest.LIST_REPOSITORIES); boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, false); boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true); // Wrap the HttpServletRequest with the RpcServletnRequest which // overrides the servlet container user principal methods. AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); UserModel user = getUser(httpRequest); if (user != null) { authenticatedRequest.setUser(user); } // BASIC authentication challenge and response processing if ((adminRequest && authenticateAdmin) || (!adminRequest && authenticateView)) { if (user == null) { // challenge client to provide credentials. send 401. if (GitBlit.isDebugMode()) { logger.info(MessageFormat.format("RPC: CHALLENGE {0}", fullUrl)); } httpResponse.setHeader("WWW-Authenticate", CHALLENGE); httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } else { // check user access for request if (user.canAdmin || canAccess(user, requestType)) { // authenticated request permitted. // pass processing to the restricted servlet. newSession(authenticatedRequest, httpResponse); logger.info(MessageFormat.format("RPC: {0} ({1}) authenticated", fullUrl, HttpServletResponse.SC_CONTINUE)); chain.doFilter(authenticatedRequest, httpResponse); return; } // valid user, but not for requested access. send 403. if (GitBlit.isDebugMode()) { logger.info(MessageFormat.format("RPC: {0} forbidden to access {1}", user.username, fullUrl)); } httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); return; } } if (GitBlit.isDebugMode()) { logger.info(MessageFormat.format("RPC: {0} ({1}) unauthenticated", fullUrl, HttpServletResponse.SC_CONTINUE)); } // unauthenticated request permitted. // pass processing to the restricted servlet. chain.doFilter(authenticatedRequest, httpResponse); } private boolean canAccess(UserModel user, RpcRequest requestType) { switch (requestType) { case LIST_REPOSITORIES: return true; default: return user.canAdmin; } } } src/com/gitblit/RpcServlet.java
@@ -15,12 +15,15 @@ */ package com.gitblit; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.gitblit.Constants.RpcRequest; @@ -51,26 +54,16 @@ * @throws java.io.IOException */ @Override protected void processRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RpcRequest reqType = RpcRequest.fromName(request.getParameter("req")); logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType, request.getRemoteAddr())); if (!GitBlit.getBoolean(Keys.web.enableRpcServlet, false)) { logger.warn(Keys.web.enableRpcServlet + " must be set TRUE for rpc requests."); response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } // TODO user authentication and authorization UserModel user = null; UserModel user = (UserModel) request.getUserPrincipal(); Object result = null; if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) { // list repositories // Determine the Gitblit clone url String gitblitUrl = HttpUtils.getGitblitURL(request); StringBuilder sb = new StringBuilder(); @@ -79,6 +72,7 @@ sb.append("{0}"); String cloneUrl = sb.toString(); // list repositories List<RepositoryModel> list = GitBlit.self().getRepositoryModels(user); Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>(); for (RepositoryModel model : list) { @@ -88,11 +82,6 @@ result = repositories; } else if (RpcRequest.LIST_USERS.equals(reqType)) { // list users if (user == null || !user.canAdmin) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } // user is authorized to retrieve all accounts List<String> names = GitBlit.self().getAllUsernames(); List<UserModel> users = new ArrayList<UserModel>(); for (String name : names) {