| // Copyright (C) 2014 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.git; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static com.google.common.truth.TruthJUnit.assume; |
| import static com.google.gerrit.acceptance.GitUtil.fetch; |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; |
| import static java.util.stream.Collectors.toList; |
| |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableList; |
| import com.google.gerrit.acceptance.AbstractDaemonTest; |
| import com.google.gerrit.acceptance.AcceptanceTestRequestScope; |
| import com.google.gerrit.acceptance.GerritConfig; |
| import com.google.gerrit.acceptance.NoHttpd; |
| import com.google.gerrit.acceptance.ProjectResetter; |
| import com.google.gerrit.acceptance.PushOneCommit; |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.common.data.AccessSection; |
| 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.groups.GroupInput; |
| import com.google.gerrit.extensions.api.projects.BranchInput; |
| import com.google.gerrit.extensions.restapi.RestApiException; |
| import com.google.gerrit.reviewdb.client.AccountGroup; |
| import com.google.gerrit.reviewdb.client.Change; |
| import com.google.gerrit.reviewdb.client.Patch; |
| 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.Sequences; |
| import com.google.gerrit.server.config.AllUsersName; |
| import com.google.gerrit.server.config.AnonymousCowardName; |
| import com.google.gerrit.server.git.receive.ReceiveCommitsAdvertiseRefsHook; |
| import com.google.gerrit.server.notedb.ChangeNoteUtil; |
| import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage; |
| import com.google.gerrit.server.permissions.PermissionBackend; |
| import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions; |
| import com.google.gerrit.server.project.testing.Util; |
| import com.google.gerrit.server.query.change.ChangeData; |
| import com.google.gerrit.testing.NoteDbMode; |
| import com.google.gerrit.testing.TestChanges; |
| import com.google.inject.Inject; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.Predicate; |
| import org.eclipse.jgit.api.Git; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.Repository; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| @NoHttpd |
| public class RefAdvertisementIT extends AbstractDaemonTest { |
| @Inject private PermissionBackend permissionBackend; |
| @Inject private ChangeNoteUtil noteUtil; |
| @Inject @AnonymousCowardName private String anonymousCowardName; |
| @Inject private AllUsersName allUsersName; |
| |
| private AccountGroup.UUID admins; |
| private AccountGroup.UUID nonInteractiveUsers; |
| |
| private ChangeData c1; |
| private ChangeData c2; |
| private ChangeData c3; |
| private ChangeData c4; |
| private String r1; |
| private String r2; |
| private String r3; |
| private String r4; |
| |
| @Before |
| public void setUp() throws Exception { |
| admins = adminGroupUuid(); |
| nonInteractiveUsers = groupUuid("Non-Interactive Users"); |
| setUpPermissions(); |
| setUpChanges(); |
| } |
| |
| private void setUpPermissions() throws Exception { |
| // Remove read permissions for all users besides admin. This method is idempotent, so is safe |
| // to call on every test setup. |
| try (ProjectConfigUpdate u = updateProject(allProjects)) { |
| for (AccessSection sec : u.getConfig().getAccessSections()) { |
| sec.removePermission(Permission.READ); |
| } |
| Util.allow(u.getConfig(), Permission.READ, admins, "refs/*"); |
| u.save(); |
| } |
| |
| // Remove all read permissions on All-Users. This method is idempotent, so is safe to call on |
| // every test setup. |
| try (ProjectConfigUpdate u = updateProject(allUsers)) { |
| for (AccessSection sec : u.getConfig().getAccessSections()) { |
| sec.removePermission(Permission.READ); |
| } |
| u.save(); |
| } |
| } |
| |
| private static String changeRefPrefix(Change.Id id) { |
| String ps = new PatchSet.Id(id, 1).toRefName(); |
| return ps.substring(0, ps.length() - 1); |
| } |
| |
| private void setUpChanges() throws Exception { |
| gApi.projects().name(project.get()).branch("branch").create(new BranchInput()); |
| |
| // First 2 changes are merged, which means the tags pointing to them are |
| // visible. |
| allow("refs/for/refs/heads/*", Permission.SUBMIT, admins); |
| PushOneCommit.Result mr = |
| pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master%submit"); |
| mr.assertOkStatus(); |
| c1 = mr.getChange(); |
| r1 = changeRefPrefix(c1.getId()); |
| PushOneCommit.Result br = |
| pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/branch%submit"); |
| br.assertOkStatus(); |
| c2 = br.getChange(); |
| r2 = changeRefPrefix(c2.getId()); |
| |
| // Second 2 changes are unmerged. |
| mr = pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master"); |
| mr.assertOkStatus(); |
| c3 = mr.getChange(); |
| r3 = changeRefPrefix(c3.getId()); |
| br = pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/branch"); |
| br.assertOkStatus(); |
| c4 = br.getChange(); |
| r4 = changeRefPrefix(c4.getId()); |
| |
| try (Repository repo = repoManager.openRepository(project)) { |
| // master-tag -> master |
| RefUpdate mtu = repo.updateRef("refs/tags/master-tag"); |
| mtu.setExpectedOldObjectId(ObjectId.zeroId()); |
| mtu.setNewObjectId(repo.exactRef("refs/heads/master").getObjectId()); |
| assertThat(mtu.update()).isEqualTo(RefUpdate.Result.NEW); |
| |
| // branch-tag -> branch |
| RefUpdate btu = repo.updateRef("refs/tags/branch-tag"); |
| btu.setExpectedOldObjectId(ObjectId.zeroId()); |
| btu.setNewObjectId(repo.exactRef("refs/heads/branch").getObjectId()); |
| assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW); |
| } |
| } |
| |
| @Test |
| public void uploadPackAllRefsVisibleNoRefsMetaConfig() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| Util.allow(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*"); |
| Util.allow(u.getConfig(), Permission.READ, admins, RefNames.REFS_CONFIG); |
| Util.doNotInherit(u.getConfig(), Permission.READ, RefNames.REFS_CONFIG); |
| u.save(); |
| } |
| |
| setApiUser(user); |
| assertUploadPackRefs( |
| "HEAD", |
| r1 + "1", |
| r1 + "meta", |
| r2 + "1", |
| r2 + "meta", |
| r3 + "1", |
| r3 + "meta", |
| r4 + "1", |
| r4 + "meta", |
| "refs/heads/branch", |
| "refs/heads/master", |
| "refs/tags/branch-tag", |
| "refs/tags/master-tag"); |
| } |
| |
| @Test |
| public void uploadPackAllRefsVisibleWithRefsMetaConfig() throws Exception { |
| allow("refs/*", Permission.READ, REGISTERED_USERS); |
| allow(RefNames.REFS_CONFIG, Permission.READ, REGISTERED_USERS); |
| |
| assertUploadPackRefs( |
| "HEAD", |
| r1 + "1", |
| r1 + "meta", |
| r2 + "1", |
| r2 + "meta", |
| r3 + "1", |
| r3 + "meta", |
| r4 + "1", |
| r4 + "meta", |
| "refs/heads/branch", |
| "refs/heads/master", |
| RefNames.REFS_CONFIG, |
| "refs/tags/branch-tag", |
| "refs/tags/master-tag"); |
| } |
| |
| @Test |
| public void uploadPackSubsetOfBranchesVisibleIncludingHead() throws Exception { |
| allow("refs/heads/master", Permission.READ, REGISTERED_USERS); |
| deny("refs/heads/branch", Permission.READ, REGISTERED_USERS); |
| |
| setApiUser(user); |
| assertUploadPackRefs( |
| "HEAD", |
| r1 + "1", |
| r1 + "meta", |
| r3 + "1", |
| r3 + "meta", |
| "refs/heads/master", |
| "refs/tags/master-tag"); |
| } |
| |
| @Test |
| public void uploadPackSubsetOfBranchesVisibleNotIncludingHead() throws Exception { |
| deny("refs/heads/master", Permission.READ, REGISTERED_USERS); |
| allow("refs/heads/branch", Permission.READ, REGISTERED_USERS); |
| |
| setApiUser(user); |
| assertUploadPackRefs( |
| r2 + "1", |
| r2 + "meta", |
| r4 + "1", |
| r4 + "meta", |
| "refs/heads/branch", |
| "refs/tags/branch-tag", |
| // master branch is not visible but master-tag is reachable from branch |
| // (since PushOneCommit always bases changes on each other). |
| "refs/tags/master-tag"); |
| } |
| |
| @Test |
| public void uploadPackSubsetOfBranchesVisibleWithEdit() throws Exception { |
| allow("refs/heads/master", Permission.READ, REGISTERED_USERS); |
| |
| Change c = notesFactory.createChecked(db, project, c3.getId()).getChange(); |
| String changeId = c.getKey().get(); |
| |
| // Admin's edit is not visible. |
| setApiUser(admin); |
| gApi.changes().id(changeId).edit().create(); |
| |
| // User's edit is visible. |
| setApiUser(user); |
| gApi.changes().id(changeId).edit().create(); |
| |
| assertUploadPackRefs( |
| "HEAD", |
| r1 + "1", |
| r1 + "meta", |
| r3 + "1", |
| r3 + "meta", |
| "refs/heads/master", |
| "refs/tags/master-tag", |
| "refs/tags/branch-tag", |
| "refs/users/01/1000001/edit-" + c3.getId() + "/1"); |
| } |
| |
| @Test |
| public void uploadPackSubsetOfBranchesAndEditsVisibleWithViewPrivateChanges() throws Exception { |
| allow("refs/heads/master", Permission.READ, REGISTERED_USERS); |
| allow("refs/*", Permission.VIEW_PRIVATE_CHANGES, REGISTERED_USERS); |
| |
| Change change3 = notesFactory.createChecked(db, project, c3.getId()).getChange(); |
| String changeId3 = change3.getKey().get(); |
| Change change4 = notesFactory.createChecked(db, project, c4.getId()).getChange(); |
| String changeId4 = change4.getKey().get(); |
| |
| // Admin's edit on change3 is visible. |
| setApiUser(admin); |
| gApi.changes().id(changeId3).edit().create(); |
| |
| // Admin's edit on change4 is not visible since user cannot see the change. |
| gApi.changes().id(changeId4).edit().create(); |
| |
| // User's edit is visible. |
| setApiUser(user); |
| gApi.changes().id(changeId3).edit().create(); |
| |
| assertUploadPackRefs( |
| "HEAD", |
| r1 + "1", |
| r1 + "meta", |
| r3 + "1", |
| r3 + "meta", |
| "refs/heads/master", |
| "refs/tags/master-tag", |
| "refs/tags/branch-tag", |
| "refs/users/00/1000000/edit-" + c3.getId() + "/1", |
| "refs/users/01/1000001/edit-" + c3.getId() + "/1"); |
| } |
| |
| @Test |
| public void uploadPackSubsetOfRefsVisibleWithAccessDatabase() throws Exception { |
| allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE); |
| deny("refs/heads/master", Permission.READ, REGISTERED_USERS); |
| allow("refs/heads/branch", Permission.READ, REGISTERED_USERS); |
| |
| String changeId = c3.change().getKey().get(); |
| setApiUser(admin); |
| gApi.changes().id(changeId).edit().create(); |
| setApiUser(user); |
| |
| assertUploadPackRefs( |
| // Change 1 is visible due to accessDatabase capability, even though |
| // refs/heads/master is not. |
| r1 + "1", |
| r1 + "meta", |
| r2 + "1", |
| r2 + "meta", |
| r3 + "1", |
| r3 + "meta", |
| r4 + "1", |
| r4 + "meta", |
| "refs/heads/branch", |
| "refs/tags/branch-tag", |
| // See comment in subsetOfBranchesVisibleNotIncludingHead. |
| "refs/tags/master-tag", |
| // All edits are visible due to accessDatabase capability. |
| "refs/users/00/1000000/edit-" + c3.getId() + "/1"); |
| } |
| |
| @Test |
| public void uploadPackNoSearchingChangeCacheImpl() throws Exception { |
| allow("refs/heads/*", Permission.READ, REGISTERED_USERS); |
| |
| setApiUser(user); |
| try (Repository repo = repoManager.openRepository(project)) { |
| assertRefs( |
| repo, |
| permissionBackend.user(user(user)).project(project), |
| // Can't use stored values from the index so DB must be enabled. |
| false, |
| "HEAD", |
| r1 + "1", |
| r1 + "meta", |
| r2 + "1", |
| r2 + "meta", |
| r3 + "1", |
| r3 + "meta", |
| r4 + "1", |
| r4 + "meta", |
| "refs/heads/branch", |
| "refs/heads/master", |
| "refs/tags/branch-tag", |
| "refs/tags/master-tag"); |
| } |
| } |
| |
| @Test |
| public void uploadPackSequencesWithAccessDatabase() throws Exception { |
| assume().that(notesMigration.readChangeSequence()).isTrue(); |
| try (Repository repo = repoManager.openRepository(allProjects)) { |
| assertRefs(repo, newFilter(allProjects, user), true); |
| |
| allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE); |
| assertRefs(repo, newFilter(allProjects, user), true, "refs/sequences/changes"); |
| } |
| } |
| |
| @Test |
| public void receivePackListsOpenChangesAsAdditionalHaves() throws Exception { |
| ReceiveCommitsAdvertiseRefsHook.Result r = getReceivePackRefs(); |
| assertThat(r.allRefs().keySet()) |
| .containsExactly( |
| // meta refs are excluded even when NoteDb is enabled. |
| "HEAD", |
| "refs/heads/branch", |
| "refs/heads/master", |
| "refs/meta/config", |
| "refs/tags/branch-tag", |
| "refs/tags/master-tag"); |
| assertThat(r.additionalHaves()).containsExactly(obj(c3, 1), obj(c4, 1)); |
| } |
| |
| @Test |
| public void receivePackRespectsVisibilityOfOpenChanges() throws Exception { |
| allow("refs/heads/master", Permission.READ, REGISTERED_USERS); |
| deny("refs/heads/branch", Permission.READ, REGISTERED_USERS); |
| setApiUser(user); |
| |
| assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(c3, 1)); |
| } |
| |
| @Test |
| public void receivePackListsOnlyLatestPatchSet() throws Exception { |
| testRepo.reset(obj(c3, 1)); |
| PushOneCommit.Result r = amendChange(c3.change().getKey().get()); |
| r.assertOkStatus(); |
| c3 = r.getChange(); |
| assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(c3, 2), obj(c4, 1)); |
| } |
| |
| @Test |
| public void receivePackOmitsMissingObject() throws Exception { |
| String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; |
| try (Repository repo = repoManager.openRepository(project)) { |
| TestRepository<?> tr = new TestRepository<>(repo); |
| String subject = "Subject for missing commit"; |
| Change c = new Change(c3.change()); |
| PatchSet.Id psId = new PatchSet.Id(c3.getId(), 2); |
| c.setCurrentPatchSet(psId, subject, c.getOriginalSubject()); |
| |
| if (notesMigration.changePrimaryStorage() == PrimaryStorage.REVIEW_DB) { |
| PatchSet ps = TestChanges.newPatchSet(psId, rev, admin.getId()); |
| db.patchSets().insert(Collections.singleton(ps)); |
| db.changes().update(Collections.singleton(c)); |
| } |
| |
| if (notesMigration.commitChangeWrites()) { |
| PersonIdent committer = serverIdent.get(); |
| PersonIdent author = |
| noteUtil.newIdent(getAccount(admin.getId()), committer.getWhen(), committer); |
| tr.branch(RefNames.changeMetaRef(c3.getId())) |
| .commit() |
| .author(author) |
| .committer(committer) |
| .message( |
| "Update patch set " |
| + psId.get() |
| + "\n" |
| + "\n" |
| + "Patch-set: " |
| + psId.get() |
| + "\n" |
| + "Commit: " |
| + rev |
| + "\n" |
| + "Subject: " |
| + subject |
| + "\n") |
| .create(); |
| } |
| indexer.index(db, c.getProject(), c.getId()); |
| } |
| |
| assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(c4, 1)); |
| } |
| |
| @Test |
| public void advertisedReferencesDontShowUserBranchWithoutRead() throws Exception { |
| TestRepository<?> userTestRepository = cloneProject(allUsers, user); |
| try (Git git = userTestRepository.git()) { |
| assertThat(getUserRefs(git)).isEmpty(); |
| } |
| } |
| |
| @Test |
| public void advertisedReferencesOmitUserBranchesOfOtherUsers() throws Exception { |
| allow(allUsersName, RefNames.REFS_USERS + "*", Permission.READ, REGISTERED_USERS); |
| TestRepository<?> userTestRepository = cloneProject(allUsers, user); |
| try (Git git = userTestRepository.git()) { |
| assertThat(getUserRefs(git)) |
| .containsExactly(RefNames.REFS_USERS_SELF, RefNames.refsUsers(user.id)); |
| } |
| } |
| |
| @Test |
| public void advertisedReferencesIncludeAllUserBranchesWithAccessDatabase() throws Exception { |
| allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE); |
| TestRepository<?> userTestRepository = cloneProject(allUsers, user); |
| try (Git git = userTestRepository.git()) { |
| assertThat(getUserRefs(git)) |
| .containsExactly( |
| RefNames.REFS_USERS_SELF, RefNames.refsUsers(user.id), RefNames.refsUsers(admin.id)); |
| } |
| } |
| |
| @Test |
| @GerritConfig(name = "noteDb.groups.write", value = "true") |
| public void advertisedReferencesDontShowGroupBranchToOwnerWithoutRead() throws Exception { |
| try (ProjectResetter resetter = resetGroups()) { |
| createSelfOwnedGroup("Foos", user); |
| TestRepository<?> userTestRepository = cloneProject(allUsers, user); |
| try (Git git = userTestRepository.git()) { |
| assertThat(getGroupRefs(git)).isEmpty(); |
| } |
| } |
| } |
| |
| @Test |
| @GerritConfig(name = "noteDb.groups.write", value = "true") |
| public void advertisedReferencesOmitGroupBranchesOfNonOwnedGroups() throws Exception { |
| try (ProjectResetter resetter = resetGroups()) { |
| allow(allUsersName, RefNames.REFS_GROUPS + "*", Permission.READ, REGISTERED_USERS); |
| AccountGroup.UUID users = createGroup("Users", admins, user); |
| AccountGroup.UUID foos = createGroup("Foos", users); |
| AccountGroup.UUID bars = createSelfOwnedGroup("Bars", user); |
| TestRepository<?> userTestRepository = cloneProject(allUsers, user); |
| try (Git git = userTestRepository.git()) { |
| assertThat(getGroupRefs(git)) |
| .containsExactly(RefNames.refsGroups(foos), RefNames.refsGroups(bars)); |
| } |
| } |
| } |
| |
| @Test |
| @GerritConfig(name = "noteDb.groups.write", value = "true") |
| public void advertisedReferencesIncludeAllGroupBranchesWithAccessDatabase() throws Exception { |
| try (ProjectResetter resetter = resetGroups()) { |
| allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE); |
| AccountGroup.UUID users = createGroup("Users", admins); |
| TestRepository<?> userTestRepository = cloneProject(allUsers, user); |
| try (Git git = userTestRepository.git()) { |
| assertThat(getGroupRefs(git)) |
| .containsExactly( |
| RefNames.refsGroups(admins), |
| RefNames.refsGroups(nonInteractiveUsers), |
| RefNames.refsGroups(users)); |
| } |
| } |
| } |
| |
| @Test |
| @GerritConfig(name = "noteDb.groups.write", value = "true") |
| public void advertisedReferencesIncludeAllGroupBranchesForAdmins() throws Exception { |
| allow(allUsersName, RefNames.REFS_GROUPS + "*", Permission.READ, REGISTERED_USERS); |
| allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ADMINISTRATE_SERVER); |
| AccountGroup.UUID users = createGroup("Users", admins); |
| TestRepository<?> userTestRepository = cloneProject(allUsers, user); |
| try (Git git = userTestRepository.git()) { |
| assertThat(getGroupRefs(git)) |
| .containsExactly( |
| RefNames.refsGroups(admins), |
| RefNames.refsGroups(nonInteractiveUsers), |
| RefNames.refsGroups(users)); |
| } |
| } |
| |
| @Test |
| @GerritConfig(name = "noteDb.groups.write", value = "true") |
| public void advertisedReferencesOmitNoteDbNotesBranches() throws Exception { |
| allow(allUsersName, RefNames.REFS + "*", Permission.READ, REGISTERED_USERS); |
| TestRepository<?> userTestRepository = cloneProject(allUsers, user); |
| try (Git git = userTestRepository.git()) { |
| assertThat(getRefs(git)).containsNoneOf(RefNames.REFS_EXTERNAL_IDS, RefNames.REFS_GROUPNAMES); |
| } |
| } |
| |
| @Test |
| public void advertisedReferencesOmitPrivateChangesOfOtherUsers() throws Exception { |
| allow("refs/heads/master", Permission.READ, REGISTERED_USERS); |
| |
| TestRepository<?> userTestRepository = cloneProject(project, user); |
| try (Git git = userTestRepository.git()) { |
| String change3RefName = c3.currentPatchSet().getRefName(); |
| assertWithMessage("Precondition violated").that(getRefs(git)).contains(change3RefName); |
| |
| gApi.changes().id(c3.getId().get()).setPrivate(true, null); |
| assertThat(getRefs(git)).doesNotContain(change3RefName); |
| } |
| } |
| |
| @Test |
| public void advertisedReferencesIncludePrivateChangesWhenAllRefsMayBeRead() throws Exception { |
| allow("refs/*", Permission.READ, REGISTERED_USERS); |
| |
| TestRepository<?> userTestRepository = cloneProject(project, user); |
| try (Git git = userTestRepository.git()) { |
| String change3RefName = c3.currentPatchSet().getRefName(); |
| assertWithMessage("Precondition violated").that(getRefs(git)).contains(change3RefName); |
| |
| gApi.changes().id(c3.getId().get()).setPrivate(true, null); |
| assertThat(getRefs(git)).contains(change3RefName); |
| } |
| } |
| |
| @Test |
| public void advertisedReferencesOmitDraftCommentRefsOfOtherUsers() throws Exception { |
| assume().that(notesMigration.commitChangeWrites()).isTrue(); |
| |
| allow(project, "refs/*", Permission.READ, REGISTERED_USERS); |
| allow(allUsersName, "refs/*", Permission.READ, REGISTERED_USERS); |
| |
| setApiUser(user); |
| DraftInput draftInput = new DraftInput(); |
| draftInput.line = 1; |
| draftInput.message = "nit: trailing whitespace"; |
| draftInput.path = Patch.COMMIT_MSG; |
| gApi.changes().id(c3.getId().get()).current().createDraft(draftInput); |
| String draftCommentRef = RefNames.refsDraftComments(c3.getId(), user.id); |
| |
| // user can see the draft comment ref of the own draft comment |
| assertThat(lsRemote(allUsersName, user)).contains(draftCommentRef); |
| |
| // user2 can't see the draft comment ref of user's draft comment |
| assertThat(lsRemote(allUsersName, accountCreator.user2())).doesNotContain(draftCommentRef); |
| } |
| |
| @Test |
| public void advertisedReferencesOmitStarredChangesRefsOfOtherUsers() throws Exception { |
| assume().that(notesMigration.commitChangeWrites()).isTrue(); |
| |
| allow(project, "refs/*", Permission.READ, REGISTERED_USERS); |
| allow(allUsersName, "refs/*", Permission.READ, REGISTERED_USERS); |
| |
| setApiUser(user); |
| gApi.accounts().self().starChange(c3.getId().toString()); |
| String starredChangesRef = RefNames.refsStarredChanges(c3.getId(), user.id); |
| |
| // user can see the starred changes ref of the own star |
| assertThat(lsRemote(allUsersName, user)).contains(starredChangesRef); |
| |
| // user2 can't see the starred changes ref of admin's star |
| assertThat(lsRemote(allUsersName, accountCreator.user2())).doesNotContain(starredChangesRef); |
| } |
| |
| @Test |
| @GerritConfig(name = "noteDb.groups.write", value = "true") |
| public void hideMetadata() throws Exception { |
| allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE); |
| // create change |
| TestRepository<?> allUsersRepo = cloneProject(allUsers); |
| fetch(allUsersRepo, RefNames.REFS_USERS_SELF + ":userRef"); |
| allUsersRepo.reset("userRef"); |
| PushOneCommit.Result mr = |
| pushFactory |
| .create(db, admin.getIdent(), allUsersRepo) |
| .to("refs/for/" + RefNames.REFS_USERS_SELF); |
| mr.assertOkStatus(); |
| |
| List<String> expectedNonMetaRefs = |
| ImmutableList.of( |
| RefNames.REFS_USERS_SELF, |
| RefNames.refsUsers(admin.id), |
| RefNames.refsUsers(user.id), |
| RefNames.REFS_EXTERNAL_IDS, |
| RefNames.REFS_GROUPNAMES, |
| RefNames.refsGroups(admins), |
| RefNames.refsGroups(nonInteractiveUsers), |
| RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS, |
| RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS, |
| RefNames.REFS_CONFIG, |
| Constants.HEAD); |
| |
| List<String> expectedMetaRefs = |
| new ArrayList<>(ImmutableList.of(mr.getPatchSetId().toRefName())); |
| if (NoteDbMode.get() != NoteDbMode.OFF) { |
| expectedMetaRefs.add(changeRefPrefix(mr.getChange().getId()) + "meta"); |
| } |
| |
| List<String> expectedAllRefs = new ArrayList<>(expectedNonMetaRefs); |
| expectedAllRefs.addAll(expectedMetaRefs); |
| |
| try (Repository repo = repoManager.openRepository(allUsers)) { |
| Map<String, Ref> all = repo.getAllRefs(); |
| |
| PermissionBackend.ForProject forProject = newFilter(allUsers, admin); |
| assertThat(forProject.filter(all, repo, RefFilterOptions.defaults()).keySet()) |
| .containsExactlyElementsIn(expectedAllRefs); |
| assertThat( |
| forProject |
| .filter(all, repo, RefFilterOptions.builder().setFilterMeta(true).build()) |
| .keySet()) |
| .containsExactlyElementsIn(expectedNonMetaRefs); |
| } |
| } |
| |
| private List<String> lsRemote(Project.NameKey p, TestAccount a) throws Exception { |
| TestRepository<?> testRepository = cloneProject(p, a); |
| try (Git git = testRepository.git()) { |
| return git.lsRemote().call().stream().map(Ref::getName).collect(toList()); |
| } |
| } |
| |
| private List<String> getRefs(Git git) throws Exception { |
| return getRefs(git, Predicates.alwaysTrue()); |
| } |
| |
| private List<String> getUserRefs(Git git) throws Exception { |
| return getRefs(git, RefNames::isRefsUsers); |
| } |
| |
| private List<String> getGroupRefs(Git git) throws Exception { |
| return getRefs(git, RefNames::isRefsGroups); |
| } |
| |
| private List<String> getRefs(Git git, Predicate<String> predicate) throws Exception { |
| return git.lsRemote().call().stream().map(Ref::getName).filter(predicate).collect(toList()); |
| } |
| |
| /** |
| * Assert that refs seen by a non-admin user match expected. |
| * |
| * @param expectedWithMeta expected refs, in order. If NoteDb is disabled by the configuration, |
| * any NoteDb refs (i.e. ending in "/meta") are removed from the expected list before |
| * comparing to the actual results. |
| * @throws Exception |
| */ |
| private void assertUploadPackRefs(String... expectedWithMeta) throws Exception { |
| try (Repository repo = repoManager.openRepository(project)) { |
| assertRefs(repo, permissionBackend.user(user(user)).project(project), true, expectedWithMeta); |
| } |
| } |
| |
| private void assertRefs( |
| Repository repo, |
| PermissionBackend.ForProject forProject, |
| boolean disableDb, |
| String... expectedWithMeta) |
| throws Exception { |
| List<String> expected = new ArrayList<>(expectedWithMeta.length); |
| for (String r : expectedWithMeta) { |
| if (notesMigration.commitChangeWrites() || !r.endsWith(RefNames.META_SUFFIX)) { |
| expected.add(r); |
| } |
| } |
| |
| AcceptanceTestRequestScope.Context ctx = null; |
| if (disableDb) { |
| ctx = disableDb(); |
| } |
| try { |
| Map<String, Ref> all = repo.getAllRefs(); |
| assertThat(forProject.filter(all, repo, RefFilterOptions.defaults()).keySet()) |
| .containsExactlyElementsIn(expected); |
| } finally { |
| if (disableDb) { |
| enableDb(ctx); |
| } |
| } |
| } |
| |
| private ReceiveCommitsAdvertiseRefsHook.Result getReceivePackRefs() throws Exception { |
| ReceiveCommitsAdvertiseRefsHook hook = |
| new ReceiveCommitsAdvertiseRefsHook(queryProvider, project); |
| try (Repository repo = repoManager.openRepository(project)) { |
| return hook.advertiseRefs(repo.getAllRefs()); |
| } |
| } |
| |
| private PermissionBackend.ForProject newFilter(Project.NameKey project, TestAccount u) { |
| return permissionBackend.user(user(u)).project(project); |
| } |
| |
| private static ObjectId obj(ChangeData cd, int psNum) throws Exception { |
| PatchSet.Id psId = new PatchSet.Id(cd.getId(), psNum); |
| PatchSet ps = cd.patchSet(psId); |
| assertWithMessage("%s not found in %s", psId, cd.patchSets()).that(ps).isNotNull(); |
| return ObjectId.fromString(ps.getRevision().get()); |
| } |
| |
| private AccountGroup.UUID createSelfOwnedGroup(String name, TestAccount... members) |
| throws RestApiException { |
| return createGroup(name, null, members); |
| } |
| |
| private AccountGroup.UUID createGroup( |
| String name, @Nullable AccountGroup.UUID ownerGroup, TestAccount... members) |
| throws RestApiException { |
| GroupInput groupInput = new GroupInput(); |
| groupInput.name = name(name); |
| groupInput.ownerId = ownerGroup != null ? ownerGroup.get() : null; |
| groupInput.members = |
| Arrays.stream(members).map(m -> String.valueOf(m.id.get())).collect(toList()); |
| return new AccountGroup.UUID(gApi.groups().create(groupInput).get().id); |
| } |
| |
| /** |
| * Create a resetter to reset the group branches in All-Users. This makes the group data between |
| * ReviewDb and NoteDb inconsistent, but in the context of this test class we only care about refs |
| * and hence this is not an issue. Once groups are no longer in ReviewDb and {@link |
| * AbstractDaemonTest#resetProjects} takes care to reset group branches we no longer need this |
| * method. |
| */ |
| private ProjectResetter resetGroups() throws IOException { |
| return projectResetter |
| .builder() |
| .build( |
| new ProjectResetter.Config() |
| .reset(allUsers, RefNames.REFS_GROUPS + "*", RefNames.REFS_GROUPNAMES)); |
| } |
| } |