diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java index b6549ea878..622ba6c171 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java @@ -15,6 +15,7 @@ package com.google.gerrit.httpd.rpc.account; import com.google.common.base.Strings; +import com.google.gerrit.audit.AuditService; import com.google.gerrit.common.ChangeHooks; import com.google.gerrit.common.data.AccountSecurity; import com.google.gerrit.common.data.ContributorAgreement; @@ -27,7 +28,6 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupMember; -import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit; import com.google.gerrit.reviewdb.client.ContactInformation; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; @@ -71,6 +71,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements private final ChangeHooks hooks; private final GroupCache groupCache; + private final AuditService auditService; @Inject AccountSecurityImpl(final Provider schema, @@ -82,7 +83,8 @@ class AccountSecurityImpl extends BaseServiceImplementation implements final ChangeUserName.CurrentUser changeUserNameFactory, final DeleteExternalIds.Factory deleteExternalIdsFactory, final ExternalIdDetailFactory.Factory externalIdDetailFactory, - final ChangeHooks hooks, final GroupCache groupCache) { + final ChangeHooks hooks, final GroupCache groupCache, + final AuditService auditService) { super(schema, currentUser); contactStore = cs; realm = r; @@ -92,6 +94,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements byEmailCache = abec; accountCache = uac; accountManager = am; + this.auditService = auditService; useContactInfo = contactStore != null && contactStore.isEnabled(); @@ -198,9 +201,8 @@ class AccountSecurityImpl extends BaseServiceImplementation implements AccountGroupMember m = db.accountGroupMembers().get(key); if (m == null) { m = new AccountGroupMember(key); - db.accountGroupMembersAudit().insert( - Collections.singleton(new AccountGroupMemberAudit( - m, account.getId(), TimeUtil.nowTs()))); + auditService.dispatchAddAccountsToGroup(account.getId(), Collections + .singleton(m)); db.accountGroupMembers().insert(Collections.singleton(m)); accountCache.evict(m.getAccountId()); } 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 dc870acfc5..89b51f81f4 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 @@ -22,6 +22,7 @@ public class AuditModule extends AbstractModule { @Override protected void configure() { DynamicSet.setOf(binder(), AuditListener.class); + DynamicSet.setOf(binder(), GroupMemberAuditListener.class); bind(AuditService.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 a992aa130a..baafb8963a 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 @@ -15,16 +15,30 @@ 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collection; + @Singleton public class AuditService { + private static final Logger log = LoggerFactory.getLogger(AuditService.class); + private final DynamicSet auditListeners; + private final DynamicSet groupMemberAuditListeners; @Inject - public AuditService(DynamicSet auditListeners) { + public AuditService(DynamicSet auditListeners, + DynamicSet groupMemberAuditListeners) { this.auditListeners = auditListeners; + this.groupMemberAuditListeners = groupMemberAuditListeners; } public void dispatch(AuditEvent action) { @@ -32,4 +46,48 @@ public class AuditService { auditListener.onAuditableAction(action); } } + + public void dispatchAddAccountsToGroup(Account.Id actor, + Collection 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 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 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 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/audit/GroupMemberAuditListener.java b/gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java new file mode 100644 index 0000000000..edf23928fe --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/audit/GroupMemberAuditListener.java @@ -0,0 +1,37 @@ +// Copyright (C) 2014 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.annotations.ExtensionPoint; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.Account.Id; +import com.google.gerrit.reviewdb.client.AccountGroupById; +import com.google.gerrit.reviewdb.client.AccountGroupMember; + +import java.util.Collection; + +@ExtensionPoint +public interface GroupMemberAuditListener { + + void onAddAccountsToGroup(Account.Id actor, + Collection added); + + void onDeleteAccountsFromGroup(Account.Id actor, + Collection removed); + + void onAddGroupsToGroup(Id actor, Collection added); + + void onDeleteGroupsFromGroup(Id actor, Collection deleted); +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java index 77ebe0f352..67dda03a86 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java @@ -14,6 +14,8 @@ package com.google.gerrit.server.account; +import com.google.gerrit.audit.AuditService; +import com.google.gerrit.common.auth.openid.OpenIdUrls; import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.Permission; @@ -23,7 +25,6 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupMember; -import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.project.ProjectCache; @@ -53,6 +54,7 @@ public class AccountManager { private final ChangeUserName.Factory changeUserNameFactory; private final ProjectCache projectCache; private final AtomicBoolean awaitsFirstAccountCheck; + private final AuditService auditService; @Inject AccountManager(final SchemaFactory schema, @@ -60,7 +62,8 @@ public class AccountManager { final Realm accountMapper, final IdentifiedUser.GenericFactory userFactory, final ChangeUserName.Factory changeUserNameFactory, - final ProjectCache projectCache) throws OrmException { + final ProjectCache projectCache, + final AuditService auditService) throws OrmException { this.schema = schema; this.byIdCache = byIdCache; this.byEmailCache = byEmailCache; @@ -69,6 +72,7 @@ public class AccountManager { this.changeUserNameFactory = changeUserNameFactory; this.projectCache = projectCache; this.awaitsFirstAccountCheck = new AtomicBoolean(true); + this.auditService = auditService; } /** @@ -227,8 +231,7 @@ public class AccountManager { final AccountGroup.Id adminId = g.getId(); final AccountGroupMember m = new AccountGroupMember(new AccountGroupMember.Key(newId, adminId)); - db.accountGroupMembersAudit().insert(Collections.singleton( - new AccountGroupMemberAudit(m, newId, TimeUtil.nowTs()))); + auditService.dispatchAddAccountsToGroup(newId, Collections.singleton(m)); db.accountGroupMembers().insert(Collections.singleton(m)); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java index 2d42f0d3dc..7537c7090b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java @@ -15,6 +15,7 @@ package com.google.gerrit.server.account; import com.google.common.collect.Sets; +import com.google.gerrit.audit.AuditService; import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.GroupDescriptions; import com.google.gerrit.common.errors.InvalidSshKeyException; @@ -30,7 +31,6 @@ import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupMember; -import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit; import com.google.gerrit.reviewdb.client.AccountSshKey; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.IdentifiedUser; @@ -74,13 +74,14 @@ public class CreateAccount implements RestModifyView { private final AccountByEmailCache byEmailCache; private final AccountInfo.Loader.Factory infoLoader; private final String username; + private final AuditService auditService; @Inject CreateAccount(ReviewDb db, Provider currentUser, GroupsCollection groupsCollection, SshKeyCache sshKeyCache, AccountCache accountCache, AccountByEmailCache byEmailCache, AccountInfo.Loader.Factory infoLoader, - @Assisted String username) { + @Assisted String username, AuditService auditService) { this.db = db; this.currentUser = currentUser; this.groupsCollection = groupsCollection; @@ -89,6 +90,7 @@ public class CreateAccount implements RestModifyView { this.byEmailCache = byEmailCache; this.infoLoader = infoLoader; this.username = username; + this.auditService = auditService; } @Override @@ -169,9 +171,8 @@ public class CreateAccount implements RestModifyView { for (AccountGroup.Id groupId : groups) { AccountGroupMember m = new AccountGroupMember(new AccountGroupMember.Key(id, groupId)); - db.accountGroupMembersAudit().insert(Collections.singleton( - new AccountGroupMemberAudit( - m, currentUser.get().getAccountId(), TimeUtil.nowTs()))); + auditService.dispatchAddAccountsToGroup(currentUser.get().getAccountId(), + Collections.singleton(m)); db.accountGroupMembers().insert(Collections.singleton(m)); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java index a54a97b5f2..bd533aa174 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java @@ -14,19 +14,17 @@ package com.google.gerrit.server.account; +import com.google.gerrit.audit.AuditService; import com.google.gerrit.common.errors.NameAlreadyUsedException; import com.google.gerrit.common.errors.PermissionDeniedException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupById; -import com.google.gerrit.reviewdb.client.AccountGroupByIdAud; import com.google.gerrit.reviewdb.client.AccountGroupMember; -import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit; import com.google.gerrit.reviewdb.client.AccountGroupName; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.util.TimeUtil; import com.google.gwtorm.server.OrmDuplicateKeyException; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; @@ -52,12 +50,13 @@ public class PerformCreateGroup { private final PersonIdent serverIdent; private final GroupCache groupCache; private final CreateGroupArgs createGroupArgs; + private final AuditService auditService; @Inject PerformCreateGroup(ReviewDb db, AccountCache accountCache, GroupIncludeCache groupIncludeCache, IdentifiedUser currentUser, @GerritPersonIdent PersonIdent serverIdent, GroupCache groupCache, - @Assisted CreateGroupArgs createGroupArgs) { + @Assisted CreateGroupArgs createGroupArgs, AuditService auditService) { this.db = db; this.accountCache = accountCache; this.groupIncludeCache = groupIncludeCache; @@ -65,6 +64,7 @@ public class PerformCreateGroup { this.serverIdent = serverIdent; this.groupCache = groupCache; this.createGroupArgs = createGroupArgs; + this.auditService = auditService; } /** @@ -127,18 +127,13 @@ public class PerformCreateGroup { private void addMembers(final AccountGroup.Id groupId, final Collection members) throws OrmException { List memberships = new ArrayList<>(); - List membershipsAudit = new ArrayList<>(); for (Account.Id accountId : members) { final AccountGroupMember membership = new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId)); memberships.add(membership); - - final AccountGroupMemberAudit audit = new AccountGroupMemberAudit( - membership, currentUser.getAccountId(), TimeUtil.nowTs()); - membershipsAudit.add(audit); } db.accountGroupMembers().insert(memberships); - db.accountGroupMembersAudit().insert(membershipsAudit); + auditService.dispatchAddAccountsToGroup(currentUser.getAccountId(), memberships); for (Account.Id accountId : members) { accountCache.evict(accountId); @@ -148,18 +143,13 @@ public class PerformCreateGroup { private void addGroups(final AccountGroup.Id groupId, final Collection groups) throws OrmException { List includeList = new ArrayList<>(); - List includesAudit = new ArrayList<>(); for (AccountGroup.UUID includeUUID : groups) { final AccountGroupById groupInclude = new AccountGroupById(new AccountGroupById.Key(groupId, includeUUID)); includeList.add(groupInclude); - - final AccountGroupByIdAud audit = new AccountGroupByIdAud( - groupInclude, currentUser.getAccountId(), TimeUtil.nowTs()); - includesAudit.add(audit); } db.accountGroupById().insert(includeList); - db.accountGroupByIdAud().insert(includesAudit); + auditService.dispatchAddGroupsToGroup(currentUser.getAccountId(), includeList); for (AccountGroup.UUID uuid : groups) { groupIncludeCache.evictMemberIn(uuid); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java index 614138ae50..361a773ee4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java @@ -18,6 +18,7 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.gerrit.audit.AuditService; import com.google.gerrit.common.data.GroupDescription; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.DefaultInput; @@ -27,14 +28,12 @@ import com.google.gerrit.extensions.restapi.UnprocessableEntityException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupById; -import com.google.gerrit.reviewdb.client.AccountGroupByIdAud; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.account.GroupIncludeCache; import com.google.gerrit.server.group.AddIncludedGroups.Input; import com.google.gerrit.server.group.GroupJson.GroupInfo; -import com.google.gerrit.server.util.TimeUtil; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; @@ -75,15 +74,17 @@ public class AddIncludedGroups implements RestModifyView { private final GroupIncludeCache groupIncludeCache; private final Provider db; private final GroupJson json; + private final AuditService auditService; @Inject public AddIncludedGroups(GroupsCollection groupsCollection, - GroupIncludeCache groupIncludeCache, - Provider db, GroupJson json) { + GroupIncludeCache groupIncludeCache, Provider db, + GroupJson json, AuditService auditService) { this.groupsCollection = groupsCollection; this.groupIncludeCache = groupIncludeCache; this.db = db; this.json = json; + this.auditService = auditService; } @Override @@ -98,7 +99,6 @@ public class AddIncludedGroups implements RestModifyView { GroupControl control = resource.getControl(); Map newIncludedGroups = Maps.newHashMap(); - List newIncludedGroupsAudits = Lists.newLinkedList(); List result = Lists.newLinkedList(); Account.Id me = ((IdentifiedUser) control.getCurrentUser()).getAccountId(); @@ -117,15 +117,13 @@ public class AddIncludedGroups implements RestModifyView { if (agi == null) { agi = new AccountGroupById(agiKey); newIncludedGroups.put(d.getGroupUUID(), agi); - newIncludedGroupsAudits.add( - new AccountGroupByIdAud(agi, me, TimeUtil.nowTs())); } } result.add(json.format(d)); } if (!newIncludedGroups.isEmpty()) { - db.get().accountGroupByIdAud().insert(newIncludedGroupsAudits); + auditService.dispatchAddGroupsToGroup(me, newIncludedGroups.values()); db.get().accountGroupById().insert(newIncludedGroups.values()); for (AccountGroupById agi : newIncludedGroups.values()) { groupIncludeCache.evictMemberIn(agi.getIncludeUUID()); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java index e720af0c7f..cf5625d341 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java @@ -17,6 +17,7 @@ package com.google.gerrit.server.group; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.gerrit.audit.AuditService; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.DefaultInput; import com.google.gerrit.extensions.restapi.MethodNotAllowedException; @@ -25,7 +26,6 @@ import com.google.gerrit.extensions.restapi.UnprocessableEntityException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupMember; -import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit; import com.google.gerrit.reviewdb.client.AuthType; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.IdentifiedUser; @@ -39,7 +39,6 @@ import com.google.gerrit.server.account.AuthRequest; import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.group.AddMembers.Input; -import com.google.gerrit.server.util.TimeUtil; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; @@ -82,6 +81,7 @@ public class AddMembers implements RestModifyView { private final AccountCache accountCache; private final AccountInfo.Loader.Factory infoFactory; private final Provider db; + private final AuditService auditService; @Inject AddMembers(AccountManager accountManager, @@ -90,8 +90,10 @@ public class AddMembers implements RestModifyView { AccountResolver accountResolver, AccountCache accountCache, AccountInfo.Loader.Factory infoFactory, - Provider db) { + Provider db, + AuditService auditService) { this.accountManager = accountManager; + this.auditService = auditService; this.authType = authConfig.getAuthType(); this.accounts = accounts; this.accountResolver = accountResolver; @@ -112,7 +114,6 @@ public class AddMembers implements RestModifyView { GroupControl control = resource.getControl(); Map newAccountGroupMembers = Maps.newHashMap(); - List newAccountGroupMemberAudits = Lists.newLinkedList(); List result = Lists.newLinkedList(); Account.Id me = ((IdentifiedUser) control.getCurrentUser()).getAccountId(); AccountInfo.Loader loader = infoFactory.create(true); @@ -135,14 +136,12 @@ public class AddMembers implements RestModifyView { if (m == null) { m = new AccountGroupMember(key); newAccountGroupMembers.put(m.getAccountId(), m); - newAccountGroupMemberAudits.add( - new AccountGroupMemberAudit(m, me, TimeUtil.nowTs())); } } result.add(loader.get(a.getId())); } - db.get().accountGroupMembersAudit().insert(newAccountGroupMemberAudits); + auditService.dispatchAddAccountsToGroup(me, newAccountGroupMembers.values()); db.get().accountGroupMembers().insert(newAccountGroupMembers.values()); for (AccountGroupMember m : newAccountGroupMembers.values()) { accountCache.evict(m.getAccountId()); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java new file mode 100644 index 0000000000..b921224435 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java @@ -0,0 +1,201 @@ +// Copyright (C) 2014 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.server.group; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.gerrit.audit.GroupMemberAuditListener; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.reviewdb.client.AccountGroupById; +import com.google.gerrit.reviewdb.client.AccountGroupByIdAud; +import com.google.gerrit.reviewdb.client.AccountGroupMember; +import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.account.GroupCache; +import com.google.gerrit.server.account.UniversalGroupBackend; +import com.google.gerrit.server.util.TimeUtil; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.slf4j.Logger; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class DbGroupMemberAuditListener implements GroupMemberAuditListener { + private static final Logger log = org.slf4j.LoggerFactory + .getLogger(DbGroupMemberAuditListener.class); + + private final Provider db; + private final AccountCache accountCache; + private final GroupCache groupCache; + private final UniversalGroupBackend groupBackend; + + @Inject + public DbGroupMemberAuditListener(Provider db, + AccountCache accountCache, GroupCache groupCache, + UniversalGroupBackend groupBackend) { + this.db = db; + this.accountCache = accountCache; + this.groupCache = groupCache; + this.groupBackend = groupBackend; + } + + @Override + public void onAddAccountsToGroup(Account.Id me, + Collection added) { + List auditInserts = Lists.newLinkedList(); + for (AccountGroupMember m : added) { + AccountGroupMemberAudit audit = + new AccountGroupMemberAudit(m, me, TimeUtil.nowTs()); + auditInserts.add(audit); + } + try { + db.get().accountGroupMembersAudit().insert(auditInserts); + } catch (OrmException e) { + logOrmExceptionForAccounts( + "Cannot log add accounts to group event performed by user", me, + added, e); + } + } + + @Override + public void onDeleteAccountsFromGroup(Account.Id me, + Collection removed) { + List auditInserts = Lists.newLinkedList(); + List auditUpdates = Lists.newLinkedList(); + ReviewDb reviewDB = db.get(); + try { + for (AccountGroupMember m : removed) { + AccountGroupMemberAudit audit = null; + for (AccountGroupMemberAudit a : reviewDB.accountGroupMembersAudit() + .byGroupAccount(m.getAccountGroupId(), m.getAccountId())) { + if (a.isActive()) { + audit = a; + break; + } + } + + if (audit != null) { + audit.removed(me, TimeUtil.nowTs()); + auditUpdates.add(audit); + } else { + audit = new AccountGroupMemberAudit(m, me, TimeUtil.nowTs()); + audit.removedLegacy(); + auditInserts.add(audit); + } + } + reviewDB.accountGroupMembersAudit().update(auditUpdates); + reviewDB.accountGroupMembersAudit().insert(auditInserts); + } catch (OrmException e) { + logOrmExceptionForAccounts( + "Cannot log delete accounts from group event performed by user", me, + removed, e); + } + } + + @Override + public void onAddGroupsToGroup(Account.Id me, + Collection added) { + List includesAudit = new ArrayList<>(); + for (AccountGroupById groupInclude : added) { + AccountGroupByIdAud audit = + new AccountGroupByIdAud(groupInclude, me, TimeUtil.nowTs()); + includesAudit.add(audit); + } + try { + db.get().accountGroupByIdAud().insert(includesAudit); + } catch (OrmException e) { + logOrmExceptionForGroups( + "Cannot log add groups to group event performed by user", me, added, + e); + } + } + + @Override + public void onDeleteGroupsFromGroup(Account.Id me, + Collection removed) { + final List auditUpdates = Lists.newLinkedList(); + try { + for (final AccountGroupById g : removed) { + AccountGroupByIdAud audit = null; + for (AccountGroupByIdAud a : db.get().accountGroupByIdAud() + .byGroupInclude(g.getGroupId(), g.getIncludeUUID())) { + if (a.isActive()) { + audit = a; + break; + } + } + + if (audit != null) { + audit.removed(me, TimeUtil.nowTs()); + auditUpdates.add(audit); + } + } + db.get().accountGroupByIdAud().update(auditUpdates); + } catch (OrmException e) { + logOrmExceptionForGroups( + "Cannot log delete groups from group event performed by user", me, + removed, e); + } + } + + private void logOrmExceptionForAccounts(String header, Account.Id me, + Collection values, OrmException e) { + List descriptions = new ArrayList<>(); + for (AccountGroupMember m : values) { + Account.Id accountId = m.getAccountId(); + String userName = accountCache.get(accountId).getUserName(); + AccountGroup.Id groupId = m.getAccountGroupId(); + String groupName = groupCache.get(groupId).getName(); + + descriptions.add(MessageFormat.format("account {0}/{1}, group {2}/{3}", + accountId, userName, groupId, groupName)); + } + logOrmException(header, me, descriptions, e); + } + + private void logOrmExceptionForGroups(String header, Account.Id me, + Collection values, OrmException e) { + List descriptions = new ArrayList<>(); + for (AccountGroupById m : values) { + AccountGroup.UUID groupUuid = m.getIncludeUUID(); + String groupName = groupBackend.get(groupUuid).getName(); + AccountGroup.Id targetGroupId = m.getGroupId(); + String targetGroupName = groupCache.get(targetGroupId).getName(); + + descriptions.add(MessageFormat.format("group {0}/{1}, group {2}/{3}", + groupUuid, groupName, targetGroupId, targetGroupName)); + } + logOrmException(header, me, descriptions, e); + } + + private void logOrmException(String header, Account.Id me, + Iterable values, OrmException e) { + StringBuilder message = new StringBuilder(header); + message.append(" "); + message.append(me); + message.append("/"); + message.append(accountCache.get(me).getUserName()); + message.append(": "); + message.append(Joiner.on("; ").join(values)); + log.error(message.toString(), e); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java index 555744ee1d..74d5946847 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java @@ -17,6 +17,7 @@ package com.google.gerrit.server.group; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.gerrit.audit.AuditService; import com.google.gerrit.common.data.GroupDescription; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.MethodNotAllowedException; @@ -26,14 +27,12 @@ import com.google.gerrit.extensions.restapi.UnprocessableEntityException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupById; -import com.google.gerrit.reviewdb.client.AccountGroupByIdAud; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.account.GroupIncludeCache; import com.google.gerrit.server.group.AddIncludedGroups.Input; -import com.google.gerrit.server.util.TimeUtil; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; @@ -48,16 +47,17 @@ public class DeleteIncludedGroups implements RestModifyView db; private final Provider self; + private final AuditService auditService; @Inject DeleteIncludedGroups(GroupsCollection groupsCollection, - GroupIncludeCache groupIncludeCache, - Provider db, - Provider self) { + GroupIncludeCache groupIncludeCache, Provider db, + Provider self, AuditService auditService) { this.groupsCollection = groupsCollection; this.groupIncludeCache = groupIncludeCache; this.db = db; this.self = self; + this.auditService = auditService; } @Override @@ -109,27 +109,10 @@ public class DeleteIncludedGroups implements RestModifyView toBeRemoved) + private void writeAudits(final List toRemoved) throws OrmException { final Account.Id me = ((IdentifiedUser) self.get()).getAccountId(); - final List auditUpdates = Lists.newLinkedList(); - for (final AccountGroupById g : toBeRemoved) { - AccountGroupByIdAud audit = null; - for (AccountGroupByIdAud a : db.get() - .accountGroupByIdAud().byGroupInclude(g.getGroupId(), - g.getIncludeUUID())) { - if (a.isActive()) { - audit = a; - break; - } - } - - if (audit != null) { - audit.removed(me, TimeUtil.nowTs()); - auditUpdates.add(audit); - } - } - db.get().accountGroupByIdAud().update(auditUpdates); + auditService.dispatchDeleteGroupsFromGroup(me, toRemoved); } @Singleton diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java index 654ad884b4..605933b918 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java @@ -16,6 +16,7 @@ package com.google.gerrit.server.group; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.gerrit.audit.AuditService; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.MethodNotAllowedException; import com.google.gerrit.extensions.restapi.Response; @@ -24,7 +25,6 @@ import com.google.gerrit.extensions.restapi.UnprocessableEntityException; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroupMember; -import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; @@ -32,7 +32,6 @@ import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountsCollection; import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.group.AddMembers.Input; -import com.google.gerrit.server.util.TimeUtil; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; @@ -47,15 +46,18 @@ public class DeleteMembers implements RestModifyView { private final AccountCache accountCache; private final Provider db; private final Provider self; + private final AuditService auditService; @Inject DeleteMembers(AccountsCollection accounts, AccountCache accountCache, Provider db, - Provider self) { + Provider self, + AuditService auditService) { this.accounts = accounts; this.accountCache = accountCache; this.db = db; this.self = self; + this.auditService = auditService; } @Override @@ -94,32 +96,9 @@ public class DeleteMembers implements RestModifyView { return Response.none(); } - private void writeAudits(final List toBeRemoved) - throws OrmException { + private void writeAudits(final List toRemove) { final Account.Id me = ((IdentifiedUser) self.get()).getAccountId(); - final List auditUpdates = Lists.newLinkedList(); - final List auditInserts = Lists.newLinkedList(); - for (final AccountGroupMember m : toBeRemoved) { - AccountGroupMemberAudit audit = null; - for (AccountGroupMemberAudit a : db.get().accountGroupMembersAudit() - .byGroupAccount(m.getAccountGroupId(), m.getAccountId())) { - if (a.isActive()) { - audit = a; - break; - } - } - - if (audit != null) { - audit.removed(me, TimeUtil.nowTs()); - auditUpdates.add(audit); - } else { - audit = new AccountGroupMemberAudit(m, me, TimeUtil.nowTs()); - audit.removedLegacy(); - auditInserts.add(audit); - } - } - db.get().accountGroupMembersAudit().update(auditUpdates); - db.get().accountGroupMembersAudit().insert(auditInserts); + auditService.dispatchDeleteAccountsFromGroup(me, toRemove); } private Map getMembers( diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java index 97338f1f65..9b5d9abae0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/Module.java @@ -18,7 +18,9 @@ import static com.google.gerrit.server.group.GroupResource.GROUP_KIND; import static com.google.gerrit.server.group.IncludedGroupResource.INCLUDED_GROUP_KIND; import static com.google.gerrit.server.group.MemberResource.MEMBER_KIND; +import com.google.gerrit.audit.GroupMemberAuditListener; import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.restapi.RestApiModule; import com.google.gerrit.server.group.AddIncludedGroups.UpdateIncludedGroup; import com.google.gerrit.server.group.AddMembers.UpdateMember; @@ -65,5 +67,9 @@ public class Module extends RestApiModule { delete(INCLUDED_GROUP_KIND).to(DeleteIncludedGroup.class); install(new FactoryModuleBuilder().build(CreateGroup.Factory.class)); + + DynamicSet.bind(binder(), GroupMemberAuditListener.class).to( + DbGroupMemberAuditListener.class); + } }