Merge "Fix Block expansion tooltip position (top/bottom)"
diff --git a/java/com/google/gerrit/common/PluginData.java b/java/com/google/gerrit/common/PluginData.java
index c440de1..289d93a 100644
--- a/java/com/google/gerrit/common/PluginData.java
+++ b/java/com/google/gerrit/common/PluginData.java
@@ -10,7 +10,7 @@
// 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.common;
+// limitations under the License.
package com.google.gerrit.common;
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index b46d10d..5a74047 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -428,6 +428,11 @@
* Updates multiple different accounts atomically. This will only store a single new value (aka
* set of all external IDs of the host) in the external ID cache, which is important for storage
* economy. All {@code updates} must be for different accounts.
+ *
+ * <p>NOTE on error handling: Since updates are executed in multiple stages, with some stages
+ * resulting from the union of all individual updates, we cannot point to the update that caused
+ * the error. Callers should be aware that a single "update of death" (or a set of updates that
+ * together have this property) will always prevent the entire batch from being executed.
*/
public ImmutableList<Optional<AccountState>> updateBatch(List<UpdateArguments> updates)
throws IOException, ConfigInvalidException {
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index d15a415..3a35d80 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -232,7 +232,14 @@
throws UpdateException, RestApiException {
try (ManualRequestContext ctx = oneOffRequestContext.openAs(sender)) {
List<ChangeData> changeDataList =
- queryProvider.get().byLegacyChangeId(Change.id(metadata.changeNumber));
+ queryProvider
+ .get()
+ .enforceVisibility(true)
+ .byLegacyChangeId(Change.id(metadata.changeNumber));
+ if (changeDataList.isEmpty()) {
+ sendRejectionEmail(message, InboundEmailRejectionSender.Error.CHANGE_NOT_FOUND);
+ return;
+ }
if (changeDataList.size() != 1) {
logger.atSevere().log(
"Message %s references unique change %s,"
diff --git a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
index 709bf61..acdeb5a 100644
--- a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
+++ b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
@@ -33,7 +33,8 @@
INACTIVE_ACCOUNT,
UNKNOWN_ACCOUNT,
INTERNAL_EXCEPTION,
- COMMENT_REJECTED
+ COMMENT_REJECTED,
+ CHANGE_NOT_FOUND
}
public interface Factory {
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index dd00dca..f800207 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -523,7 +523,10 @@
+ "who also have 'Push' rights on "
+ RefNames.REFS_CONFIG);
} else {
- pde.setAdvice("To push into this reference you need 'Push' rights.");
+ pde.setAdvice(
+ "Push to refs/for/"
+ + RefNames.shortName(refName)
+ + " to create a review, or get 'Push' rights to update the branch.");
}
break;
case DELETE:
diff --git a/java/com/google/gerrit/server/restapi/access/ListAccess.java b/java/com/google/gerrit/server/restapi/access/ListAccess.java
index 1e1bade..dca969d 100644
--- a/java/com/google/gerrit/server/restapi/access/ListAccess.java
+++ b/java/com/google/gerrit/server/restapi/access/ListAccess.java
@@ -14,11 +14,17 @@
package com.google.gerrit.server.restapi.access;
+import com.google.common.base.Strings;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.restapi.project.GetAccess;
import com.google.inject.Inject;
import java.util.ArrayList;
@@ -41,10 +47,15 @@
usage = "projects for which the access rights should be returned")
private List<String> projects = new ArrayList<>();
+ private final PermissionBackend permissionBackend;
+ private final ProjectCache projectCache;
private final GetAccess getAccess;
@Inject
- public ListAccess(GetAccess getAccess) {
+ public ListAccess(
+ PermissionBackend permissionBackend, ProjectCache projectCache, GetAccess getAccess) {
+ this.permissionBackend = permissionBackend;
+ this.projectCache = projectCache;
this.getAccess = getAccess;
}
@@ -53,7 +64,23 @@
throws Exception {
Map<String, ProjectAccessInfo> access = new TreeMap<>();
for (String p : projects) {
- access.put(p, getAccess.apply(Project.nameKey(p)));
+ if (Strings.nullToEmpty(p).isEmpty()) {
+ continue;
+ }
+
+ Project.NameKey projectName = Project.nameKey(p);
+
+ if (!projectCache.get(projectName).isPresent()) {
+ throw new ResourceNotFoundException(projectName.get());
+ }
+
+ try {
+ permissionBackend.currentUser().project(projectName).check(ProjectPermission.ACCESS);
+ } catch (AuthException e) {
+ throw new ResourceNotFoundException(projectName.get(), e);
+ }
+
+ access.put(p, getAccess.apply(projectName));
}
return Response.ok(access);
}
diff --git a/java/com/google/gerrit/server/restapi/change/CommentJson.java b/java/com/google/gerrit/server/restapi/change/CommentJson.java
index edc8fcf..81b6fb3 100644
--- a/java/com/google/gerrit/server/restapi/change/CommentJson.java
+++ b/java/com/google/gerrit/server/restapi/change/CommentJson.java
@@ -190,7 +190,7 @@
return CommentContextKey.builder()
.project(project)
.changeId(changeId)
- .id(r.id)
+ .id(Url.decode(r.id)) // We reverse the encoding done while filling comment info
.path(r.path)
.patchset(r.patchSet)
.contextPadding(contextPadding)
diff --git a/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java
new file mode 100644
index 0000000..32e8232
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java
@@ -0,0 +1,1015 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
+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.server.schema.AclUtil.grant;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static com.google.gerrit.truth.ConfigSubject.assertThat;
+import static com.google.gerrit.truth.MapSubject.assertThatMap;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.PushOneCommit;
+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.AccessSection;
+import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.entities.LabelId;
+import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.access.AccessSectionInfo;
+import com.google.gerrit.extensions.api.access.PermissionInfo;
+import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.ProjectApi;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+import com.google.gerrit.extensions.config.PluginProjectPermissionDefinition;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.webui.FileHistoryWebLink;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.server.schema.GrantRevertPermission;
+import com.google.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AccessIT extends AbstractDaemonTest {
+
+ private static final String REFS_ALL = Constants.R_REFS + "*";
+ private static final String REFS_HEADS = Constants.R_HEADS + "*";
+ private static final String REFS_META_VERSION = "refs/meta/version";
+ private static final String REFS_DRAFTS = "refs/draft-comments/*";
+ private static final String REFS_STARRED_CHANGES = "refs/starred-changes/*";
+
+ @Inject private ProjectOperations projectOperations;
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ExtensionRegistry extensionRegistry;
+ @Inject private GrantRevertPermission grantRevertPermission;
+
+ private Project.NameKey newProjectName;
+
+ @Before
+ public void setUp() throws Exception {
+ newProjectName = projectOperations.newProject().create();
+ }
+
+ @Test
+ public void grantRevertPermission() throws Exception {
+ String ref = "refs/*";
+ String groupId = "global:Registered-Users";
+
+ grantRevertPermission.execute(newProjectName);
+
+ ProjectAccessInfo info = pApi().access();
+ assertThat(info.local.containsKey(ref)).isTrue();
+ AccessSectionInfo accessSectionInfo = info.local.get(ref);
+ assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isTrue();
+ PermissionInfo permissionInfo = accessSectionInfo.permissions.get(Permission.REVERT);
+ assertThat(permissionInfo.rules.containsKey(groupId)).isTrue();
+ PermissionRuleInfo permissionRuleInfo = permissionInfo.rules.get(groupId);
+ assertThat(permissionRuleInfo.action).isEqualTo(PermissionRuleInfo.Action.ALLOW);
+ }
+
+ @Test
+ public void grantRevertPermissionByOnNewRefAndDeletingOnOldRef() throws Exception {
+ String refsHeads = "refs/heads/*";
+ String refsStar = "refs/*";
+ String groupId = "global:Registered-Users";
+ GroupReference registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS);
+
+ try (Repository repo = repoManager.openRepository(newProjectName)) {
+ MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
+ ProjectConfig projectConfig = projectConfigFactory.read(md);
+ projectConfig.upsertAccessSection(
+ AccessSection.HEADS,
+ heads -> {
+ grant(projectConfig, heads, Permission.REVERT, registeredUsers);
+ });
+ md.getCommitBuilder().setAuthor(admin.newIdent());
+ md.getCommitBuilder().setCommitter(admin.newIdent());
+ md.setMessage("Add revert permission for all registered users\n");
+
+ projectConfig.commit(md);
+ }
+ grantRevertPermission.execute(newProjectName);
+
+ ProjectAccessInfo info = pApi().access();
+
+ // Revert permission is removed on refs/heads/*.
+ assertThat(info.local.containsKey(refsHeads)).isTrue();
+ AccessSectionInfo accessSectionInfo = info.local.get(refsHeads);
+ assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isFalse();
+
+ // new permission is added on refs/* with Registered-Users.
+ assertThat(info.local.containsKey(refsStar)).isTrue();
+ accessSectionInfo = info.local.get(refsStar);
+ assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isTrue();
+ PermissionInfo permissionInfo = accessSectionInfo.permissions.get(Permission.REVERT);
+ assertThat(permissionInfo.rules.containsKey(groupId)).isTrue();
+ PermissionRuleInfo permissionRuleInfo = permissionInfo.rules.get(groupId);
+ assertThat(permissionRuleInfo.action).isEqualTo(PermissionRuleInfo.Action.ALLOW);
+ }
+
+ @Test
+ public void grantRevertPermissionDoesntDeleteAdminsPreferences() throws Exception {
+ GroupReference registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS);
+ GroupReference otherGroup = systemGroupBackend.getGroup(ANONYMOUS_USERS);
+
+ try (Repository repo = repoManager.openRepository(newProjectName)) {
+ MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
+ ProjectConfig projectConfig = projectConfigFactory.read(md);
+ projectConfig.upsertAccessSection(
+ AccessSection.HEADS,
+ heads -> {
+ grant(projectConfig, heads, Permission.REVERT, registeredUsers);
+ grant(projectConfig, heads, Permission.REVERT, otherGroup);
+ });
+ md.getCommitBuilder().setAuthor(admin.newIdent());
+ md.getCommitBuilder().setCommitter(admin.newIdent());
+ md.setMessage("Add revert permission for all registered users\n");
+
+ projectConfig.commit(md);
+ }
+ projectCache.evict(newProjectName);
+ ProjectAccessInfo expected = pApi().access();
+
+ grantRevertPermission.execute(newProjectName);
+ projectCache.evict(newProjectName);
+ ProjectAccessInfo actual = pApi().access();
+ // Permissions don't change
+ assertThat(expected.local).isEqualTo(actual.local);
+ }
+
+ @Test
+ public void grantRevertPermissionOnlyWorksOnce() throws Exception {
+ grantRevertPermission.execute(newProjectName);
+ grantRevertPermission.execute(newProjectName);
+
+ try (Repository repo = repoManager.openRepository(newProjectName)) {
+ MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
+ ProjectConfig projectConfig = projectConfigFactory.read(md);
+ AccessSection all = projectConfig.getAccessSection(AccessSection.ALL);
+
+ Permission permission = all.getPermission(Permission.REVERT);
+ assertThat(permission.getRules()).hasSize(1);
+ }
+ }
+
+ @Test
+ public void getDefaultInheritance() throws Exception {
+ String inheritedName = pApi().access().inheritsFrom.name;
+ assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
+ }
+
+ private Registration newFileHistoryWebLink() {
+ FileHistoryWebLink weblink =
+ new FileHistoryWebLink() {
+ @Override
+ public WebLinkInfo getFileHistoryWebLink(
+ String projectName, String revision, String fileName) {
+ return new WebLinkInfo(
+ "name", "imageURL", "http://view/" + projectName + "/" + fileName);
+ }
+ };
+ return extensionRegistry.newRegistration().add(weblink);
+ }
+
+ @Test
+ public void webLink() throws Exception {
+ try (Registration registration = newFileHistoryWebLink()) {
+ ProjectAccessInfo info = pApi().access();
+ assertThat(info.configWebLinks).hasSize(1);
+ assertThat(info.configWebLinks.get(0).url)
+ .isEqualTo("http://view/" + newProjectName + "/project.config");
+ }
+ }
+
+ @Test
+ public void webLinkNoRefsMetaConfig() throws Exception {
+ try (Repository repo = repoManager.openRepository(newProjectName);
+ Registration registration = newFileHistoryWebLink()) {
+ RefUpdate u = repo.updateRef(RefNames.REFS_CONFIG);
+ u.setForceUpdate(true);
+ assertThat(u.delete()).isEqualTo(Result.FORCED);
+
+ // This should not crash.
+ pApi().access();
+ }
+ }
+
+ @Test
+ public void addAccessSection() throws Exception {
+ RevCommit initialHead = projectOperations.project(newProjectName).getHead(RefNames.REFS_CONFIG);
+
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi().access(accessInput);
+
+ assertThat(pApi().access().local).isEqualTo(accessInput.add);
+
+ RevCommit updatedHead = projectOperations.project(newProjectName).getHead(RefNames.REFS_CONFIG);
+ eventRecorder.assertRefUpdatedEvents(
+ newProjectName.get(), RefNames.REFS_CONFIG, null, initialHead, initialHead, updatedHead);
+ }
+
+ @Test
+ public void addAccessSectionForPluginPermission() throws Exception {
+ try (Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(
+ new PluginProjectPermissionDefinition() {
+ @Override
+ public String getDescription() {
+ return "A Plugin Project Permission";
+ }
+ },
+ "fooPermission")) {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+ PermissionInfo foo = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ foo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSectionInfo.permissions.put(
+ "plugin-" + ExtensionRegistry.PLUGIN_NAME + "-fooPermission", foo);
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ ProjectAccessInfo updatedAccessSectionInfo = pApi().access(accessInput);
+ assertThat(updatedAccessSectionInfo.local).isEqualTo(accessInput.add);
+
+ assertThat(pApi().access().local).isEqualTo(accessInput.add);
+ }
+ }
+
+ @Test
+ public void addAccessSectionWithInvalidPermission() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ PermissionInfo push = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSectionInfo.permissions.put("Invalid Permission", push);
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ BadRequestException ex =
+ assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
+ assertThat(ex).hasMessageThat().isEqualTo("Unknown permission: Invalid Permission");
+ }
+
+ @Test
+ public void addAccessSectionWithInvalidLabelPermission() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ PermissionInfo push = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSectionInfo.permissions.put("label-Invalid Permission", push);
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ BadRequestException ex =
+ assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
+ assertThat(ex).hasMessageThat().isEqualTo("Unknown permission: label-Invalid Permission");
+ }
+
+ @Test
+ public void createAccessChangeNop() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ assertThrows(BadRequestException.class, () -> pApi().accessChange(accessInput));
+ }
+
+ @Test
+ public void createAccessChangeEmptyConfig() throws Exception {
+ try (Repository repo = repoManager.openRepository(newProjectName)) {
+ RefUpdate ru = repo.updateRef(RefNames.REFS_CONFIG);
+ ru.setForceUpdate(true);
+ assertThat(ru.delete()).isEqualTo(Result.FORCED);
+
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+ PermissionInfo read = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.BLOCK, false);
+ read.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSection.permissions.put(Permission.READ, read);
+ accessInput.add.put(REFS_HEADS, accessSection);
+
+ ChangeInfo out = pApi().accessChange(accessInput);
+ assertThat(out.status).isEqualTo(ChangeStatus.NEW);
+ }
+ }
+
+ @Test
+ public void createAccessChange() throws Exception {
+ projectOperations
+ .project(newProjectName)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
+ // User can see the branch
+ requestScopeOperations.setApiUser(user.id());
+ pApi().branch("refs/heads/master").get();
+
+ ProjectAccessInput accessInput = newProjectAccessInput();
+
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+
+ // Deny read to registered users.
+ PermissionInfo read = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
+ read.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ read.exclusive = true;
+ accessSection.permissions.put(Permission.READ, read);
+ accessInput.add.put(REFS_HEADS, accessSection);
+
+ requestScopeOperations.setApiUser(user.id());
+ ChangeInfo out = pApi().accessChange(accessInput);
+
+ assertThat(out.project).isEqualTo(newProjectName.get());
+ assertThat(out.branch).isEqualTo(RefNames.REFS_CONFIG);
+ assertThat(out.status).isEqualTo(ChangeStatus.NEW);
+ assertThat(out.submitted).isNull();
+
+ requestScopeOperations.setApiUser(admin.id());
+
+ ChangeInfo c = gApi.changes().id(out._number).get(MESSAGES);
+ assertThat(c.messages.stream().map(m -> m.message)).containsExactly("Uploaded patch set 1");
+
+ ReviewInput reviewIn = new ReviewInput();
+ reviewIn.label("Code-Review", (short) 2);
+ gApi.changes().id(out._number).current().review(reviewIn);
+ gApi.changes().id(out._number).current().submit();
+
+ // check that the change took effect.
+ requestScopeOperations.setApiUser(user.id());
+ assertThrows(ResourceNotFoundException.class, () -> pApi().branch("refs/heads/master").get());
+
+ // Restore.
+ accessInput.add.clear();
+ accessInput.remove.put(REFS_HEADS, accessSection);
+ requestScopeOperations.setApiUser(user.id());
+
+ requestScopeOperations.setApiUser(admin.id());
+ out = pApi().accessChange(accessInput);
+
+ gApi.changes().id(out._number).current().review(reviewIn);
+ gApi.changes().id(out._number).current().submit();
+
+ // Now it works again.
+ requestScopeOperations.setApiUser(user.id());
+ pApi().branch("refs/heads/master").get();
+ }
+
+ @Test
+ public void removePermission() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi().access(accessInput);
+
+ // Remove specific permission
+ AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+ accessSectionToRemove.permissions.put(
+ Permission.LABEL + LabelId.CODE_REVIEW, newPermissionInfo());
+ ProjectAccessInput removal = newProjectAccessInput();
+ removal.remove.put(REFS_HEADS, accessSectionToRemove);
+ pApi().access(removal);
+
+ // Remove locally
+ accessInput.add.get(REFS_HEADS).permissions.remove(Permission.LABEL + LabelId.CODE_REVIEW);
+
+ // Check
+ assertThat(pApi().access().local).isEqualTo(accessInput.add);
+ }
+
+ @Test
+ public void removePermissionRule() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi().access(accessInput);
+
+ // Remove specific permission rule
+ AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+ PermissionInfo codeReview = newPermissionInfo();
+ codeReview.label = LabelId.CODE_REVIEW;
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
+ codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSectionToRemove.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
+ ProjectAccessInput removal = newProjectAccessInput();
+ removal.remove.put(REFS_HEADS, accessSectionToRemove);
+ pApi().access(removal);
+
+ // Remove locally
+ accessInput
+ .add
+ .get(REFS_HEADS)
+ .permissions
+ .get(Permission.LABEL + LabelId.CODE_REVIEW)
+ .rules
+ .remove(SystemGroupBackend.REGISTERED_USERS.get());
+
+ // Check
+ assertThat(pApi().access().local).isEqualTo(accessInput.add);
+ }
+
+ @Test
+ public void removePermissionRulesAndCleanupEmptyEntries() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi().access(accessInput);
+
+ // Remove specific permission rules
+ AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+ PermissionInfo codeReview = newPermissionInfo();
+ codeReview.label = LabelId.CODE_REVIEW;
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
+ codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
+ codeReview.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
+ accessSectionToRemove.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
+ ProjectAccessInput removal = newProjectAccessInput();
+ removal.remove.put(REFS_HEADS, accessSectionToRemove);
+ pApi().access(removal);
+
+ // Remove locally
+ accessInput.add.get(REFS_HEADS).permissions.remove(Permission.LABEL + LabelId.CODE_REVIEW);
+
+ // Check
+ assertThat(pApi().access().local).isEqualTo(accessInput.add);
+ }
+
+ @Test
+ public void getPermissionsWithDisallowedUser() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
+
+ // Disallow READ
+ accessInput.add.put(REFS_META_VERSION, accessSectionInfo);
+ accessInput.add.put(REFS_DRAFTS, accessSectionInfo);
+ accessInput.add.put(REFS_STARRED_CHANGES, accessSectionInfo);
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi().access(accessInput);
+
+ requestScopeOperations.setApiUser(user.id());
+ assertThrows(ResourceNotFoundException.class, () -> pApi().access());
+ }
+
+ @Test
+ public void setPermissionsWithDisallowedUser() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
+
+ // Disallow READ
+ accessInput.add.put(REFS_META_VERSION, accessSectionInfo);
+ accessInput.add.put(REFS_DRAFTS, accessSectionInfo);
+ accessInput.add.put(REFS_STARRED_CHANGES, accessSectionInfo);
+ accessInput.add.put(REFS_HEADS, accessSectionInfo);
+ pApi().access(accessInput);
+
+ // Create a change to apply
+ ProjectAccessInput accessInfoToApply = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfoToApply = createDefaultAccessSectionInfo();
+ accessInfoToApply.add.put(REFS_HEADS, accessSectionInfoToApply);
+
+ requestScopeOperations.setApiUser(user.id());
+ assertThrows(ResourceNotFoundException.class, () -> pApi().access());
+ }
+
+ @Test
+ public void permissionsGroupMap() throws Exception {
+ // Add initial permission set
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+
+ PermissionInfo push = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ push.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
+ accessSection.permissions.put(Permission.PUSH, push);
+
+ PermissionInfo read = newPermissionInfo();
+ pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ read.rules.put(SystemGroupBackend.ANONYMOUS_USERS.get(), pri);
+ accessSection.permissions.put(Permission.READ, read);
+
+ accessInput.add.put(REFS_ALL, accessSection);
+ ProjectAccessInfo result = pApi().access(accessInput);
+ assertThatMap(result.groups)
+ .keys()
+ .containsExactly(
+ SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
+
+ // Check the name, which is what the UI cares about; exhaustive
+ // coverage of GroupInfo should be in groups REST API tests.
+ assertThat(result.groups.get(SystemGroupBackend.PROJECT_OWNERS.get()).name)
+ .isEqualTo("Project Owners");
+ // Strip the ID, since it is in the key.
+ assertThat(result.groups.get(SystemGroupBackend.PROJECT_OWNERS.get()).id).isNull();
+
+ // Get call returns groups too.
+ ProjectAccessInfo loggedInResult = pApi().access();
+ assertThatMap(loggedInResult.groups)
+ .keys()
+ .containsExactly(
+ SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
+
+ GroupInfo owners = loggedInResult.groups.get(SystemGroupBackend.PROJECT_OWNERS.get());
+ assertThat(owners.name).isEqualTo("Project Owners");
+ assertThat(owners.id).isNull();
+ assertThat(owners.members).isNull();
+ assertThat(owners.includes).isNull();
+
+ // PROJECT_OWNERS is invisible to anonymous user, but GetAccess disregards visibility.
+ requestScopeOperations.setApiUserAnonymous();
+ ProjectAccessInfo anonResult = pApi().access();
+ assertThatMap(anonResult.groups)
+ .keys()
+ .containsExactly(
+ SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
+ }
+
+ @Test
+ public void updateParentAsUser() throws Exception {
+ // Create child
+ String newParentProjectName = projectOperations.newProject().create().get();
+
+ // Set new parent
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ accessInput.parent = newParentProjectName;
+
+ requestScopeOperations.setApiUser(user.id());
+ AuthException thrown = assertThrows(AuthException.class, () -> pApi().access(accessInput));
+ assertThat(thrown).hasMessageThat().contains("administrate server not permitted");
+ }
+
+ @Test
+ public void updateParentAsAdministrator() throws Exception {
+ // Create parent
+ String newParentProjectName = projectOperations.newProject().create().get();
+
+ // Set new parent
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ accessInput.parent = newParentProjectName;
+
+ pApi().access(accessInput);
+
+ assertThat(pApi().access().inheritsFrom.name).isEqualTo(newParentProjectName);
+ }
+
+ @Test
+ public void addGlobalCapabilityAsUser() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+
+ requestScopeOperations.setApiUser(user.id());
+ assertThrows(
+ AuthException.class, () -> gApi.projects().name(allProjects.get()).access(accessInput));
+ }
+
+ @Test
+ public void addGlobalCapabilityAsAdmin() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+
+ ProjectAccessInfo updatedAccessSectionInfo =
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThatMap(updatedAccessSectionInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
+ .keys()
+ .containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
+ }
+
+ @Test
+ public void addPluginGlobalCapability() throws Exception {
+ try (Registration registration =
+ extensionRegistry
+ .newRegistration()
+ .add(
+ new CapabilityDefinition() {
+ @Override
+ public String getDescription() {
+ return "A Plugin Global Capability";
+ }
+ },
+ "fooCapability")) {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+ PermissionInfo foo = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ foo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSectionInfo.permissions.put(ExtensionRegistry.PLUGIN_NAME + "-fooCapability", foo);
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+
+ ProjectAccessInfo updatedAccessSectionInfo =
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThatMap(
+ updatedAccessSectionInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
+ .keys()
+ .containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
+ }
+ }
+
+ @Test
+ public void addPermissionAsGlobalCapability() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+ PermissionInfo push = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSectionInfo.permissions.put(Permission.PUSH, push);
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).access(accessInput));
+ assertThat(ex).hasMessageThat().isEqualTo("Unknown global capability: " + Permission.PUSH);
+ }
+
+ @Test
+ public void addInvalidGlobalCapability() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ PermissionInfo permissionInfo = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ permissionInfo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSectionInfo.permissions.put("Invalid Global Capability", permissionInfo);
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+ BadRequestException ex =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).access(accessInput));
+ assertThat(ex)
+ .hasMessageThat()
+ .isEqualTo("Unknown global capability: Invalid Global Capability");
+ }
+
+ @Test
+ public void addGlobalCapabilityForNonRootProject() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+
+ assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
+ }
+
+ @Test
+ public void addNonGlobalCapabilityToGlobalCapabilities() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+ PermissionInfo permissionInfo = newPermissionInfo();
+ permissionInfo.rules.put(adminGroupUuid().get(), null);
+ accessSectionInfo.permissions.put(Permission.PUSH, permissionInfo);
+
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).access(accessInput));
+ }
+
+ @Test
+ public void removeGlobalCapabilityAsUser() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+ accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+
+ requestScopeOperations.setApiUser(user.id());
+ assertThrows(
+ AuthException.class, () -> gApi.projects().name(allProjects.get()).access(accessInput));
+ }
+
+ @Test
+ public void removeGlobalCapabilityAsAdmin() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+ PermissionInfo permissionInfo = newPermissionInfo();
+ permissionInfo.rules.put(adminGroupUuid().get(), null);
+ accessSectionInfo.permissions.put(GlobalCapability.ACCESS_DATABASE, permissionInfo);
+
+ // Add and validate first as removing existing privileges such as
+ // administrateServer would break upcoming tests
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+
+ ProjectAccessInfo updatedProjectAccessInfo =
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThatMap(updatedProjectAccessInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
+ .keys()
+ .containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
+
+ // Remove
+ accessInput.add.clear();
+ accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+
+ updatedProjectAccessInfo = gApi.projects().name(allProjects.get()).access(accessInput);
+ assertThatMap(updatedProjectAccessInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
+ .keys()
+ .containsNoneIn(accessSectionInfo.permissions.keySet());
+ }
+
+ @Test
+ public void unknownPermissionRemainsUnchanged() throws Exception {
+ String access = "access";
+ String unknownPermission = "unknownPermission";
+ String registeredUsers = "group Registered Users";
+ String refsFor = "refs/for/*";
+ // Clone repository to forcefully add permission
+ TestRepository<InMemoryRepository> allProjectsRepo = cloneProject(allProjects, admin);
+
+ // Fetch permission ref
+ GitUtil.fetch(allProjectsRepo, "refs/meta/config:cfg");
+ allProjectsRepo.reset("cfg");
+
+ // Load current permissions
+ String config =
+ gApi.projects()
+ .name(allProjects.get())
+ .branch(RefNames.REFS_CONFIG)
+ .file(ProjectConfig.PROJECT_CONFIG)
+ .asString();
+
+ // Append and push unknown permission
+ Config cfg = new Config();
+ cfg.fromText(config);
+ cfg.setString(access, refsFor, unknownPermission, registeredUsers);
+ config = cfg.toText();
+ PushOneCommit push =
+ pushFactory.create(
+ admin.newIdent(), allProjectsRepo, "Subject", ProjectConfig.PROJECT_CONFIG, config);
+ push.to(RefNames.REFS_CONFIG).assertOkStatus();
+
+ // Verify that unknownPermission is present
+ config =
+ gApi.projects()
+ .name(allProjects.get())
+ .branch(RefNames.REFS_CONFIG)
+ .file(ProjectConfig.PROJECT_CONFIG)
+ .asString();
+ cfg.fromText(config);
+ assertThat(cfg).stringValue(access, refsFor, unknownPermission).isEqualTo(registeredUsers);
+
+ // Make permission change through API
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+ accessInput.add.put(refsFor, accessSectionInfo);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+ accessInput.add.clear();
+ accessInput.remove.put(refsFor, accessSectionInfo);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+
+ // Verify that unknownPermission is still present
+ config =
+ gApi.projects()
+ .name(allProjects.get())
+ .branch(RefNames.REFS_CONFIG)
+ .file(ProjectConfig.PROJECT_CONFIG)
+ .asString();
+ cfg.fromText(config);
+ assertThat(cfg).stringValue(access, refsFor, unknownPermission).isEqualTo(registeredUsers);
+ }
+
+ @Test
+ public void allUsersCanOnlyInheritFromAllProjects() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ accessInput.parent = project.get();
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allUsers.get()).access(accessInput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains(allUsers.get() + " must inherit from " + allProjects.get());
+ }
+
+ @Test
+ public void syncCreateGroupPermission_addAndRemoveCreateGroupCapability() throws Exception {
+ // Grant CREATE_GROUP to Registered Users
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+ PermissionInfo createGroup = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ createGroup.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+
+ // Assert that the permission was synced from All-Projects (global) to All-Users (ref)
+ Map<String, AccessSectionInfo> local = gApi.projects().name("All-Users").access().local;
+ assertThatMap(local).keys().contains(RefNames.REFS_GROUPS + "*");
+ Map<String, PermissionInfo> permissions = local.get(RefNames.REFS_GROUPS + "*").permissions;
+ // READ is the default permission and should be preserved by the syncer
+ assertThatMap(permissions).keys().containsExactly(Permission.READ, Permission.CREATE);
+ Map<String, PermissionRuleInfo> rules = permissions.get(Permission.CREATE).rules;
+ assertThatMap(rules).values().containsExactly(pri);
+
+ // Revoke the permission
+ accessInput.add.clear();
+ accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+
+ // Assert that the permission was synced from All-Projects (global) to All-Users (ref)
+ Map<String, AccessSectionInfo> local2 = gApi.projects().name("All-Users").access().local;
+ assertThatMap(local2).keys().contains(RefNames.REFS_GROUPS + "*");
+ Map<String, PermissionInfo> permissions2 = local2.get(RefNames.REFS_GROUPS + "*").permissions;
+ // READ is the default permission and should be preserved by the syncer
+ assertThatMap(permissions2).keys().containsExactly(Permission.READ);
+ }
+
+ @Test
+ public void syncCreateGroupPermission_addCreateGroupCapabilityToMultipleGroups()
+ throws Exception {
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+
+ // Grant CREATE_GROUP to Registered Users
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+ PermissionInfo createGroup = newPermissionInfo();
+ createGroup.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+
+ // Grant CREATE_GROUP to Administrators
+ accessInput = newProjectAccessInput();
+ accessSection = newAccessSectionInfo();
+ createGroup = newPermissionInfo();
+ createGroup.rules.put(adminGroupUuid().get(), pri);
+ accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
+ accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
+ gApi.projects().name(allProjects.get()).access(accessInput);
+
+ // Assert that the permissions were synced from All-Projects (global) to All-Users (ref)
+ Map<String, AccessSectionInfo> local = gApi.projects().name("All-Users").access().local;
+ assertThatMap(local).keys().contains(RefNames.REFS_GROUPS + "*");
+ Map<String, PermissionInfo> permissions = local.get(RefNames.REFS_GROUPS + "*").permissions;
+ // READ is the default permission and should be preserved by the syncer
+ assertThatMap(permissions).keys().containsExactly(Permission.READ, Permission.CREATE);
+ Map<String, PermissionRuleInfo> rules = permissions.get(Permission.CREATE).rules;
+ assertThatMap(rules)
+ .keys()
+ .containsExactly(SystemGroupBackend.REGISTERED_USERS.get(), adminGroupUuid().get());
+ assertThat(rules.get(SystemGroupBackend.REGISTERED_USERS.get())).isEqualTo(pri);
+ assertThat(rules.get(adminGroupUuid().get())).isEqualTo(pri);
+ }
+
+ @Test
+ public void addAccessSectionForInvalidRef() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ // 'refs/heads/stable_*' is invalid, correct would be '^refs/heads/stable_.*'
+ String invalidRef = Constants.R_HEADS + "stable_*";
+ accessInput.add.put(invalidRef, accessSectionInfo);
+
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
+ assertThat(thrown).hasMessageThat().contains("Invalid Name: " + invalidRef);
+ }
+
+ @Test
+ public void createAccessChangeWithAccessSectionForInvalidRef() throws Exception {
+ ProjectAccessInput accessInput = newProjectAccessInput();
+ AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+ // 'refs/heads/stable_*' is invalid, correct would be '^refs/heads/stable_.*'
+ String invalidRef = Constants.R_HEADS + "stable_*";
+ accessInput.add.put(invalidRef, accessSectionInfo);
+
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> pApi().accessChange(accessInput));
+ assertThat(thrown).hasMessageThat().contains("Invalid Name: " + invalidRef);
+ }
+
+ private ProjectApi pApi() throws Exception {
+ return gApi.projects().name(newProjectName.get());
+ }
+
+ private ProjectAccessInput newProjectAccessInput() {
+ ProjectAccessInput p = new ProjectAccessInput();
+ p.add = new HashMap<>();
+ p.remove = new HashMap<>();
+ return p;
+ }
+
+ private PermissionInfo newPermissionInfo() {
+ PermissionInfo p = new PermissionInfo(null, null);
+ p.rules = new HashMap<>();
+ return p;
+ }
+
+ private AccessSectionInfo newAccessSectionInfo() {
+ AccessSectionInfo a = new AccessSectionInfo();
+ a.permissions = new HashMap<>();
+ return a;
+ }
+
+ private AccessSectionInfo createDefaultAccessSectionInfo() {
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+
+ PermissionInfo push = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSection.permissions.put(Permission.PUSH, push);
+
+ PermissionInfo codeReview = newPermissionInfo();
+ codeReview.label = LabelId.CODE_REVIEW;
+ pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
+ codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+
+ pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ pri.max = 1;
+ pri.min = -1;
+ codeReview.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
+ accessSection.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
+
+ return accessSection;
+ }
+
+ private AccessSectionInfo createDefaultGlobalCapabilitiesAccessSectionInfo() {
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+
+ PermissionInfo email = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+ email.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+ accessSection.permissions.put(GlobalCapability.EMAIL_REVIEWERS, email);
+
+ return accessSection;
+ }
+
+ private AccessSectionInfo createAccessSectionInfoDenyAll() {
+ AccessSectionInfo accessSection = newAccessSectionInfo();
+
+ PermissionInfo read = newPermissionInfo();
+ PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
+ read.rules.put(SystemGroupBackend.ANONYMOUS_USERS.get(), pri);
+ accessSection.permissions.put(Permission.READ, read);
+
+ return accessSection;
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
index 385780b..9d1bdaa 100644
--- a/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/PushPermissionsIT.java
@@ -103,7 +103,7 @@
assertThat(r)
.hasMessages(
"error: branch refs/heads/master:",
- "To push into this reference you need 'Push' rights.",
+ "Push to refs/for/master to create a review, or get 'Push' rights to update the branch.",
"User: admin",
"Contact an administrator to fix the permissions");
assertThat(r).hasProcessed(ImmutableMap.of("refs", 1));
@@ -183,7 +183,7 @@
"You need 'Delete Reference' rights or 'Push' rights with the ",
"'Force Push' flag set to delete references.",
"error: branch refs/heads/master:",
- "To push into this reference you need 'Push' rights.",
+ "Push to refs/for/master to create a review, or get 'Push' rights to update the branch.",
"User: admin",
"Contact an administrator to fix the permissions");
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
index c08c5b6..d93d3f7 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -11,6 +11,7 @@
// 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.change;
import static com.google.common.truth.Truth.assertThat;
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
index ff4f203..b99c624 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Android Open Source Project
+// Copyright (C) 2021 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.
@@ -11,1004 +11,73 @@
// 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.common.truth.Truth8.assertThat;
-import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
-import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
-import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.schema.AclUtil.grant;
-import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import static com.google.gerrit.truth.ConfigSubject.assertThat;
-import static com.google.gerrit.truth.MapSubject.assertThatMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.ExtensionRegistry;
-import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
-import com.google.gerrit.acceptance.GitUtil;
-import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
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.AccessSection;
-import com.google.gerrit.entities.GroupReference;
-import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.Permission;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.RefNames;
-import com.google.gerrit.extensions.api.access.AccessSectionInfo;
-import com.google.gerrit.extensions.api.access.PermissionInfo;
-import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
-import com.google.gerrit.extensions.api.access.ProjectAccessInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.projects.ProjectApi;
-import com.google.gerrit.extensions.client.ChangeStatus;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.GroupInfo;
-import com.google.gerrit.extensions.common.WebLinkInfo;
-import com.google.gerrit.extensions.config.CapabilityDefinition;
-import com.google.gerrit.extensions.config.PluginProjectPermissionDefinition;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.webui.FileHistoryWebLink;
-import com.google.gerrit.server.config.AllProjectsNameProvider;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.schema.GrantRevertPermission;
+import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
-import java.util.HashMap;
import java.util.Map;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.junit.Before;
import org.junit.Test;
public class AccessIT extends AbstractDaemonTest {
-
- private static final String REFS_ALL = Constants.R_REFS + "*";
- private static final String REFS_HEADS = Constants.R_HEADS + "*";
- private static final String REFS_META_VERSION = "refs/meta/version";
- private static final String REFS_DRAFTS = "refs/draft-comments/*";
- private static final String REFS_STARRED_CHANGES = "refs/starred-changes/*";
-
@Inject private ProjectOperations projectOperations;
- @Inject private RequestScopeOperations requestScopeOperations;
- @Inject private ExtensionRegistry extensionRegistry;
- @Inject private GrantRevertPermission grantRevertPermission;
- private Project.NameKey newProjectName;
-
- @Before
- public void setUp() throws Exception {
- newProjectName = projectOperations.newProject().create();
+ @Test
+ public void listAccessWithoutSpecifyingProject() throws Exception {
+ RestResponse r = adminRestSession.get("/access/");
+ r.assertOK();
+ Map<String, ProjectAccessInfo> infoByProject =
+ newGson()
+ .fromJson(r.getReader(), new TypeToken<Map<String, ProjectAccessInfo>>() {}.getType());
+ assertThat(infoByProject).isEmpty();
}
@Test
- public void grantRevertPermission() throws Exception {
- String ref = "refs/*";
- String groupId = "global:Registered-Users";
-
- grantRevertPermission.execute(newProjectName);
-
- ProjectAccessInfo info = pApi().access();
- assertThat(info.local.containsKey(ref)).isTrue();
- AccessSectionInfo accessSectionInfo = info.local.get(ref);
- assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isTrue();
- PermissionInfo permissionInfo = accessSectionInfo.permissions.get(Permission.REVERT);
- assertThat(permissionInfo.rules.containsKey(groupId)).isTrue();
- PermissionRuleInfo permissionRuleInfo = permissionInfo.rules.get(groupId);
- assertThat(permissionRuleInfo.action).isEqualTo(PermissionRuleInfo.Action.ALLOW);
+ public void listAccessWithoutSpecifyingAnEmptyProjectName() throws Exception {
+ RestResponse r = adminRestSession.get("/access/?p=");
+ r.assertOK();
+ Map<String, ProjectAccessInfo> infoByProject =
+ newGson()
+ .fromJson(r.getReader(), new TypeToken<Map<String, ProjectAccessInfo>>() {}.getType());
+ assertThat(infoByProject).isEmpty();
}
@Test
- public void grantRevertPermissionByOnNewRefAndDeletingOnOldRef() throws Exception {
- String refsHeads = "refs/heads/*";
- String refsStar = "refs/*";
- String groupId = "global:Registered-Users";
- GroupReference registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS);
-
- try (Repository repo = repoManager.openRepository(newProjectName)) {
- MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
- ProjectConfig projectConfig = projectConfigFactory.read(md);
- projectConfig.upsertAccessSection(
- AccessSection.HEADS,
- heads -> {
- grant(projectConfig, heads, Permission.REVERT, registeredUsers);
- });
- md.getCommitBuilder().setAuthor(admin.newIdent());
- md.getCommitBuilder().setCommitter(admin.newIdent());
- md.setMessage("Add revert permission for all registered users\n");
-
- projectConfig.commit(md);
- }
- grantRevertPermission.execute(newProjectName);
-
- ProjectAccessInfo info = pApi().access();
-
- // Revert permission is removed on refs/heads/*.
- assertThat(info.local.containsKey(refsHeads)).isTrue();
- AccessSectionInfo accessSectionInfo = info.local.get(refsHeads);
- assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isFalse();
-
- // new permission is added on refs/* with Registered-Users.
- assertThat(info.local.containsKey(refsStar)).isTrue();
- accessSectionInfo = info.local.get(refsStar);
- assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isTrue();
- PermissionInfo permissionInfo = accessSectionInfo.permissions.get(Permission.REVERT);
- assertThat(permissionInfo.rules.containsKey(groupId)).isTrue();
- PermissionRuleInfo permissionRuleInfo = permissionInfo.rules.get(groupId);
- assertThat(permissionRuleInfo.action).isEqualTo(PermissionRuleInfo.Action.ALLOW);
+ public void listAccessForNonExistingProject() throws Exception {
+ RestResponse r = adminRestSession.get("/access/?project=non-existing");
+ r.assertNotFound();
+ assertThat(r.getEntityContent()).isEqualTo("non-existing");
}
@Test
- public void grantRevertPermissionDoesntDeleteAdminsPreferences() throws Exception {
- GroupReference registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS);
- GroupReference otherGroup = systemGroupBackend.getGroup(ANONYMOUS_USERS);
-
- try (Repository repo = repoManager.openRepository(newProjectName)) {
- MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
- ProjectConfig projectConfig = projectConfigFactory.read(md);
- projectConfig.upsertAccessSection(
- AccessSection.HEADS,
- heads -> {
- grant(projectConfig, heads, Permission.REVERT, registeredUsers);
- grant(projectConfig, heads, Permission.REVERT, otherGroup);
- });
- md.getCommitBuilder().setAuthor(admin.newIdent());
- md.getCommitBuilder().setCommitter(admin.newIdent());
- md.setMessage("Add revert permission for all registered users\n");
-
- projectConfig.commit(md);
- }
- projectCache.evict(newProjectName);
- ProjectAccessInfo expected = pApi().access();
-
- grantRevertPermission.execute(newProjectName);
- projectCache.evict(newProjectName);
- ProjectAccessInfo actual = pApi().access();
- // Permissions don't change
- assertThat(expected.local).isEqualTo(actual.local);
- }
-
- @Test
- public void grantRevertPermissionOnlyWorksOnce() throws Exception {
- grantRevertPermission.execute(newProjectName);
- grantRevertPermission.execute(newProjectName);
-
- try (Repository repo = repoManager.openRepository(newProjectName)) {
- MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
- ProjectConfig projectConfig = projectConfigFactory.read(md);
- AccessSection all = projectConfig.getAccessSection(AccessSection.ALL);
-
- Permission permission = all.getPermission(Permission.REVERT);
- assertThat(permission.getRules()).hasSize(1);
- }
- }
-
- @Test
- public void getDefaultInheritance() throws Exception {
- String inheritedName = pApi().access().inheritsFrom.name;
- assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
- }
-
- private Registration newFileHistoryWebLink() {
- FileHistoryWebLink weblink =
- new FileHistoryWebLink() {
- @Override
- public WebLinkInfo getFileHistoryWebLink(
- String projectName, String revision, String fileName) {
- return new WebLinkInfo(
- "name", "imageURL", "http://view/" + projectName + "/" + fileName);
- }
- };
- return extensionRegistry.newRegistration().add(weblink);
- }
-
- @Test
- public void webLink() throws Exception {
- try (Registration registration = newFileHistoryWebLink()) {
- ProjectAccessInfo info = pApi().access();
- assertThat(info.configWebLinks).hasSize(1);
- assertThat(info.configWebLinks.get(0).url)
- .isEqualTo("http://view/" + newProjectName + "/project.config");
- }
- }
-
- @Test
- public void webLinkNoRefsMetaConfig() throws Exception {
- try (Repository repo = repoManager.openRepository(newProjectName);
- Registration registration = newFileHistoryWebLink()) {
- RefUpdate u = repo.updateRef(RefNames.REFS_CONFIG);
- u.setForceUpdate(true);
- assertThat(u.delete()).isEqualTo(Result.FORCED);
-
- // This should not crash.
- pApi().access();
- }
- }
-
- @Test
- public void addAccessSection() throws Exception {
- RevCommit initialHead = projectOperations.project(newProjectName).getHead(RefNames.REFS_CONFIG);
-
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
-
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- pApi().access(accessInput);
-
- assertThat(pApi().access().local).isEqualTo(accessInput.add);
-
- RevCommit updatedHead = projectOperations.project(newProjectName).getHead(RefNames.REFS_CONFIG);
- eventRecorder.assertRefUpdatedEvents(
- newProjectName.get(), RefNames.REFS_CONFIG, null, initialHead, initialHead, updatedHead);
- }
-
- @Test
- public void addAccessSectionForPluginPermission() throws Exception {
- try (Registration registration =
- extensionRegistry
- .newRegistration()
- .add(
- new PluginProjectPermissionDefinition() {
- @Override
- public String getDescription() {
- return "A Plugin Project Permission";
- }
- },
- "fooPermission")) {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
-
- PermissionInfo foo = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- foo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSectionInfo.permissions.put(
- "plugin-" + ExtensionRegistry.PLUGIN_NAME + "-fooPermission", foo);
-
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- ProjectAccessInfo updatedAccessSectionInfo = pApi().access(accessInput);
- assertThat(updatedAccessSectionInfo.local).isEqualTo(accessInput.add);
-
- assertThat(pApi().access().local).isEqualTo(accessInput.add);
- }
- }
-
- @Test
- public void addAccessSectionWithInvalidPermission() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
-
- PermissionInfo push = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSectionInfo.permissions.put("Invalid Permission", push);
-
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- BadRequestException ex =
- assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
- assertThat(ex).hasMessageThat().isEqualTo("Unknown permission: Invalid Permission");
- }
-
- @Test
- public void addAccessSectionWithInvalidLabelPermission() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
-
- PermissionInfo push = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSectionInfo.permissions.put("label-Invalid Permission", push);
-
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- BadRequestException ex =
- assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
- assertThat(ex).hasMessageThat().isEqualTo("Unknown permission: label-Invalid Permission");
- }
-
- @Test
- public void createAccessChangeNop() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- assertThrows(BadRequestException.class, () -> pApi().accessChange(accessInput));
- }
-
- @Test
- public void createAccessChangeEmptyConfig() throws Exception {
- try (Repository repo = repoManager.openRepository(newProjectName)) {
- RefUpdate ru = repo.updateRef(RefNames.REFS_CONFIG);
- ru.setForceUpdate(true);
- assertThat(ru.delete()).isEqualTo(Result.FORCED);
-
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSection = newAccessSectionInfo();
- PermissionInfo read = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.BLOCK, false);
- read.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSection.permissions.put(Permission.READ, read);
- accessInput.add.put(REFS_HEADS, accessSection);
-
- ChangeInfo out = pApi().accessChange(accessInput);
- assertThat(out.status).isEqualTo(ChangeStatus.NEW);
- }
- }
-
- @Test
- public void createAccessChange() throws Exception {
+ public void listAccessForNonVisibleProject() throws Exception {
projectOperations
- .project(newProjectName)
+ .project(project)
.forUpdate()
- .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
.update();
- // User can see the branch
- requestScopeOperations.setApiUser(user.id());
- pApi().branch("refs/heads/master").get();
- ProjectAccessInput accessInput = newProjectAccessInput();
-
- AccessSectionInfo accessSection = newAccessSectionInfo();
-
- // Deny read to registered users.
- PermissionInfo read = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
- read.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- read.exclusive = true;
- accessSection.permissions.put(Permission.READ, read);
- accessInput.add.put(REFS_HEADS, accessSection);
-
- requestScopeOperations.setApiUser(user.id());
- ChangeInfo out = pApi().accessChange(accessInput);
-
- assertThat(out.project).isEqualTo(newProjectName.get());
- assertThat(out.branch).isEqualTo(RefNames.REFS_CONFIG);
- assertThat(out.status).isEqualTo(ChangeStatus.NEW);
- assertThat(out.submitted).isNull();
-
- requestScopeOperations.setApiUser(admin.id());
-
- ChangeInfo c = gApi.changes().id(out._number).get(MESSAGES);
- assertThat(c.messages.stream().map(m -> m.message)).containsExactly("Uploaded patch set 1");
-
- ReviewInput reviewIn = new ReviewInput();
- reviewIn.label("Code-Review", (short) 2);
- gApi.changes().id(out._number).current().review(reviewIn);
- gApi.changes().id(out._number).current().submit();
-
- // check that the change took effect.
- requestScopeOperations.setApiUser(user.id());
- assertThrows(ResourceNotFoundException.class, () -> pApi().branch("refs/heads/master").get());
-
- // Restore.
- accessInput.add.clear();
- accessInput.remove.put(REFS_HEADS, accessSection);
- requestScopeOperations.setApiUser(user.id());
-
- requestScopeOperations.setApiUser(admin.id());
- out = pApi().accessChange(accessInput);
-
- gApi.changes().id(out._number).current().review(reviewIn);
- gApi.changes().id(out._number).current().submit();
-
- // Now it works again.
- requestScopeOperations.setApiUser(user.id());
- pApi().branch("refs/heads/master").get();
+ RestResponse r = userRestSession.get("/access/?project=" + project.get());
+ r.assertNotFound();
+ assertThat(r.getEntityContent()).isEqualTo(project.get());
}
@Test
- public void removePermission() throws Exception {
- // Add initial permission set
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
-
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- pApi().access(accessInput);
-
- // Remove specific permission
- AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
- accessSectionToRemove.permissions.put(
- Permission.LABEL + LabelId.CODE_REVIEW, newPermissionInfo());
- ProjectAccessInput removal = newProjectAccessInput();
- removal.remove.put(REFS_HEADS, accessSectionToRemove);
- pApi().access(removal);
-
- // Remove locally
- accessInput.add.get(REFS_HEADS).permissions.remove(Permission.LABEL + LabelId.CODE_REVIEW);
-
- // Check
- assertThat(pApi().access().local).isEqualTo(accessInput.add);
- }
-
- @Test
- public void removePermissionRule() throws Exception {
- // Add initial permission set
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
-
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- pApi().access(accessInput);
-
- // Remove specific permission rule
- AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
- PermissionInfo codeReview = newPermissionInfo();
- codeReview.label = LabelId.CODE_REVIEW;
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
- codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSectionToRemove.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
- ProjectAccessInput removal = newProjectAccessInput();
- removal.remove.put(REFS_HEADS, accessSectionToRemove);
- pApi().access(removal);
-
- // Remove locally
- accessInput
- .add
- .get(REFS_HEADS)
- .permissions
- .get(Permission.LABEL + LabelId.CODE_REVIEW)
- .rules
- .remove(SystemGroupBackend.REGISTERED_USERS.get());
-
- // Check
- assertThat(pApi().access().local).isEqualTo(accessInput.add);
- }
-
- @Test
- public void removePermissionRulesAndCleanupEmptyEntries() throws Exception {
- // Add initial permission set
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
-
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- pApi().access(accessInput);
-
- // Remove specific permission rules
- AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
- PermissionInfo codeReview = newPermissionInfo();
- codeReview.label = LabelId.CODE_REVIEW;
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
- codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
- codeReview.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
- accessSectionToRemove.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
- ProjectAccessInput removal = newProjectAccessInput();
- removal.remove.put(REFS_HEADS, accessSectionToRemove);
- pApi().access(removal);
-
- // Remove locally
- accessInput.add.get(REFS_HEADS).permissions.remove(Permission.LABEL + LabelId.CODE_REVIEW);
-
- // Check
- assertThat(pApi().access().local).isEqualTo(accessInput.add);
- }
-
- @Test
- public void getPermissionsWithDisallowedUser() throws Exception {
- // Add initial permission set
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
-
- // Disallow READ
- accessInput.add.put(REFS_META_VERSION, accessSectionInfo);
- accessInput.add.put(REFS_DRAFTS, accessSectionInfo);
- accessInput.add.put(REFS_STARRED_CHANGES, accessSectionInfo);
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- pApi().access(accessInput);
-
- requestScopeOperations.setApiUser(user.id());
- assertThrows(ResourceNotFoundException.class, () -> pApi().access());
- }
-
- @Test
- public void setPermissionsWithDisallowedUser() throws Exception {
- // Add initial permission set
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
-
- // Disallow READ
- accessInput.add.put(REFS_META_VERSION, accessSectionInfo);
- accessInput.add.put(REFS_DRAFTS, accessSectionInfo);
- accessInput.add.put(REFS_STARRED_CHANGES, accessSectionInfo);
- accessInput.add.put(REFS_HEADS, accessSectionInfo);
- pApi().access(accessInput);
-
- // Create a change to apply
- ProjectAccessInput accessInfoToApply = newProjectAccessInput();
- AccessSectionInfo accessSectionInfoToApply = createDefaultAccessSectionInfo();
- accessInfoToApply.add.put(REFS_HEADS, accessSectionInfoToApply);
-
- requestScopeOperations.setApiUser(user.id());
- assertThrows(ResourceNotFoundException.class, () -> pApi().access());
- }
-
- @Test
- public void permissionsGroupMap() throws Exception {
- // Add initial permission set
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSection = newAccessSectionInfo();
-
- PermissionInfo push = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- push.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
- accessSection.permissions.put(Permission.PUSH, push);
-
- PermissionInfo read = newPermissionInfo();
- pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- read.rules.put(SystemGroupBackend.ANONYMOUS_USERS.get(), pri);
- accessSection.permissions.put(Permission.READ, read);
-
- accessInput.add.put(REFS_ALL, accessSection);
- ProjectAccessInfo result = pApi().access(accessInput);
- assertThatMap(result.groups)
- .keys()
- .containsExactly(
- SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
-
- // Check the name, which is what the UI cares about; exhaustive
- // coverage of GroupInfo should be in groups REST API tests.
- assertThat(result.groups.get(SystemGroupBackend.PROJECT_OWNERS.get()).name)
- .isEqualTo("Project Owners");
- // Strip the ID, since it is in the key.
- assertThat(result.groups.get(SystemGroupBackend.PROJECT_OWNERS.get()).id).isNull();
-
- // Get call returns groups too.
- ProjectAccessInfo loggedInResult = pApi().access();
- assertThatMap(loggedInResult.groups)
- .keys()
- .containsExactly(
- SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
-
- GroupInfo owners = loggedInResult.groups.get(SystemGroupBackend.PROJECT_OWNERS.get());
- assertThat(owners.name).isEqualTo("Project Owners");
- assertThat(owners.id).isNull();
- assertThat(owners.members).isNull();
- assertThat(owners.includes).isNull();
-
- // PROJECT_OWNERS is invisible to anonymous user, but GetAccess disregards visibility.
- requestScopeOperations.setApiUserAnonymous();
- ProjectAccessInfo anonResult = pApi().access();
- assertThatMap(anonResult.groups)
- .keys()
- .containsExactly(
- SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
- }
-
- @Test
- public void updateParentAsUser() throws Exception {
- // Create child
- String newParentProjectName = projectOperations.newProject().create().get();
-
- // Set new parent
- ProjectAccessInput accessInput = newProjectAccessInput();
- accessInput.parent = newParentProjectName;
-
- requestScopeOperations.setApiUser(user.id());
- AuthException thrown = assertThrows(AuthException.class, () -> pApi().access(accessInput));
- assertThat(thrown).hasMessageThat().contains("administrate server not permitted");
- }
-
- @Test
- public void updateParentAsAdministrator() throws Exception {
- // Create parent
- String newParentProjectName = projectOperations.newProject().create().get();
-
- // Set new parent
- ProjectAccessInput accessInput = newProjectAccessInput();
- accessInput.parent = newParentProjectName;
-
- pApi().access(accessInput);
-
- assertThat(pApi().access().inheritsFrom.name).isEqualTo(newParentProjectName);
- }
-
- @Test
- public void addGlobalCapabilityAsUser() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
-
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
-
- requestScopeOperations.setApiUser(user.id());
- assertThrows(
- AuthException.class, () -> gApi.projects().name(allProjects.get()).access(accessInput));
- }
-
- @Test
- public void addGlobalCapabilityAsAdmin() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
-
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
-
- ProjectAccessInfo updatedAccessSectionInfo =
- gApi.projects().name(allProjects.get()).access(accessInput);
- assertThatMap(updatedAccessSectionInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
- .keys()
- .containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
- }
-
- @Test
- public void addPluginGlobalCapability() throws Exception {
- try (Registration registration =
- extensionRegistry
- .newRegistration()
- .add(
- new CapabilityDefinition() {
- @Override
- public String getDescription() {
- return "A Plugin Global Capability";
- }
- },
- "fooCapability")) {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
-
- PermissionInfo foo = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- foo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSectionInfo.permissions.put(ExtensionRegistry.PLUGIN_NAME + "-fooCapability", foo);
-
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
-
- ProjectAccessInfo updatedAccessSectionInfo =
- gApi.projects().name(allProjects.get()).access(accessInput);
- assertThatMap(
- updatedAccessSectionInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
- .keys()
- .containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
- }
- }
-
- @Test
- public void addPermissionAsGlobalCapability() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
-
- PermissionInfo push = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSectionInfo.permissions.put(Permission.PUSH, push);
-
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
- BadRequestException ex =
- assertThrows(
- BadRequestException.class,
- () -> gApi.projects().name(allProjects.get()).access(accessInput));
- assertThat(ex).hasMessageThat().isEqualTo("Unknown global capability: " + Permission.PUSH);
- }
-
- @Test
- public void addInvalidGlobalCapability() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
-
- PermissionInfo permissionInfo = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- permissionInfo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSectionInfo.permissions.put("Invalid Global Capability", permissionInfo);
-
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
- BadRequestException ex =
- assertThrows(
- BadRequestException.class,
- () -> gApi.projects().name(allProjects.get()).access(accessInput));
- assertThat(ex)
- .hasMessageThat()
- .isEqualTo("Unknown global capability: Invalid Global Capability");
- }
-
- @Test
- public void addGlobalCapabilityForNonRootProject() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
-
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
-
- assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
- }
-
- @Test
- public void addNonGlobalCapabilityToGlobalCapabilities() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
-
- PermissionInfo permissionInfo = newPermissionInfo();
- permissionInfo.rules.put(adminGroupUuid().get(), null);
- accessSectionInfo.permissions.put(Permission.PUSH, permissionInfo);
-
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
- assertThrows(
- BadRequestException.class,
- () -> gApi.projects().name(allProjects.get()).access(accessInput));
- }
-
- @Test
- public void removeGlobalCapabilityAsUser() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
-
- accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
-
- requestScopeOperations.setApiUser(user.id());
- assertThrows(
- AuthException.class, () -> gApi.projects().name(allProjects.get()).access(accessInput));
- }
-
- @Test
- public void removeGlobalCapabilityAsAdmin() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
-
- PermissionInfo permissionInfo = newPermissionInfo();
- permissionInfo.rules.put(adminGroupUuid().get(), null);
- accessSectionInfo.permissions.put(GlobalCapability.ACCESS_DATABASE, permissionInfo);
-
- // Add and validate first as removing existing privileges such as
- // administrateServer would break upcoming tests
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
-
- ProjectAccessInfo updatedProjectAccessInfo =
- gApi.projects().name(allProjects.get()).access(accessInput);
- assertThatMap(updatedProjectAccessInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
- .keys()
- .containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
-
- // Remove
- accessInput.add.clear();
- accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
-
- updatedProjectAccessInfo = gApi.projects().name(allProjects.get()).access(accessInput);
- assertThatMap(updatedProjectAccessInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
- .keys()
- .containsNoneIn(accessSectionInfo.permissions.keySet());
- }
-
- @Test
- public void unknownPermissionRemainsUnchanged() throws Exception {
- String access = "access";
- String unknownPermission = "unknownPermission";
- String registeredUsers = "group Registered Users";
- String refsFor = "refs/for/*";
- // Clone repository to forcefully add permission
- TestRepository<InMemoryRepository> allProjectsRepo = cloneProject(allProjects, admin);
-
- // Fetch permission ref
- GitUtil.fetch(allProjectsRepo, "refs/meta/config:cfg");
- allProjectsRepo.reset("cfg");
-
- // Load current permissions
- String config =
- gApi.projects()
- .name(allProjects.get())
- .branch(RefNames.REFS_CONFIG)
- .file(ProjectConfig.PROJECT_CONFIG)
- .asString();
-
- // Append and push unknown permission
- Config cfg = new Config();
- cfg.fromText(config);
- cfg.setString(access, refsFor, unknownPermission, registeredUsers);
- config = cfg.toText();
- PushOneCommit push =
- pushFactory.create(
- admin.newIdent(), allProjectsRepo, "Subject", ProjectConfig.PROJECT_CONFIG, config);
- push.to(RefNames.REFS_CONFIG).assertOkStatus();
-
- // Verify that unknownPermission is present
- config =
- gApi.projects()
- .name(allProjects.get())
- .branch(RefNames.REFS_CONFIG)
- .file(ProjectConfig.PROJECT_CONFIG)
- .asString();
- cfg.fromText(config);
- assertThat(cfg).stringValue(access, refsFor, unknownPermission).isEqualTo(registeredUsers);
-
- // Make permission change through API
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
- accessInput.add.put(refsFor, accessSectionInfo);
- gApi.projects().name(allProjects.get()).access(accessInput);
- accessInput.add.clear();
- accessInput.remove.put(refsFor, accessSectionInfo);
- gApi.projects().name(allProjects.get()).access(accessInput);
-
- // Verify that unknownPermission is still present
- config =
- gApi.projects()
- .name(allProjects.get())
- .branch(RefNames.REFS_CONFIG)
- .file(ProjectConfig.PROJECT_CONFIG)
- .asString();
- cfg.fromText(config);
- assertThat(cfg).stringValue(access, refsFor, unknownPermission).isEqualTo(registeredUsers);
- }
-
- @Test
- public void allUsersCanOnlyInheritFromAllProjects() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- accessInput.parent = project.get();
- BadRequestException thrown =
- assertThrows(
- BadRequestException.class,
- () -> gApi.projects().name(allUsers.get()).access(accessInput));
- assertThat(thrown)
- .hasMessageThat()
- .contains(allUsers.get() + " must inherit from " + allProjects.get());
- }
-
- @Test
- public void syncCreateGroupPermission_addAndRemoveCreateGroupCapability() throws Exception {
- // Grant CREATE_GROUP to Registered Users
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSection = newAccessSectionInfo();
- PermissionInfo createGroup = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- createGroup.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
- gApi.projects().name(allProjects.get()).access(accessInput);
-
- // Assert that the permission was synced from All-Projects (global) to All-Users (ref)
- Map<String, AccessSectionInfo> local = gApi.projects().name("All-Users").access().local;
- assertThatMap(local).keys().contains(RefNames.REFS_GROUPS + "*");
- Map<String, PermissionInfo> permissions = local.get(RefNames.REFS_GROUPS + "*").permissions;
- // READ is the default permission and should be preserved by the syncer
- assertThatMap(permissions).keys().containsExactly(Permission.READ, Permission.CREATE);
- Map<String, PermissionRuleInfo> rules = permissions.get(Permission.CREATE).rules;
- assertThatMap(rules).values().containsExactly(pri);
-
- // Revoke the permission
- accessInput.add.clear();
- accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
- gApi.projects().name(allProjects.get()).access(accessInput);
-
- // Assert that the permission was synced from All-Projects (global) to All-Users (ref)
- Map<String, AccessSectionInfo> local2 = gApi.projects().name("All-Users").access().local;
- assertThatMap(local2).keys().contains(RefNames.REFS_GROUPS + "*");
- Map<String, PermissionInfo> permissions2 = local2.get(RefNames.REFS_GROUPS + "*").permissions;
- // READ is the default permission and should be preserved by the syncer
- assertThatMap(permissions2).keys().containsExactly(Permission.READ);
- }
-
- @Test
- public void syncCreateGroupPermission_addCreateGroupCapabilityToMultipleGroups()
- throws Exception {
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
-
- // Grant CREATE_GROUP to Registered Users
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSection = newAccessSectionInfo();
- PermissionInfo createGroup = newPermissionInfo();
- createGroup.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
- gApi.projects().name(allProjects.get()).access(accessInput);
-
- // Grant CREATE_GROUP to Administrators
- accessInput = newProjectAccessInput();
- accessSection = newAccessSectionInfo();
- createGroup = newPermissionInfo();
- createGroup.rules.put(adminGroupUuid().get(), pri);
- accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
- accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
- gApi.projects().name(allProjects.get()).access(accessInput);
-
- // Assert that the permissions were synced from All-Projects (global) to All-Users (ref)
- Map<String, AccessSectionInfo> local = gApi.projects().name("All-Users").access().local;
- assertThatMap(local).keys().contains(RefNames.REFS_GROUPS + "*");
- Map<String, PermissionInfo> permissions = local.get(RefNames.REFS_GROUPS + "*").permissions;
- // READ is the default permission and should be preserved by the syncer
- assertThatMap(permissions).keys().containsExactly(Permission.READ, Permission.CREATE);
- Map<String, PermissionRuleInfo> rules = permissions.get(Permission.CREATE).rules;
- assertThatMap(rules)
- .keys()
- .containsExactly(SystemGroupBackend.REGISTERED_USERS.get(), adminGroupUuid().get());
- assertThat(rules.get(SystemGroupBackend.REGISTERED_USERS.get())).isEqualTo(pri);
- assertThat(rules.get(adminGroupUuid().get())).isEqualTo(pri);
- }
-
- @Test
- public void addAccessSectionForInvalidRef() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
-
- // 'refs/heads/stable_*' is invalid, correct would be '^refs/heads/stable_.*'
- String invalidRef = Constants.R_HEADS + "stable_*";
- accessInput.add.put(invalidRef, accessSectionInfo);
-
- BadRequestException thrown =
- assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
- assertThat(thrown).hasMessageThat().contains("Invalid Name: " + invalidRef);
- }
-
- @Test
- public void createAccessChangeWithAccessSectionForInvalidRef() throws Exception {
- ProjectAccessInput accessInput = newProjectAccessInput();
- AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
-
- // 'refs/heads/stable_*' is invalid, correct would be '^refs/heads/stable_.*'
- String invalidRef = Constants.R_HEADS + "stable_*";
- accessInput.add.put(invalidRef, accessSectionInfo);
-
- BadRequestException thrown =
- assertThrows(BadRequestException.class, () -> pApi().accessChange(accessInput));
- assertThat(thrown).hasMessageThat().contains("Invalid Name: " + invalidRef);
- }
-
- private ProjectApi pApi() throws Exception {
- return gApi.projects().name(newProjectName.get());
- }
-
- private ProjectAccessInput newProjectAccessInput() {
- ProjectAccessInput p = new ProjectAccessInput();
- p.add = new HashMap<>();
- p.remove = new HashMap<>();
- return p;
- }
-
- private PermissionInfo newPermissionInfo() {
- PermissionInfo p = new PermissionInfo(null, null);
- p.rules = new HashMap<>();
- return p;
- }
-
- private AccessSectionInfo newAccessSectionInfo() {
- AccessSectionInfo a = new AccessSectionInfo();
- a.permissions = new HashMap<>();
- return a;
- }
-
- private AccessSectionInfo createDefaultAccessSectionInfo() {
- AccessSectionInfo accessSection = newAccessSectionInfo();
-
- PermissionInfo push = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSection.permissions.put(Permission.PUSH, push);
-
- PermissionInfo codeReview = newPermissionInfo();
- codeReview.label = LabelId.CODE_REVIEW;
- pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
- codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
-
- pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- pri.max = 1;
- pri.min = -1;
- codeReview.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
- accessSection.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
-
- return accessSection;
- }
-
- private AccessSectionInfo createDefaultGlobalCapabilitiesAccessSectionInfo() {
- AccessSectionInfo accessSection = newAccessSectionInfo();
-
- PermissionInfo email = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
- email.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
- accessSection.permissions.put(GlobalCapability.EMAIL_REVIEWERS, email);
-
- return accessSection;
- }
-
- private AccessSectionInfo createAccessSectionInfoDenyAll() {
- AccessSectionInfo accessSection = newAccessSectionInfo();
-
- PermissionInfo read = newPermissionInfo();
- PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
- read.rules.put(SystemGroupBackend.ANONYMOUS_USERS.get(), pri);
- accessSection.permissions.put(Permission.READ, read);
-
- return accessSection;
+ public void listAccess() throws Exception {
+ RestResponse r = adminRestSession.get("/access/?project=" + project.get());
+ r.assertOK();
+ Map<String, ProjectAccessInfo> infoByProject =
+ newGson()
+ .fromJson(r.getReader(), new TypeToken<Map<String, ProjectAccessInfo>>() {}.getType());
+ assertThat(infoByProject.keySet()).containsExactly(project.get());
}
}
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
index 5679c41..85c0212 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
@@ -16,6 +16,8 @@
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -28,7 +30,9 @@
import com.google.common.collect.Streams;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.entities.EmailHeader;
+import com.google.gerrit.entities.Permission;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
@@ -44,6 +48,7 @@
import com.google.gerrit.mail.MailMessage;
import com.google.gerrit.mail.MailProcessingUtil;
import com.google.gerrit.server.mail.receive.MailProcessor;
+import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.gerrit.testing.TestCommentHelper;
import com.google.inject.Inject;
@@ -61,6 +66,7 @@
@Inject private MailProcessor mailProcessor;
@Inject private AccountOperations accountOperations;
@Inject private TestCommentHelper testCommentHelper;
+ @Inject private ProjectOperations projectOperations;
private static final CommentValidator mockCommentValidator = mock(CommentValidator.class);
@@ -276,6 +282,133 @@
}
@Test
+ public void sendNotificationOnProjectNotFound() throws Exception {
+ String ts =
+ MailProcessingUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(TimeUtil.now(), ZoneId.of("UTC")));
+
+ String changeUrl = canonicalWebUrl.get() + "c/non-existing-project/+/123";
+
+ // Build Message
+ String txt = newPlaintextBody(changeUrl + "/1", "Test Message", null, null);
+ MailMessage.Builder b =
+ messageBuilderWithDefaultFields()
+ .from(user.getNameEmail())
+ .textContent(txt + textFooterForChange(123, ts));
+
+ sender.clear();
+ mailProcessor.process(b.build());
+
+ assertNotifyTo(user);
+ Message message = sender.nextMessage();
+ assertThat(message.body())
+ .contains(
+ "Gerrit Code Review was unable to process your email because the change was not"
+ + " found.");
+ assertThat(message.headers()).containsKey("Subject");
+ }
+
+ @Test
+ public void sendNotificationOnProjectNotVisible() throws Exception {
+ String changeId = createChangeWithReview();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+
+ String ts =
+ MailProcessingUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(
+ gApi.changes().id(changeId).get().updated.toInstant(), ZoneId.of("UTC")));
+
+ // Block read permissions on the project.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+
+ // Build Message
+ String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", "Test Message", null, null);
+ MailMessage.Builder b =
+ messageBuilderWithDefaultFields()
+ .from(user.getNameEmail())
+ .textContent(txt + textFooterForChange(changeInfo._number, ts));
+
+ sender.clear();
+ mailProcessor.process(b.build());
+
+ assertNotifyTo(user);
+ Message message = sender.nextMessage();
+ assertThat(message.body())
+ .contains(
+ "Gerrit Code Review was unable to process your email because the change was not"
+ + " found.");
+ assertThat(message.headers()).containsKey("Subject");
+ }
+
+ @Test
+ public void sendNotificationOnChangeNotFound() throws Exception {
+ String changeId = createChangeWithReview();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+
+ String ts =
+ MailProcessingUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(
+ gApi.changes().id(changeId).get().updated.toInstant(), ZoneId.of("UTC")));
+
+ // Delete the change so that it's not found.
+ gApi.changes().id(changeId).delete();
+
+ // Build Message
+ String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", "Test Message", null, null);
+ MailMessage.Builder b =
+ messageBuilderWithDefaultFields()
+ .from(user.getNameEmail())
+ .textContent(txt + textFooterForChange(changeInfo._number, ts));
+
+ sender.clear();
+ mailProcessor.process(b.build());
+
+ assertNotifyTo(user);
+ Message message = sender.nextMessage();
+ assertThat(message.body())
+ .contains(
+ "Gerrit Code Review was unable to process your email because the change was not"
+ + " found.");
+ assertThat(message.headers()).containsKey("Subject");
+ }
+
+ @Test
+ public void sendNotificationOnChangeNotVisible() throws Exception {
+ String changeId = createChangeWithReview();
+ ChangeInfo changeInfo = gApi.changes().id(changeId).get();
+
+ String ts =
+ MailProcessingUtil.rfcDateformatter.format(
+ ZonedDateTime.ofInstant(
+ gApi.changes().id(changeId).get().updated.toInstant(), ZoneId.of("UTC")));
+
+ // Make change private so that it's no visible to user.
+ gApi.changes().id(changeId).setPrivate(true);
+
+ // Build Message
+ String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", "Test Message", null, null);
+ MailMessage.Builder b =
+ messageBuilderWithDefaultFields()
+ .from(user.getNameEmail())
+ .textContent(txt + textFooterForChange(changeInfo._number, ts));
+
+ sender.clear();
+ mailProcessor.process(b.build());
+
+ assertNotifyTo(user);
+ Message message = sender.nextMessage();
+ assertThat(message.body())
+ .contains(
+ "Gerrit Code Review was unable to process your email because the change was not"
+ + " found.");
+ assertThat(message.headers()).containsKey("Subject");
+ }
+
+ @Test
public void validateChangeMessage_rejected() throws Exception {
String changeId = createChangeWithReview();
ChangeInfo changeInfo = gApi.changes().id(changeId).get();
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index 6e142d4..f6b3aa0 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -303,12 +303,8 @@
}
export declare type ImageDiffAction =
- | {
- type: 'overview-image-clicked';
- }
- | {
- type: 'overview-frame-dragged';
- }
+ | {type: 'overview-image-clicked'}
+ | {type: 'overview-frame-dragged'}
| {type: 'magnifier-clicked'}
| {type: 'magnifier-dragged'}
| {type: 'version-switcher-clicked'; button: 'base' | 'revision' | 'switch'}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts
index 17647ad..3ec4f2c 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_html.ts
@@ -32,10 +32,10 @@
}
.revertSubmissionLayout {
display: flex;
+ align-items: center;
}
.label {
margin-left: var(--spacing-m);
- margin-bottom: var(--spacing-m);
}
iron-autogrow-textarea {
font-family: var(--monospace-font-family);
@@ -47,6 +47,9 @@
color: var(--error-text-color);
margin-bottom: var(--spacing-m);
}
+ label[for='messageInput'] {
+ margin-top: var(--spacing-m);
+ }
</style>
<gr-dialog
confirm-label="Revert"
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index 26af2a2..1711499 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -184,7 +184,7 @@
type: String,
computed:
'_computeMessageContentExpanded(message.message,' +
- ' message.accountsInMessage,' +
+ ' message.accounts_in_message,' +
' message.tag)',
})
_messageContentExpanded = '';
@@ -193,7 +193,7 @@
type: String,
computed:
'_computeMessageContentCollapsed(message.message,' +
- ' message.accountsInMessage,' +
+ ' message.accounts_in_message,' +
' message.tag,' +
' message.commentThreads)',
})
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
index 8d5bc33..5595d15 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
@@ -21,13 +21,7 @@
import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
import {classMap} from 'lit-html/directives/class-map';
import {GrLitElement} from '../../lit/gr-lit-element';
-import {
- customElement,
- property,
- css,
- internalProperty,
- TemplateResult,
-} from 'lit-element';
+import {customElement, property, css, state, TemplateResult} from 'lit-element';
import {sharedStyles} from '../../../styles/shared-styles';
import {
SubmittedTogetherInfo,
@@ -76,22 +70,22 @@
@property()
mergeable?: boolean;
- @internalProperty()
+ @state()
submittedTogether?: SubmittedTogetherInfo = {
changes: [],
non_visible_changes: 0,
};
- @internalProperty()
+ @state()
relatedChanges: RelatedChangeAndCommitInfo[] = [];
- @internalProperty()
+ @state()
conflictingChanges: ChangeInfo[] = [];
- @internalProperty()
+ @state()
cherryPickChanges: ChangeInfo[] = [];
- @internalProperty()
+ @state()
sameTopicChanges: ChangeInfo[] = [];
private readonly restApiService = appContext.restApiService;
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 1c60161..c74f6f9 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -20,10 +20,10 @@
import {
css,
customElement,
- internalProperty,
property,
PropertyValues,
query,
+ state,
TemplateResult,
} from 'lit-element';
import {GrLitElement} from '../lit/gr-lit-element';
@@ -471,7 +471,7 @@
@query('#filterInput')
filterInput?: HTMLInputElement;
- @internalProperty()
+ @state()
filterRegExp = new RegExp('');
/** All runs. Shown should only the selected/filtered ones. */
@@ -511,7 +511,7 @@
>();
/** Maintains the state of which result sections should show all results. */
- @internalProperty()
+ @state()
isShowAll: Map<Category, boolean> = new Map();
/**
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index de9c5fb..8ff42ee 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -20,10 +20,10 @@
import {
css,
customElement,
- internalProperty,
property,
PropertyValues,
query,
+ state,
} from 'lit-element';
import {GrLitElement} from '../lit/gr-lit-element';
import {Action, Link, RunStatus} from '../../api/checks';
@@ -326,7 +326,7 @@
@query('#filterInput')
filterInput?: HTMLInputElement;
- @internalProperty()
+ @state()
filterRegExp = new RegExp('');
@property()
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index 418598b..b28596a 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -15,13 +15,7 @@
* limitations under the License.
*/
import {html} from 'lit-html';
-import {
- css,
- customElement,
- internalProperty,
- property,
- PropertyValues,
-} from 'lit-element';
+import {css, customElement, property, PropertyValues, state} from 'lit-element';
import {GrLitElement} from '../lit/gr-lit-element';
import {Action} from '../../api/checks';
import {
@@ -64,11 +58,11 @@
@property()
changeNum: NumericChangeId | undefined = undefined;
- @internalProperty()
+ @state()
selectedRuns: string[] = [];
/** Maps checkName to selected attempt number. `undefined` means `latest`. */
- @internalProperty()
+ @state()
selectedAttempts: Map<string, number | undefined> = new Map<
string,
number | undefined
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
index 2a4b250..9a98a21 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -29,11 +29,11 @@
css,
customElement,
html,
- internalProperty,
LitElement,
property,
PropertyValues,
query,
+ state,
} from 'lit-element';
import {classMap} from 'lit-html/directives/class-map';
import {StyleInfo, styleMap} from 'lit-html/directives/style-map';
@@ -66,23 +66,23 @@
// URL for the image to use as revision.
@property({type: String}) revisionUrl = '';
- @internalProperty() protected baseSelected = true;
+ @state() protected baseSelected = true;
- @internalProperty() protected scaledSelected = true;
+ @state() protected scaledSelected = true;
- @internalProperty() protected followMouse = false;
+ @state() protected followMouse = false;
- @internalProperty() protected scale = 1;
+ @state() protected scale = 1;
- @internalProperty() protected checkerboardSelected = true;
+ @state() protected checkerboardSelected = true;
- @internalProperty() protected backgroundColor = '';
+ @state() protected backgroundColor = '';
- @internalProperty() protected automaticBlink = false;
+ @state() protected automaticBlink = false;
- @internalProperty() protected automaticBlinkShown = false;
+ @state() protected automaticBlinkShown = false;
- @internalProperty() protected zoomedImageStyle: StyleInfo = {};
+ @state() protected zoomedImageStyle: StyleInfo = {};
@query('.imageArea') protected imageArea!: HTMLDivElement;
@@ -94,16 +94,16 @@
private imageSize: Dimensions = {width: 0, height: 0};
- @internalProperty()
+ @state()
protected magnifierSize: Dimensions = {width: 0, height: 0};
- @internalProperty()
+ @state()
protected magnifierFrame: Rect = {
origin: {x: 0, y: 0},
dimensions: {width: 0, height: 0},
};
- @internalProperty()
+ @state()
protected overviewFrame: Rect = {
origin: {x: 0, y: 0},
dimensions: {width: 0, height: 0},
@@ -118,7 +118,7 @@
2,
];
- @internalProperty() protected grabbing = false;
+ @state() protected grabbing = false;
private ownsMouseDown = false;
@@ -745,16 +745,20 @@
}
mouseupMagnifier(event: MouseEvent) {
+ if (!this.ownsMouseDown) return;
+ this.grabbing = false;
+ this.ownsMouseDown = false;
const offsetX = event.clientX - this.pointerOnDown.x;
const offsetY = event.clientY - this.pointerOnDown.y;
const distance = Math.max(Math.abs(offsetX), Math.abs(offsetY));
// Consider very short drags as clicks. These tend to happen more often on
// external mice.
- if (this.ownsMouseDown && distance < DRAG_DEAD_ZONE_PIXELS) {
+ if (distance < DRAG_DEAD_ZONE_PIXELS) {
this.toggleImage();
+ this.dispatchEvent(createEvent({type: 'magnifier-clicked'}));
+ } else {
+ this.dispatchEvent(createEvent({type: 'magnifier-dragged'}));
}
- this.grabbing = false;
- this.ownsMouseDown = false;
}
mousemoveMagnifier(event: MouseEvent) {
@@ -793,8 +797,10 @@
}
mouseleaveMagnifier() {
+ if (!this.ownsMouseDown) return;
this.grabbing = false;
this.ownsMouseDown = false;
+ this.dispatchEvent(createEvent({type: 'magnifier-dragged'}));
}
dragstartMagnifier(event: DragEvent) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
index d7b6916..9439dca 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
@@ -18,11 +18,11 @@
css,
customElement,
html,
- internalProperty,
LitElement,
property,
PropertyValues,
query,
+ state,
} from 'lit-element';
import {StyleInfo, styleMap} from 'lit-html/directives/style-map';
import {ImageDiffAction} from '../../../api/diff';
@@ -45,13 +45,13 @@
@property({type: Object})
frameRect: Rect = {origin: {x: 0, y: 0}, dimensions: {width: 0, height: 0}};
- @internalProperty() protected contentStyle: StyleInfo = {};
+ @state() protected contentStyle: StyleInfo = {};
- @internalProperty() protected contentTransformStyle: StyleInfo = {};
+ @state() protected contentTransformStyle: StyleInfo = {};
- @internalProperty() protected frameStyle: StyleInfo = {};
+ @state() protected frameStyle: StyleInfo = {};
- @internalProperty() protected dragging = false;
+ @state() protected dragging = false;
@query('.content-box') protected contentBox!: HTMLDivElement;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
index a14a9cc..4558dda 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
@@ -18,10 +18,10 @@
css,
customElement,
html,
- internalProperty,
LitElement,
property,
PropertyValues,
+ state,
} from 'lit-element';
import {StyleInfo, styleMap} from 'lit-html/directives/style-map';
import {Rect} from './util';
@@ -41,7 +41,7 @@
@property({type: Object})
frameRect: Rect = {origin: {x: 0, y: 0}, dimensions: {width: 0, height: 0}};
- @internalProperty() protected imageStyles: StyleInfo = {};
+ @state() protected imageStyles: StyleInfo = {};
static styles = css`
:host {
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index d250537..6d5e475 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -36,7 +36,7 @@
"@webcomponents/webcomponentsjs": "^1.3.3",
"ba-linkify": "file:../../lib/ba-linkify/src/",
"codemirror-minified": "^5.60.0",
- "lit-element": "^2.4.0",
+ "lit-element": "^2.5.1",
"page": "^1.11.6",
"polymer-bridges": "file:../../polymer-bridges/",
"polymer-resin": "^2.0.1",
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 70cddc0..30eb658 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -529,7 +529,7 @@
real_author?: AccountInfo;
date: Timestamp;
message: string;
- accountsInMessage?: AccountInfo[];
+ accounts_in_message?: AccountInfo[];
tag?: ReviewInputTag;
_revision_number?: PatchSetNum;
}
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index fa35f288..543017a 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -428,17 +428,17 @@
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-lit-element@^2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.4.0.tgz#b22607a037a8fc08f5a80736dddf7f3f5d401452"
- integrity sha512-pBGLglxyhq/Prk2H91nA0KByq/hx/wssJBQFiYqXhGDvEnY31PRGYf1RglVzyLeRysu0IHm2K0P196uLLWmwFg==
+lit-element@^2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.5.1.tgz#3fa74b121a6cd22902409ae3859b7847d01aa6b6"
+ integrity sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==
dependencies:
lit-html "^1.1.1"
lit-html@^1.1.1:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.3.0.tgz#c80f3cc5793a6dea6c07172be90a70ab20e56034"
- integrity sha512-0Q1bwmaFH9O14vycPHw8C/IeHMk/uSDldVLIefu/kfbTBGIc44KGH6A8p1bDfxUfHdc8q6Ct7kQklWoHgr4t1Q==
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.4.1.tgz#0c6f3ee4ad4eb610a49831787f0478ad8e9ae5e0"
+ integrity sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==
page@^1.11.6:
version "1.11.6"
diff --git a/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy b/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
index 1241665..a9e0a44 100644
--- a/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
+++ b/resources/com/google/gerrit/server/mail/InboundEmailRejection.soy
@@ -65,6 +65,14 @@
{call InboundEmailRejectionFooter /}
{/template}
+{template InboundEmailRejection_CHANGE_NOT_FOUND kind="text"}
+ Gerrit Code Review was unable to process your email because the change was not found.
+ {\n}
+ Maybe the project doesn't exist or is not visible? Maybe the change is not visible or got
+ deleted?
+ {call InboundEmailRejectionFooter /}
+{/template}
+
{template InboundEmailRejection_COMMENT_REJECTED kind="text"}
Gerrit Code Review rejected one or more comments because they did not pass validation, or
because the maximum number of comments per change would be exceeded.
diff --git a/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy b/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
index 6937d13..3444b7f 100644
--- a/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
+++ b/resources/com/google/gerrit/server/mail/InboundEmailRejectionHtml.soy
@@ -82,6 +82,17 @@
{call InboundEmailRejectionFooterHtml /}
{/template}
+{template InboundEmailRejectionHtml_CHANGE_NOT_FOUND}
+ <p>
+ Gerrit Code Review was unable to process your email because the change was not found.
+ </p>
+ <p>
+ Maybe the project doesn't exist or is not visible? Maybe the change is not visible or got
+ deleted?
+ <p>
+ {call InboundEmailRejectionFooterHtml /}
+{/template}
+
{template InboundEmailRejectionHtml_COMMENT_REJECTED}
<p>
Gerrit Code Review rejected one or more comments because they did not pass validation, or