Allow empty group name when converting from ReviewDb

Empty group names may be problematic when used elsewhere in Gerrit, but
they are technically valid in ReviewDb, and various googlesource.com
hosts actually have them. Rather than attempting to change such names
during the migration, migrate them faithfully.

Still refuse to update a group name to the empty string in a running
server, however.

Change-Id: Ib3269bd2eb637c00561f3ce1ee470365909dc20c
This commit is contained in:
Dave Borowitz
2017-11-22 10:21:20 -05:00
parent 1fd50f5fe3
commit 42286a0c86
5 changed files with 51 additions and 8 deletions

View File

@@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.collect.Streams; import com.google.common.collect.Streams;
@@ -70,6 +71,7 @@ public class GroupConfig extends VersionedMetaData {
private Function<Account.Id, String> accountNameEmailRetriever = Account.Id::toString; private Function<Account.Id, String> accountNameEmailRetriever = Account.Id::toString;
private Function<AccountGroup.UUID, String> groupNameRetriever = AccountGroup.UUID::get; private Function<AccountGroup.UUID, String> groupNameRetriever = AccountGroup.UUID::get;
private boolean isLoaded = false; private boolean isLoaded = false;
private boolean allowSaveEmptyName;
private GroupConfig(AccountGroup.UUID groupUuid) { private GroupConfig(AccountGroup.UUID groupUuid) {
this.groupUuid = checkNotNull(groupUuid); this.groupUuid = checkNotNull(groupUuid);
@@ -106,6 +108,10 @@ public class GroupConfig extends VersionedMetaData {
this.groupCreation = Optional.of(groupCreation); this.groupCreation = Optional.of(groupCreation);
} }
void setAllowSaveEmptyName() {
this.allowSaveEmptyName = true;
}
public void setGroupUpdate( public void setGroupUpdate(
InternalGroupUpdate groupUpdate, InternalGroupUpdate groupUpdate,
Function<Account.Id, String> accountNameEmailRetriever, Function<Account.Id, String> accountNameEmailRetriever,
@@ -175,6 +181,11 @@ public class GroupConfig extends VersionedMetaData {
return false; return false;
} }
if (!allowSaveEmptyName && getNewName().equals(Optional.of(""))) {
throw new ConfigInvalidException(
String.format("Name of the group %s must be defined", groupUuid.get()));
}
Timestamp createdOn; Timestamp createdOn;
if (groupCreation.isPresent()) { if (groupCreation.isPresent()) {
createdOn = groupCreation.get().getCreatedOn(); createdOn = groupCreation.get().getCreatedOn();
@@ -217,6 +228,16 @@ public class GroupConfig extends VersionedMetaData {
checkState(isLoaded, "Group %s not loaded yet", groupUuid.get()); checkState(isLoaded, "Group %s not loaded yet", groupUuid.get());
} }
private Optional<String> getNewName() {
if (groupUpdate.isPresent()) {
return groupUpdate.get().getName().map(n -> Strings.nullToEmpty(n.get()));
}
if (groupCreation.isPresent()) {
return Optional.of(Strings.nullToEmpty(groupCreation.get().getNameKey().get()));
}
return Optional.empty();
}
private Config updateGroupProperties() throws IOException, ConfigInvalidException { private Config updateGroupProperties() throws IOException, ConfigInvalidException {
Config config = readConfig(GROUP_CONFIG_FILE); Config config = readConfig(GROUP_CONFIG_FILE);
groupCreation.ifPresent( groupCreation.ifPresent(

View File

@@ -55,10 +55,11 @@ enum GroupConfigEntry {
void readFromConfig(AccountGroup.UUID groupUuid, InternalGroup.Builder group, Config config) void readFromConfig(AccountGroup.UUID groupUuid, InternalGroup.Builder group, Config config)
throws ConfigInvalidException { throws ConfigInvalidException {
String name = config.getString(SECTION_NAME, null, super.keyName); String name = config.getString(SECTION_NAME, null, super.keyName);
if (Strings.isNullOrEmpty(name)) { // An empty name is invalid in NoteDb; GroupConfig will refuse to store it and it might be
throw new ConfigInvalidException( // unusable in permissions. But, it was technically valid in the ReviewDb storage layer, and
String.format("Name of the group %s must be defined", groupUuid.get())); // the NoteDb migration converted such groups faithfully, so we need to be able to read them
} // back here.
name = Strings.nullToEmpty(name);
group.setNameKey(new AccountGroup.NameKey(name)); group.setNameKey(new AccountGroup.NameKey(name));
} }

View File

@@ -118,6 +118,7 @@ public class GroupRebuilder {
throws IOException, ConfigInvalidException, OrmDuplicateKeyException { throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersRepo, bundle.uuid()); GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersRepo, bundle.uuid());
AccountGroup group = bundle.group(); AccountGroup group = bundle.group();
groupConfig.setAllowSaveEmptyName();
groupConfig.setGroupCreation( groupConfig.setGroupCreation(
InternalGroupCreation.builder() InternalGroupCreation.builder()
.setId(bundle.id()) .setId(bundle.id())

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.group.db; package com.google.gerrit.server.group.db;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.instanceOf;
import com.google.gerrit.common.TimeUtil; import com.google.gerrit.common.TimeUtil;
@@ -120,12 +121,19 @@ public class GroupConfigTest {
} }
@Test @Test
public void nameInConfigMustBeDefined() throws Exception { public void nameInConfigMayBeUndefined() throws Exception {
populateGroupConfig(groupUuid, "[group]\n\tid = 42\n\townerGroupUuid = owners\n"); populateGroupConfig(groupUuid, "[group]\n\tid = 42\n\townerGroupUuid = owners\n");
expectedException.expect(ConfigInvalidException.class); GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
expectedException.expectMessage("Name of the group users-XYZ"); assertThat(groupConfig.getLoadedGroup().get().getName()).isEmpty();
GroupConfig.loadForGroup(repository, groupUuid); }
@Test
public void nameInConfigMayBeEmpty() throws Exception {
populateGroupConfig(groupUuid, "[group]\n\tname=\n\tid = 42\n\townerGroupUuid = owners\n");
GroupConfig groupConfig = GroupConfig.loadForGroup(repository, groupUuid);
assertThat(groupConfig.getLoadedGroup().get().getName()).isEmpty();
} }
@Test @Test

View File

@@ -107,6 +107,18 @@ public class GroupRebuilderTest extends AbstractGroupTest {
assertServerCommit(log.get(0), "Create group"); assertServerCommit(log.get(0), "Create group");
} }
@Test
public void emptyGroupName() throws Exception {
AccountGroup g = newGroup("");
GroupBundle b = builder().group(g).build();
rebuilder.rebuild(repo, b, null);
GroupBundle noteDbBundle = reload(g);
assertThat(noteDbBundle).isEqualTo(b);
assertThat(noteDbBundle.group().getName()).isEmpty();
}
@Test @Test
public void membersAndSubgroups() throws Exception { public void membersAndSubgroups() throws Exception {
AccountGroup g = newGroup("a"); AccountGroup g = newGroup("a");