| // 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.acceptance.testsuite.project.TestProjectUpdate.allow; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey; |
| 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.testing.GerritJUnit.assertThrows; |
| |
| import com.google.gerrit.acceptance.AbstractDaemonTest; |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.acceptance.testsuite.change.ChangeOperations; |
| import com.google.gerrit.acceptance.testsuite.group.GroupOperations; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.common.data.GlobalCapability; |
| import com.google.gerrit.entities.AccountGroup; |
| import com.google.gerrit.entities.BranchNameKey; |
| import com.google.gerrit.entities.Change; |
| import com.google.gerrit.entities.PatchSet; |
| import com.google.gerrit.entities.Permission; |
| import com.google.gerrit.entities.Project; |
| import com.google.gerrit.entities.RefNames; |
| 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.server.group.SystemGroupBackend; |
| import com.google.gerrit.server.notedb.Sequences; |
| import com.google.gerrit.testing.ConfigSuite; |
| import com.google.inject.Inject; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.eclipse.jgit.lib.Config; |
| import org.eclipse.jgit.lib.Repository; |
| import org.junit.Test; |
| |
| public class GetBranchIT extends AbstractDaemonTest { |
| @Inject private ChangeOperations changeOperations; |
| @Inject private GroupOperations groupOperations; |
| @Inject private ProjectOperations projectOperations; |
| @Inject private RequestScopeOperations requestScopeOperations; |
| |
| @ConfigSuite.Config |
| public static Config skipFalse() { |
| Config config = new Config(); |
| config.setBoolean("auth", null, "skipFullRefEvaluationIfAllRefsAreVisible", false); |
| return config; |
| } |
| |
| @Test |
| public void cannotGetNonExistingBranch() { |
| assertBranchNotFound(project, RefNames.fullName("non-existing")); |
| } |
| |
| @Test |
| public void getBranch() throws Exception { |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchFound(project, RefNames.fullName("master")); |
| } |
| |
| @Test |
| public void getBranchByShortName() throws Exception { |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchFound(project, "master"); |
| } |
| |
| @Test |
| public void cannotGetNonVisibleBranch() { |
| String branchName = "master"; |
| |
| // block read access to the branch |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS)) |
| .update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchNotFound(project, RefNames.fullName(branchName)); |
| } |
| |
| @Test |
| public void cannotGetNonVisibleBranchByShortName() { |
| String branchName = "master"; |
| |
| // block read access to the branch |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS)) |
| .update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchNotFound(project, branchName); |
| } |
| |
| @Test |
| public void getChangeRef() throws Exception { |
| // create a change |
| Change.Id changeId = changeOperations.newChange().project(project).create(); |
| |
| // a user without the 'Access Database' capability can see the change ref |
| requestScopeOperations.setApiUser(user.id()); |
| String changeRef = RefNames.patchSetRef(PatchSet.id(changeId, 1)); |
| assertBranchFound(project, changeRef); |
| } |
| |
| @Test |
| public void getChangeRefOfNonVisibleChange() throws Exception { |
| // create a change |
| String branchName = "master"; |
| Change.Id changeId = changeOperations.newChange().project(project).branch(branchName).create(); |
| |
| // block read access to the branch |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS)) |
| .update(); |
| |
| // a user without the 'Access Database' capability cannot see the change ref |
| requestScopeOperations.setApiUser(user.id()); |
| String changeRef = RefNames.patchSetRef(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 = changeOperations.newChange().project(project).create(); |
| |
| // create a change edit by 'user' |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes().id(changeId.get()).edit().create(); |
| |
| // every user can see their own change edit refs |
| String changeEditRef = RefNames.refsEdit(user.id(), changeId, PatchSet.id(changeId, 1)); |
| assertBranchFound(project, changeEditRef); |
| |
| // a user without the 'Access Database' capability cannot see the change edit ref of another |
| // user |
| requestScopeOperations.setApiUser(user2.id()); |
| 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 = changeOperations.newChange().project(project).branch(branchName).create(); |
| |
| // create a change edit by 'user' |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes().id(changeId.get()).edit().create(); |
| |
| // make the change non-visible by blocking read access on the destination |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS)) |
| .update(); |
| |
| // user cannot see their own change edit refs if the change is no longer visible |
| String changeEditRef = RefNames.refsEdit(user.id(), changeId, 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 = changeOperations.newChange().project(project).create(); |
| |
| // 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. |
| requestScopeOperations.setApiUser(user.id()); |
| String changeMetaRef = RefNames.changeMetaRef(changeId); |
| assertBranchFound(project, changeMetaRef); |
| } |
| |
| @Test |
| public void getRefsMetaConfig() throws Exception { |
| // a non-project owner cannot get the refs/meta/config branch |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchNotFound(project, RefNames.REFS_CONFIG); |
| |
| // a non-project owner cannot get the refs/meta/config branch even with the 'Access Database' |
| // capability |
| projectOperations |
| .project(allProjects) |
| .forUpdate() |
| .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) |
| .update(); |
| try { |
| assertBranchNotFound(project, RefNames.REFS_CONFIG); |
| } finally { |
| projectOperations |
| .allProjectsForUpdate() |
| .remove( |
| capabilityKey(GlobalCapability.ACCESS_DATABASE) |
| .group(SystemGroupBackend.REGISTERED_USERS)) |
| .update(); |
| } |
| |
| requestScopeOperations.setApiUser(user.id()); |
| |
| // a project owner can get the refs/meta/config branch |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS)) |
| .update(); |
| 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 |
| requestScopeOperations.setApiUser(user.id()); |
| 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 |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchFound(allUsers, RefNames.refsUsers(user.id())); |
| |
| // every user can see the own user ref via the magic ref/users/self ref. For this special case, |
| // the branch in the request is refs/users/self, but the response contains the actual |
| // refs/users/$sharded_id/$id |
| BranchInfo branchInfo = |
| gApi.projects().name(allUsers.get()).branch(RefNames.REFS_USERS_SELF).get(); |
| assertThat(branchInfo.ref).isEqualTo(RefNames.refsUsers(user.id())); |
| } |
| |
| @Test |
| public void getExternalIdsRefs() throws Exception { |
| // a user without the 'Access Database' capability cannot see the refs/meta/external-ids ref |
| requestScopeOperations.setApiUser(user.id()); |
| 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 { |
| // create a group |
| AccountGroup.UUID ownerGroupUuid = |
| groupOperations.newGroup().name("owner-group").addMember(admin.id()).create(); |
| AccountGroup.UUID testGroupUuid = |
| groupOperations.newGroup().name("test-group").ownerGroupUuid(ownerGroupUuid).create(); |
| |
| // a non-group owner without the 'Access Database' capability cannot see the group ref |
| requestScopeOperations.setApiUser(user.id()); |
| String groupRef = RefNames.refsGroups(testGroupUuid); |
| assertBranchNotFound(allUsers, groupRef); |
| |
| // a non-group owner with the 'Access Database' capability can see the group ref |
| testGetRefWithAccessDatabase(allUsers, groupRef); |
| |
| // a group owner can see the group ref if the group ref is visible |
| groupOperations.group(ownerGroupUuid).forUpdate().addMember(user.id()).update(); |
| assertBranchFound(allUsers, groupRef); |
| |
| // A group owner cannot see the group ref if the group ref is not visible. |
| // The READ access for refs/groups/* must be blocked on All-Projects rather than All-Users. |
| // This is because READ access for refs/groups/* on All-Users is by default granted to |
| // REGISTERED_USERS, and if an ALLOW rule and a BLOCK rule are on the same project and ref, |
| // the ALLOW rule takes precedence. |
| projectOperations |
| .project(allProjects) |
| .forUpdate() |
| .add(block(Permission.READ).ref("refs/groups/*").group(ANONYMOUS_USERS)) |
| .update(); |
| assertBranchNotFound(allUsers, groupRef); |
| } |
| |
| @Test |
| public void getGroupNamesRef() throws Exception { |
| // a user without the 'Access Database' capability cannot see the refs/meta/group-names ref |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchNotFound(allUsers, RefNames.REFS_GROUPNAMES); |
| |
| // a user with the 'Access Database' capability can see the refs/meta/group-names ref |
| testGetRefWithAccessDatabase(allUsers, RefNames.REFS_GROUPNAMES); |
| } |
| |
| @Test |
| public void getDeletedGroupRef() throws Exception { |
| // Create a deleted group ref. We must create a directly in the repo, since group deletion is |
| // not supported yet. |
| String deletedGroupRef = RefNames.refsDeletedGroups(AccountGroup.uuid("deleted-group")); |
| try (TestRepository<Repository> testRepo = |
| new TestRepository<>(repoManager.openRepository(allUsers))) { |
| testRepo |
| .branch(deletedGroupRef) |
| .commit() |
| .message("Some Message") |
| .add("group.config", "content") |
| .create(); |
| } |
| |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchNotFound(allUsers, deletedGroupRef); |
| |
| // a user with the 'Access Database' capability can see the deleted group ref |
| testGetRefWithAccessDatabase(allUsers, deletedGroupRef); |
| } |
| |
| @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' |
| requestScopeOperations.setApiUser(user.id()); |
| 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()); |
| assertBranchFound(allUsers, draftCommentsRef); |
| |
| // a user without the 'Access Database' capability cannot see the draft comments ref of another |
| // user |
| requestScopeOperations.setApiUser(user2.id()); |
| assertBranchNotFound(allUsers, draftCommentsRef); |
| |
| // a user with the 'Access Database' capability can see the draft comments ref of another user |
| 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 |
| requestScopeOperations.setApiUser(user.id()); |
| 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 |
| requestScopeOperations.setApiUser(user2.id()); |
| 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 = projectOperations.project(project).getHead("master").name(); |
| TagInfo tagInfo = gApi.projects().name(project.get()).tag("my-tag").create(input).get(); |
| |
| // any user who can see the project, can see the tag |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchFound(project, tagInfo.ref); |
| } |
| |
| @Test |
| public void cannotGetTagRefThatPointsToNonVisibleBranch() throws Exception { |
| // create a tag |
| TagInput input = new TagInput(); |
| input.message = "My Tag"; |
| input.revision = projectOperations.project(project).getHead("master").name(); |
| TagInfo tagInfo = gApi.projects().name(project.get()).tag("my-tag").create(input).get(); |
| |
| // block read access to the branch |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(block(Permission.READ).ref(RefNames.fullName("master")).group(ANONYMOUS_USERS)) |
| .update(); |
| |
| // if the user cannot see the project, the tag is not visible |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchNotFound(project, tagInfo.ref); |
| } |
| |
| @Test |
| public void getSymbolicRef() throws Exception { |
| // 'HEAD' is visible since it points to 'master' that is visible |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchFound(project, "HEAD"); |
| } |
| |
| @Test |
| public void cannotGetSymbolicRefThatPointsToNonVisibleBranch() { |
| // block read access to the branch to which HEAD points by default |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(block(Permission.READ).ref(RefNames.fullName("master")).group(ANONYMOUS_USERS)) |
| .update(); |
| |
| // since 'master' is not visible, 'HEAD' which points to 'master' is also not visible |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchNotFound(project, "HEAD"); |
| } |
| |
| @Test |
| public void getAccountSequenceRef() throws Exception { |
| // a user without the 'Access Database' capability cannot see the refs/sequences/accounts ref |
| requestScopeOperations.setApiUser(user.id()); |
| String accountSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS; |
| assertBranchNotFound(allUsers, accountSequenceRef); |
| |
| // a user with the 'Access Database' capability can see the refs/sequences/accounts ref |
| testGetRefWithAccessDatabase(allUsers, accountSequenceRef); |
| } |
| |
| @Test |
| public void getChangeSequenceRef() throws Exception { |
| // a user without the 'Access Database' capability cannot see the refs/sequences/changes ref |
| requestScopeOperations.setApiUser(user.id()); |
| String changeSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_CHANGES; |
| assertBranchNotFound(allProjects, changeSequenceRef); |
| |
| // a user with the 'Access Database' capability can see the refs/sequences/changes ref |
| testGetRefWithAccessDatabase(allProjects, changeSequenceRef); |
| } |
| |
| @Test |
| public void getGroupSequenceRef() throws Exception { |
| // a user without the 'Access Database' capability cannot see the refs/sequences/groups ref |
| requestScopeOperations.setApiUser(user.id()); |
| String groupSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS; |
| assertBranchNotFound(allUsers, groupSequenceRef); |
| |
| // a user with the 'Access Database' capability can see the refs/sequences/groups ref |
| testGetRefWithAccessDatabase(allUsers, groupSequenceRef); |
| } |
| |
| @Test |
| public void getVersionMetaRef() throws Exception { |
| // TODO: a user without the 'Access Database' capability cannot see the refs/meta/version ref |
| // requestScopeOperations.setApiUser(user.id()); |
| // assertBranchNotFound(allProjects, RefNames.REFS_VERSION); |
| |
| // a user with the 'Access Database' capability can see the refs/meta/vaersion ref |
| testGetRefWithAccessDatabase(allProjects, RefNames.REFS_VERSION); |
| } |
| |
| @Test |
| public void cannotGetAutoMergeRef() throws Exception { |
| String file = "foo/a.txt"; |
| |
| // Create a base change. |
| Change.Id baseChange = |
| changeOperations |
| .newChange() |
| .project(project) |
| .branch("master") |
| .file(file) |
| .content("base content") |
| .create(); |
| approve(Integer.toString(baseChange.get())); |
| gApi.changes().id(baseChange.get()).current().submit(); |
| |
| // Create another branch |
| String branchName = "foo"; |
| createBranchWithRevision( |
| BranchNameKey.create(project, branchName), |
| projectOperations.project(project).getHead("master").name()); |
| |
| // Create a change in master that touches the file. |
| Change.Id changeInMaster = |
| changeOperations |
| .newChange() |
| .project(project) |
| .branch("master") |
| .file(file) |
| .content("master content") |
| .create(); |
| approve(Integer.toString(changeInMaster.get())); |
| gApi.changes().id(changeInMaster.get()).current().submit(); |
| |
| // Create a change in the other branch and that touches the file. |
| Change.Id changeInOtherBranch = |
| changeOperations |
| .newChange() |
| .project(project) |
| .branch(branchName) |
| .file(file) |
| .content("other content") |
| .create(); |
| approve(Integer.toString(changeInOtherBranch.get())); |
| gApi.changes().id(changeInOtherBranch.get()).current().submit(); |
| |
| // Create a merge change with a conflict resolution for the file. |
| Change.Id mergeChange = |
| changeOperations |
| .newChange() |
| .project(project) |
| .branch("master") |
| .mergeOfButBaseOnFirst() |
| .tipOfBranch("master") |
| .and() |
| .tipOfBranch(branchName) |
| .file(file) |
| .content("merged content") |
| .create(); |
| |
| String mergeRevision = |
| changeOperations.change(mergeChange).currentPatchset().get().commitId().name(); |
| assertBranchNotFound(project, RefNames.refsCacheAutomerge(mergeRevision)); |
| } |
| |
| private void testGetRefWithAccessDatabase(Project.NameKey project, String ref) |
| throws RestApiException { |
| projectOperations |
| .project(allProjects) |
| .forUpdate() |
| .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) |
| .update(); |
| try { |
| requestScopeOperations.setApiUser(user.id()); |
| assertBranchFound(project, ref); |
| } finally { |
| projectOperations |
| .allProjectsForUpdate() |
| .remove( |
| capabilityKey(GlobalCapability.ACCESS_DATABASE) |
| .group(SystemGroupBackend.REGISTERED_USERS)) |
| .update(); |
| } |
| } |
| |
| 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)); |
| } |
| } |