Adapt Http Audit to be Servlet 2.5 compatible
Make the Git/HTTP audit filtering more compatible with earlier
versions of the Servlet definition (e.g. 2.5) and capturing the
actual response status from the whole operation.
Change-Id: I43eeace9ed510c7d11b02117f7c7c766397377e6
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index b0a39cf..7becf99 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -26,6 +26,7 @@
"//java/com/google/gerrit/pgm/util",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/audit",
"//java/com/google/gerrit/server/git/receive",
"//java/com/google/gerrit/server/project/testing:project-test-util",
"//java/com/google/gerrit/server/restapi",
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index e74c4b2..f4b0a9a 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -61,6 +61,7 @@
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.http.server.GitServlet;
import org.eclipse.jgit.http.server.GitSmartHttpTools;
@@ -130,6 +131,55 @@
}
}
+ static class HttpServletResponseWithStatusWrapper extends HttpServletResponseWrapper {
+ private int responseStatus;
+
+ HttpServletResponseWithStatusWrapper(HttpServletResponse response) {
+ super(response);
+ /* Even if we could read the status from response, we assume that it is all
+ * fine because we entered the filter without any prior issues.
+ * When Google will have upgraded to Servlet 3.0, we could actually
+ * call response.getStatus() and the code will be clearer.
+ */
+ responseStatus = HttpServletResponse.SC_OK;
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ responseStatus = sc;
+ super.setStatus(sc);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setStatus(int sc, String sm) {
+ responseStatus = sc;
+ super.setStatus(sc, sm);
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ this.responseStatus = sc;
+ super.sendError(sc);
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ this.responseStatus = sc;
+ super.sendError(sc, msg);
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException {
+ this.responseStatus = HttpServletResponse.SC_MOVED_TEMPORARILY;
+ super.sendRedirect(location);
+ }
+
+ public int getResponseStatus() {
+ return responseStatus;
+ }
+ }
+
@Inject
GitOverHttpServlet(
Resolver resolver,
@@ -163,8 +213,8 @@
.getParameterMap()
.forEach(
(k, v) -> {
- for (int i = 0; i < v.length; i++) {
- multiMap.put(k, v[i]);
+ for (String aV : v) {
+ multiMap.put(k, aV);
}
});
}
@@ -301,41 +351,48 @@
UploadPack up = (UploadPack) request.getAttribute(ServletUtils.ATTRIBUTE_HANDLER);
PermissionBackend.ForProject perm =
permissionBackend.currentUser().project(state.getNameKey());
+ HttpServletResponseWithStatusWrapper responseWrapper =
+ new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ String sessionId = httpRequest.getSession().getId();
+
try {
- perm.check(ProjectPermission.RUN_UPLOAD_PACK);
- } catch (AuthException e) {
- GitSmartHttpTools.sendError(
- (HttpServletRequest) request,
- (HttpServletResponse) response,
- HttpServletResponse.SC_FORBIDDEN,
- "upload-pack not permitted on this server");
- return;
- } catch (PermissionBackendException e) {
- throw new ServletException(e);
+ try {
+ perm.check(ProjectPermission.RUN_UPLOAD_PACK);
+ } catch (AuthException e) {
+ GitSmartHttpTools.sendError(
+ (HttpServletRequest) request,
+ responseWrapper,
+ HttpServletResponse.SC_FORBIDDEN,
+ "upload-pack not permitted on this server");
+ return;
+ } catch (PermissionBackendException e) {
+ responseWrapper.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ throw new ServletException(e);
+ }
+
+ // We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR
+ // may have been overridden by a proxy server -- we'll try to avoid this.
+ UploadValidators uploadValidators =
+ uploadValidatorsFactory.create(state.getProject(), repo, request.getRemoteHost());
+ up.setPreUploadHook(
+ PreUploadHookChain.newChain(
+ Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
+ up.setAdvertiseRefsHook(new DefaultAdvertiseRefsHook(perm, RefFilterOptions.defaults()));
+ next.doFilter(httpRequest, responseWrapper);
} finally {
- HttpServletRequest httpRequest = (HttpServletRequest) request;
- HttpServletResponse httpResponse = (HttpServletResponse) response;
groupAuditService.dispatch(
new HttpAuditEvent(
- httpRequest.getSession().getId(),
+ sessionId,
userProvider.get(),
extractWhat(httpRequest),
TimeUtil.nowMs(),
extractParameters(httpRequest),
httpRequest.getMethod(),
httpRequest,
- httpResponse.getStatus(),
- httpResponse));
+ responseWrapper.getResponseStatus(),
+ responseWrapper));
}
-
- // We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR
- // may have been overridden by a proxy server -- we'll try to avoid this.
- UploadValidators uploadValidators =
- uploadValidatorsFactory.create(state.getProject(), repo, request.getRemoteHost());
- up.setPreUploadHook(
- PreUploadHookChain.newChain(Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
- up.setAdvertiseRefsHook(new DefaultAdvertiseRefsHook(perm, RefFilterOptions.defaults()));
- next.doFilter(request, response);
}
@Override
@@ -411,25 +468,28 @@
rp.getAdvertiseRefsHook().advertiseRefs(rp);
ProjectState state = (ProjectState) request.getAttribute(ATT_STATE);
+ HttpServletResponseWithStatusWrapper responseWrapper =
+ new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
Capable canUpload;
try {
- permissionBackend
- .currentUser()
- .project(state.getNameKey())
- .check(ProjectPermission.RUN_RECEIVE_PACK);
- canUpload = arc.canUpload();
- } catch (AuthException e) {
- GitSmartHttpTools.sendError(
- (HttpServletRequest) request,
- (HttpServletResponse) response,
- HttpServletResponse.SC_FORBIDDEN,
- "receive-pack not permitted on this server");
- return;
- } catch (PermissionBackendException e) {
- throw new RuntimeException(e);
+ try {
+ permissionBackend
+ .currentUser()
+ .project(state.getNameKey())
+ .check(ProjectPermission.RUN_RECEIVE_PACK);
+ canUpload = arc.canUpload();
+ } catch (AuthException e) {
+ GitSmartHttpTools.sendError(
+ httpRequest,
+ responseWrapper,
+ HttpServletResponse.SC_FORBIDDEN,
+ "receive-pack not permitted on this server");
+ return;
+ } catch (PermissionBackendException e) {
+ throw new RuntimeException(e);
+ }
} finally {
- HttpServletRequest httpRequest = (HttpServletRequest) request;
- HttpServletResponse httpResponse = (HttpServletResponse) response;
groupAuditService.dispatch(
new HttpAuditEvent(
httpRequest.getSession().getId(),
@@ -439,26 +499,26 @@
extractParameters(httpRequest),
httpRequest.getMethod(),
httpRequest,
- httpResponse.getStatus(),
- httpResponse));
+ responseWrapper.getResponseStatus(),
+ responseWrapper));
}
if (canUpload != Capable.OK) {
GitSmartHttpTools.sendError(
- (HttpServletRequest) request,
- (HttpServletResponse) response,
+ httpRequest,
+ responseWrapper,
HttpServletResponse.SC_FORBIDDEN,
"\n" + canUpload.getMessage());
return;
}
if (!rp.isCheckReferencedObjectsAreReachable()) {
- chain.doFilter(request, response);
+ chain.doFilter(request, responseWrapper);
return;
}
if (!(userProvider.get().isIdentifiedUser())) {
- chain.doFilter(request, response);
+ chain.doFilter(request, responseWrapper);
return;
}
@@ -475,7 +535,7 @@
}
}
- chain.doFilter(request, response);
+ chain.doFilter(request, responseWrapper);
if (isGet) {
cache.put(cacheKey, Collections.unmodifiableSet(new HashSet<>(rp.getAdvertisedObjects())));
diff --git a/java/com/google/gerrit/testing/FakeGroupAuditService.java b/java/com/google/gerrit/testing/FakeGroupAuditService.java
index f2e85cd..ddf03f5 100644
--- a/java/com/google/gerrit/testing/FakeGroupAuditService.java
+++ b/java/com/google/gerrit/testing/FakeGroupAuditService.java
@@ -63,7 +63,10 @@
@Override
public void dispatch(AuditEvent action) {
- auditEvents.add(action);
+ synchronized (auditEvents) {
+ auditEvents.add(action);
+ auditEvents.notifyAll();
+ }
}
@Override
diff --git a/javatests/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java b/javatests/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java
index 42e046a..90f4134 100644
--- a/javatests/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java
@@ -17,7 +17,9 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.server.AuditEvent;
+import com.google.gerrit.server.audit.HttpAuditEvent;
import java.util.Collections;
+import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
@@ -25,6 +27,7 @@
import org.junit.Test;
public class GitOverHttpServletIT extends AbstractPushForReview {
+ private static final long AUDIT_EVENT_TIMEOUT = 500L;
@Before
public void beforeEach() throws Exception {
@@ -42,6 +45,7 @@
.setRemote("origin")
.setRefSpecs(new RefSpec("HEAD:refs/for/master"))
.call();
+ waitForAudit();
// Git smart protocol makes two requests:
// https://github.com/git/git/blob/master/Documentation/technical/http-protocol.txt
@@ -51,11 +55,13 @@
assertThat(e.who.getAccountId()).isEqualTo(admin.id);
assertThat(e.what).endsWith("/git-receive-pack");
assertThat(e.params).isEmpty();
+ assertThat(((HttpAuditEvent) e).httpStatus).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void uploadPackAuditEventLog() throws Exception {
testRepo.git().fetch().call();
+ waitForAudit();
assertThat(auditService.auditEvents.size()).isEqualTo(1);
@@ -64,5 +70,12 @@
assertThat(e.params.get("service"))
.containsExactlyElementsIn(Collections.singletonList("git-upload-pack"));
assertThat(e.what).endsWith("service=git-upload-pack");
+ assertThat(((HttpAuditEvent) e).httpStatus).isEqualTo(HttpServletResponse.SC_OK);
+ }
+
+ private void waitForAudit() throws InterruptedException {
+ synchronized (auditService.auditEvents) {
+ auditService.auditEvents.wait(AUDIT_EVENT_TIMEOUT);
+ }
}
}