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:
		@@ -0,0 +1,217 @@
 | 
			
		||||
// 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.extensions.conditions;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Iterables;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
/** Delayed evaluation of a boolean condition. */
 | 
			
		||||
public abstract class BooleanCondition {
 | 
			
		||||
  public static final BooleanCondition TRUE = new Value(true);
 | 
			
		||||
  public static final BooleanCondition FALSE = new Value(false);
 | 
			
		||||
 | 
			
		||||
  public static BooleanCondition valueOf(boolean a) {
 | 
			
		||||
    return a ? TRUE : FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static BooleanCondition and(BooleanCondition a, BooleanCondition b) {
 | 
			
		||||
    return a == FALSE || b == FALSE ? FALSE : new And(a, b);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static BooleanCondition and(boolean a, BooleanCondition b) {
 | 
			
		||||
    return and(valueOf(a), b);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static BooleanCondition or(BooleanCondition a, BooleanCondition b) {
 | 
			
		||||
    return a == TRUE || b == TRUE ? TRUE : new Or(a, b);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static BooleanCondition or(boolean a, BooleanCondition b) {
 | 
			
		||||
    return or(valueOf(a), b);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static BooleanCondition not(BooleanCondition bc) {
 | 
			
		||||
    return bc == TRUE ? FALSE : bc == FALSE ? TRUE : new Not(bc);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BooleanCondition() {}
 | 
			
		||||
 | 
			
		||||
  /** @return evaluate the condition and return its value. */
 | 
			
		||||
  public abstract boolean value();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Recursively collect all children of type {@code type}.
 | 
			
		||||
   *
 | 
			
		||||
   * @param type implementation type of the conditions to collect and return.
 | 
			
		||||
   * @return non-null, unmodifiable iteration of children of type {@code type}.
 | 
			
		||||
   */
 | 
			
		||||
  public abstract <T> Iterable<T> children(Class<T> type);
 | 
			
		||||
 | 
			
		||||
  private static final class And extends BooleanCondition {
 | 
			
		||||
    private final BooleanCondition a;
 | 
			
		||||
    private final BooleanCondition b;
 | 
			
		||||
 | 
			
		||||
    And(BooleanCondition a, BooleanCondition b) {
 | 
			
		||||
      this.a = a;
 | 
			
		||||
      this.b = b;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean value() {
 | 
			
		||||
      return a.value() && b.value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> Iterable<T> children(Class<T> type) {
 | 
			
		||||
      return Iterables.concat(a.children(type), b.children(type));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
      return a.hashCode() * 31 + b.hashCode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object other) {
 | 
			
		||||
      if (other instanceof And) {
 | 
			
		||||
        And o = (And) other;
 | 
			
		||||
        return a.equals(o.a) && b.equals(o.b);
 | 
			
		||||
      }
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
      return "(" + maybeTrim(a, getClass()) + " && " + maybeTrim(a, getClass()) + ")";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static final class Or extends BooleanCondition {
 | 
			
		||||
    private final BooleanCondition a;
 | 
			
		||||
    private final BooleanCondition b;
 | 
			
		||||
 | 
			
		||||
    Or(BooleanCondition a, BooleanCondition b) {
 | 
			
		||||
      this.a = a;
 | 
			
		||||
      this.b = b;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean value() {
 | 
			
		||||
      return a.value() || b.value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> Iterable<T> children(Class<T> type) {
 | 
			
		||||
      return Iterables.concat(a.children(type), b.children(type));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
      return a.hashCode() * 31 + b.hashCode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object other) {
 | 
			
		||||
      if (other instanceof Or) {
 | 
			
		||||
        Or o = (Or) other;
 | 
			
		||||
        return a.equals(o.a) && b.equals(o.b);
 | 
			
		||||
      }
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
      return "(" + maybeTrim(a, getClass()) + " || " + maybeTrim(a, getClass()) + ")";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static final class Not extends BooleanCondition {
 | 
			
		||||
    private final BooleanCondition cond;
 | 
			
		||||
 | 
			
		||||
    Not(BooleanCondition bc) {
 | 
			
		||||
      cond = bc;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean value() {
 | 
			
		||||
      return !cond.value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> Iterable<T> children(Class<T> type) {
 | 
			
		||||
      return cond.children(type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
      return cond.hashCode() * 31;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object other) {
 | 
			
		||||
      return other instanceof Not ? cond.equals(((Not) other).cond) : false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
      return "!" + cond;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static final class Value extends BooleanCondition {
 | 
			
		||||
    private final boolean value;
 | 
			
		||||
 | 
			
		||||
    Value(boolean v) {
 | 
			
		||||
      value = v;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean value() {
 | 
			
		||||
      return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> Iterable<T> children(Class<T> type) {
 | 
			
		||||
      return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
      return value ? 1 : 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object other) {
 | 
			
		||||
      return other instanceof Value ? value == ((Value) other).value : false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
      return Boolean.toString(value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Remove leading '(' and trailing ')' if the type is the same as the parent. */
 | 
			
		||||
  static String maybeTrim(BooleanCondition cond, Class<? extends BooleanCondition> type) {
 | 
			
		||||
    String s = cond.toString();
 | 
			
		||||
    if (cond.getClass() == type
 | 
			
		||||
        && s.length() > 2
 | 
			
		||||
        && s.charAt(0) == '('
 | 
			
		||||
        && s.charAt(s.length() - 1) == ')') {
 | 
			
		||||
      s = s.substring(1, s.length() - 1);
 | 
			
		||||
    }
 | 
			
		||||
    return s;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
// 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.extensions.conditions;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
/** <b>DO NOT USE</b> */
 | 
			
		||||
public final class PrivateInternals_BooleanCondition {
 | 
			
		||||
  private PrivateInternals_BooleanCondition() {}
 | 
			
		||||
 | 
			
		||||
  public abstract static class SubclassOnlyInCoreServer extends BooleanCondition {
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> Iterable<T> children(Class<T> type) {
 | 
			
		||||
      if (type.isAssignableFrom(getClass())) {
 | 
			
		||||
        return Collections.singleton((T) this);
 | 
			
		||||
      }
 | 
			
		||||
      return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,6 +14,7 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.extensions.webui;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.extensions.conditions.BooleanCondition;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.RestResource;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.RestView;
 | 
			
		||||
 | 
			
		||||
@@ -35,8 +36,8 @@ public interface UiAction<R extends RestResource> extends RestView<R> {
 | 
			
		||||
    private String id;
 | 
			
		||||
    private String label;
 | 
			
		||||
    private String title;
 | 
			
		||||
    private boolean visible = true;
 | 
			
		||||
    private boolean enabled = true;
 | 
			
		||||
    private BooleanCondition visible = BooleanCondition.TRUE;
 | 
			
		||||
    private BooleanCondition enabled = BooleanCondition.TRUE;
 | 
			
		||||
 | 
			
		||||
    public String getMethod() {
 | 
			
		||||
      return method;
 | 
			
		||||
@@ -77,6 +78,10 @@ public interface UiAction<R extends RestResource> extends RestView<R> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isVisible() {
 | 
			
		||||
      return getVisibleCondition().value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BooleanCondition getVisibleCondition() {
 | 
			
		||||
      return visible;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -85,16 +90,33 @@ public interface UiAction<R extends RestResource> extends RestView<R> {
 | 
			
		||||
     * action description may not be sent to the client.
 | 
			
		||||
     */
 | 
			
		||||
    public Description setVisible(boolean visible) {
 | 
			
		||||
      return setVisible(BooleanCondition.valueOf(visible));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set if the action's button is visible on screen for the current client. If not visible the
 | 
			
		||||
     * action description may not be sent to the client.
 | 
			
		||||
     */
 | 
			
		||||
    public Description setVisible(BooleanCondition visible) {
 | 
			
		||||
      this.visible = visible;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isEnabled() {
 | 
			
		||||
      return enabled && isVisible();
 | 
			
		||||
      return getEnabledCondition().value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BooleanCondition getEnabledCondition() {
 | 
			
		||||
      return BooleanCondition.and(enabled, visible);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Set if the button should be invokable (true), or greyed out (false). */
 | 
			
		||||
    public Description setEnabled(boolean enabled) {
 | 
			
		||||
      return setEnabled(BooleanCondition.valueOf(enabled));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Set if the button should be invokable (true), or greyed out (false). */
 | 
			
		||||
    public Description setEnabled(BooleanCondition enabled) {
 | 
			
		||||
      this.enabled = enabled;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
    UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
 | 
			
		||||
    if (dsc == null) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Set<GlobalOrPluginPermission> globalRequired;
 | 
			
		||||
    try {
 | 
			
		||||
      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