blob: e4aae2cd6df046e5e6bdf4d767eb11d950ebbee5 [file] [log] [blame]
// 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.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
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.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<AccountGroup.Id, GroupReference>();
List<AccountGroup> groups = db.accountGroups().all().toList();
for (AccountGroup g : groups) {
if (g.getId().equals(systemConfig.ownerGroupId)) {
g.setGroupUUID(AccountGroup.PROJECT_OWNERS);
projectOwners = GroupReference.forGroup(g);
} 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(), 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(GitRepositoryManager.REF_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(Project.SubmitType.FAST_FORWARD_ONLY);
break;
case 'M':
project.setSubmitType(Project.SubmitType.MERGE_IF_NECESSARY);
break;
case 'A':
project.setSubmitType(Project.SubmitType.MERGE_ALWAYS);
break;
case 'C':
project.setSubmitType(Project.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))
? Project.InheritableBoolean.TRUE
: Project.InheritableBoolean.INHERIT;
}
private void readOldRefRights(ReviewDb db) throws SQLException {
rightsByProject = new HashMap<Project.NameKey, List<OldRefRight>>();
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<OldRefRight>();
rightsByProject.put(right.project, list);
}
list.add(right);
}
rs.close();
stmt.close();
}
private void readProjectParents(ReviewDb db) throws SQLException {
parentsByProject = new HashMap<Project.NameKey, Project.NameKey>();
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")));
}
}
}