Assign a unique UUID to each AccountGroup
UUIDs will be used later on to link access rules to groups, even if
the group gets renamed and the access rule file didn't get updated
with the new name yet.
To generate a UUID we take the creator of the group and the original
group name and hash them with SHA-1. This should produce a globally
unique identifier that is also time dependent, as the PersonIdent
has the current time embedded as part of the creator string.
The placeholder group 'Project Owners' is assigned a constant UUID,
as the concept of project ownership is the same across all servers
and the server interpreting the rules needs to replace this group
with the actual ownership list before evaluating them.
The magic groups 'Anonymous Users' and 'Registered Users' are also
assigned constant UUIDs, as this simplifies the definition of those
automatic membership groups within a server installation.
Change-Id: I47119b325418f88e089e136cee47593d82fbc754
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroup.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroup.java
index d2aceaa..f092ffa 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroup.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroup.java
@@ -46,6 +46,32 @@
}
}
+ /** Globally unique identifier. */
+ public static class UUID extends
+ StringKey<com.google.gwtorm.client.Key<?>> {
+ private static final long serialVersionUID = 1L;
+
+ @Column(id = 1, length = 40)
+ protected String uuid;
+
+ protected UUID() {
+ }
+
+ public UUID(final String n) {
+ uuid = n;
+ }
+
+ @Override
+ public String get() {
+ return uuid;
+ }
+
+ @Override
+ protected void set(String newValue) {
+ uuid = newValue;
+ }
+ }
+
/** Distinguished name, within organization directory server. */
public static class ExternalNameKey extends
StringKey<com.google.gwtorm.client.Key<?>> {
@@ -140,6 +166,18 @@
LDAP;
}
+ /** Common UUID assigned to the "Project Owners" placeholder group. */
+ public static final AccountGroup.UUID PROJECT_OWNERS =
+ new AccountGroup.UUID("global:Project-Owners");
+
+ /** Common UUID assigned to the "Anonymous Users" group. */
+ public static final AccountGroup.UUID ANONYMOUS_USERS =
+ new AccountGroup.UUID("global:Anonymous-Users");
+
+ /** Common UUID assigned to the "Registered Users" group. */
+ public static final AccountGroup.UUID REGISTERED_USERS =
+ new AccountGroup.UUID("global:Registered-Users");
+
/** Unique name of this group within the system. */
@Column(id = 1)
protected NameKey name;
@@ -176,15 +214,20 @@
@Column(id = 8)
protected boolean emailOnlyAuthors;
+ /** Globally unique identifier name for this group. */
+ @Column(id = 9)
+ protected UUID groupUUID;
+
protected AccountGroup() {
}
public AccountGroup(final AccountGroup.NameKey newName,
- final AccountGroup.Id newId) {
+ final AccountGroup.Id newId, final AccountGroup.UUID uuid) {
name = newName;
groupId = newId;
ownerGroupId = groupId;
visibleToAll = false;
+ groupUUID = uuid;
setType(Type.INTERNAL);
}
@@ -251,4 +294,12 @@
public void setEmailOnlyAuthors(boolean emailOnlyAuthors) {
this.emailOnlyAuthors = emailOnlyAuthors;
}
+
+ public AccountGroup.UUID getGroupUUID() {
+ return groupUUID;
+ }
+
+ public void setGroupUUID(AccountGroup.UUID uuid) {
+ groupUUID = uuid;
+ }
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroupAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroupAccess.java
index 2530654..7eb7ed2 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroupAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountGroupAccess.java
@@ -25,6 +25,9 @@
@PrimaryKey("groupId")
AccountGroup get(AccountGroup.Id id) throws OrmException;
+ @Query("WHERE groupUUID = ?")
+ ResultSet<AccountGroup> byUUID(AccountGroup.UUID uuid) throws OrmException;
+
@Query("WHERE externalName = ?")
ResultSet<AccountGroup> byExternalName(AccountGroup.ExternalNameKey name)
throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
index 6ff23ed..229d173 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
@@ -65,6 +65,8 @@
/** Identity of the administration group; those with full access. */
@Column(id = 4)
public AccountGroup.Id adminGroupId;
+ @Column(id = 10)
+ public AccountGroup.UUID adminGroupUUID;
/** Identity of the anonymous group, which permits anyone. */
@Column(id = 5)
@@ -81,6 +83,8 @@
/** Identity of the batch users group */
@Column(id = 8)
public AccountGroup.Id batchUsersGroupId;
+ @Column(id = 11)
+ public AccountGroup.UUID batchUsersGroupUUID;
/** Identity of the owner group, which permits any project owner. */
@Column(id = 9)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
index 978d9c2..6dce197 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
@@ -24,6 +24,8 @@
public AccountGroup get(AccountGroup.NameKey name);
+ public AccountGroup get(AccountGroup.UUID uuid);
+
public Collection<AccountGroup> get(AccountGroup.ExternalNameKey externalName);
public void evict(AccountGroup group);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
index d948aef..9fdd69a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -29,12 +29,14 @@
import com.google.inject.name.Named;
import java.util.Collection;
+import java.util.List;
/** Tracks group objects in memory for efficient access. */
@Singleton
public class GroupCacheImpl implements GroupCache {
private static final String BYID_NAME = "groups";
private static final String BYNAME_NAME = "groups_byname";
+ private static final String BYUUID_NAME = "groups_byuuid";
private static final String BYEXT_NAME = "groups_byext";
public static Module module() {
@@ -49,6 +51,10 @@
new TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>>() {};
core(byName, BYNAME_NAME).populateWith(ByNameLoader.class);
+ final TypeLiteral<Cache<AccountGroup.UUID, AccountGroup>> byUUID =
+ new TypeLiteral<Cache<AccountGroup.UUID, AccountGroup>>() {};
+ core(byUUID, BYUUID_NAME).populateWith(ByUUIDLoader.class);
+
final TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>> byExternalName =
new TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>>() {};
core(byExternalName, BYEXT_NAME) //
@@ -62,15 +68,18 @@
private final Cache<AccountGroup.Id, AccountGroup> byId;
private final Cache<AccountGroup.NameKey, AccountGroup> byName;
+ private final Cache<AccountGroup.UUID, AccountGroup> byUUID;
private final Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName;
@Inject
GroupCacheImpl(
@Named(BYID_NAME) Cache<AccountGroup.Id, AccountGroup> byId,
@Named(BYNAME_NAME) Cache<AccountGroup.NameKey, AccountGroup> byName,
+ @Named(BYUUID_NAME) Cache<AccountGroup.UUID, AccountGroup> byUUID,
@Named(BYEXT_NAME) Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName) {
this.byId = byId;
this.byName = byName;
+ this.byUUID = byUUID;
this.byExternalName = byExternalName;
}
@@ -81,6 +90,7 @@
public void evict(final AccountGroup group) {
byId.remove(group.getId());
byName.remove(group.getNameKey());
+ byUUID.remove(group.getGroupUUID());
byExternalName.remove(group.getExternalNameKey());
}
@@ -92,6 +102,10 @@
return byName.get(name);
}
+ public AccountGroup get(final AccountGroup.UUID uuid) {
+ return byUUID.get(uuid);
+ }
+
public Collection<AccountGroup> get(
final AccountGroup.ExternalNameKey externalName) {
return byExternalName.get(externalName);
@@ -126,7 +140,7 @@
public AccountGroup missing(final AccountGroup.Id key) {
final AccountGroup.NameKey name =
new AccountGroup.NameKey("Deleted Group" + key.toString());
- final AccountGroup g = new AccountGroup(name, key);
+ final AccountGroup g = new AccountGroup(name, key, null);
g.setType(AccountGroup.Type.SYSTEM);
g.setOwnerGroupId(administrators);
return g;
@@ -160,6 +174,32 @@
}
}
+ static class ByUUIDLoader extends
+ EntryCreator<AccountGroup.UUID, AccountGroup> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByUUIDLoader(final SchemaFactory<ReviewDb> sf) {
+ schema = sf;
+ }
+
+ @Override
+ public AccountGroup createEntry(final AccountGroup.UUID uuid)
+ throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ List<AccountGroup> r = db.accountGroups().byUUID(uuid).toList();
+ if (r.size() == 1) {
+ return r.get(0);
+ } else {
+ return null;
+ }
+ } finally {
+ db.close();
+ }
+ }
+ }
+
static class ByExternalNameLoader extends
EntryCreator<AccountGroup.ExternalNameKey, Collection<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupUUID.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupUUID.java
new file mode 100644
index 0000000..9eec1df
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupUUID.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2010 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.account;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.security.MessageDigest;
+
+public class GroupUUID {
+ public static AccountGroup.UUID make(String groupName, PersonIdent creator) {
+ MessageDigest md = Constants.newMessageDigest();
+ md.update(Constants.encode("group " + groupName + "\n"));
+ md.update(Constants.encode("creator " + creator.toExternalString() + "\n"));
+ return new AccountGroup.UUID(ObjectId.fromRaw(md.digest()).name());
+ }
+
+ private GroupUUID() {
+ }
+}
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 aab1cda..8e54077 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
@@ -23,11 +23,14 @@
import com.google.gerrit.reviewdb.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
+import org.eclipse.jgit.lib.PersonIdent;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -43,14 +46,18 @@
private final AccountCache accountCache;
private final GroupIncludeCache groupIncludeCache;
private final IdentifiedUser currentUser;
+ private final PersonIdent serverIdent;
@Inject
PerformCreateGroup(final ReviewDb db, final AccountCache accountCache,
- final GroupIncludeCache groupIncludeCache, final IdentifiedUser currentUser) {
+ final GroupIncludeCache groupIncludeCache,
+ final IdentifiedUser currentUser,
+ @GerritPersonIdent final PersonIdent serverIdent) {
this.db = db;
this.accountCache = accountCache;
this.groupIncludeCache = groupIncludeCache;
this.currentUser = currentUser;
+ this.serverIdent = serverIdent;
}
/**
@@ -81,7 +88,11 @@
final AccountGroup.Id groupId =
new AccountGroup.Id(db.nextAccountGroupId());
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
- final AccountGroup group = new AccountGroup(nameKey, groupId);
+ final AccountGroup.UUID uuid = GroupUUID.make(groupName,
+ currentUser.newCommitterIdent(
+ serverIdent.getWhen(),
+ serverIdent.getTimeZone()));
+ final AccountGroup group = new AccountGroup(nameKey, groupId, uuid);
group.setVisibleToAll(visibleToAll);
if (ownerGroupId != null) {
group.setOwnerGroupId(ownerGroupId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 9d3682b..295e5b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -24,6 +24,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.GroupUUID;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -132,10 +133,19 @@
}
}
+ private AccountGroup newGroup(ReviewDb c, String name, AccountGroup.UUID uuid)
+ throws OrmException {
+ if (uuid == null) {
+ uuid = GroupUUID.make(name, serverUser);
+ }
+ return new AccountGroup( //
+ new AccountGroup.NameKey(name), //
+ new AccountGroup.Id(c.nextAccountGroupId()), //
+ uuid);
+ }
+
private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException {
- final AccountGroup admin =
- new AccountGroup(new AccountGroup.NameKey("Administrators"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ final AccountGroup admin = newGroup(c, "Administrators", null);
admin.setDescription("Gerrit Site Administrators");
admin.setType(AccountGroup.Type.INTERNAL);
c.accountGroups().insert(Collections.singleton(admin));
@@ -143,8 +153,7 @@
Collections.singleton(new AccountGroupName(admin)));
final AccountGroup anonymous =
- new AccountGroup(new AccountGroup.NameKey("Anonymous Users"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ newGroup(c, "Anonymous Users", AccountGroup.ANONYMOUS_USERS);
anonymous.setDescription("Any user, signed-in or not");
anonymous.setOwnerGroupId(admin.getId());
anonymous.setType(AccountGroup.Type.SYSTEM);
@@ -153,8 +162,7 @@
Collections.singleton(new AccountGroupName(anonymous)));
final AccountGroup registered =
- new AccountGroup(new AccountGroup.NameKey("Registered Users"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ newGroup(c, "Registered Users", AccountGroup.REGISTERED_USERS);
registered.setDescription("Any signed-in user");
registered.setOwnerGroupId(admin.getId());
registered.setType(AccountGroup.Type.SYSTEM);
@@ -162,9 +170,7 @@
c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(registered)));
- final AccountGroup batchUsers =
- new AccountGroup(new AccountGroup.NameKey("Non-Interactive Users"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ final AccountGroup batchUsers = newGroup(c, "Non-Interactive Users", null);
batchUsers.setDescription("Users who perform batch actions on Gerrit");
batchUsers.setOwnerGroupId(admin.getId());
batchUsers.setType(AccountGroup.Type.INTERNAL);
@@ -173,8 +179,7 @@
Collections.singleton(new AccountGroupName(batchUsers)));
final AccountGroup owners =
- new AccountGroup(new AccountGroup.NameKey("Project Owners"),
- new AccountGroup.Id(c.nextAccountGroupId()));
+ newGroup(c, "Project Owners", AccountGroup.PROJECT_OWNERS);
owners.setDescription("Any owner of the project");
owners.setOwnerGroupId(admin.getId());
owners.setType(AccountGroup.Type.SYSTEM);
@@ -184,10 +189,17 @@
final SystemConfig s = SystemConfig.create();
s.registerEmailPrivateKey = SignedToken.generateRandomKey();
+
s.adminGroupId = admin.getId();
+ s.adminGroupUUID = admin.getGroupUUID();
+
s.anonymousGroupId = anonymous.getId();
+
s.registeredGroupId = registered.getId();
+
s.batchUsersGroupId = batchUsers.getId();
+ s.batchUsersGroupUUID = batchUsers.getGroupUUID();
+
s.ownerGroupId = owners.getId();
s.wildProjectName = DEFAULT_WILD_NAME;
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
index af45df3..87f881c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
@@ -14,9 +14,12 @@
package com.google.gerrit.server.schema;
+import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.GroupUUID;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.NoReplication;
@@ -35,11 +38,18 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
class Schema_53 extends SchemaVersion {
private final GitRepositoryManager mgr;
private final PersonIdent serverUser;
+ private SystemConfig systemConfig;
+ private Map<AccountGroup.Id, AccountGroup> groupMap;
+
@Inject
Schema_53(Provider<Schema_52> prior, GitRepositoryManager mgr,
@GerritPersonIdent PersonIdent serverUser) {
@@ -51,7 +61,42 @@
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
SQLException {
+ systemConfig = db.systemConfig().get(new SystemConfig.Key());
+ assignGroupUUIDs(db);
+ exportProjectConfig(db);
+ }
+ private void assignGroupUUIDs(ReviewDb db) throws OrmException {
+ groupMap = new HashMap<AccountGroup.Id, AccountGroup>();
+ List<AccountGroup> groups = db.accountGroups().all().toList();
+ for (AccountGroup g : groups) {
+ if (g.getId().equals(systemConfig.ownerGroupId)) {
+ g.setGroupUUID(AccountGroup.PROJECT_OWNERS);
+
+ } else if (g.getId().equals(systemConfig.anonymousGroupId)) {
+ g.setGroupUUID(AccountGroup.ANONYMOUS_USERS);
+
+ } else if (g.getId().equals(systemConfig.registeredGroupId)) {
+ g.setGroupUUID(AccountGroup.REGISTERED_USERS);
+
+ } else {
+ g.setGroupUUID(GroupUUID.make(g.getName(), serverUser));
+ }
+ groupMap.put(g.getId(), g);
+ }
+ db.accountGroups().update(groups);
+
+ systemConfig.adminGroupUUID = toUUID(systemConfig.adminGroupId);
+ systemConfig.batchUsersGroupUUID = toUUID(systemConfig.batchUsersGroupId);
+ db.systemConfig().update(Collections.singleton(systemConfig));
+ }
+
+ private AccountGroup.UUID toUUID(AccountGroup.Id id) {
+ return groupMap.get(id).getGroupUUID();
+ }
+
+ private void exportProjectConfig(ReviewDb db) throws OrmException,
+ SQLException {
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM projects ORDER BY name");
while (rs.next()) {