// 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.schema; import static com.google.gerrit.common.data.Permission.CREATE; import static com.google.gerrit.common.data.Permission.FORGE_AUTHOR; import static com.google.gerrit.common.data.Permission.FORGE_COMMITTER; import static com.google.gerrit.common.data.Permission.FORGE_SERVER; import static com.google.gerrit.common.data.Permission.LABEL; import static com.google.gerrit.common.data.Permission.OWNER; import static com.google.gerrit.common.data.Permission.PUSH; import static com.google.gerrit.common.data.Permission.PUSH_MERGE; import static com.google.gerrit.common.data.Permission.PUSH_TAG; import static com.google.gerrit.common.data.Permission.READ; import static com.google.gerrit.common.data.Permission.SUBMIT; import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.Permission; import com.google.gerrit.common.data.PermissionRule; import com.google.gerrit.extensions.common.InheritableBoolean; import com.google.gerrit.extensions.common.SubmitType; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.SystemConfig; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.account.GroupUUID; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.group.SystemGroupBackend; import com.google.gerrit.server.schema.Schema_77.LegacyLabelTypes; import com.google.gwtorm.jdbc.JdbcSchema; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; 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, GroupReference> groupMap; private LegacyLabelTypes labelTypes; private GroupReference projectOwners; private Map<Project.NameKey, Project.NameKey> parentsByProject; private Map<Project.NameKey, List<OldRefRight>> rightsByProject; private final String OLD_SUBMIT = "SUBM"; private final String OLD_READ = "READ"; private final String OLD_OWN = "OWN"; private final String OLD_PUSH_TAG = "pTAG"; private final String OLD_PUSH_HEAD = "pHD"; private final String OLD_FORGE_IDENTITY = "FORG"; @Inject Schema_53(Provider<Schema_52> prior, GitRepositoryManager mgr, @GerritPersonIdent PersonIdent serverUser) { super(prior); this.mgr = mgr; this.serverUser = serverUser; } @Override protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException { systemConfig = db.systemConfig().get(new SystemConfig.Key()); labelTypes = Schema_77.getLegacyTypes(db); assignGroupUUIDs(db); readOldRefRights(db); readProjectParents(db); exportProjectConfig(db); deleteActionCategories(db); } private void deleteActionCategories(ReviewDb db) throws OrmException { try { Statement stmt = ((JdbcSchema) db).getConnection().createStatement(); try { stmt.executeUpdate( "DELETE FROM approval_categories WHERE position < 0"); } finally { stmt.close(); } } catch (SQLException e) { throw new OrmException(e); } } private void assignGroupUUIDs(ReviewDb db) throws OrmException { groupMap = new HashMap<>(); List<AccountGroup> groups = db.accountGroups().all().toList(); for (AccountGroup g : groups) { if (g.getId().equals(systemConfig.ownerGroupId)) { g.setGroupUUID(SystemGroupBackend.PROJECT_OWNERS); projectOwners = GroupReference.forGroup(g); } else if (g.getId().equals(systemConfig.anonymousGroupId)) { g.setGroupUUID(SystemGroupBackend.ANONYMOUS_USERS); } else if (g.getId().equals(systemConfig.registeredGroupId)) { g.setGroupUUID(SystemGroupBackend.REGISTERED_USERS); } else { g.setGroupUUID(GroupUUID.make(g.getName(), serverUser)); } groupMap.put(g.getId(), GroupReference.forGroup(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).getUUID(); } 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()) { final String name = rs.getString("name"); final Project.NameKey nameKey = new Project.NameKey(name); Repository git; try { git = mgr.openRepository(nameKey); } catch (RepositoryNotFoundException notFound) { // A repository may be missing if this project existed only to store // inheritable permissions. For example 'All-Projects'. try { git = mgr.createRepository(nameKey); } catch (IOException err) { throw new OrmException("Cannot create repository " + name, err); } } catch (IOException e) { throw new OrmException(e); } try { MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, nameKey, git); md.getCommitBuilder().setAuthor(serverUser); md.getCommitBuilder().setCommitter(serverUser); ProjectConfig config = ProjectConfig.read(md); loadProject(rs, config.getProject()); config.getAccessSections().clear(); convertRights(config); // Grant out read on the config branch by default. // if (config.getProject().getNameKey().equals(systemConfig.wildProjectName)) { AccessSection meta = config.getAccessSection(RefNames.REFS_CONFIG, true); Permission read = meta.getPermission(READ, true); read.getRule(config.resolve(projectOwners), true); } md.setMessage("Import project configuration from SQL\n"); config.commit(md); } catch (ConfigInvalidException err) { throw new OrmException("Cannot read project " + name, err); } catch (IOException err) { throw new OrmException("Cannot export project " + name, err); } finally { git.close(); } } rs.close(); stmt.close(); } private void loadProject(ResultSet rs, Project project) throws SQLException, OrmException { project.setDescription(rs.getString("description")); project.setUseContributorAgreements(asInheritableBoolean(rs, "use_contributor_agreements")); switch (rs.getString("submit_type").charAt(0)) { case 'F': project.setSubmitType(SubmitType.FAST_FORWARD_ONLY); break; case 'M': project.setSubmitType(SubmitType.MERGE_IF_NECESSARY); break; case 'A': project.setSubmitType(SubmitType.MERGE_ALWAYS); break; case 'C': project.setSubmitType(SubmitType.CHERRY_PICK); break; default: throw new OrmException("Unsupported submit_type=" + rs.getString("submit_type") + " on project " + project.getName()); } project.setUseSignedOffBy(asInheritableBoolean(rs, "use_signed_off_by")); project.setRequireChangeID(asInheritableBoolean(rs, "require_change_id")); project.setUseContentMerge(asInheritableBoolean(rs, "use_content_merge")); project.setParentName(rs.getString("parent_name")); } private static InheritableBoolean asInheritableBoolean(ResultSet rs, String col) throws SQLException { return "Y".equals(rs.getString(col)) ? InheritableBoolean.TRUE : InheritableBoolean.INHERIT; } private void readOldRefRights(ReviewDb db) throws SQLException { rightsByProject = new HashMap<>(); Statement stmt = ((JdbcSchema) db).getConnection().createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM ref_rights"); while (rs.next()) { OldRefRight right = new OldRefRight(rs); if (right.group == null || right.category == null) { continue; } List<OldRefRight> list; list = rightsByProject.get(right.project); if (list == null) { list = new ArrayList<>(); rightsByProject.put(right.project, list); } list.add(right); } rs.close(); stmt.close(); } private void readProjectParents(ReviewDb db) throws SQLException { parentsByProject = new HashMap<>(); Statement stmt = ((JdbcSchema) db).getConnection().createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM projects"); while (rs.next()) { String name = rs.getString("name"); String parent_name = rs.getString("parent_name"); if (parent_name == null) { parent_name = systemConfig.wildProjectName.get(); } parentsByProject.put(new Project.NameKey(name), // new Project.NameKey(parent_name)); } rs.close(); stmt.close(); } private void convertRights(ProjectConfig config) { List<OldRefRight> myRights = rightsByProject.get(config.getProject().getNameKey()); if (myRights == null) { return; } for (OldRefRight old : myRights) { AccessSection section = config.getAccessSection(old.ref_pattern, true); GroupReference group = config.resolve(old.group); if (OLD_SUBMIT.equals(old.category)) { PermissionRule submit = rule(group); if (old.max_value <= 0) { submit.setDeny(); } add(section, SUBMIT, old.exclusive, submit); } else if (OLD_READ.equals(old.category)) { if (old.exclusive) { section.getPermission(READ, true).setExclusiveGroup(true); newChangePermission(config, old.ref_pattern).setExclusiveGroup(true); } PermissionRule read = rule(group); if (old.max_value <= 0) { read.setDeny(); } add(section, READ, old.exclusive, read); if (3 <= old.max_value) { newMergePermission(config, old.ref_pattern).add(rule(group)); } else if (3 <= inheritedMax(config, old)) { newMergePermission(config, old.ref_pattern).add(deny(group)); } if (2 <= old.max_value) { newChangePermission(config, old.ref_pattern).add(rule(group)); } else if (2 <= inheritedMax(config, old)) { newChangePermission(config, old.ref_pattern).add(deny(group)); } } else if (OLD_OWN.equals(old.category)) { add(section, OWNER, false, rule(group)); } else if (OLD_PUSH_TAG.equals(old.category)) { PermissionRule push = rule(group); if (old.max_value <= 0) { push.setDeny(); } add(section, PUSH_TAG, old.exclusive, push); } else if (OLD_PUSH_HEAD.equals(old.category)) { if (old.exclusive) { section.getPermission(PUSH, true).setExclusiveGroup(true); section.getPermission(CREATE, true).setExclusiveGroup(true); } PermissionRule push = rule(group); if (old.max_value <= 0) { push.setDeny(); } push.setForce(3 <= old.max_value); add(section, PUSH, old.exclusive, push); if (2 <= old.max_value) { add(section, CREATE, old.exclusive, rule(group)); } else if (2 <= inheritedMax(config, old)) { add(section, CREATE, old.exclusive, deny(group)); } } else if (OLD_FORGE_IDENTITY.equals(old.category)) { if (old.exclusive) { section.getPermission(FORGE_AUTHOR, true).setExclusiveGroup(true); section.getPermission(FORGE_COMMITTER, true).setExclusiveGroup(true); section.getPermission(FORGE_SERVER, true).setExclusiveGroup(true); } if (1 <= old.max_value) { add(section, FORGE_AUTHOR, old.exclusive, rule(group)); } if (2 <= old.max_value) { add(section, FORGE_COMMITTER, old.exclusive, rule(group)); } else if (2 <= inheritedMax(config, old)) { add(section, FORGE_COMMITTER, old.exclusive, deny(group)); } if (3 <= old.max_value) { add(section, FORGE_SERVER, old.exclusive, rule(group)); } else if (3 <= inheritedMax(config, old)) { add(section, FORGE_SERVER, old.exclusive, deny(group)); } } else { PermissionRule rule = rule(group); rule.setRange(old.min_value, old.max_value); if (old.min_value == 0 && old.max_value == 0) { rule.setDeny(); } LabelType type = labelTypes.byLabel(new LabelId(old.category)); String name = type != null ? type.getName() : old.category; add(section, LABEL + name, old.exclusive, rule); } } } private static Permission newChangePermission(ProjectConfig config, String name) { if (name.startsWith(AccessSection.REGEX_PREFIX)) { name = AccessSection.REGEX_PREFIX + "refs/for/" + name.substring(AccessSection.REGEX_PREFIX.length()); } else { name = "refs/for/" + name; } return config.getAccessSection(name, true).getPermission(PUSH, true); } private static Permission newMergePermission(ProjectConfig config, String name) { if (name.startsWith(AccessSection.REGEX_PREFIX)) { name = AccessSection.REGEX_PREFIX + "refs/for/" + name.substring(AccessSection.REGEX_PREFIX.length()); } else { name = "refs/for/" + name; } return config.getAccessSection(name, true).getPermission(PUSH_MERGE, true); } private static PermissionRule rule(GroupReference group) { return new PermissionRule(group); } private static PermissionRule deny(GroupReference group) { PermissionRule rule = rule(group); rule.setDeny(); return rule; } private int inheritedMax(ProjectConfig config, OldRefRight old) { int max = 0; String ref = old.ref_pattern; String category = old.category; AccountGroup.UUID group = old.group.getUUID(); Project.NameKey project = config.getProject().getParent(); if (project == null) { project = systemConfig.wildProjectName; } do { List<OldRefRight> rights = rightsByProject.get(project); if (rights != null) { for (OldRefRight r : rights) { if (r.ref_pattern.equals(ref) // && r.group.getUUID().equals(group) // && r.category.equals(category)) { max = Math.max(max, r.max_value); break; } } } project = parentsByProject.get(project); } while (!project.equals(systemConfig.wildProjectName)); return max; } private static void add(AccessSection section, String name, boolean exclusive, PermissionRule rule) { Permission p = section.getPermission(name, true); if (exclusive) { p.setExclusiveGroup(true); } p.add(rule); } private class OldRefRight { final int min_value; final int max_value; final String ref_pattern; final boolean exclusive; final GroupReference group; final String category; final Project.NameKey project; OldRefRight(ResultSet rs) throws SQLException { min_value = rs.getInt("min_value"); max_value = rs.getInt("max_value"); project = new Project.NameKey(rs.getString("project_name")); String r = rs.getString("ref_pattern"); exclusive = r.startsWith("-"); if (exclusive) { r = r.substring(1); } ref_pattern = r; category = rs.getString("category_id"); group = groupMap.get(new AccountGroup.Id(rs.getInt("group_id"))); } } }