Honor reference level READ during replication
If reference level READ access is used in a repository, we should also honor it during replication based upon what the replication user can see within the repository. Change-Id: Ica62739cb17f497b847eeb8c1e7bf23dd978834e Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -25,6 +25,10 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class ReplicationUser extends CurrentUser {
|
public class ReplicationUser extends CurrentUser {
|
||||||
|
/** Magic set of groups enabling read of any project and reference. */
|
||||||
|
public static final Set<AccountGroup.Id> EVERYTHING_VISIBLE =
|
||||||
|
Collections.unmodifiableSet(new HashSet<AccountGroup.Id>(0));
|
||||||
|
|
||||||
public interface Factory {
|
public interface Factory {
|
||||||
ReplicationUser create(@Assisted Set<AccountGroup.Id> authGroups);
|
ReplicationUser create(@Assisted Set<AccountGroup.Id> authGroups);
|
||||||
}
|
}
|
||||||
@@ -36,7 +40,10 @@ public class ReplicationUser extends CurrentUser {
|
|||||||
@Assisted Set<AccountGroup.Id> authGroups) {
|
@Assisted Set<AccountGroup.Id> authGroups) {
|
||||||
super(AccessPath.REPLICATION, authConfig);
|
super(AccessPath.REPLICATION, authConfig);
|
||||||
|
|
||||||
if (authGroups.isEmpty()) {
|
if (authGroups == EVERYTHING_VISIBLE) {
|
||||||
|
effectiveGroups = EVERYTHING_VISIBLE;
|
||||||
|
|
||||||
|
} else if (authGroups.isEmpty()) {
|
||||||
// Only include the registered groups if no specific groups
|
// Only include the registered groups if no specific groups
|
||||||
// were provided. This allows an administrator to configure
|
// were provided. This allows an administrator to configure
|
||||||
// a replication user with a narrower view of the system than
|
// a replication user with a narrower view of the system than
|
||||||
@@ -63,4 +70,8 @@ public class ReplicationUser extends CurrentUser {
|
|||||||
public Set<Change.Id> getStarredChanges() {
|
public Set<Change.Id> getStarredChanges() {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEverythingVisible() {
|
||||||
|
return getEffectiveGroups() == EVERYTHING_VISIBLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,12 @@
|
|||||||
package com.google.gerrit.server.git;
|
package com.google.gerrit.server.git;
|
||||||
|
|
||||||
import com.google.gerrit.reviewdb.Project;
|
import com.google.gerrit.reviewdb.Project;
|
||||||
|
import com.google.gerrit.reviewdb.ReviewDb;
|
||||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||||
|
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||||
|
import com.google.gerrit.server.project.ProjectControl;
|
||||||
|
import com.google.gwtorm.client.OrmException;
|
||||||
|
import com.google.gwtorm.client.SchemaFactory;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
|
||||||
@@ -40,6 +45,8 @@ import org.slf4j.Logger;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -60,6 +67,7 @@ class PushOp implements ProjectRunnable {
|
|||||||
static final String MIRROR_ALL = "..all..";
|
static final String MIRROR_ALL = "..all..";
|
||||||
|
|
||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
|
private final SchemaFactory<ReviewDb> schema;
|
||||||
private final PushReplication.ReplicationConfig pool;
|
private final PushReplication.ReplicationConfig pool;
|
||||||
private final RemoteConfig config;
|
private final RemoteConfig config;
|
||||||
|
|
||||||
@@ -71,10 +79,11 @@ class PushOp implements ProjectRunnable {
|
|||||||
private Repository db;
|
private Repository db;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PushOp(final GitRepositoryManager grm,
|
PushOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> s,
|
||||||
final PushReplication.ReplicationConfig p, final RemoteConfig c,
|
final PushReplication.ReplicationConfig p, final RemoteConfig c,
|
||||||
@Assisted final Project.NameKey d, @Assisted final URIish u) {
|
@Assisted final Project.NameKey d, @Assisted final URIish u) {
|
||||||
repoManager = grm;
|
repoManager = grm;
|
||||||
|
schema = s;
|
||||||
pool = p;
|
pool = p;
|
||||||
config = c;
|
config = c;
|
||||||
projectName = d;
|
projectName = d;
|
||||||
@@ -198,9 +207,44 @@ class PushOp implements ProjectRunnable {
|
|||||||
|
|
||||||
private List<RemoteRefUpdate> generateUpdates(final Transport tn)
|
private List<RemoteRefUpdate> generateUpdates(final Transport tn)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
final List<RemoteRefUpdate> cmds = new ArrayList<RemoteRefUpdate>();
|
final ProjectControl pc;
|
||||||
final Map<String, Ref> local = db.getAllRefs();
|
try {
|
||||||
|
pc = pool.controlFor(projectName);
|
||||||
|
} catch (NoSuchProjectException e) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Ref> local = db.getAllRefs();
|
||||||
|
if (!pc.allRefsAreVisible()) {
|
||||||
|
if (!mirror) {
|
||||||
|
// If we aren't mirroring, reduce the space we need to filter
|
||||||
|
// to only the references we will update during this operation.
|
||||||
|
//
|
||||||
|
Map<String, Ref> n = new HashMap<String, Ref>();
|
||||||
|
for (String src : delta) {
|
||||||
|
Ref r = local.get(src);
|
||||||
|
if (r != null) {
|
||||||
|
n.put(src, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ReviewDb meta;
|
||||||
|
try {
|
||||||
|
meta = schema.open();
|
||||||
|
} catch (OrmException e) {
|
||||||
|
log.error("Cannot read database to replicate to " + projectName, e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
local = new VisibleRefFilter(db, pc, meta).filter(local);
|
||||||
|
} finally {
|
||||||
|
meta.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<RemoteRefUpdate> cmds = new ArrayList<RemoteRefUpdate>();
|
||||||
if (mirror) {
|
if (mirror) {
|
||||||
final Map<String, Ref> remote = listRemote(tn);
|
final Map<String, Ref> remote = listRemote(tn);
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,6 @@ public class PushReplication implements ReplicationQueue {
|
|||||||
private final Map<URIish, PushOp> pending = new HashMap<URIish, PushOp>();
|
private final Map<URIish, PushOp> pending = new HashMap<URIish, PushOp>();
|
||||||
private final PushOp.Factory opFactory;
|
private final PushOp.Factory opFactory;
|
||||||
private final ProjectControl.Factory projectControlFactory;
|
private final ProjectControl.Factory projectControlFactory;
|
||||||
private final boolean authEnabled;
|
|
||||||
|
|
||||||
ReplicationConfig(final Injector injector, final WorkQueue workQueue,
|
ReplicationConfig(final Injector injector, final WorkQueue workQueue,
|
||||||
final RemoteConfig rc, final Config cfg, SchemaFactory<ReviewDb> db,
|
final RemoteConfig rc, final Config cfg, SchemaFactory<ReviewDb> db,
|
||||||
@@ -319,9 +318,13 @@ public class PushReplication implements ReplicationQueue {
|
|||||||
|
|
||||||
String[] authGroupNames =
|
String[] authGroupNames =
|
||||||
cfg.getStringList("remote", rc.getName(), "authGroup");
|
cfg.getStringList("remote", rc.getName(), "authGroup");
|
||||||
authEnabled = authGroupNames.length > 0;
|
final Set<AccountGroup.Id> authGroups;
|
||||||
Set<AccountGroup.Id> authGroups = ConfigUtil.groupsFor(db, authGroupNames, log,
|
if (authGroupNames.length > 0) {
|
||||||
"Group \"{0}\" not in database, removing from authGroup");
|
authGroups = ConfigUtil.groupsFor(db, authGroupNames, //
|
||||||
|
log, "Group \"{0}\" not in database, removing from authGroup");
|
||||||
|
} else {
|
||||||
|
authGroups = ReplicationUser.EVERYTHING_VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
final ReplicationUser remoteUser =
|
final ReplicationUser remoteUser =
|
||||||
replicationUserFactory.create(authGroups);
|
replicationUserFactory.create(authGroups);
|
||||||
@@ -354,8 +357,7 @@ public class PushReplication implements ReplicationQueue {
|
|||||||
void schedule(final Project.NameKey project, final String ref,
|
void schedule(final Project.NameKey project, final String ref,
|
||||||
final URIish uri) {
|
final URIish uri) {
|
||||||
try {
|
try {
|
||||||
if (authEnabled
|
if (!controlFor(project).isVisible()) {
|
||||||
&& !projectControlFactory.controlFor(project).isVisible()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (NoSuchProjectException e1) {
|
} catch (NoSuchProjectException e1) {
|
||||||
@@ -374,6 +376,11 @@ public class PushReplication implements ReplicationQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProjectControl controlFor(final Project.NameKey project)
|
||||||
|
throws NoSuchProjectException {
|
||||||
|
return projectControlFactory.controlFor(project);
|
||||||
|
}
|
||||||
|
|
||||||
void notifyStarting(final PushOp op) {
|
void notifyStarting(final PushOp op) {
|
||||||
synchronized (pending) {
|
synchronized (pending) {
|
||||||
pending.remove(op.getURI());
|
pending.remove(op.getURI());
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.Change;
|
|||||||
import com.google.gerrit.reviewdb.Project;
|
import com.google.gerrit.reviewdb.Project;
|
||||||
import com.google.gerrit.reviewdb.RefRight;
|
import com.google.gerrit.reviewdb.RefRight;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.ReplicationUser;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
@@ -116,12 +117,20 @@ public class ProjectControl {
|
|||||||
|
|
||||||
/** Can this user see this project exists? */
|
/** Can this user see this project exists? */
|
||||||
public boolean isVisible() {
|
public boolean isVisible() {
|
||||||
return canPerformOnAnyRef(ApprovalCategory.READ, (short) 1);
|
return visibleForReplication()
|
||||||
|
|| canPerformOnAnyRef(ApprovalCategory.READ, (short) 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Can this user see all the refs in this projects? */
|
/** Can this user see all the refs in this projects? */
|
||||||
public boolean allRefsAreVisible() {
|
public boolean allRefsAreVisible() {
|
||||||
return canPerformOnAllRefs(ApprovalCategory.READ, (short) 1);
|
return visibleForReplication()
|
||||||
|
|| canPerformOnAllRefs(ApprovalCategory.READ, (short) 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Is this project completely visible for replication? */
|
||||||
|
boolean visibleForReplication() {
|
||||||
|
return getCurrentUser() instanceof ReplicationUser
|
||||||
|
&& ((ReplicationUser) getCurrentUser()).isEverythingVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
|
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ public class RefControl {
|
|||||||
|
|
||||||
/** Can this user see this reference exists? */
|
/** Can this user see this reference exists? */
|
||||||
public boolean isVisible() {
|
public boolean isVisible() {
|
||||||
return canPerform(READ, (short) 1);
|
return getProjectControl().visibleForReplication()
|
||||||
|
|| canPerform(READ, (short) 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user