blob: c4cc8785c3ea2517354057b552d1dce4517af9fa [file] [log] [blame]
// Copyright (C) 2017 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.db;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.AccountGroupByIdAudit;
import com.google.gerrit.entities.AccountGroupMemberAudit;
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.server.account.GroupUuid;
import com.google.gerrit.server.account.externalids.storage.notedb.DisabledExternalIdCache;
import com.google.gerrit.server.notedb.NoteDbUtil;
import java.time.Instant;
import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link AuditLogReader}. */
public final class AuditLogReaderTest extends AbstractGroupTest {
private AuditLogReader auditLogReader;
@Before
public void setUp() throws Exception {
auditLogReader =
new AuditLogReader(
allUsersName, new NoteDbUtil(SERVER_ID, new DisabledExternalIdCache()), new Config());
}
@Test
public void createGroupAsUserIdent() throws Exception {
InternalGroup group = createGroupAsUser(1, "test-group");
AccountGroup.UUID uuid = group.getGroupUUID();
AccountGroupMemberAudit expAudit =
createExpMemberAudit(group.getId(), userId, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid)).containsExactly(expAudit);
}
@Test
public void createGroupAsServerIdent() throws Exception {
InternalGroup group = createGroup(1, "test-group", serverIdent, null);
assertThat(auditLogReader.getMembersAudit(allUsersRepo, group.getGroupUUID())).isEmpty();
}
@Test
public void addAndRemoveMember() throws Exception {
InternalGroup group = createGroupAsUser(1, "test-group");
AccountGroup.UUID uuid = group.getGroupUUID();
AccountGroupMemberAudit expAudit1 =
createExpMemberAudit(group.getId(), userId, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid)).containsExactly(expAudit1);
// User adds account 100002 to the group.
Account.Id id = Account.id(100002);
addMembers(uuid, ImmutableSet.of(id));
AccountGroupMemberAudit expAudit2 =
createExpMemberAudit(group.getId(), id, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(expAudit1, expAudit2)
.inOrder();
// User removes account 100002 from the group.
removeMembers(uuid, ImmutableSet.of(id));
expAudit2 = expAudit2.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(expAudit1, expAudit2)
.inOrder();
}
@Test
public void addMemberByUnknownAuthorAndRemoveMemberByKnownAuthor() throws Exception {
InternalGroup group = createGroupAsUser(1, "test-group");
AccountGroup.UUID uuid = group.getGroupUUID();
AccountGroupMemberAudit expAudit1 =
createExpMemberAudit(group.getId(), userId, userId, getTipTimestamp(uuid));
// An unidentified user adds account 100002 to the group.
Account.Id id = Account.id(100002);
addMembers(
uuid,
ImmutableSet.of(id),
new PersonIdent("Test ident", "random@gerrit"),
Optional.of(Instant.ofEpochSecond(1)));
// Identified user removes account 100002 from the group.
removeMembers(uuid, ImmutableSet.of(id));
AccountGroupMemberAudit expAudit2 =
createExpMemberAudit(
group.getId(), id, Account.UNKNOWN_ACCOUNT_ID, Instant.ofEpochSecond(1))
.toBuilder()
.removed(userId, getTipTimestamp(uuid))
.build();
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(expAudit1, expAudit2)
.inOrder();
}
@Test
public void addMemberByUnknownAuthorAndRemoveMemberByKnownAuthor_ignoreUnidentifiedUser()
throws Exception {
// This test doesn't repeat the previous test, because it doesn't produce the correct result.
// Instead this test adds and removes uses by an unidentified user and expects nothing in the
// output.
Config cfg = new Config();
cfg.setBoolean("groups", "auditLog", "ignoreRecordsFromUnidentifiedUsers", true);
auditLogReader =
new AuditLogReader(
allUsersName, new NoteDbUtil(SERVER_ID, new DisabledExternalIdCache()), cfg);
InternalGroup group = createGroupAsUser(1, "test-group");
AccountGroup.UUID uuid = group.getGroupUUID();
AccountGroupMemberAudit expAudit1 =
createExpMemberAudit(group.getId(), userId, userId, getTipTimestamp(uuid));
// An unidentified user adds account 100002 to the group.
Account.Id id = Account.id(100002);
addMembers(
uuid,
ImmutableSet.of(id),
new PersonIdent("Test ident", "random@gerrit"),
Optional.of(Instant.ofEpochSecond(1)));
// Identified user removes account 100002 from the group.
removeMembers(
uuid,
ImmutableSet.of(id),
new PersonIdent("Test ident", "random@gerrit"),
Optional.of(Instant.ofEpochSecond(100)));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid)).containsExactly(expAudit1);
}
@Test
public void addMultiMembers() throws Exception {
InternalGroup group = createGroupAsUser(1, "test-group");
AccountGroup.Id groupId = group.getId();
AccountGroup.UUID uuid = group.getGroupUUID();
AccountGroupMemberAudit expAudit1 =
createExpMemberAudit(groupId, userId, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid)).containsExactly(expAudit1);
Account.Id id1 = Account.id(100002);
Account.Id id2 = Account.id(100003);
addMembers(uuid, ImmutableSet.of(id1, id2));
AccountGroupMemberAudit expAudit2 =
createExpMemberAudit(groupId, id1, userId, getTipTimestamp(uuid));
AccountGroupMemberAudit expAudit3 =
createExpMemberAudit(groupId, id2, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(expAudit1, expAudit2, expAudit3)
.inOrder();
}
@Test
public void addAndRemoveSubgroups() throws Exception {
InternalGroup group = createGroupAsUser(1, "test-group");
AccountGroup.UUID uuid = group.getGroupUUID();
InternalGroup subgroup = createGroupAsUser(2, "test-group-2");
AccountGroup.UUID subgroupUuid = subgroup.getGroupUUID();
addSubgroups(uuid, ImmutableSet.of(subgroupUuid));
AccountGroupByIdAudit expAudit =
createExpGroupAudit(group.getId(), subgroupUuid, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid)).containsExactly(expAudit);
removeSubgroups(uuid, ImmutableSet.of(subgroupUuid));
expAudit = expAudit.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid)).containsExactly(expAudit);
}
@Test
public void addMultiSubgroups() throws Exception {
InternalGroup group = createGroupAsUser(1, "test-group");
AccountGroup.UUID uuid = group.getGroupUUID();
InternalGroup subgroup1 = createGroupAsUser(2, "test-group-2");
InternalGroup subgroup2 = createGroupAsUser(3, "test-group-3");
AccountGroup.UUID subgroupUuid1 = subgroup1.getGroupUUID();
AccountGroup.UUID subgroupUuid2 = subgroup2.getGroupUUID();
addSubgroups(uuid, ImmutableSet.of(subgroupUuid1, subgroupUuid2));
AccountGroupByIdAudit expAudit1 =
createExpGroupAudit(group.getId(), subgroupUuid1, userId, getTipTimestamp(uuid));
AccountGroupByIdAudit expAudit2 =
createExpGroupAudit(group.getId(), subgroupUuid2, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expAudit1, expAudit2)
.inOrder();
}
@Test
public void addAndRemoveMembersAndSubgroups() throws Exception {
InternalGroup group = createGroupAsUser(1, "test-group");
AccountGroup.Id groupId = group.getId();
AccountGroup.UUID uuid = group.getGroupUUID();
AccountGroupMemberAudit expMemberAudit =
createExpMemberAudit(groupId, userId, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid)).containsExactly(expMemberAudit);
Account.Id id1 = Account.id(100002);
Account.Id id2 = Account.id(100003);
Account.Id id3 = Account.id(100004);
InternalGroup subgroup1 = createGroupAsUser(2, "test-group-2");
InternalGroup subgroup2 = createGroupAsUser(3, "test-group-3");
InternalGroup subgroup3 = createGroupAsUser(4, "test-group-4");
AccountGroup.UUID subgroupUuid1 = subgroup1.getGroupUUID();
AccountGroup.UUID subgroupUuid2 = subgroup2.getGroupUUID();
AccountGroup.UUID subgroupUuid3 = subgroup3.getGroupUUID();
// Add two accounts.
addMembers(uuid, ImmutableSet.of(id1, id2));
AccountGroupMemberAudit expMemberAudit1 =
createExpMemberAudit(groupId, id1, userId, getTipTimestamp(uuid));
AccountGroupMemberAudit expMemberAudit2 =
createExpMemberAudit(groupId, id2, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(expMemberAudit, expMemberAudit1, expMemberAudit2)
.inOrder();
// Add one subgroup.
addSubgroups(uuid, ImmutableSet.of(subgroupUuid1));
AccountGroupByIdAudit expGroupAudit1 =
createExpGroupAudit(group.getId(), subgroupUuid1, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expGroupAudit1);
// Remove one account.
removeMembers(uuid, ImmutableSet.of(id2));
expMemberAudit2 = expMemberAudit2.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(expMemberAudit, expMemberAudit1, expMemberAudit2)
.inOrder();
// Add two subgroups.
addSubgroups(uuid, ImmutableSet.of(subgroupUuid2, subgroupUuid3));
AccountGroupByIdAudit expGroupAudit2 =
createExpGroupAudit(group.getId(), subgroupUuid2, userId, getTipTimestamp(uuid));
AccountGroupByIdAudit expGroupAudit3 =
createExpGroupAudit(group.getId(), subgroupUuid3, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expGroupAudit1, expGroupAudit2, expGroupAudit3)
.inOrder();
// Add two account, including a removed account.
addMembers(uuid, ImmutableSet.of(id2, id3));
AccountGroupMemberAudit expMemberAudit4 =
createExpMemberAudit(groupId, id2, userId, getTipTimestamp(uuid));
AccountGroupMemberAudit expMemberAudit3 =
createExpMemberAudit(groupId, id3, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getMembersAudit(allUsersRepo, uuid))
.containsExactly(
expMemberAudit, expMemberAudit1, expMemberAudit2, expMemberAudit4, expMemberAudit3)
.inOrder();
// Remove two subgroups.
removeSubgroups(uuid, ImmutableSet.of(subgroupUuid1, subgroupUuid3));
expGroupAudit1 = expGroupAudit1.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
expGroupAudit3 = expGroupAudit3.toBuilder().removed(userId, getTipTimestamp(uuid)).build();
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expGroupAudit1, expGroupAudit2, expGroupAudit3)
.inOrder();
// Add back one removed subgroup.
addSubgroups(uuid, ImmutableSet.of(subgroupUuid1));
AccountGroupByIdAudit expGroupAudit4 =
createExpGroupAudit(group.getId(), subgroupUuid1, userId, getTipTimestamp(uuid));
assertThat(auditLogReader.getSubgroupsAudit(allUsersRepo, uuid))
.containsExactly(expGroupAudit1, expGroupAudit2, expGroupAudit3, expGroupAudit4)
.inOrder();
}
private InternalGroup createGroupAsUser(int next, String groupName) throws Exception {
return createGroup(next, groupName, userIdent, userId);
}
private InternalGroup createGroup(
int next, String groupName, PersonIdent authorIdent, Account.Id authorId) throws Exception {
return testRefAction(
() -> {
InternalGroupCreation groupCreation =
InternalGroupCreation.builder()
.setGroupUUID(GroupUuid.make(groupName, serverIdent))
.setNameKey(AccountGroup.nameKey(groupName))
.setId(AccountGroup.id(next))
.build();
GroupDelta groupDelta =
authorIdent.equals(serverIdent)
? GroupDelta.builder().setDescription("Groups").build()
: GroupDelta.builder()
.setDescription("Groups")
.setMemberModification(members -> ImmutableSet.of(authorId))
.build();
GroupConfig groupConfig =
GroupConfig.createForNewGroup(allUsersName, allUsersRepo, groupCreation);
groupConfig.setGroupDelta(groupDelta, getAuditLogFormatter());
groupConfig.commit(createMetaDataUpdate(authorIdent));
return groupConfig
.getLoadedGroup()
.orElseThrow(() -> new IllegalStateException("create group failed"));
});
}
private void updateGroup(AccountGroup.UUID uuid, GroupDelta groupDelta) throws Exception {
updateGroup(uuid, groupDelta, userIdent);
}
private void updateGroup(AccountGroup.UUID uuid, GroupDelta groupDelta, PersonIdent authorIdent)
throws Exception {
testRefAction(
() -> {
GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersName, allUsersRepo, uuid);
groupConfig.setGroupDelta(groupDelta, getAuditLogFormatter());
groupConfig.commit(createMetaDataUpdate(authorIdent));
});
}
private void addMembers(AccountGroup.UUID groupUuid, Set<Account.Id> ids) throws Exception {
addMembers(groupUuid, ids, userIdent, Optional.empty());
}
private void addMembers(
AccountGroup.UUID groupUuid,
Set<Account.Id> ids,
PersonIdent authorIdent,
Optional<Instant> updatedOn)
throws Exception {
GroupDelta.Builder groupDelta =
GroupDelta.builder().setMemberModification(memberIds -> Sets.union(memberIds, ids));
if (updatedOn.isPresent()) {
groupDelta.setUpdatedOn(updatedOn.get());
}
updateGroup(groupUuid, groupDelta.build(), authorIdent);
}
private void removeMembers(AccountGroup.UUID groupUuid, Set<Account.Id> ids) throws Exception {
removeMembers(groupUuid, ids, userIdent, Optional.empty());
}
private void removeMembers(
AccountGroup.UUID groupUuid,
Set<Account.Id> ids,
PersonIdent authorIdent,
Optional<Instant> updatedOn)
throws Exception {
GroupDelta.Builder groupDelta =
GroupDelta.builder().setMemberModification(memberIds -> Sets.difference(memberIds, ids));
if (updatedOn.isPresent()) {
groupDelta.setUpdatedOn(updatedOn.get());
}
updateGroup(groupUuid, groupDelta.build(), authorIdent);
}
private void addSubgroups(AccountGroup.UUID groupUuid, Set<AccountGroup.UUID> uuids)
throws Exception {
GroupDelta groupDelta =
GroupDelta.builder()
.setSubgroupModification(memberIds -> Sets.union(memberIds, uuids))
.build();
updateGroup(groupUuid, groupDelta);
}
private void removeSubgroups(AccountGroup.UUID groupUuid, Set<AccountGroup.UUID> uuids)
throws Exception {
GroupDelta groupDelta =
GroupDelta.builder()
.setSubgroupModification(memberIds -> Sets.difference(memberIds, uuids))
.build();
updateGroup(groupUuid, groupDelta);
}
private static AccountGroupMemberAudit createExpMemberAudit(
AccountGroup.Id groupId, Account.Id id, Account.Id addedBy, Instant addedOn) {
return AccountGroupMemberAudit.builder()
.groupId(groupId)
.memberId(id)
.addedOn(addedOn)
.addedBy(addedBy)
.build();
}
private static AccountGroupByIdAudit createExpGroupAudit(
AccountGroup.Id groupId, AccountGroup.UUID uuid, Account.Id addedBy, Instant addedOn) {
return AccountGroupByIdAudit.builder()
.groupId(groupId)
.includeUuid(uuid)
.addedOn(addedOn)
.addedBy(addedBy)
.build();
}
}