Merge changes from topic "boolcond"
* changes: UiActions: defer GlobalPermission checks on views CherryPick: use BooleanCondition for visible PermissionBackend: support bulk evaluation of UiAction PermissionBackend: support testCond for UiAction Delay UiAction visible and enabled with BooleanCondition
This commit is contained in:
@@ -16,7 +16,6 @@ package com.google.gerrit.server.change;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -36,6 +35,7 @@ import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -162,7 +162,7 @@ public class ActionJson {
|
||||
return out;
|
||||
}
|
||||
|
||||
FluentIterable<UiAction.Description> descs =
|
||||
Iterable<UiAction.Description> descs =
|
||||
uiActions.from(changeViews, changeResourceFactory.create(ctl));
|
||||
|
||||
// The followup action is a client-side only operation that does not
|
||||
@@ -174,7 +174,7 @@ public class ActionJson {
|
||||
PrivateInternals_UiActionDescription.setMethod(descr, "POST");
|
||||
descr.setTitle("Create follow-up change");
|
||||
descr.setLabel("Follow-Up");
|
||||
descs = descs.append(descr);
|
||||
descs = Iterables.concat(descs, Collections.singleton(descr));
|
||||
}
|
||||
|
||||
ACTION:
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
package com.google.gerrit.server.change;
|
||||
|
||||
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
|
||||
|
||||
import com.google.gerrit.extensions.api.changes.CherryPickInput;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
@@ -106,10 +108,11 @@ public class CherryPick
|
||||
.setLabel("Cherry Pick")
|
||||
.setTitle("Cherry pick change to a different branch")
|
||||
.setVisible(
|
||||
rsrc.isCurrent()
|
||||
&& permissionBackend
|
||||
and(
|
||||
rsrc.isCurrent(),
|
||||
permissionBackend
|
||||
.user(user)
|
||||
.project(rsrc.getProject())
|
||||
.testOrFalse(ProjectPermission.CREATE_CHANGE));
|
||||
.testCond(ProjectPermission.CREATE_CHANGE)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,23 +14,32 @@
|
||||
|
||||
package com.google.gerrit.server.extensions.webui;
|
||||
|
||||
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
|
||||
import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
|
||||
import com.google.gerrit.extensions.conditions.BooleanCondition;
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.extensions.restapi.RestCollection;
|
||||
import com.google.gerrit.extensions.restapi.RestResource;
|
||||
import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
|
||||
import com.google.gerrit.extensions.webui.UiAction;
|
||||
import com.google.gerrit.extensions.webui.UiAction.Description;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.permissions.GlobalPermission;
|
||||
import com.google.gerrit.server.permissions.PermissionBackend;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendCondition;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.slf4j.Logger;
|
||||
@@ -53,16 +62,35 @@ public class UiActions {
|
||||
this.userProvider = userProvider;
|
||||
}
|
||||
|
||||
public <R extends RestResource> FluentIterable<UiAction.Description> from(
|
||||
public <R extends RestResource> Iterable<UiAction.Description> from(
|
||||
RestCollection<?, R> collection, R resource) {
|
||||
return from(collection.views(), resource);
|
||||
}
|
||||
|
||||
public <R extends RestResource> FluentIterable<UiAction.Description> from(
|
||||
public <R extends RestResource> Iterable<UiAction.Description> from(
|
||||
DynamicMap<RestView<R>> views, R resource) {
|
||||
return FluentIterable.from(views)
|
||||
.transform((e) -> describe(e, resource))
|
||||
.filter(Objects::nonNull);
|
||||
List<UiAction.Description> descs =
|
||||
Streams.stream(views)
|
||||
.map(e -> describe(e, resource))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toList());
|
||||
|
||||
List<PermissionBackendCondition> conds =
|
||||
Streams.concat(
|
||||
descs.stream().flatMap(u -> Streams.stream(visibleCondition(u))),
|
||||
descs.stream().flatMap(u -> Streams.stream(enabledCondition(u))))
|
||||
.collect(toList());
|
||||
permissionBackend.bulkEvaluateTest(conds);
|
||||
|
||||
return descs.stream().filter(u -> u.isVisible()).collect(toList());
|
||||
}
|
||||
|
||||
private static Iterable<PermissionBackendCondition> visibleCondition(Description u) {
|
||||
return u.getVisibleCondition().children(PermissionBackendCondition.class);
|
||||
}
|
||||
|
||||
private static Iterable<PermissionBackendCondition> enabledCondition(Description u) {
|
||||
return u.getEnabledCondition().children(PermissionBackendCondition.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -86,22 +114,27 @@ public class UiActions {
|
||||
return null;
|
||||
}
|
||||
|
||||
UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
|
||||
if (dsc == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<GlobalOrPluginPermission> globalRequired;
|
||||
try {
|
||||
Set<GlobalOrPluginPermission> need =
|
||||
GlobalPermission.fromAnnotation(e.getPluginName(), view.getClass());
|
||||
if (!need.isEmpty() && permissionBackend.user(userProvider).test(need).isEmpty()) {
|
||||
// A permission is required, but test returned no candidates.
|
||||
return null;
|
||||
}
|
||||
globalRequired = GlobalPermission.fromAnnotation(e.getPluginName(), view.getClass());
|
||||
} catch (PermissionBackendException err) {
|
||||
log.error(
|
||||
String.format("exception testing view %s.%s", e.getPluginName(), e.getExportName()), err);
|
||||
return null;
|
||||
}
|
||||
|
||||
UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
|
||||
if (dsc == null || !dsc.isVisible()) {
|
||||
return null;
|
||||
if (!globalRequired.isEmpty()) {
|
||||
PermissionBackend.WithUser withUser = permissionBackend.user(userProvider);
|
||||
Iterator<GlobalOrPluginPermission> i = globalRequired.iterator();
|
||||
BooleanCondition p = withUser.testCond(i.next());
|
||||
while (i.hasNext()) {
|
||||
p = or(p, withUser.testCond(i.next()));
|
||||
}
|
||||
dsc.setVisible(and(p, dsc.getVisibleCondition()));
|
||||
}
|
||||
|
||||
String name = e.getExportName().substring(d + 1);
|
||||
|
||||
@@ -20,6 +20,7 @@ import static java.util.stream.Collectors.toSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.common.data.LabelType;
|
||||
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
|
||||
import com.google.gerrit.extensions.conditions.BooleanCondition;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
@@ -78,7 +79,7 @@ import org.slf4j.LoggerFactory;
|
||||
* public UiAction.Description getDescription(ChangeResource rsrc) {
|
||||
* return new UiAction.Description()
|
||||
* .setLabel("Submit")
|
||||
* .setVisible(rsrc.permissions().testOrFalse(ChangePermission.SUBMIT));
|
||||
* .setVisible(rsrc.permissions().testCond(ChangePermission.SUBMIT));
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@@ -94,6 +95,24 @@ public abstract class PermissionBackend {
|
||||
return user(checkNotNull(user, "Provider<CurrentUser>").get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk evaluate a collection of {@link PermissionBackendCondition} for view handling.
|
||||
*
|
||||
* <p>Overridden implementations should call {@link PermissionBackendCondition#set(boolean)} to
|
||||
* cache the result of {@code testOrFalse} in the condition for later evaluation. Caching the
|
||||
* result will bypass the usual invocation of {@code testOrFalse}.
|
||||
*
|
||||
* <p>{@code conds} may contain duplicate entries (such as same user, resource, permission
|
||||
* triplet). When duplicates exist, implementations should set a result into all instances to
|
||||
* ensure {@code testOrFalse} does not get invoked during evaluation of the containing condition.
|
||||
*
|
||||
* @param conds conditions to consider.
|
||||
*/
|
||||
public void bulkEvaluateTest(Collection<PermissionBackendCondition> conds) {
|
||||
// Do nothing by default. The default implementation of PermissionBackendCondition
|
||||
// delegates to the appropriate testOrFalse method in PermissionBackend.
|
||||
}
|
||||
|
||||
/** PermissionBackend with an optional per-request ReviewDb handle. */
|
||||
public abstract static class AcceptsReviewDb<T> {
|
||||
protected Provider<ReviewDb> db;
|
||||
@@ -198,6 +217,10 @@ public abstract class PermissionBackend {
|
||||
}
|
||||
}
|
||||
|
||||
public BooleanCondition testCond(GlobalOrPluginPermission perm) {
|
||||
return new PermissionBackendCondition.WithUser(this, perm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a set of projects using {@code check(perm)}.
|
||||
*
|
||||
@@ -265,6 +288,10 @@ public abstract class PermissionBackend {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public BooleanCondition testCond(ProjectPermission perm) {
|
||||
return new PermissionBackendCondition.ForProject(this, perm);
|
||||
}
|
||||
}
|
||||
|
||||
/** PermissionBackend scoped to a user, project and reference. */
|
||||
@@ -313,6 +340,10 @@ public abstract class PermissionBackend {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public BooleanCondition testCond(RefPermission perm) {
|
||||
return new PermissionBackendCondition.ForRef(this, perm);
|
||||
}
|
||||
}
|
||||
|
||||
/** PermissionBackend scoped to a user, project, reference and change. */
|
||||
@@ -354,6 +385,10 @@ public abstract class PermissionBackend {
|
||||
}
|
||||
}
|
||||
|
||||
public BooleanCondition testCond(ChangePermissionOrLabel perm) {
|
||||
return new PermissionBackendCondition.ForChange(this, perm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test which values of a label the user may be able to set.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (C) 2017 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.server.permissions;
|
||||
|
||||
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
|
||||
import com.google.gerrit.extensions.conditions.BooleanCondition;
|
||||
import com.google.gerrit.extensions.conditions.PrivateInternals_BooleanCondition;
|
||||
|
||||
/** {@link BooleanCondition} to evaluate a permission. */
|
||||
public abstract class PermissionBackendCondition
|
||||
extends PrivateInternals_BooleanCondition.SubclassOnlyInCoreServer {
|
||||
Boolean value;
|
||||
|
||||
/**
|
||||
* Assign a specific {@code testOrFalse} result to this condition.
|
||||
*
|
||||
* <p>By setting the condition to a specific value the condition will bypass calling {@link
|
||||
* PermissionBackend} during {@code value()}, and immediately return the set value instead.
|
||||
*
|
||||
* @param val value to return from {@code value()}.
|
||||
*/
|
||||
public void set(boolean val) {
|
||||
value = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
|
||||
public static class WithUser extends PermissionBackendCondition {
|
||||
private final PermissionBackend.WithUser impl;
|
||||
private final GlobalOrPluginPermission perm;
|
||||
|
||||
WithUser(PermissionBackend.WithUser impl, GlobalOrPluginPermission perm) {
|
||||
this.impl = impl;
|
||||
this.perm = perm;
|
||||
}
|
||||
|
||||
public PermissionBackend.WithUser withUser() {
|
||||
return impl;
|
||||
}
|
||||
|
||||
public GlobalOrPluginPermission permission() {
|
||||
return perm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean value() {
|
||||
return value != null ? value : impl.testOrFalse(perm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PermissionBackendCondition.WithUser(" + perm + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForProject extends PermissionBackendCondition {
|
||||
private final PermissionBackend.ForProject impl;
|
||||
private final ProjectPermission perm;
|
||||
|
||||
ForProject(PermissionBackend.ForProject impl, ProjectPermission perm) {
|
||||
this.impl = impl;
|
||||
this.perm = perm;
|
||||
}
|
||||
|
||||
public PermissionBackend.ForProject project() {
|
||||
return impl;
|
||||
}
|
||||
|
||||
public ProjectPermission permission() {
|
||||
return perm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean value() {
|
||||
return value != null ? value : impl.testOrFalse(perm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PermissionBackendCondition.ForProject(" + perm + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForRef extends PermissionBackendCondition {
|
||||
private final PermissionBackend.ForRef impl;
|
||||
private final RefPermission perm;
|
||||
|
||||
ForRef(PermissionBackend.ForRef impl, RefPermission perm) {
|
||||
this.impl = impl;
|
||||
this.perm = perm;
|
||||
}
|
||||
|
||||
public PermissionBackend.ForRef ref() {
|
||||
return impl;
|
||||
}
|
||||
|
||||
public RefPermission permission() {
|
||||
return perm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean value() {
|
||||
return value != null ? value : impl.testOrFalse(perm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PermissionBackendCondition.ForRef(" + perm + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForChange extends PermissionBackendCondition {
|
||||
private final PermissionBackend.ForChange impl;
|
||||
private final ChangePermissionOrLabel perm;
|
||||
|
||||
ForChange(PermissionBackend.ForChange impl, ChangePermissionOrLabel perm) {
|
||||
this.impl = impl;
|
||||
this.perm = perm;
|
||||
}
|
||||
|
||||
public PermissionBackend.ForChange change() {
|
||||
return impl;
|
||||
}
|
||||
|
||||
public ChangePermissionOrLabel permission() {
|
||||
return perm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean value() {
|
||||
return value != null ? value : impl.testOrFalse(perm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PermissionBackendCondition.ForChange(" + perm + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user