diff options
Diffstat (limited to 'src/main/java/com/gitblit/servlet')
20 files changed, 4575 insertions, 0 deletions
diff --git a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java new file mode 100644 index 00000000..d5ded33c --- /dev/null +++ b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java @@ -0,0 +1,245 @@ +/*
+ * 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.servlet;
+
+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.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.ISessionManager;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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
+ * 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 abstract class AccessRestrictionFilter extends AuthenticationFilter {
+
+ protected final IRuntimeManager runtimeManager;
+
+ protected final IRepositoryManager repositoryManager;
+
+ protected AccessRestrictionFilter(
+ IRuntimeManager runtimeManager,
+ ISessionManager sessionManager,
+ IRepositoryManager repositoryManager) {
+ super(sessionManager);
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
+ }
+
+ /**
+ * Extract the repository name from the url.
+ *
+ * @param url
+ * @return repository name
+ */
+ protected abstract String extractRepositoryName(String url);
+
+ /**
+ * Analyze the url and returns the action of the request.
+ *
+ * @param url
+ * @return action of the request
+ */
+ protected abstract String getUrlRequestAction(String url);
+
+ /**
+ * Determine if a non-existing repository can be created using this filter.
+ *
+ * @return true if the filter allows repository creation
+ */
+ protected abstract boolean isCreationAllowed();
+
+ /**
+ * Determine if the action may be executed on the repository.
+ *
+ * @param repository
+ * @param action
+ * @return true if the action may be performed
+ */
+ protected abstract boolean isActionAllowed(RepositoryModel repository, String action);
+
+ /**
+ * Determine if the repository requires authentication.
+ *
+ * @param repository
+ * @param action
+ * @return true if authentication required
+ */
+ protected abstract boolean requiresAuthentication(RepositoryModel repository, String action);
+
+ /**
+ * Determine if the user can access the repository and perform the specified
+ * action.
+ *
+ * @param repository
+ * @param user
+ * @param action
+ * @return true if user may execute the action on the repository
+ */
+ protected abstract boolean canAccess(RepositoryModel repository, UserModel user, String action);
+
+ /**
+ * Allows a filter to create a repository, if one does not exist.
+ *
+ * @param user
+ * @param repository
+ * @param action
+ * @return the repository model, if it is created, null otherwise
+ */
+ protected RepositoryModel createRepository(UserModel user, String repository, String action) {
+ return null;
+ }
+
+ /**
+ * 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;
+
+ String fullUrl = getFullUrl(httpRequest);
+ String repository = extractRepositoryName(fullUrl);
+
+ if (repositoryManager.isCollectingGarbage(repository)) {
+ logger.info(MessageFormat.format("ARF: Rejecting request for {0}, busy collecting garbage!", repository));
+ httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ // Determine if the request URL is restricted
+ String fullSuffix = fullUrl.substring(repository.length());
+ String urlRequestType = getUrlRequestAction(fullSuffix);
+
+ UserModel user = getUser(httpRequest);
+
+ // Load the repository model
+ RepositoryModel model = repositoryManager.getRepositoryModel(repository);
+ if (model == null) {
+ if (isCreationAllowed()) {
+ if (user == null) {
+ // challenge client to provide credentials for creation. send 401.
+ if (runtimeManager.isDebugMode()) {
+ logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl));
+ }
+ httpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+ httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ } else {
+ // see if we can create a repository for this request
+ model = createRepository(user, repository, urlRequestType);
+ }
+ }
+
+ if (model == null) {
+ // repository not found. send 404.
+ logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl,
+ HttpServletResponse.SC_NOT_FOUND));
+ httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+ }
+
+ // Confirm that the action may be executed on the repository
+ if (!isActionAllowed(model, urlRequestType)) {
+ logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})",
+ urlRequestType, model, HttpServletResponse.SC_FORBIDDEN));
+ httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
+ 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);
+ if (user != null) {
+ authenticatedRequest.setUser(user);
+ }
+
+ // BASIC authentication challenge and response processing
+ if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType)) {
+ if (user == null) {
+ // challenge client to provide credentials. send 401.
+ if (runtimeManager.isDebugMode()) {
+ 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 (runtimeManager.isDebugMode()) {
+ logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}",
+ user.username, fullUrl));
+ }
+ httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ }
+
+ if (runtimeManager.isDebugMode()) {
+ logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl,
+ HttpServletResponse.SC_CONTINUE));
+ }
+ // unauthenticated request permitted.
+ // pass processing to the restricted servlet.
+ chain.doFilter(authenticatedRequest, httpResponse);
+ }
+}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java new file mode 100644 index 00000000..214f2042 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java @@ -0,0 +1,195 @@ +/*
+ * 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.servlet;
+
+import java.io.IOException;
+import java.security.Principal;
+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.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.manager.ISessionManager;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.DeepCopier;
+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 CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
+
+ protected static final String SESSION_SECURED = "com.gitblit.secured";
+
+ protected transient Logger logger = LoggerFactory.getLogger(getClass());
+
+ protected final ISessionManager sessionManager;
+
+ protected AuthenticationFilter(ISessionManager sessionManager) {
+ this.sessionManager = sessionManager;
+ }
+
+ /**
+ * 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;
+
+ /**
+ * Allow the filter to require a client certificate to continue processing.
+ *
+ * @return true, if a client certificate is required
+ */
+ protected boolean requiresClientCertificate() {
+ return false;
+ }
+
+ /**
+ * 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 = sessionManager.authenticate(httpRequest, requiresClientCertificate());
+ return user;
+ }
+
+ /**
+ * 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 AuthenticatedRequest extends HttpServletRequestWrapper {
+
+ private UserModel user;
+
+ public AuthenticatedRequest(HttpServletRequest req) {
+ super(req);
+ user = DeepCopier.copy(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();
+ }
+ // Gitblit does not currently use actual roles in the traditional
+ // servlet container sense. That is the reason this is marked
+ // deprecated, but I may want to revisit this.
+ return user.canAccessRepository(role);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return user;
+ }
+ }
+}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/servlet/BranchGraphServlet.java b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java new file mode 100644 index 00000000..3efe60de --- /dev/null +++ b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java @@ -0,0 +1,409 @@ +/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright 2013 gitblit.com.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * 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.servlet;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.imageio.ImageIO;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revplot.AbstractPlotRenderer;
+import org.eclipse.jgit.revplot.PlotCommit;
+import org.eclipse.jgit.revplot.PlotCommitList;
+import org.eclipse.jgit.revplot.PlotLane;
+import org.eclipse.jgit.revplot.PlotWalk;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.Constants;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Keys.web;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Handles requests for branch graphs
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class BranchGraphServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final int LANE_WIDTH = 14;
+
+ // must match tr.commit css height
+ private static final int ROW_HEIGHT = 24;
+
+ private static final int RIGHT_PAD = 2;
+
+ private final Stroke[] strokeCache;
+
+ private final IStoredSettings settings;
+
+ private final IRepositoryManager repositoryManager;
+
+ @Inject
+ public BranchGraphServlet(
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager) {
+
+ super();
+ this.settings = runtimeManager.getSettings();
+ this.repositoryManager = repositoryManager;
+
+ strokeCache = new Stroke[4];
+ for (int i = 1; i < strokeCache.length; i++)
+ strokeCache[i] = new BasicStroke(i);
+ }
+
+ /**
+ * Returns an url to this servlet for the specified parameters.
+ *
+ * @param baseURL
+ * @param repository
+ * @param objectId
+ * @param numberCommits
+ * @return an url
+ */
+ public static String asLink(String baseURL, String repository, String objectId, int numberCommits) {
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+ baseURL = baseURL.substring(0, baseURL.length() - 1);
+ }
+ return baseURL + Constants.BRANCH_GRAPH_PATH + "?r=" + repository
+ + (objectId == null ? "" : ("&h=" + objectId))
+ + (numberCommits > 0 ? ("&l=" + numberCommits) : "");
+ }
+
+ @Override
+ protected long getLastModified(HttpServletRequest req) {
+ String repository = req.getParameter("r");
+ String objectId = req.getParameter("h");
+ Repository r = null;
+ try {
+ r = repositoryManager.getRepository(repository);
+ if (StringUtils.isEmpty(objectId)) {
+ objectId = JGitUtils.getHEADRef(r);
+ }
+ RevCommit commit = JGitUtils.getCommit(r, objectId);
+ return JGitUtils.getCommitDate(commit).getTime();
+ } finally {
+ if (r != null) {
+ r.close();
+ }
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ InputStream is = null;
+ Repository r = null;
+ PlotWalk rw = null;
+ try {
+ String repository = request.getParameter("r");
+ String objectId = request.getParameter("h");
+ String length = request.getParameter("l");
+
+ r = repositoryManager.getRepository(repository);
+
+ rw = new PlotWalk(r);
+ if (StringUtils.isEmpty(objectId)) {
+ objectId = JGitUtils.getHEADRef(r);
+ }
+
+ rw.markStart(rw.lookupCommit(r.resolve(objectId)));
+
+ // default to the items-per-page setting, unless specified
+ int maxCommits = settings.getInteger(Keys.web.itemsPerPage, 50);
+ int requestedCommits = maxCommits;
+ if (!StringUtils.isEmpty(length)) {
+ int l = Integer.parseInt(length);
+ if (l > 0) {
+ requestedCommits = l;
+ }
+ }
+
+ // fetch the requested commits plus some extra so that the last
+ // commit displayed *likely* has correct lane assignments
+ CommitList commitList = new CommitList();
+ commitList.source(rw);
+ commitList.fillTo(2*Math.max(requestedCommits, maxCommits));
+
+ // determine the appropriate width for the image
+ int numLanes = 1;
+ int numCommits = Math.min(requestedCommits, commitList.size());
+ if (numCommits > 1) {
+ // determine graph width
+ Set<String> parents = new TreeSet<String>();
+ for (int i = 0; i < commitList.size(); i++) {
+ PlotCommit<Lane> commit = commitList.get(i);
+ boolean checkLane = false;
+
+ if (i < numCommits) {
+ // commit in visible list
+ checkLane = true;
+
+ // remember parents
+ for (RevCommit p : commit.getParents()) {
+ parents.add(p.getName());
+ }
+ } else if (parents.contains(commit.getName())) {
+ // commit outside visible list, but it is a parent of a
+ // commit in the visible list so we need to know it's lane
+ // assignment
+ checkLane = true;
+ }
+
+ if (checkLane) {
+ int pos = commit.getLane().getPosition();
+ numLanes = Math.max(numLanes, pos + 1);
+ }
+ }
+ }
+
+ int graphWidth = numLanes * LANE_WIDTH + RIGHT_PAD;
+ int rowHeight = ROW_HEIGHT;
+
+ // create an image buffer and render the lanes
+ BufferedImage image = new BufferedImage(graphWidth, rowHeight*numCommits, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g = null;
+ try {
+ g = image.createGraphics();
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ LanesRenderer renderer = new LanesRenderer();
+ for (int i = 0; i < commitList.size(); i++) {
+ PlotCommit<Lane> commit = commitList.get(i);
+ Graphics row = g.create(0, i*rowHeight, graphWidth, rowHeight);
+ try {
+ renderer.paint(row, commit, rowHeight, graphWidth);
+ } finally {
+ row.dispose();
+ row = null;
+ }
+ }
+ } finally {
+ if (g != null) {
+ g.dispose();
+ g = null;
+ }
+ }
+
+ // write the image buffer to the client
+ response.setContentType("image/png");
+ if (numCommits > 1) {
+ response.setHeader("Cache-Control", "public, max-age=60, must-revalidate");
+ response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commitList.get(0)).getTime());
+ }
+ OutputStream os = response.getOutputStream();
+ ImageIO.write(image, "png", os);
+ os.flush();
+ image.flush();
+ image = null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (is != null) {
+ is.close();
+ is = null;
+ }
+ if (rw != null) {
+ rw.dispose();
+ rw = null;
+ }
+ if (r != null) {
+ r.close();
+ r = null;
+ }
+ }
+ }
+
+ private Stroke stroke(final int width) {
+ if (width < strokeCache.length)
+ return strokeCache[width];
+ return new BasicStroke(width);
+ }
+
+ static class CommitList extends PlotCommitList<Lane> {
+ final List<Color> laneColors;
+ final LinkedList<Color> colors;
+
+ CommitList() {
+ laneColors = new ArrayList<Color>();
+ laneColors.add(new Color(133, 166, 214));
+ laneColors.add(new Color(221, 205, 93));
+ laneColors.add(new Color(199, 134, 57));
+ laneColors.add(new Color(131, 150, 98));
+ laneColors.add(new Color(197, 123, 127));
+ laneColors.add(new Color(139, 136, 140));
+ laneColors.add(new Color(48, 135, 144));
+ laneColors.add(new Color(190, 93, 66));
+ laneColors.add(new Color(143, 163, 54));
+ laneColors.add(new Color(180, 148, 74));
+ laneColors.add(new Color(101, 101, 217));
+ laneColors.add(new Color(72, 153, 119));
+ laneColors.add(new Color(23, 101, 160));
+ laneColors.add(new Color(132, 164, 118));
+ laneColors.add(new Color(255, 230, 59));
+ laneColors.add(new Color(136, 176, 70));
+ laneColors.add(new Color(255, 138, 1));
+ laneColors.add(new Color(123, 187, 95));
+ laneColors.add(new Color(233, 88, 98));
+ laneColors.add(new Color(93, 158, 254));
+ laneColors.add(new Color(175, 215, 0));
+ laneColors.add(new Color(140, 134, 142));
+ laneColors.add(new Color(232, 168, 21));
+ laneColors.add(new Color(0, 172, 191));
+ laneColors.add(new Color(251, 58, 4));
+ laneColors.add(new Color(63, 64, 255));
+ laneColors.add(new Color(27, 194, 130));
+ laneColors.add(new Color(0, 104, 183));
+
+ colors = new LinkedList<Color>();
+ repackColors();
+ }
+
+ private void repackColors() {
+ colors.addAll(laneColors);
+ }
+
+ @Override
+ protected Lane createLane() {
+ final Lane lane = new Lane();
+ if (colors.isEmpty())
+ repackColors();
+ lane.color = colors.removeFirst();
+ return lane;
+ }
+
+ @Override
+ protected void recycleLane(final Lane lane) {
+ colors.add(lane.color);
+ }
+ }
+
+ static class Lane extends PlotLane {
+
+ private static final long serialVersionUID = 1L;
+
+ Color color;
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o) && color.equals(((Lane)o).color);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() ^ color.hashCode();
+ }
+ }
+
+ class LanesRenderer extends AbstractPlotRenderer<Lane, Color> implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ final Color commitDotFill = new Color(220, 220, 220);
+
+ final Color commitDotOutline = new Color(110, 110, 110);
+
+ transient Graphics2D g;
+
+ void paint(Graphics in, PlotCommit<Lane> commit, int h, int w) {
+ g = (Graphics2D) in.create();
+ try {
+ if (commit != null)
+ paintCommit(commit, h);
+ } finally {
+ g.dispose();
+ g = null;
+ }
+ }
+
+ @Override
+ protected void drawLine(Color color, int x1, int y1, int x2, int y2, int width) {
+ if (y1 == y2) {
+ x1 -= width / 2;
+ x2 -= width / 2;
+ } else if (x1 == x2) {
+ y1 -= width / 2;
+ y2 -= width / 2;
+ }
+
+ g.setColor(color);
+ g.setStroke(stroke(width));
+ g.drawLine(x1, y1, x2, y2);
+ }
+
+ @Override
+ protected void drawCommitDot(int x, int y, int w, int h) {
+ g.setColor(commitDotFill);
+ g.setStroke(strokeCache[2]);
+ g.fillOval(x + 2, y + 1, w - 2, h - 2);
+ g.setColor(commitDotOutline);
+ g.drawOval(x + 2, y + 1, w - 2, h - 2);
+ }
+
+ @Override
+ protected void drawBoundaryDot(int x, int y, int w, int h) {
+ drawCommitDot(x, y, w, h);
+ }
+
+ @Override
+ protected void drawText(String msg, int x, int y) {
+ }
+
+ @Override
+ protected Color laneColor(Lane myLane) {
+ return myLane != null ? myLane.color : Color.black;
+ }
+
+ @Override
+ protected int drawLabel(int x, int y, Ref ref) {
+ return 0;
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java new file mode 100644 index 00000000..f2064e3a --- /dev/null +++ b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java @@ -0,0 +1,123 @@ +/*
+ * 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.servlet;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.ISessionManager;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * The DownloadZipFilter is an AccessRestrictionFilter which ensures that zip
+ * requests for view-restricted repositories have proper authentication
+ * credentials and are authorized.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class DownloadZipFilter extends AccessRestrictionFilter {
+
+ @Inject
+ public DownloadZipFilter(
+ IRuntimeManager runtimeManager,
+ ISessionManager sessionManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, sessionManager, repositoryManager);
+ }
+
+ /**
+ * Extract the repository name from the url.
+ *
+ * @param url
+ * @return repository name
+ */
+ @Override
+ protected String extractRepositoryName(String url) {
+ int a = url.indexOf("r=");
+ String repository = url.substring(a + 2);
+ if (repository.indexOf('&') > -1) {
+ repository = repository.substring(0, repository.indexOf('&'));
+ }
+ return repository;
+ }
+
+ /**
+ * Analyze the url and returns the action of the request.
+ *
+ * @param url
+ * @return action of the request
+ */
+ @Override
+ protected String getUrlRequestAction(String url) {
+ return "DOWNLOAD";
+ }
+
+ /**
+ * Determine if a non-existing repository can be created using this filter.
+ *
+ * @return true if the filter allows repository creation
+ */
+ @Override
+ protected boolean isCreationAllowed() {
+ return false;
+ }
+
+ /**
+ * Determine if the action may be executed on the repository.
+ *
+ * @param repository
+ * @param action
+ * @return true if the action may be performed
+ */
+ @Override
+ protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ return true;
+ }
+
+ /**
+ * Determine if the repository requires authentication.
+ *
+ * @param repository
+ * @param action
+ * @return true if authentication required
+ */
+ @Override
+ protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
+ }
+
+ /**
+ * Determine if the user can access the repository and perform the specified
+ * action.
+ *
+ * @param repository
+ * @param user
+ * @param action
+ * @return true if user may execute the action on the repository
+ */
+ @Override
+ protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
+ return user.canView(repository);
+ }
+
+}
diff --git a/src/main/java/com/gitblit/servlet/DownloadZipServlet.java b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java new file mode 100644 index 00000000..d26f73ea --- /dev/null +++ b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java @@ -0,0 +1,236 @@ +/*
+ * 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.servlet;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.Date;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Keys.web;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.CompressionUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Streams out a zip file from the specified repository for any tree path at any
+ * revision.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class DownloadZipServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private transient Logger logger = LoggerFactory.getLogger(DownloadZipServlet.class);
+
+ private final IStoredSettings settings;
+
+ private final IRepositoryManager repositoryManager;
+
+ public static enum Format {
+ zip(".zip"), tar(".tar"), gz(".tar.gz"), xz(".tar.xz"), bzip2(".tar.bzip2");
+
+ public final String extension;
+
+ Format(String ext) {
+ this.extension = ext;
+ }
+
+ public static Format fromName(String name) {
+ for (Format format : values()) {
+ if (format.name().equalsIgnoreCase(name)) {
+ return format;
+ }
+ }
+ return zip;
+ }
+ }
+
+ @Inject
+ public DownloadZipServlet(
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager) {
+
+ super();
+ this.settings = runtimeManager.getSettings();
+ this.repositoryManager = repositoryManager;
+ }
+
+ /**
+ * Returns an url to this servlet for the specified parameters.
+ *
+ * @param baseURL
+ * @param repository
+ * @param objectId
+ * @param path
+ * @param format
+ * @return an url
+ */
+ public static String asLink(String baseURL, String repository, String objectId, String path, Format format) {
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+ baseURL = baseURL.substring(0, baseURL.length() - 1);
+ }
+ return baseURL + Constants.ZIP_PATH + "?r=" + repository
+ + (path == null ? "" : ("&p=" + path))
+ + (objectId == null ? "" : ("&h=" + objectId))
+ + (format == null ? "" : ("&format=" + format.name()));
+ }
+
+ /**
+ * Creates a zip stream from the repository of the requested data.
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ private void processRequest(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ if (!settings.getBoolean(Keys.web.allowZipDownloads, true)) {
+ logger.warn("Zip downloads are disabled");
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ Format format = Format.zip;
+ String repository = request.getParameter("r");
+ String basePath = request.getParameter("p");
+ String objectId = request.getParameter("h");
+ String f = request.getParameter("format");
+ if (!StringUtils.isEmpty(f)) {
+ format = Format.fromName(f);
+ }
+
+ try {
+ String name = repository;
+ if (name.indexOf('/') > -1) {
+ name = name.substring(name.lastIndexOf('/') + 1);
+ }
+ name = StringUtils.stripDotGit(name);
+
+ if (!StringUtils.isEmpty(basePath)) {
+ name += "-" + basePath.replace('/', '_');
+ }
+ if (!StringUtils.isEmpty(objectId)) {
+ name += "-" + objectId;
+ }
+
+ Repository r = repositoryManager.getRepository(repository);
+ if (r == null) {
+ if (repositoryManager.isCollectingGarbage(repository)) {
+ error(response, MessageFormat.format("# Error\nGitblit is busy collecting garbage in {0}", repository));
+ return;
+ } else {
+ error(response, MessageFormat.format("# Error\nFailed to find repository {0}", repository));
+ return;
+ }
+ }
+ RevCommit commit = JGitUtils.getCommit(r, objectId);
+ if (commit == null) {
+ error(response, MessageFormat.format("# Error\nFailed to find commit {0}", objectId));
+ r.close();
+ return;
+ }
+ Date date = JGitUtils.getCommitDate(commit);
+
+ String contentType = "application/octet-stream";
+ response.setContentType(contentType + "; charset=" + response.getCharacterEncoding());
+ response.setHeader("Content-Disposition", "attachment; filename=\"" + name + format.extension + "\"");
+ response.setDateHeader("Last-Modified", date.getTime());
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Pragma", "no-cache");
+ response.setDateHeader("Expires", 0);
+
+ try {
+ switch (format) {
+ case zip:
+ CompressionUtils.zip(r, basePath, objectId, response.getOutputStream());
+ break;
+ case tar:
+ CompressionUtils.tar(r, basePath, objectId, response.getOutputStream());
+ break;
+ case gz:
+ CompressionUtils.gz(r, basePath, objectId, response.getOutputStream());
+ break;
+ case xz:
+ CompressionUtils.xz(r, basePath, objectId, response.getOutputStream());
+ break;
+ case bzip2:
+ CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream());
+ break;
+ }
+
+ response.flushBuffer();
+ } catch (IOException t) {
+ String message = t.getMessage() == null ? "" : t.getMessage().toLowerCase();
+ if (message.contains("reset") || message.contains("broken pipe")) {
+ logger.error("Client aborted zip download: " + message);
+ } else {
+ logger.error("Failed to write attachment to client", t);
+ }
+ } catch (Throwable t) {
+ logger.error("Failed to write attachment to client", t);
+ }
+
+ // close the repository
+ r.close();
+ } catch (Throwable t) {
+ logger.error("Failed to write attachment to client", t);
+ }
+ }
+
+ private void error(HttpServletResponse response, String mkd) throws ServletException,
+ IOException, ParseException {
+ String content = MarkdownUtils.transformMarkdown(mkd);
+ response.setContentType("text/html; charset=" + Constants.ENCODING);
+ response.getWriter().write(content);
+ }
+
+ @Override
+ protected void doPost(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doGet(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ processRequest(request, response);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java new file mode 100644 index 00000000..d690fd2c --- /dev/null +++ b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013 Laurens Vrijnsen + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; + +import javax.inject.Inject; +import javax.inject.Singleton; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Keys.web; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.UserModel; + +/** + * This filter enforces authentication via HTTP Basic Authentication, if the settings indicate so. + * It looks at the settings "web.authenticateViewPages" and "web.enforceHttpBasicAuthentication"; if + * both are true, any unauthorized access will be met with a HTTP Basic Authentication header. + * + * @author Laurens Vrijnsen + * + */ +@Singleton +public class EnforceAuthenticationFilter implements Filter { + + protected transient Logger logger = LoggerFactory.getLogger(getClass()); + + private final IStoredSettings settings; + + private final ISessionManager sessionManager; + + @Inject + public EnforceAuthenticationFilter( + IRuntimeManager runtimeManager, + ISessionManager sessionManager) { + + super(); + this.settings = runtimeManager.getSettings(); + this.sessionManager = sessionManager; + } + + /* + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + /* + * This does the actual filtering: is the user authenticated? If not, enforce HTTP authentication (401) + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + Boolean mustForceAuth = settings.getBoolean(Keys.web.authenticateViewPages, false) + && settings.getBoolean(Keys.web.enforceHttpBasicAuthentication, false); + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + UserModel user = sessionManager.authenticate(httpRequest); + + if (mustForceAuth && (user == null)) { + // not authenticated, enforce now: + logger.debug(MessageFormat.format("EnforceAuthFilter: user not authenticated for URL {0}!", request.toString())); + String challenge = MessageFormat.format("Basic realm=\"{0}\"", settings.getString(Keys.web.siteName, "")); + httpResponse.setHeader("WWW-Authenticate", challenge); + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + + } else { + // user is authenticated, or don't care, continue handling + chain.doFilter(request, response); + } + } + + + /* + * @see javax.servlet.Filter#destroy() + */ + @Override + public void destroy() { + } +} diff --git a/src/main/java/com/gitblit/servlet/FederationServlet.java b/src/main/java/com/gitblit/servlet/FederationServlet.java new file mode 100644 index 00000000..e86e5d66 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/FederationServlet.java @@ -0,0 +1,296 @@ +/*
+ * 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.servlet;
+
+import java.io.File;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.http.HttpServletResponse;
+
+import com.gitblit.Constants;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Keys.federation;
+import com.gitblit.Keys.git;
+import com.gitblit.Keys.groovy;
+import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+
+/**
+ * Handles federation requests.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class FederationServlet extends JsonServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private final IStoredSettings settings;
+
+ private final IUserManager userManager;
+
+ private final IRepositoryManager repositoryManager;
+
+ private final IFederationManager federationManager;
+
+ @Inject
+ public FederationServlet(
+ IRuntimeManager runtimeManager,
+ IUserManager userManager,
+ IRepositoryManager repositoryManager,
+ IFederationManager federationManager) {
+
+ super();
+ this.settings = runtimeManager.getSettings();
+ this.userManager = userManager;
+ this.repositoryManager = repositoryManager;
+ this.federationManager = federationManager;
+ }
+
+ /**
+ * Processes a federation request.
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @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 {
+
+ FederationRequest reqType = FederationRequest.fromName(request.getParameter("req"));
+ logger.info(MessageFormat.format("Federation {0} request from {1}", reqType,
+ request.getRemoteAddr()));
+
+ if (FederationRequest.POKE.equals(reqType)) {
+ // Gitblit always responds to POKE requests to verify a connection
+ logger.info("Received federation POKE from " + request.getRemoteAddr());
+ return;
+ }
+
+ if (!settings.getBoolean(Keys.git.enableGitServlet, true)) {
+ logger.warn(Keys.git.enableGitServlet + " must be set TRUE for federation requests.");
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ String uuid = settings.getString(Keys.federation.passphrase, "");
+ if (StringUtils.isEmpty(uuid)) {
+ logger.warn(Keys.federation.passphrase
+ + " is not properly set! Federation request denied.");
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ if (FederationRequest.PROPOSAL.equals(reqType)) {
+ // Receive a gitblit federation proposal
+ FederationProposal proposal = deserialize(request, response, FederationProposal.class);
+ if (proposal == null) {
+ return;
+ }
+
+ // reject proposal, if not receipt prohibited
+ if (!settings.getBoolean(Keys.federation.allowProposals, false)) {
+ logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}",
+ proposal.tokenType.name(), proposal.url));
+ response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+
+ // poke the origin Gitblit instance that is proposing federation
+ boolean poked = false;
+ try {
+ poked = FederationUtils.poke(proposal.url);
+ } catch (Exception e) {
+ logger.error("Failed to poke origin", e);
+ }
+ if (!poked) {
+ logger.error(MessageFormat.format("Failed to send federation poke to {0}",
+ proposal.url));
+ response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+ return;
+ }
+
+ String url = HttpUtils.getGitblitURL(request);
+ federationManager.submitFederationProposal(proposal, url);
+ logger.info(MessageFormat.format(
+ "Submitted {0} federation proposal to pull {1} repositories from {2}",
+ proposal.tokenType.name(), proposal.repositories.size(), proposal.url));
+ response.setStatus(HttpServletResponse.SC_OK);
+ return;
+ }
+
+ if (FederationRequest.STATUS.equals(reqType)) {
+ // Receive a gitblit federation status acknowledgment
+ String remoteId = StringUtils.decodeFromHtml(request.getParameter("url"));
+ String identification = MessageFormat.format("{0} ({1})", remoteId,
+ request.getRemoteAddr());
+
+ // deserialize the status data
+ FederationModel results = deserialize(request, response, FederationModel.class);
+ if (results == null) {
+ return;
+ }
+
+ // setup the last and netx pull dates
+ results.lastPull = new Date();
+ int mins = TimeUtils.convertFrequencyToMinutes(results.frequency);
+ results.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L));
+
+ // acknowledge the receipt of status
+ federationManager.acknowledgeFederationStatus(identification, results);
+ logger.info(MessageFormat.format(
+ "Received status of {0} federated repositories from {1}", results
+ .getStatusList().size(), identification));
+ response.setStatus(HttpServletResponse.SC_OK);
+ return;
+ }
+
+ // Determine the federation tokens for this gitblit instance
+ String token = request.getParameter("token");
+ List<String> tokens = federationManager.getFederationTokens();
+ if (!tokens.contains(token)) {
+ logger.warn(MessageFormat.format(
+ "Received Federation token ''{0}'' does not match the server tokens", token));
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ Object result = null;
+ if (FederationRequest.PULL_REPOSITORIES.equals(reqType)) {
+ String gitblitUrl = HttpUtils.getGitblitURL(request);
+ result = federationManager.getRepositories(gitblitUrl, token);
+ } else {
+ if (FederationRequest.PULL_SETTINGS.equals(reqType)) {
+ // pull settings
+ if (!federationManager.validateFederationRequest(reqType, token)) {
+ // invalid token to pull users or settings
+ logger.warn(MessageFormat.format(
+ "Federation token from {0} not authorized to pull SETTINGS",
+ request.getRemoteAddr()));
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ Map<String, String> map = new HashMap<String, String>();
+ List<String> keys = settings.getAllKeys(null);
+ for (String key : keys) {
+ map.put(key, settings.getString(key, ""));
+ }
+ result = map;
+ } else if (FederationRequest.PULL_USERS.equals(reqType)) {
+ // pull users
+ if (!federationManager.validateFederationRequest(reqType, token)) {
+ // invalid token to pull users or settings
+ logger.warn(MessageFormat.format(
+ "Federation token from {0} not authorized to pull USERS",
+ request.getRemoteAddr()));
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ List<String> usernames = userManager.getAllUsernames();
+ List<UserModel> users = new ArrayList<UserModel>();
+ for (String username : usernames) {
+ UserModel user = userManager.getUserModel(username);
+ if (!user.excludeFromFederation) {
+ users.add(user);
+ }
+ }
+ result = users;
+ } else if (FederationRequest.PULL_TEAMS.equals(reqType)) {
+ // pull teams
+ if (!federationManager.validateFederationRequest(reqType, token)) {
+ // invalid token to pull teams
+ logger.warn(MessageFormat.format(
+ "Federation token from {0} not authorized to pull TEAMS",
+ request.getRemoteAddr()));
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ List<String> teamnames = userManager.getAllTeamNames();
+ List<TeamModel> teams = new ArrayList<TeamModel>();
+ for (String teamname : teamnames) {
+ TeamModel user = userManager.getTeamModel(teamname);
+ teams.add(user);
+ }
+ result = teams;
+ } else if (FederationRequest.PULL_SCRIPTS.equals(reqType)) {
+ // pull scripts
+ if (!federationManager.validateFederationRequest(reqType, token)) {
+ // invalid token to pull script
+ logger.warn(MessageFormat.format(
+ "Federation token from {0} not authorized to pull SCRIPTS",
+ request.getRemoteAddr()));
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ Map<String, String> scripts = new HashMap<String, String>();
+
+ Set<String> names = new HashSet<String>();
+ names.addAll(settings.getStrings(Keys.groovy.preReceiveScripts));
+ names.addAll(settings.getStrings(Keys.groovy.postReceiveScripts));
+ for (TeamModel team : userManager.getAllTeams()) {
+ names.addAll(team.preReceiveScripts);
+ names.addAll(team.postReceiveScripts);
+ }
+ File scriptsFolder = repositoryManager.getHooksFolder();
+ for (String name : names) {
+ File file = new File(scriptsFolder, name);
+ if (!file.exists() && !file.getName().endsWith(".groovy")) {
+ file = new File(scriptsFolder, name + ".groovy");
+ }
+ if (file.exists()) {
+ // read the script
+ String content = FileUtils.readContent(file, "\n");
+ scripts.put(name, content);
+ } else {
+ // missing script?!
+ logger.warn(MessageFormat.format("Failed to find push script \"{0}\"", name));
+ }
+ }
+ result = scripts;
+ }
+ }
+
+ // send the result of the request
+ serialize(response, result);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/GitFilter.java b/src/main/java/com/gitblit/servlet/GitFilter.java new file mode 100644 index 00000000..f39d68fd --- /dev/null +++ b/src/main/java/com/gitblit/servlet/GitFilter.java @@ -0,0 +1,270 @@ +/*
+ * 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.servlet;
+
+import java.text.MessageFormat;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlitException;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Keys.git;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.ISessionManager;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * The GitFilter is an AccessRestrictionFilter which ensures that Git client
+ * requests for push, clone, or view restricted repositories are authenticated
+ * and authorized.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class GitFilter extends AccessRestrictionFilter {
+
+ protected static final String gitReceivePack = "/git-receive-pack";
+
+ protected static final String gitUploadPack = "/git-upload-pack";
+
+ protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
+ "/objects" };
+
+ private final IStoredSettings settings;
+
+ @Inject
+ public GitFilter(
+ IRuntimeManager runtimeManager,
+ ISessionManager sessionManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, sessionManager, repositoryManager);
+ this.settings = runtimeManager.getSettings();
+ }
+
+ /**
+ * Extract the repository name from the url.
+ *
+ * @param cloneUrl
+ * @return repository name
+ */
+ public static String getRepositoryName(String value) {
+ String repository = value;
+ // get the repository name from the url by finding a known url suffix
+ for (String urlSuffix : suffixes) {
+ if (repository.indexOf(urlSuffix) > -1) {
+ repository = repository.substring(0, repository.indexOf(urlSuffix));
+ }
+ }
+ return repository;
+ }
+
+ /**
+ * Extract the repository name from the url.
+ *
+ * @param url
+ * @return repository name
+ */
+ @Override
+ protected String extractRepositoryName(String url) {
+ return GitFilter.getRepositoryName(url);
+ }
+
+ /**
+ * Analyze the url and returns the action of the request. Return values are
+ * either "/git-receive-pack" or "/git-upload-pack".
+ *
+ * @param serverUrl
+ * @return action of the request
+ */
+ @Override
+ protected String getUrlRequestAction(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;
+ } else {
+ return gitUploadPack;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determine if a non-existing repository can be created using this filter.
+ *
+ * @return true if the server allows repository creation on-push
+ */
+ @Override
+ protected boolean isCreationAllowed() {
+ return settings.getBoolean(Keys.git.allowCreateOnPush, true);
+ }
+
+ /**
+ * Determine if the repository can receive pushes.
+ *
+ * @param repository
+ * @param action
+ * @return true if the action may be performed
+ */
+ @Override
+ protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ // the log here has been moved into ReceiveHook to provide clients with
+ // error messages
+ return true;
+ }
+
+ @Override
+ protected boolean requiresClientCertificate() {
+ return settings.getBoolean(Keys.git.requiresClientCertificate, false);
+ }
+
+ /**
+ * Determine if the repository requires authentication.
+ *
+ * @param repository
+ * @param action
+ * @return true if authentication required
+ */
+ @Override
+ protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ if (gitUploadPack.equals(action)) {
+ // send to client
+ return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
+ } else if (gitReceivePack.equals(action)) {
+ // receive from client
+ return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+ }
+ return false;
+ }
+
+ /**
+ * Determine if the user can access the repository and perform the specified
+ * action.
+ *
+ * @param repository
+ * @param user
+ * @param action
+ * @return true if user may execute the action on the repository
+ */
+ @Override
+ protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
+ if (!settings.getBoolean(Keys.git.enableGitServlet, true)) {
+ // Git Servlet disabled
+ return false;
+ }
+ if (action.equals(gitReceivePack)) {
+ // Push request
+ if (user.canPush(repository)) {
+ 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 (action.equals(gitUploadPack)) {
+ // Clone request
+ if (user.canClone(repository)) {
+ 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;
+ }
+
+ /**
+ * An authenticated user with the CREATE role can create a repository on
+ * push.
+ *
+ * @param user
+ * @param repository
+ * @param action
+ * @return the repository model, if it is created, null otherwise
+ */
+ @Override
+ protected RepositoryModel createRepository(UserModel user, String repository, String action) {
+ boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action);
+ if (isPush) {
+ if (user.canCreate(repository)) {
+ // user is pushing to a new repository
+ // validate name
+ if (repository.startsWith("../")) {
+ logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
+ return null;
+ }
+ if (repository.contains("/../")) {
+ logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
+ return null;
+ }
+
+ // confirm valid characters in repository name
+ Character c = StringUtils.findInvalidCharacter(repository);
+ if (c != null) {
+ logger.error(MessageFormat.format("Invalid character '{0}' in repository name {1}!", c, repository));
+ return null;
+ }
+
+ // create repository
+ RepositoryModel model = new RepositoryModel();
+ model.name = repository;
+ model.addOwner(user.username);
+ model.projectPath = StringUtils.getFirstPathElement(repository);
+ if (model.isUsersPersonalRepository(user.username)) {
+ // personal repository, default to private for user
+ model.authorizationControl = AuthorizationControl.NAMED;
+ model.accessRestriction = AccessRestrictionType.VIEW;
+ } else {
+ // common repository, user default server settings
+ model.authorizationControl = AuthorizationControl.fromName(settings.getString(Keys.git.defaultAuthorizationControl, ""));
+ model.accessRestriction = AccessRestrictionType.fromName(settings.getString(Keys.git.defaultAccessRestriction, "PUSH"));
+ }
+
+ // create the repository
+ try {
+ repositoryManager.updateRepositoryModel(model.name, model, true);
+ logger.info(MessageFormat.format("{0} created {1} ON-PUSH", user.username, model.name));
+ return repositoryManager.getRepositoryModel(model.name);
+ } catch (GitBlitException e) {
+ logger.error(MessageFormat.format("{0} failed to create repository {1} ON-PUSH!", user.username, model.name), e);
+ }
+ } else {
+ logger.warn(MessageFormat.format("{0} is not permitted to create repository {1} ON-PUSH!", user.username, repository));
+ }
+ }
+
+ // repository could not be created or action was not a push
+ return null;
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/GitblitContext.java b/src/main/java/com/gitblit/servlet/GitblitContext.java new file mode 100644 index 00000000..73250121 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/GitblitContext.java @@ -0,0 +1,432 @@ +/* + * 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.servlet; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.servlet.ServletContext; +import javax.servlet.annotation.WebListener; + +import com.gitblit.Constants; +import com.gitblit.DaggerModule; +import com.gitblit.FileSettings; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.WebXmlSettings; +import com.gitblit.dagger.DaggerContextListener; +import com.gitblit.git.GitServlet; +import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IGitblitManager; +import com.gitblit.manager.IManager; +import com.gitblit.manager.INotificationManager; +import com.gitblit.manager.IProjectManager; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IServicesManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.manager.IUserManager; +import com.gitblit.utils.ContainerUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.GitblitWicketFilter; + +import dagger.ObjectGraph; + +/** + * This class is the main entry point for the entire webapp. It is a singleton + * created manually by Gitblit GO or dynamically by the WAR/Express servlet + * container. This class instantiates and starts all managers followed by + * instantiating and registering all servlets and filters. + * + * Leveraging Servlet 3 and Dagger static dependency injection allows Gitblit to + * be modular and completely code-driven rather then relying on the fragility of + * a web.xml descriptor and the static & monolithic design previously used. + * + * @author James Moger + * + */ +@WebListener +public class GitblitContext extends DaggerContextListener { + + private static GitblitContext gitblit; + + private final List<IManager> managers = new ArrayList<IManager>(); + + private final IStoredSettings goSettings; + + private final File goBaseFolder; + + /** + * Construct a Gitblit WAR/Express context. + */ + public GitblitContext() { + this.goSettings = null; + this.goBaseFolder = null; + gitblit = this; + } + + /** + * Construct a Gitblit GO context. + * + * @param settings + * @param baseFolder + */ + public GitblitContext(IStoredSettings settings, File baseFolder) { + this.goSettings = settings; + this.goBaseFolder = baseFolder; + gitblit = this; + } + + /** + * This method is only used for unit and integration testing. + * + * @param managerClass + * @return a manager + */ + @SuppressWarnings("unchecked") + public static <X extends IManager> X getManager(Class<X> managerClass) { + for (IManager manager : gitblit.managers) { + if (managerClass.isAssignableFrom(manager.getClass())) { + return (X) manager; + } + } + return null; + } + + /** + * Returns Gitblit's Dagger injection modules. + */ + @Override + protected Object [] getModules() { + return new Object [] { new DaggerModule() }; + } + + /** + * Prepare runtime settings and start all manager instances. + */ + @Override + protected void beforeServletInjection(ServletContext context) { + ObjectGraph injector = getInjector(context); + + // create the runtime settings object + IStoredSettings runtimeSettings = injector.get(IStoredSettings.class); + final File baseFolder; + + if (goSettings != null) { + // Gitblit GO + baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings); + } else { + // servlet container + WebXmlSettings webxmlSettings = new WebXmlSettings(context); + String contextRealPath = context.getRealPath("/"); + File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null; + + if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) { + // RedHat OpenShift + baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings); + } else { + // standard WAR + baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings); + } + + // Test for Tomcat forward-slash/%2F issue and auto-adjust settings + ContainerUtils.CVE_2007_0450.test(runtimeSettings); + } + + // Manually configure IRuntimeManager + logManager(IRuntimeManager.class); + IRuntimeManager runtime = injector.get(IRuntimeManager.class); + runtime.setBaseFolder(baseFolder); + runtime.getStatus().isGO = goSettings != null; + runtime.getStatus().servletContainer = context.getServerInfo(); + runtime.start(); + managers.add(runtime); + + // start all other managers + startManager(injector, INotificationManager.class); + startManager(injector, IUserManager.class); + startManager(injector, ISessionManager.class); + startManager(injector, IRepositoryManager.class); + startManager(injector, IProjectManager.class); + startManager(injector, IGitblitManager.class); + startManager(injector, IFederationManager.class); + startManager(injector, IServicesManager.class); + + logger.info(""); + logger.info("All managers started."); + logger.info(""); + } + + protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) { + logManager(clazz); + X x = injector.get(clazz); + x.start(); + managers.add(x); + return x; + } + + protected void logManager(Class<? extends IManager> clazz) { + logger.info(""); + logger.info("----[{}]----", clazz.getName()); + } + + /** + * Instantiate and inject all filters and servlets into the container using + * the servlet 3 specification. + */ + @Override + protected void injectServlets(ServletContext context) { + // access restricted servlets + serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class); + serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class); + serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class); + serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class); + serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class); + + // servlets + serve(context, Constants.FEDERATION_PATH, FederationServlet.class); + serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class); + serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class); + file(context, "/robots.txt", RobotsTxtServlet.class); + file(context, "/logo.png", LogoServlet.class); + + // optional force basic authentication + filter(context, "/*", EnforceAuthenticationFilter.class, null); + + // Wicket + String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ","); + Map<String, String> params = new HashMap<String, String>(); + params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*"); + params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore); + filter(context, "/*", GitblitWicketFilter.class, params); + } + + /** + * Gitblit is being shutdown either because the servlet container is + * shutting down or because the servlet container is re-deploying Gitblit. + */ + @Override + protected void destroyContext(ServletContext context) { + logger.info("Gitblit context destroyed by servlet container."); + for (IManager manager : managers) { + logger.debug("stopping {}", manager.getClass().getSimpleName()); + manager.stop(); + } + } + + /** + * Configures Gitblit GO + * + * @param context + * @param settings + * @param baseFolder + * @param runtimeSettings + * @return the base folder + */ + protected File configureGO( + ServletContext context, + IStoredSettings goSettings, + File goBaseFolder, + IStoredSettings runtimeSettings) { + + logger.debug("configuring Gitblit GO"); + + // merge the stored settings into the runtime settings + // + // if runtimeSettings is also a FileSettings w/o a specified target file, + // the target file for runtimeSettings is set to "localSettings". + runtimeSettings.merge(goSettings); + File base = goBaseFolder; + return base; + } + + + /** + * Configures a standard WAR instance of Gitblit. + * + * @param context + * @param webxmlSettings + * @param contextFolder + * @param runtimeSettings + * @return the base folder + */ + protected File configureWAR( + ServletContext context, + WebXmlSettings webxmlSettings, + File contextFolder, + IStoredSettings runtimeSettings) { + + // Gitblit is running in a standard servlet container + logger.debug("configuring Gitblit WAR"); + logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>")); + + String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data"); + + if (path.contains(Constants.contextFolder$) && contextFolder == null) { + // warn about null contextFolder (issue-199) + logger.error(""); + logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!", + Constants.baseFolder, Constants.contextFolder$, context.getServerInfo())); + logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder)); + logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder)); + logger.error(""); + } + + try { + // try to lookup JNDI env-entry for the baseFolder + InitialContext ic = new InitialContext(); + Context env = (Context) ic.lookup("java:comp/env"); + String val = (String) env.lookup("baseFolder"); + if (!StringUtils.isEmpty(val)) { + path = val; + } + } catch (NamingException n) { + logger.error("Failed to get JNDI env-entry: " + n.getExplanation()); + } + + File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path); + base.mkdirs(); + + // try to extract the data folder resource to the baseFolder + File localSettings = new File(base, "gitblit.properties"); + if (!localSettings.exists()) { + extractResources(context, "/WEB-INF/data/", base); + } + + // delegate all config to baseFolder/gitblit.properties file + FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); + + // merge the stored settings into the runtime settings + // + // if runtimeSettings is also a FileSettings w/o a specified target file, + // the target file for runtimeSettings is set to "localSettings". + runtimeSettings.merge(fileSettings); + + return base; + } + + /** + * Configures an OpenShift instance of Gitblit. + * + * @param context + * @param webxmlSettings + * @param contextFolder + * @param runtimeSettings + * @return the base folder + */ + private File configureExpress( + ServletContext context, + WebXmlSettings webxmlSettings, + File contextFolder, + IStoredSettings runtimeSettings) { + + // Gitblit is running in OpenShift/JBoss + logger.debug("configuring Gitblit Express"); + String openShift = System.getenv("OPENSHIFT_DATA_DIR"); + File base = new File(openShift); + logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath()); + + // Copy the included scripts to the configured groovy folder + String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"); + File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path); + if (!localScripts.exists()) { + File warScripts = new File(contextFolder, "/WEB-INF/data/groovy"); + if (!warScripts.equals(localScripts)) { + try { + com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles()); + } catch (IOException e) { + logger.error(MessageFormat.format( + "Failed to copy included Groovy scripts from {0} to {1}", + warScripts, localScripts)); + } + } + } + + // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty) + runtimeSettings.merge(webxmlSettings); + + // settings are to be stored in openshift/gitblit.properties + File localSettings = new File(base, "gitblit.properties"); + FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); + + // merge the stored settings into the runtime settings + // + // if runtimeSettings is also a FileSettings w/o a specified target file, + // the target file for runtimeSettings is set to "localSettings". + runtimeSettings.merge(fileSettings); + + return base; + } + + protected void extractResources(ServletContext context, String path, File toDir) { + for (String resource : context.getResourcePaths(path)) { + // extract the resource to the directory if it does not exist + File f = new File(toDir, resource.substring(path.length())); + if (!f.exists()) { + InputStream is = null; + OutputStream os = null; + try { + if (resource.charAt(resource.length() - 1) == '/') { + // directory + f.mkdirs(); + extractResources(context, resource, f); + } else { + // file + f.getParentFile().mkdirs(); + is = context.getResourceAsStream(resource); + os = new FileOutputStream(f); + byte [] buffer = new byte[4096]; + int len = 0; + while ((len = is.read(buffer)) > -1) { + os.write(buffer, 0, len); + } + } + } catch (FileNotFoundException e) { + logger.error("Failed to find resource \"" + resource + "\"", e); + } catch (IOException e) { + logger.error("Failed to copy resource \"" + resource + "\" to " + f, e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // ignore + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e) { + // ignore + } + } + } + } + } + } +} diff --git a/src/main/java/com/gitblit/servlet/InjectionContextListener.java b/src/main/java/com/gitblit/servlet/InjectionContextListener.java new file mode 100644 index 00000000..b0e10985 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/InjectionContextListener.java @@ -0,0 +1,241 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletRegistration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Injection context listener instantiates and injects servlets, filters, and + * anything else you might want into a servlet context. This class provides + * convenience methods for servlet & filter registration and also tracks + * registered paths. + * + * @author James Moger + * + */ +public abstract class InjectionContextListener implements ServletContextListener { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private final List<String> registeredPaths = new ArrayList<String>(); + + protected final List<String> getRegisteredPaths() { + return registeredPaths; + } + + /** + * Hook for subclasses to manipulate context initialization before + * standard initialization procedure. + * + * @param context + */ + protected void beforeServletInjection(ServletContext context) { + // NOOP + } + + /** + * Hook for subclasses to instantiate and inject servlets and filters + * into the servlet context. + * + * @param context + */ + protected abstract void injectServlets(ServletContext context); + + /** + * Hook for subclasses to manipulate context initialization after + * servlet registration. + * + * @param context + */ + protected void afterServletInjection(ServletContext context) { + // NOOP + } + + /** + * Configure Gitblit from the web.xml, if no configuration has already been + * specified. + * + * @see ServletContextListener.contextInitialize(ServletContextEvent) + */ + @Override + public final void contextInitialized(ServletContextEvent contextEvent) { + ServletContext context = contextEvent.getServletContext(); + beforeServletInjection(context); + injectServlets(context); + afterServletInjection(context); + } + + + /** + * Registers a file path. + * + * @param context + * @param file + * @param servletClass + */ + protected void file(ServletContext context, String file, Class<? extends Servlet> servletClass) { + file(context, file, servletClass, null); + } + + /** + * Registers a file path with init parameters. + * + * @param context + * @param file + * @param servletClass + * @param initParams + */ + protected void file(ServletContext context, String file, Class<? extends Servlet> servletClass, Map<String, String> initParams) { + Servlet servlet = instantiate(context, servletClass); + ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + file), servlet); + d.addMapping(file); + if (initParams != null) { + d.setInitParameters(initParams); + } + registeredPaths.add(file); + } + + /** + * Serves a path (trailing wildcard will be appended). + * + * @param context + * @param route + * @param servletClass + */ + protected void serve(ServletContext context, String route, Class<? extends Servlet> servletClass) { + serve(context, route, servletClass, (Class<Filter>) null); + } + + /** + * Serves a path (trailing wildcard will be appended) with init parameters. + * + * @param context + * @param route + * @param servletClass + * @param initParams + */ + protected void serve(ServletContext context, String route, Class<? extends Servlet> servletClass, Map<String, String> initParams) { + Servlet servlet = instantiate(context, servletClass); + ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + route), servlet); + d.addMapping(route + "*"); + if (initParams != null) { + d.setInitParameters(initParams); + } + registeredPaths.add(route); + } + + /** + * Serves a path (trailing wildcard will be appended) and also maps a filter + * to that path. + * + * @param context + * @param route + * @param servletClass + * @param filterClass + */ + protected void serve(ServletContext context, String route, Class<? extends Servlet> servletClass, Class<? extends Filter> filterClass) { + Servlet servlet = instantiate(context, servletClass); + ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + route), servlet); + d.addMapping(route + "*"); + if (filterClass != null) { + filter(context, route + "*", filterClass); + } + registeredPaths.add(route); + } + + /** + * Registers a path filter. + * + * @param context + * @param route + * @param filterClass + */ + protected void filter(ServletContext context, String route, Class<? extends Filter> filterClass) { + filter(context, route, filterClass, null); + } + + /** + * Registers a path filter with init parameters. + * + * @param context + * @param route + * @param filterClass + * @param initParams + */ + protected void filter(ServletContext context, String route, Class<? extends Filter> filterClass, Map<String, String> initParams) { + Filter filter = instantiate(context, filterClass); + FilterRegistration.Dynamic d = context.addFilter(sanitize(filterClass.getSimpleName() + route), filter); + d.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, route); + if (initParams != null) { + d.setInitParameters(initParams); + } + } + + /** + * Limit the generated servlet/filter names to alpha-numeric values with a + * handful of acceptable other characters. + * + * @param name + * @return a sanitized name + */ + protected String sanitize(String name) { + StringBuilder sb = new StringBuilder(); + for (char c : name.toCharArray()) { + if (Character.isLetterOrDigit(c)) { + sb.append(c); + } else if ('-' == c) { + sb.append(c); + } else if ('*' == c) { + sb.append("all"); + } else if ('.' == c) { + sb.append('.'); + } else { + sb.append('_'); + } + } + return sb.toString(); + } + + /** + * Instantiates an object. + * + * @param clazz + * @return the object + */ + protected <X> X instantiate(ServletContext context, Class<X> clazz) { + try { + return clazz.newInstance(); + } catch (Throwable t) { + logger.error(null, t); + } + return null; + } +} diff --git a/src/main/java/com/gitblit/servlet/JsonServlet.java b/src/main/java/com/gitblit/servlet/JsonServlet.java new file mode 100644 index 00000000..abc0f292 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/JsonServlet.java @@ -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.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.text.MessageFormat;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.utils.JsonUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Servlet class for interpreting json requests.
+ *
+ * @author James Moger
+ *
+ */
+public abstract class JsonServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ protected final int forbiddenCode = HttpServletResponse.SC_FORBIDDEN;
+
+ protected final int notAllowedCode = HttpServletResponse.SC_METHOD_NOT_ALLOWED;
+
+ protected final int failureCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+ protected final Logger logger;
+
+ public JsonServlet() {
+ super();
+ logger = LoggerFactory.getLogger(getClass());
+ }
+
+ /**
+ * Processes an gson request.
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ protected abstract void processRequest(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException;
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, java.io.IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processRequest(request, response);
+ }
+
+ protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response,
+ Class<X> clazz) throws IOException {
+ String json = readJson(request, response);
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+
+ X object = JsonUtils.fromJsonString(json.toString(), clazz);
+ return object;
+ }
+
+ protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response, Type type)
+ throws IOException {
+ String json = readJson(request, response);
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+
+ X object = JsonUtils.fromJsonString(json.toString(), type);
+ return object;
+ }
+
+ private String readJson(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ BufferedReader reader = request.getReader();
+ StringBuilder json = new StringBuilder();
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ json.append(line);
+ }
+ reader.close();
+
+ if (json.length() == 0) {
+ logger.error(MessageFormat.format("Failed to receive json data from {0}",
+ request.getRemoteAddr()));
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return null;
+ }
+ return json.toString();
+ }
+
+ protected void serialize(HttpServletResponse response, Object o) throws IOException {
+ if (o != null) {
+ // Send JSON response
+ String json = JsonUtils.toJsonString(o);
+ response.setCharacterEncoding(Constants.ENCODING);
+ response.setContentType("application/json");
+ response.getWriter().append(json);
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/LogoServlet.java b/src/main/java/com/gitblit/servlet/LogoServlet.java new file mode 100644 index 00000000..e91fad05 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/LogoServlet.java @@ -0,0 +1,107 @@ +/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.servlet;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.gitblit.Keys;
+import com.gitblit.Keys.web;
+import com.gitblit.manager.IRuntimeManager;
+
+/**
+ * Handles requests for logo.png
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class LogoServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final long lastModified = System.currentTimeMillis();
+
+ private final IRuntimeManager runtimeManager;
+
+ @Inject
+ public LogoServlet(IRuntimeManager runtimeManager) {
+ super();
+ this.runtimeManager = runtimeManager;
+ }
+
+ @Override
+ protected long getLastModified(HttpServletRequest req) {
+ File file = runtimeManager.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png");
+ if (file.exists()) {
+ return Math.max(lastModified, file.lastModified());
+ } else {
+ return lastModified;
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ InputStream is = null;
+ try {
+ String contentType = null;
+ File file = runtimeManager.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png");
+ if (file.exists()) {
+ // custom logo
+ ServletContext context = request.getSession().getServletContext();
+ contentType = context.getMimeType(file.getName());
+ response.setContentLength((int) file.length());
+ response.setDateHeader("Last-Modified", Math.max(lastModified, file.lastModified()));
+ is = new FileInputStream(file);
+ } else {
+ // default logo
+ response.setDateHeader("Last-Modified", lastModified);
+ is = getClass().getResourceAsStream("/logo.png");
+ }
+ if (contentType == null) {
+ contentType = "image/png";
+ }
+ response.setContentType(contentType);
+ response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+ OutputStream os = response.getOutputStream();
+ byte[] buf = new byte[4096];
+ int bytesRead = is.read(buf);
+ while (bytesRead != -1) {
+ os.write(buf, 0, bytesRead);
+ bytesRead = is.read(buf);
+ }
+ os.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/PagesFilter.java b/src/main/java/com/gitblit/servlet/PagesFilter.java new file mode 100644 index 00000000..23e7859f --- /dev/null +++ b/src/main/java/com/gitblit/servlet/PagesFilter.java @@ -0,0 +1,141 @@ +/*
+ * Copyright 2012 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.servlet;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.ISessionManager;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages
+ * requests for a view-restricted repository are authenticated and authorized.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class PagesFilter extends AccessRestrictionFilter {
+
+ @Inject
+ public PagesFilter(IRuntimeManager runtimeManager,
+ ISessionManager sessionManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, sessionManager, repositoryManager);
+ }
+
+ /**
+ * Extract the repository name from the url.
+ *
+ * @param url
+ * @return repository name
+ */
+ @Override
+ protected String extractRepositoryName(String url) {
+ // get the repository name from the url by finding a known url suffix
+ String repository = "";
+ Repository r = null;
+ int offset = 0;
+ while (r == null) {
+ int slash = url.indexOf('/', offset);
+ if (slash == -1) {
+ repository = url;
+ } else {
+ repository = url.substring(0, slash);
+ }
+ r = repositoryManager.getRepository(repository, false);
+ if (r == null) {
+ // try again
+ offset = slash + 1;
+ } else {
+ // close the repo
+ r.close();
+ }
+ if (repository.equals(url)) {
+ // either only repository in url or no repository found
+ break;
+ }
+ }
+ return repository;
+ }
+
+ /**
+ * Analyze the url and returns the action of the request.
+ *
+ * @param cloneUrl
+ * @return action of the request
+ */
+ @Override
+ protected String getUrlRequestAction(String suffix) {
+ return "VIEW";
+ }
+
+ /**
+ * Determine if a non-existing repository can be created using this filter.
+ *
+ * @return true if the filter allows repository creation
+ */
+ @Override
+ protected boolean isCreationAllowed() {
+ return false;
+ }
+
+ /**
+ * Determine if the action may be executed on the repository.
+ *
+ * @param repository
+ * @param action
+ * @return true if the action may be performed
+ */
+ @Override
+ protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ return true;
+ }
+
+ /**
+ * Determine if the repository requires authentication.
+ *
+ * @param repository
+ * @param action
+ * @return true if authentication required
+ */
+ @Override
+ protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
+ }
+
+ /**
+ * Determine if the user can access the repository and perform the specified
+ * action.
+ *
+ * @param repository
+ * @param user
+ * @param action
+ * @return true if user may execute the action on the repository
+ */
+ @Override
+ protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
+ return user.canView(repository);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/PagesServlet.java b/src/main/java/com/gitblit/servlet/PagesServlet.java new file mode 100644 index 00000000..6146f132 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/PagesServlet.java @@ -0,0 +1,318 @@ +/*
+ * Copyright 2012 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.servlet;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Keys.web;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.models.PathModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.ByteFormat;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.MarkupProcessor;
+import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
+
+/**
+ * Serves the content of a gh-pages branch.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class PagesServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private transient Logger logger = LoggerFactory.getLogger(PagesServlet.class);
+
+ private final IStoredSettings settings;
+
+ private final IRepositoryManager repositoryManager;
+
+ @Inject
+ public PagesServlet(
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager) {
+
+ super();
+ this.settings = runtimeManager.getSettings();
+ this.repositoryManager = repositoryManager;
+ }
+
+ /**
+ * Returns an url to this servlet for the specified parameters.
+ *
+ * @param baseURL
+ * @param repository
+ * @param path
+ * @return an url
+ */
+ public static String asLink(String baseURL, String repository, String path) {
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+ baseURL = baseURL.substring(0, baseURL.length() - 1);
+ }
+ return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
+ }
+
+ /**
+ * Retrieves the specified resource from the gh-pages branch of the
+ * repository.
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ private void processRequest(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String path = request.getPathInfo();
+ if (path.toLowerCase().endsWith(".git")) {
+ // forward to url with trailing /
+ // this is important for relative pages links
+ response.sendRedirect(request.getServletPath() + path + "/");
+ return;
+ }
+ if (path.charAt(0) == '/') {
+ // strip leading /
+ path = path.substring(1);
+ }
+
+ // determine repository and resource from url
+ String repository = "";
+ String resource = "";
+ Repository r = null;
+ int offset = 0;
+ while (r == null) {
+ int slash = path.indexOf('/', offset);
+ if (slash == -1) {
+ repository = path;
+ } else {
+ repository = path.substring(0, slash);
+ }
+ r = repositoryManager.getRepository(repository, false);
+ offset = slash + 1;
+ if (offset > 0) {
+ resource = path.substring(offset);
+ }
+ if (repository.equals(path)) {
+ // either only repository in url or no repository found
+ break;
+ }
+ }
+
+ ServletContext context = request.getSession().getServletContext();
+
+ try {
+ if (r == null) {
+ // repository not found!
+ String mkd = MessageFormat.format(
+ "# Error\nSorry, no valid **repository** specified in this url: {0}!",
+ repository);
+ error(response, mkd);
+ return;
+ }
+
+ // retrieve the content from the repository
+ RefModel pages = JGitUtils.getPagesBranch(r);
+ RevCommit commit = JGitUtils.getCommit(r, pages.getObjectId().getName());
+
+ if (commit == null) {
+ // branch not found!
+ String mkd = MessageFormat.format(
+ "# Error\nSorry, the repository {0} does not have a **gh-pages** branch!",
+ repository);
+ error(response, mkd);
+ r.close();
+ return;
+ }
+
+ MarkupProcessor processor = new MarkupProcessor(settings);
+ String [] encodings = settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]);
+
+ RevTree tree = commit.getTree();
+
+ String res = resource;
+ if (res.endsWith("/")) {
+ res = res.substring(0, res.length() - 1);
+ }
+ Set<String> names = new TreeSet<String>();
+ for (PathModel entry : JGitUtils.getFilesInPath(r, res, commit)) {
+ names.add(entry.name);
+ }
+
+ byte[] content = null;
+ if (names.isEmpty()) {
+ // not a path, a specific resource
+ try {
+ String contentType = context.getMimeType(resource);
+ if (contentType == null) {
+ contentType = "text/plain";
+ }
+ if (contentType.startsWith("text")) {
+ content = JGitUtils.getStringContent(r, tree, resource, encodings).getBytes(
+ Constants.ENCODING);
+ } else {
+ content = JGitUtils.getByteContent(r, tree, resource, false);
+ }
+ response.setContentType(contentType);
+ } catch (Exception e) {
+ }
+ } else {
+ // path
+ List<String> extensions = new ArrayList<String>();
+ extensions.add("html");
+ extensions.add("htm");
+ extensions.addAll(processor.getMarkupExtensions());
+ for (String ext : extensions) {
+ String file = "index." + ext;
+
+ if (names.contains(file)) {
+ String stringContent = JGitUtils.getStringContent(r, tree, file, encodings);
+ if (stringContent == null) {
+ continue;
+ }
+ content = stringContent.getBytes(Constants.ENCODING);
+ if (content != null) {
+ resource = file;
+ // assume text/html unless the servlet container
+ // overrides
+ response.setContentType("text/html; charset=" + Constants.ENCODING);
+ break;
+ }
+ }
+ }
+ }
+
+ // no content, try custom 404 page
+ if (ArrayUtils.isEmpty(content)) {
+ String ext = StringUtils.getFileExtension(resource);
+ if (StringUtils.isEmpty(ext)) {
+ // document list
+ response.setContentType("text/html");
+ response.getWriter().append("<style>table th, table td { min-width: 150px; text-align: left; }</style>");
+ response.getWriter().append("<table>");
+ response.getWriter().append("<thead><tr><th>path</th><th>mode</th><th>size</th></tr>");
+ response.getWriter().append("</thead>");
+ response.getWriter().append("<tbody>");
+ String pattern = "<tr><td><a href=\"{0}\">{0}</a></td><td>{1}</td><td>{2}</td></tr>";
+ final ByteFormat byteFormat = new ByteFormat();
+ List<PathModel> entries = JGitUtils.getFilesInPath(r, resource, commit);
+ for (PathModel entry : entries) {
+ response.getWriter().append(MessageFormat.format(pattern, entry.name, JGitUtils.getPermissionsFromMode(entry.mode), byteFormat.format(entry.size)));
+ }
+ response.getWriter().append("</tbody>");
+ response.getWriter().append("</table>");
+ } else {
+ // 404
+ String custom404 = JGitUtils.getStringContent(r, tree, "404.html", encodings);
+ if (!StringUtils.isEmpty(custom404)) {
+ content = custom404.getBytes(Constants.ENCODING);
+ }
+
+ // still no content
+ if (ArrayUtils.isEmpty(content)) {
+ String str = MessageFormat.format(
+ "# Error\nSorry, the requested resource **{0}** was not found.",
+ resource);
+ content = MarkdownUtils.transformMarkdown(str).getBytes(Constants.ENCODING);
+ }
+
+ try {
+ // output the content
+ logger.warn("Pages 404: " + resource);
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ response.getOutputStream().write(content);
+ response.flushBuffer();
+ } catch (Throwable t) {
+ logger.error("Failed to write page to client", t);
+ }
+ }
+ return;
+ }
+
+ // check to see if we should transform markup files
+ String ext = StringUtils.getFileExtension(resource);
+ if (processor.getMarkupExtensions().contains(ext)) {
+ String markup = new String(content, Constants.ENCODING);
+ MarkupDocument markupDoc = processor.parse(repository, commit.getName(), resource, markup);
+ content = markupDoc.html.getBytes("UTF-8");
+ response.setContentType("text/html; charset=" + Constants.ENCODING);
+ }
+
+ try {
+ // output the content
+ response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+ response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
+ response.getOutputStream().write(content);
+ response.flushBuffer();
+ } catch (Throwable t) {
+ logger.error("Failed to write page to client", t);
+ }
+
+ // close the repository
+ r.close();
+ } catch (Throwable t) {
+ logger.error("Failed to write page to client", t);
+ }
+ }
+
+ private void error(HttpServletResponse response, String mkd) throws ServletException,
+ IOException, ParseException {
+ String content = MarkdownUtils.transformMarkdown(mkd);
+ response.setContentType("text/html; charset=" + Constants.ENCODING);
+ response.getWriter().write(content);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processRequest(request, response);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java new file mode 100644 index 00000000..c93675a0 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java @@ -0,0 +1,75 @@ +/*
+ * Copyright 2012 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.servlet;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.gitblit.Keys;
+import com.gitblit.Keys.web;
+import com.gitblit.Keys.web.robots;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.FileUtils;
+
+/**
+ * Handles requests for robots.txt
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class RobotsTxtServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private final IRuntimeManager runtimeManager;
+
+ @Inject
+ public RobotsTxtServlet(IRuntimeManager runtimeManager) {
+ super();
+ this.runtimeManager = runtimeManager;
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, java.io.IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processRequest(request, response);
+ }
+
+ protected void processRequest(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ File file = runtimeManager.getFileOrFolder(Keys.web.robots.txt, null);
+ String content = "";
+ if (file.exists()) {
+ content = FileUtils.readContent(file, "\n");
+ }
+ response.getWriter().append(content);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/RpcFilter.java b/src/main/java/com/gitblit/servlet/RpcFilter.java new file mode 100644 index 00000000..02f419ff --- /dev/null +++ b/src/main/java/com/gitblit/servlet/RpcFilter.java @@ -0,0 +1,169 @@ +/*
+ * 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.servlet;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+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;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.Keys.web;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.ISessionManager;
+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
+ *
+ */
+@Singleton
+public class RpcFilter extends AuthenticationFilter {
+
+ private final IStoredSettings settings;
+
+ private final IRuntimeManager runtimeManager;
+
+ @Inject
+ public RpcFilter(
+ IRuntimeManager runtimeManager,
+ ISessionManager sessionManager) {
+
+ super(sessionManager);
+ this.settings = runtimeManager.getSettings();
+ this.runtimeManager = runtimeManager;
+ }
+
+ /**
+ * 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;
+
+ String fullUrl = getFullUrl(httpRequest);
+ RpcRequest requestType = RpcRequest.fromName(httpRequest.getParameter("req"));
+ if (requestType == null) {
+ httpResponse.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
+ return;
+ }
+
+ boolean adminRequest = requestType.exceeds(RpcRequest.LIST_SETTINGS);
+
+ // conditionally reject all rpc requests
+ if (!settings.getBoolean(Keys.web.enableRpcServlet, true)) {
+ logger.warn(Keys.web.enableRpcServlet + " must be set TRUE for rpc requests.");
+ httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ boolean authenticateView = settings.getBoolean(Keys.web.authenticateViewPages, false);
+ boolean authenticateAdmin = settings.getBoolean(Keys.web.authenticateAdminPages, true);
+
+ // Wrap the HttpServletRequest with the RpcServletRequest which
+ // overrides the servlet container user principal methods.
+ AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest);
+ UserModel user = getUser(httpRequest);
+ if (user != null) {
+ authenticatedRequest.setUser(user);
+ }
+
+ // conditionally reject rpc management/administration requests
+ if (adminRequest && !settings.getBoolean(Keys.web.enableRpcManagement, false)) {
+ logger.warn(MessageFormat.format("{0} must be set TRUE for {1} rpc requests.",
+ Keys.web.enableRpcManagement, requestType.toString()));
+ httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ // BASIC authentication challenge and response processing
+ if ((adminRequest && authenticateAdmin) || (!adminRequest && authenticateView)) {
+ if (user == null) {
+ // challenge client to provide credentials. send 401.
+ if (runtimeManager.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 (runtimeManager.isDebugMode()) {
+ logger.info(MessageFormat.format("RPC: {0} forbidden to access {1}",
+ user.username, fullUrl));
+ }
+ httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ }
+
+ if (runtimeManager.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 GET_PROTOCOL:
+ return true;
+ case LIST_REPOSITORIES:
+ return true;
+ default:
+ return user.canAdmin();
+ }
+ }
+}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/servlet/RpcServlet.java b/src/main/java/com/gitblit/servlet/RpcServlet.java new file mode 100644 index 00000000..3a115b17 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/RpcServlet.java @@ -0,0 +1,413 @@ +/*
+ * 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.servlet;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlitException;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.Keys.federation;
+import com.gitblit.Keys.realm;
+import com.gitblit.Keys.web;
+import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IGitblitManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.ServerSettings;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.RpcUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Handles remote procedure calls.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class RpcServlet extends JsonServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final int PROTOCOL_VERSION = 6;
+
+ private final IStoredSettings settings;
+
+ private final IRuntimeManager runtimeManager;
+
+ private final IUserManager userManager;
+
+ private final IRepositoryManager repositoryManager;
+
+ private final IFederationManager federationManager;
+
+ private final IGitblitManager gitblitManager;
+
+ @Inject
+ public RpcServlet(
+ IRuntimeManager runtimeManager,
+ IUserManager userManager,
+ IRepositoryManager repositoryManager,
+ IFederationManager federationManager,
+ IGitblitManager gitblitManager) {
+
+ super();
+
+ this.settings = runtimeManager.getSettings();
+ this.runtimeManager = runtimeManager;
+ this.userManager = userManager;
+ this.repositoryManager = repositoryManager;
+ this.federationManager = federationManager;
+ this.gitblitManager = gitblitManager;
+ }
+
+ /**
+ * Processes an rpc request.
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ @Override
+ protected void processRequest(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ RpcRequest reqType = RpcRequest.fromName(request.getParameter("req"));
+ String objectName = request.getParameter("name");
+ logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType,
+ request.getRemoteAddr()));
+
+ UserModel user = (UserModel) request.getUserPrincipal();
+
+ boolean allowManagement = user != null && user.canAdmin()
+ && settings.getBoolean(Keys.web.enableRpcManagement, false);
+
+ boolean allowAdmin = user != null && user.canAdmin()
+ && settings.getBoolean(Keys.web.enableRpcAdministration, false);
+
+ Object result = null;
+ if (RpcRequest.GET_PROTOCOL.equals(reqType)) {
+ // Return the protocol version
+ result = PROTOCOL_VERSION;
+ } else if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) {
+ // Determine the Gitblit clone url
+ String gitblitUrl = HttpUtils.getGitblitURL(request);
+ StringBuilder sb = new StringBuilder();
+ sb.append(gitblitUrl);
+ sb.append(Constants.GIT_PATH);
+ sb.append("{0}");
+ String cloneUrl = sb.toString();
+
+ // list repositories
+ List<RepositoryModel> list = repositoryManager.getRepositoryModels(user);
+ Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
+ for (RepositoryModel model : list) {
+ String url = MessageFormat.format(cloneUrl, model.name);
+ repositories.put(url, model);
+ }
+ result = repositories;
+ } else if (RpcRequest.LIST_BRANCHES.equals(reqType)) {
+ // list all local branches in all repositories accessible to user
+ Map<String, List<String>> localBranches = new HashMap<String, List<String>>();
+ List<RepositoryModel> models = repositoryManager.getRepositoryModels(user);
+ for (RepositoryModel model : models) {
+ if (!model.hasCommits) {
+ // skip empty repository
+ continue;
+ }
+ if (model.isCollectingGarbage) {
+ // skip garbage collecting repository
+ logger.warn(MessageFormat.format("Temporarily excluding {0} from RPC, busy collecting garbage", model.name));
+ continue;
+ }
+ // get local branches
+ Repository repository = repositoryManager.getRepository(model.name);
+ List<RefModel> refs = JGitUtils.getLocalBranches(repository, false, -1);
+ if (model.showRemoteBranches) {
+ // add remote branches if repository displays them
+ refs.addAll(JGitUtils.getRemoteBranches(repository, false, -1));
+ }
+ if (refs.size() > 0) {
+ List<String> branches = new ArrayList<String>();
+ for (RefModel ref : refs) {
+ branches.add(ref.getName());
+ }
+ localBranches.put(model.name, branches);
+ }
+ repository.close();
+ }
+ result = localBranches;
+ } else if (RpcRequest.GET_USER.equals(reqType)) {
+ if (StringUtils.isEmpty(objectName)) {
+ if (UserModel.ANONYMOUS.equals(user)) {
+ response.sendError(forbiddenCode);
+ } else {
+ // return the current user, reset credentials
+ UserModel requestedUser = DeepCopier.copy(user);
+ result = requestedUser;
+ }
+ } else {
+ if (user.canAdmin() || objectName.equals(user.username)) {
+ // return the specified user
+ UserModel requestedUser = userManager.getUserModel(objectName);
+ if (requestedUser == null) {
+ response.setStatus(failureCode);
+ } else {
+ result = requestedUser;
+ }
+ } else {
+ response.sendError(forbiddenCode);
+ }
+ }
+ } else if (RpcRequest.LIST_USERS.equals(reqType)) {
+ // list users
+ List<String> names = userManager.getAllUsernames();
+ List<UserModel> users = new ArrayList<UserModel>();
+ for (String name : names) {
+ users.add(userManager.getUserModel(name));
+ }
+ result = users;
+ } else if (RpcRequest.LIST_TEAMS.equals(reqType)) {
+ // list teams
+ List<String> names = userManager.getAllTeamNames();
+ List<TeamModel> teams = new ArrayList<TeamModel>();
+ for (String name : names) {
+ teams.add(userManager.getTeamModel(name));
+ }
+ result = teams;
+ } else if (RpcRequest.CREATE_REPOSITORY.equals(reqType)) {
+ // create repository
+ RepositoryModel model = deserialize(request, response, RepositoryModel.class);
+ try {
+ repositoryManager.updateRepositoryModel(model.name, model, true);
+ } catch (GitBlitException e) {
+ response.setStatus(failureCode);
+ }
+ } else if (RpcRequest.EDIT_REPOSITORY.equals(reqType)) {
+ // edit repository
+ RepositoryModel model = deserialize(request, response, RepositoryModel.class);
+ // name specifies original repository name in event of rename
+ String repoName = objectName;
+ if (repoName == null) {
+ repoName = model.name;
+ }
+ try {
+ repositoryManager.updateRepositoryModel(repoName, model, false);
+ } catch (GitBlitException e) {
+ response.setStatus(failureCode);
+ }
+ } else if (RpcRequest.DELETE_REPOSITORY.equals(reqType)) {
+ // delete repository
+ RepositoryModel model = deserialize(request, response, RepositoryModel.class);
+ repositoryManager.deleteRepositoryModel(model);
+ } else if (RpcRequest.CREATE_USER.equals(reqType)) {
+ // create user
+ UserModel model = deserialize(request, response, UserModel.class);
+ try {
+ gitblitManager.updateUserModel(model.username, model, true);
+ } catch (GitBlitException e) {
+ response.setStatus(failureCode);
+ }
+ } else if (RpcRequest.EDIT_USER.equals(reqType)) {
+ // edit user
+ UserModel model = deserialize(request, response, UserModel.class);
+ // name parameter specifies original user name in event of rename
+ String username = objectName;
+ if (username == null) {
+ username = model.username;
+ }
+ try {
+ gitblitManager.updateUserModel(username, model, false);
+ } catch (GitBlitException e) {
+ response.setStatus(failureCode);
+ }
+ } else if (RpcRequest.DELETE_USER.equals(reqType)) {
+ // delete user
+ UserModel model = deserialize(request, response, UserModel.class);
+ if (!userManager.deleteUser(model.username)) {
+ response.setStatus(failureCode);
+ }
+ } else if (RpcRequest.CREATE_TEAM.equals(reqType)) {
+ // create team
+ TeamModel model = deserialize(request, response, TeamModel.class);
+ try {
+ gitblitManager.updateTeamModel(model.name, model, true);
+ } catch (GitBlitException e) {
+ response.setStatus(failureCode);
+ }
+ } else if (RpcRequest.EDIT_TEAM.equals(reqType)) {
+ // edit team
+ TeamModel model = deserialize(request, response, TeamModel.class);
+ // name parameter specifies original team name in event of rename
+ String teamname = objectName;
+ if (teamname == null) {
+ teamname = model.name;
+ }
+ try {
+ gitblitManager.updateTeamModel(teamname, model, false);
+ } catch (GitBlitException e) {
+ response.setStatus(failureCode);
+ }
+ } else if (RpcRequest.DELETE_TEAM.equals(reqType)) {
+ // delete team
+ TeamModel model = deserialize(request, response, TeamModel.class);
+ if (!userManager.deleteTeam(model.name)) {
+ response.setStatus(failureCode);
+ }
+ } else if (RpcRequest.LIST_REPOSITORY_MEMBERS.equals(reqType)) {
+ // get repository members
+ RepositoryModel model = repositoryManager.getRepositoryModel(objectName);
+ result = repositoryManager.getRepositoryUsers(model);
+ } else if (RpcRequest.SET_REPOSITORY_MEMBERS.equals(reqType)) {
+ // rejected since 1.2.0
+ response.setStatus(failureCode);
+ } else if (RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) {
+ // get repository member permissions
+ RepositoryModel model = repositoryManager.getRepositoryModel(objectName);
+ result = repositoryManager.getUserAccessPermissions(model);
+ } else if (RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) {
+ // set the repository permissions for the specified users
+ RepositoryModel model = repositoryManager.getRepositoryModel(objectName);
+ Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE);
+ result = repositoryManager.setUserAccessPermissions(model, permissions);
+ } else if (RpcRequest.LIST_REPOSITORY_TEAMS.equals(reqType)) {
+ // get repository teams
+ RepositoryModel model = repositoryManager.getRepositoryModel(objectName);
+ result = repositoryManager.getRepositoryTeams(model);
+ } else if (RpcRequest.SET_REPOSITORY_TEAMS.equals(reqType)) {
+ // rejected since 1.2.0
+ response.setStatus(failureCode);
+ } else if (RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) {
+ // get repository team permissions
+ RepositoryModel model = repositoryManager.getRepositoryModel(objectName);
+ result = repositoryManager.getTeamAccessPermissions(model);
+ } else if (RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) {
+ // set the repository permissions for the specified teams
+ RepositoryModel model = repositoryManager.getRepositoryModel(objectName);
+ Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE);
+ result = repositoryManager.setTeamAccessPermissions(model, permissions);
+ } else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) {
+ // return the list of federation registrations
+ if (allowAdmin) {
+ result = federationManager.getFederationRegistrations();
+ } else {
+ response.sendError(notAllowedCode);
+ }
+ } else if (RpcRequest.LIST_FEDERATION_RESULTS.equals(reqType)) {
+ // return the list of federation result registrations
+ if (allowAdmin && federationManager.canFederate()) {
+ result = federationManager.getFederationResultRegistrations();
+ } else {
+ response.sendError(notAllowedCode);
+ }
+ } else if (RpcRequest.LIST_FEDERATION_PROPOSALS.equals(reqType)) {
+ // return the list of federation proposals
+ if (allowAdmin && federationManager.canFederate()) {
+ result = federationManager.getPendingFederationProposals();
+ } else {
+ response.sendError(notAllowedCode);
+ }
+ } else if (RpcRequest.LIST_FEDERATION_SETS.equals(reqType)) {
+ // return the list of federation sets
+ if (allowAdmin && federationManager.canFederate()) {
+ String gitblitUrl = HttpUtils.getGitblitURL(request);
+ result = federationManager.getFederationSets(gitblitUrl);
+ } else {
+ response.sendError(notAllowedCode);
+ }
+ } else if (RpcRequest.LIST_SETTINGS.equals(reqType)) {
+ // return the server's settings
+ ServerSettings serverSettings = runtimeManager.getSettingsModel();
+ if (allowAdmin) {
+ // return all settings
+ result = serverSettings;
+ } else {
+ // anonymous users get a few settings to allow browser launching
+ List<String> keys = new ArrayList<String>();
+ keys.add(Keys.web.siteName);
+ keys.add(Keys.web.mountParameters);
+ keys.add(Keys.web.syndicationEntries);
+
+ if (allowManagement) {
+ // keys necessary for repository and/or user management
+ keys.add(Keys.realm.minPasswordLength);
+ keys.add(Keys.realm.passwordStorage);
+ keys.add(Keys.federation.sets);
+ }
+ // build the settings
+ ServerSettings managementSettings = new ServerSettings();
+ for (String key : keys) {
+ managementSettings.add(serverSettings.get(key));
+ }
+ if (allowManagement) {
+ managementSettings.pushScripts = serverSettings.pushScripts;
+ }
+ result = managementSettings;
+ }
+ } else if (RpcRequest.EDIT_SETTINGS.equals(reqType)) {
+ // update settings on the server
+ if (allowAdmin) {
+ Map<String, String> map = deserialize(request, response,
+ RpcUtils.SETTINGS_TYPE);
+ runtimeManager.updateSettings(map);
+ } else {
+ response.sendError(notAllowedCode);
+ }
+ } else if (RpcRequest.LIST_STATUS.equals(reqType)) {
+ // return the server's status information
+ if (allowAdmin) {
+ result = runtimeManager.getStatus();
+ } else {
+ response.sendError(notAllowedCode);
+ }
+ } else if (RpcRequest.CLEAR_REPOSITORY_CACHE.equals(reqType)) {
+ // clear the repository list cache
+ if (allowManagement) {
+ repositoryManager.resetRepositoryListCache();
+ } else {
+ response.sendError(notAllowedCode);
+ }
+ }
+
+ // send the result of the request
+ serialize(response, result);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java new file mode 100644 index 00000000..4b8b24f4 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java @@ -0,0 +1,142 @@ +/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.servlet;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.gitblit.Constants;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Keys.fanout;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.ISessionManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Handles requests for Sparkleshare Invites
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class SparkleShareInviteServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private final IStoredSettings settings;
+
+ private final IUserManager userManager;
+
+ private final ISessionManager sessionManager;
+
+ private final IRepositoryManager repositoryManager;
+
+ @Inject
+ public SparkleShareInviteServlet(
+ IRuntimeManager runtimeManager,
+ IUserManager userManager,
+ ISessionManager sessionManager,
+ IRepositoryManager repositoryManager) {
+
+ super();
+ this.settings = runtimeManager.getSettings();
+ this.userManager = userManager;
+ this.sessionManager = sessionManager;
+ this.repositoryManager = repositoryManager;
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, java.io.IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processRequest(request, response);
+ }
+
+ protected void processRequest(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+
+ // extract repo name from request
+ String repoUrl = request.getPathInfo().substring(1);
+
+ // trim trailing .xml
+ if (repoUrl.endsWith(".xml")) {
+ repoUrl = repoUrl.substring(0, repoUrl.length() - 4);
+ }
+
+ String servletPath = Constants.GIT_PATH;
+
+ int schemeIndex = repoUrl.indexOf("://") + 3;
+ String host = repoUrl.substring(0, repoUrl.indexOf('/', schemeIndex));
+ String path = repoUrl.substring(repoUrl.indexOf(servletPath) + servletPath.length());
+ String username = null;
+ int fetchIndex = repoUrl.indexOf('@');
+ if (fetchIndex > -1) {
+ username = repoUrl.substring(schemeIndex, fetchIndex);
+ }
+ UserModel user;
+ if (StringUtils.isEmpty(username)) {
+ user = sessionManager.authenticate(request);
+ } else {
+ user = userManager.getUserModel(username);
+ }
+ if (user == null) {
+ user = UserModel.ANONYMOUS;
+ username = "";
+ }
+
+ // ensure that the requested repository exists
+ RepositoryModel model = repositoryManager.getRepositoryModel(path);
+ if (model == null) {
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ response.getWriter().append(MessageFormat.format("Repository \"{0}\" not found!", path));
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ sb.append("<sparkleshare><invite>\n");
+ sb.append(MessageFormat.format("<address>{0}</address>\n", host));
+ sb.append(MessageFormat.format("<remote_path>{0}{1}</remote_path>\n", servletPath, model.name));
+ if (settings.getInteger(Keys.fanout.port, 0) > 0) {
+ // Gitblit is running it's own fanout service for pubsub notifications
+ sb.append(MessageFormat.format("<announcements_url>tcp://{0}:{1}</announcements_url>\n", request.getServerName(), settings.getString(Keys.fanout.port, "")));
+ }
+ sb.append("</invite></sparkleshare>\n");
+
+ // write invite to client
+ response.setContentType("application/xml");
+ response.setContentLength(sb.length());
+ response.getWriter().append(sb.toString());
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/SyndicationFilter.java b/src/main/java/com/gitblit/servlet/SyndicationFilter.java new file mode 100644 index 00000000..adf9ba94 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/SyndicationFilter.java @@ -0,0 +1,168 @@ +/*
+ * 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.servlet;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+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.AccessRestrictionType;
+import com.gitblit.manager.IProjectManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.ISessionManager;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * The SyndicationFilter is an AuthenticationFilter which ensures that feed
+ * requests for projects or view-restricted repositories have proper authentication
+ * credentials and are authorized for the requested feed.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class SyndicationFilter extends AuthenticationFilter {
+
+ private final IRuntimeManager runtimeManager;
+ private final IRepositoryManager repositoryManager;
+ private final IProjectManager projectManager;
+
+ @Inject
+ public SyndicationFilter(
+ IRuntimeManager runtimeManager,
+ ISessionManager sessionManager,
+ IRepositoryManager repositoryManager,
+ IProjectManager projectManager) {
+
+ super(sessionManager);
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
+ this.projectManager = projectManager;
+ }
+
+ /**
+ * Extract the repository name from the url.
+ *
+ * @param url
+ * @return repository name
+ */
+ protected String extractRequestedName(String url) {
+ if (url.indexOf('?') > -1) {
+ return url.substring(0, url.indexOf('?'));
+ }
+ return url;
+ }
+
+ /**
+ * 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;
+
+ String fullUrl = getFullUrl(httpRequest);
+ String name = extractRequestedName(fullUrl);
+
+ ProjectModel project = projectManager.getProjectModel(name);
+ RepositoryModel model = null;
+
+ if (project == null) {
+ // try loading a repository model
+ model = repositoryManager.getRepositoryModel(name);
+ if (model == null) {
+ // repository not found. send 404.
+ 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 (model != null) {
+ if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
+ if (user == null) {
+ // challenge client to provide credentials. send 401.
+ if (runtimeManager.isDebugMode()) {
+ 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.canView(model)) {
+ // 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 (runtimeManager.isDebugMode()) {
+ logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}",
+ user.username, fullUrl));
+ }
+ httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ }
+ }
+
+ if (runtimeManager.isDebugMode()) {
+ logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl,
+ HttpServletResponse.SC_CONTINUE));
+ }
+ // unauthenticated request permitted.
+ // pass processing to the restricted servlet.
+ chain.doFilter(authenticatedRequest, httpResponse);
+ }
+}
diff --git a/src/main/java/com/gitblit/servlet/SyndicationServlet.java b/src/main/java/com/gitblit/servlet/SyndicationServlet.java new file mode 100644 index 00000000..739ee2d9 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/SyndicationServlet.java @@ -0,0 +1,352 @@ +/*
+ * 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.servlet;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.http.HttpServlet;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IProjectManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.models.FeedEntryModel;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.servlet.AuthenticationFilter.AuthenticatedRequest;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MessageProcessor;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.SyndicationUtils;
+
+/**
+ * SyndicationServlet generates RSS 2.0 feeds and feed links.
+ *
+ * Access to this servlet is protected by the SyndicationFilter.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class SyndicationServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private transient Logger logger = LoggerFactory.getLogger(SyndicationServlet.class);
+
+ private final IStoredSettings settings;
+
+ private final IRepositoryManager repositoryManager;
+
+ private final IProjectManager projectManager;
+
+ @Inject
+ public SyndicationServlet(
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager,
+ IProjectManager projectManager) {
+
+ super();
+ this.settings = runtimeManager.getSettings();
+ this.repositoryManager = repositoryManager;
+ this.projectManager = projectManager;
+ }
+
+ /**
+ * Create a feed link for the specified repository and branch/tag/commit id.
+ *
+ * @param baseURL
+ * @param repository
+ * the repository name
+ * @param objectId
+ * the branch, tag, or first commit for the feed
+ * @param length
+ * the number of commits to include in the feed
+ * @return an RSS feed url
+ */
+ public static String asLink(String baseURL, String repository, String objectId, int length) {
+ if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+ baseURL = baseURL.substring(0, baseURL.length() - 1);
+ }
+ StringBuilder url = new StringBuilder();
+ url.append(baseURL);
+ url.append(Constants.SYNDICATION_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();
+ }
+
+ /**
+ * Determines the appropriate title for a feed.
+ *
+ * @param repository
+ * @param objectId
+ * @return title of the feed
+ */
+ 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);
+ }
+
+ /**
+ * Generates the feed content.
+ *
+ * @param request
+ * @param response
+ * @throws javax.servlet.ServletException
+ * @throws java.io.IOException
+ */
+ private void processRequest(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+
+ String servletUrl = request.getContextPath() + request.getServletPath();
+ String url = request.getRequestURI().substring(servletUrl.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");
+ String page = request.getParameter("pg");
+ String searchString = request.getParameter("s");
+ Constants.SearchType searchType = Constants.SearchType.COMMIT;
+ if (!StringUtils.isEmpty(request.getParameter("st"))) {
+ Constants.SearchType type = Constants.SearchType.forName(request.getParameter("st"));
+ if (type != null) {
+ searchType = type;
+ }
+ }
+ int length = settings.getInteger(Keys.web.syndicationEntries, 25);
+ if (StringUtils.isEmpty(objectId)) {
+ objectId = org.eclipse.jgit.lib.Constants.HEAD;
+ }
+ if (!StringUtils.isEmpty(l)) {
+ try {
+ length = Integer.parseInt(l);
+ } catch (NumberFormatException x) {
+ }
+ }
+ int offset = 0;
+ if (!StringUtils.isEmpty(page)) {
+ try {
+ offset = length * Integer.parseInt(page);
+ } catch (NumberFormatException x) {
+ }
+ }
+
+ response.setContentType("application/rss+xml; charset=UTF-8");
+
+ boolean isProjectFeed = false;
+ String feedName = null;
+ String feedTitle = null;
+ String feedDescription = null;
+
+ List<String> repositories = null;
+ if (repositoryName.indexOf('/') == -1 && !repositoryName.toLowerCase().endsWith(".git")) {
+ // try to find a project
+ UserModel user = null;
+ if (request instanceof AuthenticatedRequest) {
+ user = ((AuthenticatedRequest) request).getUser();
+ }
+ ProjectModel project = projectManager.getProjectModel(repositoryName, user);
+ if (project != null) {
+ isProjectFeed = true;
+ repositories = new ArrayList<String>(project.repositories);
+
+ // project feed
+ feedName = project.name;
+ feedTitle = project.title;
+ feedDescription = project.description;
+ }
+ }
+
+ if (repositories == null) {
+ // could not find project, assume this is a repository
+ repositories = Arrays.asList(repositoryName);
+ }
+
+
+ boolean mountParameters = settings.getBoolean(Keys.web.mountParameters, true);
+ String urlPattern;
+ if (mountParameters) {
+ // mounted parameters
+ urlPattern = "{0}/commit/{1}/{2}";
+ } else {
+ // parameterized parameters
+ urlPattern = "{0}/commit/?r={1}&h={2}";
+ }
+ String gitblitUrl = HttpUtils.getGitblitURL(request);
+ char fsc = settings.getChar(Keys.web.forwardSlashCharacter, '/');
+
+ List<FeedEntryModel> entries = new ArrayList<FeedEntryModel>();
+
+ for (String name : repositories) {
+ Repository repository = repositoryManager.getRepository(name);
+ RepositoryModel model = repositoryManager.getRepositoryModel(name);
+
+ if (repository == null) {
+ if (model.isCollectingGarbage) {
+ logger.warn(MessageFormat.format("Temporarily excluding {0} from feed, busy collecting garbage", name));
+ }
+ continue;
+ }
+ if (!isProjectFeed) {
+ // single-repository feed
+ feedName = model.name;
+ feedTitle = model.name;
+ feedDescription = model.description;
+ }
+
+ List<RevCommit> commits;
+ if (StringUtils.isEmpty(searchString)) {
+ // standard log/history lookup
+ commits = JGitUtils.getRevLog(repository, objectId, offset, length);
+ } else {
+ // repository search
+ commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType,
+ offset, length);
+ }
+ Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, model.showRemoteBranches);
+ MessageProcessor processor = new MessageProcessor(settings);
+
+ // convert RevCommit to SyndicatedEntryModel
+ for (RevCommit commit : commits) {
+ FeedEntryModel entry = new FeedEntryModel();
+ entry.title = commit.getShortMessage();
+ entry.author = commit.getAuthorIdent().getName();
+ entry.link = MessageFormat.format(urlPattern, gitblitUrl,
+ StringUtils.encodeURL(model.name.replace('/', fsc)), commit.getName());
+ entry.published = commit.getCommitterIdent().getWhen();
+ entry.contentType = "text/html";
+ String message = processor.processCommitMessage(model, commit.getFullMessage());
+ entry.content = message;
+ entry.repository = model.name;
+ entry.branch = objectId;
+ entry.tags = new ArrayList<String>();
+
+ // add commit id and parent commit ids
+ entry.tags.add("commit:" + commit.getName());
+ for (RevCommit parent : commit.getParents()) {
+ entry.tags.add("parent:" + parent.getName());
+ }
+
+ // add refs to tabs list
+ List<RefModel> refs = allRefs.get(commit.getId());
+ if (refs != null && refs.size() > 0) {
+ for (RefModel ref : refs) {
+ entry.tags.add("ref:" + ref.getName());
+ }
+ }
+ entries.add(entry);
+ }
+ }
+
+ // sort & truncate the feed
+ Collections.sort(entries);
+ if (entries.size() > length) {
+ // clip the list
+ entries = entries.subList(0, length);
+ }
+
+ String feedLink;
+ if (isProjectFeed) {
+ // project feed
+ if (mountParameters) {
+ // mounted url
+ feedLink = MessageFormat.format("{0}/project/{1}", gitblitUrl,
+ StringUtils.encodeURL(feedName));
+ } else {
+ // parameterized url
+ feedLink = MessageFormat.format("{0}/project/?p={1}", gitblitUrl,
+ StringUtils.encodeURL(feedName));
+ }
+ } else {
+ // repository feed
+ if (mountParameters) {
+ // mounted url
+ feedLink = MessageFormat.format("{0}/summary/{1}", gitblitUrl,
+ StringUtils.encodeURL(feedName));
+ } else {
+ // parameterized url
+ feedLink = MessageFormat.format("{0}/summary/?r={1}", gitblitUrl,
+ StringUtils.encodeURL(feedName));
+ }
+ }
+
+ try {
+ SyndicationUtils.toRSS(gitblitUrl, feedLink, getTitle(feedTitle, objectId),
+ feedDescription, entries, response.getOutputStream());
+ } catch (Exception e) {
+ logger.error("An error occurred during feed generation", e);
+ }
+ }
+
+ @Override
+ protected void doPost(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ processRequest(request, response);
+ }
+
+ @Override
+ protected void doGet(javax.servlet.http.HttpServletRequest request,
+ javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+ java.io.IOException {
+ processRequest(request, response);
+ }
+}
|