ACLs for superproject subscriptions

This allows submodules of the superproject subscription feature to specify
fine grained, who is allowed to subscribe to it. See
Documentation/user-submodules.txt for the changed
handling of subscriptions.

The current tests were kept closely as-is, just enabling the subscription
feature. New tests have been written to test for the denial of superproject
subscriptions.

As of this change the superproject subscription table which was an
approximate cache for the subscriptions is dropped, and the superproject
subscription is performed as
 * parse the submodule ACL for potential superprojects
 * check the .gitmodules file in all potential superprojects for a real
   subscription
 * perform the superproject update if the supscription is valid

The cache worked semi-reliable (e.g. 2015-04-28 Submodule Subscriptions:
Remove subscriptions by deleting .gitmodules,
I1eaf452d5499397644e8eea3707a1352af89126d), so drop the cache and trade in
correctness over performance.

Bug: Issue 3311
Change-Id: Id74dc5a34a50b336a22005c96b79f3c4688a36ec
Signed-off-by: Stefan Beller <sbeller@google.com>
This commit is contained in:
Stefan Beller
2016-03-21 12:14:58 -07:00
parent 8cc252ef0c
commit c62f9fe511
23 changed files with 723 additions and 359 deletions

View File

@@ -32,7 +32,7 @@ import java.util.List;
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
public static final Class<Schema_119> C = Schema_119.class;
public static final Class<Schema_120> C = Schema_120.class;
public static int getBinaryVersion() {
return guessVersion(C);

View File

@@ -0,0 +1,114 @@
// Copyright (C) 2016 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 com.google.gerrit.common.data.SubscribeSection;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
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.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.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Schema_120 extends SchemaVersion {
private final GitRepositoryManager mgr;
@Inject
Schema_120(Provider<Schema_119> prior,
GitRepositoryManager mgr) {
super(prior);
this.mgr = mgr;
}
private void allowSubmoduleSubscription(Branch.NameKey subbranch,
Branch.NameKey superBranch) throws OrmException {
try (Repository git = mgr.openRepository(subbranch.getParentKey());
RevWalk rw = new RevWalk(git)) {
BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
try(MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED,
subbranch.getParentKey(), git, bru)) {
md.setMessage("Added superproject subscription during upgrade");
ProjectConfig pc = ProjectConfig.read(md);
SubscribeSection s = null;
for (SubscribeSection s1 : pc.getSubscribeSections(subbranch)) {
if (s.getProject() == superBranch.getParentKey()) {
s = s1;
}
}
if (s == null) {
s = new SubscribeSection(superBranch.getParentKey());
pc.addSubscribeSection(s);
}
RefSpec newRefSpec = new RefSpec(subbranch.get() + ":" + superBranch.get());
if (!s.getRefSpecs().contains(newRefSpec)) {
// For the migration we use only exact RefSpecs, we're not trying to
// generalize it.
s.addRefSpec(newRefSpec);
}
pc.commit(md);
}
bru.execute(rw, NullProgressMonitor.INSTANCE);
} catch (ConfigInvalidException | IOException e) {
throw new OrmException(e);
}
}
@Override
protected void migrateData(ReviewDb db, UpdateUI ui)
throws OrmException, SQLException {
ui.message("Generating Superproject subscriptions table to submodule ACLs");
try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT " +
"key.super_project.project_name, " +
"key.super_project.branch_name, " +
"submodule.project_name " +
"submodule.branch_name " +
"FROM submodule_subscriptions");) {
while (rs.next()) {
Project.NameKey superproject = new Project.NameKey(rs.getString(1));
Branch.NameKey superbranch = new Branch.NameKey(superproject,
rs.getString(2));
Project.NameKey submodule = new Project.NameKey(rs.getString(4));
Branch.NameKey subbranch = new Branch.NameKey(submodule,
rs.getString(5));
allowSubmoduleSubscription(subbranch, superbranch);
}
}
}
}