| // Copyright (C) 2019 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.testsuite.project; | |
| import static com.google.common.base.Preconditions.checkArgument; | |
| import static com.google.gerrit.entities.AccessSection.GLOBAL_CAPABILITIES; | |
| import static java.util.Objects.requireNonNull; | |
| import com.google.auto.value.AutoValue; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.gerrit.acceptance.testsuite.ThrowingConsumer; | |
| import com.google.gerrit.common.data.GlobalCapability; | |
| import com.google.gerrit.entities.AccountGroup; | |
| import com.google.gerrit.entities.LabelType; | |
| import com.google.gerrit.entities.Permission; | |
| import com.google.gerrit.entities.PermissionRange; | |
| import com.google.gerrit.entities.PermissionRule; | |
| import com.google.gerrit.entities.Project; | |
| import com.google.gerrit.server.config.AllProjectsName; | |
| import java.util.Optional; | |
| import org.eclipse.jgit.lib.Constants; | |
| @AutoValue | |
| public abstract class TestProjectUpdate { | |
| /** Starts a builder for allowing a capability. */ | |
| public static TestCapability.Builder allowCapability(String name) { | |
| return TestCapability.builder().name(name); | |
| } | |
| /** Records a global capability to be updated. */ | |
| @AutoValue | |
| public abstract static class TestCapability { | |
| private static Builder builder() { | |
| return new AutoValue_TestProjectUpdate_TestCapability.Builder(); | |
| } | |
| abstract String name(); | |
| abstract AccountGroup.UUID group(); | |
| abstract int min(); | |
| abstract int max(); | |
| /** Builder for {@link TestCapability}. */ | |
| @AutoValue.Builder | |
| public abstract static class Builder { | |
| /** Sets the name of the capability. */ | |
| public abstract Builder name(String name); | |
| abstract String name(); | |
| /** Sets the group to which the capability applies. */ | |
| public abstract Builder group(AccountGroup.UUID group); | |
| abstract Builder min(int min); | |
| abstract Optional<Integer> min(); | |
| abstract Builder max(int max); | |
| abstract Optional<Integer> max(); | |
| /** Sets the minimum and maximum values for the capability. */ | |
| public Builder range(int min, int max) { | |
| checkNonInvertedRange(min, max); | |
| return min(min).max(max); | |
| } | |
| /** Builds the {@link TestCapability}. */ | |
| abstract TestCapability autoBuild(); | |
| public TestCapability build() { | |
| PermissionRange.WithDefaults withDefaults = GlobalCapability.getRange(name()); | |
| if (withDefaults != null) { | |
| int min = min().orElse(withDefaults.getDefaultMin()); | |
| int max = max().orElse(withDefaults.getDefaultMax()); | |
| range(min, max); | |
| // Don't enforce range is nonempty; this is allowed for e.g. batchChangesLimit. | |
| } else { | |
| checkArgument( | |
| !min().isPresent() && !max().isPresent(), | |
| "capability %s does not support ranges", | |
| name()); | |
| range(0, 0); | |
| } | |
| return autoBuild(); | |
| } | |
| } | |
| } | |
| /** Starts a builder for allowing a permission. */ | |
| public static TestPermission.Builder allow(String name) { | |
| return TestPermission.builder().name(name).action(PermissionRule.Action.ALLOW); | |
| } | |
| /** Starts a builder for denying a permission. */ | |
| public static TestPermission.Builder deny(String name) { | |
| return TestPermission.builder().name(name).action(PermissionRule.Action.DENY); | |
| } | |
| /** Starts a builder for blocking a permission. */ | |
| public static TestPermission.Builder block(String name) { | |
| return TestPermission.builder().name(name).action(PermissionRule.Action.BLOCK); | |
| } | |
| /** | |
| * Records a permission to be updated. | |
| * | |
| * <p>Not used for permissions that have ranges (label permissions) or global capabilities. | |
| */ | |
| @AutoValue | |
| public abstract static class TestPermission { | |
| private static Builder builder() { | |
| return new AutoValue_TestProjectUpdate_TestPermission.Builder().force(false); | |
| } | |
| abstract String name(); | |
| abstract String ref(); | |
| abstract AccountGroup.UUID group(); | |
| abstract PermissionRule.Action action(); | |
| abstract boolean force(); | |
| /** Builder for {@link TestPermission}. */ | |
| @AutoValue.Builder | |
| public abstract static class Builder { | |
| abstract Builder name(String name); | |
| /** Sets the ref pattern used on the permission. */ | |
| public abstract Builder ref(String ref); | |
| /** Sets the group to which the permission applies. */ | |
| public abstract Builder group(AccountGroup.UUID groupUuid); | |
| abstract Builder action(PermissionRule.Action action); | |
| /** Sets whether the permission is a force permission. */ | |
| public abstract Builder force(boolean force); | |
| /** Builds the {@link TestPermission}. */ | |
| public abstract TestPermission build(); | |
| } | |
| } | |
| /** Starts a builder for allowing a label permission. */ | |
| public static TestLabelPermission.Builder allowLabel(String name) { | |
| return TestLabelPermission.builder() | |
| .name(name) | |
| .isAddPermission(true) | |
| .action(PermissionRule.Action.ALLOW); | |
| } | |
| /** Starts a builder for denying a label permission. */ | |
| public static TestLabelPermission.Builder blockLabel(String name) { | |
| return TestLabelPermission.builder() | |
| .name(name) | |
| .isAddPermission(true) | |
| .action(PermissionRule.Action.BLOCK); | |
| } | |
| /** Starts a builder for allowing a remove-label permission. */ | |
| public static TestLabelPermission.Builder allowLabelRemoval(String name) { | |
| return TestLabelPermission.builder() | |
| .name(name) | |
| .isAddPermission(false) | |
| .action(PermissionRule.Action.ALLOW); | |
| } | |
| /** Starts a builder for denying a remove-label permission. */ | |
| public static TestLabelPermission.Builder blockLabelRemoval(String name) { | |
| return TestLabelPermission.builder() | |
| .name(name) | |
| .isAddPermission(false) | |
| .action(PermissionRule.Action.BLOCK); | |
| } | |
| /** Records a label permission to be updated. */ | |
| @AutoValue | |
| public abstract static class TestLabelPermission { | |
| private static Builder builder() { | |
| return new AutoValue_TestProjectUpdate_TestLabelPermission.Builder().impersonation(false); | |
| } | |
| abstract String name(); | |
| abstract String ref(); | |
| abstract AccountGroup.UUID group(); | |
| abstract PermissionRule.Action action(); | |
| abstract int min(); | |
| abstract int max(); | |
| abstract boolean impersonation(); | |
| abstract boolean isAddPermission(); | |
| /** Builder for {@link TestLabelPermission}. */ | |
| @AutoValue.Builder | |
| public abstract static class Builder { | |
| abstract Builder name(String name); | |
| /** Sets the ref pattern used on the permission. */ | |
| public abstract Builder ref(String ref); | |
| /** Sets the group to which the permission applies. */ | |
| public abstract Builder group(AccountGroup.UUID group); | |
| abstract Builder action(PermissionRule.Action action); | |
| abstract Builder min(int min); | |
| abstract Builder max(int max); | |
| abstract Builder isAddPermission(boolean isAddPermission); | |
| /** Sets the minimum and maximum values for the permission. */ | |
| public Builder range(int min, int max) { | |
| checkArgument(min != 0 || max != 0, "empty range"); | |
| checkNonInvertedRange(min, max); | |
| return min(min).max(max); | |
| } | |
| /** Sets whether this permission should be for impersonating another user's votes. */ | |
| public abstract Builder impersonation(boolean impersonation); | |
| abstract TestLabelPermission autoBuild(); | |
| /** Builds the {@link TestPermission}. */ | |
| public TestLabelPermission build() { | |
| TestLabelPermission result = autoBuild(); | |
| checkLabelName(result.name()); | |
| return result; | |
| } | |
| } | |
| } | |
| /** | |
| * Starts a builder for describing a permission key for deletion. Not for label permissions or | |
| * global capabilities. | |
| */ | |
| public static TestPermissionKey.Builder permissionKey(String name) { | |
| return TestPermissionKey.builder().name(name); | |
| } | |
| /** Starts a builder for describing a label permission key for deletion. */ | |
| public static TestPermissionKey.Builder labelPermissionKey(String name) { | |
| checkLabelName(name); | |
| return TestPermissionKey.builder().name(Permission.forLabel(name)); | |
| } | |
| /** Starts a builder for describing a label removal permission key for deletion. */ | |
| public static TestPermissionKey.Builder labelRemovalPermissionKey(String name) { | |
| checkLabelName(name); | |
| return TestPermissionKey.builder().name(Permission.forRemoveLabel(name)); | |
| } | |
| /** Starts a builder for describing a capability key for deletion. */ | |
| public static TestPermissionKey.Builder capabilityKey(String name) { | |
| return TestPermissionKey.builder().name(name).section(GLOBAL_CAPABILITIES); | |
| } | |
| /** Records the key of a permission (of any type) for deletion. */ | |
| @AutoValue | |
| public abstract static class TestPermissionKey { | |
| private static Builder builder() { | |
| return new AutoValue_TestProjectUpdate_TestPermissionKey.Builder(); | |
| } | |
| abstract String section(); | |
| abstract String name(); | |
| abstract Optional<AccountGroup.UUID> group(); | |
| @AutoValue.Builder | |
| public abstract static class Builder { | |
| abstract Builder section(String section); | |
| abstract Optional<String> section(); | |
| /** Sets the ref pattern used on the permission. Not for global capabilities. */ | |
| public Builder ref(String ref) { | |
| requireNonNull(ref); | |
| checkArgument(ref.startsWith(Constants.R_REFS), "must be a ref: %s", ref); | |
| checkArgument( | |
| !section().isPresent() || !section().get().equals(GLOBAL_CAPABILITIES), | |
| "can't set ref on global capability"); | |
| return section(ref); | |
| } | |
| abstract Builder name(String name); | |
| /** Sets the group to which the permission applies. */ | |
| public abstract Builder group(AccountGroup.UUID group); | |
| /** Builds the {@link TestPermissionKey}. */ | |
| public abstract TestPermissionKey build(); | |
| } | |
| } | |
| static Builder builder( | |
| Project.NameKey nameKey, | |
| AllProjectsName allProjectsName, | |
| ThrowingConsumer<TestProjectUpdate> projectUpdater) { | |
| return new AutoValue_TestProjectUpdate.Builder() | |
| .nameKey(nameKey) | |
| .allProjectsName(allProjectsName) | |
| .projectUpdater(projectUpdater) | |
| .removeAllAccessSections(false); | |
| } | |
| /** Builder for {@link TestProjectUpdate}. */ | |
| @AutoValue.Builder | |
| public abstract static class Builder { | |
| abstract Builder nameKey(Project.NameKey project); | |
| abstract Builder allProjectsName(AllProjectsName allProjects); | |
| abstract ImmutableList.Builder<TestPermission> addedPermissionsBuilder(); | |
| abstract ImmutableList.Builder<TestLabelPermission> addedLabelPermissionsBuilder(); | |
| abstract ImmutableList.Builder<TestCapability> addedCapabilitiesBuilder(); | |
| abstract ImmutableList.Builder<TestPermissionKey> removedPermissionsBuilder(); | |
| abstract ImmutableMap.Builder<TestPermissionKey, Boolean> exclusiveGroupPermissionsBuilder(); | |
| abstract Builder removeAllAccessSections(boolean value); | |
| /** | |
| * Removes all access sections. Useful when testing against a specific set of access sections or | |
| * permissions. | |
| */ | |
| public Builder removeAllAccessSections() { | |
| return removeAllAccessSections(true); | |
| } | |
| /** Adds a permission to be included in this update. */ | |
| public Builder add(TestPermission testPermission) { | |
| addedPermissionsBuilder().add(testPermission); | |
| return this; | |
| } | |
| /** Adds a permission to be included in this update. */ | |
| public Builder add(TestPermission.Builder testPermissionBuilder) { | |
| return add(testPermissionBuilder.build()); | |
| } | |
| /** Adds a label permission to be included in this update. */ | |
| public Builder add(TestLabelPermission testLabelPermission) { | |
| addedLabelPermissionsBuilder().add(testLabelPermission); | |
| return this; | |
| } | |
| /** Adds a label permission to be included in this update. */ | |
| public Builder add(TestLabelPermission.Builder testLabelPermissionBuilder) { | |
| return add(testLabelPermissionBuilder.build()); | |
| } | |
| /** Adds a capability to be included in this update. */ | |
| public Builder add(TestCapability testCapability) { | |
| addedCapabilitiesBuilder().add(testCapability); | |
| return this; | |
| } | |
| /** Adds a capability to be included in this update. */ | |
| public Builder add(TestCapability.Builder testCapabilityBuilder) { | |
| return add(testCapabilityBuilder.build()); | |
| } | |
| /** Removes a permission, label permission, or capability as part of this update. */ | |
| public Builder remove(TestPermissionKey testPermissionKey) { | |
| removedPermissionsBuilder().add(testPermissionKey); | |
| return this; | |
| } | |
| /** Removes a permission, label permission, or capability as part of this update. */ | |
| public Builder remove(TestPermissionKey.Builder testPermissionKeyBuilder) { | |
| return remove(testPermissionKeyBuilder.build()); | |
| } | |
| /** Sets the exclusive bit bit for the given permission key. */ | |
| public Builder setExclusiveGroup( | |
| TestPermissionKey.Builder testPermissionKeyBuilder, boolean exclusive) { | |
| return setExclusiveGroup(testPermissionKeyBuilder.build(), exclusive); | |
| } | |
| /** Sets the exclusive bit bit for the given permission key. */ | |
| public Builder setExclusiveGroup(TestPermissionKey testPermissionKey, boolean exclusive) { | |
| checkArgument( | |
| !testPermissionKey.group().isPresent(), | |
| "do not specify group for setExclusiveGroup: %s", | |
| testPermissionKey); | |
| checkArgument( | |
| !testPermissionKey.section().equals(GLOBAL_CAPABILITIES), | |
| "setExclusiveGroup not valid for global capabilities: %s", | |
| testPermissionKey); | |
| exclusiveGroupPermissionsBuilder().put(testPermissionKey, exclusive); | |
| return this; | |
| } | |
| abstract Builder projectUpdater(ThrowingConsumer<TestProjectUpdate> projectUpdater); | |
| abstract TestProjectUpdate autoBuild(); | |
| TestProjectUpdate build() { | |
| TestProjectUpdate projectUpdate = autoBuild(); | |
| if (projectUpdate.hasCapabilityUpdates()) { | |
| checkArgument( | |
| projectUpdate.nameKey().equals(projectUpdate.allProjectsName()), | |
| "cannot update global capabilities on %s, only %s: %s", | |
| projectUpdate.nameKey(), | |
| projectUpdate.allProjectsName(), | |
| projectUpdate); | |
| } | |
| return projectUpdate; | |
| } | |
| /** Executes the update, updating the underlying project. */ | |
| public void update() { | |
| TestProjectUpdate projectUpdate = build(); | |
| projectUpdate.projectUpdater().acceptAndThrowSilently(projectUpdate); | |
| } | |
| } | |
| abstract Project.NameKey nameKey(); | |
| abstract AllProjectsName allProjectsName(); | |
| abstract ImmutableList<TestPermission> addedPermissions(); | |
| abstract ImmutableList<TestLabelPermission> addedLabelPermissions(); | |
| abstract ImmutableList<TestCapability> addedCapabilities(); | |
| abstract ImmutableList<TestPermissionKey> removedPermissions(); | |
| abstract ImmutableMap<TestPermissionKey, Boolean> exclusiveGroupPermissions(); | |
| abstract ThrowingConsumer<TestProjectUpdate> projectUpdater(); | |
| abstract boolean removeAllAccessSections(); | |
| boolean hasCapabilityUpdates() { | |
| return !addedCapabilities().isEmpty() | |
| || removedPermissions().stream().anyMatch(k -> k.section().equals(GLOBAL_CAPABILITIES)); | |
| } | |
| private static void checkLabelName(String name) { | |
| // "label-Code-Review" is technically a valid label name, and we don't prevent users from | |
| // using it in production, but specifying it in a test is programmer error. | |
| checkArgument(!Permission.isLabel(name), "expected label name, got permission name: %s", name); | |
| LabelType.checkName(name); | |
| } | |
| private static void checkNonInvertedRange(int min, int max) { | |
| checkArgument(min <= max, "inverted range: %s > %s", min, max); | |
| } | |
| } |