Merge branch 'stable-2.15-2020-11.notedb-refs-tags' into stable-2.15
* stable-2.15-2020-11.notedb-refs-tags: Set version to 2.15.22-SNAPSHOT Set version to 2.15.21 Workaround Gitiles bug on All-Users visibility Set version to 2.15.21-SNAPSHOT Set version to 2.15.20 Fetch JGit documentation from the archive site Remove generation for c.g.gwtexpui.* JavaDoc Make PermissionBackend#ForRef authoritative Validate Gerrit changes on stable-2.15 with Jenkins Fix tests for stable-2.15 branch Change-Id: I91db12c2c627550b2e897ccb4d7e27ee760cd32d
This commit is contained in:
1
Jenkinsfile
vendored
Normal file
1
Jenkinsfile
vendored
Normal file
@@ -0,0 +1 @@
|
||||
gerritPipeline()
|
||||
@@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.google.gerrit</groupId>
|
||||
<artifactId>gerrit-acceptance-framework</artifactId>
|
||||
<version>2.15.20-SNAPSHOT</version>
|
||||
<version>2.15.22-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>Gerrit Code Review - Acceptance Test Framework</name>
|
||||
<description>Framework for Gerrit's acceptance tests</description>
|
||||
|
||||
@@ -157,6 +157,29 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
|
||||
pushTagDeletion(tagName, Status.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTagForExistingCommit_withoutGlobalReadPermissions() throws Exception {
|
||||
removeReadAccessOnRefsStar();
|
||||
grantReadAccessOnRefsHeadsStar();
|
||||
createTagForExistingCommit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTagForNewCommit_withoutGlobalReadPermissions() throws Exception {
|
||||
removeReadAccessOnRefsStar();
|
||||
grantReadAccessOnRefsHeadsStar();
|
||||
createTagForNewCommit();
|
||||
}
|
||||
|
||||
private void removeReadAccessOnRefsStar() throws Exception {
|
||||
removePermission(allProjects, "refs/heads/*", Permission.READ);
|
||||
removePermission(project, "refs/heads/*", Permission.READ);
|
||||
}
|
||||
|
||||
private void grantReadAccessOnRefsHeadsStar() throws Exception {
|
||||
grant(project, "refs/heads/*", Permission.READ, false, REGISTERED_USERS);
|
||||
}
|
||||
|
||||
private String pushTagForExistingCommit(Status expectedStatus) throws Exception {
|
||||
return pushTag(null, false, false, expectedStatus);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
// Copyright (C) 2020 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.acceptance.rest.project;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
|
||||
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
|
||||
import static com.google.gerrit.testutil.GerritJUnit.assertThrows;
|
||||
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.TestAccount;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
import com.google.gerrit.extensions.api.changes.DraftInput;
|
||||
import com.google.gerrit.extensions.api.projects.BranchInfo;
|
||||
import com.google.gerrit.extensions.api.projects.TagInfo;
|
||||
import com.google.gerrit.extensions.api.projects.TagInput;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.testutil.NoteDbMode;
|
||||
import com.google.inject.Inject;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GetBranchIT extends AbstractDaemonTest {
|
||||
@Inject protected AllUsersName allUsers;
|
||||
|
||||
@Test
|
||||
public void cannotGetNonExistingBranch() {
|
||||
assertBranchNotFound(project, RefNames.fullName("non-existing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBranch() throws Exception {
|
||||
setApiUser(user);
|
||||
assertBranchFound(project, RefNames.fullName("master"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBranchByShortName() throws Exception {
|
||||
setApiUser(user);
|
||||
assertBranchFound(project, "master");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotGetNonVisibleBranch() throws Exception {
|
||||
String branchName = "master";
|
||||
|
||||
// block read access to the branch
|
||||
block(project, RefNames.fullName(branchName), Permission.READ, ANONYMOUS_USERS);
|
||||
|
||||
setApiUser(user);
|
||||
assertBranchNotFound(project, RefNames.fullName(branchName));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotGetNonVisibleBranchByShortName() throws Exception {
|
||||
String branchName = "master";
|
||||
|
||||
// block read access to the branch
|
||||
block(project, RefNames.fullName(branchName), Permission.READ, ANONYMOUS_USERS);
|
||||
|
||||
setApiUser(user);
|
||||
assertBranchNotFound(project, branchName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangeRef() throws Exception {
|
||||
// create a change
|
||||
Change.Id changeId = createChange("refs/for/master").getPatchSetId().changeId;
|
||||
|
||||
// a user without the 'Access Database' capability can see the change ref
|
||||
setApiUser(user);
|
||||
String changeRef = RefNames.patchSetRef(new PatchSet.Id(changeId, 1));
|
||||
assertBranchFound(project, changeRef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangeRefOfNonVisibleChange() throws Exception {
|
||||
// create a change
|
||||
String branchName = "master";
|
||||
Change.Id changeId = createChange("refs/for/" + branchName).getPatchSetId().changeId;
|
||||
|
||||
// block read access to the branch
|
||||
block(project, RefNames.fullName(branchName), Permission.READ, ANONYMOUS_USERS);
|
||||
|
||||
// a user without the 'Access Database' capability cannot see the change ref
|
||||
setApiUser(user);
|
||||
String changeRef = RefNames.patchSetRef(new PatchSet.Id(changeId, 1));
|
||||
assertBranchNotFound(project, changeRef);
|
||||
|
||||
// a user with the 'Access Database' capability can see the change ref
|
||||
testGetRefWithAccessDatabase(project, changeRef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangeEditRef() throws Exception {
|
||||
TestAccount user2 = accountCreator.user2();
|
||||
|
||||
// create a change
|
||||
Change.Id changeId = createChange("refs/for/master").getPatchSetId().changeId;
|
||||
|
||||
// create a change edit by 'user'
|
||||
setApiUser(user);
|
||||
gApi.changes().id(changeId.get()).edit().create();
|
||||
|
||||
// every user can see their own change edit refs
|
||||
String changeEditRef = RefNames.refsEdit(user.id, changeId, new PatchSet.Id(changeId, 1));
|
||||
assertBranchFound(project, changeEditRef);
|
||||
|
||||
// a user without the 'Access Database' capability cannot see the change edit ref of another
|
||||
// user
|
||||
setApiUser(user2);
|
||||
assertBranchNotFound(project, changeEditRef);
|
||||
|
||||
// a user with the 'Access Database' capability can see the change edit ref of another user
|
||||
testGetRefWithAccessDatabase(project, changeEditRef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotGetChangeEditRefOfNonVisibleChange() throws Exception {
|
||||
// create a change
|
||||
String branchName = "master";
|
||||
Change.Id changeId = createChange("refs/for/" + branchName).getPatchSetId().changeId;
|
||||
|
||||
// create a change edit by 'user'
|
||||
setApiUser(user);
|
||||
gApi.changes().id(changeId.get()).edit().create();
|
||||
|
||||
// make the change non-visible by blocking read access on the destination
|
||||
block(project, RefNames.fullName(branchName), Permission.READ, ANONYMOUS_USERS);
|
||||
|
||||
// user cannot see their own change edit refs if the change is no longer visible
|
||||
String changeEditRef = RefNames.refsEdit(user.id, changeId, new PatchSet.Id(changeId, 1));
|
||||
assertBranchNotFound(project, changeEditRef);
|
||||
|
||||
// a user with the 'Access Database' capability can see the change edit ref
|
||||
testGetRefWithAccessDatabase(project, changeEditRef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChangeMetaRef() throws Exception {
|
||||
// create a change
|
||||
Change.Id changeId = createChange("refs/for/master").getPatchSetId().changeId;
|
||||
|
||||
// A user without the 'Access Database' capability can see the change meta ref.
|
||||
// This may be surprising, as 'Access Database' guards access to meta refs and the change meta
|
||||
// ref is a meta ref, however change meta refs have been always visible to all users that can
|
||||
// see the change and some tools rely on seeing these refs, so we have to keep the current
|
||||
// behaviour.
|
||||
setApiUser(user);
|
||||
String changeMetaRef = RefNames.changeMetaRef(changeId);
|
||||
if (NoteDbMode.get().ordinal() >= NoteDbMode.WRITE.ordinal()) {
|
||||
// Branch is there when we write to NoteDb
|
||||
assertBranchFound(project, changeMetaRef);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRefsMetaConfig() throws Exception {
|
||||
// a non-project owner cannot get the refs/meta/config branch
|
||||
setApiUser(user);
|
||||
assertBranchNotFound(project, RefNames.REFS_CONFIG);
|
||||
|
||||
// a non-project owner cannot get the refs/meta/config branch even with the 'Access Database'
|
||||
// capability
|
||||
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
|
||||
try {
|
||||
assertBranchNotFound(project, RefNames.REFS_CONFIG);
|
||||
} finally {
|
||||
removeGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
|
||||
}
|
||||
|
||||
setApiUser(user);
|
||||
|
||||
// a project owner can get the refs/meta/config branch
|
||||
allow(project, "refs/*", Permission.OWNER, REGISTERED_USERS);
|
||||
assertBranchFound(project, RefNames.REFS_CONFIG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUserRefOfOtherUser() throws Exception {
|
||||
String userRef = RefNames.refsUsers(admin.id);
|
||||
|
||||
// a user without the 'Access Database' capability cannot see the user ref of another user
|
||||
setApiUser(user);
|
||||
assertBranchNotFound(allUsers, userRef);
|
||||
|
||||
// a user with the 'Access Database' capability can see the user ref of another user
|
||||
testGetRefWithAccessDatabase(allUsers, userRef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOwnUserRef() throws Exception {
|
||||
// every user can see the own user ref
|
||||
setApiUser(user);
|
||||
assertBranchFound(allUsers, RefNames.refsUsers(user.id));
|
||||
|
||||
// TODO: every user can see the own user ref via the magic ref/users/self ref
|
||||
// setApiUser(user);
|
||||
// assertBranchFound(allUsers, RefNames.REFS_USERS_SELF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExternalIdsRefs() throws Exception {
|
||||
// a user without the 'Access Database' capability cannot see the refs/meta/external-ids ref
|
||||
setApiUser(user);
|
||||
assertBranchNotFound(allUsers, RefNames.REFS_EXTERNAL_IDS);
|
||||
|
||||
// a user with the 'Access Database' capability can see the refs/meta/external-ids ref
|
||||
testGetRefWithAccessDatabase(allUsers, RefNames.REFS_EXTERNAL_IDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGroupRef() throws Exception {
|
||||
// Groups were not yet in NoteDb in this release.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGroupNamesRef() throws Exception {
|
||||
// Groups were not yet in NoteDb in this release.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDeletedGroupRef() throws Exception {
|
||||
// Groups were not yet in NoteDb in this release.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDraftCommentsRef() throws Exception {
|
||||
TestAccount user2 = accountCreator.user2();
|
||||
|
||||
// create a change
|
||||
String fileName = "a.txt";
|
||||
Change change = createChange("A Change", fileName, "content").getChange().change();
|
||||
|
||||
// create a draft comment by the by 'user'
|
||||
setApiUser(user);
|
||||
DraftInput draftInput = new DraftInput();
|
||||
draftInput.path = fileName;
|
||||
draftInput.line = 0;
|
||||
draftInput.message = "Some Comment";
|
||||
gApi.changes().id(change.getChangeId()).current().createDraft(draftInput);
|
||||
|
||||
// every user can see their own draft comments refs
|
||||
// TODO: is this a bug?
|
||||
String draftCommentsRef = RefNames.refsDraftComments(change.getId(), user.id);
|
||||
if (NoteDbMode.get().ordinal() >= NoteDbMode.WRITE.ordinal()) {
|
||||
// Branch is there when we write to NoteDb
|
||||
assertBranchFound(allUsers, draftCommentsRef);
|
||||
}
|
||||
|
||||
// a user without the 'Access Database' capability cannot see the draft comments ref of another
|
||||
// user
|
||||
setApiUser(user2);
|
||||
assertBranchNotFound(allUsers, draftCommentsRef);
|
||||
|
||||
// a user with the 'Access Database' capability can see the draft comments ref of another user
|
||||
if (NoteDbMode.get().ordinal() >= NoteDbMode.WRITE.ordinal()) {
|
||||
// Branch is there when we write to NoteDb
|
||||
testGetRefWithAccessDatabase(allUsers, draftCommentsRef);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getStarredChangesRef() throws Exception {
|
||||
TestAccount user2 = accountCreator.user2();
|
||||
|
||||
// create a change
|
||||
Change change = createChange().getChange().change();
|
||||
|
||||
// let user star the change
|
||||
setApiUser(user);
|
||||
gApi.accounts().self().starChange(Integer.toString(change.getChangeId()));
|
||||
|
||||
// every user can see their own starred changes refs
|
||||
// TODO: is this a bug?
|
||||
String starredChangesRef = RefNames.refsStarredChanges(change.getId(), user.id);
|
||||
assertBranchFound(allUsers, starredChangesRef);
|
||||
|
||||
// a user without the 'Access Database' capability cannot see the starred changes ref of another
|
||||
// user
|
||||
setApiUser(user2);
|
||||
assertBranchNotFound(allUsers, starredChangesRef);
|
||||
|
||||
// a user with the 'Access Database' capability can see the starred changes ref of another user
|
||||
testGetRefWithAccessDatabase(allUsers, starredChangesRef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTagRef() throws Exception {
|
||||
// create a tag
|
||||
TagInput input = new TagInput();
|
||||
input.message = "My Tag";
|
||||
input.revision = gApi.projects().name(project.get()).head();
|
||||
TagInfo tagInfo = gApi.projects().name(project.get()).tag("my-tag").create(input).get();
|
||||
|
||||
// any user who can see the project, can see the tag
|
||||
setApiUser(user);
|
||||
assertBranchFound(project, tagInfo.ref);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotGetTagRefThatPointsToNonVisibleBranch() throws Exception {
|
||||
// create a tag
|
||||
TagInput input = new TagInput();
|
||||
input.message = "My Tag";
|
||||
input.revision = gApi.projects().name(project.get()).head();
|
||||
TagInfo tagInfo = gApi.projects().name(project.get()).tag("my-tag").create(input).get();
|
||||
|
||||
// block read access to the branch
|
||||
block(project, RefNames.fullName("master"), Permission.READ, ANONYMOUS_USERS);
|
||||
|
||||
// if the user cannot see the project, the tag is not visible
|
||||
setApiUser(user);
|
||||
assertBranchNotFound(project, tagInfo.ref);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSymbolicRef() throws Exception {
|
||||
// 'HEAD' is visible since it points to 'master' that is visible
|
||||
setApiUser(user);
|
||||
assertBranchFound(project, "HEAD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotGetSymbolicRefThatPointsToNonVisibleBranch() throws Exception {
|
||||
// block read access to the branch to which HEAD points by default
|
||||
block(project, RefNames.fullName("master"), Permission.READ, ANONYMOUS_USERS);
|
||||
|
||||
// since 'master' is not visible, 'HEAD' which points to 'master' is also not visible
|
||||
setApiUser(user);
|
||||
assertBranchNotFound(project, "HEAD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAccountSequenceRef() throws Exception {
|
||||
// Sequences were not yet in NoteDb in this release.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGroupSequenceRef() throws Exception {
|
||||
// Sequences were not yet in NoteDb in this release.
|
||||
}
|
||||
|
||||
private void testGetRefWithAccessDatabase(Project.NameKey project, String ref) throws Exception {
|
||||
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
|
||||
try {
|
||||
setApiUser(user);
|
||||
assertBranchFound(project, ref);
|
||||
} finally {
|
||||
removeGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertBranchNotFound(Project.NameKey project, String ref) {
|
||||
ResourceNotFoundException exception =
|
||||
assertThrows(
|
||||
ResourceNotFoundException.class,
|
||||
() -> gApi.projects().name(project.get()).branch(ref).get());
|
||||
assertThat(exception).hasMessageThat().isEqualTo("Not found: " + ref);
|
||||
}
|
||||
|
||||
private void assertBranchFound(Project.NameKey project, String ref) throws RestApiException {
|
||||
BranchInfo branchInfo = gApi.projects().name(project.get()).branch(ref).get();
|
||||
assertThat(branchInfo.ref).isEqualTo(RefNames.fullName(ref));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.google.gerrit</groupId>
|
||||
<artifactId>gerrit-extension-api</artifactId>
|
||||
<version>2.15.20-SNAPSHOT</version>
|
||||
<version>2.15.22-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>Gerrit Code Review - Extension API</name>
|
||||
<description>API for Gerrit Extensions</description>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.google.gerrit</groupId>
|
||||
<artifactId>gerrit-plugin-api</artifactId>
|
||||
<version>2.15.20-SNAPSHOT</version>
|
||||
<version>2.15.22-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>Gerrit Code Review - Plugin API</name>
|
||||
<description>API for Gerrit Plugins</description>
|
||||
|
||||
@@ -74,10 +74,6 @@ java_doc(
|
||||
],
|
||||
pkgs = [
|
||||
"com.google.gerrit.plugin",
|
||||
"com.google.gwtexpui.clippy",
|
||||
"com.google.gwtexpui.globalkey",
|
||||
"com.google.gwtexpui.safehtml",
|
||||
"com.google.gwtexpui.user",
|
||||
],
|
||||
title = "Gerrit Review GWT Extension API Documentation",
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.google.gerrit</groupId>
|
||||
<artifactId>gerrit-plugin-gwtui</artifactId>
|
||||
<version>2.15.20-SNAPSHOT</version>
|
||||
<version>2.15.22-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>Gerrit Code Review - Plugin GWT UI</name>
|
||||
<description>Common Classes for Gerrit GWT UI Plugins</description>
|
||||
|
||||
@@ -101,11 +101,32 @@ public class RefNames {
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the ref is managed by Gerrit. Covers all Gerrit-internal refs like refs/cache-automerge
|
||||
* and refs/meta as well as refs/changes. Does not cover user-created refs like branches or custom
|
||||
* ref namespaces like refs/my-company.
|
||||
*/
|
||||
public static boolean isGerritRef(String ref) {
|
||||
return ref.startsWith(REFS_CHANGES)
|
||||
|| ref.startsWith(REFS_EXTERNAL_IDS)
|
||||
|| ref.startsWith(REFS_CACHE_AUTOMERGE)
|
||||
|| ref.startsWith(REFS_DRAFT_COMMENTS)
|
||||
|| ref.startsWith(REFS_SEQUENCES)
|
||||
|| ref.startsWith(REFS_USERS)
|
||||
|| ref.startsWith(REFS_STARRED_CHANGES)
|
||||
|| ref.startsWith(REFS_REJECT_COMMITS);
|
||||
}
|
||||
|
||||
public static String changeMetaRef(Change.Id id) {
|
||||
StringBuilder r = newStringBuilder().append(REFS_CHANGES);
|
||||
return shard(id.get(), r).append(META_SUFFIX).toString();
|
||||
}
|
||||
|
||||
public static String patchSetRef(PatchSet.Id id) {
|
||||
StringBuilder r = newStringBuilder().append(REFS_CHANGES);
|
||||
return shard(id.changeId.get(), r).append('/').append(id.get()).toString();
|
||||
}
|
||||
|
||||
public static String robotCommentsRef(Change.Id id) {
|
||||
StringBuilder r = newStringBuilder().append(REFS_CHANGES);
|
||||
return shard(id.get(), r).append(ROBOT_COMMENTS_SUFFIX).toString();
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package com.google.gerrit.server.git;
|
||||
|
||||
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CACHE_AUTOMERGE;
|
||||
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
|
||||
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
|
||||
import static com.google.gerrit.reviewdb.client.RefNames.REFS_USERS_SELF;
|
||||
@@ -38,6 +37,7 @@ import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.permissions.ProjectPermission;
|
||||
import com.google.gerrit.server.permissions.RefPermission;
|
||||
import com.google.gerrit.server.permissions.RefVisibilityControl;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
@@ -80,6 +80,7 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
|
||||
private final PermissionBackend.ForProject perm;
|
||||
private final ProjectState projectState;
|
||||
private final Repository git;
|
||||
private final RefVisibilityControl refVisibilityControl;
|
||||
private ProjectControl projectCtl;
|
||||
private boolean showMetadata = true;
|
||||
private String userEditPrefix;
|
||||
@@ -93,6 +94,7 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
|
||||
Provider<ReviewDb> db,
|
||||
Provider<CurrentUser> user,
|
||||
PermissionBackend permissionBackend,
|
||||
RefVisibilityControl refVisibilityControl,
|
||||
@Assisted ProjectState projectState,
|
||||
@Assisted Repository git) {
|
||||
this.tagCache = tagCache;
|
||||
@@ -105,6 +107,7 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
|
||||
permissionBackend.user(user).database(db).project(projectState.getProject().getNameKey());
|
||||
this.projectState = projectState;
|
||||
this.git = git;
|
||||
this.refVisibilityControl = refVisibilityControl;
|
||||
}
|
||||
|
||||
/** Show change references. Default is {@code true}. */
|
||||
@@ -128,68 +131,54 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
|
||||
}
|
||||
}
|
||||
|
||||
Account.Id userId;
|
||||
boolean viewMetadata;
|
||||
boolean hasAccessDatabase;
|
||||
if (user.get().isIdentifiedUser()) {
|
||||
viewMetadata = withUser.testOrFalse(GlobalPermission.ACCESS_DATABASE);
|
||||
hasAccessDatabase = withUser.testOrFalse(GlobalPermission.ACCESS_DATABASE);
|
||||
IdentifiedUser u = user.get().asIdentifiedUser();
|
||||
userId = u.getAccountId();
|
||||
Account.Id userId = u.getAccountId();
|
||||
userEditPrefix = RefNames.refsEditPrefix(userId);
|
||||
} else {
|
||||
userId = null;
|
||||
viewMetadata = false;
|
||||
hasAccessDatabase = false;
|
||||
}
|
||||
|
||||
Map<String, Ref> result = new HashMap<>();
|
||||
Map<String, Ref> resultRefs = new HashMap<>();
|
||||
List<Ref> deferredTags = new ArrayList<>();
|
||||
|
||||
projectCtl = projectState.controlFor(user.get());
|
||||
for (Ref ref : refs.values()) {
|
||||
String name = ref.getName();
|
||||
String refName = ref.getName();
|
||||
Change.Id changeId;
|
||||
Account.Id accountId;
|
||||
if (name.startsWith(REFS_CACHE_AUTOMERGE) || (!showMetadata && isMetadata(name))) {
|
||||
continue;
|
||||
} else if (RefNames.isRefsEdit(name)) {
|
||||
// Edits are visible only to the owning user, if change is visible.
|
||||
if (viewMetadata || visibleEdit(name)) {
|
||||
result.put(name, ref);
|
||||
}
|
||||
} else if ((changeId = Change.Id.fromRef(name)) != null) {
|
||||
// Change ref is visible only if the change is visible.
|
||||
if (viewMetadata || visible(changeId)) {
|
||||
result.put(name, ref);
|
||||
}
|
||||
} else if ((accountId = Account.Id.fromRef(name)) != null) {
|
||||
// Account ref is visible only to corresponding account.
|
||||
if (viewMetadata || (accountId.equals(userId) && canReadRef(name))) {
|
||||
result.put(name, ref);
|
||||
}
|
||||
if (!showMetadata && isMetadata(refName)) {
|
||||
log.debug("Filter out metadata ref %s", refName);
|
||||
} else if (isTag(ref)) {
|
||||
// If its a tag, consider it later.
|
||||
if (ref.getObjectId() != null) {
|
||||
log.debug("Defer tag ref %s", refName);
|
||||
deferredTags.add(ref);
|
||||
} else {
|
||||
log.debug("Filter out tag ref %s that is not a tag", refName);
|
||||
}
|
||||
} else if (name.startsWith(RefNames.REFS_SEQUENCES)) {
|
||||
// Sequences are internal database implementation details.
|
||||
if (viewMetadata) {
|
||||
result.put(name, ref);
|
||||
} else if ((changeId = Change.Id.fromRef(refName)) != null) {
|
||||
// This is a mere performance optimization. RefVisibilityControl could determine the
|
||||
// visibility of these refs just fine. But instead, we use highly-optimized logic that
|
||||
// looks only on the last 10k most recent changes using the change index and a cache.
|
||||
if (hasAccessDatabase) {
|
||||
resultRefs.put(refName, ref);
|
||||
} else if (!visible(changeId)) {
|
||||
log.debug("Filter out invisible change ref %s", refName);
|
||||
} else if (RefNames.isRefsEdit(refName) && !visibleEdit(refName)) {
|
||||
log.debug("Filter out invisible change edit ref %s", refName);
|
||||
} else {
|
||||
// Change is visible
|
||||
resultRefs.put(refName, ref);
|
||||
}
|
||||
} else if (projectState.isAllUsers() && name.equals(RefNames.REFS_EXTERNAL_IDS)) {
|
||||
// The notes branch with the external IDs of all users must not be exposed to normal users.
|
||||
if (viewMetadata) {
|
||||
result.put(name, ref);
|
||||
}
|
||||
} else if (canReadRef(ref.getLeaf().getName())) {
|
||||
// Use the leaf to lookup the control data. If the reference is
|
||||
// symbolic we want the control around the final target. If its
|
||||
// not symbolic then getLeaf() is a no-op returning ref itself.
|
||||
result.put(name, ref);
|
||||
} else if (isRefsUsersSelf(ref)) {
|
||||
// viewMetadata allows to see all account refs, hence refs/users/self should be included as
|
||||
// well
|
||||
if (viewMetadata) {
|
||||
result.put(name, ref);
|
||||
} else {
|
||||
try {
|
||||
if (refVisibilityControl.isVisible(projectCtl, ref.getLeaf().getName())) {
|
||||
resultRefs.put(refName, ref);
|
||||
}
|
||||
} catch (PermissionBackendException e) {
|
||||
log.warn("could not evaluate ref permission", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,22 +186,22 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
|
||||
// If we have tags that were deferred, we need to do a revision walk
|
||||
// to identify what tags we can actually reach, and what we cannot.
|
||||
//
|
||||
if (!deferredTags.isEmpty() && (!result.isEmpty() || filterTagsSeparately)) {
|
||||
if (!deferredTags.isEmpty() && (!resultRefs.isEmpty() || filterTagsSeparately)) {
|
||||
TagMatcher tags =
|
||||
tagCache
|
||||
.get(projectState.getNameKey())
|
||||
.matcher(
|
||||
tagCache,
|
||||
git,
|
||||
filterTagsSeparately ? filter(git.getAllRefs()).values() : result.values());
|
||||
filterTagsSeparately ? filter(git.getAllRefs()).values() : resultRefs.values());
|
||||
for (Ref tag : deferredTags) {
|
||||
if (tags.isReachable(tag)) {
|
||||
result.put(tag.getName(), tag);
|
||||
resultRefs.put(tag.getName(), tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return resultRefs;
|
||||
}
|
||||
|
||||
private Map<String, Ref> fastHideRefsMetaConfig(Map<String, Ref> refs) {
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
// Copyright (C) 2020 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.permissions;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.util.ManualRequestContext;
|
||||
import com.google.gerrit.server.util.OneOffRequestContext;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class is a component that is internal to {@link DefaultPermissionBackend}. It can
|
||||
* authoritatively tell if a ref is accessible by a user.
|
||||
*/
|
||||
@Singleton
|
||||
public class RefVisibilityControl {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RefVisibilityControl.class);
|
||||
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final OneOffRequestContext oneOffRequestContext;
|
||||
private final PermissionBackend permissionBackend;
|
||||
private final ChangeData.Factory changeDataFactory;
|
||||
|
||||
@Inject
|
||||
RefVisibilityControl(
|
||||
Provider<ReviewDb> dbProvider,
|
||||
OneOffRequestContext oneOffRequestContext,
|
||||
PermissionBackend permissionBackend,
|
||||
ChangeData.Factory changeDataFactory) {
|
||||
this.dbProvider = dbProvider;
|
||||
this.oneOffRequestContext = oneOffRequestContext;
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.changeDataFactory = changeDataFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an authoritative answer if the ref is visible to the user. Does not have support for
|
||||
* tags and will throw a {@link PermissionBackendException} if asked for tags visibility.
|
||||
*/
|
||||
public boolean isVisible(ProjectControl projectControl, String refName)
|
||||
throws PermissionBackendException {
|
||||
if (refName.startsWith(Constants.R_TAGS)) {
|
||||
throw new PermissionBackendException(
|
||||
"can't check tags through RefVisibilityControl. Use PermissionBackend#filter instead.");
|
||||
}
|
||||
if (!RefNames.isGerritRef(refName)) {
|
||||
// This is not a special Gerrit ref and not a NoteDb ref. Likely, it's just a ref under
|
||||
// refs/heads or another ref the user created. Apply the regular permissions with inheritance.
|
||||
return projectControl.controlForRef(refName).hasReadPermissionOnRef(false);
|
||||
}
|
||||
|
||||
if (refName.startsWith(RefNames.REFS_CACHE_AUTOMERGE)) {
|
||||
// Internal cache state that is accessible to no one.
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasAccessDatabase =
|
||||
permissionBackend
|
||||
.user(projectControl.getUser())
|
||||
.testOrFalse(GlobalPermission.ACCESS_DATABASE);
|
||||
if (hasAccessDatabase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change and change edit visibility
|
||||
Change.Id changeId;
|
||||
if ((changeId = Change.Id.fromRef(refName)) != null) {
|
||||
// Change ref is visible only if the change is visible.
|
||||
try (CloseableOneTimeReviewDb ignored = new CloseableOneTimeReviewDb()) {
|
||||
ChangeData cd;
|
||||
try {
|
||||
cd =
|
||||
changeDataFactory.create(
|
||||
dbProvider.get(), projectControl.getProject().getNameKey(), changeId);
|
||||
checkState(cd.change().getId().equals(changeId));
|
||||
} catch (OrmException e) {
|
||||
if (Throwables.getCausalChain(e).stream()
|
||||
.anyMatch(e2 -> e2 instanceof NoSuchChangeException)) {
|
||||
// The change was deleted or is otherwise not accessible anymore.
|
||||
// If the caller can see all refs and is allowed to see private changes on refs/, allow
|
||||
// access. This is an escape hatch for receivers of "ref deleted" events.
|
||||
PermissionBackend.ForProject forProject = projectControl.asForProject();
|
||||
return forProject.test(ProjectPermission.READ);
|
||||
}
|
||||
throw new PermissionBackendException(e);
|
||||
}
|
||||
if (RefNames.isRefsEdit(refName)) {
|
||||
// Edits are visible only to the owning user, if change is visible.
|
||||
return visibleEdit(refName, projectControl, cd);
|
||||
}
|
||||
return isVisible(projectControl.controlFor(getNotes(cd)).setChangeData(cd));
|
||||
}
|
||||
}
|
||||
|
||||
// Account visibility
|
||||
CurrentUser user = projectControl.getUser();
|
||||
Account.Id currentUserAccountId = user.isIdentifiedUser() ? user.getAccountId() : null;
|
||||
Account.Id accountId;
|
||||
if ((accountId = Account.Id.fromRef(refName)) != null) {
|
||||
// Account ref is visible only to the corresponding account.
|
||||
if (accountId.equals(currentUserAccountId)
|
||||
&& projectControl.controlForRef(refName).hasReadPermissionOnRef(true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are done checking all cases where we would allow access to Gerrit-managed refs. Deny
|
||||
// access in case we got this far.
|
||||
logger.debug(
|
||||
"Denying access to %s because user doesn't have access to this Gerrit ref", refName);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean visibleEdit(String refName, ProjectControl projectControl, ChangeData cd)
|
||||
throws PermissionBackendException {
|
||||
Change.Id id = Change.Id.fromEditRefPart(refName);
|
||||
if (id == null) {
|
||||
throw new IllegalStateException("unable to parse change id from edit ref " + refName);
|
||||
}
|
||||
|
||||
if (!isVisible(projectControl.controlFor(getNotes(cd)).setChangeData(cd))) {
|
||||
// The user can't see the change so they can't see any edits.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (projectControl.getUser().isIdentifiedUser()
|
||||
&& refName.startsWith(
|
||||
RefNames.refsEditPrefix(projectControl.getUser().asIdentifiedUser().getAccountId()))) {
|
||||
logger.debug("Own change edit ref is visible: %s", refName);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ChangeNotes getNotes(ChangeData cd) throws PermissionBackendException {
|
||||
try {
|
||||
return cd.notes();
|
||||
} catch (OrmException e) {
|
||||
throw new PermissionBackendException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isVisible(ChangeControl changeControl) throws PermissionBackendException {
|
||||
try {
|
||||
return changeControl.isVisible(dbProvider.get());
|
||||
} catch (OrmException e) {
|
||||
throw new PermissionBackendException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<ReviewDb> getReviewDb() {
|
||||
try {
|
||||
return Optional.of(dbProvider.get());
|
||||
} catch (Exception e) {
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper to establish a database connection. */
|
||||
private class CloseableOneTimeReviewDb implements AutoCloseable {
|
||||
@Nullable private final ManualRequestContext ctx;
|
||||
|
||||
CloseableOneTimeReviewDb() throws PermissionBackendException {
|
||||
if (!getReviewDb().isPresent()) {
|
||||
try {
|
||||
ctx = oneOffRequestContext.open();
|
||||
} catch (OrmException e) {
|
||||
throw new PermissionBackendException(e);
|
||||
}
|
||||
} else {
|
||||
ctx = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (ctx != null) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Access control management for a user accessing a single change. */
|
||||
class ChangeControl {
|
||||
public class ChangeControl {
|
||||
@Singleton
|
||||
static class Factory {
|
||||
private final ChangeData.Factory changeDataFactory;
|
||||
@@ -87,6 +87,8 @@ class ChangeControl {
|
||||
private final ChangeNotes notes;
|
||||
private final PatchSetUtil patchSetUtil;
|
||||
|
||||
private ChangeData cd;
|
||||
|
||||
ChangeControl(
|
||||
ChangeData.Factory changeDataFactory,
|
||||
ApprovalsUtil approvalsUtil,
|
||||
@@ -128,17 +130,20 @@ class ChangeControl {
|
||||
return notes;
|
||||
}
|
||||
|
||||
/** Can this user see this change? */
|
||||
private boolean isVisible(ReviewDb db, @Nullable ChangeData cd) throws OrmException {
|
||||
if (getChange().isPrivate() && !isPrivateVisible(db, cd)) {
|
||||
return false;
|
||||
public ChangeControl setChangeData(@Nullable ChangeData cd) {
|
||||
if (cd != null) {
|
||||
this.cd = cd;
|
||||
}
|
||||
return isRefVisible();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Can the user see this change? Does not account for draft status */
|
||||
private boolean isRefVisible() {
|
||||
return getRefControl().isVisible();
|
||||
/** Can this user see this change? */
|
||||
public boolean isVisible(ReviewDb db) throws OrmException {
|
||||
if (getChange().isPrivate() && !isPrivateVisible(db, changeData(db))) {
|
||||
return false;
|
||||
}
|
||||
// Does the user have READ permission on the destination?
|
||||
return refControl.asForRef().testOrFalse(RefPermission.READ);
|
||||
}
|
||||
|
||||
/** Can this user abandon this change? */
|
||||
@@ -237,7 +242,7 @@ class ChangeControl {
|
||||
/** Is this user a reviewer for the change? */
|
||||
private boolean isReviewer(ReviewDb db, @Nullable ChangeData cd) throws OrmException {
|
||||
if (getUser().isIdentifiedUser()) {
|
||||
Collection<Account.Id> results = changeData(db, cd).reviewers().all();
|
||||
Collection<Account.Id> results = setChangeData(cd).changeData(db).reviewers().all();
|
||||
return results.contains(getUser().getAccountId());
|
||||
}
|
||||
return false;
|
||||
@@ -282,8 +287,8 @@ class ChangeControl {
|
||||
|| getProjectControl().isAdmin();
|
||||
}
|
||||
|
||||
private ChangeData changeData(ReviewDb db, @Nullable ChangeData cd) {
|
||||
return cd != null ? cd : changeDataFactory.create(db, getNotes());
|
||||
private ChangeData changeData(ReviewDb db) {
|
||||
return this.cd != null ? cd : changeDataFactory.create(db, getNotes());
|
||||
}
|
||||
|
||||
private boolean isPrivateVisible(ReviewDb db, ChangeData cd) throws OrmException {
|
||||
@@ -294,15 +299,13 @@ class ChangeControl {
|
||||
}
|
||||
|
||||
ForChange asForChange(@Nullable ChangeData cd, @Nullable Provider<ReviewDb> db) {
|
||||
return new ForChangeImpl(cd, db);
|
||||
return new ForChangeImpl(db);
|
||||
}
|
||||
|
||||
private class ForChangeImpl extends ForChange {
|
||||
private ChangeData cd;
|
||||
private Map<String, PermissionRange> labels;
|
||||
|
||||
ForChangeImpl(@Nullable ChangeData cd, @Nullable Provider<ReviewDb> db) {
|
||||
this.cd = cd;
|
||||
ForChangeImpl(@Nullable Provider<ReviewDb> db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@@ -370,7 +373,7 @@ class ChangeControl {
|
||||
try {
|
||||
switch (perm) {
|
||||
case READ:
|
||||
return isVisible(db(), changeData());
|
||||
return isVisible(db());
|
||||
case ABANDON:
|
||||
return canAbandon(db());
|
||||
case DELETE:
|
||||
|
||||
@@ -39,8 +39,11 @@ import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.account.GroupMembership;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.config.GitReceivePackGroups;
|
||||
import com.google.gerrit.server.config.GitUploadPackGroups;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.VisibleRefFilter;
|
||||
import com.google.gerrit.server.group.SystemGroupBackend;
|
||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||
import com.google.gerrit.server.permissions.FailedPermissionBackend;
|
||||
@@ -51,6 +54,7 @@ import com.google.gerrit.server.permissions.PermissionBackend.ForProject;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.permissions.ProjectPermission;
|
||||
import com.google.gerrit.server.permissions.RefVisibilityControl;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
@@ -134,6 +138,10 @@ public class ProjectControl {
|
||||
private final CommitsCollection commits;
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
private final PermissionCollection.Factory permissionFilter;
|
||||
private final RefVisibilityControl refVisibilityControl;
|
||||
private final VisibleRefFilter.Factory visibleRefFilterFactory;
|
||||
private final GitRepositoryManager gitRepositoryManager;
|
||||
private final AllUsersName allUsersName;
|
||||
|
||||
private List<SectionMatcher> allSections;
|
||||
private Map<String, RefControl> refControls;
|
||||
@@ -147,6 +155,10 @@ public class ProjectControl {
|
||||
CommitsCollection commits,
|
||||
ChangeControl.Factory changeControlFactory,
|
||||
PermissionBackend permissionBackend,
|
||||
RefVisibilityControl refVisibilityControl,
|
||||
GitRepositoryManager gitRepositoryManager,
|
||||
VisibleRefFilter.Factory visibleRefFilterFactory,
|
||||
AllUsersName allUsersName,
|
||||
@Assisted CurrentUser who,
|
||||
@Assisted ProjectState ps) {
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
@@ -155,6 +167,10 @@ public class ProjectControl {
|
||||
this.permissionFilter = permissionFilter;
|
||||
this.commits = commits;
|
||||
this.perm = permissionBackend.user(who);
|
||||
this.refVisibilityControl = refVisibilityControl;
|
||||
this.gitRepositoryManager = gitRepositoryManager;
|
||||
this.visibleRefFilterFactory = visibleRefFilterFactory;
|
||||
this.allUsersName = allUsersName;
|
||||
user = who;
|
||||
state = ps;
|
||||
}
|
||||
@@ -186,7 +202,14 @@ public class ProjectControl {
|
||||
RefControl ctl = refControls.get(refName);
|
||||
if (ctl == null) {
|
||||
PermissionCollection relevant = permissionFilter.filter(access(), refName, user);
|
||||
ctl = new RefControl(this, refName, relevant);
|
||||
ctl =
|
||||
new RefControl(
|
||||
visibleRefFilterFactory,
|
||||
refVisibilityControl,
|
||||
this,
|
||||
gitRepositoryManager,
|
||||
refName,
|
||||
relevant);
|
||||
refControls.put(refName, ctl);
|
||||
}
|
||||
return ctl;
|
||||
@@ -243,7 +266,9 @@ public class ProjectControl {
|
||||
}
|
||||
|
||||
private boolean allRefsAreVisible(Set<String> ignore) {
|
||||
return user.isInternalUser() || canPerformOnAllRefs(Permission.READ, ignore);
|
||||
return user.isInternalUser()
|
||||
|| (!getProject().getNameKey().equals(allUsersName)
|
||||
&& canPerformOnAllRefs(Permission.READ, ignore));
|
||||
}
|
||||
|
||||
/** Returns whether the project is hidden. */
|
||||
@@ -428,11 +453,11 @@ public class ProjectControl {
|
||||
}
|
||||
}
|
||||
|
||||
ForProject asForProject() {
|
||||
public ForProject asForProject() {
|
||||
return new ForProjectImpl();
|
||||
}
|
||||
|
||||
private class ForProjectImpl extends ForProject {
|
||||
public class ForProjectImpl extends ForProject {
|
||||
@Override
|
||||
public ForProject user(CurrentUser user) {
|
||||
return forUser(user).asForProject().database(db);
|
||||
|
||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server.project;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
import com.google.gerrit.common.data.PermissionRange;
|
||||
import com.google.gerrit.common.data.PermissionRule;
|
||||
@@ -24,15 +25,20 @@ import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.VisibleRefFilter;
|
||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||
import com.google.gerrit.server.permissions.FailedPermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.permissions.ProjectPermission;
|
||||
import com.google.gerrit.server.permissions.RefPermission;
|
||||
import com.google.gerrit.server.permissions.RefVisibilityControl;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.util.Providers;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -42,10 +48,16 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
/** Manages access control for Git references (aka branches, tags). */
|
||||
public class RefControl {
|
||||
private final VisibleRefFilter.Factory visibleRefFilterFactory;
|
||||
private final RefVisibilityControl refVisibilityControl;
|
||||
private final ProjectControl projectControl;
|
||||
private final GitRepositoryManager repositoryManager;
|
||||
private final String refName;
|
||||
|
||||
/** All permissions that apply to this reference. */
|
||||
@@ -57,10 +69,19 @@ public class RefControl {
|
||||
private Boolean owner;
|
||||
private Boolean canForgeAuthor;
|
||||
private Boolean canForgeCommitter;
|
||||
private Boolean isVisible;
|
||||
private Boolean hasReadPermissionOnRef;
|
||||
|
||||
RefControl(ProjectControl projectControl, String ref, PermissionCollection relevant) {
|
||||
RefControl(
|
||||
VisibleRefFilter.Factory visibleRefFilterFactory,
|
||||
RefVisibilityControl refVisibilityControl,
|
||||
ProjectControl projectControl,
|
||||
GitRepositoryManager repositoryManager,
|
||||
String ref,
|
||||
PermissionCollection relevant) {
|
||||
this.visibleRefFilterFactory = visibleRefFilterFactory;
|
||||
this.refVisibilityControl = refVisibilityControl;
|
||||
this.projectControl = projectControl;
|
||||
this.repositoryManager = repositoryManager;
|
||||
this.refName = ref;
|
||||
this.relevant = relevant;
|
||||
this.effective = new HashMap<>();
|
||||
@@ -83,7 +104,13 @@ public class RefControl {
|
||||
if (relevant.isUserSpecific()) {
|
||||
return newCtl.controlForRef(getRefName());
|
||||
}
|
||||
return new RefControl(newCtl, getRefName(), relevant);
|
||||
return new RefControl(
|
||||
visibleRefFilterFactory,
|
||||
refVisibilityControl,
|
||||
newCtl,
|
||||
repositoryManager,
|
||||
getRefName(),
|
||||
relevant);
|
||||
}
|
||||
|
||||
/** Is this user a ref owner? */
|
||||
@@ -99,14 +126,24 @@ public class RefControl {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/** Can this user see this reference exists? */
|
||||
boolean isVisible() {
|
||||
if (isVisible == null) {
|
||||
isVisible =
|
||||
/**
|
||||
* Returns {@code true} if the user has permission to read the ref. This method evaluates {@link
|
||||
* RefPermission#READ} only. Hence, it is not authoritative. For example, it does not tell if the
|
||||
* user can see NoteDb refs such as {@code refs/meta/external-ids} which requires {@link
|
||||
* GlobalPermission#ACCESS_DATABASE} and deny access in this case.
|
||||
*/
|
||||
public boolean hasReadPermissionOnRef(boolean allowNoteDbRefs) {
|
||||
// Don't allow checking for NoteDb refs unless instructed otherwise.
|
||||
if (!allowNoteDbRefs
|
||||
&& (refName.startsWith(Constants.R_TAGS) || RefNames.isGerritRef(refName))) {
|
||||
return false;
|
||||
}
|
||||
if (hasReadPermissionOnRef == null) {
|
||||
hasReadPermissionOnRef =
|
||||
(getUser().isInternalUser() || canPerform(Permission.READ))
|
||||
&& isProjectStatePermittingRead();
|
||||
}
|
||||
return isVisible;
|
||||
return hasReadPermissionOnRef;
|
||||
}
|
||||
|
||||
/** Can this user see other users change edits? */
|
||||
@@ -552,7 +589,10 @@ public class RefControl {
|
||||
private boolean can(RefPermission perm) throws PermissionBackendException {
|
||||
switch (perm) {
|
||||
case READ:
|
||||
return isVisible();
|
||||
if (refName.startsWith(Constants.R_TAGS)) {
|
||||
return isTagVisible();
|
||||
}
|
||||
return refVisibilityControl.isVisible(projectControl, refName);
|
||||
case CREATE:
|
||||
// TODO This isn't an accurate test.
|
||||
return canPerform(perm.permissionName().get());
|
||||
@@ -588,4 +628,34 @@ public class RefControl {
|
||||
throw new PermissionBackendException(perm + " unsupported");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTagVisible() throws PermissionBackendException {
|
||||
if (projectControl.asForProject().test(ProjectPermission.READ)) {
|
||||
// The user has READ on refs/* with no effective block permission. This is the broadest
|
||||
// permission one can assign. There is no way to grant access to (specific) tags in Gerrit,
|
||||
// so we have to assume that these users can see all tags because there could be tags that
|
||||
// aren't reachable by any visible ref while the user can see all non-Gerrit refs. This
|
||||
// matches Gerrit's historic behavior.
|
||||
// This makes it so that these users could see commits that they can't see otherwise
|
||||
// (e.g. a private change ref) if a tag was attached to it. Tags are meant to be used on
|
||||
// the regular Git tree that users interact with, not on any of the Gerrit trees, so this
|
||||
// is a negligible risk.
|
||||
return true;
|
||||
}
|
||||
|
||||
try (Repository repo =
|
||||
repositoryManager.openRepository(projectControl.getProject().getNameKey())) {
|
||||
// Tag visibility requires going through RefFilter because it entails loading all taggable
|
||||
// refs and filtering them all by visibility.
|
||||
Ref resolvedRef = repo.getRefDatabase().exactRef(refName);
|
||||
if (resolvedRef == null) {
|
||||
return false;
|
||||
}
|
||||
return visibleRefFilterFactory.create(projectControl.getProjectState(), repo)
|
||||
.filter(ImmutableMap.of(resolvedRef.getName(), resolvedRef), true).values().stream()
|
||||
.anyMatch(r -> refName.equals(r.getName()));
|
||||
} catch (IOException e) {
|
||||
throw new PermissionBackendException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +55,16 @@ import com.google.gerrit.server.config.AllProjectsNameProvider;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.config.AllUsersNameProvider;
|
||||
import com.google.gerrit.server.config.SitePaths;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.ProjectConfig;
|
||||
import com.google.gerrit.server.git.TransferConfig;
|
||||
import com.google.gerrit.server.git.VisibleRefFilter;
|
||||
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.permissions.ProjectPermission;
|
||||
import com.google.gerrit.server.permissions.RefPermission;
|
||||
import com.google.gerrit.server.permissions.RefVisibilityControl;
|
||||
import com.google.gerrit.server.schema.SchemaCreator;
|
||||
import com.google.gerrit.server.util.RequestContext;
|
||||
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
||||
@@ -107,6 +111,16 @@ public class RefControlTest {
|
||||
assertThat(u.controlForRef(ref).isOwner()).named("NOT OWN " + ref).isFalse();
|
||||
}
|
||||
|
||||
private void assertAllRefsAreVisible(ProjectControl u) throws PermissionBackendException {
|
||||
assertThat(u.asForProject().test(ProjectPermission.READ)).named("all refs visible").isTrue();
|
||||
}
|
||||
|
||||
private void assertAllRefsAreNotVisible(ProjectControl u) throws PermissionBackendException {
|
||||
assertThat(u.asForProject().test(ProjectPermission.READ))
|
||||
.named("all refs NOT visible")
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
private void assertCanAccess(ProjectControl u) {
|
||||
boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
|
||||
assertThat(access).named("can access").isTrue();
|
||||
@@ -118,11 +132,13 @@ public class RefControlTest {
|
||||
}
|
||||
|
||||
private void assertCanRead(String ref, ProjectControl u) {
|
||||
assertThat(u.controlForRef(ref).isVisible()).named("can read " + ref).isTrue();
|
||||
assertThat(u.controlForRef(ref).hasReadPermissionOnRef(true)).named("can read " + ref).isTrue();
|
||||
}
|
||||
|
||||
private void assertCannotRead(String ref, ProjectControl u) {
|
||||
assertThat(u.controlForRef(ref).isVisible()).named("cannot read " + ref).isFalse();
|
||||
assertThat(u.controlForRef(ref).hasReadPermissionOnRef(true))
|
||||
.named("cannot read " + ref)
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
private void assertCanSubmit(String ref, ProjectControl u) {
|
||||
@@ -194,6 +210,7 @@ public class RefControlTest {
|
||||
private final Map<Project.NameKey, ProjectState> all = new HashMap<>();
|
||||
private Project.NameKey localKey = new Project.NameKey("local");
|
||||
private ProjectConfig local;
|
||||
private ProjectConfig allUsers;
|
||||
private Project.NameKey parentKey = new Project.NameKey("parent");
|
||||
private ProjectConfig parent;
|
||||
private InMemoryRepositoryManager repoManager;
|
||||
@@ -209,6 +226,9 @@ public class RefControlTest {
|
||||
@Inject private InMemoryDatabase schemaFactory;
|
||||
@Inject private ThreadLocalRequestContext requestContext;
|
||||
@Inject private TransferConfig transferConfig;
|
||||
@Inject private RefVisibilityControl refVisibilityControl;
|
||||
@Inject private GitRepositoryManager gitRepositoryManager;
|
||||
@Inject private VisibleRefFilter.Factory visibleRefFilterFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -222,7 +242,7 @@ public class RefControlTest {
|
||||
|
||||
@Override
|
||||
public ProjectState getAllUsers() {
|
||||
return null;
|
||||
return get(allUsersName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,6 +302,11 @@ public class RefControlTest {
|
||||
LabelType cr = Util.codeReview();
|
||||
allProjects.getLabelSections().put(cr.getName(), cr);
|
||||
add(allProjects);
|
||||
|
||||
Repository allUsersRepo = repoManager.createRepository(allUsersName);
|
||||
allUsers = new ProjectConfig(new Project.NameKey(allUsersName.get()));
|
||||
allUsers.load(allUsersRepo);
|
||||
add(allUsers);
|
||||
} catch (IOException | ConfigInvalidException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -355,6 +380,24 @@ public class RefControlTest {
|
||||
assertAdminsAreOwnersAndDevsAreNot();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allRefsAreVisibleForRegularProject() throws Exception {
|
||||
allow(local, READ, DEVS, "refs/*");
|
||||
allow(local, READ, DEVS, "refs/groups/*");
|
||||
allow(local, READ, DEVS, "refs/users/default");
|
||||
|
||||
assertAllRefsAreVisible(user(local, DEVS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allRefsAreNotVisibleForAllUsers() throws Exception {
|
||||
allow(allUsers, READ, DEVS, "refs/*");
|
||||
allow(allUsers, READ, DEVS, "refs/groups/*");
|
||||
allow(allUsers, READ, DEVS, "refs/users/default");
|
||||
|
||||
assertAllRefsAreNotVisible(user(allUsers, DEVS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void branchDelegation1() {
|
||||
allow(local, OWNER, ADMIN, "refs/*");
|
||||
@@ -886,6 +929,10 @@ public class RefControlTest {
|
||||
null, // commitsCollection
|
||||
changeControlFactory,
|
||||
permissionBackend,
|
||||
refVisibilityControl,
|
||||
gitRepositoryManager,
|
||||
visibleRefFilterFactory,
|
||||
allUsersName,
|
||||
new MockUser(name, memberOf),
|
||||
newProjectState(local));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.google.gerrit</groupId>
|
||||
<artifactId>gerrit-war</artifactId>
|
||||
<version>2.15.20-SNAPSHOT</version>
|
||||
<version>2.15.22-SNAPSHOT</version>
|
||||
<packaging>war</packaging>
|
||||
<name>Gerrit Code Review - WAR</name>
|
||||
<description>Gerrit WAR</description>
|
||||
|
||||
@@ -4,7 +4,7 @@ _JGIT_VERS = "4.11.9.201909030838-r"
|
||||
|
||||
_DOC_VERS = _JGIT_VERS # Set to _JGIT_VERS unless using a snapshot
|
||||
|
||||
JGIT_DOC_URL = "https://download.eclipse.org/jgit/site/" + _DOC_VERS + "/apidocs"
|
||||
JGIT_DOC_URL = "https://archive.eclipse.org/jgit/site/" + _DOC_VERS + "/apidocs"
|
||||
|
||||
_JGIT_REPO = MAVEN_CENTRAL # Leave here even if set to MAVEN_CENTRAL.
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ DIRECTORIES = [
|
||||
tags = [
|
||||
# Should not run sandboxed.
|
||||
"local",
|
||||
"manual",
|
||||
"template",
|
||||
],
|
||||
) for directory in DIRECTORIES]
|
||||
|
||||
@@ -48,7 +48,7 @@ java_package_configuration(
|
||||
"-Xep:ElementsCountedInLoop:WARN",
|
||||
"-Xep:EqualsHashCode:WARN",
|
||||
"-Xep:EqualsIncompatibleType:WARN",
|
||||
"-Xep:ExpectedExceptionChecker:ERROR",
|
||||
"-Xep:ExpectedExceptionChecker:WARN",
|
||||
"-Xep:Finally:WARN",
|
||||
"-Xep:FloatingPointLiteralPrecision:WARN",
|
||||
"-Xep:FragmentInjection:WARN",
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
# Used by :api_install and :api_deploy targets
|
||||
# when talking to the destination repository.
|
||||
#
|
||||
GERRIT_VERSION = "2.15.20-SNAPSHOT"
|
||||
GERRIT_VERSION = "2.15.22-SNAPSHOT"
|
||||
|
||||
Reference in New Issue
Block a user