summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntonio Barone <syntonyze@gmail.com>2018-11-09 10:12:24 +0000
committerAntonio Barone <syntonyze@gmail.com>2018-11-12 11:11:18 -0800
commit76394d2a89b5e38ccd4496ae39cc45dde551415f (patch)
tree47fff3582af83f286031d56e1a45d9b372bfd6e0
parent2d1c67e51bac58ca196e455899878ce91502d5af (diff)
Trigger audit for GIT over Http commands
Before this change only git over SSH commands were audited, this change allows auditing of git-receive-pack and git-upload-pack commands over http. To allow testing AuditService is now an interface so that a fake implementation can be injected in the tests. Bug: Issue 9982 Change-Id: Iffcd0fbd7332ef0f6b4b4a8e57bb5d571ee4cb39
-rw-r--r--gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java2
-rw-r--r--gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java2
-rw-r--r--gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java68
-rw-r--r--gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java74
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java12
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java71
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/audit/AuditServiceImpl.java94
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java2
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAuditService.java89
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java2
11 files changed, 349 insertions, 69 deletions
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 9e4595351e..3d81179e69 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -109,6 +109,7 @@ import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gerrit.testutil.FakeAuditService;
import com.google.gerrit.testutil.FakeEmailSender;
import com.google.gerrit.testutil.FakeEmailSender.Message;
import com.google.gerrit.testutil.NoteDbMode;
@@ -215,6 +216,7 @@ public abstract class AbstractDaemonTest {
@Inject protected ChangeNoteUtil changeNoteUtil;
@Inject protected ChangeResource.Factory changeResourceFactory;
@Inject protected FakeEmailSender sender;
+ @Inject protected FakeAuditService auditService;
@Inject protected GerritApi gApi;
@Inject protected GitRepositoryManager repoManager;
@Inject protected GroupCache groupCache;
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index f72403368e..7e5fd5cc94 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -33,6 +33,7 @@ import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.server.util.SocketUtil;
import com.google.gerrit.server.util.SystemLog;
+import com.google.gerrit.testutil.FakeAuditService;
import com.google.gerrit.testutil.FakeEmailSender;
import com.google.gerrit.testutil.NoteDbChecker;
import com.google.gerrit.testutil.NoteDbMode;
@@ -301,6 +302,7 @@ public class GerritServer implements AutoCloseable {
},
site);
daemon.setEmailModuleForTesting(new FakeEmailSender.Module());
+ daemon.setAuditEventModuleForTesting(new FakeAuditService.Module());
daemon.setAdditionalSysModuleForTesting(testSysModule);
daemon.setEnableSshd(desc.useSsh());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java
new file mode 100644
index 0000000000..f6d4277d63
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.google.gerrit.acceptance.git;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.audit.AuditEvent;
+import java.util.Collections;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GitOverHttpServletIT extends AbstractPushForReview {
+
+ @Before
+ public void beforeEach() throws Exception {
+ CredentialsProvider.setDefault(
+ new UsernamePasswordCredentialsProvider(admin.username, admin.httpPassword));
+ selectProtocol(AbstractPushForReview.Protocol.HTTP);
+ auditService.clearEvents();
+ }
+
+ @Test
+ public void receivePackAuditEventLog() throws Exception {
+ testRepo
+ .git()
+ .push()
+ .setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:refs/for/master"))
+ .call();
+
+ // Git smart protocol makes two requests:
+ // https://github.com/git/git/blob/master/Documentation/technical/http-protocol.txt
+ assertThat(auditService.auditEvents.size()).isEqualTo(2);
+
+ AuditEvent e = auditService.auditEvents.get(1);
+ assertThat(e.who.getAccountId()).isEqualTo(admin.id);
+ assertThat(e.what).endsWith("/git-receive-pack");
+ assertThat(e.params).isEmpty();
+ }
+
+ @Test
+ public void uploadPackAuditEventLog() throws Exception {
+ testRepo.git().fetch().call();
+
+ assertThat(auditService.auditEvents.size()).isEqualTo(1);
+
+ AuditEvent e = auditService.auditEvents.get(0);
+ assertThat(e.who.toString()).isEqualTo("ANONYMOUS");
+ assertThat(e.params.get("service"))
+ .containsExactlyElementsIn(Collections.singletonList("git-upload-pack"));
+ assertThat(e.what).endsWith("service=git-upload-pack");
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 329beabfb7..0da2f92cd2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -15,8 +15,13 @@
package com.google.gerrit.httpd;
import com.google.common.cache.Cache;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
+import com.google.gerrit.audit.AuditService;
+import com.google.gerrit.audit.HttpAuditEvent;
+import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -141,6 +146,30 @@ public class GitOverHttpServlet extends GitServlet {
addReceivePackFilter(receiveFilter);
}
+ private static String extractWhat(HttpServletRequest request) {
+ StringBuilder commandName = new StringBuilder(request.getRequestURL());
+ if (request.getQueryString() != null) {
+ commandName.append("?").append(request.getQueryString());
+ }
+ return commandName.toString();
+ }
+
+ private static ListMultimap<String, String> extractParameters(HttpServletRequest request) {
+
+ ListMultimap<String, String> multiMap = ArrayListMultimap.create();
+ if (request.getQueryString() != null) {
+ request
+ .getParameterMap()
+ .forEach(
+ (k, v) -> {
+ for (int i = 0; i < v.length; i++) {
+ multiMap.put(k, v[i]);
+ }
+ });
+ }
+ return multiMap;
+ }
+
static class Resolver implements RepositoryResolver<HttpServletRequest> {
private final GitRepositoryManager manager;
private final PermissionBackend permissionBackend;
@@ -243,15 +272,21 @@ public class GitOverHttpServlet extends GitServlet {
private final VisibleRefFilter.Factory refFilterFactory;
private final UploadValidators.Factory uploadValidatorsFactory;
private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> userProvider;
+ private final AuditService auditService;
@Inject
UploadFilter(
VisibleRefFilter.Factory refFilterFactory,
UploadValidators.Factory uploadValidatorsFactory,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ Provider<CurrentUser> userProvider,
+ AuditService auditService) {
this.refFilterFactory = refFilterFactory;
this.uploadValidatorsFactory = uploadValidatorsFactory;
this.permissionBackend = permissionBackend;
+ this.userProvider = userProvider;
+ this.auditService = auditService;
}
@Override
@@ -276,7 +311,22 @@ public class GitOverHttpServlet extends GitServlet {
return;
} catch (PermissionBackendException e) {
throw new ServletException(e);
+ } finally {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+ auditService.dispatch(
+ new HttpAuditEvent(
+ httpRequest.getSession().getId(),
+ userProvider.get(),
+ extractWhat(httpRequest),
+ TimeUtil.nowMs(),
+ extractParameters(httpRequest),
+ httpRequest.getMethod(),
+ httpRequest,
+ httpResponse.getStatus(),
+ httpResponse));
}
+
// 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 =
@@ -331,13 +381,19 @@ public class GitOverHttpServlet extends GitServlet {
static class ReceiveFilter implements Filter {
private final Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache;
private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> userProvider;
+ private final AuditService auditService;
@Inject
ReceiveFilter(
@Named(ID_CACHE) Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ Provider<CurrentUser> userProvider,
+ AuditService auditService) {
this.cache = cache;
this.permissionBackend = permissionBackend;
+ this.userProvider = userProvider;
+ this.auditService = auditService;
}
@Override
@@ -365,6 +421,20 @@ public class GitOverHttpServlet extends GitServlet {
return;
} catch (PermissionBackendException e) {
throw new RuntimeException(e);
+ } finally {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+ auditService.dispatch(
+ new HttpAuditEvent(
+ httpRequest.getSession().getId(),
+ userProvider.get(),
+ extractWhat(httpRequest),
+ TimeUtil.nowMs(),
+ extractParameters(httpRequest),
+ httpRequest.getMethod(),
+ httpRequest,
+ httpResponse.getStatus(),
+ httpResponse));
}
Capable s = arc.canUpload();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index edebd9a1f5..5fd3968631 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -20,6 +20,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
+import com.google.gerrit.audit.AuditModule;
import com.google.gerrit.common.EventBroker;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.elasticsearch.ElasticIndexModule;
@@ -193,6 +194,7 @@ public class Daemon extends SiteProgram {
private AbstractModule luceneModule;
private Module emailModule;
private Module testSysModule;
+ private Module auditEventModule;
private Runnable serverStarted;
private IndexType indexType;
@@ -310,6 +312,11 @@ public class Daemon extends SiteProgram {
}
@VisibleForTesting
+ public void setAuditEventModuleForTesting(Module module) {
+ auditEventModule = module;
+ }
+
+ @VisibleForTesting
public void setLuceneModule(LuceneIndexModule m) {
luceneModule = m;
inMemoryTest = true;
@@ -421,6 +428,11 @@ public class Daemon extends SiteProgram {
} else {
modules.add(new SmtpEmailSender.Module());
}
+ if (auditEventModule != null) {
+ modules.add(auditEventModule);
+ } else {
+ modules.add(new AuditModule());
+ }
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PluginRestApiModule());
modules.add(new RestCacheAdminModule());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
index aedb8a7a28..d5bd8d86c7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
@@ -23,6 +23,6 @@ public class AuditModule extends AbstractModule {
protected void configure() {
DynamicSet.setOf(binder(), AuditListener.class);
DynamicSet.setOf(binder(), GroupMemberAuditListener.class);
- bind(AuditService.class);
+ bind(AuditService.class).to(AuditServiceImpl.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
index cc29559327..171ff45c16 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,76 +14,19 @@
package com.google.gerrit.audit;
-import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroupById;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
import java.util.Collection;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-@Singleton
-public class AuditService {
- private static final Logger log = LoggerFactory.getLogger(AuditService.class);
+public interface AuditService {
+ void dispatch(AuditEvent action);
- private final DynamicSet<AuditListener> auditListeners;
- private final DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners;
+ void dispatchAddAccountsToGroup(Account.Id actor, Collection<AccountGroupMember> added);
- @Inject
- public AuditService(
- DynamicSet<AuditListener> auditListeners,
- DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners) {
- this.auditListeners = auditListeners;
- this.groupMemberAuditListeners = groupMemberAuditListeners;
- }
+ void dispatchDeleteAccountsFromGroup(Account.Id actor, Collection<AccountGroupMember> removed);
- public void dispatch(AuditEvent action) {
- for (AuditListener auditListener : auditListeners) {
- auditListener.onAuditableAction(action);
- }
- }
+ void dispatchAddGroupsToGroup(Account.Id actor, Collection<AccountGroupById> added);
- public void dispatchAddAccountsToGroup(Account.Id actor, Collection<AccountGroupMember> added) {
- for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
- try {
- auditListener.onAddAccountsToGroup(actor, added);
- } catch (RuntimeException e) {
- log.error("failed to log add accounts to group event", e);
- }
- }
- }
-
- public void dispatchDeleteAccountsFromGroup(
- Account.Id actor, Collection<AccountGroupMember> removed) {
- for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
- try {
- auditListener.onDeleteAccountsFromGroup(actor, removed);
- } catch (RuntimeException e) {
- log.error("failed to log delete accounts from group event", e);
- }
- }
- }
-
- public void dispatchAddGroupsToGroup(Account.Id actor, Collection<AccountGroupById> added) {
- for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
- try {
- auditListener.onAddGroupsToGroup(actor, added);
- } catch (RuntimeException e) {
- log.error("failed to log add groups to group event", e);
- }
- }
- }
-
- public void dispatchDeleteGroupsFromGroup(
- Account.Id actor, Collection<AccountGroupById> removed) {
- for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
- try {
- auditListener.onDeleteGroupsFromGroup(actor, removed);
- } catch (RuntimeException e) {
- log.error("failed to log delete groups from group event", e);
- }
- }
- }
+ void dispatchDeleteGroupsFromGroup(Account.Id actor, Collection<AccountGroupById> removed);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditServiceImpl.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditServiceImpl.java
new file mode 100644
index 0000000000..940742fc9d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditServiceImpl.java
@@ -0,0 +1,94 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.google.gerrit.audit;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.Collection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class AuditServiceImpl implements AuditService {
+ private static final Logger log = LoggerFactory.getLogger(AuditServiceImpl.class);
+
+ private final DynamicSet<AuditListener> auditListeners;
+ private final DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners;
+
+ @Inject
+ public AuditServiceImpl(
+ DynamicSet<AuditListener> auditListeners,
+ DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners) {
+ this.auditListeners = auditListeners;
+ this.groupMemberAuditListeners = groupMemberAuditListeners;
+ }
+
+ @Override
+ public void dispatch(AuditEvent action) {
+ for (AuditListener auditListener : auditListeners) {
+ auditListener.onAuditableAction(action);
+ }
+ }
+
+ @Override
+ public void dispatchAddAccountsToGroup(Account.Id actor, Collection<AccountGroupMember> added) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onAddAccountsToGroup(actor, added);
+ } catch (RuntimeException e) {
+ log.error("failed to log add accounts to group event", e);
+ }
+ }
+ }
+
+ @Override
+ public void dispatchDeleteAccountsFromGroup(
+ Account.Id actor, Collection<AccountGroupMember> removed) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onDeleteAccountsFromGroup(actor, removed);
+ } catch (RuntimeException e) {
+ log.error("failed to log delete accounts from group event", e);
+ }
+ }
+ }
+
+ @Override
+ public void dispatchAddGroupsToGroup(Account.Id actor, Collection<AccountGroupById> added) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onAddGroupsToGroup(actor, added);
+ } catch (RuntimeException e) {
+ log.error("failed to log add groups to group event", e);
+ }
+ }
+ }
+
+ @Override
+ public void dispatchDeleteGroupsFromGroup(
+ Account.Id actor, Collection<AccountGroupById> removed) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ try {
+ auditListener.onDeleteGroupsFromGroup(actor, removed);
+ } catch (RuntimeException e) {
+ log.error("failed to log delete groups from group event", e);
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 2e2d675c26..773d4a9f8b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -17,7 +17,6 @@ package com.google.gerrit.server.config;
import static com.google.inject.Scopes.SINGLETON;
import com.google.common.cache.Cache;
-import com.google.gerrit.audit.AuditModule;
import com.google.gerrit.common.EventListener;
import com.google.gerrit.common.UserScopedEventListener;
import com.google.gerrit.extensions.annotations.Exports;
@@ -299,7 +298,6 @@ public class GerritGlobalModule extends FactoryModule {
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
bind(AccountControl.Factory.class);
- install(new AuditModule());
bind(UiActions.class);
install(new com.google.gerrit.server.access.Module());
install(new com.google.gerrit.server.account.Module());
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAuditService.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAuditService.java
new file mode 100644
index 0000000000..1eb5bdb680
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAuditService.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.google.gerrit.testutil;
+
+import com.google.gerrit.audit.AuditEvent;
+import com.google.gerrit.audit.AuditService;
+import com.google.gerrit.audit.GroupMemberAuditListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroupById;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@Singleton
+public class FakeAuditService implements AuditService {
+
+ private final DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners;
+
+ public static class Module extends AbstractModule {
+ @Override
+ public void configure() {
+ DynamicSet.setOf(binder(), GroupMemberAuditListener.class);
+ bind(AuditService.class).to(FakeAuditService.class);
+ }
+ }
+
+ @Inject
+ public FakeAuditService(DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners) {
+ this.groupMemberAuditListeners = groupMemberAuditListeners;
+ }
+
+ public List<AuditEvent> auditEvents = new ArrayList<>();
+
+ public void clearEvents() {
+ auditEvents.clear();
+ }
+
+ @Override
+ public void dispatch(AuditEvent action) {
+ auditEvents.add(action);
+ }
+
+ @Override
+ public void dispatchAddAccountsToGroup(Account.Id actor, Collection<AccountGroupMember> added) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ auditListener.onAddAccountsToGroup(actor, added);
+ }
+ }
+
+ @Override
+ public void dispatchDeleteAccountsFromGroup(
+ Account.Id actor, Collection<AccountGroupMember> removed) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ auditListener.onDeleteAccountsFromGroup(actor, removed);
+ }
+ }
+
+ @Override
+ public void dispatchAddGroupsToGroup(Account.Id actor, Collection<AccountGroupById> added) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ auditListener.onAddGroupsToGroup(actor, added);
+ }
+ }
+
+ @Override
+ public void dispatchDeleteGroupsFromGroup(
+ Account.Id actor, Collection<AccountGroupById> removed) {
+ for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
+ auditListener.onDeleteGroupsFromGroup(actor, removed);
+ }
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 5f144e504c..883515086d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -19,6 +19,7 @@ import static com.google.inject.Scopes.SINGLETON;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.audit.AuditModule;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
@@ -162,6 +163,7 @@ public class InMemoryModule extends FactoryModule {
install(new DefaultPermissionBackendModule());
install(new SearchingChangeCacheImpl.Module());
factory(GarbageCollection.Factory.class);
+ install(new AuditModule());
bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);