diff options
Diffstat (limited to 'java/com/google/gerrit/server/extensions/webui/UiActions.java')
-rw-r--r-- | java/com/google/gerrit/server/extensions/webui/UiActions.java | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/extensions/webui/UiActions.java b/java/com/google/gerrit/server/extensions/webui/UiActions.java new file mode 100644 index 0000000000..3ca2bdb93d --- /dev/null +++ b/java/com/google/gerrit/server/extensions/webui/UiActions.java @@ -0,0 +1,177 @@ +// Copyright (C) 2013 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.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.annotations.VisibleForTesting; +import com.google.common.base.Predicate; +import com.google.common.collect.Streams; +import com.google.common.flogger.FluentLogger; +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.registration.Extension; +import com.google.gerrit.extensions.registration.PluginName; +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.metrics.Description.Units; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer1; +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.Singleton; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@Singleton +public class UiActions { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + public static Predicate<UiAction.Description> enabled() { + return UiAction.Description::isEnabled; + } + + private final PermissionBackend permissionBackend; + private final Timer1<String> uiActionLatency; + + @Inject + UiActions(PermissionBackend permissionBackend, MetricMaker metricMaker) { + this.permissionBackend = permissionBackend; + this.uiActionLatency = + metricMaker.newTimer( + "http/server/rest_api/ui_actions/latency", + new com.google.gerrit.metrics.Description("Latency for RestView#getDescription calls") + .setCumulative() + .setUnit(Units.MILLISECONDS), + Field.ofString("view")); + } + + public <R extends RestResource> Iterable<UiAction.Description> from( + RestCollection<?, R> collection, R resource) { + return from(collection.views(), resource); + } + + public <R extends RestResource> Iterable<UiAction.Description> from( + DynamicMap<RestView<R>> views, R resource) { + 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()); + + evaluatePermissionBackendConditions(permissionBackend, conds); + + return descs.stream().filter(Description::isVisible).collect(toList()); + } + + @VisibleForTesting + static void evaluatePermissionBackendConditions( + PermissionBackend perm, List<PermissionBackendCondition> conds) { + Map<PermissionBackendCondition, PermissionBackendCondition> dedupedConds = + new HashMap<>(conds.size()); + for (PermissionBackendCondition cond : conds) { + dedupedConds.put(cond, cond); + } + perm.bulkEvaluateTest(dedupedConds.keySet()); + for (PermissionBackendCondition cond : conds) { + cond.set(dedupedConds.get(cond).value()); + } + } + + private static Iterable<PermissionBackendCondition> visibleCondition(Description u) { + return u.getVisibleCondition().reduce().children(PermissionBackendCondition.class); + } + + private static Iterable<PermissionBackendCondition> enabledCondition(Description u) { + return u.getEnabledCondition().reduce().children(PermissionBackendCondition.class); + } + + @Nullable + private <R extends RestResource> UiAction.Description describe( + Extension<RestView<R>> e, R resource) { + int d = e.getExportName().indexOf('.'); + if (d < 0) { + return null; + } + + RestView<R> view; + try { + view = e.getProvider().get(); + } catch (RuntimeException err) { + logger.atSevere().withCause(err).log( + "error creating view %s.%s", e.getPluginName(), e.getExportName()); + return null; + } + + if (!(view instanceof UiAction)) { + return null; + } + + String name = e.getExportName().substring(d + 1); + UiAction.Description dsc; + try (Timer1.Context ignored = uiActionLatency.start(name)) { + 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) { + logger.atSevere().withCause(err).log( + "exception testing view %s.%s", e.getPluginName(), e.getExportName()); + return null; + } + if (!globalRequired.isEmpty()) { + PermissionBackend.WithUser withUser = permissionBackend.currentUser(); + 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())); + } + + PrivateInternals_UiActionDescription.setMethod(dsc, e.getExportName().substring(0, d)); + PrivateInternals_UiActionDescription.setId( + dsc, PluginName.GERRIT.equals(e.getPluginName()) ? name : e.getPluginName() + '~' + name); + return dsc; + } +} |