blob: 9a9a21a3d50b0388e727fd9273119b1946a6a17c [file] [log] [blame]
// 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).action(PermissionRule.Action.ALLOW);
}
/** Starts a builder for denying a label permission. */
public static TestLabelPermission.Builder blockLabel(String name) {
return TestLabelPermission.builder().name(name).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();
/** 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);
/** 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 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);
}
}